diff --git a/.github/workflows/performance-contracts.yml b/.github/workflows/performance-contracts.yml index e8132e2aa79..2c453310526 100644 --- a/.github/workflows/performance-contracts.yml +++ b/.github/workflows/performance-contracts.yml @@ -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' diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 13bed76ed0c..8826fd393ed 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -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 diff --git a/.gitignore b/.gitignore index e9ca3822d36..b585bdd9e91 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/README.md b/README.md index 79d002770f7..bc1defaa9c6 100644 --- a/README.md +++ b/README.md @@ -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. - WeChat QR code for the Orca community - WeChat QR code for the Orca community - WeChat QR code for the Orca community + WeChat QR code for the Orca community - **Feedback & Ideas:** We ship fast. Missing something? [Request a new feature](https://github.com/stablyai/orca/issues). - **Privacy:** See the [privacy & telemetry docs](https://www.onorca.dev/docs/telemetry) for what anonymous usage data Orca collects and how to opt out. diff --git a/cloud/apps/relay-edge/spike/run-all-lifecycle.ts b/cloud/apps/relay-edge/spike/run-all-lifecycle.ts index 04de1249906..a4e00c9fc29 100644 --- a/cloud/apps/relay-edge/spike/run-all-lifecycle.ts +++ b/cloud/apps/relay-edge/spike/run-all-lifecycle.ts @@ -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((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((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}`) - } } diff --git a/cloud/apps/relay-edge/spike/run-all.ts b/cloud/apps/relay-edge/spike/run-all.ts index ce1d8a6ee09..49627e7cf86 100644 --- a/cloud/apps/relay-edge/spike/run-all.ts +++ b/cloud/apps/relay-edge/spike/run-all.ts @@ -100,11 +100,6 @@ async function runTests(ctx: Ctx): Promise { 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) diff --git a/config/ast-grep/rule-tests/__snapshots__/claude-chat-entrypoint-callers-snapshot.yml b/config/ast-grep/rule-tests/__snapshots__/claude-chat-entrypoint-callers-snapshot.yml new file mode 100644 index 00000000000..6bd5f856dd1 --- /dev/null +++ b/config/ast-grep/rule-tests/__snapshots__/claude-chat-entrypoint-callers-snapshot.yml @@ -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 diff --git a/config/ast-grep/rule-tests/__snapshots__/claude-chat-entrypoint-owner-snapshot.yml b/config/ast-grep/rule-tests/__snapshots__/claude-chat-entrypoint-owner-snapshot.yml new file mode 100644 index 00000000000..05f177c95df --- /dev/null +++ b/config/ast-grep/rule-tests/__snapshots__/claude-chat-entrypoint-owner-snapshot.yml @@ -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 diff --git a/config/ast-grep/rule-tests/__snapshots__/claude-entrypoint-key-owner-snapshot.yml b/config/ast-grep/rule-tests/__snapshots__/claude-entrypoint-key-owner-snapshot.yml new file mode 100644 index 00000000000..ea2790098fb --- /dev/null +++ b/config/ast-grep/rule-tests/__snapshots__/claude-entrypoint-key-owner-snapshot.yml @@ -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 diff --git a/config/ast-grep/rule-tests/claude-chat-entrypoint-callers-test.yml b/config/ast-grep/rule-tests/claude-chat-entrypoint-callers-test.yml new file mode 100644 index 00000000000..cca0fcf607c --- /dev/null +++ b/config/ast-grep/rule-tests/claude-chat-entrypoint-callers-test.yml @@ -0,0 +1,6 @@ +id: claude-chat-entrypoint-callers +valid: + - 'stripInheritedClaudeSessionEnv(env)' +invalid: + - 'withOrcaClaudeChatEntrypoint(env, platform)' + - "import { withOrcaClaudeChatEntrypoint as stamp } from './claude-chat-entrypoint'" diff --git a/config/ast-grep/rule-tests/claude-chat-entrypoint-owner-test.yml b/config/ast-grep/rule-tests/claude-chat-entrypoint-owner-test.yml new file mode 100644 index 00000000000..87a75f4a143 --- /dev/null +++ b/config/ast-grep/rule-tests/claude-chat-entrypoint-owner-test.yml @@ -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'" diff --git a/config/ast-grep/rule-tests/claude-entrypoint-key-owner-test.yml b/config/ast-grep/rule-tests/claude-entrypoint-key-owner-test.yml new file mode 100644 index 00000000000..c63b8b38063 --- /dev/null +++ b/config/ast-grep/rule-tests/claude-entrypoint-key-owner-test.yml @@ -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'" diff --git a/config/ast-grep/rules/claude-chat-entrypoint.yml b/config/ast-grep/rules/claude-chat-entrypoint.yml new file mode 100644 index 00000000000..cb56d1fa679 --- /dev/null +++ b/config/ast-grep/rules/claude-chat-entrypoint.yml @@ -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$' diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index 38058ad8ba8..60f4477543d 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -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 diff --git a/config/focus-setting-read-baseline.txt b/config/focus-setting-read-baseline.txt index 0a5203edd59..f907d5d4bef 100644 --- a/config/focus-setting-read-baseline.txt +++ b/config/focus-setting-read-baseline.txt @@ -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 diff --git a/config/host-list-render-baseline.txt b/config/host-list-render-baseline.txt index 09d3a029300..8cceee047a8 100644 --- a/config/host-list-render-baseline.txt +++ b/config/host-list-render-baseline.txt @@ -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 diff --git a/config/main-sync-fs-baseline.txt b/config/main-sync-fs-baseline.txt index f3905b1ae93..1026db7975b 100644 --- a/config/main-sync-fs-baseline.txt +++ b/config/main-sync-fs-baseline.txt @@ -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 diff --git a/config/mobile-localization-coverage-allowlist.json b/config/mobile-localization-coverage-allowlist.json index 265dd13cf28..113c65c34da 100644 --- a/config/mobile-localization-coverage-allowlist.json +++ b/config/mobile-localization-coverage-allowlist.json @@ -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, diff --git a/config/react-compiler-bailout-baseline.txt b/config/react-compiler-bailout-baseline.txt index 7eca34d1494..12aa339d614 100644 --- a/config/react-compiler-bailout-baseline.txt +++ b/config/react-compiler-bailout-baseline.txt @@ -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 diff --git a/config/recurring-timer-baseline.txt b/config/recurring-timer-baseline.txt index e48d5c80d66..6e17989d975 100644 --- a/config/recurring-timer-baseline.txt +++ b/config/recurring-timer-baseline.txt @@ -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 diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 0ad7088de9a..90b27e1136b 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -460,10 +460,10 @@ "oracle": "Audit2251 narrows agent-paste-draft.test.ts from 25 authored cases (26 literal cases) to four. Retained cases cover buffered readiness on PTY binding, separate spawn/composer lifetimes, real per-PTY paste/Enter serialization against a competing paste, and frame closure/no Enter after a rejected content chunk. Intentionally lost alarms include agent marker and fallback examples, direct-write failure/rejection, missing-PTY and Codex process-only timeout paths, exact nonprimary-PTY routing, full large-body chunk reconstruction, escaping/CRLF samples, internal laziness/yield work assertions, oversized preflight refusal, and exact configured delay/budget counts. No equivalent replacement, new execution, real PTY/provider delivery, rendered/native UI, deployment, measured performance or cross-platform result is claimed. Historical prior account (preserved verbatim): Mobile owner-layout policy retires whole tests and mixed registrations without equivalent replacement coverage. Retained callbacks and reduced selectors need fresh execution at their stated scope; prior measurements do not validate them. Historical oracle: Audit2003: Remove agent-prompt absence routing, throwing/live/missing-probe and cached-probe-count examples from the stale-leaf source. Controller-proven absence, unknown-liveness admission, obsolete-absence clearing and same-ID respawn fences remain at a supplied provider/row seam. Preserve the independent wire-codec receipt only in its original scope. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove partial-runtime terminal-send guard/status/floor/query projection and argument-inventory samples. Retained RPC viewport admission, authenticated-client spoof refusal, text admission and stale-binding fences remain bounded mock-boundary behavior. The unchanged wire-codec receipt remains valid only for its original command; it does not validate this reduced RPC selector. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1908 removes the complete prompt-presentation hook and literal interactive-protocol mapping suites and narrows the interactive card and prompt-card input suites. Lost alarms include replacement approval A settling after answered B; collapse/expand composer ownership and partial-answer presentation; identical new waits after an unmounted view; collapsed transcript re-reads; answered/dismissed status-backed persistence and retirement; and literal answer-to-PTY byte arrays. The retained stale-PTY acknowledgment, cancellation/answer retry, and answered-approval remount/rebind assertions remain meaningful but are not equivalent replacement coverage. No fresh reduced-selector result, physical provider, rendered/native-focus, mixed-version deployment, soak, or measured-budget validation is claimed. The unchanged wire-codec receipt remains valid only for its exact command. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Retained unchanged-selector receipts are the only current execution evidence. Reduced-source assertions await validation. Historical prior oracle: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): After acknowledged phone replies with stale host status, the exact card disappears and Send is enabled; unknown, refused or unverifiable-settlement replies retain enabled choices, while an older host's accepted reply dismisses like an acknowledged one; a same-occurrence view toggle retains pending acknowledgment and real replacements discard it. A slow 32 KiB SSH answer completes all chunks before Enter; cancellation preserves issued bytes but omits remaining groups. Paired settlement invokes the actual provider and lost callbacks remain unknown; older hosts deliver ordinary messages, launch and selector groups once and their accepted answers dismiss as before. An answered card stays hidden across a view remount; a collapsed card is a strip that frees the composer and expands back; an identical question in a new wait shows again; a composer send caught by a new card keeps its bubble without submitting or clearing the line under it. Audit2450 removes the MobileNativeChatView failure-banner callback-only example whose old banner is supplied unchanged by its props. Retain fatal-read gating and the ordering test that preserves a new delivery notice. The removed false-send/old-banner combination has no equivalent replacement; prior raw receipts and budgets retain their original source scope. No new soak, platform, performance or red/green claim.", "commands": [ "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/prompts/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/prompts/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/surfaces/integration/NativeChatResolvedView.prompt-card-composer.test.tsx src/renderer/src/components/native-chat/conversation/turns/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/runtime/hosted-terminals/inspection/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-session/messaging/paste/agent-paste-draft.test.ts src/renderer/src/lib/agent-session/messaging/paste/agent-paste-draft-submit-retry.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts src/main/runtime/terminal/input/runtime-terminal-writer.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/runtime/terminal/input/terminal-send-stale-leaf-liveness.test.ts --maxWorkers=2", - "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/mobile-prompt-controller.test.ts --maxWorkers=2", + "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts mobile/src/session/native-chat/input/mobile-prompt-controller.test.ts --maxWorkers=2", "ORCA_BACKGROUND_LAUNCH=1 pnpm test tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts --maxWorkers=1", "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/components/native-chat/prompts/NativeChatInteractiveCard.test.tsx src/renderer/src/components/native-chat/prompts/NativeChatResolvedView.prompt-card-input.test.tsx src/renderer/src/components/native-chat/conversation/turns/NativeChatResolvedView.turn-status.test.tsx src/renderer/src/runtime/hosted-terminals/inspection/runtime-terminal-inspection.test.ts src/renderer/src/lib/agent-session/messaging/paste/agent-paste-draft.test.ts src/renderer/src/lib/agent-session/messaging/paste/agent-paste-draft-submit-retry.test.ts src/main/ipc/pty/ipc/write-input-user-input.test.ts src/main/ipc/pty/pty-write-ipc-validation.test.ts --maxWorkers=2", - "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts --maxWorkers=2" + "ORCA_BACKGROUND_LAUNCH=1 mobile/node_modules/.bin/vitest run --config mobile/vitest.config.ts mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts --maxWorkers=2" ], "testFiles": [ "src/renderer/src/components/native-chat/prompts/NativeChatInteractiveCard.test.tsx", @@ -478,9 +478,9 @@ "src/main/runtime/terminal/input/runtime-terminal-writer.test.ts", "src/main/runtime/rpc/terminal-send.test.ts", "src/main/runtime/terminal/input/terminal-send-stale-leaf-liveness.test.ts", - "mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts", + "mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts", - "mobile/src/session/mobile-prompt-controller.test.ts", + "mobile/src/session/native-chat/input/mobile-prompt-controller.test.ts", "tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts" ], "assertionRefs": [ @@ -491,18 +491,12 @@ "sends only one cancellation, can cancel an answer, and permits retry after unknown" ] }, + { - "file": "mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts", - "assertions": [ - "a late answer cannot reshow an answered replacement occurrence", - "a late answer cannot dismiss a replacement session" - ] - }, - { - "file": "mobile/src/session/mobile-prompt-controller.test.ts", + "file": "mobile/src/session/native-chat/input/mobile-prompt-controller.test.ts", "assertions": [ "retains permission choices on %s", - "shows a collapsed prompt again as a new wait", + "keeps an acknowledged Deny hidden after the chat screen remounts on a lingering status", "settles the terminal answer for Q1 after Q2 arrives without hiding Q2", "keeps structured answer controls after a thrown send" @@ -1095,7 +1089,7 @@ "id": "settings.terminal-section-lifetime", "title": "Terminal settings search preserves matching section state", "maturity": "experimental", - "protection": "partial", + "protection": "none", "owner": "terminal-runtime", "layer": "renderer-unit", "surfaces": [ @@ -1105,66 +1099,43 @@ ], "platforms": ["macos", "linux", "windows"], "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], - "coveredPlatforms": ["macos"], + "coveredPlatforms": [], "coveredProviders": [], - "coverageNotes": "Actual TerminalPane, ManageSessionsSection, health listener and scrollback form with mocked store/provider and display/action boundaries. Unix/Windows and Mac-client/Windows-host flags simulated; no physical provider, process-stop or native input coverage.", + "coverageNotes": "Module-layout wave1 retires the terminal-section lifetime owning suite and its complete dated receipt. The remaining TCC attribution notice suite does not establish the search-lifetime invariant. No current protection, replacement coverage, native/provider execution or fresh evidence is claimed. Historical scope before retirement: Actual TerminalPane, ManageSessionsSection, health listener and scrollback form with mocked store/provider and display/action boundaries. Unix/Windows and Mac-client/Windows-host flags simulated; no physical provider, process-stop or native input coverage.", "motivatingLinks": [ "https://github.com/stablyai/orca/blob/main/src/renderer/src/components/settings/terminal/TerminalPane.tsx" ], "invariant": "A matching settings section retains its state when preceding sections are filtered away; a section removed from search releases its owner and refreshes when shown again. A retained section still refreshes when another surface invalidates the session inventory. Diagnostic inventory never becomes authority for process death.", - "oracle": "Across 15 matched search edits the matched section keeps the same DOM node and the inventory/health read counts do not grow, instead of six reads each. A foreign inventory invalidation still reads once on the retained section and not after unmount. A draft and pending target survive matched search; actual hide/reopen reloads, explicit Refresh still reads, and hidden/unmounted health listeners do not fire.", - "commands": [ - "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx" - ], - "testFiles": [ - "src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx", - "src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx" - ], - "assertionRefs": [ - { - "file": "src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx", - "assertions": [ - "refreshes a retained section when another surface invalidates the inventory", - "refreshes explicitly, reloads genuinely hidden sections, and removes focus listeners", - "preserves an uncommitted scrollback draft while its section remains visible", - "retains a pending action target only while its section remains visible" - ] - } - ], - "evidenceRuns": [ - { - "date": "2026-09-26", - "runner": "local", - "platform": "macos", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/TerminalTccAttributionNotice.test.tsx", - "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx", - "result": "passed", - "durationSeconds": 1.48, - "summary": "12 tests/2 suites pass after the shared SettingsSectionStack adoption and the inventory-invalidation subscription. Read-count oracles restated as node identity plus non-growing read counts; original main fails all three simulated platform cases, the draft, the pending target and the foreign-invalidation refresh." - } - ], + "oracle": "Target oracle, currently unprotected after whole-suite retirement: Across 15 matched search edits the matched section keeps the same DOM node and the inventory/health read counts do not grow, instead of six reads each. A foreign inventory invalidation still reads once on the retained section and not after unmount. A draft and pending target survive matched search; actual hide/reopen reloads, explicit Refresh still reads, and hidden/unmounted health listeners do not fire.", + "commands": [], + "testFiles": [], + "assertionRefs": [], + "evidenceRuns": [], "runtimeBudget": { "p95Seconds": 10, "scope": "Focused renderer-unit suites, p95 not established." }, "flakeHistory": { "status": "not-started", - "evidence": "Author and independent local evidence only; no CI soak." + "evidence": "No current search-lifetime suite or soak evidence remains. Historical statement before retirement: Author and independent local evidence only; no CI soak." }, "redGreenEvidence": { - "status": "complete", - "evidence": "Original new suite fails all three simulated platform read-count cases, draft and pending target; one cleanup/refresh control passes. Candidate6pass. Independent scratch5pass/original4fail1control." + "status": "missing", + "evidence": "The owning search-lifetime suite was retired whole. Its historical red/green result does not validate the remaining unrelated TCC attribution notice suite." }, "performanceBudget": { "required": true, - "evidence": "Actual child inventory and health reads 6 to 1 across 15 matched search edits. Fixed small section filter/map now lives in the shared SettingsSectionStack; no new polling, cache or subprocess. One added in-renderer invalidation listener replaces the incidental search-driven refresh, so a retained section stays as fresh as the signal every other inventory reader already uses. No packaged CPU/frame/heap claim." + "evidence": "Historical budget account only; no current selector or fresh performance measurement: Actual child inventory and health reads 6 to 1 across 15 matched search edits. Fixed small section filter/map now lives in the shared SettingsSectionStack; no new polling, cache or subprocess. One added in-renderer invalidation listener replaces the incidental search-driven refresh, so a retained section stays as fresh as the signal every other inventory reader already uses. No packaged CPU/frame/heap claim." }, "knownGaps": [ "CI soak pending.", "No physical local/daemon/SSH/WSL/remote-runtime provider, native Linux/Windows or process-stop behavior exercised; routing, PTY ownership, commands and wire formats unchanged.", "Matched searches keep the loaded snapshot until existing Refresh, completed mutation, a foreign inventory invalidation or genuine hide/reopen; health also refreshes on focus/revision.", "Pending action target and drafts remain while their parent section matches, including individual controls hidden inside a retained section.", - "No packaged app latency, heap, real focus or pending destructive-mutation completion evidence. Hidden Electron component visual proof is separate from the gate." + "No packaged app latency, heap, real focus or pending destructive-mutation completion evidence. Hidden Electron component visual proof is separate from the gate.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx. Lost alarms: refreshes a retained section when another surface invalidates the inventory; refreshes explicitly, reloads genuinely hidden sections, and removes focus listeners; preserves an uncommitted scrollback draft while its section remains visible; retains a pending action target only while its section remains visible. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope.", + "Module-layout wave1 retires this complete historical receipt because its explicit current selector was reduced. It does not validate the reduced suite. Original receipt verbatim:\n{\n \"date\": \"2026-09-26\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/TerminalTccAttributionNotice.test.tsx\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.48,\n \"summary\": \"12 tests/2 suites pass after the shared SettingsSectionStack adoption and the inventory-invalidation subscription. Read-count oracles restated as node identity plus non-growing read counts; original main fails all three simulated platform cases, the draft, the pending target and the foreign-invalidation refresh.\"\n }", + "Module-layout wave1 historical red/green statement before owning-suite retirement, retained verbatim without current validation credit: {\"status\":\"complete\",\"evidence\":\"Original new suite fails all three simulated platform read-count cases, draft and pending target; one cleanup/refresh control passes. Candidate6pass. Independent scratch5pass/original4fail1control.\"}" ], "promotionCriteria": [ "Retain count, real draft, pending target and cleanup oracles; obtain stable CI history and relevant full-app/provider evidence before promotion." @@ -1649,13 +1620,13 @@ "invariant": "Automatic hibernation must retain two stable confirmations and fresh execution-host evidence before shutdown. Skipping a workspace with no completed agent must not authorize a newly completed pane using stale client PTYs.", "oracle": "Audit2058 intentionally retires the standalone visibility subscription/callback-count alarm without replacement. Audit2310 additionally retires two pane-age getter-map expiry/pruning samples without equivalent replacement. The reduced five-source hibernation selector was freshly run on Linux:52 tests pass. The earlier 54-test receipt is archived verbatim in knownGaps and does not validate the reduced source. Its prior macOS execution receipt/current-command association is archived verbatim in knownGaps and is not current execution evidence for the reduced selector. Current covered platform is Linux; remote replies remain scripted, with no live SSH/WSL/native provider or p95 claim. Historical prior account (preserved verbatim): Working and waiting agents across remote workspaces must not be shut down. A skipped workspace completing while another inventory awaits remains ineligible until two later host-confirmed passes, and so does a workspace that only becomes runtime-owned while an inventory is outstanding — a pass carrying no host evidence for a workspace never counts as one of its two confirmations. Folder and git workspaces hibernate the exact host PTY; rejected/truncated inventories and intervening input/output or state changes block shutdown.", "commands": [ - "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-planner.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-pane-age.test.ts src/renderer/src/lib/foreground-terminal-tabs.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-runtime-liveness-race.test.ts --maxWorkers=2" + "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-planner.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-pane-age.test.ts src/renderer/src/components/terminal-pane/parking/retention/foreground-terminal-tabs.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-runtime-liveness-race.test.ts --maxWorkers=2" ], "testFiles": [ "src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.test.ts", "src/renderer/src/lib/agent-session/hibernation/agent-hibernation-planner.test.ts", "src/renderer/src/lib/agent-session/hibernation/agent-hibernation-pane-age.test.ts", - "src/renderer/src/lib/foreground-terminal-tabs.test.ts", + "src/renderer/src/components/terminal-pane/parking/retention/foreground-terminal-tabs.test.ts", "src/renderer/src/lib/agent-session/hibernation/agent-hibernation-runtime-liveness-race.test.ts" ], "assertionRefs": [ @@ -1682,6 +1653,7 @@ "runner": "local", "platform": "linux", "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-planner.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-pane-age.test.ts src/renderer/src/lib/foreground-terminal-tabs.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-runtime-liveness-race.test.ts --maxWorkers=2", + "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-planner.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-pane-age.test.ts src/renderer/src/components/terminal-pane/parking/retention/foreground-terminal-tabs.test.ts src/renderer/src/lib/agent-session/hibernation/agent-hibernation-runtime-liveness-race.test.ts --maxWorkers=2", "result": "passed", "durationSeconds": 5.426, "summary": "52 tests across the five coordinator/planner/age/foreground/liveness-race files passed on public parent60aee85d9182f602af45cf5f6facae7257670061 plus the audit2310 test-removal candidate (source tree da2595af5391e9fa07a924ac69afede47ad31906). Includes the reduced two-case pane-age suite. Scripted runtime contracts; no live SSH/native WSL, soak or measured p95 claim." @@ -1796,7 +1768,7 @@ "Complete CI soak with zero unexplained flakes and preserve the observable oracle." ], "demotionRule": "Keep experimental until CI soak; investigate failures without relaxing fidelity or resource-count assertions.", - "coverageNotes": "Audit2365 further reduces the supporting renderer agent-status suites. It removes the title-batch publication bound and generated-title sample, ordered acceptance/publication and nested/reentrant scheduling ledgers, Jcode manually assembled session-start sample, freshness-tick epoch example, repeated quit-capture identity and batched-clear examples, and sequential/batch retained-cap examples. Those alarms are no longer present; the following historical account must not be read as current coverage for them. No equivalent replacement or fresh execution evidence is claimed. Audit1400 intentionally deletes or reduces the listed sources. No fresh execution result validates their reduced selectors. Historical coverage account: Actual title-sync subscriber and Zustand publications are covered on macOS. Existing fixtures include SSH/runtime owner invalidation and folder workspaces; no live remote transport was launched. The guard has no platform branches, remote execution, liveness verdict or wire change. The test-value audit retires sequential/batch same-reducer replay, isolated capture-field examples, provider permutations and mock scheduling ledgers from the supporting renderer suites. The title batch publication bound remains, as do checkpoint precedence and real shutdown capture checks. This supplies no replacement evidence for the retired alarms; historical executed commands, counts and results remain historical. The test-value audit also retires isolated freshness-epoch, timestamp/attribution-copy, suppressor/reference and retained below-cap/overwrite examples from agent-status.test.ts. The original title-sync assertion references remain; retained stale-snapshot, cleanup and hard-bound checks are not equivalent replacements for these lost alarms. Historical commands, counts and results remain historical. Audit1400: src/renderer/src/lib/ai-vault-tab-title-sync-inputs.test.ts reduced. Loses one scan/count registration; retains owner invalidation and actual stale-reference release. No equivalent replacement coverage is claimed.", + "coverageNotes": "Audit2365 further reduces the supporting renderer agent-status suites. It removes the title-batch publication bound and generated-title sample, ordered acceptance/publication and nested/reentrant scheduling ledgers, Jcode manually assembled session-start sample, freshness-tick epoch example, repeated quit-capture identity and batched-clear examples, and sequential/batch retained-cap examples. Those alarms are no longer present; the following historical account must not be read as current coverage for them. No equivalent replacement or fresh execution evidence is claimed. Audit1400 intentionally deletes or reduces the listed sources. No fresh execution result validates their reduced selectors. Historical coverage account: Actual title-sync subscriber and Zustand publications are covered on macOS. Existing fixtures include SSH/runtime owner invalidation and folder workspaces; no live remote transport was launched. The guard has no platform branches, remote execution, liveness verdict or wire change. The test-value audit retires sequential/batch same-reducer replay, isolated capture-field examples, provider permutations and mock scheduling ledgers from the supporting renderer suites. The title batch publication bound remains, as do checkpoint precedence and real shutdown capture checks. This supplies no replacement evidence for the retired alarms; historical executed commands, counts and results remain historical. The test-value audit also retires isolated freshness-epoch, timestamp/attribution-copy, suppressor/reference and retained below-cap/overwrite examples from agent-status.test.ts. The original title-sync assertion references remain; retained stale-snapshot, cleanup and hard-bound checks are not equivalent replacements for these lost alarms. Historical commands, counts and results remain historical. Audit1400: src/renderer/src/lib/ai-vault-tab-title-sync-inputs.test.ts reduced. Loses one scan/count registration; retains owner invalidation and actual stale-reference release. No equivalent replacement coverage is claimed. Audit3146 incorporates source3132: seven authored/nine expanded provider-session lifecycle and shutdown examples are deliberately retired without replacement, including completed-session remote/manual-shutdown promotion and quit/done precedence over heartbeat. Captured-record conditional replacement and clockless Codex owner/root turn-fact isolation remain; they do not replace these alarms or establish actual shutdown/resume coverage.", "invariant": "Title-sync publications must not enumerate unchanged session collections, while title identity, provider availability, active pane, stored title and effective execution-host changes retain their previous invalidation decisions.", "oracle": "No current automated execution oracle is claimed for this gate after Audit1400 retired changed-source receipts. Historical oracle account: Fifty live heartbeat publications with 500 retained and 500 sleeping records cause zero unchanged-map enumerations and no extra scheduled reconciliations. Provider identity, additions/removals, agent/pane ownership, effective host, active pane and stored title changes still invalidate.", "commands": [], @@ -4230,10 +4202,10 @@ "providers": ["remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": ["remote-runtime"], - "coverageNotes": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Deterministic main-IPC contract tests cover disconnect-driven close delivery, exactly-once close, per-subscription teardown isolation against a failing socket close and a throwing liveness probe, containment of a throwing renderer send on the unguarded host-close path, and continued suppression of stale payloads from a retired transport. A headed paired-server journey (real Orca host plus a separate paired Orca desktop client) covers hidden-but-mounted reveal, cold-parked reveal, and cold-parked reveal across a disconnect/reconnect. Live Linux and Windows paired-server evidence and real sleep/wake transport loss remain uncollected.", + "coverageNotes": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Deterministic main-IPC contract tests cover disconnect-driven close delivery, exactly-once close, per-subscription teardown isolation against a failing socket close and a throwing liveness probe, containment of a throwing renderer send on the unguarded host-close path, and continued suppression of stale payloads from a retired transport. A headed paired-server journey (real Orca host plus a separate paired Orca desktop client) covers hidden-but-mounted reveal, cold-parked reveal, and cold-parked reveal across a disconnect/reconnect. Live Linux and Windows paired-server evidence and real sleep/wake transport loss remain uncollected.", "motivatingLinks": ["tests/e2e/paired-remote-terminal-parked-reveal-interactivity.spec.ts"], "invariant": "Every renderer-held runtime subscription receives exactly one terminal close event when its transport is retired, including when the retirement advanced the transport generation first, and a single failing teardown never abandons that environment's remaining subscriptions nor escapes into the transport that reported the close. Payload frames from a retired transport stay suppressed. A revealed remote terminal therefore reattaches over a live multiplex connection: its buffer restores, typed input reaches the host PTY, the echo paints without a tab flip, and the PTY converges on the revealed pane grid.", - "oracle": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): The main IPC contract test subscribes terminal.multiplex through the real handler, disconnects the environment, and asserts the renderer received exactly one {type: close} subscription event. Two isolation tests subscribe a second stream to the same environment and make the first one fail -- in its socket close, and in the liveness probe inside notifyClosed -- then assert the disconnect does not throw, both transports closed, and every close the renderer could still receive was delivered. A third drives a host-initiated close through the transport callback, which is the one notifyClosed call site with no surrounding guard, with a renderer send that throws, and asserts it cannot escape into the WebSocket close handler. A fourth test asserts that after retirement a late response frame is not forwarded and a late transport close does not re-send. The paired-server journey runs three reveal scenarios against one real host and one real paired desktop client, and for each records buffer restore, host-side receipt of the typed marker through an out-of-band host sink file, live paint without a tab flip, and PTY-versus-pane grid convergence.", + "oracle": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): The main IPC contract test subscribes terminal.multiplex through the real handler, disconnects the environment, and asserts the renderer received exactly one {type: close} subscription event. Two isolation tests subscribe a second stream to the same environment and make the first one fail -- in its socket close, and in the liveness probe inside notifyClosed -- then assert the disconnect does not throw, both transports closed, and every close the renderer could still receive was delivered. A third drives a host-initiated close through the transport callback, which is the one notifyClosed call site with no surrounding guard, with a renderer send that throws, and asserts it cannot escape into the WebSocket close handler. A fourth test asserts that after retirement a late response frame is not forwarded and a late transport close does not re-send. The paired-server journey runs three reveal scenarios against one real host and one real paired desktop client, and for each records buffer restore, host-side receipt of the typed marker through an out-of-band host sink file, live paint without a tab flip, and PTY-versus-pane grid convergence.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts", "pnpm exec playwright test tests/e2e/paired-remote-terminal-parked-reveal-interactivity.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1" @@ -4323,7 +4295,18 @@ "Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed.", "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed.", "Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation.", - "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed." + "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed.", + "Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked.", + "Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed.", + "Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms.", + "Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery.", + "Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction.", + "Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed.", + "Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed.", + "Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed.", + "Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed.", + "Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed.", + "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired." ], "demotionRule": "Demote if a retired runtime transport can leave a renderer subscription without a close event, if a close is delivered more than once, if one failing teardown strands its sibling subscriptions, if payload frames from a retired transport reach the renderer, or if a revealed remote terminal can stay blank or reject input after a reconnect." }, @@ -4467,7 +4450,7 @@ "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/guest-lifecycle/manager/viewport/browser-manager-annotation-bridge.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/document-preview/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/link-actions/LinkActionPopover.test.tsx src/renderer/src/runtime/sync-runtime-graph/sync-runtime-graph-editor-diff-tabs.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/browser/guest-lifecycle/manager/viewport/browser-manager-annotation-bridge.test.ts src/main/browser/backends/offscreen-browser-backend-lifecycle.test.ts src/main/browser/document-preview/doc-preview-guest-policy.test.ts src/shared/document-preview/doc-preview-scheme.test.ts src/renderer/src/components/browser-pane/workspace-doc/use-doc-preview-guest-tools.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser/pages/browser-page-conversion.test.ts src/renderer/src/runtime/sync-runtime-graph/sync-runtime-graph-conversion-publish.test.ts src/renderer/src/lib/workspace-doc-address-input.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/browser/pages/browser-page-conversion.test.ts src/renderer/src/runtime/sync-runtime-graph/sync-runtime-graph-conversion-publish.test.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.toolbar.test.tsx", "SKIP_BUILD=1 ORCA_E2E_WEB_CLIENT=1 ORCA_E2E_FORCE_HEADFUL=1 pnpm exec playwright test tests/e2e/paired-preview-address-bar-convergence.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1 --reporter=list", "SKIP_BUILD=1 ORCA_E2E_WEB_CLIENT=1 ORCA_E2E_FORCE_HEADFUL=1 pnpm exec playwright test tests/e2e/paired-remote-html-preview-local-render.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1 --reporter=line", "SKIP_BUILD=1 ORCA_E2E_WEB_CLIENT=1 ORCA_E2E_FORCE_HEADFUL=1 pnpm exec playwright test tests/e2e/paired-remote-html-preview-local-render.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1 --reporter=line --repeat-each=3", @@ -4500,7 +4483,6 @@ "src/renderer/src/runtime/sync-runtime-graph/sync-runtime-graph-editor-diff-tabs.test.ts", "src/renderer/src/store/slices/browser/pages/browser-page-conversion.test.ts", "src/renderer/src/runtime/sync-runtime-graph/sync-runtime-graph-conversion-publish.test.ts", - "src/renderer/src/lib/workspace-doc-address-input.test.ts", "tests/e2e/paired-remote-html-preview-local-render.spec.ts", "tests/e2e/paired-preview-address-bar-convergence.spec.ts", "tests/e2e/paired-browser-create-navigation-deadline.spec.ts", @@ -4954,7 +4936,8 @@ "D01 candidate audit retires this complete dated receipt because a named test source changes in the candidate overlay. It supplies no new execution or equivalent-coverage claim. Original receipt verbatim:\n{\n \"date\": \"2026-08-27\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.54,\n \"summary\": \"14/14 on the reader-facing failure suite at its relocated path. This and the red run below replace the 2026-08-26 pair recorded against the pre-relocation path (10/10 green, 3/10 red), re-measured today so the entry/subresource split keeps a red half rather than only a green one.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx\"\n }", "D01 final candidate whole-registration policy removes retired module selectors and obsolete assertion descriptions prospectively. Existing unaffected receipts retain their original scope; reduced selectors have no new execution or equivalent replacement claim. Retired named modules: [\"src/preload/doc-preview-link-interception.test.ts\",\"src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.failure-message.test.tsx\"]. Retired compound assertion refs, preserved as historical data: [{\"file\":\"src/renderer/src/components/browser-pane/workspace-doc/doc-preview-external-link-confirmation.test.ts\",\"assertions\":[\"an external preview link shows the reader its exact destination and becomes a browser tab only after confirmation\",\"a declined confirmation opens nothing, and a confirmed tab that is refused or fails outright surfaces instead of being dropped\"]}]", "Audit2452 removes the whole fake-effect browser-close routing suite. Active selectors and assertion references no longer select it. Its requested-worktree default close, sibling-page close and explicit missing-page typed reply alarms are intentionally retired without equivalent replacement. Prior dated receipts remain unchanged and scoped to their original sources; no fresh execution of the reduced gate, rendered browser, live remote, mobile or cross-platform validation is claimed. Retired assertion reference: {\"file\":\"src/renderer/src/hooks/useIpcEvents-close-routing-browser-pages.test.ts\",\"assertions\":[\"closing an absent renderer page returns browser_tab_not_found instead of an untyped failure\"]}", - "Audit3108: doc-preview-grants.test.ts now retains only the stale grant-rejection race: a late rejected grant request must not evict a newer grant registry entry, witnessed by later revocation of grant-2. Retire stable minted-location and outside-workspace POSIX/Windows request-projection alarms without replacement. The direct nonempty grant selector remains unchanged; no real grant-server/file authorization, SSH/HTML delivery or native-focus result is claimed." + "Audit3108: doc-preview-grants.test.ts now retains only the stale grant-rejection race: a late rejected grant request must not evict a newer grant registry entry, witnessed by later revocation of grant-2. Retire stable minted-location and outside-workspace POSIX/Windows request-projection alarms without replacement. The direct nonempty grant selector remains unchanged; no real grant-server/file authorization, SSH/HTML delivery or native-focus result is claimed.", + "File/document owner layout retires the complete workspace-doc-address-input unit suite because its sole mixed case asserts internal catalog-enumeration work. The current-workspace HTML-resolution alarm is lost; no equivalent replacement coverage or pass for the reduced unit selector is claimed. Historical receipts and all remaining selectors/assertions retain their original scope." ], "demotionRule": "Demote if a paired HTML preview creates a browser workspace on either side, renders from anything other than the orca-preview scheme, serves a read the host reported as truncated or over-cap, serves a document without the self-only content security policy, lets the preview session carry an off-machine request, reads an SSH path that canonicalizes outside its grant root, steals focus from the source editor when it opens, removes unrelated editor or terminal state on close, hides a document that rendered because one subresource was refused, leaves a grant readable after the tab or renderer that owns it is gone, mints one for a sender that is not the trusted renderer, opens a browser tab for a navigation the document started with no one at the keyboard, or lets the guest gather an ICE candidate; or if a paired browser creation this gate still owns produces duplicate client or host identities or loses its requested split." }, @@ -5852,14 +5835,12 @@ "invariant": "Append-only external file growth changes only Monaco's model suffix, retaining the viewport, selection, Find state, and renderer liveness above the append point; read-only live tails do not create undo history, while editable external updates remain undoable and arbitrary rewrites continue to replace the model content.", "oracle": "Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit1709 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Focused tests assert one post-mount content owner and actual lifecycle remount ordering across retained path models. Real-Monaco tests cover undo behavior for read-only live tails versus editable files. Standalone Node timing and private undo-retention budgets at 9 and 50 MiB were retired by audit1677 and provide no current alarm. The Electron scenario compares legacy setValue with watcher appends, checking visible ranges, selection, Find state, scroll offset, non-undoability, renderer survival and memory budgets. Mock-only edit-shape assertions were retired during test-value audit.", "commands": [ - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/editor/monaco-content-sync.undo-history.test.ts", "node --expose-gc ./node_modules/vitest/vitest.mjs bench --pool=threads", "pnpm run test:e2e -- tests/e2e/agent-session-log-tail-stability.spec.ts --workers=1" ], "testFiles": [ "src/renderer/src/components/editor/monaco-content-sync.undo-history.test.ts", - "src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx", "tests/e2e/agent-session-log-tail-stability.spec.ts" ], "assertionRefs": [ @@ -5869,12 +5850,6 @@ "a real Monaco read-only live-tail append leaves canUndo false while an ordinary external update remains undoable" ] }, - { - "file": "src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx", - "assertions": [ - "a same-pane path switch unmounts the prior outer Monaco before real mount reconciliation refreshes the stale target; the prior retained model content and undo sentinel remain untouched" - ] - }, { "file": "tests/e2e/agent-session-log-tail-stability.spec.ts", "assertions": [ @@ -5925,7 +5900,8 @@ "Audit1677 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded.", "No live Windows or Linux View Log evidence yet.", "Historical retired benchmark receipt (not active evidence): {\"date\":\"2026-07-12\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"node --expose-gc ./node_modules/vitest/vitest.mjs bench src/renderer/src/components/editor/monaco-content-sync.bench.ts --pool=threads\",\"result\":\"passed\",\"durationSeconds\":75,\"summary\":\"Forced-GC, settled, alternating fresh-model Monaco p95: 9 MiB append 4.02-5.51ms versus replacement 81.42-83.76ms; 50 MiB append 22.72-26.60ms versus replacement 445.11-449.21ms.\"}", - "Historical retired benchmark receipt (not active evidence): {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"node --expose-gc ./node_modules/vitest/vitest.mjs bench src/renderer/src/components/editor/monaco-content-sync.undo-retention.bench.ts --pool=threads\",\"result\":\"passed\",\"durationSeconds\":5,\"summary\":\"The undoable 50 MiB control retained 104,858,630 undo-service bytes and 104,857,790 ArrayBuffer bytes; the read-only live-tail arm retained zero of both and remained non-undoable.\"}" + "Historical retired benchmark receipt (not active evidence): {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"node --expose-gc ./node_modules/vitest/vitest.mjs bench src/renderer/src/components/editor/monaco-content-sync.undo-retention.bench.ts --pool=threads\",\"result\":\"passed\",\"durationSeconds\":5,\"summary\":\"The undoable 50 MiB control retained 104,858,630 undo-service bytes and 104,857,790 ArrayBuffer bytes; the read-only live-tail arm retained zero of both and remained non-undoable.\"}", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx. Lost alarms: unmounts the prior path before reconciling a stale retained target model; keeps the diff editor mounted when a save updates the modified content; still remounts the diff editor for an explicit reload request. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Quarantine the Electron scenario if it flakes without a product or harness bug; demote if viewport/Find drift, renderer loss, or Electron memory retention exceeds the registered budgets." }, @@ -6770,7 +6746,8 @@ "Module layout policy correction: retire the two remaining worktree-ps-agent-row-dismissal.spec.ts registrations. The remint/dismissal/exit case computed expected snapshotVersion increments from a production snapshot and used real-clock vi.waitFor; the legacy OSC case derived its expected paneKey from the production layout-owner switch. The runtime registrar no longer imports this file. These public projection scenarios have no replacement coverage or fresh execution claimed; all historical receipts and earlier loss notes retain their original source scope.", "Audit2153: Intentionally retire the ordinary fake-create event-forwarding registration from src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts. The two retained callbacks prove encrypted fanout/reconnect and physical connection multiplexing under a fake business runtime; neither establishes close-intent authorization, actual terminal sleep, or equivalent replacement for the removed forwarding smoke. Remove this unrelated current close-safety selector. Existing assertionRefs and dated evidenceRuns remain byte-identical and scoped to their own source/commands. No fresh execution or platform/provider coverage is claimed.", "Audit2153 historical original commands item, preserved verbatim; retired selector is not active execution evidence: \"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/construction/orca-runtime.test.ts src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts src/main/runtime/rpc/e2ee-channel.test.ts src/main/runtime/rpc/e2ee-channel-v2.test.ts src/main/runtime/rpc/mobile-socket-wiring.test.ts src/main/runtime/rpc/runtime-client-capabilities.test.ts src/shared/remote-runtime/remote-runtime-client.test.ts src/shared/remote-runtime/requests/remote-runtime-request-connection.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts src/cli/runtime/websocket-transport.test.ts src/renderer/src/web/web-runtime-client.test.ts src/renderer/src/components/terminal/terminal-tab-actions.test.ts src/renderer/src/components/terminal/terminal-close-incarnation.test.ts src/renderer/src/components/terminal-pane/parking/output-watchers/terminal-parked-tab-watchers.test.ts\"", - "Audit2153 historical original testFiles item, preserved verbatim; retired selector is not active execution evidence: \"src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts\"" + "Audit2153 historical original testFiles item, preserved verbatim; retired selector is not active execution evidence: \"src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts\"", + "Terminal/runtime defining-owner cleanup retires complete parking timer, lifecycle, mocked close/disposal and render/flip/mount work-count cases under repository test policy. The retained watcher source has only two literal-oracle coverage decisions; the retained cold-parking hook source has only the layout-PTY exemption invalidation case. Lost alarms include cooldown/measure/pin wakeups, split exit/close attribution and cancellation, watcher disposal/restart, flood timer rearm, worktree verdict damping, and destroyed-transport cold restore. No equivalent replacement coverage or execution pass for the reduced cases is claimed; prior dated receipts retain their original scope." ], "demotionRule": "Keep experimental or quarantine if reconnect emits session-killed for a live canary, an exact root/descendant dies, an old server receives fallback destructive close, cleanup leaks a fixture process/pipe, or the gate flakes without a proven harness defect." }, @@ -7095,7 +7072,8 @@ "Audit1396: src/renderer/src/components/terminal-pane/pty-connection-pending-spawn.spec.ts reduced. Lose standalone debug-state retention/log silence alarm. React StrictMode is not mounted; bindings manually simulate remount over mocked transport. No equivalent replacement coverage is claimed.", "Audit1396 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-08-12\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-connection-fresh-spawn-guards.test.ts --reporter=dot\",\n \"result\": \"passed\",\n \"durationSeconds\": 20,\n \"summary\": \"The full PTY connection suite passed the independent Floating native-host transport oracle.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-connection-spawn-and-startup.test.ts --reporter=dot\"\n }", "Audit1396 historical platform/provider scope before source changes: {\"coveredPlatforms\":[\"macos\",\"windows\"],\"coveredProviders\":[\"local\",\"wsl\"]}. Remaining provider scope applies only to unchanged-source receipts.", - "Audit2371 reduces repos-project-group-create-race.test.ts by removing the two-host generic failed-create-response/no-op example. Its alarm that a failed create reply leaves an intervening refreshed state untouched is intentionally lost. The surviving refreshed-field and original-owner races do not supply equivalent replacement coverage. No fresh execution or red/green evidence validates the reduced source; unrelated physical Windows/WSL receipt retains its original command and scope." + "Audit2371 reduces repos-project-group-create-race.test.ts by removing the two-host generic failed-create-response/no-op example. Its alarm that a failed create reply leaves an intervening refreshed state untouched is intentionally lost. The surviving refreshed-field and original-owner races do not supply equivalent replacement coverage. No fresh execution or red/green evidence validates the reduced source; unrelated physical Windows/WSL receipt retains its original command and scope.", + "Finite R02 ownership closure retires all three cases from pty-connection/spawn/pty-connection-fresh-spawn-guards.spec.ts and its empty aggregate registration. Intentionally lost alarms: deletion-phase fresh-spawn admission; wrapped worktree-removal fence error suppression; classified typing versus synthetic query/mouse/alternate-wheel replay input, normal-arrow preservation, deferred input provenance and post-replay restoration. No equivalent replacement coverage, new execution, real PTY delivery or CI claim. Historical receipts and their original commands/counts/results remain historical and do not validate the reduced selector." ], "demotionRule": "Demote if discovery and launch can resolve different authorities, a scoped Floating probe overwrites the active-project inventory, or local context selection adds recurring scans or retained in-flight entries." }, @@ -7188,7 +7166,7 @@ "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/agent-session/resume/resume-sleeping-agent-session.test.ts", "pnpm exec vitest run --config config/vitest.config.ts tests/e2e/completed-worker-retirement-resume.unit.test.ts --reporter=verbose", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/agent-session/resume/resume-sleeping-agent-session.test.ts src/main/providers/local-pty/local-pty-provider-spawn-session.test.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/ipc/pty/pty-pane-reservation-settlement.test.ts src/main/runtime/construction/orca-runtime.test.ts src/renderer/src/lib/pane-manager/pane-fit.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/agent-session/resume/resume-sleeping-agent-session.test.ts src/main/providers/local-pty/local-pty-provider-spawn-session.test.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/ipc/pty/pty-pane-reservation-settlement.test.ts src/main/runtime/construction/orca-runtime.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", "pnpm exec electron-vite build --mode e2e && SKIP_BUILD=1 pnpm exec playwright test tests/e2e/live-background-terminal-mount-authority.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/agent-session/lifecycle/structured-agent-cli-writers.test.ts src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-adoption-replay.test.ts src/main/providers/agent-foreground-process-git-bash.win32.test.ts" ], @@ -7200,7 +7178,6 @@ "src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts", "src/main/ipc/pty/pty-pane-reservation-settlement.test.ts", "src/main/runtime/construction/orca-runtime.test.ts", - "src/renderer/src/lib/pane-manager/pane-fit.test.ts", "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", "tests/e2e/live-background-terminal-mount-authority.spec.ts", @@ -7233,12 +7210,6 @@ "hibernated stable panes with cleared live PTY bindings are skipped" ] }, - { - "file": "src/renderer/src/lib/pane-manager/pane-fit.test.ts", - "assertions": [ - "withheld hidden-window animation frames exhaust the bounded fit retry and release its continuation" - ] - }, { "file": "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts", "assertions": [ @@ -7324,7 +7295,8 @@ "Audit2056 (sources2054/2055): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-part-14.spec'). src/main/runtime/orca-runtime-tests/terminal-side-effect-facts-part-03.spec.ts: arms the stale-title timer for a seeded working title; arms the Command Code scrape from the noted spawn command; prefers the tracked title over a stale renderer lastTitle in the hydration seed; writes recent output into the current runtime output map; uses a later title handler replacement with the runtime receiver; calls the current injected listener with the runtime receiver; keeps optional reconciliation lazy after its callback is removed. src/main/runtime/orca-runtime-tests/mobile-session-tabs-part-14.spec.ts: lists them, so a paired client can mirror a migrated editor; closes one by retiring it from the host session; keeps an unsaved draft unless the close is forced; shares aggregate work but leaves a replacement pending promise alone; lets a targeted refresh finish while an aggregate refresh waits; refuses a close when the store lacks %s before the guard; keeps a later missing %s on the original caught failure path. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed.", "Audit2061 (sources2059/2060): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec'); await import('./orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec'); await import('./orca-runtime-tests/worktree-removal-execution-host.spec'); await import('./orca-runtime-tests/terminal-spawn-dispatch.spec'). src/main/runtime/orca-runtime-tests/headless-snapshots-part-02.spec.ts: replaces a cwd parsed before late WSL context with the provider cwd; keeps a live WSL cwd that arrives during late-context snapshot recovery; infers local reconstructed WSL context from a WSL UNC worktree; clears stale terminal file URI hostnames after empty-host OSC7 cwd updates; serializes SSH headless OSC7 cwd as POSIX when the desktop runtime is on Windows; keeps Windows SSH OSC7 cwd as a drive path when the desktop runtime is POSIX; serializes Windows SSH headless OSC7 cwd as a drive path on POSIX desktops; infers restored SSH connection identity from app-scoped PTY ids. src/main/runtime/orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec.ts: survives the rebuild that closing a saved editor tab forces; keeps its place in the tab order when the host lists the workspace again; lists tabs to phones in the order desktop clients draw after a rebuild; keeps the place a window gave a chat across a graph resend before the chat is restored. src/main/runtime/orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec.ts: lists every persisted terminal of a windowless host after a cold restart; lists every persisted terminal in the all-worktrees inventory after a cold restart; lists persisted editor tabs beside the terminals after a cold restart; restores a persisted split group layout on the cold rebuild; restores the saved active group of a split even when it is not the first group; falls back to the active tab group when the saved active group was not restored; keeps the headless group active when there is no persisted split; keeps phone and CLI creates in the group they joined across a cold restart. src/main/runtime/orca-runtime-tests/worktree-removal-failed-retry.spec.ts: lists the leftover with its error, though Git no longer does; takes the normal delete once Git registers a checkout at the path again; joins a retry another client started while this Delete listed Git; shows the failed row with its error, not the scan cached before the delete. src/main/runtime/orca-runtime-tests/worktree-removal-execution-host.spec.ts: refuses an SSH row whose host is unreachable instead of deleting here; refuses a runtime row with no nested SSH target rather than deleting locally; refuses a runtime row that also names a nested SSH target; retains runtime purge dispatch in a recovery job; uses the runtime helper override before resuming an interrupted removal; uses the runtime helper override before retrying a failed removal; captures the watcher callback and retains its job-host receiver; reads the current runtime removal queue. src/main/runtime/orca-runtime-tests/terminal-spawn-dispatch.spec.ts: reports the spawn request before it leaves for the pty controller; reports it even when the spawn itself then fails; does not report it when the create fails before any spawn request; captures the admission method before reading the incarnation; captures the record method before evaluating its state object; skips database payload serialization when no database exists; skips a throwing database payload when no database exists; keeps the database method and receiver captured before its payload; captures the canonical map method before a binding getter changes it; completes the captured replacement fence without another map lookup. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed.", "Module layout policy correction: retire the remaining program-status-osc.spec.ts event-projection case (production/mock-derived pane-key oracle) and sign-in timeout case (real setTimeout wait with Date-only fake clocks). The runtime test registrar no longer imports that file. No replacement coverage or fresh execution is claimed for these losses; historical receipts retain their original source scope.", - "Module layout policy correction: retire the two remaining worktree-ps-agent-row-dismissal.spec.ts registrations. The remint/dismissal/exit case computed expected snapshotVersion increments from a production snapshot and used real-clock vi.waitFor; the legacy OSC case derived its expected paneKey from the production layout-owner switch. The runtime registrar no longer imports this file. These public projection scenarios have no replacement coverage or fresh execution claimed; all historical receipts and earlier loss notes retain their original source scope." + "Module layout policy correction: retire the two remaining worktree-ps-agent-row-dismissal.spec.ts registrations. The remint/dismissal/exit case computed expected snapshotVersion increments from a production snapshot and used real-clock vi.waitFor; the legacy OSC case derived its expected paneKey from the production layout-owner switch. The runtime registrar no longer imports this file. These public projection scenarios have no replacement coverage or fresh execution claimed; all historical receipts and earlier loss notes retain their original source scope.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/lib/pane-manager/pane-fit.test.ts. Lost alarms: runs the continuation when the reveal fit arrives on a different pane object; does not let a stale handle cancel its deferred replacement; resolves failure when hidden-window animation frames are withheld. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Demote or quarantine if failures are non-actionable or if a duplicate resume escape occurs outside the modeled matrix." }, @@ -7362,8 +7334,8 @@ "invariant": "For every claim-capable execution route, one provider-session identity has at most one live PTY owner and one canonical host surface across concurrent clients, retries, reconnects, and stale publications. For paired structured fresh and resume requests, the authenticated owning runtime creates in background without a renderer window; activate=true focuses the exact requested leaf only on the requesting viewer, while activate=false changes no viewer focus. A live orphan may be adopted only when the controller proves its exact handle and incarnation, its worktree and host owner match, no competing visual owner exists, and a host topology CAS wins. A viewer may classify completion only from successful host/provider inspection or explicit lifecycle evidence; transport, handle, or provider unavailability remains unknown and breaks any consecutive-idle proof. A physical exit retires that exact incarnation durably so stale client state and host restart cannot recreate it. Mixed-version routes select the unchanged legacy request before any authority side effect or execution-owner-local filesystem access.", "oracle": "Audit2694 source-only review: Remove14 authored/14 expanded cases in the renderer completion wrapper family, including the complete bell/query-reply/stale-input factory, without replacement. Retain29 authored/34 expanded cases with unchanged callback bodies. Three removals are the existing pending2203 overlap and must be adopted/count-reconciled once; eleven are fresh removals. No current-source runtime or gate execution is supplied by this packet; dated evidence retains its original source scope and does not validate the reduced factory bytes. No equivalent replacement, physical provider, mixed-version, mobile, rendered UI, cross-platform, soak or measured-performance result is claimed. The existing command and testFiles selector retain tests/pty-connection-completion-and-notification.test.ts; no wrapper-specific assertionRef is removed. Historical selector results do not validate its changed reachable factories. Historical prior account (preserved verbatim): Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2003: Delete the complete pure orphan-topology suite and its file membership; preserve the other selectors from its combined command after archiving the original command string. Lost alarms are synthetic current-layout preservation, one-anchor grafting and ambiguous-anchor appending examples. Do not transfer their ownership/topology scope to sibling suites. Preserve all5 independent historical receipts exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Delete the mocked agent-session suite and its prospective selector and aliases. Retire all3 whole dated changed-selector receipts, preserving their original raw objects in historical gaps without transferring counts or durations. Preserve5 unrelated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial agent-operation service suite retains only concurrent retry joining with changed-fingerprint conflict. Retire its complete three-title assertion object: old-owner legacy fallback before side effects, nested SSH Pi transcript refusal and completed/ambiguous physical-commit replay fences are no longer backed. Other probe/launch option/ledger/failure/remote-inventory examples disappear as detailed below. No actual provider spawn, live SSH or physical commit proof is retained by this source. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1911: The complete provider PTY-inspection mock suite is removed: missing-provider rejection, stale-PTY renderer unverifiable mapping, preserved supplied renderer failures, and supplied transport-loss mapping alarms are intentionally lost. No actual terminal teardown, daemon error or remote transport was exercised. Only the deleted explicit source tokens, testFile and whole assertionRef are removed. Meaningful remaining source selectors/assertions and unaffected dated receipts retain their original scope. No equivalent replacement, fresh reduced-gate, native/platform/provider, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Race independent clients and repeated operation IDs, then assert one physical spawn and one canonical PTY/surface; inject exit-before-reply, provider disconnect, conflicting claim scope, old daemon/relay capabilities, reused handles, stale incarnations, owner mismatch, and topology revision conflict; assert safe adoption or explicit failure without a second spawn or wrong-process attachment. Run fresh/resume with activate true/false against an isolated headed desktop host and a separate paired client, then against isolated headless serve: assert host presentation stays background, only the requesting viewer focuses the exact leaf, inactive calls preserve client/DOM focus, a same-version publication replay cannot lose focus intent, and sibling-first split publication cannot consume exact-leaf intent. Restore legacy split panes and groups beside a newer host-owned tab, preserving exact predecessor/new/successor order, output, input, resize, titles, tab/leaf identity, active group, and multi-client convergence. For completion, drive a known running agent through outer transport loss, authoritative provider rejection, reconnect, explicit stop, real exit status, and successful hook completion; assert unavailable evidence never dispatches completion and two fresh authoritative idle samples are required after the gap. After exact exit, assert terminal and tab listings omit the surface, a stale publication cannot restore it, restart cannot resurrect it, exact tokened fixture PIDs are dead, and unrelated tabs/processes survive until scoped cleanup.", "commands": [ - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync/navigation/web-session-tabs-sync-focus-intent.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-session-tabs-sync/navigation/web-session-tabs-sync-focus-intent.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts", "pnpm exec vitest run --config config/vitest.config.ts tests/e2e/remote-terminal-tab-retirement.unit.test.ts", "pnpm run build:cli && pnpm run build:electron-vite && node config/scripts/diagnostics/remote-agent-authority/remote-agent-session-authority-repro.mjs", "node --check config/scripts/development/remote-session-cleanup/remote-agent-session-process-cleanup.mjs && node config/scripts/diagnostics/remote-agent-authority/remote-agent-session-authority-repro.mjs", @@ -7392,7 +7364,6 @@ "src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts", - "src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", "tests/e2e/remote-agent-session-focus-authority.spec.ts", "config/scripts/diagnostics/remote-agent-authority/remote-agent-session-authority-repro.mjs", "config/scripts/development/remote-session-cleanup/remote-agent-session-process-cleanup.mjs", @@ -7474,10 +7445,6 @@ "same-epoch stale publications cannot resurrect the retired surface after reconnect" ] }, - { - "file": "src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", - "assertions": ["confirms only the exact handoff after its post-create list completes"] - }, { "file": "src/renderer/src/runtime/web-session-tabs-sync/navigation/web-session-tabs-sync-focus-intent.test.ts", "assertions": [ @@ -7623,7 +7590,8 @@ "Audit2061 (sources2059/2060): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec'); await import('./orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec'); await import('./orca-runtime-tests/worktree-removal-execution-host.spec'); await import('./orca-runtime-tests/terminal-spawn-dispatch.spec'). src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-completion-and-notification.test.ts: ordinary registration cleanup removes only import { registerPtyConnectionSshHookStatusAuthorityTests } from '../host-ownership/pty-connection-ssh-hook-status-authority.spec'; registerPtyConnectionSshHookStatusAuthorityTests(). src/main/runtime/orca-runtime-tests/headless-snapshots-part-02.spec.ts: replaces a cwd parsed before late WSL context with the provider cwd; keeps a live WSL cwd that arrives during late-context snapshot recovery; infers local reconstructed WSL context from a WSL UNC worktree; clears stale terminal file URI hostnames after empty-host OSC7 cwd updates; serializes SSH headless OSC7 cwd as POSIX when the desktop runtime is on Windows; keeps Windows SSH OSC7 cwd as a drive path when the desktop runtime is POSIX; serializes Windows SSH headless OSC7 cwd as a drive path on POSIX desktops; infers restored SSH connection identity from app-scoped PTY ids. src/main/runtime/orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec.ts: survives the rebuild that closing a saved editor tab forces; keeps its place in the tab order when the host lists the workspace again; lists tabs to phones in the order desktop clients draw after a rebuild; keeps the place a window gave a chat across a graph resend before the chat is restored. src/main/runtime/orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec.ts: lists every persisted terminal of a windowless host after a cold restart; lists every persisted terminal in the all-worktrees inventory after a cold restart; lists persisted editor tabs beside the terminals after a cold restart; restores a persisted split group layout on the cold rebuild; restores the saved active group of a split even when it is not the first group; falls back to the active tab group when the saved active group was not restored; keeps the headless group active when there is no persisted split; keeps phone and CLI creates in the group they joined across a cold restart. src/main/runtime/orca-runtime-tests/worktree-removal-failed-retry.spec.ts: lists the leftover with its error, though Git no longer does; takes the normal delete once Git registers a checkout at the path again; joins a retry another client started while this Delete listed Git; shows the failed row with its error, not the scan cached before the delete. src/main/runtime/orca-runtime-tests/worktree-removal-execution-host.spec.ts: refuses an SSH row whose host is unreachable instead of deleting here; refuses a runtime row with no nested SSH target rather than deleting locally; refuses a runtime row that also names a nested SSH target; retains runtime purge dispatch in a recovery job; uses the runtime helper override before resuming an interrupted removal; uses the runtime helper override before retrying a failed removal; captures the watcher callback and retains its job-host receiver; reads the current runtime removal queue. src/main/runtime/orca-runtime-tests/terminal-spawn-dispatch.spec.ts: reports the spawn request before it leaves for the pty controller; reports it even when the spawn itself then fails; does not report it when the create fails before any spawn request; captures the admission method before reading the incarnation; captures the record method before evaluating its state object; skips database payload serialization when no database exists; skips a throwing database payload when no database exists; keeps the database method and receiver captured before its payload; captures the canonical map method before a binding getter changes it; completes the captured replacement fence without another map lookup. src/renderer/src/components/terminal-pane/pty-connection/host-ownership/pty-connection-ssh-hook-status-authority.spec.ts: leaves SSH IPC OSC 9999 status ownership in the main runtime. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed.", "Module layout policy correction: retire the remaining program-status-osc.spec.ts event-projection case (production/mock-derived pane-key oracle) and sign-in timeout case (real setTimeout wait with Date-only fake clocks). The runtime test registrar no longer imports that file. No replacement coverage or fresh execution is claimed for these losses; historical receipts retain their original source scope.", "Module layout policy correction: retire the two remaining worktree-ps-agent-row-dismissal.spec.ts registrations. The remint/dismissal/exit case computed expected snapshotVersion increments from a production snapshot and used real-clock vi.waitFor; the legacy OSC case derived its expected paneKey from the production layout-owner switch. The runtime registrar no longer imports this file. These public projection scenarios have no replacement coverage or fresh execution claimed; all historical receipts and earlier loss notes retain their original source scope.", - "Audit2709 removes src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts: intentionally retire waiting out an older inventory and sharing a fresh post-operation request among concurrent callers. No equivalent replacement or fresh execution is claimed. Dated receipts remain historical evidence for their unchanged selectors only; they do not validate the reduced commands or full current gate. Original prospective command before removal: pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts" + "Audit2709 removes src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts: intentionally retire waiting out an older inventory and sharing a fresh post-operation request among concurrent callers. No equivalent replacement or fresh execution is claimed. Dated receipts remain historical evidence for their unchanged selectors only; they do not validate the reduced commands or full current gate. Original prospective command before removal: pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts. Lost alarms: confirms only the exact handoff after its post-create list completes. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Keep experimental or demote if the focused gate flakes without a product or harness bug, if a retry can physically spawn twice, if a stale exit/publication can replace or resurrect a terminal, or if mixed-version fallback occurs after an authority side effect." }, @@ -8562,13 +8530,13 @@ "providers": ["paired-runtime", "ssh"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": [], - "coverageNotes": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. The direct side-effect-facts selector retains six cases: replay exclusion, live sequence handling, stale disposer ownership, one handoff drain, drain-before-snapshot, and stale replacement-snapshot refusal. Successful current-entry snapshot application is not witnessed by the retained snapshot case. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2162: Reduced inventory-authority source intentionally loses the examples recorded in knownGaps; historical execution is not validation of changed bytes. Historical prior account (preserved verbatim): Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Module ownership cleanup retires the modeled cold-park private watcher/render/work-count suites under the current test policy. Their assertions are no longer active evidence. Retained lifecycle tests and headed/headless journeys do not establish equivalent coverage for those removed harness alarms. Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/pty-connection-hidden-tui-snapshot-replay.spec.ts: Loses rich hidden snapshot sequence replay/grid resize and forced repaint after hidden snapshot alarms, including live-trigger deduplication. deletes src/renderer/src/components/terminal-pane/terminal-cold-park-verdict-loop.test.tsx: Loses modeled React oscillation alarm; real pane/watchers are not exercised. deletes src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts: Loses delayed credit until parsing, malformed frame/unknown stream handling, discarded transformed output recovery and failed ACK rearm alarms. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1412 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): This audit removes the browser response-after-manual-disconnect example. Its distinct in-flight response fencing alarm is lost; retained queued-call disconnect tests are not an equivalent replacement. Historical commands and results remain historical. Deterministic headed macOS runs launch an isolated Orca desktop server and a separate paired web client. A byte-identical headless run uses an isolated `orca serve` host. Both create six real paired host PTYs with bounded high-output scrollback, prove sustained output while all six client xterms are warm-mounted but hidden causes zero renderer scheduler work, then drive 180 timed real-PTY spinner frames through a second production `session.tabs.subscribeAll` transport and the real Zustand store. Each worktree must publish and apply only its exact initial-working and final-idle titles. Both topologies ordinary-park five xterms without enabling the lossy retention budget, assert bounded cells/heap/timer lag, then restore the bounded authoritative tail exactly once on the original PTY including output produced while parked and continued input/output. The headed oracle additionally keeps every remote target workspace and terminal unmounted in the host renderer before and after client recovery. Production-parity React contracts reproduce and remove the independent cold-park watcher/title feedback loop, including memo-cache discard, StrictMode cleanup replay, detached split-PTY replacement, and the zero-park fast path. A deterministic cross-boundary contract drives 24 real PTY trackers in distinct worktrees through 2,880 decorative spinner and bare-native redraw chunks and counts host publications, serialized bytes, renderer apply calls, and store mutations while preserving exact per-PTY raw chunks and per-worktree semantic transitions. Fake-clock continuation beyond the 30-minute stale boundary requires one deduplicated refresh per worktree every 15 minutes, globally spaced by at least 50 ms, with every mirrored working status still fresh. Exact-identical Pi plus rotating normalized Pi/Grok frames renew coarse working/permission state; rich prompt/tool/question fields retain their original 30-minute lease, and `stateStartedAt`/acknowledgement identity remain stable across title/hook source swaps. A separate headed desktop-client journey relaunches the host main process while retaining the daemon and a daemon-backgrounded PTY, then requires first-observation foreground sync and continued input/output on the same PTY. Unit coverage keeps layout versions out of snapshot output high-waters when a restored snapshot has no output sequence and retries an existing subscriber only after authoritative provider inventory proves the retained PTY exists. Separate headed/headless ACK-starvation tests recover one stalled stream without replacing its PTY. A headed output-drop oracle proves a successful snapshot probe cannot certify a stale live stream when the authoritative PTY sequence advanced beyond the client high-water. Unit coverage requires an unsequenced client to establish a first-probe baseline, remain attached at the same sequence, and recover only after a later probe advances. A capability-disabled run proves legacy hosts retain the prior lossy limit/TTL fallback. Unit tests cover exact-owner capability routing, raw-stream release, singleton side-effect facts with timed handoff cleanup, 64 simultaneous synthetic-title sources with zero decorative client events after convergence, per-client late-subscribe recovery, semantic bell/title transitions, local animation-frame preservation, legacy no-output-pause delivery gating with exact snapshot restore, provider-authoritative snapshots, 128 active streams plus retry after a 129th-stream capacity rejection, capacity-pressure backoff, full split-leaf remint reconciliation, truncation, and manual-disconnect queue fencing. Linux, Windows, live SSH, and production-scale paired hosts remain gaps. The audit removed the E2E parking override/debug-handle suite. Override exposure guards, delay/retention independence, TTL filtering and debug-handle forwarding alarms are lost. Retained real parking journeys do not establish equivalent test-harness configuration coverage. The test-value audit removes separate responsive-stream, intervening-control-frame and capacity-error classification examples. Retained multiplexer checks use fake clocks and captured transport callbacks; their per-stream isolation, deadline and probe-baseline assertions do not prove an actual host restart or equivalent replacement coverage. Historical run commands and results remain historical. The test-value audit removes the standalone retained/live metadata projection and additive-preference negative example from runtime-client-events.test.ts. Its remaining case checks partial-authority event admission through a captured callback; it does not establish full-pair projection, actual peer delivery or equivalent replacement coverage. Historical commands and results remain historical. The isolated accepted-handle-events observer suite is removed. Its runtime/worktree/pane membership, once-only delivery and listener-release ledger alarms are lost. Live hidden-retention evidence does not establish equivalent observer coverage. Audit1292: The entire host-session mirror empty-inventory settle suite is removed. Live-unpublished versus genuinely-empty hydration, same-ID revision response/in-flight fencing and cleared-tracking pending-probe invalidation alarms are lost. The singleton store and inventories were mocked; no real host census or resume delivery or equivalent replacement coverage is claimed. Audit1302: src/main/runtime/session-tabs-inventory-publication.test.ts: Keep actual runtime public inventory authority across renderer epochs/resync, fallback, aborts and invalid graph publishers. Remove copied nonempty publication, private scan counters/collector and known-host replacement, fake refresh that performs the restoration itself, environment capability flag, and private resume-store characterization whose fixture supplies reservation/rollback ordering. Lose isolated refresh/churn/receiver/capability and copied-payload wiring alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1318: src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts: Retain real local encrypted WebSocket regressions for pre-ready socket replacement preserving a request, close-before-ready logical subscription cleanup, unrelated RPC surviving another deadline, and pending setup abort leaving a sibling pending. Host server is synthetic and uses production crypto; kept scenarios also retain internal diagnostics/memory assertions unchanged. Remove happy routing, authority-envelope, unknown-ID/log, cleanup enum, binary, keepalive/liveness and production-budget/private socket inventories; intentionally lose those alarms, no physical host authorization, independent crypto or remote provider claim. Reduced selectors need fresh validation; historical full-suite receipts do not validate the changed coverage. No equivalent replacement or physical-provider claim. Audit1346 removes the entire private-state-injected session-tabs rich-status boundary suite. Its stale task/question, neutral-title preservation, owner-change and provider-generation alarms are intentionally lost. Retained real terminal journeys do not establish equivalent replacement coverage. The prior mixed 42-contract execution receipt is historical and must not validate the reduced current selector; fresh validation remains required. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1391: src/renderer/src/components/terminal-pane/pty-connection-remote-pause-drop-notice.spec.ts reduced. Lose repeated quiet switches avoiding snapshots and legacy host without drop-report capability requesting a snapshot. Retained imported registrar uses fake transport/xterm writes, exercising real gap/restore lifecycle rather than actual terminal rendering. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1396: src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts reduced. Retire standalone reconcile set output and positive untouchedFreshSpawn/workspaceOwner copying alarms; no native watcher execution claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1402: src/renderer/src/components/terminal-pane/pty-connection-park-reveal-model-snapshot.spec.ts reduced. Loses basic paired serialize/initial replay forwarding check; hidden alt-frame grid/precedence policy remains. No real terminal renderer claim. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1412: src/main/runtime/mobile-session-tabs-agent-status-heartbeat.test.ts reduced. Loses FIFO fairness/spacing, semantic bypass/cancellation, delayed-boundary scheduling and teardown timer-cleanup alarms. Retained fixed emission cases do not replace those outcomes. No equivalent replacement coverage is claimed.", + "coverageNotes": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. The direct side-effect-facts selector retains six cases: replay exclusion, live sequence handling, stale disposer ownership, one handoff drain, drain-before-snapshot, and stale replacement-snapshot refusal. Successful current-entry snapshot application is not witnessed by the retained snapshot case. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2162: Reduced inventory-authority source intentionally loses the examples recorded in knownGaps; historical execution is not validation of changed bytes. Historical prior account (preserved verbatim): Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Module ownership cleanup retires the modeled cold-park private watcher/render/work-count suites under the current test policy. Their assertions are no longer active evidence. Retained lifecycle tests and headed/headless journeys do not establish equivalent coverage for those removed harness alarms. Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Audit1417 intentionally deletes src/renderer/src/components/terminal-pane/pty-connection-hidden-tui-snapshot-replay.spec.ts: Loses rich hidden snapshot sequence replay/grid resize and forced repaint after hidden snapshot alarms, including live-trigger deduplication. deletes src/renderer/src/components/terminal-pane/terminal-cold-park-verdict-loop.test.tsx: Loses modeled React oscillation alarm; real pane/watchers are not exercised. deletes src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts: Loses delayed credit until parsing, malformed frame/unknown stream handling, discarded transformed output recovery and failed ACK rearm alarms. No equivalent replacement coverage or fresh result is claimed. Historical coverage account: Audit1412 changes referenced sources. Retained unchanged-source receipts apply only to their unchanged commands; the reduced selectors have no new execution result. Historical coverage account (not a current claim for changed sources): This audit removes the browser response-after-manual-disconnect example. Its distinct in-flight response fencing alarm is lost; retained queued-call disconnect tests are not an equivalent replacement. Historical commands and results remain historical. Deterministic headed macOS runs launch an isolated Orca desktop server and a separate paired web client. A byte-identical headless run uses an isolated `orca serve` host. Both create six real paired host PTYs with bounded high-output scrollback, prove sustained output while all six client xterms are warm-mounted but hidden causes zero renderer scheduler work, then drive 180 timed real-PTY spinner frames through a second production `session.tabs.subscribeAll` transport and the real Zustand store. Each worktree must publish and apply only its exact initial-working and final-idle titles. Both topologies ordinary-park five xterms without enabling the lossy retention budget, assert bounded cells/heap/timer lag, then restore the bounded authoritative tail exactly once on the original PTY including output produced while parked and continued input/output. The headed oracle additionally keeps every remote target workspace and terminal unmounted in the host renderer before and after client recovery. Production-parity React contracts reproduce and remove the independent cold-park watcher/title feedback loop, including memo-cache discard, StrictMode cleanup replay, detached split-PTY replacement, and the zero-park fast path. A deterministic cross-boundary contract drives 24 real PTY trackers in distinct worktrees through 2,880 decorative spinner and bare-native redraw chunks and counts host publications, serialized bytes, renderer apply calls, and store mutations while preserving exact per-PTY raw chunks and per-worktree semantic transitions. Fake-clock continuation beyond the 30-minute stale boundary requires one deduplicated refresh per worktree every 15 minutes, globally spaced by at least 50 ms, with every mirrored working status still fresh. Exact-identical Pi plus rotating normalized Pi/Grok frames renew coarse working/permission state; rich prompt/tool/question fields retain their original 30-minute lease, and `stateStartedAt`/acknowledgement identity remain stable across title/hook source swaps. A separate headed desktop-client journey relaunches the host main process while retaining the daemon and a daemon-backgrounded PTY, then requires first-observation foreground sync and continued input/output on the same PTY. Unit coverage keeps layout versions out of snapshot output high-waters when a restored snapshot has no output sequence and retries an existing subscriber only after authoritative provider inventory proves the retained PTY exists. Separate headed/headless ACK-starvation tests recover one stalled stream without replacing its PTY. A headed output-drop oracle proves a successful snapshot probe cannot certify a stale live stream when the authoritative PTY sequence advanced beyond the client high-water. Unit coverage requires an unsequenced client to establish a first-probe baseline, remain attached at the same sequence, and recover only after a later probe advances. A capability-disabled run proves legacy hosts retain the prior lossy limit/TTL fallback. Unit tests cover exact-owner capability routing, raw-stream release, singleton side-effect facts with timed handoff cleanup, 64 simultaneous synthetic-title sources with zero decorative client events after convergence, per-client late-subscribe recovery, semantic bell/title transitions, local animation-frame preservation, legacy no-output-pause delivery gating with exact snapshot restore, provider-authoritative snapshots, 128 active streams plus retry after a 129th-stream capacity rejection, capacity-pressure backoff, full split-leaf remint reconciliation, truncation, and manual-disconnect queue fencing. Linux, Windows, live SSH, and production-scale paired hosts remain gaps. The audit removed the E2E parking override/debug-handle suite. Override exposure guards, delay/retention independence, TTL filtering and debug-handle forwarding alarms are lost. Retained real parking journeys do not establish equivalent test-harness configuration coverage. The test-value audit removes separate responsive-stream, intervening-control-frame and capacity-error classification examples. Retained multiplexer checks use fake clocks and captured transport callbacks; their per-stream isolation, deadline and probe-baseline assertions do not prove an actual host restart or equivalent replacement coverage. Historical run commands and results remain historical. The test-value audit removes the standalone retained/live metadata projection and additive-preference negative example from runtime-client-events.test.ts. Its remaining case checks partial-authority event admission through a captured callback; it does not establish full-pair projection, actual peer delivery or equivalent replacement coverage. Historical commands and results remain historical. The isolated accepted-handle-events observer suite is removed. Its runtime/worktree/pane membership, once-only delivery and listener-release ledger alarms are lost. Live hidden-retention evidence does not establish equivalent observer coverage. Audit1292: The entire host-session mirror empty-inventory settle suite is removed. Live-unpublished versus genuinely-empty hydration, same-ID revision response/in-flight fencing and cleared-tracking pending-probe invalidation alarms are lost. The singleton store and inventories were mocked; no real host census or resume delivery or equivalent replacement coverage is claimed. Audit1302: src/main/runtime/session-tabs-inventory-publication.test.ts: Keep actual runtime public inventory authority across renderer epochs/resync, fallback, aborts and invalid graph publishers. Remove copied nonempty publication, private scan counters/collector and known-host replacement, fake refresh that performs the restoration itself, environment capability flag, and private resume-store characterization whose fixture supplies reservation/rollback ordering. Lose isolated refresh/churn/receiver/capability and copied-payload wiring alarms; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1318: src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts: Retain real local encrypted WebSocket regressions for pre-ready socket replacement preserving a request, close-before-ready logical subscription cleanup, unrelated RPC surviving another deadline, and pending setup abort leaving a sibling pending. Host server is synthetic and uses production crypto; kept scenarios also retain internal diagnostics/memory assertions unchanged. Remove happy routing, authority-envelope, unknown-ID/log, cleanup enum, binary, keepalive/liveness and production-budget/private socket inventories; intentionally lose those alarms, no physical host authorization, independent crypto or remote provider claim. Reduced selectors need fresh validation; historical full-suite receipts do not validate the changed coverage. No equivalent replacement or physical-provider claim. Audit1346 removes the entire private-state-injected session-tabs rich-status boundary suite. Its stale task/question, neutral-title preservation, owner-change and provider-generation alarms are intentionally lost. Retained real terminal journeys do not establish equivalent replacement coverage. The prior mixed 42-contract execution receipt is historical and must not validate the reduced current selector; fresh validation remains required. Audit1352: focused Linux unit selections validated on source head b638e07d395b76af3ba6b03761a1a0fe7e5e026e. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1360: focused Linux unit selections validated on source head 415c9cffc2d10024131ec4b6721767a22195de6d. Historical statements above retain their original scope. Intentional test losses are recorded in knownGaps; these runs claim no physical-provider, p95, rendered E2E or cross-platform validation. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1391: src/renderer/src/components/terminal-pane/pty-connection-remote-pause-drop-notice.spec.ts reduced. Lose repeated quiet switches avoiding snapshots and legacy host without drop-report capability requesting a snapshot. Retained imported registrar uses fake transport/xterm writes, exercising real gap/restore lifecycle rather than actual terminal rendering. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1396: src/renderer/src/components/terminal-pane/terminal-parked-watcher-reconciliation.test.ts reduced. Retire standalone reconcile set output and positive untouchedFreshSpawn/workspaceOwner copying alarms; no native watcher execution claim. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1402: src/renderer/src/components/terminal-pane/pty-connection-park-reveal-model-snapshot.spec.ts reduced. Loses basic paired serialize/initial replay forwarding check; hidden alt-frame grid/precedence policy remains. No real terminal renderer claim. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1412: src/main/runtime/mobile-session-tabs-agent-status-heartbeat.test.ts reduced. Loses FIFO fairness/spacing, semantic bypass/cancellation, delayed-boundary scheduling and teardown timer-cleanup alarms. Retained fixed emission cases do not replace those outcomes. No equivalent replacement coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/8652", "https://github.com/stablyai/orca/pull/10625" ], "invariant": "A host advertising terminal.paired-parking.v1 keeps the PTY and bounded authoritative history alive while an ordinary hidden-view park destroys the client xterm and releases its raw per-PTY stream. Reveal must restore up to the requested 5,000 rows, parked-time side effects/output, the same PTY identity, and continued input/output. Parked watcher synchronization compares parking semantics rather than fresh object identity, owns setup and cleanup across StrictMode replay, reconciles real PTY/layout changes, and performs no terminal-state scan when nothing is parked. After a host main-process relaunch, unknown local delivery-sync state must not be treated as proof that a surviving daemon PTY is already foregrounded. An empty headed session-tab inventory is authoritative only after the current renderer graph generation publishes a complete inventory and every execution host answers the PTY census; unpublished, resync-pending, transport-failed, and partially scoped inventories remain unverifiable, while genuinely authoritative empty and non-empty inventories settle and a scope-less legacy empty keeps its pre-authority best-effort settle so outdated hosts retain agent auto-resume. The unpublished-empty regression must keep the resume dispatch parked with exactly one daemon process/writer; restoring unconditional empty hydration must release one dispatch and create two live writers. If subscription precedes provider readiness, authoritative inventory must reconsider that existing subscriber without spawning, resizing, or changing reconnect state. A snapshot without an output high-water must keep it unknown rather than borrowing a layout version that can suppress newer live output. Hosts without the capability must gate hidden raw output before xterm scheduling and repaint from the authoritative snapshot on reveal while retaining the existing limit/TTL force-parking fallback. After the first semantic title state, decorative spinner frequency must add zero paired client-event frames and zero full session-tabs work between status-freshness leases regardless of hidden worktree count. Continuous working or permission evidence may refresh each affected worktree once per 15 minutes through one globally spaced FIFO so current and legacy viewers do not decay before 30 minutes; sibling PTYs share that worktree refresh, while local title animation, raw output, and semantic title, status, bell, completion, and query facts remain intact. A paired terminal stream whose delivery credits stop progressing must replace only that stream; command silence first probes authoritative state and replaces the stream if the probe times out or proves the PTY advanced beyond the client's delivered output sequence. When no comparable delivery high-water exists, the first sequenced probe establishes a baseline and only later advancement proves staleness. A same-sequence snapshot remains valid proof of a responsive silent command. A successful status probe may replace a pre-ready shared-control socket without rejecting or duplicating calls already waiting for that transport. Manual disconnect must retain pairing while preventing queued or passive calls and subscriptions from recreating transport until explicit Connect. A paired pane reconnecting to a relaunched host that has not yet published its window graph (an unpublished frame, bare or client-projected) must stay in recovery rather than retire, and the host's first published snapshot carrying the surface must reattach it; a published frame lacking the surface still retires it. The placeholder is only ever sent before the host's graph first publishes; afterwards a worktree with no tabs gets a real empty answer, pushed once to any client that asked during startup, so a paired client opening a never-opened host worktree still gets its first terminal.", - "oracle": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. The direct side-effect-facts selector retains six cases: replay exclusion, live sequence handling, stale disposer ownership, one handoff drain, drain-before-snapshot, and stale replacement-snapshot refusal. Successful current-entry snapshot application is not witnessed by the retained snapshot case. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Run one byte-identical six-terminal oracle against an isolated headed desktop host and an isolated headless `orca serve` host. Stage at least 1,000,000 xterm cells, hide all six warm managers, emit an initial working title followed by 30 timed real-PTY spinner frames and a final idle title per worktree, and observe a second real session-tabs subscription plus the production renderer store. Require exactly two transport envelopes and two title mutations per worktree, no decorative title crossing either boundary, and positive decoded-envelope bytes. Enable ordinary parking with the lossy retention budget disabled, require exactly one mounted manager and five parked tabs, at most 45% retained cells, no more than 16 MiB heap growth, and under 500 ms timer drift. In headed mode, require the host renderer to remain on its original workspace with zero target terminal managers mounted throughout client park, reveal, and live I/O. While a tab is parked, require authoritative terminal.read to observe new PTY output; reveal it and require the original PTY, a marker within the requested 5,000-row history, the parked marker, and post-reveal input/output. Drive 24 real PTY title trackers in distinct worktrees through 40 decorative spinner frames each, then 40 spinner frames alternating with Cursor's bare native identity redraw, using fake clocks. Require all 2,880 raw chunks exactly and zero host session-tabs publications, serialized bytes, renderer apply calls, and renderer store mutations. Continue decorative evidence beyond 30 minutes, require exactly one refresh per worktree per 15-minute lease through a FIFO spaced by at least 50 ms, and require all 24 viewer statuses to remain working and fresh; then require one exact semantic idle transition and one visible-output chunk per PTY. Require ordinary and interior-Braille title changes to publish. Drive 64 host PTYs through ten 80 ms synthetic title frames, require zero paired client events after the first frame per PTY while all 640 local frames remain observable, attach a late client and require one current frame per PTY, then require semantic bell and idle transitions on both clients. Reconstruct a legacy subscribed stream with no outputPause capability, hide a chatty pane, require no hidden xterm writes, reveal it, and require one authoritative snapshot plus continued live output. Drop and acknowledge output only for one original paired-client stream, prove the fixture process consumed input while the host model advanced and the client stayed stale, then require the command snapshot probe to replace that stream, repaint exact fixture output, preserve authoritative PTY identity and target-tab cardinality, and resume live I/O. For a client without a delivered sequence, require the first numeric probe to establish a baseline without replacement, a same-sequence probe to remain attached, and a later advanced probe to replace only that stream. Withhold the first encrypted shared-control ready frame, start one RPC, trigger a status-probe refresh, and require two connections, one host delivery, successful response, and zero retained request bytes. Admit 128 active streams, reject the 129th as retryable, release one stream, then require the retry to attach and publish a snapshot without multiplying retained subscribers. Disable terminal.paired-parking.v1 and require the same oracle to fail before parking, while the legacy limit-one fallback separately passes. Also preserve truncated first paint, ACK-starved same-PTY recovery, responsive silent-command snapshot probes, dead-stream probe timeout recovery, and queued manual-disconnect fencing.", + "oracle": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. The direct side-effect-facts selector retains six cases: replay exclusion, live sequence handling, stale disposer ownership, one handoff drain, drain-before-snapshot, and stale replacement-snapshot refusal. Successful current-entry snapshot application is not witnessed by the retained snapshot case. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2003: Remove SSH/absent-inventory attach refusal, later-subscriber retry, no-view adoption/takeback, provider-tail/unknown-read projections, failed-keystroke retry and simpler loss reattach examples. Keep the existing-subscriber inventory-ready retry alias only at its retained callback, plus retained held-input cancellation/replacement and overlapping attach/loss fences. The inventory driver also loses the whole empty-worktree-publication registration and folder-host/unverifiable scalar examples. Retire the entire dated10-case attach receipt; retain16 independent receipts only in their original scopes. No retained-memory or real-daemon measurement follows from supplied provider inventories. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1991: Remove initial-snapshot sequence-domain, limited-tail/truncation and unknown-output-sequence aliases whose callbacks are removed. The source retains partial-chunk replay and requested-snapshot retry/admission at supplied snapshot/data seams, not equivalent replacement or measured retained-memory proof. Preserve the existing stream/pending-wait capacity aliases only to their retained owning multiplex callbacks with supplied PTY resolutions. Removing the sibling-PTY heartbeat dedup work-count sample does not establish a new decorative fanout budget; the unrelated-PTY cancellation fence survives. Preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Remove the reasonless-close selector and narrow census aliases to actual retained runtime and injected-inventory callbacks. The removed synthetic census-dedup and isolated pre-boundary timing cases receive no replacement credit; preserve17 unrelated dated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1939 (sources1934/1935): Delete the synthetic watcher-synchronization and partial-reconciliation suites. Retire all affected host-fact, capability-owner, activation-residue and retention-cap aliases because the remaining callbacks do not establish those full claims. Keep exact local hook/timer, byte-stream side effects, exit/disposal, scroll-intent cleanup and deterministic local rank/TTL progression callbacks within their mocked-host scope. No physical paired-host retention, resource budget or equivalent replacement evidence is assigned. The generic broad test command remains a runnable discovery command, never a dated execution receipt. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete hook-agent-status projection suite and its two behavioral assertion aliases. Supplied stored hook/title/resume/staleness/rename/question/generation/PTY-retirement/snapshot-version projections are intentionally lost. The separate osc-status-scan gate mentions this source only in existing historical notes and receives no mutation. No measured hidden-output retention, CPU/RSS or resource budget result is claimed by the deleted source or this audit. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Run one byte-identical six-terminal oracle against an isolated headed desktop host and an isolated headless `orca serve` host. Stage at least 1,000,000 xterm cells, hide all six warm managers, emit an initial working title followed by 30 timed real-PTY spinner frames and a final idle title per worktree, and observe a second real session-tabs subscription plus the production renderer store. Require exactly two transport envelopes and two title mutations per worktree, no decorative title crossing either boundary, and positive decoded-envelope bytes. Enable ordinary parking with the lossy retention budget disabled, require exactly one mounted manager and five parked tabs, at most 45% retained cells, no more than 16 MiB heap growth, and under 500 ms timer drift. In headed mode, require the host renderer to remain on its original workspace with zero target terminal managers mounted throughout client park, reveal, and live I/O. While a tab is parked, require authoritative terminal.read to observe new PTY output; reveal it and require the original PTY, a marker within the requested 5,000-row history, the parked marker, and post-reveal input/output. Drive 24 real PTY title trackers in distinct worktrees through 40 decorative spinner frames each, then 40 spinner frames alternating with Cursor's bare native identity redraw, using fake clocks. Require all 2,880 raw chunks exactly and zero host session-tabs publications, serialized bytes, renderer apply calls, and renderer store mutations. Continue decorative evidence beyond 30 minutes, require exactly one refresh per worktree per 15-minute lease through a FIFO spaced by at least 50 ms, and require all 24 viewer statuses to remain working and fresh; then require one exact semantic idle transition and one visible-output chunk per PTY. Require ordinary and interior-Braille title changes to publish. Drive 64 host PTYs through ten 80 ms synthetic title frames, require zero paired client events after the first frame per PTY while all 640 local frames remain observable, attach a late client and require one current frame per PTY, then require semantic bell and idle transitions on both clients. Reconstruct a legacy subscribed stream with no outputPause capability, hide a chatty pane, require no hidden xterm writes, reveal it, and require one authoritative snapshot plus continued live output. Drop and acknowledge output only for one original paired-client stream, prove the fixture process consumed input while the host model advanced and the client stayed stale, then require the command snapshot probe to replace that stream, repaint exact fixture output, preserve authoritative PTY identity and target-tab cardinality, and resume live I/O. For a client without a delivered sequence, require the first numeric probe to establish a baseline without replacement, a same-sequence probe to remain attached, and a later advanced probe to replace only that stream. Withhold the first encrypted shared-control ready frame, start one RPC, trigger a status-probe refresh, and require two connections, one host delivery, successful response, and zero retained request bytes. Admit 128 active streams, reject the 129th as retryable, release one stream, then require the retry to attach and publish a snapshot without multiplying retained subscribers. Disable terminal.paired-parking.v1 and require the same oracle to fail before parking, while the legacy limit-one fallback separately passes. Also preserve truncated first paint, ACK-starved same-PTY recovery, responsive silent-command snapshot probes, dead-stream probe timeout recovery, and queued manual-disconnect fencing.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-unpublished-host-graph.test.ts", "ORCA_BACKGROUND_LAUNCH=1 pnpm exec playwright test tests/e2e/paired-remote-terminal-host-quit-reconnect-input.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", @@ -9043,7 +9011,19 @@ "Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed.", "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed.", "Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation.", - "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed." + "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed.", + "Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked.", + "Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed.", + "Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms.", + "Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery.", + "Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction.", + "Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed.", + "Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed.", + "Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed.", + "Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed.", + "Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed.", + "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired.", + "Terminal/runtime defining-owner cleanup retires complete parking timer, lifecycle, mocked close/disposal and render/flip/mount work-count cases under repository test policy. The retained watcher source has only two literal-oracle coverage decisions; the retained cold-parking hook source has only the layout-PTY exemption invalidation case. Lost alarms include cooldown/measure/pin wakeups, split exit/close attribution and cancellation, watcher disposal/restart, flood timer rearm, worktree verdict damping, and destroyed-transport cold restore. No equivalent replacement coverage or execution pass for the reduced cases is claimed; prior dated receipts retain their original scope." ], "demotionRule": "Keep experimental or demote to protection none if decorative title frequency changes paired client-event or session-tabs publication volume after convergence, a semantic fact is dropped, paired first paint is blank, passive work reconnects after manual disconnect, a capable hidden paired manager survives ordinary parking, a legacy hidden pane schedules raw xterm output or bypasses its fallback, reveal loses requested bounded history or live PTY identity, retained buffer cells do not fall, or either headed/headless scenario flakes." }, @@ -9137,36 +9117,34 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": [], - "coverageNotes": "Renderer unit coverage proves replay-guard lost-completion and certified-wedge events carry correlatable tab, worktree, durable leaf, pane, and redacted PTY identity without exposing path-bearing values. Full provider lifecycle attribution and diagnostics-bundle artifact proof remain gaps.", + "coverageNotes": "Retained renderer unit cases exercise guard release after a silent stalled parse and hardcoded breadcrumb event/pane identity with a fake terminal and mocked recorder. The identity-rich hashing/redaction registration was retired whole. No hashed tab/worktree/leaf identity, path-redaction, diagnostics-bundle export, live provider or packaged runtime coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/6800", "https://github.com/stablyai/orca/issues/6773" ], "invariant": "Terminal lifecycle anomalies around reattach, restore, replay wedges, provider ownership, stale liveness, and fallback routing must leave compact, deduped, privacy-safe breadcrumbs in crash diagnostics so future reports can be attributed from evidence. Replay anomalies must distinguish pane managers and PTYs without recording path-bearing worktree or session identities.", - "oracle": "Drop a replay write completion while allowing its FIFO probe to parse, then require the lost-completion breadcrumb to include pane ID, stable hashes for tab/worktree/leaf identity, and a path-redacted PTY ID. Existing wedge tests require both lost-completion and certified-dead paths to record their distinct event names. Full pane transition traces and diagnostics-bundle artifact proof remain follow-ups.", + "oracle": "Simulate replay and probe completions that never arrive, advance fake timers through both quiet windows, and require the real replay guard to transition from engaged to released while the mocked recorder receives terminal_replay_guard_wedged_release with paneId 1. This does not establish correlation hashing, path redaction or diagnostics-bundle export.", "commands": [ - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts" + "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts --maxWorkers=2" ], "testFiles": ["src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts"], "assertionRefs": [ { "file": "src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts", "assertions": [ - "records correlatable replay identity without exposing worktree or PTY paths", "releases after the probe itself never parses (wedged pipeline) and reports it" ] } ], "evidenceRuns": [ { - "date": "2026-07-29", + "date": "2026-11-10", "runner": "local", "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/replay-guard.test.ts", "result": "passed", - "durationSeconds": 0.201, - "summary": "The focused file passed 29 tests; replay anomaly breadcrumbs preserved event classification while adding hashed tab/worktree/leaf correlation and a path-redacted PTY identity.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts" + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts --maxWorkers=2", + "durationSeconds": 1.52, + "summary": "Retained replay-guard suite passed 19 cases on the module-layout wave1 candidate. The named breadcrumb case preserves real replay guard release and the hardcoded wedged event/pane1 with a fake terminal and mocked recorder. No identity-hash/redaction, live provider, diagnostics-bundle, fresh red/green, performance or CI-soak claim." } ], "runtimeBudget": { @@ -9175,11 +9153,11 @@ }, "flakeHistory": { "status": "unknown", - "evidence": "The replay-guard unit slice passed locally once; no CI soak history yet." + "evidence": "Retained selector has no CI soak history; fresh focused execution is recorded separately." }, "redGreenEvidence": { - "status": "partial", - "evidence": "Tests fail if replay anomalies stop recording their event name, omit pane/terminal correlation, or expose the fixture's path-bearing worktree and PTY prefixes. Needs diagnostics-bundle artifact proof and full transition-trace evidence before promotion." + "status": "missing", + "evidence": "No fresh revert/fault control was run for the retained selector. Historical richer-oracle statement before retirement is preserved in knownGaps and is not current reduced-selector evidence." }, "performanceBudget": { "required": true, @@ -9194,7 +9172,10 @@ "The replay identity schema has not yet been exercised in a live Electron/provider failure.", "Current coverage records anomaly breadcrumbs only, not a full pane lifecycle state-machine trace.", "Current coverage does not prove crash/diagnostics bundle export includes the breadcrumb.", - "Current coverage does not assert forbidden transitions across live Electron/provider flows." + "Current coverage does not assert forbidden transitions across live Electron/provider flows.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts. Lost alarms: a wedged release hands the terminal to pane recovery; a healthy parse completion never notifies pane recovery; HOLDS the guard while a slow replay is still parsing — a probe is queued, never a blind release; records correlatable replay identity without exposing worktree or PTY paths; holds past the probe deadline while sibling completions prove the parser alive; settles guards on a disposed xterm without certifying a wedge. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope.", + "Module-layout wave1 retires this complete historical receipt because its explicit current selector was reduced. It does not validate the reduced suite. Original receipt verbatim:\n{\n \"date\": \"2026-07-29\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/replay-guard.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 0.201,\n \"summary\": \"The focused file passed 29 tests; replay anomaly breadcrumbs preserved event classification while adding hashed tab/worktree/leaf correlation and a path-redacted PTY identity.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts\"\n }", + "Module-layout wave1 historical richer-oracle metadata before identity-registration retirement, retained verbatim without current validation credit: {\"coverageNotes\":\"Renderer unit coverage proves replay-guard lost-completion and certified-wedge events carry correlatable tab, worktree, durable leaf, pane, and redacted PTY identity without exposing path-bearing values. Full provider lifecycle attribution and diagnostics-bundle artifact proof remain gaps.\",\"oracle\":\"Drop a replay write completion while allowing its FIFO probe to parse, then require the lost-completion breadcrumb to include pane ID, stable hashes for tab/worktree/leaf identity, and a path-redacted PTY ID. Existing wedge tests require both lost-completion and certified-dead paths to record their distinct event names. Full pane transition traces and diagnostics-bundle artifact proof remain follow-ups.\",\"redGreenEvidence\":{\"status\":\"partial\",\"evidence\":\"Tests fail if replay anomalies stop recording their event name, omit pane/terminal correlation, or expose the fixture's path-bearing worktree and PTY prefixes. Needs diagnostics-bundle artifact proof and full transition-trace evidence before promotion.\"}}" ], "demotionRule": "Cannot promote if diagnostics are console-only, unbounded, or missing from support artifacts." }, @@ -9221,7 +9202,7 @@ "providers": ["local", "daemon", "ssh", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": [], - "coverageNotes": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Module layout SSH policy: Retire the multiplexer private-clock saturation registration, the writer saturation transition-count registration and the changed-source dated writer/backpressure receipt. Retained byte delivery, lane order, fairness, settlement and cancellation cases are unvalidated by that historical receipt. No equivalent replacement, new execution, measured performance, live SSH/WSL or cross-platform proof is claimed. The unchanged WSL protocol receipt retains only its original command scope. Historical prior account (preserved verbatim): Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2070 (sources2068/2069): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/hooks-and-hosted-review.spec'). src/main/ssh/ssh-multiplexer-transport-writer.test.ts: reports saturated epochs and their drain boundary exactly once. src/main/runtime/orca-runtime-tests/worktree-removal-archive-hook-gate.spec.ts: removes and records the waiver when the failure is explicitly overridden; removes without an override record when the hook succeeds; removes when the hook is configured but not requested; removes when no archive hook is configured; holds a desktop removal in the same repo until this one is accepted; joins a desktop removal of the same worktree accepted while this one waited. src/main/runtime/orca-runtime-tests/worktree-ps-agent-row-dismissal.spec.ts: drops the row as soon as the user dismisses it, without waiting for the PTY to exit; tells paired clients to republish on the transition and on the dismissal; rejoins a row through its %s handle after pane ownership clears. src/main/runtime/orca-runtime-tests/hooks-and-hosted-review.spec.ts: reports an error when the SSH filesystem provider is unavailable; reports an SSH orca.yaml as an error without reading any filesystem; reports ok for a local repo hook check; reads SSH issue commands as absent and drops writes without touching any filesystem; allows host integration slug helpers for SSH repos through provider-aware GitHub clients; routes runtime GitHub repo identity helpers through the selected WSL project runtime; routes runtime GitHub issue and work-item actions through the selected WSL project runtime; pins explicit origin preference on runtime open-by-number work item lookups; forwards check-details cancellation without local Git overrides. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. The historical receipt/currentCommand selecting src/main/ssh/ssh-multiplexer-transport-writer.test.ts remains historical and is not a passing result for the reduced current command. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete tail-wait memo suite: its sole current authored callback checks supplied fromTail/blocked field values. Retire all three broader memoization/equivalence/eviction assertion aliases; they are not backed by retained source callbacks. No scan-work budget, sustained output, physical terminal or resource measurement is assigned to this removed test. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1674 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Local macOS evidence covers main-process pending-output and projection-admission caps, saturated-tail wait detection, direct-SSH source retention/accounting, always-on V1 negotiation with legacy peer fallback, exact provider-generation pause ownership, renderer exit/data ordering, decoder input bounds, bounded reconnect scheduling, and WSL stdio transport settlement through deterministic unit contracts. A joined main/runtime oracle exercises renderer-sourced headed semantics and headless-model snapshot semantics without launching a live paired server. Separately, a macOS-hosted Docker OpenSSH run exercises only the deployed Linux relay and direct SSH provider with an exact 256 KiB source-credit plateau, concurrent PTY typing, fixed-size filesystem frames, Git churn, and owner-lease reconnect. Neither deterministic topology labels nor Docker SSH constitute live headed desktop, headless orca serve, or physical WSL evidence; local/daemon, prior-version daemons, Windows named pipes/ConPTY, folder workspaces, and mixed-version clients also remain uncovered. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", + "coverageNotes": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Module layout SSH policy: Retire the multiplexer private-clock saturation registration, the writer saturation transition-count registration and the changed-source dated writer/backpressure receipt. Retained byte delivery, lane order, fairness, settlement and cancellation cases are unvalidated by that historical receipt. No equivalent replacement, new execution, measured performance, live SSH/WSL or cross-platform proof is claimed. The unchanged WSL protocol receipt retains only its original command scope. Historical prior account (preserved verbatim): Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2070 (sources2068/2069): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/hooks-and-hosted-review.spec'). src/main/ssh/ssh-multiplexer-transport-writer.test.ts: reports saturated epochs and their drain boundary exactly once. src/main/runtime/orca-runtime-tests/worktree-removal-archive-hook-gate.spec.ts: removes and records the waiver when the failure is explicitly overridden; removes without an override record when the hook succeeds; removes when the hook is configured but not requested; removes when no archive hook is configured; holds a desktop removal in the same repo until this one is accepted; joins a desktop removal of the same worktree accepted while this one waited. src/main/runtime/orca-runtime-tests/worktree-ps-agent-row-dismissal.spec.ts: drops the row as soon as the user dismisses it, without waiting for the PTY to exit; tells paired clients to republish on the transition and on the dismissal; rejoins a row through its %s handle after pane ownership clears. src/main/runtime/orca-runtime-tests/hooks-and-hosted-review.spec.ts: reports an error when the SSH filesystem provider is unavailable; reports an SSH orca.yaml as an error without reading any filesystem; reports ok for a local repo hook check; reads SSH issue commands as absent and drops writes without touching any filesystem; allows host integration slug helpers for SSH repos through provider-aware GitHub clients; routes runtime GitHub repo identity helpers through the selected WSL project runtime; routes runtime GitHub issue and work-item actions through the selected WSL project runtime; pins explicit origin preference on runtime open-by-number work item lookups; forwards check-details cancellation without local Git overrides. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. The historical receipt/currentCommand selecting src/main/ssh/ssh-multiplexer-transport-writer.test.ts remains historical and is not a passing result for the reduced current command. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete tail-wait memo suite: its sole current authored callback checks supplied fromTail/blocked field values. Retire all three broader memoization/equivalence/eviction assertion aliases; they are not backed by retained source callbacks. No scan-work budget, sustained output, physical terminal or resource measurement is assigned to this removed test. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1674 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Local macOS evidence covers main-process pending-output and projection-admission caps, saturated-tail wait detection, direct-SSH source retention/accounting, always-on V1 negotiation with legacy peer fallback, exact provider-generation pause ownership, renderer exit/data ordering, decoder input bounds, bounded reconnect scheduling, and WSL stdio transport settlement through deterministic unit contracts. A joined main/runtime oracle exercises renderer-sourced headed semantics and headless-model snapshot semantics without launching a live paired server. Separately, a macOS-hosted Docker OpenSSH run exercises only the deployed Linux relay and direct SSH provider with an exact 256 KiB source-credit plateau, concurrent PTY typing, fixed-size filesystem frames, Git churn, and owner-lease reconnect. Neither deterministic topology labels nor Docker SSH constitute live headed desktop, headless orca serve, or physical WSL evidence; local/daemon, prior-version daemons, Windows named pipes/ConPTY, folder workspaces, and mixed-version clients also remain uncovered. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/6836", "https://github.com/stablyai/orca/pull/6858", @@ -9229,7 +9210,7 @@ "https://github.com/stablyai/orca/pull/7054" ], "invariant": "High-volume terminal output must stay bounded across daemon socket writes, SSH relay writer/decoder/source retention/reconnect, main model and projection admission, runtime metadata, detectors, and tail checks, main-to-renderer in-flight bytes, renderer scheduler queues, and hidden-output restore without starving focused input. Every new SSH session offers V1 and every same-build relay supports it; legacy delivery is reachable only when capability negotiation proves the peer cannot use V1. Negotiated SSH spans remain exact and contiguous through activation, exit, cancellation, recovery, desktop projection, and required remote replacement; provisional activation data cannot project, reconnect may release it only into the exact attempt's private recovery quarantine after contract validation, and exit seals that quarantine against later same-token frames until ordered admission or exact cancellation proof. Reconnect checkpoint capture freezes exact provider-generation/PTy admission, cancels queued old work, and waits for the running raw completion before exposing its accepted checkpoint; timeout or failure detaches only that PTy's old model, makes the checkpoint unavailable, and releases the bounded fence without closing the shared provider, while the same failure outside an active exact migration remains generation-fatal and overlapping reconnects preserve any earlier outstanding fence. Pending renderer projection IDs are capped and compacted across split remainders, exit preparation owns its renderer fence through finalization, exit-time cancellation transfers published projections before proof commit, generation close fences late proof while draining exact projection waiters, closed-generation identity compacts without weakening stale rejection, canceled source-delivery retirement retains at most one ordered token per PTY, and pause/resume targets only the exact provider generation. Additional subscribers cannot stall the owner, while the required legacy primary retains backpressure.", - "oracle": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Module layout SSH policy: Retire the multiplexer private-clock saturation registration, the writer saturation transition-count registration and the changed-source dated writer/backpressure receipt. Retained byte delivery, lane order, fairness, settlement and cancellation cases are unvalidated by that historical receipt. No equivalent replacement, new execution, measured performance, live SSH/WSL or cross-platform proof is claimed. The unchanged WSL protocol receipt retains only its original command scope. Historical prior account (preserved verbatim): Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2070 (sources2068/2069): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/hooks-and-hosted-review.spec'). src/main/ssh/ssh-multiplexer-transport-writer.test.ts: reports saturated epochs and their drain boundary exactly once. src/main/runtime/orca-runtime-tests/worktree-removal-archive-hook-gate.spec.ts: removes and records the waiver when the failure is explicitly overridden; removes without an override record when the hook succeeds; removes when the hook is configured but not requested; removes when no archive hook is configured; holds a desktop removal in the same repo until this one is accepted; joins a desktop removal of the same worktree accepted while this one waited. src/main/runtime/orca-runtime-tests/worktree-ps-agent-row-dismissal.spec.ts: drops the row as soon as the user dismisses it, without waiting for the PTY to exit; tells paired clients to republish on the transition and on the dismissal; rejoins a row through its %s handle after pane ownership clears. src/main/runtime/orca-runtime-tests/hooks-and-hosted-review.spec.ts: reports an error when the SSH filesystem provider is unavailable; reports an SSH orca.yaml as an error without reading any filesystem; reports ok for a local repo hook check; reads SSH issue commands as absent and drops writes without touching any filesystem; allows host integration slug helpers for SSH repos through provider-aware GitHub clients; routes runtime GitHub repo identity helpers through the selected WSL project runtime; routes runtime GitHub issue and work-item actions through the selected WSL project runtime; pins explicit origin preference on runtime open-by-number work item lookups; forwards check-details cancellation without local Git overrides. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. The historical receipt/currentCommand selecting src/main/ssh/ssh-multiplexer-transport-writer.test.ts remains historical and is not a passing result for the reduced current command. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete tail-wait memo suite: its sole current authored callback checks supplied fromTail/blocked field values. Retire all three broader memoization/equivalence/eviction assertion aliases; they are not backed by retained source callbacks. No scan-work budget, sustained output, physical terminal or resource measurement is assigned to this removed test. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1674 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: The current executable slice injects main-process renderer backlog pressure, then asserts ordinary terminal-wait detection without a joined-tail allocation and with full prompt-family continuity, per-PTY and total pending-output caps, a 1,024-ID projection cap with split-remainder compaction, preserved sequenced-tail metadata, active-pending protection ahead of background trimming, exact provider-generation pause ownership, renderer exit/data ordering, and ACK-gated in-flight bounds. The negotiation seam proves every initial connection and reconnect offers V1, the same-build relay advertises V1 without launch flags, old clients remain token-free, and method-not-found peers fall back without installing source ACK publication. The joined source-intake/multiplex oracle blocks renderer or headless serialization, admits one snapshot-covered span and one trailing span, requires replacement reservation only after the authoritative sequence is known, advances upstream ACK eligibility exactly to the covered boundary after SnapshotEnd, and delivers then ACKs the trailing span through the ordinary live path without cancellation. Source-range ledger and multiplex seams additionally require cumulative byte ACK 40 then 100 for one 100-byte frame to release exact byte credit without early source settlement, admit a contiguous higher-generation recovered token while the prior token remains unsettled, and reject stale stream or source generations without releasing in-flight byte credit. Replacement reservation rolls back every earlier span on a later failure, commit requires the exact frozen transfer states, and an authoritatively reclaimed covered span rejects commit without local trim authority while rollback removes the reservation idempotently. The exit-deadline oracle publishes a source-backed desktop projection, advances a fake clock to cancellation, and requires one projection transfer, proof commit, preparation, and final exit with no provider close, retained obligation, duplicate finalization, or process-lifetime cancellation tombstone; a second controlled proof remains pending across generation close and must never publish final exit, while both paths release the renderer preparation lease exactly once. The model-migration oracle accepts span A, blocks span B's raw emulator completion, begins disconnect migration, and proves no checkpoint or reconnect attach can remain at A; releasing B advances the exact checkpoint to B and projects B once. Its fake-clock arm reaches the 10-second deadline, requires checkpoint-unavailable, one exact model reset, zero retained admission charge and timers, and no effect from late raw settlement. Its two-PTY failure arm rejects one running raw callback under an active exact migration and requires one checkpoint-unavailable result, one model reset, zero provider closes, retained sibling checkpoint and transport liveness, zero charges and timers, and an unaffected other generation; a non-migrating sibling oracle still requires generation close. The intake additionally closes 2,048 sequential provider generations into one exact range, rejects stale events across that range, and preserves an out-of-order live gap until its own close. Direct-SSH contracts additionally prove activation-response settlement before the first source frame, claim-gated projection under same-decoder-turn response/data delivery, exact early-ACK reservation through send settlement or same-token retry, recovery completion visible before lease-held frames transfers those frames only into the private quarantine and admits no output until exact checkpoint-to-recoveryEnd coverage, invalid-checkpoint restore retention through response settlement followed by fresh-token retry and one live source frame, isolated saturated-subscriber eviction with healthy owner liveness, preserved legacy-primary backpressure, one-write ownership across write(false)/drain, control selection ahead of queued ordinary PTY input with FIFO lanes and bounded fairness, liveness rebasing, failed-exit retention through late ACK or exact recovery, response-settlement cancellation authority, token-scoped exit cancellation, exact checkpoint-to-recoveryEnd continuity, stale-owner one-shot retry, no physical PTY teardown on recovery failure, exact provider-generation closure and once-only cleanup when recovery cancellation publication or proof rejects, 16 MiB-plus-1 MiB decoder caps, charged per-PTY/session retention budgets, and eight-wide isolated reattach. The live Electron perf oracle adds hidden-output floods, renderer scheduler queue depth, dropped-output-zero normal scenarios, and active key latency budgets before promotion.", + "oracle": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Module layout SSH policy: Retire the multiplexer private-clock saturation registration, the writer saturation transition-count registration and the changed-source dated writer/backpressure receipt. Retained byte delivery, lane order, fairness, settlement and cancellation cases are unvalidated by that historical receipt. No equivalent replacement, new execution, measured performance, live SSH/WSL or cross-platform proof is claimed. The unchanged WSL protocol receipt retains only its original command scope. Historical prior account (preserved verbatim): Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2070 (sources2068/2069): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/hooks-and-hosted-review.spec'). src/main/ssh/ssh-multiplexer-transport-writer.test.ts: reports saturated epochs and their drain boundary exactly once. src/main/runtime/orca-runtime-tests/worktree-removal-archive-hook-gate.spec.ts: removes and records the waiver when the failure is explicitly overridden; removes without an override record when the hook succeeds; removes when the hook is configured but not requested; removes when no archive hook is configured; holds a desktop removal in the same repo until this one is accepted; joins a desktop removal of the same worktree accepted while this one waited. src/main/runtime/orca-runtime-tests/worktree-ps-agent-row-dismissal.spec.ts: drops the row as soon as the user dismisses it, without waiting for the PTY to exit; tells paired clients to republish on the transition and on the dismissal; rejoins a row through its %s handle after pane ownership clears. src/main/runtime/orca-runtime-tests/hooks-and-hosted-review.spec.ts: reports an error when the SSH filesystem provider is unavailable; reports an SSH orca.yaml as an error without reading any filesystem; reports ok for a local repo hook check; reads SSH issue commands as absent and drops writes without touching any filesystem; allows host integration slug helpers for SSH repos through provider-aware GitHub clients; routes runtime GitHub repo identity helpers through the selected WSL project runtime; routes runtime GitHub issue and work-item actions through the selected WSL project runtime; pins explicit origin preference on runtime open-by-number work item lookups; forwards check-details cancellation without local Git overrides. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. The historical receipt/currentCommand selecting src/main/ssh/ssh-multiplexer-transport-writer.test.ts remains historical and is not a passing result for the reduced current command. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Delete the complete tail-wait memo suite: its sole current authored callback checks supplied fromTail/blocked field values. Retire all three broader memoization/equivalence/eviction assertion aliases; they are not backed by retained source callbacks. No scan-work budget, sustained output, physical terminal or resource measurement is assigned to this removed test. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1674 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: The current executable slice injects main-process renderer backlog pressure, then asserts ordinary terminal-wait detection without a joined-tail allocation and with full prompt-family continuity, per-PTY and total pending-output caps, a 1,024-ID projection cap with split-remainder compaction, preserved sequenced-tail metadata, active-pending protection ahead of background trimming, exact provider-generation pause ownership, renderer exit/data ordering, and ACK-gated in-flight bounds. The negotiation seam proves every initial connection and reconnect offers V1, the same-build relay advertises V1 without launch flags, old clients remain token-free, and method-not-found peers fall back without installing source ACK publication. The joined source-intake/multiplex oracle blocks renderer or headless serialization, admits one snapshot-covered span and one trailing span, requires replacement reservation only after the authoritative sequence is known, advances upstream ACK eligibility exactly to the covered boundary after SnapshotEnd, and delivers then ACKs the trailing span through the ordinary live path without cancellation. Source-range ledger and multiplex seams additionally require cumulative byte ACK 40 then 100 for one 100-byte frame to release exact byte credit without early source settlement, admit a contiguous higher-generation recovered token while the prior token remains unsettled, and reject stale stream or source generations without releasing in-flight byte credit. Replacement reservation rolls back every earlier span on a later failure, commit requires the exact frozen transfer states, and an authoritatively reclaimed covered span rejects commit without local trim authority while rollback removes the reservation idempotently. The exit-deadline oracle publishes a source-backed desktop projection, advances a fake clock to cancellation, and requires one projection transfer, proof commit, preparation, and final exit with no provider close, retained obligation, duplicate finalization, or process-lifetime cancellation tombstone; a second controlled proof remains pending across generation close and must never publish final exit, while both paths release the renderer preparation lease exactly once. The model-migration oracle accepts span A, blocks span B's raw emulator completion, begins disconnect migration, and proves no checkpoint or reconnect attach can remain at A; releasing B advances the exact checkpoint to B and projects B once. Its fake-clock arm reaches the 10-second deadline, requires checkpoint-unavailable, one exact model reset, zero retained admission charge and timers, and no effect from late raw settlement. Its two-PTY failure arm rejects one running raw callback under an active exact migration and requires one checkpoint-unavailable result, one model reset, zero provider closes, retained sibling checkpoint and transport liveness, zero charges and timers, and an unaffected other generation; a non-migrating sibling oracle still requires generation close. The intake additionally closes 2,048 sequential provider generations into one exact range, rejects stale events across that range, and preserves an out-of-order live gap until its own close. Direct-SSH contracts additionally prove activation-response settlement before the first source frame, claim-gated projection under same-decoder-turn response/data delivery, exact early-ACK reservation through send settlement or same-token retry, recovery completion visible before lease-held frames transfers those frames only into the private quarantine and admits no output until exact checkpoint-to-recoveryEnd coverage, invalid-checkpoint restore retention through response settlement followed by fresh-token retry and one live source frame, isolated saturated-subscriber eviction with healthy owner liveness, preserved legacy-primary backpressure, one-write ownership across write(false)/drain, control selection ahead of queued ordinary PTY input with FIFO lanes and bounded fairness, liveness rebasing, failed-exit retention through late ACK or exact recovery, response-settlement cancellation authority, token-scoped exit cancellation, exact checkpoint-to-recoveryEnd continuity, stale-owner one-shot retry, no physical PTY teardown on recovery failure, exact provider-generation closure and once-only cleanup when recovery cancellation publication or proof rejects, 16 MiB-plus-1 MiB decoder caps, charged per-PTY/session retention budgets, and eight-wide isolated reattach. The live Electron perf oracle adds hidden-output floods, renderer scheduler queue depth, dropped-output-zero normal scenarios, and active key latency budgets before promotion.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/construction/orca-runtime.test.ts", @@ -9376,7 +9357,18 @@ "Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed.", "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed.", "Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation.", - "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed." + "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed.", + "Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked.", + "Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed.", + "Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms.", + "Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery.", + "Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction.", + "Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed.", + "Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed.", + "Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed.", + "Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed.", + "Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed.", + "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired." ], "demotionRule": "Cannot promote without metric artifacts and bounded-byte assertions." }, @@ -9632,8 +9624,7 @@ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/paired-reconnect-multi-pane-materialization.test.ts src/renderer/src/runtime/web-session-tabs-sync/agent-status/paired-reconnect-sidebar-agent-count.test.ts --reporter=dot", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts src/renderer/src/components/terminal-pane/mounting/queued-pane-startup.test.ts src/renderer/src/components/terminal-pane/parking/one-shot-park-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/store/terminals/terminal-pty-identity-replacement.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-detach-attach-handoff.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts --reporter=dot", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-expired-pane-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-outcome-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/remote-runtime-pty-recovery-state.test.ts src/shared/remote-runtime/remote-runtime-socket-liveness.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-socket-generation.test.ts src/shared/remote-runtime/remote-runtime-client-error-classification.test.ts src/main/runtime/rpc/remote-runtime-server-heartbeat.test.ts src/main/runtime/terminal/launch/orca-runtime-terminal-create-idempotency.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-expired-pane-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-outcome-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/remote-runtime-pty-recovery-state.test.ts src/shared/remote-runtime/remote-runtime-socket-liveness.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-socket-generation.test.ts src/shared/remote-runtime/remote-runtime-client-error-classification.test.ts src/main/runtime/rpc/remote-runtime-server-heartbeat.test.ts src/main/runtime/terminal/launch/orca-runtime-terminal-create-idempotency.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/ui/workspace-navigation-history-hydration.test.ts src/renderer/src/store/repos/repos-ssh-host-reconciliation.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts --reporter=dot", "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/ssh/ssh.test.ts src/main/ipc/worktrees-ssh-repo-owner-resolution.test.ts src/main/ssh/connection/ssh-connection-manager.test.ts src/main/ssh/connection/ssh-connection.test.ts src/preload/ssh-authority-forwarding.test.ts src/renderer/src/runtime/rpc/subscriptions/runtime-client-events.test.ts src/renderer/src/runtime/hosts/ssh/runtime-environment-ssh-state.test.ts src/shared/ssh-retained-payload-admission.test.ts --reporter=dot", "pnpm exec electron-vite build --mode e2e", @@ -9649,7 +9640,6 @@ "src/renderer/src/components/terminal-pane/parking/one-shot-park-replay.test.ts", "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-detach-attach-handoff.test.ts", - "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-expired-pane-recovery.test.ts", @@ -9697,15 +9687,6 @@ "replacement polling and each in-flight request share a 15-second deadline, then accepted snapshots own recovery without input re-arming polling" ] }, - { - "file": "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts", - "assertions": [ - "cold restored-terminal subscription failure retries and resumes snapshot, output, and input without a fatal error", - "a canonical close before subscription readiness opens exactly one replacement stream without surfacing a fatal error", - "cached terminal pixels remain disconnected until authoritative replay completes", - "authoritative current-stream replay clears every distinct error surfaced by that transport" - ] - }, { "file": "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts", "assertions": [ @@ -9896,7 +9877,8 @@ "Audit2195 removes the low-value startup admission boolean and host deferral predicate inventories from background-terminal-worktree-mount.test.ts. The reduced source retains concrete targeted mount, prune and reveal ownership transitions, not those oracle assertions. Existing selectors remain unexecuted here; no equivalent replacement or new runtime/provider/platform/budget result is claimed. Original retired assertion object verbatim:\n{\n \"file\": \"src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts\",\n \"assertions\": [\n \"startup waits for hydration before mounting terminal panes while degraded mode remains interactive\",\n \"only an explicit local execution host can defer cold activation\",\n \"SSH, remote-runtime, and unresolved owners remain eager\"\n ]\n }", "Audit2709 removes src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts: intentionally retire waiting out an older inventory and sharing a fresh post-operation request among concurrent callers. No equivalent replacement or fresh execution is claimed. Dated receipts remain historical evidence for their unchanged selectors only; they do not validate the reduced commands or full current gate. Original prospective command before removal: pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts src/renderer/src/components/terminal-pane/mounting/queued-pane-startup.test.ts src/renderer/src/components/terminal-pane/parking/one-shot-park-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/store/terminals/terminal-pty-identity-replacement.test.ts", "Audit3086: retire the destroyed fresh-session fallback settlement/non-destructive reattach alias and its direct selector; retain the detach/attach selector and assertion unchanged. The retained admission cases cover adopt-before-data and explicitly buffered-death admission without a new spawn, without replacing fallback retirement. July 28 evidence was already archived by audit3077; no duplicate retirement or new execution is claimed.", - "Audit3096: remove the deleted reconnect-banner selector and its two UI aliases. The reduced recovery-state selector retains only the deadline cancelling pending backoff and explicit retry owning a new epoch alias; healthy-replacement stale-schedule and independent caller-cutoff cancellation aliases retire. Other retained state cases cover reentrant retry, owner settlement and same-epoch parked revival, without replacing those removed alarms. Prior admission-selector retirement, archived July 28 receipt, unrelated selectors/claims/receipts and old gaps remain unchanged; no fresh execution is claimed." + "Audit3096: remove the deleted reconnect-banner selector and its two UI aliases. The reduced recovery-state selector retains only the deadline cancelling pending backoff and explicit retry owning a new epoch alias; healthy-replacement stale-schedule and independent caller-cutoff cancellation aliases retire. Other retained state cases cover reentrant retry, owner settlement and same-epoch parked revival, without replacing those removed alarms. Prior admission-selector retirement, archived July 28 receipt, unrelated selectors/claims/receipts and old gaps remain unchanged; no fresh execution is claimed.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts. Lost alarms: attaches to an existing remote runtime terminal handle; reports a rejected multiplex write through the pane recovery callback; does not report a rejected write from a superseded multiplex stream; does not report attachment health until the authoritative PTY snapshot arrives; drops its side-effect gauge from the census on %s; recovers when the first restored-terminal subscription attempt is offline; recovers when the runtime closes before a restored subscription becomes ready; surfaces a fatal error during subscription setup exactly once; reports viewer disconnect as an unverifiable remote exit; clears every retained transport error when the current stream becomes healthy. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Cannot promote if provider failure can close panes or if the oracle is screenshot-only." }, @@ -12079,7 +12061,7 @@ "providers": ["remote-runtime", "ssh", "local", "daemon"], "coveredPlatforms": [], "coveredProviders": [], - "coverageNotes": "Audit1400 intentionally deletes or reduces the listed sources. No fresh execution result validates their reduced selectors. Historical coverage account: Local macOS evidence covers runtime-RPC stream budgets, paired-renderer parse/discard credit, and negotiated host-side suppression for hidden paired desktop panes. Deferred credit is shared by local and remote transports, batches ACKs at 192 KiB or 4 ms, grows per-stream windows from 512 KiB to 2 MiB and aggregate windows from 2 MiB to 8 MiB, bounds queued output to 256 KiB per stream, and caps each multiplex connection at 32 active or pending streams for an 8 MiB aggregate pending-output ceiling. Deterministic tests cover replay ordering, stale generations, malformed frames, hidden panes, queue eviction, disposal, send/recovery failure, repeated pending-slot replacement, reconnect, mixed-version pause negotiation, and round-robin fairness. The opt-in benchmark models 1/20/100 ms RTT and 1/4/8 viewers, throughput, window use, completion spread and protocol-frame counts. It measures model scheduler elapsed time separately from actual @xterm/headless parser CPU/retained heap; modeled credit and ACK results do not qualify production credit enforcement. Live Android restore evidence, browser/WebGL parser measurements, and legacy JSON subscribe parity remain required. The audit removed the PTY ACK wrapper suite, which replayed credit arithmetic with mocked IPC. Its isolated wrapper alarms are no longer registered here; generic credit and runtime stream tests do not establish equivalent PTY integration coverage. The audit removes the standalone auto-settle-before-deferred-claim example. Nested-owner and claimed-child completion tests remain; they do not establish that removed timing alarm. The test-value audit removes the bulk-ACK threshold, output-pause capability/paused-input, transformed-metadata disposal, late-parser and renderer-throw examples. The retained captured-parser, malformed-frame and failed-ACK timer checks do not exercise real xterm parsing or server credit enforcement and do not replace the removed alarms. Historical run commands and results remain historical. The test-value audit removes the pure mobile stream-decision suite: pause/resume/rearm/viewport branch inventories no longer have that isolated alarm. The surviving stream-hook tests do not establish equivalent WebView restore, PTY resize, input-lease or subscription delivery coverage. Historical run commands and counts are preserved as historical records; a narrowed selector is not a new passed run. Audit1302: src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts: Keep production RPC dispatcher/terminal framing with emitted binary bytes, UTF-8 reconstruction and client ACK releasing genuinely withheld output; also keep the direct binary-subscribe chunk boundary. Remove shared-budget fairness matrix that sleeps real time and relies on exact synthetic per-stream window scheduling. Lose that eight-stream fairness/call wiring alarm; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1341 scope1334 removes/narrows mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1391: src/renderer/src/components/terminal-pane/pty-connection-remote-pause-drop-notice.spec.ts reduced. Lose repeated quiet switches avoiding snapshots and legacy host without drop-report capability requesting a snapshot. Retained imported registrar uses fake transport/xterm writes, exercising real gap/restore lifecycle rather than actual terminal rendering. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1400: src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts removed. Lose ORCA_TERMINAL_PERF_BENCH diagnostic report and local xterm parser measurements; this was not an ordinary CI assertion gate. No equivalent replacement coverage is claimed.", + "coverageNotes": "Audit1400 intentionally deletes or reduces the listed sources. No fresh execution result validates their reduced selectors. Historical coverage account: Local macOS evidence covers runtime-RPC stream budgets, paired-renderer parse/discard credit, and negotiated host-side suppression for hidden paired desktop panes. Deferred credit is shared by local and remote transports, batches ACKs at 192 KiB or 4 ms, grows per-stream windows from 512 KiB to 2 MiB and aggregate windows from 2 MiB to 8 MiB, bounds queued output to 256 KiB per stream, and caps each multiplex connection at 32 active or pending streams for an 8 MiB aggregate pending-output ceiling. Deterministic tests cover replay ordering, stale generations, malformed frames, hidden panes, queue eviction, disposal, send/recovery failure, repeated pending-slot replacement, reconnect, mixed-version pause negotiation, and round-robin fairness. The opt-in benchmark models 1/20/100 ms RTT and 1/4/8 viewers, throughput, window use, completion spread and protocol-frame counts. It measures model scheduler elapsed time separately from actual @xterm/headless parser CPU/retained heap; modeled credit and ACK results do not qualify production credit enforcement. Live Android restore evidence, browser/WebGL parser measurements, and legacy JSON subscribe parity remain required. The audit removed the PTY ACK wrapper suite, which replayed credit arithmetic with mocked IPC. Its isolated wrapper alarms are no longer registered here; generic credit and runtime stream tests do not establish equivalent PTY integration coverage. The audit removes the standalone auto-settle-before-deferred-claim example. Nested-owner and claimed-child completion tests remain; they do not establish that removed timing alarm. The test-value audit removes the bulk-ACK threshold, output-pause capability/paused-input, transformed-metadata disposal, late-parser and renderer-throw examples. The retained captured-parser, malformed-frame and failed-ACK timer checks do not exercise real xterm parsing or server credit enforcement and do not replace the removed alarms. Historical run commands and results remain historical. The test-value audit removes the pure mobile stream-decision suite: pause/resume/rearm/viewport branch inventories no longer have that isolated alarm. The surviving stream-hook tests do not establish equivalent WebView restore, PTY resize, input-lease or subscription delivery coverage. Historical run commands and counts are preserved as historical records; a narrowed selector is not a new passed run. Audit1302: src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts: Keep production RPC dispatcher/terminal framing with emitted binary bytes, UTF-8 reconstruction and client ACK releasing genuinely withheld output; also keep the direct binary-subscribe chunk boundary. Remove shared-budget fairness matrix that sleeps real time and relies on exact synthetic per-stream window scheduling. Lose that eight-stream fairness/call wiring alarm; no equivalent replacement. Reduced selectors require fresh validation; prior full-suite receipts do not validate removed or changed coverage. No equivalent replacement or physical-provider claim. Audit1341 scope1334 removes/narrows mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts. Exact retired scenarios are intentionally lost; surviving assertions are limited to unchanged retained authored cases. No equivalent replacement, physical phone or deployed mixed-host claim. Fresh validation is required for narrowed commands. Audit1391: src/renderer/src/components/terminal-pane/pty-connection-remote-pause-drop-notice.spec.ts reduced. Lose repeated quiet switches avoiding snapshots and legacy host without drop-report capability requesting a snapshot. Retained imported registrar uses fake transport/xterm writes, exercising real gap/restore lifecycle rather than actual terminal rendering. No equivalent replacement coverage is claimed. The existing primary-selector receipt and its claims remain scoped to their unchanged sources; the broad optional command has no new execution result. Audit1400: src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts removed. Lose ORCA_TERMINAL_PERF_BENCH diagnostic report and local xterm parser measurements; this was not an ordinary CI assertion gate. No equivalent replacement coverage is claimed. Broader617 Mobile owner follow-up retires six whole work-count/timing registrations from mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts, retaining five functional handoff/readiness/absence/recovery registrations. Prior Audit1400 retained-but-unvalidated and archived inventories describe earlier source and remain historical after this follow-up. No equivalent replacement, fresh execution, timing measurement, physical phone, or deployed mixed-host validation is claimed by this source change. Current premerge review correction for the Broader617 Mobile owner follow-up: four additional whole mock-only/mock-produced-state stream registrations are retired, for ten total cuts and one unchanged retained registration, keeps asking until a tab snapshot replaces the exhausted handle. The preceding five-retained description records the superseded candidate, not current coverage. The retained case checks the real hook-owned recovery-needed result; handoff, readiness and absence-marker mock-delivery alarms are intentionally lost without equivalent replacement. Dead test render counting and unused handoff setup are removed. No new native, physical-phone, timing or deployed-host proof is implied.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/6951", "https://github.com/stablyai/orca/pull/6955", @@ -12136,7 +12118,9 @@ "Audit1400 retained but unvalidated assertion inventory pending a new run of the reduced command:\n[\n {\n \"file\": \"src/main/runtime/rpc/terminal-subscribe-buffer.test.ts\",\n \"assertions\": [\n \"legacy binary output queued during initial snapshot serialization stays bounded\",\n \"stale mobile resize re-stream completions are dropped for legacy binary streams\",\n \"aborted stream signals do not register stale listeners\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-output-batching.test.ts\",\n \"assertions\": [\n \"desktop output bursts coalesce before stream emission\",\n \"large binary output is encoded lazily before the first output frame\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts\",\n \"assertions\": [\n \"requested snapshots fall back smaller when serialized data exceeds the send budget\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts\",\n \"assertions\": [\n \"oversized live output frames are bounded for subscribed binary streams\",\n \"multibyte live output flushes when encoded bytes reach the batch budget\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts\",\n \"assertions\": [\n \"send and recovery serialization failures detach once instead of leaking credit or retrying forever\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts\",\n \"assertions\": [\n \"32 active or pending slots cap aggregate queued output and repeated pending-slot subscribe cancels its older waiter\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts\",\n \"assertions\": [\n \"three negotiated hidden desktop streams emit zero output frames under sustained load while an unpaused stream remains live\",\n \"an older client that omits pause negotiation continues receiving output\"\n ]\n },\n {\n \"file\": \"src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts\",\n \"assertions\": [\n \"a runtime-owned hidden pane pauses output, consumes status/title/theme facts, and keeps input writable\",\n \"reveal resumes before one authoritative snapshot and exact live output with no hidden raw write, loss, or duplication\",\n \"dispose releases pause and unregisters the fact consumer\"\n ]\n },\n {\n \"file\": \"src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stream-reconnect.test.ts\",\n \"assertions\": [\n \"desired pause reapplies after either capability/snapshot ordering and across reconnect\",\n \"reconnect delivers each authoritative snapshot and post-resume live marker exactly once\"\n ]\n },\n {\n \"file\": \"src/renderer/src/lib/pane-manager/terminal-delivery-credit.test.ts\",\n \"assertions\": [\n \"nested synchronous deliveries restore the outer credit owner\",\n \"every claimed scheduler child must settle before the parent credits\"\n ]\n },\n {\n \"file\": \"src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts\",\n \"assertions\": [\n \"paired renderer ACK waits for the output scheduler captured parse callback before releasing encoded-byte credit\",\n \"unknown stream output is unsubscribed and malformed framing closes the connection; malformed transformed data is discarded and credited before resync\",\n \"an ACK write failure tears down the stream with zero rearmed flush timers under fake time\"\n ]\n },\n {\n \"file\": \"mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts\",\n \"assertions\": [\n \"trades a lease-only stream for output when a chat tab hands off to a terminal tab\",\n \"repairs a lease-only stream once its WebView reports ready\",\n \"stops rearming a handle whose stream never comes back (covered: %s, #10681)\",\n \"does not charge the rearm budget for a subscribe its own gates turned away\",\n \"charges the rearm budget for a subscribe the bridge refused before returning\",\n \"refills the rearm budget when terminal.list reports the handle again (covered: %s, #10681)\",\n \"does not let a dead PTY buy a new budget with its own `subscribed` ack\",\n \"refills the budget for a rearmed stream that outlives the teardown window\",\n \"keeps an absence marker observed before the budget ran out\",\n \"keeps asking until a tab snapshot replaces the exhausted handle\",\n \"resumes a cold-start lease-only stream when WebView readiness arrives late\"\n ]\n }\n]", "Audit1400 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts\",\n \"assertions\": [\n \"the opt-in benchmark reports modeled RTT throughput, window use, protocol-frame counts and completion spread, with model scheduler elapsed time and separately measured @xterm/headless parser CPU and retained heap\"\n ]\n }\n]", "Audit1400 archived original active assertionRefs verbatim after retirement of all passed receipts:\n[\n {\n \"file\": \"src/main/runtime/rpc/terminal-subscribe-buffer.test.ts\",\n \"assertions\": [\n \"legacy binary output queued during initial snapshot serialization stays bounded\",\n \"stale mobile resize re-stream completions are dropped for legacy binary streams\",\n \"aborted stream signals do not register stale listeners\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-output-batching.test.ts\",\n \"assertions\": [\n \"desktop output bursts coalesce before stream emission\",\n \"large binary output is encoded lazily before the first output frame\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts\",\n \"assertions\": [\n \"requested snapshots fall back smaller when serialized data exceeds the send budget\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts\",\n \"assertions\": [\n \"oversized live output frames are bounded for subscribed binary streams\",\n \"multibyte live output flushes when encoded bytes reach the batch budget\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts\",\n \"assertions\": [\n \"send and recovery serialization failures detach once instead of leaking credit or retrying forever\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts\",\n \"assertions\": [\n \"32 active or pending slots cap aggregate queued output and repeated pending-slot subscribe cancels its older waiter\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts\",\n \"assertions\": [\n \"three negotiated hidden desktop streams emit zero output frames under sustained load while an unpaused stream remains live\",\n \"an older client that omits pause negotiation continues receiving output\"\n ]\n },\n {\n \"file\": \"src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts\",\n \"assertions\": [\n \"a runtime-owned hidden pane pauses output, consumes status/title/theme facts, and keeps input writable\",\n \"reveal resumes before one authoritative snapshot and exact live output with no hidden raw write, loss, or duplication\",\n \"dispose releases pause and unregisters the fact consumer\"\n ]\n },\n {\n \"file\": \"src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stream-reconnect.test.ts\",\n \"assertions\": [\n \"desired pause reapplies after either capability/snapshot ordering and across reconnect\",\n \"reconnect delivers each authoritative snapshot and post-resume live marker exactly once\"\n ]\n },\n {\n \"file\": \"src/renderer/src/lib/pane-manager/terminal-delivery-credit.test.ts\",\n \"assertions\": [\n \"nested synchronous deliveries restore the outer credit owner\",\n \"every claimed scheduler child must settle before the parent credits\"\n ]\n },\n {\n \"file\": \"src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts\",\n \"assertions\": [\n \"paired renderer ACK waits for the output scheduler captured parse callback before releasing encoded-byte credit\",\n \"unknown stream output is unsubscribed and malformed framing closes the connection; malformed transformed data is discarded and credited before resync\",\n \"an ACK write failure tears down the stream with zero rearmed flush timers under fake time\"\n ]\n },\n {\n \"file\": \"src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts\",\n \"assertions\": [\n \"the opt-in benchmark reports modeled RTT throughput, window use, protocol-frame counts and completion spread, with model scheduler elapsed time and separately measured @xterm/headless parser CPU and retained heap\"\n ]\n },\n {\n \"file\": \"mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts\",\n \"assertions\": [\n \"trades a lease-only stream for output when a chat tab hands off to a terminal tab\",\n \"repairs a lease-only stream once its WebView reports ready\",\n \"stops rearming a handle whose stream never comes back (covered: %s, #10681)\",\n \"does not charge the rearm budget for a subscribe its own gates turned away\",\n \"charges the rearm budget for a subscribe the bridge refused before returning\",\n \"refills the rearm budget when terminal.list reports the handle again (covered: %s, #10681)\",\n \"does not let a dead PTY buy a new budget with its own `subscribed` ack\",\n \"refills the budget for a rearmed stream that outlives the teardown window\",\n \"keeps an absence marker observed before the budget ran out\",\n \"keeps asking until a tab snapshot replaces the exhausted handle\",\n \"resumes a cold-start lease-only stream when WebView readiness arrives late\"\n ]\n }\n ]", - "Audit1400 archived active selectors after retirement of all passed receipts: {\"commands\":[\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts --maxWorkers=1\",\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/main/runtime/rpc/terminal-output-batching.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts\",\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/main/runtime/rpc/terminal-output-batching.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/lib/pane-manager/terminal-delivery-credit.test.ts src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts src/renderer/src/runtime/runtime-terminal-stream.test.ts --maxWorkers=1\",\"ORCA_TERMINAL_PERF_BENCH=1 pnpm exec vitest run --config config/vitest.config.ts --disableConsoleIntercept src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts\",\"pnpm --dir mobile exec vitest run --root .. mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts\"],\"testFiles\":[\"src/main/runtime/rpc/terminal-subscribe-buffer.test.ts\",\"src/main/runtime/rpc/terminal-output-batching.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts\",\"src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts\",\"src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stream-reconnect.test.ts\",\"src/renderer/src/lib/pane-manager/terminal-delivery-credit.test.ts\",\"src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts\",\"src/renderer/src/runtime/runtime-terminal-stream.test.ts\",\"mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts\"]}" + "Audit1400 archived active selectors after retirement of all passed receipts: {\"commands\":[\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts --maxWorkers=1\",\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/main/runtime/rpc/terminal-output-batching.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts\",\"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/rpc/terminal-subscribe-buffer.test.ts src/main/runtime/rpc/terminal-output-batching.test.ts src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/lib/pane-manager/terminal-delivery-credit.test.ts src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts src/renderer/src/runtime/runtime-terminal-stream.test.ts --maxWorkers=1\",\"ORCA_TERMINAL_PERF_BENCH=1 pnpm exec vitest run --config config/vitest.config.ts --disableConsoleIntercept src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts\",\"pnpm --dir mobile exec vitest run --root .. mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts\"],\"testFiles\":[\"src/main/runtime/rpc/terminal-subscribe-buffer.test.ts\",\"src/main/runtime/rpc/terminal-output-batching.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-snapshot-serialization.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-ack-output-budget.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-ack-overflow-recovery.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-pty-wait-capacity.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-output-pause-and-viewport.test.ts\",\"src/main/runtime/rpc/terminal-multiplex-flow-control.bench.test.ts\",\"src/renderer/src/components/terminal-pane/pty-connection-hidden-replay.test.ts\",\"src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stream-reconnect.test.ts\",\"src/renderer/src/lib/pane-manager/terminal-delivery-credit.test.ts\",\"src/renderer/src/runtime/remote-runtime-terminal-parse-backpressure.test.ts\",\"src/renderer/src/runtime/runtime-terminal-stream.test.ts\",\"mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts\"]}", + "Broader617 Mobile owner follow-up retires six whole work-count/timing registrations from mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts, retaining five functional handoff/readiness/absence/recovery registrations. Prior Audit1400 retained-but-unvalidated and archived inventories describe earlier source and remain historical after this follow-up. No equivalent replacement, fresh execution, timing measurement, physical phone, or deployed mixed-host validation is claimed by this source change.", + "Current premerge review correction for the Broader617 Mobile owner follow-up: four additional whole mock-only/mock-produced-state stream registrations are retired, for ten total cuts and one unchanged retained registration, keeps asking until a tab snapshot replaces the exhausted handle. The preceding five-retained description records the superseded candidate, not current coverage. The retained case checks the real hook-owned recovery-needed result; handoff, readiness and absence-marker mock-delivery alarms are intentionally lost without equivalent replacement. Dead test render counting and unused handoff setup are removed. No new native, physical-phone, timing or deployed-host proof is implied." ], "demotionRule": "Cannot promote while a supported stream path has uncapped snapshot or live-output buffering." }, @@ -12752,14 +12736,14 @@ "providers": ["local", "remote-runtime", "ssh"], "coveredPlatforms": ["macos", "linux"], "coveredProviders": ["local", "remote-runtime", "ssh"], - "coverageNotes": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2056 (sources2054/2055): Intentionally retire these low-value alarms without replacement: src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts: delivers remote file changes to listeners for the same worktree. src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-part-14.spec'). src/main/runtime/orca-runtime-tests/terminal-side-effect-facts-part-03.spec.ts: arms the stale-title timer for a seeded working title; arms the Command Code scrape from the noted spawn command; prefers the tracked title over a stale renderer lastTitle in the hydration seed; writes recent output into the current runtime output map; uses a later title handler replacement with the runtime receiver; calls the current injected listener with the runtime receiver; keeps optional reconciliation lazy after its callback is removed. src/main/runtime/orca-runtime-tests/mobile-session-tabs-part-14.spec.ts: lists them, so a paired client can mirror a migrated editor; closes one by retiring it from the host session; keeps an unsaved draft unless the close is forced; shares aggregate work but leaves a replacement pending promise alone; lets a targeted refresh finish while an aggregate refresh waits; refuses a close when the store lacks %s before the guard; keeps a later missing %s on the original caught failure path. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Exact retired assertionRefs, preserved as historical objects only: {\n \"file\": \"src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts\",\n \"assertions\": [\n \"terminal watcher error and setup failure evict the shared entry before retrying callbacks run\",\n \"an old stream end cannot evict the replacement stream, which still delivers to its listener\",\n \"two same-worktree listeners share a synthetic subscription until the final stop unsubscribes and sends unwatch\"\n ]\n } Historical prior account (preserved verbatim): Audit1973: Delete the standalone mocked watcher cleanup suite and its entire sole-file prospective command. Retire the removed file-command transport-abort alias; preserve the independent cancellation suite and final-overflow/error/end cleanup callback, and13 unrelated receipt objects. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial runtime files-watch suite retains controlled Windows close-event waiting, shared-client survival, late physical-exit ownership/deadline cleanup and shutdown joining. Its combined Windows close/deadline/late-exit alias remains backed by unchanged callbacks. Retire native-error-close, pre-publication setup ownership and failed-delete rearm aliases; associated delegation, failure forwarding, removal routing and recovery examples are lost. These tests inject watch/subscription seams and do not observe an actual native watcher or physical process. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1925 (sources1922/1923): Partial runtime watcher-host suite loses mocked option/event/overflow/resubscription forwarding, supplied teardown-error propagation, aborted-signal/no-call and private ten-thousand-waiter-count inventories. Retire the 10,000-caller bound and failed-recovery-subscriber-ending assertion aliases. Keep same-root owner cancellation, pre-ready replacement, held root-close join, stale-generation recovery delivery and initial failed-root ownership mappings backed by retained real-host callbacks with injected subscription/error/physical-exit promises. No actual native watcher, process termination or resource bound was observed. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1683 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1645 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1623 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1516 removes package-electron-runtime-contract.test.mjs, a copied release-workflow platform and marker matrix. Its workflow wiring alarm is intentionally retired; no actual packaged watcher delivery was exercised and no equivalent replacement is claimed. Unaffected dated execution receipts retain their original scope; changed optional selectors have no fresh full-gate execution result. This audit removes the paired-web stopped-during-outage and late-ready direct-branch examples. The retained pre-ready cancellation, failed-unwatch retry and replay-continuity cases are not equivalent replacements for those ordering alarms. Current web-runtime-client.test.ts assertions below name only its retained pre-ready callback case; replay cases remain in their separate suite. Historical commands and results remain historical. Deterministic tests cover one shared healthy child per runtime process plus at most four bounded fault-quarantine children, native desktop, paired-runtime, and WSL snapshot processes sharing the same eight-physical-child reservation with typed serialized event-driven capacity recovery, including recursive re-wait when crash recovery reclaims an announced slot, one quarantine attempt per watch lifetime, generation-scoped cancellation, bounded termination with removable deadline waiters, replacement-crawl errors that cannot be blessed by a late readiness ack, pre-ready paired-web cancellation registered before an unbounded capacity wait, already-resolved and late physical-exit retry of a rejected paired-web teardown, physical desktop-install and local/SSH PTY teardown, stale-generation rejection, per-child stat bounds, final-overflow RPC delivery, and renderer eviction before terminal callbacks can retry. Destructive local and SSH removal fences both ID-derived and resolved-cwd terminal roots, including sibling-root/cwd combinations with reverse admission rollback, closes descendant relay watches before parent deletion, enumerates authoritative provider/cwd ownership, falls back to daemon spawn cwd before OSC 7, and keeps daemon/relay immediate shutdown pending until native exit or a bounded fail-closed error; a dead relay PID discovered during attach also settles concurrent shutdown before its stale entry is reaped, while Windows runtime watcher deadlines retain one removable physical-close waiter, treat Node's error-close path as positive physical-exit proof, and clear root ownership on late close. The relay policy test maps a standard repository's base, Git common directory, and worktree roots to one healthy supervisor, then proves a shared-child failure recovers those roots in separate quarantine supervisors with overflow and resumed events. Abortable capacity, quarantine, runtime-root, relay, SSH, and relay pre-install setup waits attach one reaction to each shared promise and explicitly remove 10,000 cancelled caller closures while one anchor remains. Built-entry macOS harnesses kill the desktop/runtime and SSH relay watcher children, then require automatic resubscription and later events while the host process and relay PTY survive. SSH bundles the child boundary beside relay.js, requires both artifacts for install completeness, and preserves registration-owned same-root cancellation. The test-value audit corrects the active renderer watch command/file reference to the existing runtime-file-client-watch.test.ts, whose three cases are retained unchanged. They observe synthetic subscription retry/fanout and unsubscribe/unwatch calls, not actual preload/main bookkeeping or physical watcher-process cleanup. The separate read/preview client suite loses forwarding, supplied-error and negative-only examples, retaining binary detection and Unicode SVG decoding. Historical evidence commands/results are unchanged. Test-value audit1293 removes src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts (2 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1412: src/main/workspaces/removal/watcher-removal-gate.test.ts reduced. Loses exact removal-error compatibility with renderer predicate under shared message constants. Kept public refusal outcomes do not establish that renderer classification. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", + "coverageNotes": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2056 (sources2054/2055): Intentionally retire these low-value alarms without replacement: src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts: delivers remote file changes to listeners for the same worktree. src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-part-14.spec'). src/main/runtime/orca-runtime-tests/terminal-side-effect-facts-part-03.spec.ts: arms the stale-title timer for a seeded working title; arms the Command Code scrape from the noted spawn command; prefers the tracked title over a stale renderer lastTitle in the hydration seed; writes recent output into the current runtime output map; uses a later title handler replacement with the runtime receiver; calls the current injected listener with the runtime receiver; keeps optional reconciliation lazy after its callback is removed. src/main/runtime/orca-runtime-tests/mobile-session-tabs-part-14.spec.ts: lists them, so a paired client can mirror a migrated editor; closes one by retiring it from the host session; keeps an unsaved draft unless the close is forced; shares aggregate work but leaves a replacement pending promise alone; lets a targeted refresh finish while an aggregate refresh waits; refuses a close when the store lacks %s before the guard; keeps a later missing %s on the original caught failure path. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Exact retired assertionRefs, preserved as historical objects only: {\n \"file\": \"src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts\",\n \"assertions\": [\n \"terminal watcher error and setup failure evict the shared entry before retrying callbacks run\",\n \"an old stream end cannot evict the replacement stream, which still delivers to its listener\",\n \"two same-worktree listeners share a synthetic subscription until the final stop unsubscribes and sends unwatch\"\n ]\n } Historical prior account (preserved verbatim): Audit1973: Delete the standalone mocked watcher cleanup suite and its entire sole-file prospective command. Retire the removed file-command transport-abort alias; preserve the independent cancellation suite and final-overflow/error/end cleanup callback, and13 unrelated receipt objects. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial runtime files-watch suite retains controlled Windows close-event waiting, shared-client survival, late physical-exit ownership/deadline cleanup and shutdown joining. Its combined Windows close/deadline/late-exit alias remains backed by unchanged callbacks. Retire native-error-close, pre-publication setup ownership and failed-delete rearm aliases; associated delegation, failure forwarding, removal routing and recovery examples are lost. These tests inject watch/subscription seams and do not observe an actual native watcher or physical process. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1925 (sources1922/1923): Partial runtime watcher-host suite loses mocked option/event/overflow/resubscription forwarding, supplied teardown-error propagation, aborted-signal/no-call and private ten-thousand-waiter-count inventories. Retire the 10,000-caller bound and failed-recovery-subscriber-ending assertion aliases. Keep same-root owner cancellation, pre-ready replacement, held root-close join, stale-generation recovery delivery and initial failed-root ownership mappings backed by retained real-host callbacks with injected subscription/error/physical-exit promises. No actual native watcher, process termination or resource bound was observed. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1683 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1645 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1640 removes low-value IPC, SSH, workspace and transcript-watcher tests without equivalent replacement coverage. Per-source reviews record retained public refusal, cancellation, execution-host and real-filesystem outcomes with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1634 removes low-value IPC, filesystem and runtime projections, argument inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained cancellation, ownership, authorization, filesystem and SQLite boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1627 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1623 removes low-value mocked daemon projections, scalar tables and duplicate terminal examples without equivalent replacement coverage. Per-source reviews record retained terminal content preservation, process ownership refusal and recovery boundaries together with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded. Historical scope before this audit: Audit1516 removes package-electron-runtime-contract.test.mjs, a copied release-workflow platform and marker matrix. Its workflow wiring alarm is intentionally retired; no actual packaged watcher delivery was exercised and no equivalent replacement is claimed. Unaffected dated execution receipts retain their original scope; changed optional selectors have no fresh full-gate execution result. This audit removes the paired-web stopped-during-outage and late-ready direct-branch examples. The retained pre-ready cancellation, failed-unwatch retry and replay-continuity cases are not equivalent replacements for those ordering alarms. Current web-runtime-client.test.ts assertions below name only its retained pre-ready callback case; replay cases remain in their separate suite. Historical commands and results remain historical. Deterministic tests cover one shared healthy child per runtime process plus at most four bounded fault-quarantine children, native desktop, paired-runtime, and WSL snapshot processes sharing the same eight-physical-child reservation with typed serialized event-driven capacity recovery, including recursive re-wait when crash recovery reclaims an announced slot, one quarantine attempt per watch lifetime, generation-scoped cancellation, bounded termination with removable deadline waiters, replacement-crawl errors that cannot be blessed by a late readiness ack, pre-ready paired-web cancellation registered before an unbounded capacity wait, already-resolved and late physical-exit retry of a rejected paired-web teardown, physical desktop-install and local/SSH PTY teardown, stale-generation rejection, per-child stat bounds, final-overflow RPC delivery, and renderer eviction before terminal callbacks can retry. Destructive local and SSH removal fences both ID-derived and resolved-cwd terminal roots, including sibling-root/cwd combinations with reverse admission rollback, closes descendant relay watches before parent deletion, enumerates authoritative provider/cwd ownership, falls back to daemon spawn cwd before OSC 7, and keeps daemon/relay immediate shutdown pending until native exit or a bounded fail-closed error; a dead relay PID discovered during attach also settles concurrent shutdown before its stale entry is reaped, while Windows runtime watcher deadlines retain one removable physical-close waiter, treat Node's error-close path as positive physical-exit proof, and clear root ownership on late close. The relay policy test maps a standard repository's base, Git common directory, and worktree roots to one healthy supervisor, then proves a shared-child failure recovers those roots in separate quarantine supervisors with overflow and resumed events. Abortable capacity, quarantine, runtime-root, relay, SSH, and relay pre-install setup waits attach one reaction to each shared promise and explicitly remove 10,000 cancelled caller closures while one anchor remains. Built-entry macOS harnesses kill the desktop/runtime and SSH relay watcher children, then require automatic resubscription and later events while the host process and relay PTY survive. SSH bundles the child boundary beside relay.js, requires both artifacts for install completeness, and preserves registration-owned same-root cancellation. The test-value audit corrects the active renderer watch command/file reference to the existing runtime-file-client-watch.test.ts, whose three cases are retained unchanged. They observe synthetic subscription retry/fanout and unsubscribe/unwatch calls, not actual preload/main bookkeeping or physical watcher-process cleanup. The separate read/preview client suite loses forwarding, supplied-error and negative-only examples, retaining binary detection and Unicode SVG decoding. Historical evidence commands/results are unchanged. Test-value audit1293 removes src/main/providers/local-pty/local-pty-provider-session-inventory.test.ts (2 authored cases). Their isolated assertions no longer run; surviving tests are not claimed as equivalent coverage. Fresh execution against the candidate SHA is required. Audit1312 removes low-value imported specs from aggregate test drivers; old whole-driver execution receipts are archived. Retained registrations are unchanged. No equivalent replacement coverage is claimed. Audit1383: src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts reduced. Lose command-list/receipt/default/background activation matrices whose provider spawn returns canned IDs. Retained test proves actual tab allocation separation, not terminal startup. No equivalent replacement coverage is claimed. Existing exact unaffected receipts and their platform/provider/budget claims remain scoped to unchanged selectors. Changed optional selectors have no new execution result. Audit1401: src/main/runtime/orca-runtime-tests/mobile-session-tabs-duplicate-pty-repair.spec.ts removed. Loses listing-on-already-repaired-data alarm; it never established runtime repair invocation. src/main/runtime/orca-runtime-tests/mobile-session-tabs-split-agent-tab.spec.ts removed. Loses actual mobile split sibling shell versus recorded-agent resume command alarms; no equivalent coverage asserted. src/main/runtime/orca-runtime-tests/worktree-native-chat-setup.spec.ts removed. Loses separate setup/startup tab result in that manually assembled path. src/main/runtime/orca-runtime.test.ts reduced. Driver registration/import of a whole-deleted selected spec is removed; directly authored cases remain unchanged. No extra authored removal credit. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result. Audit1412: src/main/workspaces/removal/watcher-removal-gate.test.ts reduced. Loses exact removal-error compatibility with renderer predicate under shared message constants. Kept public refusal outcomes do not establish that renderer classification. No equivalent replacement coverage is claimed. Existing exact unaffected receipts remain unchanged and apply only to their unchanged selectors. Changed optional selectors have no new execution result.", "motivatingLinks": [ "https://github.com/stablyai/orca/issues/5308", "https://github.com/stablyai/orca/issues/7547", "https://github.com/stablyai/orca/issues/8212" ], "invariant": "Local runtime and SSH relay roots share one watcher child per host process while healthy and use at most four quarantine children after shard failure; native desktop, paired-runtime, and direct WSL snapshot children consume the same process-wide eight-child reservation and retry transient exhaustion without permanently disabling a root. Cancellation remains owned by its child generation and settles only after acknowledgement or bounded physical exit; paired-web retains cleanup ownership and can cancel before readiness even while capacity is exhausted, retries a failed unwatch after already-resolved or late physical exit without duplicating stream termination, replacements never overlap a still-retiring generation, and aborted or timed-out callers detach immediately even while queued behind same-root relay teardown. Destructive local and SSH cleanup holds one containment-aware watcher/terminal removal fence through descendant watcher and PTY physical exit plus parent Git/filesystem deletion; daemon and relay kill requests retain ownership until the actual subprocess exit callback or an equivalent positive dead-PID proof and propagate rejected signals or a bounded failure otherwise. A crash-fused shared shard moves affected roots into the bounded quarantine pool, a second fused quarantine ends those watch lifetimes, interrupted setup emits overflow after recovery, metadata work stays bounded, and the SSH relay, PTYs, Git, and other filesystem operations never load the watcher native addon or share its fault boundary.", - "oracle": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2056 (sources2054/2055): Intentionally retire these low-value alarms without replacement: src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts: delivers remote file changes to listeners for the same worktree. src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-part-14.spec'). src/main/runtime/orca-runtime-tests/terminal-side-effect-facts-part-03.spec.ts: arms the stale-title timer for a seeded working title; arms the Command Code scrape from the noted spawn command; prefers the tracked title over a stale renderer lastTitle in the hydration seed; writes recent output into the current runtime output map; uses a later title handler replacement with the runtime receiver; calls the current injected listener with the runtime receiver; keeps optional reconciliation lazy after its callback is removed. src/main/runtime/orca-runtime-tests/mobile-session-tabs-part-14.spec.ts: lists them, so a paired client can mirror a migrated editor; closes one by retiring it from the host session; keeps an unsaved draft unless the close is forced; shares aggregate work but leaves a replacement pending promise alone; lets a targeted refresh finish while an aggregate refresh waits; refuses a close when the store lacks %s before the guard; keeps a later missing %s on the original caught failure path. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Exact retired assertionRefs, preserved as historical objects only: {\n \"file\": \"src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts\",\n \"assertions\": [\n \"terminal watcher error and setup failure evict the shared entry before retrying callbacks run\",\n \"an old stream end cannot evict the replacement stream, which still delivers to its listener\",\n \"two same-worktree listeners share a synthetic subscription until the final stop unsubscribes and sends unwatch\"\n ]\n } Historical prior account (preserved verbatim): Audit1973: Delete the standalone mocked watcher cleanup suite and its entire sole-file prospective command. Retire the removed file-command transport-abort alias; preserve the independent cancellation suite and final-overflow/error/end cleanup callback, and13 unrelated receipt objects. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial runtime files-watch suite retains controlled Windows close-event waiting, shared-client survival, late physical-exit ownership/deadline cleanup and shutdown joining. Its combined Windows close/deadline/late-exit alias remains backed by unchanged callbacks. Retire native-error-close, pre-publication setup ownership and failed-delete rearm aliases; associated delegation, failure forwarding, removal routing and recovery examples are lost. These tests inject watch/subscription seams and do not observe an actual native watcher or physical process. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1925 (sources1922/1923): Partial runtime watcher-host suite loses mocked option/event/overflow/resubscription forwarding, supplied teardown-error propagation, aborted-signal/no-call and private ten-thousand-waiter-count inventories. Retire the 10,000-caller bound and failed-recovery-subscriber-ending assertion aliases. Keep same-root owner cancellation, pre-ready replacement, held root-close join, stale-generation recovery delivery and initial failed-root ownership mappings backed by retained real-host callbacks with injected subscription/error/physical-exit promises. No actual native watcher, process termination or resource bound was observed. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1683 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Fill the global watcher-child reservation, require native desktop, paired-runtime, and WSL setup to retain typed transient capacity and retry after one serialized release, then cancel pending desktop and paired-client watches and require no retry. Hold one live anchor while aborting 10,000 callers at the capacity, quarantine-assignment, runtime-root, relay-setup, and SSH-registration layers; require exactly one explicit waiter and one base-promise reaction to remain, then zero after settlement. Hold an old relay unsubscribe, queue one same-root setup leader and one anchor, then abort 10,000 joiners; require every cancellation to settle before teardown releases, exactly one anchor waiter, and only one later physical install. Advance the former paired-web callback deadline while capacity remains full, require cancellation ownership to stay live, publish the server token before ready, and require files.unwatch to abort setup before a later slot release can install a native root. Reject the first ready-watch unwatch with a physical-exit promise, resolve that exit later, and require a fresh physical unwatch attempt, registry release, and exactly one stream end. Fill one healthy shared child with multiple roots, map a standard SSH repository's base, Git common directory, and worktree roots to one relay supervisor, then inject IPC disconnect-before-exit, queued and active cancellation, operations during termination, kill-without-exit, repeated unkillable generations, initial and recovery timeouts, and shard failure. During local and SSH deletion, attempt a late terminal spawn whose worktree identity and cwd name different roots, enumerate a non-prefix remote PTY by authoritative worktree ownership, withhold daemon/relay exit callbacks, make attach positively prove a concurrent-shutdown PID dead, and make the provider unavailable; require every acquired admission to roll back on rejection, dead-PID proof to settle physical exit before reaping, timeout to retain ownership, and Git/filesystem deletion never to start. Require bounded watcher process count, physical-exit settlement, healthy-root recovery, stale-generation rejection, overflow plus later events, and fail-closed destructive cleanup. Verify relay build hashes and remote install probes require both relay.js and relay-watcher.js. Launch the built relay with a real PTY and Parcel watch, kill only the watcher child, and require the same relay PID and PTY to answer before a replacement child delivers overflow and a post-crash event.", + "oracle": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2056 (sources2054/2055): Intentionally retire these low-value alarms without replacement: src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts: delivers remote file changes to listeners for the same worktree. src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-part-14.spec'). src/main/runtime/orca-runtime-tests/terminal-side-effect-facts-part-03.spec.ts: arms the stale-title timer for a seeded working title; arms the Command Code scrape from the noted spawn command; prefers the tracked title over a stale renderer lastTitle in the hydration seed; writes recent output into the current runtime output map; uses a later title handler replacement with the runtime receiver; calls the current injected listener with the runtime receiver; keeps optional reconciliation lazy after its callback is removed. src/main/runtime/orca-runtime-tests/mobile-session-tabs-part-14.spec.ts: lists them, so a paired client can mirror a migrated editor; closes one by retiring it from the host session; keeps an unsaved draft unless the close is forced; shares aggregate work but leaves a replacement pending promise alone; lets a targeted refresh finish while an aggregate refresh waits; refuses a close when the store lacks %s before the guard; keeps a later missing %s on the original caught failure path. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Exact retired assertionRefs, preserved as historical objects only: {\n \"file\": \"src/renderer/src/runtime/files/watching/runtime-file-client-watch.test.ts\",\n \"assertions\": [\n \"terminal watcher error and setup failure evict the shared entry before retrying callbacks run\",\n \"an old stream end cannot evict the replacement stream, which still delivers to its listener\",\n \"two same-worktree listeners share a synthetic subscription until the final stop unsubscribes and sends unwatch\"\n ]\n } Historical prior account (preserved verbatim): Audit1973: Delete the standalone mocked watcher cleanup suite and its entire sole-file prospective command. Retire the removed file-command transport-abort alias; preserve the independent cancellation suite and final-overflow/error/end cleanup callback, and13 unrelated receipt objects. Whole original changed dated receipts are retired, never narrowed or transferred. This metadata review claims no fresh execution, equivalent replacement, provider/PTY, live remote/mobile, rendered, cross-platform, soak or measured-budget result. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial runtime files-watch suite retains controlled Windows close-event waiting, shared-client survival, late physical-exit ownership/deadline cleanup and shutdown joining. Its combined Windows close/deadline/late-exit alias remains backed by unchanged callbacks. Retire native-error-close, pre-publication setup ownership and failed-delete rearm aliases; associated delegation, failure forwarding, removal routing and recovery examples are lost. These tests inject watch/subscription seams and do not observe an actual native watcher or physical process. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1925 (sources1922/1923): Partial runtime watcher-host suite loses mocked option/event/overflow/resubscription forwarding, supplied teardown-error propagation, aborted-signal/no-call and private ten-thousand-waiter-count inventories. Retire the 10,000-caller bound and failed-recovery-subscriber-ending assertion aliases. Keep same-root owner cancellation, pre-ready replacement, held root-close join, stale-generation recovery delivery and initial failed-root ownership mappings backed by retained real-host callbacks with injected subscription/error/physical-exit promises. No actual native watcher, process termination or resource bound was observed. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1683 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: Fill the global watcher-child reservation, require native desktop, paired-runtime, and WSL setup to retain typed transient capacity and retry after one serialized release, then cancel pending desktop and paired-client watches and require no retry. Hold one live anchor while aborting 10,000 callers at the capacity, quarantine-assignment, runtime-root, relay-setup, and SSH-registration layers; require exactly one explicit waiter and one base-promise reaction to remain, then zero after settlement. Hold an old relay unsubscribe, queue one same-root setup leader and one anchor, then abort 10,000 joiners; require every cancellation to settle before teardown releases, exactly one anchor waiter, and only one later physical install. Advance the former paired-web callback deadline while capacity remains full, require cancellation ownership to stay live, publish the server token before ready, and require files.unwatch to abort setup before a later slot release can install a native root. Reject the first ready-watch unwatch with a physical-exit promise, resolve that exit later, and require a fresh physical unwatch attempt, registry release, and exactly one stream end. Fill one healthy shared child with multiple roots, map a standard SSH repository's base, Git common directory, and worktree roots to one relay supervisor, then inject IPC disconnect-before-exit, queued and active cancellation, operations during termination, kill-without-exit, repeated unkillable generations, initial and recovery timeouts, and shard failure. During local and SSH deletion, attempt a late terminal spawn whose worktree identity and cwd name different roots, enumerate a non-prefix remote PTY by authoritative worktree ownership, withhold daemon/relay exit callbacks, make attach positively prove a concurrent-shutdown PID dead, and make the provider unavailable; require every acquired admission to roll back on rejection, dead-PID proof to settle physical exit before reaping, timeout to retain ownership, and Git/filesystem deletion never to start. Require bounded watcher process count, physical-exit settlement, healthy-root recovery, stale-generation rejection, overflow plus later events, and fail-closed destructive cleanup. Verify relay build hashes and remote install probes require both relay.js and relay-watcher.js. Launch the built relay with a real PTY and Parcel watch, kill only the watcher child, and require the same relay PID and PTY to answer before a replacement child delivers overflow and a post-crash event.", "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-unsubscribe-timeout.test.ts --reporter=dot", "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/filesystem/watchers/filesystem-watcher.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-native-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-registry.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-child-launch.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-process.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-supervisor-capacity.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-disconnect-termination.test.ts src/main/ipc/filesystem/watcher-process/runtime-watcher-pending-assignment.test.ts src/main/ipc/filesystem/watcher-process/parcel-watcher-event-delivery.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-local-unsubscribe.test.ts src/main/ipc/filesystem/watchers/filesystem-watcher-wsl.test.ts src/main/ipc/worktrees-delete-pty-teardown.test.ts src/main/ipc/worktrees-orphan-directory-cleanup.test.ts src/main/ipc/worktrees-remove-preflight.test.ts src/main/workspaces/removal/watcher-removal-gate.test.ts src/main/runtime/files/watch/orca-runtime-files-watch.test.ts src/main/runtime/workspaces/removal/worktree-teardown.test.ts src/main/runtime/construction/orca-runtime.test.ts src/main/runtime/rpc/methods/files/files.test.ts src/main/runtime/rpc/methods/files/files-watch-cancellation.test.ts src/renderer/src/web/web-runtime-client.test.ts src/shared/cross-platform-path.test.ts src/shared/physical-exit-tracker.test.ts src/shared/promise-settlement-waiters.test.ts src/main/ipc/pty/pty-spawn-cwd-fallback.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/daemon/pty-subprocess/testing/pty-subprocess.test.ts src/main/daemon/sessions/testing/session.test.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/daemon/terminal-host/testing/terminal-host-session-reaping-leak.test.ts --reporter=dot", @@ -13182,7 +13166,18 @@ "Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed.", "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed.", "Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation.", - "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed." + "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed.", + "Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked.", + "Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed.", + "Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms.", + "Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery.", + "Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction.", + "Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed.", + "Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed.", + "Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed.", + "Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed.", + "Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed.", + "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired." ], "demotionRule": "Demote or quarantine if the fault harness flakes without a product or harness bug, if healthy operation exceeds one runtime watcher child, if total physical operation exceeds eight children including retiring generations, if quarantine children outlive their roots or repeat after fusing, if event delivery becomes unbounded, or if metadata/stat work returns to the serve process." }, @@ -14124,7 +14119,8 @@ "Audit1342 retires this complete dated receipt because a source in its selector was deleted or reduced. It does not validate the reduced selector. Original receipt verbatim:\n{\n \"date\": \"2026-11-04\",\n \"runner\": \"local\",\n \"platform\": \"linux\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm --config.verifyDepsBeforeRun=false test src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.test.ts src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge-split.test.ts src/main/runtime/orca-runtime-terminal-split-authority.test.ts src/renderer/src/runtime/sync-runtime-graph-parked-leaf.test.ts src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption-regressions.test.ts src/renderer/src/runtime/web-session-terminal-pending-handle-recovery.test.ts tests/e2e/host-cold-park-remote-subscriber.unit.test.ts --maxWorkers=2\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.test.ts src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge-split.test.ts src/main/runtime/orca-runtime-terminal-split-authority.test.ts src/renderer/src/runtime/sync-runtime-graph-parked-leaf.test.ts src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption-regressions.test.ts src/renderer/src/runtime/web-session-terminal-pending-handle-recovery.test.ts tests/e2e/host-cold-park-remote-subscriber.unit.test.ts --reporter=dot\",\n \"result\": \"passed\",\n \"durationSeconds\": 14.82,\n \"summary\": \"Test Files 12 passed (12); Tests 86 passed (86). Deterministic fixtures; no physical phone or deployed mixed-host claim.\"\n }", "Audit1342 historical platform/provider scope before source changes: {\"coveredPlatforms\":[\"macos\",\"linux\"],\"coveredProviders\":[\"local-daemon\",\"paired-runtime\"]}. Remaining provider scope applies only to unchanged-source receipts.", "Audit1342 historical assertion inventory for deleted/reduced sources; not current validated assertions:\n[\n {\n \"file\": \"src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts\",\n \"assertions\": [\n \"coalesces degraded frames to one active and one latest queued recovery\",\n \"lets a newer fully ready frame overtake an in-flight degraded recovery\",\n \"limits recovery RPC concurrency globally across worktrees\"\n ]\n }\n]", - "Audit1939 (sources1934/1935): Pruned cold-parking hook and parked-watcher suites retain local timer/selection/split and exit/cancel/disposal progression. Lose supplied capability/eligibility/topology/admission and duplicate state examples recorded below. No assertion objects map to these sources here; preserve independent actual E2E receipt scopes without transferring their results to reduced units. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Exact retired authored alarms: 'parks paired-runtime tabs only when their exact host advertises restore'; 'skips parking scans for capability-equivalent status writes and scans membership changes'; 'keeps eviction-exempt tabs mounted when the worktree is force-parked'; 'resolves eviction exemptions once per force-park input change'; 'does not exempt a tab from a truncated malformed pane key'; 'never starts a watcher for a PTY that exited into the park handoff gap'; 'collapses the exited leaf out of the stored layout when deferring'; 'retires launch/title hints when the launch-owning parked leaf exits'; 'keeps launch ownership when only a parked shell sibling exits'; 'rejects ordinary parking for a preserved daemon with a lossy snapshot'; 'rejects a capture containing a PTY without snapshot backing'; 'accepts layout-derived candidates when the capture is stale'; 'rejects layout-derived candidates missing a leaf PTY binding'." + "Audit1939 (sources1934/1935): Pruned cold-parking hook and parked-watcher suites retain local timer/selection/split and exit/cancel/disposal progression. Lose supplied capability/eligibility/topology/admission and duplicate state examples recorded below. No assertion objects map to these sources here; preserve independent actual E2E receipt scopes without transferring their results to reduced units. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Exact retired authored alarms: 'parks paired-runtime tabs only when their exact host advertises restore'; 'skips parking scans for capability-equivalent status writes and scans membership changes'; 'keeps eviction-exempt tabs mounted when the worktree is force-parked'; 'resolves eviction exemptions once per force-park input change'; 'does not exempt a tab from a truncated malformed pane key'; 'never starts a watcher for a PTY that exited into the park handoff gap'; 'collapses the exited leaf out of the stored layout when deferring'; 'retires launch/title hints when the launch-owning parked leaf exits'; 'keeps launch ownership when only a parked shell sibling exits'; 'rejects ordinary parking for a preserved daemon with a lossy snapshot'; 'rejects a capture containing a PTY without snapshot backing'; 'accepts layout-derived candidates when the capture is stale'; 'rejects layout-derived candidates missing a leaf PTY binding'.", + "Terminal/runtime defining-owner cleanup retires complete parking timer, lifecycle, mocked close/disposal and render/flip/mount work-count cases under repository test policy. The retained watcher source has only two literal-oracle coverage decisions; the retained cold-parking hook source has only the layout-PTY exemption invalidation case. Lost alarms include cooldown/measure/pin wakeups, split exit/close attribution and cancellation, watcher disposal/restart, flood timer rearm, worktree verdict damping, and destroyed-transport cold restore. No equivalent replacement coverage or execution pass for the reduced cases is claimed; prior dated receipts retain their original scope." ], "demotionRule": "Demote if a live parked/restarting PTY loses its exact graph leaf or verified viewer binding, a retired PTY remains published, multi-pane identity drifts, or either routed client round trip flakes without a diagnosed cause." }, @@ -14426,10 +14422,10 @@ "providers": ["paired-runtime"], "coveredPlatforms": ["linux"], "coveredProviders": ["paired-runtime"], - "coverageNotes": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit1579 removes workflow/manifest/regex/private binary-header and diagnostic metric inventories, fixture selectors, injected forwarding/platform/signature/process examples, unproven overlap and internal churn counters without equivalent replacement coverage. Retains actual Git trust/diffs; filesystem hash/ELF-region/native discovery; child CLI module repair; compiler/lock/publication/rollback and synthetic Electron install/cache ownership; executable shell quoting/exit; controlled notary/timer cleanup; local patched extractor file/AST freshness; real Chromium/WebKit CSP/sandbox/HTTPS image/referrer/link/preservation controls under parsed Android headers. Five unchanged setup-pnpm/VM request-cleanup definitions reviewed and not rerun. GitHub, installer/archive, OS metric, client, native delegate, compressor, builder and Apple boundaries injected or absent where recorded. No actual native PTY, shipped full/native app, real Actions/cache/npm signature authority, downloaded Electron archive, real objdump/linker/native CLI/AppImage, signing/NSIS/notary/upload/updater or live freeze proof. Historical scope before this audit: Published Linux 1.4.188 desktop against current source in both directions; scheduled weekly and manually runnable. No required PR check.", + "coverageNotes": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit1579 removes workflow/manifest/regex/private binary-header and diagnostic metric inventories, fixture selectors, injected forwarding/platform/signature/process examples, unproven overlap and internal churn counters without equivalent replacement coverage. Retains actual Git trust/diffs; filesystem hash/ELF-region/native discovery; child CLI module repair; compiler/lock/publication/rollback and synthetic Electron install/cache ownership; executable shell quoting/exit; controlled notary/timer cleanup; local patched extractor file/AST freshness; real Chromium/WebKit CSP/sandbox/HTTPS image/referrer/link/preservation controls under parsed Android headers. Five unchanged setup-pnpm/VM request-cleanup definitions reviewed and not rerun. GitHub, installer/archive, OS metric, client, native delegate, compressor, builder and Apple boundaries injected or absent where recorded. No actual native PTY, shipped full/native app, real Actions/cache/npm signature authority, downloaded Electron archive, real objdump/linker/native CLI/AppImage, signing/NSIS/notary/upload/updater or live freeze proof. Historical scope before this audit: Published Linux 1.4.188 desktop against current source in both directions; scheduled weekly and manually runnable. No required PR check.", "motivatingLinks": ["https://github.com/stablyai/orca/actions/runs/34069063016"], "invariant": "A paired client and host without client-hosted browser capabilities retain server-hosted browser placement across supported version skew.", - "oracle": "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Require both existing named browser placement scenarios to pass three times with one attempt, zero skips, zero failures, and no report errors.", + "oracle": "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired. Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed. Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed. Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed. Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed. Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed. Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction. Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery. Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms. Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed. Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked. Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed. Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation. Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed. Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed. Audit3096 source-only reduction (3087/3088): retire 69 authored/76 expanded cases, including complete recovery-portals and reconnect-banner suites. Intentionally lose dead/default TUI replay, generated alternate-frame style composition, reset/mode/dimension and legacy missing-mode-boundary alarms, UI notice/action/reveal policies, and isolated recovery lifecycle variants, without equivalent replacement. Retained registrations and callbacks are unchanged; headless cells are not pixels, and no fresh runtime, host, SSH, WSL, rendered-app or performance result is claimed. Audit3086 source-only reduction (3079/3080/3081): intentionally retire 93 authored/100 expanded mock-boundary cases, including the complete spawn-errors and SSH reconnect overlay suites, without replacement. Retired alarms include spawn classification/ownership, reply ceilings and renderer-write failure recovery, disconnect/detach fencing branches, authority facts, and overlay retry/visibility wiring. Retained callbacks are unchanged; no fresh execution, real PTY/SSH, rendered UI, or performance result is claimed. Audit2432: The broad unit command loses the isolated BranchPicker open-change notification sample. Its unique true/false callback alarm is intentionally retired; reopening/status-refresh behavior was not exercised. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2425: The broad unit command loses the workspace-copy-status supplied readError forwarding sample. It did not exercise the branch-switch blocking promised by its title. This alarm is intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2417: The broad unit command loses two ordinary clean Git cherry-pick/revert success samples and two terminal startup foreground-read work-budget samples. These unique alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2412: The broad unit command loses the whole six-case fake-React-hook scroll-anchor suite, two generic draft-retention samples, a simple new-agent order sample, a real history-publication batching-count sample, an unmounted handoff abort/timer sample and three basic WSL preview/error samples. These alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2404: The broad unit command loses the standalone share-name lookup 503 propagation suite, initial deletion placement-kind sample, direct changed-mtime stale sample, first-pass rollback existence example and abandoned-preparation directory sample. These exact alarms are intentionally retired without equivalent replacement or fresh validation of the reduced selector. Audit2397 review amendment: The broad unit command loses three standalone suites, two partial-case definitions and two URL table rows across package/download/placement tests. Keep initial wrong-port authorization, modified independent-copy digest refusal and abort-after-begin cancellation dispatch. The remaining alarms are deliberately retired; no equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2393 current-parent refresh2401: Remove the basic skill-install service alias sample and original public managed-remove receiptless refusal case. Preserve the upstream request ownership suite and transaction interruption coverage. No equivalent complete replacement or fresh validation of the reduced selector is claimed. Audit2389: The broad unit command loses the missing-preview suite, plain-global destination sample and prepublished-receipt recovery example. No equivalent replacement or fresh validation of the reduced selector is claimed. Audit2385: The broad unit command loses four authored walker/staging/protocol examples (six expanded cases); no equivalent replacement or fresh validation is claimed for the reduced suite. Audit2383: The unscoped Vitest command no longer includes the removed Antigravity catalogue suite; its exact provider-root/owner example has no equivalent replacement or fresh validation claim. Audit2377/2381: The unscoped Vitest command includes the reduced bundled-skill and native-discovery suites; the eight removed backup/legacy/catalogue/symlink/SSH-builder examples have no replacement-equivalence or fresh validation claim here. Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Require both existing named browser placement scenarios to pass three times with one attempt, zero skips, zero failures, and no report errors.", "commands": [ "gh workflow run packaged-browser-e2e.yml", "pnpm exec playwright test tests/e2e/packaged-mixed-version-browser-placement.spec.ts --config tests/playwright.config.ts --project=electron-headless --workers=1 --repeat-each=3 --retries=0", @@ -14500,7 +14496,18 @@ "Audit3108 source-only reduction (3094/3095/3100): retire 27 authored/30 expanded cases, including complete markdown view-state, file-preview link-open and CLI agent-server-route suites, without replacement. Deliberate losses include preview visibility/reference lifetime and same-path owners; SSH stat, directory/missing-target, explicit-null owner, nonmarkdown line/column, local URI routing, remote-to-client guard and same-file reuse; document-preview source-owner/folder/reuse routing, stable grant location and outside-workspace POSIX/Windows request projection; and verified remote CLI send/check refusal, suggested --on argv/copy, shell no-probe bypass, unverified-launch admission and older-peer refusal. Retain two markdown source-runtime/qualified-reveal alarms, one stale grant-rejection fence and two remote read/mutation evidence policies, with original callbacks. Final last-wins reveal did not prove transient cancellation; CLI prerequisite did not prove no real send, and envelope tests do not execute server refusal. No fresh runtime, rendered UI, actual file/SSH/paired transport, authorization, mixed-version or performance result is claimed.", "Audit3114 incorporates source3101: retire three structured Claude child-environment wrapper cases covering inherited stamp isolation/default and explicit entrypoint, Windows SDK-key canonicalization/default/duplicate-key order, and configured lookup/final environment/config-dir conjunction. Keep direct auth/session sanitation and Windows-versus-Linux overlay checks, actual executable lookup from supplied inherited PATH, native Windows child environment delivery, and stale option-mutation fencing. These are not equivalent replacements for the retired wrapper alarms. No fresh test/runtime, real login-shell probe, Claude SDK startup, remote/mixed-version, UI or performance result is claimed.", "Audit3114 incorporates source3105: retire 17 authored/20 expanded daemon launch-note cases, including the entire two-case SessionLaunchNote suite. Deliberately lose split-marker carry across same-token re-arm, stale-token retirement refusal, foreground tri-state and serial settlement, omitted-note/no-incarnation mixed-version policy, sticky retirement, first-incarnation/replacement launch preservation and held-launch precedence. Keep only the unchanged unresolved foreground-read race against a later finished attach. Supplied notes/foreground and runtime calls do not establish actual daemon/main restart, SSH, native Windows observation, mobile UI, equivalent replacement or fresh runtime/performance validation.", - "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed." + "Audit3114 incorporates source3109: retire six authored/35 expanded pricing cases: 21 accepted model-name examples, 10 unsupported/future-model fail-closed examples, malformed TTL clamp, unknown-model estimate refusal, flat-model long-context disregard and oversized long-context clamp. Keep mixed five-minute/one-hour write allocation and short/all-long/mixed long-context allocation with original fixed oracles. Real parser/refusal/bounds alarms are knowingly lost without equivalent replacement; retained tests do not validate provider rates, actual billing or request threshold classification. Preserve the archived usage-scan-append source-freeze receipt without transferring historical execution/performance credit. No fresh runtime result is claimed.", + "Audit3128 scoped coverage reduction: Audit3121 removes VoiceDictationLanguageSetting.test.tsx (Spanish-to-es callback and unsupported-model Korean option presence) and voice-speech-model-rows.test.ts (provider/language-order/retired-on-disk list policy); these real narrow alarms are deliberately retired without replacement. VoiceSpeechModelSection per-model concurrent deletion remains unchanged. Audit3122 removes the direct absolute/parent-segment sparse-directory exception-type sample and the five-row IPC sparse-preset error-code/non-save table (two authored/six expanded cases). Those real typed-validation and IPC refusal/error-code alarms are deliberately retired. Keep both real symlink/junction floating-directory trust cases and the minimal RPC structured validation-reason serialization case. The RPC case does not replace IPC save/refusal behavior, typed normalization for both path inputs, actual Git sparse checkout or rendered localization. No equivalent replacement or fresh test/runtime/platform/remote/performance validation is claimed. Audit3124 removes ExperimentalPane.test.tsx (ordinary Pet switch callback), WorkspaceDirectorySetting.test.tsx (padded absolute saved-path normalization/no-write blur), and repository-search.perf.test.ts (navigation/selected-pane builder-call policy, fixture cardinality/nonempty entries and renamed project keyword inclusion). These real narrow alarms are retired without replacement; RepositoryPane.search.test.tsx search-to-sparse-editor request remains unchanged. Audit3129: resolver Windows case-insensitive and POSIX case-sensitive configured-env composition plus mocked-base launch-preflight Windows composition are intentionally no longer checked by the two deleted suites. No equivalent coverage claimed. Keep runtime-structured-agent-host-env.test.ts unchanged: configured Windows Claude-home account identity and host-account precedence remain checked.", + "Audit3146 combines source reviews3132/3133/3135/3138/3143: intentionally retire20authored/31expanded cases without equivalent replacement. Provider-session losses: structured-native terminal-resume exclusion, metadata-less completed identity, new-turn completion clearing, Pi provider-only/periodic/manual promotion, changed-transcript launch-config isolation, Pi/omp/prime-agent completed-session remote/manual-shutdown promotion, and quit/done precedence over heartbeat. Side-thread/question losses: finished-pane acquisition, hidden title/recap states, local/relay/relay-done conversation takeover with prompt/session/child resets, plain-message abandonment, compaction/session replacement and completion resets. Codex startup losses: incoming-owner cached child replacement, old-relay foreign live-session correction, completed foreign-root turn-fact permutations. Transcript losses: real temporary-JSONL partial sibling/final-child completion, unlabelled child attribution, compact cached replay preservation and100/1000completed-child roster retention/release. Compiler-ratchet losses: valid-line uncompiled-list parser grammar examples and injected-existence stale-path filtering; these did not exercise actual filesystem canonicalization or build exclusion. These are real lifecycle, ownership, replay, liveness and retention alarms, including recent#40246 regressions, deliberately lost. Retained conditional record replacement, clockless root/SSH owner isolation, persisted main-thread fork rejection, partial/final question-answer lifetime, cached-roster/missing-ID/live HTTP/durable startup boundaries and restarted-relay refusal/acceptance do not replace those losses. No native/SSH/WSL execution or equivalent coverage is claimed.", + "Audit3156 combines3147/3150/3152/3153/3154: deliberately retires39authored/40expanded native-chat recovery cases, eight whole suites and two orphan test-support files without equivalent replacement. Lost alarms: lost-reply unconfirmed reconciliation, skipped rows/counts and missed-frame repair, unknown/pending outcome and omitted-failure-list compatibility, uncertain history after dismissal, retry/refusal/dismiss lifecycle, hidden/consumed picker ownership, result-tree keyboard focus/disclosure and all-skipped retirement; unconfirmed-over-conflict guidance and failed-chat changes despite older host timestamps; nearest-listed ancestor/cyclic forest shape, descendant membership, host ordering/missing-host local fallback; skipped/readiness/admission counts, provisional skip/progress monotonicity, early per-chat verdicts with batch pending protection, older-host waiting, start-slot/delivery distinction, retry-over-failure and unknown-after-withdrawal attention; remote snooze/reconnect/reoffer, unseen mixed summary through open/close, watched completion retirement, new success beside old failure, failed-reread mixed-result preservation, filter/collapse completion resets, retry summary replacement and current-offer/history selection; cross-host workspace identity, rendered cyclic reachability, nested tri-state/toggle propagation, project eligibility isolation and collapse preserving selection. Exact fallback-list completion barrier, batched verdict/cleanup retention, stale-read versus dismissal publication race, and requested-but-never-rendered finished-run retirement remain; they do not replace retired alarms.", + "Audit3165 combines source3160/3162: deliberately retires six authored/expanded cases across three whole suites without equivalent replacement. Launch/status losses: observed React-render pending/resuming coordination across settings and deferred offer read; stale-offer reread suppressing dialog reopen; held-local-chat recovery visibility with new-chat preferences off; disabled-local admission avoiding offer RPC despite an injected but unconsumed paired-tab fixture; and run-only lost-reply uncertainty reachability until direct viewed-run retirement. Launch probe does not prove arbitrary synchronous-store atomicity, the paired fixture is not remote runtime enrollment, and the status case directly marks viewed state rather than rendering the summary. Crew-hook losses: old-host method_not_found cache persists across party changes but is released by independently supplied connection generation/verified pairing status or pairing revision, with actual frame publication and teardown. These are real narrow coordination, admission, uncertainty and mixed-version cache alarms consciously lost. Exact web verified-runtime writer handoff guard remains; it does not replace those alarms or prove live host/native/mobile delivery.", + "Audit3178 combines source3169/3170/3175: deliberately retires eight authored units (six Vitest cases and two imperative custom relay scenarios), including two whole suites, without equivalent replacement. Voice Section loses per-model pending-delete isolation across interleaved A/B completion and the two-success refresh-call alarm; exact VoicePane delayed key-clear/newer enabled-toggle merge remains and does not replace row deletion coverage. Speech loses dedicated rating-scale/inclusive-ceiling, unsupported-versus-unmeasured, explicit/system-language precedence/locale alias, accuracy versus fast named/default/best-measured selection, and unmeasured/no-match examples; exact offered-user-pick versus retired-selection case and its fixture remain. Relay loses legacy ping-delivery count/liveness sampled before the silence deadline and grouped malformed Bearer/host-ID 401/429 routing checks; twelve real Worker/DurableObject/shipped-client scenarios remain, not equivalent replacement for those narrow alarms. Six prior speech cases are not all deleted: approved successor3175 keeps the original user-choice case. No actual credential/file persistence, native/GPU/engine/download delivery, production assignment policy or deployed-runtime validation is claimed by this reduction.", + "Audit3191 combines approved source3182/3187/3188: deliberately retires thirteen authored Vitest registrations and fifteen expanded cases, including five whole suites, without equivalent replacement. Attachment intake loses real portal/native-capture terminal interception and unowned-container refusal, immediate child-only committed availability/claim-ref negotiation and delivery, and direct-target over stable focused/background composer routing through disable/re-enable/remount and independent question focus. Exact destination/cache/draft and final-stat owner/workspace/File-release suites remain; they do not replace Surface recipient routing. Automation authoring loses destination runtime catalog membership, local/direct-SSH exclusion of paired-runtime repos, unknown-runtime empty catalog, logical project/host/setup/repo/path context payload, and missing/nonready setup refusal; these were genuine direct helper alarms, while exact schedule transformation, legacy schedule and setup-decision suites remain. Agent picker loses setup-chat/terminal/missing-installer/unsupported-or-unknown-host row projection samples and the ordinary unsearched menu variant; the entire original26-line resume recipe suite remains, including reported bypass versus default/absent permission modes, as does the exact search-result menu callback with its fixtures, model/effort commits and staged Arguments reopen outcome. Rejected predecessor3180 full recipe deletion is excluded. No actual native drag/cursor/focus/IME, upload bytes or disk/restart persistence, full automation save/scheduler/runtime transport, real resume-process flags/environment delivery, or native menu/rendered layout validation is claimed.", + "Audit3200 composes approved source3194: deliberately retires thirteen authored registrations/twenty-seven expanded cases in six whole automation host/list policy suites without equivalent replacement. Real RPC category/retryability and wrapped-timeout recognition, unscoped-failure/scoped-success health evidence precedence, exact-ID create selectors and per-run workspace omission, paired-web local/SSH/absent host catalog routing, incompatible/removed/unhydrated/offline execution health, runtime-before-nested-SSH recovery dispatch, and no-owned-focus/successor/foreign-list focus policy alarms are consciously lost. Retain exact20line orphan allow/refuse/precondition and exact90line cross-host same-ID search suites. No retry-timer/fence lifecycle, actual reconnect/network/write delivery, native focus, mobile/mixed-version runtime or disk persistence coverage is claimed.", + "Audit3209 composes approved sources3202/3206: deliberately retires seven authored/expanded cases in four whole suites without equivalent replacement. Work-item queries lose real GitHub/GitLab oversized ASCII secret-prefix empty-query and whitespace-before-trim refusal alarms; inputs share the imported size bound and exercise only its fast length branch, with no multibyte or actual provider/exposure guarantee. Skill sharing loses complete/partial managed-bundle and destination-ambiguity association checks plus three negative origin/path/scheme admission examples; these do not establish uploaded contents, arbitrary-URL fetching, authorization or execution. Retain exact originless-project direct-link fallback, cloud-delete confirmation, bundled-target repair/owner isolation and explicit Windows-host preference over stale WSL suites. Rejected3201 runtime-preference removal is excluded. No real cloud/network/host/disk, mobile or mixed-version execution validation is claimed.", + "Audit3219 removes the synthetic Cmd+J extra-source suite; this broad command no longer checks its match/tie placement and selection, announced count, empty-query cap, or workspace hiding with retained recent tabs. No replacement coverage is claimed.", + "Audit3231 retires supported-host restore-pane key mapping, closed/open creation-sidebar toggle visibility, unrelated scope-error/null classification and quick-submit reveal/no-create checks. These broad commands lose those real narrow alarms; no replacement coverage is claimed.", + "Audit3246 retires NewRowsMarker reveal/no-repeat and pane-scoped beforeinput checks; skill-owner option availability/count/dedup/order; six styled Input/Textarea IME forwarding rows; the static Codex/OpenCode smart-key inventory; tab-map AST ratchet diagnostic fixtures; child-roster work-budget and retained-aggregate alarms; malformed persisted floating-cwd fallback/no-crash/empty exact-root checks; the unanswered-host empty-list discriminator; the direct invalidated-cache commit-false assertion; one redundant digit-keyup IME row; cached non-supported Claude title-generator rows; and the duplicate late naming-result singleton. These root broad commands lose those specific alarms without equivalent replacement. Distinct retained owner/state checks have their own bounded claims; the real scheduler still rejects stale response data, while the direct false-return alarm is retired." ], "demotionRule": "Keep experimental if any direction skips or fails; do not extend timeouts or retry to green." }, diff --git a/config/render-path-translate-baseline.txt b/config/render-path-translate-baseline.txt index 0a7b19061a2..2da76433302 100644 --- a/config/render-path-translate-baseline.txt +++ b/config/render-path-translate-baseline.txt @@ -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 diff --git a/config/renderer-layout-writer-baseline.txt b/config/renderer-layout-writer-baseline.txt index 1c132f4e79c..750730bd624 100644 --- a/config/renderer-layout-writer-baseline.txt +++ b/config/renderer-layout-writer-baseline.txt @@ -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 diff --git a/config/scripts/build/mobile-web-app/build-mobile-web-app-bundle.mjs b/config/scripts/build/mobile-web-app/build-mobile-web-app-bundle.mjs index 605d2ac7816..4b1a1d008b8 100644 --- a/config/scripts/build/mobile-web-app/build-mobile-web-app-bundle.mjs +++ b/config/scripts/build/mobile-web-app/build-mobile-web-app-bundle.mjs @@ -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), diff --git a/config/scripts/build/mobile-web-app/mobile-web-bundle-manifest.mjs b/config/scripts/build/mobile-web-app/mobile-web-bundle-manifest.mjs index 4504d345fd4..ad84a39bd02 100644 --- a/config/scripts/build/mobile-web-app/mobile-web-bundle-manifest.mjs +++ b/config/scripts/build/mobile-web-app/mobile-web-bundle-manifest.mjs @@ -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) diff --git a/config/scripts/build/mobile-web-app/verify-mobile-web-app-bundle.mjs b/config/scripts/build/mobile-web-app/verify-mobile-web-app-bundle.mjs index b853a7d7a88..b6832054702 100644 --- a/config/scripts/build/mobile-web-app/verify-mobile-web-app-bundle.mjs +++ b/config/scripts/build/mobile-web-app/verify-mobile-web-app-bundle.mjs @@ -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( diff --git a/config/scripts/ci/sharding/ci-shard-timings.json b/config/scripts/ci/sharding/ci-shard-timings.json index 4fb29f3f0fd..9ed17e62079 100644 --- a/config/scripts/ci/sharding/ci-shard-timings.json +++ b/config/scripts/ci/sharding/ci-shard-timings.json @@ -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, diff --git a/config/scripts/desktop-distribution/runtime-verification/verify-packaged-mobile-web-bundle.cjs b/config/scripts/desktop-distribution/runtime-verification/verify-packaged-mobile-web-bundle.cjs index 769e1c90235..46370dc1d46 100644 --- a/config/scripts/desktop-distribution/runtime-verification/verify-packaged-mobile-web-bundle.cjs +++ b/config/scripts/desktop-distribution/runtime-verification/verify-packaged-mobile-web-bundle.cjs @@ -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) { diff --git a/config/scripts/localization/source-extraction/audit-localization-coverage.mjs b/config/scripts/localization/source-extraction/audit-localization-coverage.mjs index 6eaef1bb1b0..cd7cf997a7b 100644 --- a/config/scripts/localization/source-extraction/audit-localization-coverage.mjs +++ b/config/scripts/localization/source-extraction/audit-localization-coverage.mjs @@ -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) { diff --git a/config/scripts/localization/source-extraction/audit-localization-native-dialogs.test.mjs b/config/scripts/localization/source-extraction/audit-localization-native-dialogs.test.mjs new file mode 100644 index 00000000000..be3dd5d8541 --- /dev/null +++ b/config/scripts/localization/source-extraction/audit-localization-native-dialogs.test.mjs @@ -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 }) + } + }) +}) diff --git a/config/scripts/localization/source-extraction/localization-visible-object-keys.mjs b/config/scripts/localization/source-extraction/localization-visible-object-keys.mjs new file mode 100644 index 00000000000..0e5b73ec6a9 --- /dev/null +++ b/config/scripts/localization/source-extraction/localization-visible-object-keys.mjs @@ -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)) + ) +} diff --git a/config/scripts/mobile-web-app/session/mobile-web-app-queued-messages-render.test.mjs b/config/scripts/mobile-web-app/session/mobile-web-app-queued-messages-render.test.mjs index caca85ce51a..864d07093c3 100644 --- a/config/scripts/mobile-web-app/session/mobile-web-app-queued-messages-render.test.mjs +++ b/config/scripts/mobile-web-app/session/mobile-web-app-queued-messages-render.test.mjs @@ -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(':') diff --git a/config/scripts/orcad-distribution/server-bundle/orcad-mobile-web-bundle.mjs b/config/scripts/orcad-distribution/server-bundle/orcad-mobile-web-bundle.mjs index dd94c56f62e..7fa363a1907 100644 --- a/config/scripts/orcad-distribution/server-bundle/orcad-mobile-web-bundle.mjs +++ b/config/scripts/orcad-distribution/server-bundle/orcad-mobile-web-bundle.mjs @@ -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 }) } diff --git a/config/scripts/performance/renderer-projection/locale-collator-sort-benchmark.mjs b/config/scripts/performance/renderer-projection/locale-collator-sort-benchmark.mjs index 7253d22afb5..073a78ffaa5 100644 --- a/config/scripts/performance/renderer-projection/locale-collator-sort-benchmark.mjs +++ b/config/scripts/performance/renderer-projection/locale-collator-sort-benchmark.mjs @@ -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' diff --git a/config/scripts/performance/skills-plugins/skill-source-inventory-benchmark.mjs b/config/scripts/performance/skills-plugins/skill-source-inventory-benchmark.mjs index d774eb03fe0..64b69c9a1e7 100644 --- a/config/scripts/performance/skills-plugins/skill-source-inventory-benchmark.mjs +++ b/config/scripts/performance/skills-plugins/skill-source-inventory-benchmark.mjs @@ -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')], diff --git a/config/scripts/preflight/changed-checks/check-changed-selection.mjs b/config/scripts/preflight/changed-checks/check-changed-selection.mjs index f8f576a27a5..a8dddc16970 100644 --- a/config/scripts/preflight/changed-checks/check-changed-selection.mjs +++ b/config/scripts/preflight/changed-checks/check-changed-selection.mjs @@ -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' ] }, { diff --git a/config/scripts/preflight/changed-checks/run-lint.mjs b/config/scripts/preflight/changed-checks/run-lint.mjs index e882fcb03d2..2e4afe7b826 100644 --- a/config/scripts/preflight/changed-checks/run-lint.mjs +++ b/config/scripts/preflight/changed-checks/run-lint.mjs @@ -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', diff --git a/config/scripts/skills/bundle-generation/generate-plugin-authoring-skill-references.mjs b/config/scripts/skills/bundle-generation/generate-plugin-authoring-skill-references.mjs index 1efc10e312c..3c656d5530b 100644 --- a/config/scripts/skills/bundle-generation/generate-plugin-authoring-skill-references.mjs +++ b/config/scripts/skills/bundle-generation/generate-plugin-authoring-skill-references.mjs @@ -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', diff --git a/config/scripts/static-analysis/ratchets/react-compiler/check-react-compiler-compat-ratchet.test.mjs b/config/scripts/static-analysis/ratchets/react-compiler/check-react-compiler-compat-ratchet.test.mjs deleted file mode 100644 index abd806c4003..00000000000 --- a/config/scripts/static-analysis/ratchets/react-compiler/check-react-compiler-compat-ratchet.test.mjs +++ /dev/null @@ -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']) - }) -}) diff --git a/config/scripts/static-analysis/ratchets/renderer-state/check-tab-map-seam-ratchet.test.mjs b/config/scripts/static-analysis/ratchets/renderer-state/check-tab-map-seam-ratchet.test.mjs deleted file mode 100644 index 7f4a6dfb6b1..00000000000 --- a/config/scripts/static-analysis/ratchets/renderer-state/check-tab-map-seam-ratchet.test.mjs +++ /dev/null @@ -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}` - ]) - }) -}) diff --git a/config/scripts/static-analysis/ratchets/renderer-state/check-terminal-parking-ownership-ratchet.mjs b/config/scripts/static-analysis/ratchets/renderer-state/check-terminal-parking-ownership-ratchet.mjs new file mode 100644 index 00000000000..112b837bbb7 --- /dev/null +++ b/config/scripts/static-analysis/ratchets/renderer-state/check-terminal-parking-ownership-ratchet.mjs @@ -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() +} diff --git a/config/scripts/test-runtime/execution-policy/vitest-shared-worker-setup-runner.mjs b/config/scripts/test-runtime/execution-policy/vitest-shared-worker-setup-runner.mjs new file mode 100644 index 00000000000..1b41d2349cc --- /dev/null +++ b/config/scripts/test-runtime/execution-policy/vitest-shared-worker-setup-runner.mjs @@ -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) + } +} diff --git a/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs b/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs index 0aebc9e3011..d7542ed8616 100644 --- a/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs +++ b/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs @@ -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. */ diff --git a/config/scripts/workflow/source-routing/pr-layout-oracle-route.mjs b/config/scripts/workflow/source-routing/pr-layout-oracle-route.mjs index ac16f8b4df8..62e6e863cec 100644 --- a/config/scripts/workflow/source-routing/pr-layout-oracle-route.mjs +++ b/config/scripts/workflow/source-routing/pr-layout-oracle-route.mjs @@ -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', diff --git a/config/startup-worktree-localization-allowlist.json b/config/startup-worktree-localization-allowlist.json new file mode 100644 index 00000000000..57d98c87207 --- /dev/null +++ b/config/startup-worktree-localization-allowlist.json @@ -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." + } +] diff --git a/config/tsconfig.node.json b/config/tsconfig.node.json index 563748d8a33..f68039491a0 100644 --- a/config/tsconfig.node.json +++ b/config/tsconfig.node.json @@ -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", diff --git a/config/tsconfig.tc.web.json b/config/tsconfig.tc.web.json index d7542c5e628..a8d6f7c30cb 100644 --- a/config/tsconfig.tc.web.json +++ b/config/tsconfig.tc.web.json @@ -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", diff --git a/config/tsconfig.test.pane-launch.json b/config/tsconfig.test.pane-launch.json index 63896e6d839..0885af36f1d 100644 --- a/config/tsconfig.test.pane-launch.json +++ b/config/tsconfig.test.pane-launch.json @@ -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", diff --git a/config/tsconfig.web.json b/config/tsconfig.web.json index 02a420684d3..8b5c4e283c7 100644 --- a/config/tsconfig.web.json +++ b/config/tsconfig.web.json @@ -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": { diff --git a/config/vitest.config.ts b/config/vitest.config.ts index 7dc0f04c525..bfed0dadfdd 100644 --- a/config/vitest.config.ts +++ b/config/vitest.config.ts @@ -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') diff --git a/config/vitest.performance.config.ts b/config/vitest.performance.config.ts index ceb2d73b083..a068105564d 100644 --- a/config/vitest.performance.config.ts +++ b/config/vitest.performance.config.ts @@ -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', diff --git a/config/workspace-record-read-baseline.txt b/config/workspace-record-read-baseline.txt index ca14f9bf2ad..8734401d303 100644 --- a/config/workspace-record-read-baseline.txt +++ b/config/workspace-record-read-baseline.txt @@ -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 diff --git a/docs/assets/readme-downloads.svg b/docs/assets/readme-downloads.svg index 5d3b3001552..25b9a04169b 100644 --- a/docs/assets/readme-downloads.svg +++ b/docs/assets/readme-downloads.svg @@ -1,5 +1,5 @@ - - downloads: 163m + + downloads: 168m @@ -15,7 +15,7 @@ downloads downloads - 163m - 163m + 168m + 168m diff --git a/docs/assets/wechat-qr-group12.jpg b/docs/assets/wechat-qr-group12.jpg deleted file mode 100644 index 475c483ca76..00000000000 Binary files a/docs/assets/wechat-qr-group12.jpg and /dev/null differ diff --git a/docs/assets/wechat-qr-group13.jpg b/docs/assets/wechat-qr-group13.jpg deleted file mode 100644 index 48c67e4031b..00000000000 Binary files a/docs/assets/wechat-qr-group13.jpg and /dev/null differ diff --git a/docs/assets/wechat-qr-group14.jpg b/docs/assets/wechat-qr-group14.jpg deleted file mode 100644 index 76745e168d5..00000000000 Binary files a/docs/assets/wechat-qr-group14.jpg and /dev/null differ diff --git a/docs/assets/wechat-qr-group15.jpg b/docs/assets/wechat-qr-group15.jpg deleted file mode 100644 index 97ca38eb20b..00000000000 Binary files a/docs/assets/wechat-qr-group15.jpg and /dev/null differ diff --git a/docs/assets/wechat-qr-group16.jpg b/docs/assets/wechat-qr-group16.jpg deleted file mode 100644 index 158a50794cc..00000000000 Binary files a/docs/assets/wechat-qr-group16.jpg and /dev/null differ diff --git a/docs/assets/wechat-qr.jpg b/docs/assets/wechat-qr.jpg index 4b4f61e17f5..ecc8de57c7b 100644 Binary files a/docs/assets/wechat-qr.jpg and b/docs/assets/wechat-qr.jpg differ diff --git a/docs/development.md b/docs/development.md index 07cc3fd619d..56656dfe72e 100644 --- a/docs/development.md +++ b/docs/development.md @@ -53,5 +53,5 @@ pnpm dev:fresh [--keep] [--seed-from[=]] # 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. diff --git a/docs/readme/README.fr.md b/docs/readme/README.fr.md index 67a9b140ecb..67025f76663 100644 --- a/docs/readme/README.fr.md +++ b/docs/readme/README.fr.md @@ -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. - QR code du groupe WeChat de la communauté Orca - QR code du groupe WeChat de la communauté Orca - QR code du groupe WeChat de la communauté Orca + QR code du groupe WeChat de la communauté Orca - **Feedback & idées :** On ship vite. Il manque quelque chose ? [Demandez une feature](https://github.com/stablyai/orca/issues). - **Confidentialité :** Voir la [doc confidentialité & 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. diff --git a/docs/readme/README.ko.md b/docs/readme/README.ko.md index 02ceb179d9c..d44ef909489 100644 --- a/docs/readme/README.ko.md +++ b/docs/readme/README.ko.md @@ -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 그룹에 참여하세요. - Orca 커뮤니티 WeChat 그룹 QR 코드 - Orca 커뮤니티 WeChat 그룹 QR 코드 - Orca 커뮤니티 WeChat 그룹 QR 코드 + Orca 커뮤니티 WeChat 그룹 QR 코드 - **피드백과 아이디어:** 우리는 빠르게 출시합니다. 필요한 기능이 있나요? [새 기능을 요청](https://github.com/stablyai/orca/issues)하세요. - **개인정보 보호:** Orca가 수집하는 익명 사용 데이터와 수집 거부 방법은 [개인정보 및 텔레메트리 문서](https://www.onorca.dev/docs/telemetry)를 참고하세요. diff --git a/docs/readme/README.ru.md b/docs/readme/README.ru.md index b9f35580121..576d8250409 100644 --- a/docs/readme/README.ru.md +++ b/docs/readme/README.ru.md @@ -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. - QR-код группы сообщества Orca в WeChat - QR-код группы сообщества Orca в WeChat - QR-код группы сообщества Orca в WeChat + QR-код группы сообщества Orca в WeChat - **Отзывы и идеи:** Мы быстро выпускаем обновления. Чего-то не хватает? [Предложите новую возможность](https://github.com/stablyai/orca/issues). - **Конфиденциальность:** В [документации о конфиденциальности и телеметрии](https://www.onorca.dev/docs/telemetry) описано, какие анонимные данные об использовании собирает Orca и как отказаться от их сбора. diff --git a/docs/readme/README.tr.md b/docs/readme/README.tr.md index de57ef3c8a4..c3952fa63b8 100644 --- a/docs/readme/README.tr.md +++ b/docs/readme/README.tr.md @@ -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. - Orca topluluğunun WeChat grubu karekodu - Orca topluluğunun WeChat grubu karekodu - Orca topluluğunun WeChat grubu karekodu + Orca topluluğunun WeChat grubu karekodu - **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. diff --git a/docs/readme/README.zh-CN.md b/docs/readme/README.zh-CN.md index 4d9dea01319..20cfaae5e8d 100644 --- a/docs/readme/README.zh-CN.md +++ b/docs/readme/README.zh-CN.md @@ -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 社区微信群。 - Orca 社区微信群二维码 - Orca 社区微信群二维码 - Orca 社区微信群二维码 + Orca 社区微信群二维码 - **反馈与想法:** 我们发布很快。缺少什么功能?[提交功能请求](https://github.com/stablyai/orca/issues)。 - **隐私:** 查看[隐私与遥测文档](https://www.onorca.dev/docs/telemetry),了解 Orca 收集哪些匿名使用数据以及如何退出。 diff --git a/docs/reference/plugin-host-api.md b/docs/reference/plugin-host-api.md index 87dee966b62..b0f38a0930d 100644 --- a/docs/reference/plugin-host-api.md +++ b/docs/reference/plugin-host-api.md @@ -2,7 +2,7 @@ -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:.`. @@ -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 diff --git a/docs/reference/plugin-manifest.md b/docs/reference/plugin-manifest.md index dd085638f00..a257a924c7c 100644 --- a/docs/reference/plugin-manifest.md +++ b/docs/reference/plugin-manifest.md @@ -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 diff --git a/docs/schemas/plugins/orca-plugin.v2.schema.json b/docs/schemas/plugins/orca-plugin.v2.schema.json index 28b4a822243..665f0887bee 100644 --- a/docs/schemas/plugins/orca-plugin.v2.schema.json +++ b/docs/schemas/plugins/orca-plugin.v2.schema.json @@ -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", diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx index ba532452c05..4ac8bba5cdb 100644 --- a/mobile/app/_layout.tsx +++ b/mobile/app/_layout.tsx @@ -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' diff --git a/mobile/app/connection-log.tsx b/mobile/app/connection-log.tsx index 8fbcd0057fa..addd3b182b5 100644 --- a/mobile/app/connection-log.tsx +++ b/mobile/app/connection-log.tsx @@ -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. diff --git a/mobile/app/h/[hostId]/edit.tsx b/mobile/app/h/[hostId]/edit.tsx index 38dc364e575..acd13c84186 100644 --- a/mobile/app/h/[hostId]/edit.tsx +++ b/mobile/app/h/[hostId]/edit.tsx @@ -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' diff --git a/mobile/app/terminal-settings.tsx b/mobile/app/terminal-settings.tsx index fbd32b1b284..ef85607d591 100644 --- a/mobile/app/terminal-settings.tsx +++ b/mobile/app/terminal-settings.tsx @@ -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' diff --git a/mobile/app/voice-settings.tsx b/mobile/app/voice-settings.tsx index ef3a48ea832..3bdb0a66df0 100644 --- a/mobile/app/voice-settings.tsx +++ b/mobile/app/voice-settings.tsx @@ -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' diff --git a/mobile/rpc-foundation/pilot-scenarios.json b/mobile/rpc-foundation/pilot-scenarios.json index eeb45a4f5ee..dfd3079e39d 100644 --- a/mobile/rpc-foundation/pilot-scenarios.json +++ b/mobile/rpc-foundation/pilot-scenarios.json @@ -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": [ { diff --git a/mobile/src/accounts/MobileAccountsScreen.tsx b/mobile/src/accounts/MobileAccountsScreen.tsx index 2ea1d8b0c18..a76da2ffa62 100644 --- a/mobile/src/accounts/MobileAccountsScreen.tsx +++ b/mobile/src/accounts/MobileAccountsScreen.tsx @@ -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' diff --git a/mobile/src/agent-history/MobileAgentSessionHistoryPanel.tsx b/mobile/src/agent-history/MobileAgentSessionHistoryPanel.tsx index fdbd7fc5ff8..08a2d65d451 100644 --- a/mobile/src/agent-history/MobileAgentSessionHistoryPanel.tsx +++ b/mobile/src/agent-history/MobileAgentSessionHistoryPanel.tsx @@ -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' diff --git a/mobile/src/agent-history/ai-vault-resume-launch.ts b/mobile/src/agent-history/ai-vault-resume-launch.ts index 1926ddaa787..33594d0b2a3 100644 --- a/mobile/src/agent-history/ai-vault-resume-launch.ts +++ b/mobile/src/agent-history/ai-vault-resume-launch.ts @@ -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 diff --git a/mobile/src/agent-history/ai-vault-resume-preparation.ts b/mobile/src/agent-history/ai-vault-resume-preparation.ts index ef8194abb8c..f35d5609657 100644 --- a/mobile/src/agent-history/ai-vault-resume-preparation.ts +++ b/mobile/src/agent-history/ai-vault-resume-preparation.ts @@ -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 { // Why: per-account repinning runs on the serving host, whose account diff --git a/mobile/src/agent-history/mobile-ai-vault-resume-operations.ts b/mobile/src/agent-history/mobile-ai-vault-resume-operations.ts index e3a0dbc0ef2..879c6fde5e3 100644 --- a/mobile/src/agent-history/mobile-ai-vault-resume-operations.ts +++ b/mobile/src/agent-history/mobile-ai-vault-resume-operations.ts @@ -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( diff --git a/mobile/src/agent-history/open-mobile-agent-history-chat.ts b/mobile/src/agent-history/open-mobile-agent-history-chat.ts index e435509888e..46705610706 100644 --- a/mobile/src/agent-history/open-mobile-agent-history-chat.ts +++ b/mobile/src/agent-history/open-mobile-agent-history-chat.ts @@ -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: { diff --git a/mobile/src/agent-history/use-mobile-agent-history-state.ts b/mobile/src/agent-history/use-mobile-agent-history-state.ts index 5b8ebe2e2bc..122708bdeef 100644 --- a/mobile/src/agent-history/use-mobile-agent-history-state.ts +++ b/mobile/src/agent-history/use-mobile-agent-history-state.ts @@ -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' diff --git a/mobile/src/agents/MobileAgentListRows.tsx b/mobile/src/agents/MobileAgentListRows.tsx index 0f5f5b7908b..b564b7d4bdf 100644 --- a/mobile/src/agents/MobileAgentListRows.tsx +++ b/mobile/src/agents/MobileAgentListRows.tsx @@ -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' diff --git a/mobile/src/agents/agent-list-model.test.ts b/mobile/src/agents/agent-list-model.test.ts index 1b8ccb3bc2d..0af7468eaaa 100644 --- a/mobile/src/agents/agent-list-model.test.ts +++ b/mobile/src/agents/agent-list-model.test.ts @@ -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', () => { diff --git a/mobile/src/agents/agent-list-row-text.ts b/mobile/src/agents/agent-list-row-text.ts index c3e9d8ef8b7..194a77e7616 100644 --- a/mobile/src/agents/agent-list-row-text.ts +++ b/mobile/src/agents/agent-list-row-text.ts @@ -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 & { + 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 { diff --git a/mobile/src/agents/agent-list-rows.ts b/mobile/src/agents/agent-list-rows.ts index a9490a5929b..fc44c14f64b 100644 --- a/mobile/src/agents/agent-list-rows.ts +++ b/mobile/src/agents/agent-list-rows.ts @@ -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> { diff --git a/mobile/src/agents/use-agent-list-row-actions.tsx b/mobile/src/agents/use-agent-list-row-actions.tsx index dcd191a4b6c..f5b45eda889 100644 --- a/mobile/src/agents/use-agent-list-row-actions.tsx +++ b/mobile/src/agents/use-agent-list-row-actions.tsx @@ -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' diff --git a/mobile/src/automations/MobileAutomationsScreen.tsx b/mobile/src/automations/MobileAutomationsScreen.tsx index 9cb62a41d91..b9efea06339 100644 --- a/mobile/src/automations/MobileAutomationsScreen.tsx +++ b/mobile/src/automations/MobileAutomationsScreen.tsx @@ -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' diff --git a/mobile/src/automations/use-automation-catalog.ts b/mobile/src/automations/use-automation-catalog.ts index e51cf5190fa..18bc9e31df7 100644 --- a/mobile/src/automations/use-automation-catalog.ts +++ b/mobile/src/automations/use-automation-catalog.ts @@ -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' diff --git a/mobile/src/browser/mobile-session-browser-operations.ts b/mobile/src/browser/mobile-session-browser-operations.ts index 597ef6fac7a..2dee93227b4 100644 --- a/mobile/src/browser/mobile-session-browser-operations.ts +++ b/mobile/src/browser/mobile-session-browser-operations.ts @@ -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({ diff --git a/mobile/src/components/DragReorderList.test.tsx b/mobile/src/components/DragReorderList.test.tsx index defe697b10a..e70e5fecf88 100644 --- a/mobile/src/components/DragReorderList.test.tsx +++ b/mobile/src/components/DragReorderList.test.tsx @@ -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, { - items: ['a', 'b', 'c'], - itemKey: (item) => item, - rowPresentation: 'plain', - renderRow: (item, handle) => createElement('Text', null, item, handle), - onMove: move, - scrollRef: useAnimatedRef(), - scrollOffsetY: useSharedValue(0), - scrollContentHeight: useSharedValue(140) - }) - } - let root: ReturnType - 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()) -}) diff --git a/mobile/src/components/NewWorktreeModal.test.tsx b/mobile/src/components/NewWorktreeModal.test.tsx index 6da85abe6b3..439e4f2014e 100644 --- a/mobile/src/components/NewWorktreeModal.test.tsx +++ b/mobile/src/components/NewWorktreeModal.test.tsx @@ -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) { - 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(() => {})), - serverClients: new Map([['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) => { diff --git a/mobile/src/components/NewWorktreeModal.tsx b/mobile/src/components/NewWorktreeModal.tsx index ccae8181be7..801655b778c 100644 --- a/mobile/src/components/NewWorktreeModal.tsx +++ b/mobile/src/components/NewWorktreeModal.tsx @@ -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, diff --git a/mobile/src/components/SwipeRowActions.tsx b/mobile/src/components/SwipeRowActions.tsx index 704077f636d..b070de4ff38 100644 --- a/mobile/src/components/SwipeRowActions.tsx +++ b/mobile/src/components/SwipeRowActions.tsx @@ -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 ( - {leading ? renderAction(leading, 'leading') : null} - {trailing ? renderAction(trailing, 'trailing') : null} + + {leading ? renderAction(leading, 'leading') : null} + {trailing ? renderAction(trailing, 'trailing') : null} + {/* pan-y keeps the page scrolling vertically over the row on the web. */} {children(guardPress)} @@ -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 }, diff --git a/mobile/src/components/use-connected-host-picker.test.ts b/mobile/src/components/use-connected-host-picker.test.ts index 052c1b4a5f2..d986ef795cb 100644 --- a/mobile/src/components/use-connected-host-picker.test.ts +++ b/mobile/src/components/use-connected-host-picker.test.ts @@ -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 { diff --git a/mobile/src/components/use-connected-host-picker.ts b/mobile/src/components/use-connected-host-picker.ts index e6eec0b1e13..2697c9d18b9 100644 --- a/mobile/src/components/use-connected-host-picker.ts +++ b/mobile/src/components/use-connected-host-picker.ts @@ -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[]) { diff --git a/mobile/src/components/use-new-workspace-repositories.test.ts b/mobile/src/components/use-new-workspace-repositories.test.ts index 14196e03e81..09ada54c9cf 100644 --- a/mobile/src/components/use-new-workspace-repositories.test.ts +++ b/mobile/src/components/use-new-workspace-repositories.test.ts @@ -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 }) + }) }) diff --git a/mobile/src/device-store/device-store.ts b/mobile/src/device-store/device-store.ts index c86b067d79c..adbb20ff677 100644 --- a/mobile/src/device-store/device-store.ts +++ b/mobile/src/device-store/device-store.ts @@ -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, diff --git a/mobile/src/diagnostics/connection-diagnostics-report.ts b/mobile/src/diagnostics/connection-diagnostics-report.ts index 3a6dd9728bf..42a9581cb3b 100644 --- a/mobile/src/diagnostics/connection-diagnostics-report.ts +++ b/mobile/src/diagnostics/connection-diagnostics-report.ts @@ -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' diff --git a/mobile/src/diagnostics/connection-diagnostics-screen-data.test.ts b/mobile/src/diagnostics/connection-diagnostics-screen-data.test.ts index 3a895518657..fa859474b28 100644 --- a/mobile/src/diagnostics/connection-diagnostics-screen-data.test.ts +++ b/mobile/src/diagnostics/connection-diagnostics-screen-data.test.ts @@ -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, diff --git a/mobile/src/diagnostics/connection-diagnostics-screen-data.ts b/mobile/src/diagnostics/connection-diagnostics-screen-data.ts index 341044db3da..360b1e437ae 100644 --- a/mobile/src/diagnostics/connection-diagnostics-screen-data.ts +++ b/mobile/src/diagnostics/connection-diagnostics-screen-data.ts @@ -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. */ diff --git a/mobile/src/diagnostics/mobile-web-bundle-probe-row.tsx b/mobile/src/diagnostics/mobile-web-bundle-probe-row.tsx index 038c7f04810..37ab11331b1 100644 --- a/mobile/src/diagnostics/mobile-web-bundle-probe-row.tsx +++ b/mobile/src/diagnostics/mobile-web-bundle-probe-row.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { View, Text, Pressable, ActivityIndicator } from 'react-native' import { Package } from 'lucide-react-native' -import { loadHosts } from '../transport/hosts/host-store' +import { loadHosts } from '../host-catalog/host-store' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { troubleshootScreenStyles as styles } from './troubleshoot-screen-styles' @@ -9,7 +9,7 @@ import { useMobileWebBundleProbe, type MobileWebBundleProbeState } from './use-mobile-web-bundle-probe' -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import type { Translate } from '../i18n/mobile-i18n' import { useMobileTranslation } from '../i18n/use-mobile-translation' import { ListGroup, ListRow } from '../components/list/ListGroup' diff --git a/mobile/src/diagnostics/mobile-web-shell-update-failure-row.tsx b/mobile/src/diagnostics/mobile-web-shell-update-failure-row.tsx index 526bec82310..78889ea5e51 100644 --- a/mobile/src/diagnostics/mobile-web-shell-update-failure-row.tsx +++ b/mobile/src/diagnostics/mobile-web-shell-update-failure-row.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { Text, View } from 'react-native' import type { MobileWebShellUpdateFailure } from '../mobile-web-shell/mobile-web-shell-update-failure' import { processGenerationStore } from '../mobile-web-shell/process-generation-store' -import { loadHosts } from '../transport/hosts/host-store' +import { loadHosts } from '../host-catalog/host-store' import { formatUpdateFailure } from './mobile-web-shell-update-failure-copy' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { troubleshootScreenStyles as styles } from './troubleshoot-screen-styles' diff --git a/mobile/src/diagnostics/native-diagnostics-operations.test.ts b/mobile/src/diagnostics/native-diagnostics-operations.test.ts index 38cbb6e3ab2..8f57d41ce43 100644 --- a/mobile/src/diagnostics/native-diagnostics-operations.test.ts +++ b/mobile/src/diagnostics/native-diagnostics-operations.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ConnectionState } from '../transport/connection/connection-state' -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import { createNativeDiagnosticsOperations } from './native-diagnostics-operations' const logStore = vi.hoisted(() => ({ diff --git a/mobile/src/diagnostics/native-diagnostics-operations.ts b/mobile/src/diagnostics/native-diagnostics-operations.ts index 0b26bc5f912..5192c6d962d 100644 --- a/mobile/src/diagnostics/native-diagnostics-operations.ts +++ b/mobile/src/diagnostics/native-diagnostics-operations.ts @@ -1,6 +1,6 @@ import { Platform } from 'react-native' import Constants from 'expo-constants' -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' import { connectionLogStore } from './connection/persisted-connection-log-store' import { diff --git a/mobile/src/diagnostics/use-troubleshoot-diagnostics.ts b/mobile/src/diagnostics/use-troubleshoot-diagnostics.ts index 2eee9847c73..297addc4ed5 100644 --- a/mobile/src/diagnostics/use-troubleshoot-diagnostics.ts +++ b/mobile/src/diagnostics/use-troubleshoot-diagnostics.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import type { View } from 'react-native' import { Platform } from 'react-native' import { useMobileTranslation } from '../i18n/use-mobile-translation' -import { loadHostCatalog, loadHosts } from '../transport/hosts/host-store' +import { loadHostCatalog, loadHosts } from '../host-catalog/host-store' import { startDiagnosticFetchTimeout, type DiagnosticFetchTimeout diff --git a/mobile/src/dictation/dictation-reply-schema.ts b/mobile/src/dictation/dictation-reply-schema.ts index 23a738977ac..9d2bd752fcc 100644 --- a/mobile/src/dictation/dictation-reply-schema.ts +++ b/mobile/src/dictation/dictation-reply-schema.ts @@ -37,6 +37,8 @@ export const dictationSetupSchema = z.looseObject({ dictationHoldToRecord: salvagedOptional('dictationHoldToRecord', z.boolean()), // Absent from older hosts, which remove no fillers either; the phone then keeps them on, the host default. removeFillerWords: salvagedOptional('removeFillerWords', z.boolean()), + // Absent from older hosts, which stop after a minute; the shared reader maps absent and unknown to that. + stopAfterSilenceSeconds: salvagedOptional('stopAfterSilenceSeconds', z.number().nullable()), // Absent from older hosts, which spell nothing; malformed entries are dropped. dictationTerms: salvagedOptional( 'dictationTerms', diff --git a/mobile/src/dictation/mobile-dictation-host-context.ts b/mobile/src/dictation/mobile-dictation-host-context.ts index 9f4d3c23798..f9acb271e93 100644 --- a/mobile/src/dictation/mobile-dictation-host-context.ts +++ b/mobile/src/dictation/mobile-dictation-host-context.ts @@ -4,6 +4,7 @@ import type { RpcClient } from '../transport/rpc/rpc-client-contract' import type { MobileDictationAwayTarget } from './mobile-dictation-away-insert' import type { MobileDictationLiveText } from './use-mobile-dictation-announcements' import type { DictationTerm } from '../../../src/shared/dictation-dictionary' +import type { StopAfterSilenceSeconds } from '../../../src/shared/dictation-silence-stop' export type MobileDictationHostToast = { message: string; open?: () => void } @@ -21,6 +22,7 @@ export type MobileDictationOwner = { // The target tab's pane key, for a screen still in the stack to switch back to it. readonly paneKey: string | undefined readonly dictationMode: string | undefined + readonly silenceSeconds: StopAfterSilenceSeconds readonly removeFillerWords: boolean // The computer's terms for this worktree, for words the phone recognizes itself. readonly dictationTerms: readonly DictationTerm[] | undefined diff --git a/mobile/src/dictation/mobile-dictation-host.tsx b/mobile/src/dictation/mobile-dictation-host.tsx index b2276df26aa..37daa11a247 100644 --- a/mobile/src/dictation/mobile-dictation-host.tsx +++ b/mobile/src/dictation/mobile-dictation-host.tsx @@ -7,6 +7,7 @@ import { useClipboardWriter } from '../platform/clipboard' import { dictationFailureMessage, isDictationSetupRequiredError } from './mobile-dictation-setup' import { useMobileDictationHaptics } from './use-mobile-dictation-haptics' import { useMobileDictationSilenceStop } from './use-mobile-dictation-silence-stop' +import { DEFAULT_STOP_AFTER_SILENCE_SECONDS } from '../../../src/shared/dictation-silence-stop' import { insertMobileDictationAway } from './mobile-dictation-away-insert' import { nativeChatDictationCopy } from './mobile-session-native-chat-dictation-copy' import { dictationAwayCopy } from './mobile-dictation-away-copy' @@ -145,7 +146,14 @@ export function MobileDictationHostProvider({ children }: { children: ReactNode onAudioLimitReached: () => say(nativeChatDictationCopy.stoppedAtCloudLimit()) }) const stop = useMobileDictationHaptics(dictation) - useMobileDictationSilenceStop(dictation, owner?.dictationMode, stop, (message) => say(message)) + useMobileDictationSilenceStop( + dictation, + owner?.dictationMode, + // Off is null, so only a missing owner takes the default. + owner ? owner.silenceSeconds : DEFAULT_STOP_AFTER_SILENCE_SECONDS, + stop, + (message) => say(message) + ) // Screens subscribe to the live dictation; the host's context only changes when its owner or pill does. const liveRef = useRef(dictation) diff --git a/mobile/src/dictation/mobile-dictation-setup.ts b/mobile/src/dictation/mobile-dictation-setup.ts index 61e607ca7a4..9ddcee87e81 100644 --- a/mobile/src/dictation/mobile-dictation-setup.ts +++ b/mobile/src/dictation/mobile-dictation-setup.ts @@ -3,7 +3,7 @@ import { hostDictationErrorMessage } from '../../../src/shared/host-dictation-error-messages' import { translate } from '../i18n/mobile-i18n' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import type { RpcResponse } from '../transport/rpc/rpc-wire-types' import { interpretOrThrowRefusalMessage } from '../transport/rpc/rpc-refusal-message' import '../transport/connection/stable-logical-rpc-client' @@ -128,7 +128,7 @@ export function isDictationSetupRequiredError(message: string): boolean { /** `worktreeId` asks for that repo's dictionary terms too. */ export async function fetchDictationSetup( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId?: string ): Promise { const reply = await requestDictationSetupReply(client, worktreeId ? { worktreeId } : null) @@ -150,7 +150,7 @@ export async function fetchDictationSetup( } async function requestDictationSetupReply( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, params: { worktreeId: string } | null ): Promise { try { @@ -165,7 +165,7 @@ async function requestDictationSetupReply( } export async function downloadDictationModel( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, modelId: string ): Promise { const reply = await dictationModelDownload.request(client, { modelId }) @@ -176,7 +176,7 @@ export async function downloadDictationModel( } export async function deleteDictationModel( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, modelId: string ): Promise { const reply = await dictationModelDelete.request(client, { modelId }) @@ -187,7 +187,7 @@ export async function deleteDictationModel( } export async function setDictationConfig( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, params: { enabled?: boolean modelId?: string diff --git a/mobile/src/dictation/mobile-session-native-chat-dictation-copy.ts b/mobile/src/dictation/mobile-session-native-chat-dictation-copy.ts index f7ea2f1ee2e..74babf880fa 100644 --- a/mobile/src/dictation/mobile-session-native-chat-dictation-copy.ts +++ b/mobile/src/dictation/mobile-session-native-chat-dictation-copy.ts @@ -14,9 +14,10 @@ export const nativeChatDictationCopy = { 'session.useNativeChatDictation.stoppedAtCloudLimit', 'Dictation stopped at the 10-minute cloud limit.' ), - stoppedAfterSilence: () => + stoppedAfterSilence: (seconds: number) => translate( - 'session.useNativeChatDictation.stoppedAfterSilence', - 'Stopped after a minute of silence' + 'session.useNativeChatDictation.stoppedAfterSilenceSeconds', + 'Stopped after {{seconds}} seconds of silence', + { seconds } ) } diff --git a/mobile/src/dictation/use-mobile-dictation-live.ts b/mobile/src/dictation/use-mobile-dictation-live.ts index 13aa5f5081a..b5b89eff6f0 100644 --- a/mobile/src/dictation/use-mobile-dictation-live.ts +++ b/mobile/src/dictation/use-mobile-dictation-live.ts @@ -1,8 +1,8 @@ import { useCallback, useLayoutEffect, useMemo, useRef } from 'react' import type { UseMobileDictationResult } from '../hooks/mobile-dictation-session-state' import { useWorkspaceExecutionHost } from '../navigation/workspace-execution-host' -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 type { MobileDictationLive } from './mobile-dictation-live-context' import { dictationFailureReason } from './mobile-dictation-controls-copy' diff --git a/mobile/src/dictation/use-mobile-dictation-silence-stop.ts b/mobile/src/dictation/use-mobile-dictation-silence-stop.ts index 11cc537bccc..e9a6baf5a31 100644 --- a/mobile/src/dictation/use-mobile-dictation-silence-stop.ts +++ b/mobile/src/dictation/use-mobile-dictation-silence-stop.ts @@ -1,5 +1,8 @@ import { useEffect, useEffectEvent } from 'react' -import { watchDictationSilence } from '../../../src/shared/dictation-silence-stop' +import { + watchDictationSilence, + type StopAfterSilenceSeconds +} from '../../../src/shared/dictation-silence-stop' import type { UseMobileDictationResult } from '../hooks/mobile-dictation-session-state' import { nativeChatDictationCopy } from './mobile-session-native-chat-dictation-copy' @@ -7,20 +10,23 @@ import { nativeChatDictationCopy } from './mobile-session-native-chat-dictation- export function useMobileDictationSilenceStop( dictation: Pick, dictationMode: string | undefined, + silenceSeconds: StopAfterSilenceSeconds, stop: () => void, showToast: (message: string) => void ): void { const { meter } = dictation - const watching = dictation.isRecording && dictationMode !== 'hold' + const watching = dictation.isRecording && dictationMode !== 'hold' && silenceSeconds !== null const stopForSilence = useEffectEvent(() => { - showToast(nativeChatDictationCopy.stoppedAfterSilence()) + showToast(nativeChatDictationCopy.stoppedAfterSilence(silenceSeconds ?? 0)) stop() }) useEffect(() => { - if (!watching) { + if (!watching || silenceSeconds === null) { return } - return watchDictationSilence(meter.lastSpeechAt, Date.now, () => stopForSilence()) - }, [meter, watching]) + return watchDictationSilence(meter.lastSpeechAt, Date.now, silenceSeconds * 1000, () => + stopForSilence() + ) + }, [meter, watching, silenceSeconds]) } diff --git a/mobile/src/files/attachments/use-mobile-session-image-attachments.ts b/mobile/src/files/attachments/use-mobile-session-image-attachments.ts index b4ccc6417d4..00af1d341b1 100644 --- a/mobile/src/files/attachments/use-mobile-session-image-attachments.ts +++ b/mobile/src/files/attachments/use-mobile-session-image-attachments.ts @@ -4,8 +4,8 @@ import type { MobileImageSource } from '../../platform/media-picker-contract' import type { MobileNativeChatSendIntent, MobileNativeChatSendOutcome -} from '../../session/native-chat/mobile-native-chat-send' -import type { StructuredMobileSendAttachment } from '../../session/use-mobile-structured-send-with-outcome' +} from '../../session/native-chat/composer/mobile-native-chat-send' +import type { StructuredMobileSendAttachment } from '../../session/native-chat/composer/use-mobile-structured-send-with-outcome' import { useMobileImageAttachment } from './use-mobile-image-attachment' import { useMobileNativeChatImageAttachments, diff --git a/mobile/src/files/mobile-chat-attachment-read.ts b/mobile/src/files/mobile-chat-attachment-read.ts index 5a68f6cc431..c3f2953ff98 100644 --- a/mobile/src/files/mobile-chat-attachment-read.ts +++ b/mobile/src/files/mobile-chat-attachment-read.ts @@ -8,7 +8,7 @@ import { type AttachmentTextRead } from '../../../src/shared/attachment-text/attachment-text-source' import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' // Match the shared wire identifier without importing the host capability aggregate into this route. const AGENT_SESSION_TEXT_ATTACHMENT_READ_CAPABILITY = 'native-chat.text-attachment-read.v1' @@ -23,7 +23,7 @@ const textRead = bindDeferredRpcOperation( }) ) -type AttachmentReaderClient = RpcOperationSender & +type AttachmentReaderClient = UnvalidatedRpcRequestPort & Pick export function createMobileChatAttachmentReader( diff --git a/mobile/src/files/native-chat-file-search-operations.ts b/mobile/src/files/native-chat-file-search-operations.ts index 44192e60de9..8f39e7d5873 100644 --- a/mobile/src/files/native-chat-file-search-operations.ts +++ b/mobile/src/files/native-chat-file-search-operations.ts @@ -2,13 +2,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc/r import { rpcResultVariant } from '../transport/rpc/rpc-operation-result-reader' import '../terminal/terminal-reply-schema' import { workspaceFilePathsSchema } from './workspace-file-paths-reply-schema' -import '../session/agent-launch/mobile-agent-detection-reply-schema' -import './session-documents/mobile-markdown-tab-reply-schema' -import '../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../terminal/session/mobile-session-terminal-reply-schema' -import '../worktree/session-worktree-record-reply-schema' -import '../session/native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../terminal/quick-commands/mobile-quick-command-reply-schema' // Skip refusals to retain suggestions; inspect method_not_found first to fall back from search to inventory. export const nativeChatFileSearchRead = bindDeferredRpcOperation( diff --git a/mobile/src/files/session-documents/mobile-file-tap-open.ts b/mobile/src/files/session-documents/mobile-file-tap-open.ts index 67a654ee852..cabeebe5a3d 100644 --- a/mobile/src/files/session-documents/mobile-file-tap-open.ts +++ b/mobile/src/files/session-documents/mobile-file-tap-open.ts @@ -12,7 +12,7 @@ import { type OpenedMobileSessionTabActivationState } from '../../session/tabs/opened-mobile-session-tab' import { isRendererUnavailableReply } from '../../transport/rpc/renderer-unavailable-reply' -import type { RpcOperationSender } from '../../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../../transport/rpc/unvalidated-rpc-request-port' import type { RpcResponse } from '../../transport/rpc/rpc-wire-types' // Hard-wrap guesses can number in the hundreds; stay far below the page bridge's 64 pending requests. @@ -30,7 +30,7 @@ export type MobileFileTapFailure = { } export type OpenMobileFileTapOptions = { - client: RpcOperationSender + client: UnvalidatedRpcRequestPort hostId: string worktreeId: string worktreeName?: string diff --git a/mobile/src/files/session-documents/mobile-file-tap-operations.ts b/mobile/src/files/session-documents/mobile-file-tap-operations.ts index ce347a864c1..d4da628703a 100644 --- a/mobile/src/files/session-documents/mobile-file-tap-operations.ts +++ b/mobile/src/files/session-documents/mobile-file-tap-operations.ts @@ -4,7 +4,6 @@ import '../mobile-file-preview-operations' import { fileTapOpenedSchema } from './mobile-file-tap-reply-schema' import '../../agent-history/mobile-ai-vault-resume-reply-schema' import '../../browser/mobile-session-browser-reply-schema' -import '../../transport/rpc/rpc-uninterpreted-reply-schemas' // Best-effort taps skip refused opens; explicit Changes-screen opens instead show the host error. export const fileTapOpenRun = bindDeferredRpcOperation( diff --git a/mobile/src/files/session-documents/mobile-markdown-tab-operations.ts b/mobile/src/files/session-documents/mobile-markdown-tab-operations.ts index 6978b9515cf..2d3d4a336d6 100644 --- a/mobile/src/files/session-documents/mobile-markdown-tab-operations.ts +++ b/mobile/src/files/session-documents/mobile-markdown-tab-operations.ts @@ -19,13 +19,6 @@ export const sessionMarkdownNoteCreate = bindDeferredRpcOperation( import '../../terminal/terminal-reply-schema' import { markdownTabDocumentSchema } from './mobile-markdown-tab-reply-schema' -import '../../session/agent-launch/mobile-agent-detection-reply-schema' - -import '../../terminal/session/mobile-session-terminal-reply-schema' -import '../../worktree/session-worktree-record-reply-schema' -import '../../session/native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../../terminal/quick-commands/mobile-quick-command-reply-schema' -import '../workspace-file-paths-reply-schema' // Inspect renderer_unavailable before interpretation so headless hosts can fall back to disk. export const markdownTabRead = bindDeferredRpcOperation( @@ -38,8 +31,6 @@ export const markdownTabRead = bindDeferredRpcOperation( }) ) -import '../../session/tabs/mobile-session-tab-write-reply-schema' - import '../../terminal/quick-commands/mobile-quick-command-operations' export const markdownTabSave = bindDeferredRpcOperation( diff --git a/mobile/src/files/session-documents/use-mobile-file-tap-handlers.ts b/mobile/src/files/session-documents/use-mobile-file-tap-handlers.ts index ff6dfc7ddfd..31752b15f51 100644 --- a/mobile/src/files/session-documents/use-mobile-file-tap-handlers.ts +++ b/mobile/src/files/session-documents/use-mobile-file-tap-handlers.ts @@ -6,10 +6,10 @@ import { useWorkspaceRouteHandoff } from '../../navigation/workspace-route-hando import { triggerSelection } from '../../platform/haptics' import { openMobileFileTap, type FileTapSessionTab } from './mobile-file-tap-open' import { openMobileNativeChatFileTap } from '../../session/native-chat/visual/mobile-native-chat-open-file' -import type { RpcOperationSender } from '../../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../../transport/rpc/unvalidated-rpc-request-port' type MobileFileTapHandlerOptions = { - client: RpcOperationSender | null + client: UnvalidatedRpcRequestPort | null hostId: string worktreeId: string worktreeName?: string diff --git a/mobile/src/home/MobileHomeNeedsYou.tsx b/mobile/src/home/MobileHomeActiveAgents.tsx similarity index 70% rename from mobile/src/home/MobileHomeNeedsYou.tsx rename to mobile/src/home/MobileHomeActiveAgents.tsx index e31ca0db5c1..a8f41a222b0 100644 --- a/mobile/src/home/MobileHomeNeedsYou.tsx +++ b/mobile/src/home/MobileHomeActiveAgents.tsx @@ -2,15 +2,15 @@ import { memo } from 'react' import type { AgentListRow } from '../agents/agent-list-rows' import { MobileAgentListRows, type AgentListRowHandlers } from '../agents/MobileAgentListRows' import { useMobileTranslation } from '../i18n/use-mobile-translation' -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import { areStructurallyEqual } from '../worktree/structural-equality' import { MobileHomeSectionHeading } from './MobileHomeSectionHeading' // Enough to act on without pushing Desktops off the first screen; the Agents tab has the rest. -const NEEDS_YOU_ROWS_SHOWN = 3 +const ACTIVE_ROWS_SHOWN = 3 -/** Feed's short list of agents waiting on the user; hidden when none are. */ -export const MobileHomeNeedsYou = memo(function MobileHomeNeedsYou(props: { +/** Feed's short list of agents waiting on the user or working, those waiting first; hidden when none are. */ +export const MobileHomeActiveAgents = memo(function MobileHomeActiveAgents(props: { handlers: AgentListRowHandlers rows: readonly AgentListRow[] hosts: readonly HostProfile[] @@ -24,14 +24,14 @@ export const MobileHomeNeedsYou = memo(function MobileHomeNeedsYou(props: { return ( <> 1} hosts={props.hosts} diff --git a/mobile/src/home/MobileHomeDesktopRow.tsx b/mobile/src/home/MobileHomeDesktopRow.tsx index 2d8786ddb8a..71fb2e14a4a 100644 --- a/mobile/src/home/MobileHomeDesktopRow.tsx +++ b/mobile/src/home/MobileHomeDesktopRow.tsx @@ -6,10 +6,10 @@ import { statusDotColor } from '../components/StatusDot' import type { ConnectionVerdict } from '../transport/connection/connection-health' import { verdictDisplayLabel } from '../transport/connection/connection-health' import { mobileConnectionPathAccessibilityLabel } from '../transport/connection/mobile-connection-path-label' -import { useHostDisplay } from '../transport/hosts/use-host-display' +import { useHostDisplay } from '../host-descriptors/use-host-display' import type { MobileConnectionPath } from '../transport/connection/stable-logical-rpc-client' import type { ConnectionState } from '../transport/connection/connection-state' -import type { HostCatalogEntry, HostProfile } from '../transport/hosts/host-profile' +import type { HostCatalogEntry, HostProfile } from '../host-catalog/host-profile' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { homeHostWorktreeSummary, type HostWorktreeInfo } from '../worktree/home-worktree-info' diff --git a/mobile/src/home/MobileHomeHostList.tsx b/mobile/src/home/MobileHomeHostList.tsx index ad442bc3472..f336850241d 100644 --- a/mobile/src/home/MobileHomeHostList.tsx +++ b/mobile/src/home/MobileHomeHostList.tsx @@ -7,7 +7,7 @@ import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { classifyConnection } from '../transport/connection/connection-health' import { resolveHomeHostConnectionState } from '../transport/hosts/home-host-auto-connect' import type { ConnectionState } from '../transport/connection/connection-state' -import type { HostCatalogEntry } from '../transport/hosts/host-profile' +import type { HostCatalogEntry } from '../host-catalog/host-profile' import type { HostWorktreeInfo } from '../worktree/home-worktree-info' import type { HomeHostConnections } from './home-host-connection-projection' import { homeListStyles } from './home-list-styles' diff --git a/mobile/src/home/MobileHomeScreen.tsx b/mobile/src/home/MobileHomeScreen.tsx index b54010f1322..048b43c3ee9 100644 --- a/mobile/src/home/MobileHomeScreen.tsx +++ b/mobile/src/home/MobileHomeScreen.tsx @@ -23,9 +23,9 @@ import { } from '../transport/connection/host-client-hooks' import { hostEndpointLabel } from '../transport/hosts/host-endpoint-label' import { resolveHomeHostConnectionState } from '../transport/hosts/home-host-auto-connect' -import { removeHostAndCloseClient } from '../transport/hosts/host-removal-lifecycle' -import { loadHostCatalog } from '../transport/hosts/host-store' -import type { HostCatalogEntry, HostProfile } from '../transport/hosts/host-profile' +import { removeHostAndCloseClient } from '../host-catalog/host-removal-lifecycle' +import { loadHostCatalog } from '../host-catalog/host-store' +import type { HostCatalogEntry, HostProfile } from '../host-catalog/host-profile' import { useOpenMobileHostEdit } from '../host-screen/use-open-mobile-host-edit' import { mobileConnectionPathLabel } from '../transport/connection/mobile-connection-path-label' import { newWorkspaceOverlayHref } from '../new-workspace/new-workspace-overlay-route' diff --git a/mobile/src/home/MobileHomeTasksSection.tsx b/mobile/src/home/MobileHomeTasksSection.tsx index 8df195fc8b7..cb695351164 100644 --- a/mobile/src/home/MobileHomeTasksSection.tsx +++ b/mobile/src/home/MobileHomeTasksSection.tsx @@ -6,7 +6,7 @@ import { PickerModal } from '../components/PickerModal' import { useConnectedHostPicker } from '../components/use-connected-host-picker' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import { homeListStyles as styles } from './home-list-styles' type HomeHostSectionProps = { diff --git a/mobile/src/home/mobile-home-connection-state.ts b/mobile/src/home/mobile-home-connection-state.ts index 39353be62dd..03e3b95cf9e 100644 --- a/mobile/src/home/mobile-home-connection-state.ts +++ b/mobile/src/home/mobile-home-connection-state.ts @@ -1,6 +1,6 @@ import type { RpcClient } from '../transport/rpc/rpc-client-contract' import type { ConnectionState } from '../transport/connection/connection-state' -import type { HostCatalogEntry } from '../transport/hosts/host-profile' +import type { HostCatalogEntry } from '../host-catalog/host-profile' export type MobileHomeClientEntry = { hostId: string diff --git a/mobile/src/home/tabs/MobileHomeAgentsDesktopFilter.tsx b/mobile/src/home/tabs/MobileHomeAgentsDesktopFilter.tsx index 04b40720156..778dcb1a621 100644 --- a/mobile/src/home/tabs/MobileHomeAgentsDesktopFilter.tsx +++ b/mobile/src/home/tabs/MobileHomeAgentsDesktopFilter.tsx @@ -7,8 +7,8 @@ import type { Translate } from '../../i18n/mobile-i18n' import { useMobileTranslation } from '../../i18n/use-mobile-translation' import { colors } from '../../theme/mobile-theme' 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 { MobileHomePill } from './MobileHomeHostPill' const ALL_DESKTOPS = '__all__' diff --git a/mobile/src/home/tabs/MobileHomeAgentsTab.tsx b/mobile/src/home/tabs/MobileHomeAgentsTab.tsx index c31b8a37079..e1029611889 100644 --- a/mobile/src/home/tabs/MobileHomeAgentsTab.tsx +++ b/mobile/src/home/tabs/MobileHomeAgentsTab.tsx @@ -29,8 +29,8 @@ import { useSerializedStoredState } from '../../storage/use-serialized-stored-st 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 { formatTimeAgo } from '../../worktree/agent-row-display' import { homeListStyles } from '../home-list-styles' import { MobileHomeSectionHeading } from '../MobileHomeSectionHeading' diff --git a/mobile/src/home/tabs/MobileHomeAutomationsTab.tsx b/mobile/src/home/tabs/MobileHomeAutomationsTab.tsx index b385ac496eb..eeda1071407 100644 --- a/mobile/src/home/tabs/MobileHomeAutomationsTab.tsx +++ b/mobile/src/home/tabs/MobileHomeAutomationsTab.tsx @@ -20,8 +20,8 @@ import { themedStyleSheet } from '../../theme/themed-styles' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { useHostClient } from '../../transport/connection/host-client-hooks' import type { ConnectionState } from '../../transport/connection/connection-state' -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 { homeListStyles as list } from '../home-list-styles' import { MobileHomeSectionHeading } from '../MobileHomeSectionHeading' import { MobileHomeAutomationRow, MobileHomeAutomationRunRow } from './MobileHomeAutomationRows' diff --git a/mobile/src/home/tabs/MobileHomeFeedTab.tsx b/mobile/src/home/tabs/MobileHomeFeedTab.tsx index 68e1d7ad013..3ba20626d9a 100644 --- a/mobile/src/home/tabs/MobileHomeFeedTab.tsx +++ b/mobile/src/home/tabs/MobileHomeFeedTab.tsx @@ -4,16 +4,16 @@ import { useResponsiveLayout } from '../../layout/responsive-layout' import { spacing } from '../../theme/mobile-theme' import { themedStyleSheet } from '../../theme/themed-styles' import type { AgentListRowHandlers } from '../../agents/MobileAgentListRows' -import type { HostCatalogEntry } from '../../transport/hosts/host-profile' +import type { HostCatalogEntry } from '../../host-catalog/host-profile' import { MobileHomeAppUpdateCard } from '../MobileHomeAppUpdateCard' import { MobileHomeEmptyState } from '../MobileHomeEmptyState' import { MobileHomeHostList } from '../MobileHomeHostList' -import { MobileHomeNeedsYou } from '../MobileHomeNeedsYou' +import { MobileHomeActiveAgents } from '../MobileHomeActiveAgents' import { MobileHomeTasksSection } from '../MobileHomeTasksSection' import type { MobileHomeData } from '../use-mobile-home-data' import { MobileHomeTabHeader } from './MobileHomeTabHeader' -/** Home's first tab: agents that need you, the desktops, and Tasks. */ +/** Home's first tab: agents that need you or are working, the desktops, and Tasks. */ export function MobileHomeFeedTab(props: { data: MobileHomeData bottomInset: number @@ -42,7 +42,7 @@ export function MobileHomeFeedTab(props: { /> ) } - const needsYou = data.agentsNeedingYou.length > 0 + const hasActiveAgents = data.activeAgents.length > 0 return ( <> {title} @@ -53,8 +53,8 @@ export function MobileHomeFeedTab(props: { isWideLayout && { maxWidth: contentMaxWidth, width: '100%', alignSelf: 'center' } ]} > - selectAgentsNeedingYou(agentList), [agentList]) + const activeAgents = useMemo(() => selectActiveAgents(agentList), [agentList]) const needsYouByHost = useMemo( () => countAgentsNeedingYouByHost(agentsNeedingYou), [agentsNeedingYou] @@ -274,6 +276,7 @@ export function useMobileHomeData() { ...connections, accountsByHost, applyAgentActivity: agentActivity.apply, + activeAgents, agentList, agentsNeedingYou, connectedHosts, diff --git a/mobile/src/home/use-mobile-home-host-connections.ts b/mobile/src/home/use-mobile-home-host-connections.ts index 11b26c25c46..cb5838a8510 100644 --- a/mobile/src/home/use-mobile-home-host-connections.ts +++ b/mobile/src/home/use-mobile-home-host-connections.ts @@ -12,7 +12,7 @@ import { usePrimeHosts } from '../transport/connection/host-client-hooks' import { selectHomeAutoConnectHostIds } from '../transport/hosts/home-host-auto-connect' import type { RpcClient } from '../transport/rpc/rpc-client-contract' import type { ConnectionState } from '../transport/connection/connection-state' -import type { HostCatalogEntry, HostProfile } from '../transport/hosts/host-profile' +import type { HostCatalogEntry, HostProfile } from '../host-catalog/host-profile' import { useAllHostClients } from '../transport/connection/use-all-host-clients' import { readMobileHomeLastConnected, diff --git a/mobile/src/transport/hosts/host-catalog-credential-join.ts b/mobile/src/host-catalog/host-catalog-credential-join.ts similarity index 92% rename from mobile/src/transport/hosts/host-catalog-credential-join.ts rename to mobile/src/host-catalog/host-catalog-credential-join.ts index 5f7b45e1cc1..be175ff615b 100644 --- a/mobile/src/transport/hosts/host-catalog-credential-join.ts +++ b/mobile/src/host-catalog/host-catalog-credential-join.ts @@ -1,4 +1,4 @@ -import type { MobileRelayEndpoint } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' +import type { MobileRelayEndpoint } from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import type { HostListSnapshot } from './host-list-load-sharing' import type { HostCatalogEntry, StoredHostProfile } from './host-profile' diff --git a/mobile/src/transport/hosts/host-catalog-selection.ts b/mobile/src/host-catalog/host-catalog-selection.ts similarity index 100% rename from mobile/src/transport/hosts/host-catalog-selection.ts rename to mobile/src/host-catalog/host-catalog-selection.ts diff --git a/mobile/src/host-catalog/host-connection-history.ts b/mobile/src/host-catalog/host-connection-history.ts new file mode 100644 index 00000000000..a34e4289c12 --- /dev/null +++ b/mobile/src/host-catalog/host-connection-history.ts @@ -0,0 +1,17 @@ +import { mutateStoredHosts } from './host-list-mutation-queue' + +export async function updateLastConnected(hostId: string): Promise { + try { + await mutateStoredHosts((hosts) => { + const index = hosts.findIndex((h) => h.id === hostId) + if (index === -1) { + return hosts + } + const next = hosts.slice() + next[index] = { ...next[index]!, lastConnected: Date.now() } + return next + }) + } catch { + // Swallow bookkeeping failures because void callers cannot catch them. + } +} diff --git a/mobile/src/host-catalog/host-connection-history.web.ts b/mobile/src/host-catalog/host-connection-history.web.ts new file mode 100644 index 00000000000..095f18af6dd --- /dev/null +++ b/mobile/src/host-catalog/host-connection-history.web.ts @@ -0,0 +1,2 @@ +// Recency belongs to the native host list, which this page does not show. +export const updateLastConnected = (_hostId: string): Promise => Promise.resolve() diff --git a/mobile/src/transport/hosts/host-list-load-sharing.test.ts b/mobile/src/host-catalog/host-list-load-sharing.test.ts similarity index 100% rename from mobile/src/transport/hosts/host-list-load-sharing.test.ts rename to mobile/src/host-catalog/host-list-load-sharing.test.ts diff --git a/mobile/src/transport/hosts/host-list-load-sharing.ts b/mobile/src/host-catalog/host-list-load-sharing.ts similarity index 100% rename from mobile/src/transport/hosts/host-list-load-sharing.ts rename to mobile/src/host-catalog/host-list-load-sharing.ts diff --git a/mobile/src/transport/hosts/host-list-mutation-queue.ts b/mobile/src/host-catalog/host-list-mutation-queue.ts similarity index 100% rename from mobile/src/transport/hosts/host-list-mutation-queue.ts rename to mobile/src/host-catalog/host-list-mutation-queue.ts diff --git a/mobile/src/transport/hosts/host-metadata-store.ts b/mobile/src/host-catalog/host-metadata-store.ts similarity index 100% rename from mobile/src/transport/hosts/host-metadata-store.ts rename to mobile/src/host-catalog/host-metadata-store.ts diff --git a/mobile/src/transport/hosts/host-name-identity.test.ts b/mobile/src/host-catalog/host-name-identity.test.ts similarity index 96% rename from mobile/src/transport/hosts/host-name-identity.test.ts rename to mobile/src/host-catalog/host-name-identity.test.ts index 0c4878d0ef4..b69bc8d38fb 100644 --- a/mobile/src/transport/hosts/host-name-identity.test.ts +++ b/mobile/src/host-catalog/host-name-identity.test.ts @@ -25,7 +25,7 @@ vi.mock('react-native', () => ({ Platform: { OS: 'ios' } })) -vi.mock('./host-credential-cleanup', () => ({ +vi.mock('../host-credentials/host-credential-cleanup', () => ({ cancelPendingHostCredentialCleanup: vi.fn().mockResolvedValue(undefined), recordHostCredentialCleanupIntent: vi.fn().mockResolvedValue(undefined), scheduleHostCredentialCleanup: vi.fn().mockResolvedValue(undefined), @@ -38,7 +38,7 @@ import { savePairedHost, updateHostNameAndEndpoint } from './host-store' -import { updateHostDescriptor } from './host-descriptor-persistence' +import { updateHostDescriptor } from '../host-descriptors/host-descriptor-persistence' import { resetMobileRelayHostOverlayStoreForTests } from './mobile-relay-host-overlay-store' import { StoredHostProfileSchema, type StoredHostProfile } from './host-profile' diff --git a/mobile/src/transport/hosts/host-name-identity.ts b/mobile/src/host-catalog/host-name-identity.ts similarity index 97% rename from mobile/src/transport/hosts/host-name-identity.ts rename to mobile/src/host-catalog/host-name-identity.ts index b87cf1c273f..1b5bdb878e1 100644 --- a/mobile/src/transport/hosts/host-name-identity.ts +++ b/mobile/src/host-catalog/host-name-identity.ts @@ -1,6 +1,6 @@ import { GENERATED_HOST_NAME_PATTERN, getNextHostNameFromHosts } from './host-names' import type { StoredHostProfile } from './host-profile' -import { sameHostMachine, type HostMachine } from '../../../../src/shared/host-machine-line' +import { sameHostMachine, type HostMachine } from '../../../src/shared/host-machine-line' // Resolve name identity inside host-store’s serialized mutation so display name and sources commit together; see withReportedDescriptor’s omission exception. diff --git a/mobile/src/transport/hosts/host-names.ts b/mobile/src/host-catalog/host-names.ts similarity index 100% rename from mobile/src/transport/hosts/host-names.ts rename to mobile/src/host-catalog/host-names.ts diff --git a/mobile/src/transport/hosts/host-profile.ts b/mobile/src/host-catalog/host-profile.ts similarity index 82% rename from mobile/src/transport/hosts/host-profile.ts rename to mobile/src/host-catalog/host-profile.ts index 9fb53ad7404..f74c01a0f79 100644 --- a/mobile/src/transport/hosts/host-profile.ts +++ b/mobile/src/host-catalog/host-profile.ts @@ -1,13 +1,13 @@ import { z } from 'zod' -import '../../../../src/shared/remote-runtime/pairing/mobile-relay-pairing-offer' +import '../../../src/shared/remote-runtime/pairing/mobile-relay-pairing-offer' import { MobileRelayEndpointSchema, type MobileRelayEndpoint -} from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' -import { NODE_PLATFORM_NAMES } from '../connection/mobile-runtime-host-platform' -import { salvagedOptional } from '../../../../src/shared/zod-salvage' -import type { HostMachine } from '../../../../src/shared/host-machine-line' -import { HostMachineSchema } from './host-status-reply-schema' +} from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' +import { NODE_PLATFORM_NAMES } from '../transport/connection/mobile-runtime-host-platform' +import { salvagedOptional } from '../../../src/shared/zod-salvage' +import type { HostMachine } from '../../../src/shared/host-machine-line' +import { HostMachineSchema } from '../transport/hosts/host-status-reply-schema' export type HostProfile = { id: string diff --git a/mobile/src/transport/hosts/host-removal-lifecycle.ts b/mobile/src/host-catalog/host-removal-lifecycle.ts similarity index 76% rename from mobile/src/transport/hosts/host-removal-lifecycle.ts rename to mobile/src/host-catalog/host-removal-lifecycle.ts index 4349f09127e..65ff0eff370 100644 --- a/mobile/src/transport/hosts/host-removal-lifecycle.ts +++ b/mobile/src/host-catalog/host-removal-lifecycle.ts @@ -1,12 +1,12 @@ import type { QueryClient } from '@tanstack/react-query' -import { wipeHostStore } from '../../device-store/device-store' +import { wipeHostStore } from '../device-store/device-store' import { deleteHostPageCache, forgetHostUpdateFailures -} from '../../mobile-web-shell/removed-host-shell-cache' -import { unregisterPushForRemovedHost } from '../../notifications/push-registration' -import { forgetNativeHostQueries } from '../../query/native-query-persistence' -import { forgetHostDescriptor } from './host-descriptor-store' +} from '../mobile-web-shell/removed-host-shell-cache' +import { unregisterPushForRemovedHost } from '../notifications/push-registration' +import { forgetNativeHostQueries } from '../query/native-query-persistence' +import { forgetHostDescriptor } from '../host-descriptors/host-descriptor-store' import { removeHost } from './host-store' export async function removeHostAndCloseClient( diff --git a/mobile/src/transport/hosts/host-removal-lifecycle.web.ts b/mobile/src/host-catalog/host-removal-lifecycle.web.ts similarity index 100% rename from mobile/src/transport/hosts/host-removal-lifecycle.web.ts rename to mobile/src/host-catalog/host-removal-lifecycle.web.ts diff --git a/mobile/src/transport/hosts/host-store.test.ts b/mobile/src/host-catalog/host-store.test.ts similarity index 55% rename from mobile/src/transport/hosts/host-store.test.ts rename to mobile/src/host-catalog/host-store.test.ts index 04b82058be9..9b2a3a60382 100644 --- a/mobile/src/transport/hosts/host-store.test.ts +++ b/mobile/src/host-catalog/host-store.test.ts @@ -31,24 +31,14 @@ vi.mock('react-native', () => ({ Platform: platformMock })) -vi.mock('./host-credential-cleanup', () => ({ +vi.mock('../host-credentials/host-credential-cleanup', () => ({ cancelPendingHostCredentialCleanup: (...args: unknown[]) => cancelCleanupMock(...args), recordHostCredentialCleanupIntent: (...args: unknown[]) => recordCleanupIntentMock(...args), scheduleHostCredentialCleanup: (...args: unknown[]) => scheduleCleanupMock(...args), retryPendingHostCredentialCleanups: vi.fn() })) -import { - loadHostCatalog, - loadHosts, - RelayRoutingHostRemovedError, - removeHost, - resolvePairingHostIdentity, - resetHostStoreForTests, - savePairedHost, - setRelayRouting, - updateHostNameAndEndpoint -} from './host-store' +import { loadHostCatalog, loadHosts, resetHostStoreForTests, savePairedHost } from './host-store' import { resetMobileRelayHostOverlayStoreForTests } from './mobile-relay-host-overlay-store' const HOSTS_STORAGE_KEY = 'orca:hosts' @@ -67,14 +57,6 @@ const HOST_TWO = { publicKeyB64: 'key-2', lastConnected: 0 } -const HOST_ONE_RELAY = { - v: 1 as const, - directorUrl: 'https://relay.onorca.dev', - cellUrl: 'https://relay-c1.onorca.dev', - assignmentEpoch: 7, - relayHostId: 'AbCdEf0123_-xyZ9', - e2eeFraming: 2 as const -} describe('host-store list mutations', () => { let storedHostsRaw: string @@ -134,13 +116,6 @@ describe('host-store list mutations', () => { expect(profiles.map(({ id }) => id)).toEqual([HOST_TWO.id]) }) - it('fails closed when durable host identity storage is unreadable', async () => { - asyncStorageMock.getItem.mockRejectedValueOnce(new Error('storage unavailable')) - - await expect(resolvePairingHostIdentity('key-new', 'host-new')).rejects.toThrow(/unreadable/) - expect(asyncStorageMock.setItem).not.toHaveBeenCalled() - }) - it('serves a committed replacement token when same-key metadata persistence fails', async () => { const duplicate = { ...HOST_TWO, @@ -164,148 +139,6 @@ describe('host-store list mutations', () => { loadHosts().then((hosts) => hosts.find(({ id }) => id === HOST_ONE.id)?.deviceToken) ).resolves.toBe('replacement-token') }) - - it('refuses an early same-key token write without durable cleanup intent', async () => { - const replacement = { - ...HOST_TWO, - id: 'host-replacement', - publicKeyB64: HOST_ONE.publicKeyB64, - deviceToken: 'replacement-token' - } - recordCleanupIntentMock.mockRejectedValueOnce(new Error('intent storage unavailable')) - - await expect(savePairedHost(replacement)).rejects.toThrow('intent storage unavailable') - - expect(JSON.parse(storedHostsRaw)).toEqual([HOST_ONE, HOST_TWO]) - expect(secureStoreMock.setItemAsync).not.toHaveBeenCalled() - }) - - it('refuses to resurrect a removed host during relay upgrade publication', async () => { - storedHostsRaw = JSON.stringify([HOST_TWO]) - - await expect(setRelayRouting(HOST_ONE.id, HOST_ONE_RELAY)).rejects.toBeInstanceOf( - RelayRoutingHostRemovedError - ) - - expect(JSON.parse(storedHostsRaw)).toEqual([HOST_TWO]) - expect(storedOverlayRaw).toBeNull() - expect(secureStoreMock.setItemAsync).not.toHaveBeenCalled() - }) - - it('applies concurrent rename and remove without clobbering either', async () => { - let releaseReads: (() => void) | undefined - const readsReleased = new Promise((resolve) => { - releaseReads = resolve - }) - let pendingReads = 0 - asyncStorageMock.getItem.mockImplementation(async (key: string) => { - if (key !== HOSTS_STORAGE_KEY) { - return null - } - pendingReads += 1 - if (pendingReads <= 2) { - await readsReleased - } - return storedHostsRaw - }) - - const rename = updateHostNameAndEndpoint(HOST_ONE.id, { personalName: 'Renamed Host' }) - const remove = removeHost(HOST_TWO.id) - // Overlap writers behind one read gate to expose lost updates without serialization. - await vi.waitFor(() => { - expect(pendingReads).toBeGreaterThanOrEqual(1) - }) - releaseReads?.() - await Promise.all([rename, remove]) - - expect(JSON.parse(storedHostsRaw)).toEqual([ - { ...HOST_ONE, name: 'Renamed Host', personalName: 'Renamed Host' } - ]) - }) - - it('does not wipe the host list when storage is unreadable during mutation', async () => { - storedHostsRaw = '{' - await expect(updateHostNameAndEndpoint(HOST_ONE.id, { personalName: 'Nope' })).rejects.toThrow( - /unreadable/ - ) - expect(asyncStorageMock.setItem).not.toHaveBeenCalled() - expect(storedHostsRaw).toBe('{') - }) - - // Park the slow keychain pass while a durable write invalidates it. - function gateKeychainReads(): () => void { - let release: () => void = () => {} - const gate = new Promise((resolve) => { - release = resolve - }) - secureStoreMock.getItemAsync.mockImplementation(async (key: string) => { - await gate - return key.endsWith(HOST_ONE.id) || key.endsWith(HOST_TWO.id) ? `token-${key.at(-1)}` : null - }) - return release - } - - it('does not serve a pre-removal snapshot to a load issued after the removal (#8791)', async () => { - const releaseKeychain = gateKeychainReads() - const parkedLoad = loadHosts() - await vi.waitFor(() => { - expect(secureStoreMock.getItemAsync).toHaveBeenCalled() - }) - - await removeHost(HOST_ONE.id) - const afterRemoval = loadHosts() - releaseKeychain() - - await parkedLoad - await expect(afterRemoval.then((hosts) => hosts.map((host) => host.id))).resolves.toEqual([ - HOST_TWO.id - ]) - }) - - it('does not serve a pre-rename snapshot to a load issued after the rename (#8791)', async () => { - const releaseKeychain = gateKeychainReads() - const parkedLoad = loadHosts() - await vi.waitFor(() => { - expect(secureStoreMock.getItemAsync).toHaveBeenCalled() - }) - - await updateHostNameAndEndpoint(HOST_ONE.id, { personalName: 'Living Room Mac' }) - const afterRename = loadHosts() - releaseKeychain() - - await parkedLoad - const hosts = await afterRename - expect(hosts.find((host) => host.id === HOST_ONE.id)?.name).toBe('Living Room Mac') - }) - - it('does not let a late pre-write token read poison the token cache', async () => { - const newHost = { - id: 'host-new', - name: 'New Host', - endpoint: 'ws://127.0.0.1:3', - publicKeyB64: 'key-new', - deviceToken: 'token-new', - lastConnected: 0 - } - storedHostsRaw = JSON.stringify([{ ...newHost, deviceToken: undefined }]) - let resolvePrewriteTokenRead: (token: string) => void = () => {} - secureStoreMock.getItemAsync.mockReturnValue( - new Promise((resolve) => { - resolvePrewriteTokenRead = resolve - }) - ) - - const parkedLoad = loadHosts() - await vi.waitFor(() => { - expect(secureStoreMock.getItemAsync).toHaveBeenCalled() - }) - await savePairedHost(newHost) - resolvePrewriteTokenRead('token-old') - await parkedLoad - - const hosts = await loadHosts() - expect(hosts.find((host) => host.id === newHost.id)?.deviceToken).toBe(newHost.deviceToken) - }) }) describe('host-store pairing save after an Android encryption rejection', () => { diff --git a/mobile/src/transport/hosts/host-store.ts b/mobile/src/host-catalog/host-store.ts similarity index 91% rename from mobile/src/transport/hosts/host-store.ts rename to mobile/src/host-catalog/host-store.ts index 487b1344b13..f8e09c068dd 100644 --- a/mobile/src/transport/hosts/host-store.ts +++ b/mobile/src/host-catalog/host-store.ts @@ -1,4 +1,4 @@ -import type { MobileRelayEndpoint } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' +import type { MobileRelayEndpoint } from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import { HostProfileSchema } from './host-profile' import type { HostCatalogEntry, HostProfile, StoredHostProfile } from './host-profile' @@ -6,27 +6,30 @@ import { getNextHostNameFromHosts } from './host-names' import { mergeHostNameIdentity, withPersonalName } from './host-name-identity' import * as hostListLoads from './host-list-load-sharing' import { joinHostCatalogCredentials } from './host-catalog-credential-join' -import { resetPairingKeychainForTests } from './pairing-keychain' -import { readHostDeviceToken, writeHostDeviceToken } from './host-device-token-store' +import { resetPairingKeychainForTests } from '../host-credentials/pairing-keychain' +import { + readHostDeviceToken, + writeHostDeviceToken +} from '../host-credentials/host-device-token-store' import { cancelPendingHostCredentialCleanup, recordHostCredentialCleanupIntent, retryPendingHostCredentialCleanups, scheduleHostCredentialCleanup -} from './host-credential-cleanup' +} from '../host-credentials/host-credential-cleanup' import { loadMobileRelayHostRoutingState, removeMobileRelayHostRouting, removeMobileRelayHostRoutings, saveMobileRelayHostRouting } from './mobile-relay-host-overlay-store' -import { scheduleOrphanedMobileRelayCleanup } from '../relay/mobile-relay-orphan-cleanup' +import { scheduleOrphanedMobileRelayCleanup } from '../transport/relay/mobile-relay-orphan-cleanup' import { getHostCredentialWriteRevision, markHostCredentialWrite, resetHostCredentialWriteRevisionsForTests -} from './host-credential-write-revision' -import { createUnpairedHostCredentialDeletion } from '../../pairing/unpaired-host-credential-deletion' +} from '../host-credentials/host-credential-write-revision' +import { createUnpairedHostCredentialDeletion } from '../pairing/unpaired-host-credential-deletion' import { loadStoredHostProfiles, readStoredHostProfilesForMutation, @@ -148,7 +151,7 @@ function removeOrphanOverlayIfUnpaired(hostId: string): Promise { }) } -import './host-descriptor-persistence' +import '../host-descriptors/host-descriptor-persistence' export class RelayRoutingHostRemovedError extends Error {} @@ -315,22 +318,6 @@ export async function updateHostNameAndEndpoint( }) } -export async function updateLastConnected(hostId: string): Promise { - try { - await mutateStoredHosts((hosts) => { - const index = hosts.findIndex((h) => h.id === hostId) - if (index === -1) { - return hosts - } - const next = hosts.slice() - next[index] = { ...next[index]!, lastConnected: Date.now() } - return next - }) - } catch { - // Swallow bookkeeping failures because void callers cannot catch them. - } -} - export function resetHostStoreForTests(): void { resetHostListMutationQueueForTests() tokenCache.clear() diff --git a/mobile/src/transport/hosts/host-store.web.ts b/mobile/src/host-catalog/host-store.web.ts similarity index 77% rename from mobile/src/transport/hosts/host-store.web.ts rename to mobile/src/host-catalog/host-store.web.ts index 3b8e3f33ac8..37475ac40ed 100644 --- a/mobile/src/transport/hosts/host-store.web.ts +++ b/mobile/src/host-catalog/host-store.web.ts @@ -1,5 +1,5 @@ -import { readPageHostProfile } from '../../mobile-web-shell/bridge/page-host-profile' -import type { MobileRelayEndpoint } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' +import { readPageHostProfile } from '../mobile-web-shell/bridge/page-host-profile' +import type { MobileRelayEndpoint } from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import type { HostCatalogEntry, HostProfile } from './host-profile' // The page gets no device credential or public key; RPC uses the shell’s paired client. @@ -26,9 +26,6 @@ export const setRelayRouting = (_hostId: string, _relay: MobileRelayEndpoint): P export const removeHost = (_hostId: string): Promise => Promise.resolve() -// Recency belongs to the native host list, which this page does not show. -export const updateLastConnected = (_hostId: string): Promise => Promise.resolve() - export function updateHostNameAndEndpoint( _hostId: string, _updates: { personalName?: string | null; endpoint?: string } diff --git a/mobile/src/transport/hosts/mobile-relay-host-overlay-store.test.ts b/mobile/src/host-catalog/mobile-relay-host-overlay-store.test.ts similarity index 78% rename from mobile/src/transport/hosts/mobile-relay-host-overlay-store.test.ts rename to mobile/src/host-catalog/mobile-relay-host-overlay-store.test.ts index 6d4daea98d3..fd4e8f54f2f 100644 --- a/mobile/src/transport/hosts/mobile-relay-host-overlay-store.test.ts +++ b/mobile/src/host-catalog/mobile-relay-host-overlay-store.test.ts @@ -74,22 +74,4 @@ describe('mobile relay host overlay store', () => { await expect(saveMobileRelayHostRouting('host-1', RELAY)).resolves.toBe(true) expect(JSON.parse(stored!)).toEqual([RELAY_ONLY_OVERLAY]) }) - - it('never overlays or resurrects a host whose legacy base was removed', async () => { - stored = JSON.stringify([LEGACY_OVERLAY]) - - await expect(loadMobileRelayHostRoutingState(new Set())).resolves.toEqual({ - relays: new Map(), - orphanHostIds: ['host-1'] - }) - expect(asyncStorage.setItem).not.toHaveBeenCalled() - expect(JSON.parse(stored)).toEqual([LEGACY_OVERLAY]) - }) - - it('refuses to overwrite unreadable overlay storage', async () => { - stored = '{' - - await expect(saveMobileRelayHostRouting('host-1', RELAY)).rejects.toThrow(/unreadable/) - expect(asyncStorage.setItem).not.toHaveBeenCalled() - }) }) diff --git a/mobile/src/transport/hosts/mobile-relay-host-overlay-store.ts b/mobile/src/host-catalog/mobile-relay-host-overlay-store.ts similarity index 96% rename from mobile/src/transport/hosts/mobile-relay-host-overlay-store.ts rename to mobile/src/host-catalog/mobile-relay-host-overlay-store.ts index 8a30cd7219a..3f4da803bd2 100644 --- a/mobile/src/transport/hosts/mobile-relay-host-overlay-store.ts +++ b/mobile/src/host-catalog/mobile-relay-host-overlay-store.ts @@ -1,5 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage' -import type { MobileRelayEndpoint } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' +import type { MobileRelayEndpoint } from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import { MobileRelayHostOverlaySchema, toStoredMobileRelayHostOverlay, diff --git a/mobile/src/transport/hosts/mobile-relay-host-overlay.ts b/mobile/src/host-catalog/mobile-relay-host-overlay.ts similarity index 92% rename from mobile/src/transport/hosts/mobile-relay-host-overlay.ts rename to mobile/src/host-catalog/mobile-relay-host-overlay.ts index 3e82389cde5..f97d6f7cb2c 100644 --- a/mobile/src/transport/hosts/mobile-relay-host-overlay.ts +++ b/mobile/src/host-catalog/mobile-relay-host-overlay.ts @@ -2,8 +2,8 @@ import { z } from 'zod' import { MobileRelayEndpointSchema, type MobileRelayEndpoint -} from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' -import { relayConnectWebSocketUrl } from '../relay/mobile-relay-connect-url' +} from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' +import { relayConnectWebSocketUrl } from '../transport/relay/mobile-relay-connect-url' const MobileAccessEndpointSchema = z .object({ diff --git a/mobile/src/transport/hosts/page-host-removal-refusal.ts b/mobile/src/host-catalog/page-host-removal-refusal.ts similarity index 100% rename from mobile/src/transport/hosts/page-host-removal-refusal.ts rename to mobile/src/host-catalog/page-host-removal-refusal.ts diff --git a/mobile/src/transport/hosts/use-host-profile.ts b/mobile/src/host-catalog/use-host-profile.ts similarity index 100% rename from mobile/src/transport/hosts/use-host-profile.ts rename to mobile/src/host-catalog/use-host-profile.ts diff --git a/mobile/src/transport/hosts/host-credential-cleanup.ts b/mobile/src/host-credentials/host-credential-cleanup.ts similarity index 100% rename from mobile/src/transport/hosts/host-credential-cleanup.ts rename to mobile/src/host-credentials/host-credential-cleanup.ts diff --git a/mobile/src/transport/hosts/host-credential-write-revision.ts b/mobile/src/host-credentials/host-credential-write-revision.ts similarity index 100% rename from mobile/src/transport/hosts/host-credential-write-revision.ts rename to mobile/src/host-credentials/host-credential-write-revision.ts diff --git a/mobile/src/transport/hosts/host-device-token-store.ts b/mobile/src/host-credentials/host-device-token-store.ts similarity index 100% rename from mobile/src/transport/hosts/host-device-token-store.ts rename to mobile/src/host-credentials/host-device-token-store.ts diff --git a/mobile/src/transport/hosts/host-device-token-store.web.ts b/mobile/src/host-credentials/host-device-token-store.web.ts similarity index 100% rename from mobile/src/transport/hosts/host-device-token-store.web.ts rename to mobile/src/host-credentials/host-device-token-store.web.ts diff --git a/mobile/src/transport/hosts/mobile-relay-credential-bundle.test.ts b/mobile/src/host-credentials/mobile-relay-credential-bundle.test.ts similarity index 96% rename from mobile/src/transport/hosts/mobile-relay-credential-bundle.test.ts rename to mobile/src/host-credentials/mobile-relay-credential-bundle.test.ts index 48b66a8b2da..37e2f71cbd9 100644 --- a/mobile/src/transport/hosts/mobile-relay-credential-bundle.test.ts +++ b/mobile/src/host-credentials/mobile-relay-credential-bundle.test.ts @@ -19,7 +19,7 @@ vi.mock('expo-secure-store', () => ({ vi.mock('react-native', () => ({ Platform: platform })) import { promotePairingJournalCredential } from './mobile-relay-credential-bundle' -import type { MobileRelayPairingJournal } from '../../pairing/mobile-relay-pairing-journal' +import type { MobileRelayPairingJournal } from '../pairing/mobile-relay-pairing-journal' import { resetPairingKeychainForTests } from './pairing-keychain' const journal = { diff --git a/mobile/src/transport/hosts/mobile-relay-credential-bundle.ts b/mobile/src/host-credentials/mobile-relay-credential-bundle.ts similarity index 93% rename from mobile/src/transport/hosts/mobile-relay-credential-bundle.ts rename to mobile/src/host-credentials/mobile-relay-credential-bundle.ts index 21e103cfc40..f48b5b939d3 100644 --- a/mobile/src/transport/hosts/mobile-relay-credential-bundle.ts +++ b/mobile/src/host-credentials/mobile-relay-credential-bundle.ts @@ -1,7 +1,7 @@ import { Platform } from 'react-native' import { z } from 'zod' -import type { DeviceCredentialInstalled } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' -import type { MobileRelayPairingJournal } from '../../pairing/mobile-relay-pairing-journal' +import type { DeviceCredentialInstalled } from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' +import type { MobileRelayPairingJournal } from '../pairing/mobile-relay-pairing-journal' import { deletePairingKeychainItem, readPairingKeychainItem, diff --git a/mobile/src/transport/hosts/pairing-keychain.ts b/mobile/src/host-credentials/pairing-keychain.ts similarity index 100% rename from mobile/src/transport/hosts/pairing-keychain.ts rename to mobile/src/host-credentials/pairing-keychain.ts diff --git a/mobile/src/transport/hosts/host-app-version-store.ts b/mobile/src/host-descriptors/host-app-version-store.ts similarity index 100% rename from mobile/src/transport/hosts/host-app-version-store.ts rename to mobile/src/host-descriptors/host-app-version-store.ts diff --git a/mobile/src/transport/hosts/host-app-version-store.web.ts b/mobile/src/host-descriptors/host-app-version-store.web.ts similarity index 100% rename from mobile/src/transport/hosts/host-app-version-store.web.ts rename to mobile/src/host-descriptors/host-app-version-store.web.ts diff --git a/mobile/src/transport/hosts/host-app-version.ts b/mobile/src/host-descriptors/host-app-version.ts similarity index 100% rename from mobile/src/transport/hosts/host-app-version.ts rename to mobile/src/host-descriptors/host-app-version.ts diff --git a/mobile/src/transport/hosts/host-descriptor-persistence.ts b/mobile/src/host-descriptors/host-descriptor-persistence.ts similarity index 82% rename from mobile/src/transport/hosts/host-descriptor-persistence.ts rename to mobile/src/host-descriptors/host-descriptor-persistence.ts index 12fd0d18d24..dbd342527ed 100644 --- a/mobile/src/transport/hosts/host-descriptor-persistence.ts +++ b/mobile/src/host-descriptors/host-descriptor-persistence.ts @@ -1,5 +1,8 @@ -import { mutateStoredHosts } from './host-list-mutation-queue' -import { withReportedDescriptor, type ReportedHostDescriptor } from './host-name-identity' +import { mutateStoredHosts } from '../host-catalog/host-list-mutation-queue' +import { + withReportedDescriptor, + type ReportedHostDescriptor +} from '../host-catalog/host-name-identity' // Keep authenticated descriptor upkeep best-effort; older hosts omitting both fields must not churn stored rows. export async function updateHostDescriptor( diff --git a/mobile/src/transport/hosts/host-descriptor-persistence.web.ts b/mobile/src/host-descriptors/host-descriptor-persistence.web.ts similarity index 59% rename from mobile/src/transport/hosts/host-descriptor-persistence.web.ts rename to mobile/src/host-descriptors/host-descriptor-persistence.web.ts index 43933095a61..b99ea73eb3c 100644 --- a/mobile/src/transport/hosts/host-descriptor-persistence.web.ts +++ b/mobile/src/host-descriptors/host-descriptor-persistence.web.ts @@ -1,5 +1,5 @@ -import '../../mobile-web-shell/bridge/page-host-profile' -import type { ReportedHostDescriptor } from './host-name-identity' +import '../mobile-web-shell/bridge/page-host-profile' +import type { ReportedHostDescriptor } from '../host-catalog/host-name-identity' // Last-known descriptors belong to the native host list. export function updateHostDescriptor( diff --git a/mobile/src/transport/hosts/host-descriptor-recorder.ts b/mobile/src/host-descriptors/host-descriptor-recorder.ts similarity index 83% rename from mobile/src/transport/hosts/host-descriptor-recorder.ts rename to mobile/src/host-descriptors/host-descriptor-recorder.ts index 0b5ab0e5cdb..49aad137b63 100644 --- a/mobile/src/transport/hosts/host-descriptor-recorder.ts +++ b/mobile/src/host-descriptors/host-descriptor-recorder.ts @@ -1,7 +1,7 @@ import { recordHostDescriptor } from './host-descriptor-store' -import './host-store' +import '../host-catalog/host-store' import { updateHostDescriptor } from './host-descriptor-persistence' -import type { HostStatusReply } from './host-status-reply-schema' +import type { HostStatusReply } from '../transport/hosts/host-status-reply-schema' // Update live rows and persisted last-known descriptors together; best-effort bookkeeping must never gate connection. export function recordHostDescriptorFromStatus(hostId: string, status: HostStatusReply): void { diff --git a/mobile/src/transport/hosts/host-descriptor-store.ts b/mobile/src/host-descriptors/host-descriptor-store.ts similarity index 95% rename from mobile/src/transport/hosts/host-descriptor-store.ts rename to mobile/src/host-descriptors/host-descriptor-store.ts index bb114074164..ad33c055070 100644 --- a/mobile/src/transport/hosts/host-descriptor-store.ts +++ b/mobile/src/host-descriptors/host-descriptor-store.ts @@ -1,5 +1,5 @@ import { useCallback, useSyncExternalStore } from 'react' -import { sameHostMachine, type HostMachine } from '../../../../src/shared/host-machine-line' +import { sameHostMachine, type HostMachine } from '../../../src/shared/host-machine-line' export type HostMachineDescriptor = { machineName: string | null diff --git a/mobile/src/transport/hosts/use-host-display.ts b/mobile/src/host-descriptors/use-host-display.ts similarity index 85% rename from mobile/src/transport/hosts/use-host-display.ts rename to mobile/src/host-descriptors/use-host-display.ts index 0f0e8abefe6..49ab4f235df 100644 --- a/mobile/src/transport/hosts/use-host-display.ts +++ b/mobile/src/host-descriptors/use-host-display.ts @@ -1,10 +1,10 @@ import { resolveHostDisplay, type HostDisplayResolution -} from '../../../../src/shared/host-display-resolution' +} from '../../../src/shared/host-display-resolution' import { useHostDescriptor } from './host-descriptor-store' -import { classifyLegacyHostName } from './host-name-identity' -import type { HostMachine } from '../../../../src/shared/host-machine-line' +import { classifyLegacyHostName } from '../host-catalog/host-name-identity' +import type { HostMachine } from '../../../src/shared/host-machine-line' export type HostDisplaySource = { id: string diff --git a/mobile/src/host-list-action-sheet-actions.ts b/mobile/src/host-list-action-sheet-actions.ts index 036d025d798..ea156122a5a 100644 --- a/mobile/src/host-list-action-sheet-actions.ts +++ b/mobile/src/host-list-action-sheet-actions.ts @@ -1,7 +1,7 @@ import { Activity, Edit3, PowerOff, RefreshCw } from 'lucide-react-native' import type { ActionSheetAction } from './components/ActionSheetModal' import type { ConnectionState } from './transport/connection/connection-state' -import type { HostProfile } from './transport/hosts/host-profile' +import type { HostProfile } from './host-catalog/host-profile' import { translate } from './i18n/mobile-i18n' // Defer navigation and new drawers until this native Modal unmounts; presenting into a live one freezes iOS (#8791). diff --git a/mobile/src/host-screen/host-stored-identity.ts b/mobile/src/host-screen/host-stored-identity.ts index 31d75015e5d..dd02444eb75 100644 --- a/mobile/src/host-screen/host-stored-identity.ts +++ b/mobile/src/host-screen/host-stored-identity.ts @@ -1,4 +1,4 @@ -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import type { HostMachine } from '../../../src/shared/host-machine-line' export type HostStoredDescriptor = { diff --git a/mobile/src/host-screen/use-host-display-worktrees.ts b/mobile/src/host-screen/use-host-display-worktrees.ts index 84f72fa8a86..517c0924fa2 100644 --- a/mobile/src/host-screen/use-host-display-worktrees.ts +++ b/mobile/src/host-screen/use-host-display-worktrees.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import { useHostDescriptor } from '../transport/hosts/host-descriptor-store' +import { useHostDescriptor } from '../host-descriptors/host-descriptor-store' import type { ConnectionState } from '../transport/connection/connection-state' import { serverHostHealth, diff --git a/mobile/src/host-screen/use-host-screen-controller.ts b/mobile/src/host-screen/use-host-screen-controller.ts index 983fa99eb96..e5ea6277d43 100644 --- a/mobile/src/host-screen/use-host-screen-controller.ts +++ b/mobile/src/host-screen/use-host-screen-controller.ts @@ -28,7 +28,7 @@ import { useHostViewSettings } from './use-host-view-settings' import { countActiveWorkspaceFilters } from '../../../src/shared/workspace-view/workspace-row-filters' import { useHostWorktreeActions } from './use-host-worktree-actions' import { useHostWorktreeCatalog } from './use-host-worktree-catalog' -import { useHostDisplay } from '../transport/hosts/use-host-display' +import { useHostDisplay } from '../host-descriptors/use-host-display' import { classifyConnection } from '../transport/connection/connection-health' export type HostScreenProps = { diff --git a/mobile/src/host-screen/use-host-screen-identity.ts b/mobile/src/host-screen/use-host-screen-identity.ts index f4e10c10019..057a23b8718 100644 --- a/mobile/src/host-screen/use-host-screen-identity.ts +++ b/mobile/src/host-screen/use-host-screen-identity.ts @@ -3,8 +3,9 @@ import { useQueryClient } from '@tanstack/react-query' import { translate } from '../i18n/mobile-i18n' import { ORCA_SIDEBAR_INPUTS_IS_MAIN } from '../../../src/shared/sidebar-inputs-switch' import { loadPinnedIds } from '../storage/preferences' -import { loadHosts, peekHosts, updateLastConnected } from '../transport/hosts/host-store' -import type { HostProfile } from '../transport/hosts/host-profile' +import { loadHosts, peekHosts } from '../host-catalog/host-store' +import { updateLastConnected } from '../host-catalog/host-connection-history' +import type { HostProfile } from '../host-catalog/host-profile' import type { RpcClient } from '../transport/rpc/rpc-client-contract' import type { HostScreenState } from './use-host-screen-state' import { storedHostDescriptor } from './host-stored-identity' diff --git a/mobile/src/host-screen/use-host-screen-state.ts b/mobile/src/host-screen/use-host-screen-state.ts index 742a6ac91c5..4f40a1a95bb 100644 --- a/mobile/src/host-screen/use-host-screen-state.ts +++ b/mobile/src/host-screen/use-host-screen-state.ts @@ -4,7 +4,7 @@ import type { ExecutionHostId } from '../../../src/shared/execution-host' import type { ExecutionHostHealth } from '../../../src/shared/execution-host/execution-host-health' import { storedHostDescriptor, type HostStoredDescriptor } from './host-stored-identity' import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import { INITIAL_MOBILE_VIEW, type MobileViewState } from '../worktree/workspace-view-settings' import type { Worktree } from '../worktree/workspace-list-sections' import type { ServedProjectSections } from '../worktree/project-sections-operations' diff --git a/mobile/src/host-screen/use-host-worktree-actions.ts b/mobile/src/host-screen/use-host-worktree-actions.ts index 5d2814b1049..d96d2e8c9ab 100644 --- a/mobile/src/host-screen/use-host-worktree-actions.ts +++ b/mobile/src/host-screen/use-host-worktree-actions.ts @@ -6,8 +6,8 @@ import { floatingWorkspaceSessionPath } from '../session/floating-workspace' import { holdSessionLeave } from '../session/session-leave-guard' import { savePinnedIds } from '../storage/preferences' import type { useForgetHostClient } from '../transport/connection/host-client-hooks' -import { removeHostAndCloseClient } from '../transport/hosts/host-removal-lifecycle' -import { isPageHostRemovalUnavailable } from '../transport/hosts/page-host-removal-refusal' +import { removeHostAndCloseClient } from '../host-catalog/host-removal-lifecycle' +import { isPageHostRemovalUnavailable } from '../host-catalog/page-host-removal-refusal' import type { RpcClient } from '../transport/rpc/rpc-client-contract' import { rpcClientForExecutionHost, diff --git a/mobile/src/i18n/bundled-locale-catalogs.ts b/mobile/src/i18n/bundled-locale-catalogs.ts new file mode 100644 index 00000000000..5883e969ed4 --- /dev/null +++ b/mobile/src/i18n/bundled-locale-catalogs.ts @@ -0,0 +1,23 @@ +import type { SupportedUiLocale } from '../../../src/shared/ui-locale' + +export type LocaleCatalog = Readonly> + +// Literal specifiers: Metro evaluates only the chosen module, and the page build splits each into its own chunk. +const CATALOG_IMPORTS: Record, () => Promise> = { + de: () => import('./locales/de.json').then((module) => module.default), + es: () => import('./locales/es.json').then((module) => module.default), + fr: () => import('./locales/fr.json').then((module) => module.default), + ja: () => import('./locales/ja.json').then((module) => module.default), + ko: () => import('./locales/ko.json').then((module) => module.default), + pt: () => import('./locales/pt.json').then((module) => module.default), + ru: () => import('./locales/ru.json').then((module) => module.default), + tr: () => import('./locales/tr.json').then((module) => module.default), + zh: () => import('./locales/zh.json').then((module) => module.default), + 'zh-Hant': () => import('./locales/zh-Hant.json').then((module) => module.default) +} + +export function importBundledLocaleCatalog( + locale: Exclude +): Promise { + return CATALOG_IMPORTS[locale]() +} diff --git a/mobile/src/i18n/locale-catalog-imports.ts b/mobile/src/i18n/locale-catalog-imports.ts index 664d608019e..d89bb938fea 100644 --- a/mobile/src/i18n/locale-catalog-imports.ts +++ b/mobile/src/i18n/locale-catalog-imports.ts @@ -1,23 +1,9 @@ import type { SupportedUiLocale } from '../../../src/shared/ui-locale' +import type { LocaleCatalog } from './bundled-locale-catalogs' -export type LocaleCatalog = Readonly> +export { importBundledLocaleCatalog as importLocaleCatalog } from './bundled-locale-catalogs' -// Literal specifiers: Metro evaluates only the chosen module, and the page build splits each into its own chunk. -const CATALOG_IMPORTS: Record, () => Promise> = { - de: () => import('./locales/de.json').then((module) => module.default), - es: () => import('./locales/es.json').then((module) => module.default), - fr: () => import('./locales/fr.json').then((module) => module.default), - ja: () => import('./locales/ja.json').then((module) => module.default), - ko: () => import('./locales/ko.json').then((module) => module.default), - pt: () => import('./locales/pt.json').then((module) => module.default), - ru: () => import('./locales/ru.json').then((module) => module.default), - tr: () => import('./locales/tr.json').then((module) => module.default), - zh: () => import('./locales/zh.json').then((module) => module.default), - 'zh-Hant': () => import('./locales/zh-Hant.json').then((module) => module.default) -} - -export function importLocaleCatalog( - locale: Exclude -): Promise { - return CATALOG_IMPORTS[locale]() -} +// Bundled catalogs never change after their import, so none arrives later; the page build's .web.ts does. +export const onLocaleCatalogArrival: ( + listener: (locale: Exclude, catalog: LocaleCatalog) => void +) => () => void = () => () => undefined diff --git a/mobile/src/i18n/locale-catalog-imports.web.ts b/mobile/src/i18n/locale-catalog-imports.web.ts new file mode 100644 index 00000000000..9b3d85fdce2 --- /dev/null +++ b/mobile/src/i18n/locale-catalog-imports.web.ts @@ -0,0 +1,36 @@ +import type { SupportedUiLocale } from '../../../src/shared/ui-locale' +import { importBundledLocaleCatalog, type LocaleCatalog } from './bundled-locale-catalogs' + +// A shell that skips the catalog chunks has page-locale-catalog-source.ts read them from the desktop +// and the device store; other shells import the chunk as before. No transport here, so pages that +// only translate pull in none. +export type { LocaleCatalog } +type CatalogLocale = Exclude +type CatalogLoader = (locale: CatalogLocale) => Promise +type ArrivalListener = (locale: CatalogLocale, catalog: LocaleCatalog) => void + +let loader: CatalogLoader | null = null +const arrivalListeners = new Set() + +export function publishPageLocaleCatalogLoader(next: CatalogLoader): void { + loader = next +} + +// Resolve the catalog or reject so activation falls back to English. +export function importLocaleCatalog(locale: CatalogLocale): Promise { + return loader === null ? importBundledLocaleCatalog(locale) : loader(locale) +} + +// A catalog that arrives after activation settled: a newer copy, or the first one after an offline start. +export function onLocaleCatalogArrival(listener: ArrivalListener): () => void { + arrivalListeners.add(listener) + return () => { + arrivalListeners.delete(listener) + } +} + +export function announceLocaleCatalog(locale: CatalogLocale, catalog: LocaleCatalog): void { + for (const listener of arrivalListeners) { + listener(locale, catalog) + } +} diff --git a/mobile/src/i18n/locales/de.json b/mobile/src/i18n/locales/de.json index 6093f930436..c8c64c2cf93 100644 --- a/mobile/src/i18n/locales/de.json +++ b/mobile/src/i18n/locales/de.json @@ -2454,8 +2454,7 @@ "tabStrip": { "checkingCapabilities": "Desktop-Funktionen werden geprüft – gleich erneut versuchen", "newTab": "Neuen Tab erstellen", - "quickCommandsUpdateRequired": "Desktop-Update für Schnellbefehle erforderlich", - "tabStatus": "{{title}}, {{status}}" + "quickCommandsUpdateRequired": "Desktop-Update für Schnellbefehle erforderlich" }, "terminalActionSheetActions": { "clearTerminal": "Terminal leeren", @@ -2568,8 +2567,8 @@ "insertFailed": "Diktat konnte nicht eingefügt werden", "insertFailedCopied": "Diktat konnte nicht eingefügt werden und wurde daher in die Zwischenablage kopiert", "inserted": "Diktat eingefügt", - "stoppedAfterSilence": "Nach einer Minute Stille beendet", - "stoppedAtCloudLimit": "Diktat am 10-Minuten-Limit der Cloud gestoppt." + "stoppedAtCloudLimit": "Diktat am 10-Minuten-Limit der Cloud gestoppt.", + "stoppedAfterSilenceSeconds": "Nach {{seconds}} Sekunden Stille gestoppt" }, "useNativeChatStop": { "stopNotSent": "Stoppbefehl nicht gesendet", @@ -3364,6 +3363,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "Zusammengeführt", + "closed": "Geschlossen", + "conflicts": "Konflikte", + "behind": "Nicht aktuell", + "blocked": "Blockiert", + "checksPending": "Prüfungen ausstehend", + "ableToMerge": "Zusammenführen möglich", + "unknown": "Unbekannt" + }, + "pagination": { + "pageUnreachable": "Seite {{page}} liegt außerhalb des Bereichs der GitHub-Suche.", + "loadPageFailed": "Seite {{page}} konnte nicht von GitHub geladen werden.", + "noMoreResults": "Keine weiteren Ergebnisse auf Seite {{page}}." + }, "composerSource": { "baseFailed": "Der Basis-Branch konnte nicht ermittelt werden.", "forkPushWarning": "Bei diesem PR ist „Allow edits from maintainers“ deaktiviert; GitHub könnte einen Push zum Fork ablehnen." diff --git a/mobile/src/i18n/locales/en.json b/mobile/src/i18n/locales/en.json index eecc184562f..f14faed6778 100644 --- a/mobile/src/i18n/locales/en.json +++ b/mobile/src/i18n/locales/en.json @@ -661,8 +661,8 @@ "openAccountsHint": "Opens this desktop’s accounts", "emptyTitle": "No usage yet" }, - "needsYou": { - "heading": "Needs you", + "activeAgents": { + "heading": "Active", "seeAll": "See all agents" } }, @@ -2182,10 +2182,10 @@ "renameTerminal": "Rename Terminal", "renameTerminalFailed": "Couldn't rename the terminal. {{reason}}", "terminalName": "Terminal name", - "allowAutoClose": "Allow Auto-Close" + "allowAutoClose": "Allow Auto-Close", + "agents": "Agents" }, "tabStrip": { - "tabStatus": "{{title}}, {{status}}", "newTab": "New tab", "quickCommandsUpdateRequired": "Desktop update required for quick commands", "checkingCapabilities": "Checking desktop capabilities — try again in a moment" @@ -2406,8 +2406,8 @@ "insertFailedCopied": "Couldn't insert dictation, so it was copied to the clipboard", "insertFailed": "Couldn't insert dictation", "inserted": "Dictation inserted", - "stoppedAfterSilence": "Stopped after a minute of silence", - "stoppedAtCloudLimit": "Dictation stopped at the 10-minute cloud limit." + "stoppedAtCloudLimit": "Dictation stopped at the 10-minute cloud limit.", + "stoppedAfterSilenceSeconds": "Stopped after {{seconds}} seconds of silence" }, "quickCommandLaunch": { "editBeforeRunning": "Edit this quick command before running it", @@ -2887,8 +2887,6 @@ }, "presentation": { "loadingTabs": "Loading tabs", - "oneTab": "1 tab", - "tabCount": "{{tabCount}} tabs", "tapToRetry": "{{status}} — tap to retry" }, "documentReaders": { @@ -2926,6 +2924,29 @@ "skip": "Skip", "stop": "Stop", "yes": "Yes" + }, + "tabsSheet": { + "needsYou": "Needs you", + "working": "Working", + "everythingElse": "Everything else", + "open": "Open", + "closeTab": "Close {{title}}" + }, + "tabRows": { + "browser": "Browser", + "markdown": "Markdown note", + "file": "File", + "chat": "Chat", + "terminal": "Terminal" + }, + "tabBar": { + "oneTab": "1 tab", + "tabCount": "{{count}} tabs", + "anotherNeedsYou": "another tab needs you", + "actionsHint": "Shows actions for this tab", + "next": "Next tab", + "previous": "Previous tab", + "hint": "Shows all tabs" } }, "chat": { @@ -2967,7 +2988,12 @@ "skip": "Skip", "step": "Step {{step}}", "other": "Other…", - "answerPlaceholder": "Type your answer" + "answerPlaceholder": "Type your answer", + "optionPreview": "{{option}} preview", + "previewPosition": "{{current}} of {{total}}", + "chosen": "Chosen", + "chooseThis": "Choose this", + "viewPreview": "View" }, "permission": { "reason": "Reason: ", diff --git a/mobile/src/i18n/locales/es.json b/mobile/src/i18n/locales/es.json index e898993112e..78f372f2264 100644 --- a/mobile/src/i18n/locales/es.json +++ b/mobile/src/i18n/locales/es.json @@ -1502,11 +1502,11 @@ "updateRequired": "Actualiza Orca Mobile para abrir archivos PDF" }, "unsavedEditsOffer": { - "changedSince": "Modificado en el escritorio desde tus cambios de las {{time}}", + "changedSince": "Modificado en el ordenador después de tus cambios de {{time}}", "copyMyEdits": "Copiar mis cambios", "discard": "Descartar", "restore": "Restaurar", - "unsavedFrom": "Cambios sin guardar de las {{time}}" + "unsavedFrom": "Cambios sin guardar ({{time}})" } }, "home": { @@ -2524,8 +2524,7 @@ "tabStrip": { "checkingCapabilities": "Comprobando las funciones del escritorio. Inténtalo de nuevo en un momento.", "newTab": "Nueva pestaña", - "quickCommandsUpdateRequired": "Actualiza el escritorio para usar los comandos rápidos.", - "tabStatus": "{{title}}, {{status}}" + "quickCommandsUpdateRequired": "Actualiza el escritorio para usar los comandos rápidos." }, "terminalActionSheetActions": { "clearTerminal": "Limpiar terminal", @@ -2638,8 +2637,8 @@ "insertFailed": "No se pudo insertar el dictado", "insertFailedCopied": "No se pudo insertar el dictado, así que se copió al portapapeles.", "inserted": "Dictado insertado", - "stoppedAfterSilence": "Se detuvo tras un minuto de silencio", - "stoppedAtCloudLimit": "Dictado detenido al llegar al límite de 10 minutos en la nube." + "stoppedAtCloudLimit": "Dictado detenido al llegar al límite de 10 minutos en la nube.", + "stoppedAfterSilenceSeconds": "Detenido tras {{seconds}} segundos de silencio" }, "useNativeChatStop": { "stopNotSent": "No se envió la orden de detener", @@ -3452,6 +3451,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "Fusionado", + "closed": "Cerrado", + "conflicts": "Conflictos", + "behind": "Desactualizado", + "blocked": "Bloqueado", + "checksPending": "Comprobaciones pendientes", + "ableToMerge": "Se puede fusionar", + "unknown": "Desconocido" + }, + "pagination": { + "pageUnreachable": "La página {{page}} está fuera del alcance de la búsqueda de GitHub.", + "loadPageFailed": "No se pudo cargar la página {{page}} desde GitHub.", + "noMoreResults": "No hay más resultados en la página {{page}}." + }, "composerSource": { "baseFailed": "No se pudo resolver la rama base.", "forkPushWarning": "Esta PR tiene desactivada la opción \"Allow edits from maintainers\"; GitHub podría rechazar el push al fork." diff --git a/mobile/src/i18n/locales/fr.json b/mobile/src/i18n/locales/fr.json index 8d807315fb1..20d2d3b5828 100644 --- a/mobile/src/i18n/locales/fr.json +++ b/mobile/src/i18n/locales/fr.json @@ -2524,8 +2524,7 @@ "tabStrip": { "checkingCapabilities": "Vérification des fonctionnalités de l'ordinateur — réessayez dans un instant", "newTab": "Nouvel onglet", - "quickCommandsUpdateRequired": "Mettez à jour Orca sur l'ordinateur pour utiliser les commandes rapides", - "tabStatus": "{{title}}, {{status}}" + "quickCommandsUpdateRequired": "Mettez à jour Orca sur l'ordinateur pour utiliser les commandes rapides" }, "terminalActionSheetActions": { "clearTerminal": "Effacer le terminal", @@ -2638,8 +2637,8 @@ "insertFailed": "Impossible d'insérer la dictée", "insertFailedCopied": "Impossible d'insérer la dictée ; elle a été copiée dans le presse-papiers", "inserted": "Dictée insérée", - "stoppedAfterSilence": "Arrêt après une minute de silence", - "stoppedAtCloudLimit": "Dictée arrêtée à la limite cloud de 10 minutes." + "stoppedAtCloudLimit": "Dictée arrêtée à la limite cloud de 10 minutes.", + "stoppedAfterSilenceSeconds": "Arrêté après {{seconds}} secondes de silence" }, "useNativeChatStop": { "stopNotSent": "Commande d'arrêt non envoyée", @@ -3452,6 +3451,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "Fusionné", + "closed": "Fermé", + "conflicts": "Conflits", + "behind": "En retard", + "blocked": "Bloqué", + "checksPending": "Vérifications en attente", + "ableToMerge": "Fusion possible", + "unknown": "Inconnu" + }, + "pagination": { + "pageUnreachable": "La page {{page}} dépasse la limite des résultats de recherche GitHub.", + "loadPageFailed": "La page {{page}} n’a pas pu être chargée depuis GitHub.", + "noMoreResults": "Aucun autre résultat sur la page {{page}}." + }, "composerSource": { "baseFailed": "Échec de la résolution de la branche de base.", "forkPushWarning": "L'option « Autoriser les modifications des mainteneurs » est désactivée pour cette PR ; le push vers le fork peut être refusé par GitHub." diff --git a/mobile/src/i18n/locales/ja.json b/mobile/src/i18n/locales/ja.json index d95daaff72f..c6ee2e0fa4f 100644 --- a/mobile/src/i18n/locales/ja.json +++ b/mobile/src/i18n/locales/ja.json @@ -2450,8 +2450,7 @@ "tabStrip": { "checkingCapabilities": "デスクトップの対応機能を確認中です。少し待ってから再試行してください。", "newTab": "新規タブ", - "quickCommandsUpdateRequired": "クイックコマンドの利用にはデスクトップの更新が必要です。", - "tabStatus": "{{title}}、{{status}}" + "quickCommandsUpdateRequired": "クイックコマンドの利用にはデスクトップの更新が必要です。" }, "terminalActionSheetActions": { "clearTerminal": "ターミナルをクリア", @@ -2564,8 +2563,8 @@ "insertFailed": "ディクテーションの結果を挿入できませんでした。", "insertFailedCopied": "ディクテーションの結果を挿入できず、クリップボードにコピーしました。", "inserted": "ディクテーションの結果を挿入しました。", - "stoppedAfterSilence": "1分間音声がなかったため停止しました。", - "stoppedAtCloudLimit": "クラウドの10分の上限に達したため、ディクテーションが停止しました。" + "stoppedAtCloudLimit": "クラウドの10分の上限に達したため、ディクテーションが停止しました。", + "stoppedAfterSilenceSeconds": "{{seconds}} 秒間無音だったため停止しました" }, "useNativeChatStop": { "stopNotSent": "停止要求を送信できませんでした。", @@ -3440,6 +3439,16 @@ "githubViewControls": { "openView": "GitHub でビューを開く" }, + "hostedMerge": { + "merged": "マージ済み", + "closed": "クローズ済み", + "conflicts": "競合あり", + "behind": "遅れあり", + "blocked": "ブロック中", + "checksPending": "チェック待ち", + "ableToMerge": "マージ可能", + "unknown": "不明" + }, "itemActions": { "changeStatus": "ステータスを変更", "copyGitHubLink": "GitHub リンクをコピーする", @@ -3586,6 +3595,11 @@ "updated": "更新日時", "updatedSortDescription": "アクティビティが新しい順" }, + "pagination": { + "pageUnreachable": "ページ {{page}} は GitHub 検索で取得できる範囲を超えています。", + "loadPageFailed": "ページ {{page}} を GitHub から読み込めませんでした。", + "noMoreResults": "ページ {{page}} にこれ以上の結果はありません。" + }, "pickerProjection": { "allRepos": "すべてのリポジトリ", "allSites": "すべてのサイト", diff --git a/mobile/src/i18n/locales/ko.json b/mobile/src/i18n/locales/ko.json index 2f8b4179263..2b51f3ce5ae 100644 --- a/mobile/src/i18n/locales/ko.json +++ b/mobile/src/i18n/locales/ko.json @@ -2450,8 +2450,7 @@ "tabStrip": { "checkingCapabilities": "데스크톱에서 지원하는 기능을 확인 중입니다. 잠시 후 다시 시도하세요.", "newTab": "새 탭", - "quickCommandsUpdateRequired": "빠른 명령을 사용하려면 데스크톱 업데이트가 필요합니다.", - "tabStatus": "{{title}}, {{status}}" + "quickCommandsUpdateRequired": "빠른 명령을 사용하려면 데스크톱 업데이트가 필요합니다." }, "terminalActionSheetActions": { "clearTerminal": "터미널 지우기", @@ -2564,8 +2563,8 @@ "insertFailed": "받아쓰기 내용을 삽입하지 못했습니다.", "insertFailedCopied": "받아쓰기 내용을 삽입하지 못해 클립보드에 복사했습니다.", "inserted": "받아쓰기 내용 삽입됨", - "stoppedAfterSilence": "1분 동안 말소리가 없어 중지했습니다.", - "stoppedAtCloudLimit": "클라우드 10분 제한에 도달해 받아쓰기가 중지되었습니다." + "stoppedAtCloudLimit": "클라우드 10분 제한에 도달해 받아쓰기가 중지되었습니다.", + "stoppedAfterSilenceSeconds": "{{seconds}}초 동안 조용해서 중지했습니다" }, "useNativeChatStop": { "stopNotSent": "중지 요청 전송 안 됨", @@ -3360,6 +3359,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "병합됨", + "closed": "닫힘", + "conflicts": "충돌", + "behind": "뒤처짐", + "blocked": "차단됨", + "checksPending": "검사 대기 중", + "ableToMerge": "병합 가능", + "unknown": "알 수 없음" + }, + "pagination": { + "pageUnreachable": "{{page}}페이지는 GitHub 검색에서 반환할 수 있는 범위를 벗어났습니다.", + "loadPageFailed": "GitHub에서 {{page}}페이지를 불러오지 못했습니다.", + "noMoreResults": "{{page}}페이지에 더 이상 결과가 없습니다." + }, "composerSource": { "baseFailed": "기본 브랜치를 확인하지 못했습니다.", "forkPushWarning": "이 PR에서 \"유지 관리자의 수정 허용\"이 꺼져 있어 GitHub에서 포크로의 푸시를 거부할 수 있습니다." diff --git a/mobile/src/i18n/locales/pt.json b/mobile/src/i18n/locales/pt.json index bee99bc37a9..0d4a2150210 100644 --- a/mobile/src/i18n/locales/pt.json +++ b/mobile/src/i18n/locales/pt.json @@ -2524,8 +2524,7 @@ "tabStrip": { "checkingCapabilities": "Verificando os recursos do desktop — tente novamente em instantes", "newTab": "Nova aba", - "quickCommandsUpdateRequired": "Atualize o desktop para usar comandos rápidos", - "tabStatus": "{{title}}, {{status}}" + "quickCommandsUpdateRequired": "Atualize o desktop para usar comandos rápidos" }, "terminalActionSheetActions": { "clearTerminal": "Limpar Terminal", @@ -2638,8 +2637,8 @@ "insertFailed": "Não foi possível inserir o ditado", "insertFailedCopied": "Não foi possível inserir o ditado, então ele foi copiado para a área de transferência", "inserted": "Ditado inserido", - "stoppedAfterSilence": "Parou após um minuto de silêncio", - "stoppedAtCloudLimit": "O ditado foi interrompido no limite de 10 minutos da nuvem." + "stoppedAtCloudLimit": "O ditado foi interrompido no limite de 10 minutos da nuvem.", + "stoppedAfterSilenceSeconds": "Parado após {{seconds}} segundos de silêncio" }, "useNativeChatStop": { "stopNotSent": "Comando de parada não enviado", @@ -3452,6 +3451,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "Mesclado", + "closed": "Fechado", + "conflicts": "Conflitos", + "behind": "Desatualizado", + "blocked": "Bloqueado", + "checksPending": "Verificações pendentes", + "ableToMerge": "Pode ser mesclado", + "unknown": "Desconhecido" + }, + "pagination": { + "pageUnreachable": "A página {{page}} está além do que a pesquisa do GitHub pode retornar.", + "loadPageFailed": "A página {{page}} não pôde ser carregada do GitHub.", + "noMoreResults": "Não há mais resultados na página {{page}}." + }, "composerSource": { "baseFailed": "Falha ao determinar a branch base.", "forkPushWarning": "Este PR está com \"Permitir edições de mantenedores\" desativado; o GitHub pode rejeitar o push para o fork." diff --git a/mobile/src/i18n/locales/ru.json b/mobile/src/i18n/locales/ru.json index 8ac3e79c21b..cec7d8ced57 100644 --- a/mobile/src/i18n/locales/ru.json +++ b/mobile/src/i18n/locales/ru.json @@ -2594,8 +2594,7 @@ "tabStrip": { "checkingCapabilities": "Проверяем возможности Orca на компьютере — повторите попытку чуть позже", "newTab": "Новая вкладка", - "quickCommandsUpdateRequired": "Для быстрых команд обновите Orca на компьютере", - "tabStatus": "{{title}}, {{status}}" + "quickCommandsUpdateRequired": "Для быстрых команд обновите Orca на компьютере" }, "terminalActionSheetActions": { "clearTerminal": "Очистить терминал", @@ -2708,8 +2707,8 @@ "insertFailed": "Не удалось вставить текст диктовки", "insertFailedCopied": "Не удалось вставить текст диктовки — он скопирован в буфер обмена", "inserted": "Текст диктовки вставлен", - "stoppedAfterSilence": "Остановлено после минуты тишины", - "stoppedAtCloudLimit": "Диктовка остановлена: достигнут 10-минутный облачный лимит." + "stoppedAtCloudLimit": "Диктовка остановлена: достигнут 10-минутный облачный лимит.", + "stoppedAfterSilenceSeconds": "Остановлено после {{seconds}} с тишины" }, "useNativeChatStop": { "stopNotSent": "Команда остановки не отправлена", @@ -3540,6 +3539,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "Слито", + "closed": "Закрыто", + "conflicts": "Конфликты", + "behind": "Отстаёт", + "blocked": "Заблокировано", + "checksPending": "Проверки ожидаются", + "ableToMerge": "Можно слить", + "unknown": "Неизвестно" + }, + "pagination": { + "pageUnreachable": "Страница {{page}} выходит за пределы результатов поиска GitHub.", + "loadPageFailed": "Не удалось загрузить страницу {{page}} из GitHub.", + "noMoreResults": "На странице {{page}} больше нет результатов." + }, "composerSource": { "baseFailed": "Не удалось определить базовую ветку.", "forkPushWarning": "В этом PR отключена настройка «Разрешить правки сопровождающим»; GitHub может отклонить отправку в форк." diff --git a/mobile/src/i18n/locales/tr.json b/mobile/src/i18n/locales/tr.json index bf6323cdbc8..81be23db055 100644 --- a/mobile/src/i18n/locales/tr.json +++ b/mobile/src/i18n/locales/tr.json @@ -1461,11 +1461,11 @@ "updateRequired": "PDF'leri açmak için Orca Mobile'ı güncelleyin" }, "unsavedEditsOffer": { - "changedSince": "{{time}} tarihli düzenlemelerinizden sonra masaüstünde değişti", + "changedSince": "Düzenlemelerinizden ({{time}}) sonra masaüstünde değişti", "copyMyEdits": "Düzenlemelerimi kopyala", "discard": "Vazgeç", "restore": "Geri yükle", - "unsavedFrom": "{{time}} tarihli kaydedilmemiş düzenlemeler" + "unsavedFrom": "Kaydedilmemiş düzenlemeler ({{time}})" } }, "home": { @@ -2454,8 +2454,7 @@ "tabStrip": { "checkingCapabilities": "Masaüstü özellikleri kontrol ediliyor — birazdan tekrar deneyin", "newTab": "Yeni sekme", - "quickCommandsUpdateRequired": "Hızlı komutlar için masaüstü uygulaması güncellenmeli", - "tabStatus": "{{title}}, {{status}}" + "quickCommandsUpdateRequired": "Hızlı komutlar için masaüstü uygulaması güncellenmeli" }, "terminalActionSheetActions": { "clearTerminal": "Terminali Temizle", @@ -2568,8 +2567,8 @@ "insertFailed": "Dikte metni eklenemedi", "insertFailedCopied": "Dikte metni eklenemediği için panoya kopyalandı", "inserted": "Dikte metni eklendi", - "stoppedAfterSilence": "Bir dakika sessizlikten sonra durduruldu", - "stoppedAtCloudLimit": "Dikte, 10 dakikalık bulut sınırında durduruldu." + "stoppedAtCloudLimit": "Dikte, 10 dakikalık bulut sınırında durduruldu.", + "stoppedAfterSilenceSeconds": "{{seconds}} saniyelik sessizlikten sonra durduruldu" }, "useNativeChatStop": { "stopNotSent": "Durdurma isteği gönderilemedi", @@ -3364,6 +3363,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "Birleştirildi", + "closed": "Kapatıldı", + "conflicts": "Çakışmalar", + "behind": "Geride", + "blocked": "Engellendi", + "checksPending": "Kontroller bekleniyor", + "ableToMerge": "Birleştirilebilir", + "unknown": "Bilinmiyor" + }, + "pagination": { + "pageUnreachable": "{{page}}. sayfa GitHub aramasının döndürebileceği aralığın dışında.", + "loadPageFailed": "{{page}}. sayfa GitHub’dan yüklenemedi.", + "noMoreResults": "{{page}}. sayfada başka sonuç yok." + }, "composerSource": { "baseFailed": "Temel branch çözümlenemedi.", "forkPushWarning": "Bu PR'da “Allow edits from maintainers” kapalı; fork'a push işlemi GitHub tarafından reddedilebilir." diff --git a/mobile/src/i18n/locales/zh-Hant.json b/mobile/src/i18n/locales/zh-Hant.json index 5ae56b6e610..535fb5d378a 100644 --- a/mobile/src/i18n/locales/zh-Hant.json +++ b/mobile/src/i18n/locales/zh-Hant.json @@ -2450,8 +2450,7 @@ "tabStrip": { "checkingCapabilities": "正在檢查桌面版支援的功能,請稍後再試", "newTab": "新分頁", - "quickCommandsUpdateRequired": "需更新桌面版才能使用快速命令", - "tabStatus": "{{title}},{{status}}" + "quickCommandsUpdateRequired": "需更新桌面版才能使用快速命令" }, "terminalActionSheetActions": { "clearTerminal": "清除終端機", @@ -2564,8 +2563,8 @@ "insertFailed": "無法插入聽寫內容", "insertFailedCopied": "無法插入聽寫內容,已複製到剪貼簿", "inserted": "已插入聽寫內容", - "stoppedAfterSilence": "靜音一分鐘後已停止", - "stoppedAtCloudLimit": "已達 10 分鐘雲端上限。聽寫已停止。" + "stoppedAtCloudLimit": "已達 10 分鐘雲端上限。聽寫已停止。", + "stoppedAfterSilenceSeconds": "靜音 {{seconds}} 秒後已停止" }, "useNativeChatStop": { "stopNotSent": "未送出停止命令", @@ -3360,6 +3359,21 @@ } }, "tasks": { + "hostedMerge": { + "merged": "已合併", + "closed": "已關閉", + "conflicts": "有衝突", + "behind": "落後", + "blocked": "已封鎖", + "checksPending": "等待檢查", + "ableToMerge": "可以合併", + "unknown": "未知" + }, + "pagination": { + "pageUnreachable": "第 {{page}} 頁超出 GitHub 搜尋可傳回的範圍。", + "loadPageFailed": "無法從 GitHub 載入第 {{page}} 頁。", + "noMoreResults": "第 {{page}} 頁沒有更多結果。" + }, "composerSource": { "baseFailed": "無法解析基礎分支。", "forkPushWarning": "此 PR 未啟用「允許維護者編輯」;推送至分叉存放庫可能會遭 GitHub 拒絕。" diff --git a/mobile/src/i18n/locales/zh.json b/mobile/src/i18n/locales/zh.json index 04c1c5e2e2c..f845b0e146d 100644 --- a/mobile/src/i18n/locales/zh.json +++ b/mobile/src/i18n/locales/zh.json @@ -2450,8 +2450,7 @@ "tabStrip": { "checkingCapabilities": "正在检查桌面支持的功能,请稍后重试", "newTab": "新标签页", - "quickCommandsUpdateRequired": "使用快捷命令需更新桌面应用", - "tabStatus": "{{title}},{{status}}" + "quickCommandsUpdateRequired": "使用快捷命令需更新桌面应用" }, "terminalActionSheetActions": { "clearTerminal": "清空终端", @@ -2564,8 +2563,8 @@ "insertFailed": "无法插入听写文本", "insertFailedCopied": "无法插入听写文本,已复制到剪贴板", "inserted": "已插入听写文本", - "stoppedAfterSilence": "静音一分钟后已停止", - "stoppedAtCloudLimit": "已达 10 分钟云端上限。听写已停止。" + "stoppedAtCloudLimit": "已达 10 分钟云端上限。听写已停止。", + "stoppedAfterSilenceSeconds": "静音 {{seconds}} 秒后已停止" }, "useNativeChatStop": { "stopNotSent": "停止指令未发送", @@ -3440,6 +3439,16 @@ "githubViewControls": { "openView": "在 GitHub 中打开视图" }, + "hostedMerge": { + "merged": "已合并", + "closed": "已关闭", + "conflicts": "有冲突", + "behind": "落后", + "blocked": "已阻止", + "checksPending": "检查待完成", + "ableToMerge": "可以合并", + "unknown": "未知" + }, "itemActions": { "changeStatus": "更改状态", "copyGitHubLink": "复制 GitHub 链接", @@ -3586,6 +3595,11 @@ "updated": "已更新", "updatedSortDescription": "最新活动优先" }, + "pagination": { + "pageUnreachable": "第 {{page}} 页超出了 GitHub 搜索可返回的范围。", + "loadPageFailed": "无法从 GitHub 加载第 {{page}} 页。", + "noMoreResults": "第 {{page}} 页没有更多结果。" + }, "pickerProjection": { "allRepos": "所有仓库", "allSites": "所有站点", diff --git a/mobile/src/i18n/mobile-i18n.test.ts b/mobile/src/i18n/mobile-i18n.test.ts index a2906b02959..08e08089f53 100644 --- a/mobile/src/i18n/mobile-i18n.test.ts +++ b/mobile/src/i18n/mobile-i18n.test.ts @@ -7,7 +7,8 @@ vi.mock('./locale-catalog-imports', () => ({ importLocaleCatalog: (locale: string) => failing.has(locale) ? Promise.reject(new Error('missing chunk')) - : new Promise((resolve) => pending.set(locale, resolve)) + : new Promise((resolve) => pending.set(locale, resolve)), + onLocaleCatalogArrival: () => () => undefined })) const { activateMobileUiLocale, getMobileUiLocale, mobileI18n, translate } = diff --git a/mobile/src/i18n/mobile-i18n.ts b/mobile/src/i18n/mobile-i18n.ts index c3f58905000..d3a5b94be34 100644 --- a/mobile/src/i18n/mobile-i18n.ts +++ b/mobile/src/i18n/mobile-i18n.ts @@ -3,7 +3,7 @@ import { resolvePluralFamilyKey } from '../../../src/shared/i18n-plural-family' import { pseudoLocalizeString } from '../../../src/shared/pseudo-localization' import { DEFAULT_UI_LOCALE, type SupportedUiLocale } from '../../../src/shared/ui-locale' import enRuntimeRequired from './en-runtime-required.json' -import { importLocaleCatalog } from './locale-catalog-imports' +import { importLocaleCatalog, onLocaleCatalogArrival } from './locale-catalog-imports' export type Translate = (key: string, fallback: string, options?: TOptions) => string @@ -83,10 +83,12 @@ export function subscribeMobileUiLanguage(listener: () => void): () => void { } let activationRequest = 0 +let requestedLocale: SupportedUiLocale = DEFAULT_UI_LOCALE // Latest request wins: an older catalog import that settles later must not replace a newer choice. export async function activateMobileUiLocale(locale: SupportedUiLocale): Promise { const request = ++activationRequest + requestedLocale = locale try { if (locale !== 'en' && !mobileI18n.hasResourceBundle(locale, 'translation')) { const catalog = await importLocaleCatalog(locale) @@ -115,6 +117,16 @@ export async function activateMobileUiLocale(locale: SupportedUiLocale): Promise publish() } +// Switch in place, without a reload: a newer copy replaces the shown one, a first copy replaces the English fallback. +// Install it even when another language is up now, so switching back shows the newer copy. +onLocaleCatalogArrival((locale, catalog) => { + mobileI18n.removeResourceBundle(locale, 'translation') + mobileI18n.addResourceBundle(locale, 'translation', catalog, false, true, { skipCopy: true }) + if (locale === requestedLocale) { + void activateMobileUiLocale(locale) + } +}) + // Development-only layout check: accents and pads every string so clipping shows on device. export function setMobilePseudoLocalization(enabled: boolean): void { if (pseudoLocalization !== enabled) { diff --git a/mobile/src/i18n/page-locale-catalog-source.test.ts b/mobile/src/i18n/page-locale-catalog-source.test.ts new file mode 100644 index 00000000000..d69a92dc1b2 --- /dev/null +++ b/mobile/src/i18n/page-locale-catalog-source.test.ts @@ -0,0 +1,218 @@ +import { describe, expect, it, vi } from 'vitest' +import type { ConnectionState } from '../transport/connection/connection-state' +import type { RpcResponse } from '../transport/rpc/rpc-wire-types' +import { + decodeDeviceStoreValue, + encodeDeviceStoreValue +} from '../device-store/device-store-value-codec' +import type { PageDeviceStore } from '../mobile-web-shell/bridge/page-device-store' + +// The page build resolves mobile-i18n's catalog import to the .web module; mirror that here. +vi.mock('./locale-catalog-imports', () => import('./locale-catalog-imports.web')) +// A shell that skipped the catalog chunks: only locales listed here are on disk. +const chunksOnDisk = new Map>() +vi.mock('./bundled-locale-catalogs', () => ({ + importBundledLocaleCatalog: async (locale: string) => { + const catalog = chunksOnDisk.get(locale) + if (!catalog) { + throw new Error('not on disk') + } + return catalog + } +})) + +const OLD_SHA = 'a'.repeat(64) +const NEW_SHA = 'b'.repeat(64) + +type SentRequest = { + method: string + params: unknown + reply: (response: RpcResponse) => void + fail: (error: Error) => void +} + +function fakeClient(initial: ConnectionState) { + let state = initial + let generation = 1 + const listeners = new Set<(next: ConnectionState) => void>() + const sent: SentRequest[] = [] + return { + sent, + getState: () => state, + getGeneration: () => generation, + onStateChange: (listener: (next: ConnectionState) => void) => { + listeners.add(listener) + return () => { + listeners.delete(listener) + } + }, + sendRequest: (method: string, params?: unknown) => + new Promise((reply, fail) => sent.push({ method, params, reply, fail })), + moveTo(next: ConnectionState) { + state = next + for (const listener of listeners) { + listener(next) + } + }, + // The shell swaps in an already-connected replacement, as on a relay-to-LAN move. + cutover() { + generation += 1 + for (const listener of listeners) { + listener('connected') + } + } + } +} + +function fakeStore(rows: Record): PageDeviceStore & { rows: Map } { + const stored = new Map(Object.entries(rows)) + return { + rows: stored, + available: () => true, + read: async (_table, keys) => ({ + status: 'ok', + values: keys.map((key) => stored.get(key) ?? null) + }), + write: async (_table, ops) => { + for (const op of ops) { + if (op.value !== null) { + stored.set(op.key, op.value) + } + } + return { written: true } + } + } +} + +const { startPageLocaleCatalogs } = await import('./page-locale-catalog-source') +const i18n = await import('./mobile-i18n') + +// Module state outlives a test, so each test uses its own locale and publishes its own source. +function openPage(client: ReturnType, store: PageDeviceStore): void { + startPageLocaleCatalogs({ client, store }) +} + +// Microtasks, then one macrotask, which is when the store write runs. +async function settle(): Promise { + for (let turn = 0; turn < 20; turn++) { + await Promise.resolve() + } + await new Promise((resolve) => setTimeout(resolve, 0)) +} + +describe('the page language catalog', () => { + it('opens on the stored copy, then switches in place to the desktop newer one', async () => { + const client = fakeClient('connected') + const store = fakeStore({ + 'i18n-catalog:de': encodeDeviceStoreValue({ + v: 1, + sha256: OLD_SHA, + catalog: { greeting: 'Hallo' } + }) + }) + openPage(client, store) + + await i18n.activateMobileUiLocale('de') + expect(i18n.translate('greeting', 'Hello')).toBe('Hallo') + expect(client.sent.map(({ method, params }) => ({ method, params }))).toEqual([ + { method: 'mobileWeb.bundle.localeCatalog', params: { locale: 'de', knownSha256: OLD_SHA } } + ]) + + client.sent[0].reply({ + id: 'r1', + ok: true, + result: { locale: 'de', sha256: NEW_SHA, catalog: { greeting: 'Servus' } } + }) + await settle() + + expect(i18n.getMobileUiLocale()).toBe('de') + expect(i18n.translate('greeting', 'Hello')).toBe('Servus') + expect(decodeDeviceStoreValue(store.rows.get('i18n-catalog:de') ?? '')).toEqual({ + v: 1, + sha256: NEW_SHA, + catalog: { greeting: 'Servus' } + }) + }) + + it('shows English offline with nothing stored, then the language once the desktop answers', async () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}) + const client = fakeClient('reconnecting') + openPage(client, fakeStore({})) + + await i18n.activateMobileUiLocale('fr') + expect(i18n.getMobileUiLocale()).toBe('en') + expect(i18n.translate('greeting', 'Hello')).toBe('Hello') + expect(client.sent).toHaveLength(0) + + client.moveTo('connected') + await settle() + expect(client.sent.map(({ params }) => params)).toEqual([{ locale: 'fr' }]) + client.sent[0].reply({ + id: 'r1', + ok: true, + result: { locale: 'fr', sha256: NEW_SHA, catalog: { greeting: 'Bonjour' } } + }) + await settle() + + expect(i18n.getMobileUiLocale()).toBe('fr') + expect(i18n.translate('greeting', 'Hello')).toBe('Bonjour') + }) + + it('opens in English when the first request fails on a live link, then switches on the next connection', async () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}) + const client = fakeClient('connected') + openPage(client, fakeStore({})) + + const activation = i18n.activateMobileUiLocale('ja') + await settle() + client.sent[0].fail(new Error('timed out')) + await activation + expect(i18n.getMobileUiLocale()).toBe('en') + + client.moveTo('reconnecting') + client.moveTo('connected') + await settle() + client.sent[1].reply({ + id: 'r2', + ok: true, + result: { locale: 'ja', sha256: NEW_SHA, catalog: { greeting: 'こんにちは' } } + }) + await settle() + + expect(i18n.getMobileUiLocale()).toBe('ja') + expect(i18n.translate('greeting', 'Hello')).toBe('こんにちは') + }) + + it('asks again on the connection a cutover swaps in, without waiting for another reconnect', async () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}) + const client = fakeClient('reconnecting') + openPage(client, fakeStore({})) + + await i18n.activateMobileUiLocale('ko') + client.moveTo('connected') + await settle() + client.cutover() + client.sent[0].fail(new Error('client cut over')) + await settle() + client.sent[1].reply({ + id: 'r2', + ok: true, + result: { locale: 'ko', sha256: NEW_SHA, catalog: { greeting: '안녕하세요' } } + }) + await settle() + + expect(i18n.getMobileUiLocale()).toBe('ko') + expect(i18n.translate('greeting', 'Hello')).toBe('안녕하세요') + }) + + it('imports the chunk an older shell left on disk, with no wait and no request', async () => { + chunksOnDisk.set('es', { greeting: 'Hola' }) + const client = fakeClient('reconnecting') + openPage(client, fakeStore({})) + + await i18n.activateMobileUiLocale('es') + + expect(i18n.translate('greeting', 'Hello')).toBe('Hola') + expect(client.sent).toHaveLength(0) + }) +}) diff --git a/mobile/src/i18n/page-locale-catalog-source.ts b/mobile/src/i18n/page-locale-catalog-source.ts new file mode 100644 index 00000000000..76333fdc22b --- /dev/null +++ b/mobile/src/i18n/page-locale-catalog-source.ts @@ -0,0 +1,265 @@ +import * as z from 'zod' +import type { SupportedUiLocale } from '../../../src/shared/ui-locale' +import { SHA256_PATTERN } from '../../../src/shared/mobile-web-bundle/manifest-contract' +import { + MOBILE_WEB_BUNDLE_LOCALE_CATALOG_METHOD, + MobileWebBundleLocaleCatalogResultSchema +} from '../../../src/shared/mobile-web-bundle/locale-catalog-contract' +import { withTimeoutResult } from '../../../src/shared/promise-timeout-fallback' +import { + decodeDeviceStoreValue, + encodeDeviceStoreValue +} from '../device-store/device-store-value-codec' +import type { BridgeRpcClient } from '../mobile-web-shell/bridge/bridge-rpc-client' +import type { PageDeviceStore } from '../mobile-web-shell/bridge/page-device-store' +import { isMobileMethodUnavailableError } from '../transport/rpc/mobile-method-unavailable' +import { captureRpcOperationSettlement, defineRpcOperation } from '../transport/rpc/rpc-operation' +import { rpcResultVariant } from '../transport/rpc/rpc-operation-result-reader' +import { importBundledLocaleCatalog } from './bundled-locale-catalogs' +import { + announceLocaleCatalog as announce, + publishPageLocaleCatalogLoader, + type LocaleCatalog +} from './locale-catalog-imports.web' + +// The desktop serves the active catalog and the phone keeps a copy per host; only the page entry imports this. +type CatalogLocale = Exclude +type HeldCatalog = { readonly sha256: string; readonly catalog: LocaleCatalog } +type CatalogClient = Pick< + BridgeRpcClient, + 'getState' | 'getGeneration' | 'onStateChange' | 'sendRequest' +> +type CatalogSource = { readonly client: CatalogClient; readonly store: PageDeviceStore } + +const storedCatalogSchema = z.object({ + v: z.literal(1), + sha256: z.string().regex(SHA256_PATTERN), + catalog: z.record(z.string(), z.unknown()) +}) + +const localeCatalogRead = defineRpcOperation({ + name: 'mobileWeb.bundle-locale-catalog', + method: MOBILE_WEB_BUNDLE_LOCALE_CATALOG_METHOD, + acceptance: 'require-result-or-throw', + barrier: 'on-settle', + // Loose like every phone reader, so a host may add fields. + read: rpcResultVariant( + 'mobile-web-bundle-locale-catalog', + MobileWebBundleLocaleCatalogResultSchema.loose() + ) +}) + +const syncing = new Set() +// The stored read plus a relay round trip with the largest catalog fit well inside; past it the page opens in English. +const FIRST_PAINT_CATALOG_WAIT_MS = 3_000 + +const storeKey = (locale: CatalogLocale): string => `i18n-catalog:${locale}` + +async function readStoredCatalog( + store: PageDeviceStore, + locale: CatalogLocale +): Promise { + if (!store.available()) { + return null + } + const read = await store.read('cache', [storeKey(locale)]).catch(() => null) + const raw = read?.status === 'ok' ? read.values[0] : null + if (!raw) { + return null + } + const parsed = storedCatalogSchema.safeParse(decodeDeviceStoreValue(raw)) + return parsed.success ? { sha256: parsed.data.sha256, catalog: parsed.data.catalog } : null +} + +function writeStoredCatalog( + store: PageDeviceStore, + locale: CatalogLocale, + next: HeldCatalog +): void { + if (store.available()) { + const value = encodeDeviceStoreValue({ v: 1, ...next }) + void store.write('cache', [{ key: storeKey(locale), value }]).catch(() => undefined) + } +} + +// A send the host never answered, as opposed to a refusal or an unreadable reply. +class CatalogSendFailed extends Error { + constructor( + message: string, + readonly generation: number | null = null + ) { + super(message) + } +} + +const generationOf = (client: CatalogClient): number | null => client.getGeneration?.() ?? null + +// After a failed send, wait for a reconnect or a cutover to another connection, which is often connected already. +// False once pairing is revoked: that connection never comes back. +function whenConnected(client: CatalogClient, failedGeneration: number | null): Promise { + let reconnected = failedGeneration === null || client.getState() !== 'connected' + const ready = (): boolean => + client.getState() === 'connected' && (reconnected || generationOf(client) !== failedGeneration) + if (ready() || client.getState() === 'auth-failed') { + return Promise.resolve(ready()) + } + return new Promise((resolve) => { + const stop = client.onStateChange((state) => { + reconnected ||= state !== 'connected' + if (ready() || state === 'auth-failed') { + stop() + resolve(ready()) + } + }) + }) +} + +// Rejects when the connection drops first, so a first paint never waits on a reply that can't come. +function beforeConnectionLoss(client: CatalogClient, work: Promise): Promise { + return new Promise((resolve, reject) => { + const stop = client.onStateChange((state) => { + if (state !== 'connected') { + stop() + reject(new CatalogSendFailed('[i18n] connection lost before the catalog arrived')) + } + }) + work.then( + (value) => { + stop() + resolve(value) + }, + (error: unknown) => { + stop() + reject(error) + } + ) + }) +} + +// One request. On an answer the copy becomes the page's and the store's. +async function askHost( + { client, store }: CatalogSource, + locale: CatalogLocale, + known: HeldCatalog | undefined +): Promise { + const generation = generationOf(client) + const settled = await captureRpcOperationSettlement(client, localeCatalogRead, { + locale, + ...(known ? { knownSha256: known.sha256 } : {}) + }) + if (settled.status === 'rejected') { + throw new CatalogSendFailed(`[i18n] the ${locale} catalog request got no answer`, generation) + } + const { outcome } = settled + if (outcome.kind === 'decoded') { + const { sha256, catalog } = outcome.value + // A null catalog only confirms the copy the phone named. + const next = catalog !== null ? { sha256, catalog } : known?.sha256 === sha256 ? known : null + if (next) { + if (next !== known) { + // After the first paint: encoding the copy is main-thread work nothing waits on. + setTimeout(() => { + writeStoredCatalog(store, locale, next) + }, 0) + } + return next + } + } + const refusal = outcome.kind === 'outer-refused' ? outcome.error : undefined + throw new Error( + isMobileMethodUnavailableError(refusal?.code, refusal?.message) + ? '[i18n] this desktop serves no language catalogs' + : `[i18n] the desktop answered no ${locale} catalog` + ) +} + +// Asks until the host answers, resending once per new connection; a refusal ends it. +async function syncFromHost( + current: CatalogSource, + locale: CatalogLocale, + known: HeldCatalog | undefined, + failedGeneration: number | null +): Promise { + for (;;) { + if (!(await whenConnected(current.client, failedGeneration))) { + throw new Error('[i18n] this phone is no longer paired with the desktop') + } + try { + return await askHost(current, locale, known) + } catch (error) { + if (!(error instanceof CatalogSendFailed)) { + throw error + } + failedGeneration = error.generation + } + } +} + +// One sync per locale, announcing a copy other than the one the page already shows. +function syncOnce( + current: CatalogSource, + locale: CatalogLocale, + known: HeldCatalog | undefined, + failedGeneration: number | null = null +): void { + if (syncing.has(locale)) { + return + } + syncing.add(locale) + syncFromHost(current, locale, known, failedGeneration).then( + (next) => { + syncing.delete(locale) + if (next !== known) { + announce(locale, next.catalog) + } + }, + () => { + syncing.delete(locale) + } + ) +} + +// What a first paint may wait for: the stored copy, else one request while connected. +async function firstCatalog(current: CatalogSource, locale: CatalogLocale): Promise { + // Only a locale with no catalog installed is asked for, so nothing is held in memory here. + const known = await readStoredCatalog(current.store, locale) + if (known) { + // Show the stored copy now; a newer one replaces it when the host answers. + syncOnce(current, locale, known) + return known + } + if (current.client.getState() !== 'connected') { + throw new CatalogSendFailed('[i18n] no connection for the catalog yet') + } + return beforeConnectionLoss(current.client, askHost(current, locale, undefined)) +} + +async function loadCatalog(current: CatalogSource, locale: CatalogLocale): Promise { + // A generation an older shell downloaded still holds the chunk: no wait, and this page's own strings. + const bundled = await importBundledLocaleCatalog(locale).catch(() => null) + if (bundled !== null) { + return bundled + } + const first = firstCatalog(current, locale) + const settled = await withTimeoutResult(first, FIRST_PAINT_CATALOG_WAIT_MS) + if (settled.ok) { + return settled.value.catalog + } + // Activation shows English meanwhile; whatever comes later switches the page in place. + first.then( + (late) => { + announce(locale, late.catalog) + }, + (failure: unknown) => { + if (failure instanceof CatalogSendFailed) { + syncOnce(current, locale, undefined, failure.generation) + } + } + ) + throw new Error(`[i18n] no ${locale} catalog for the first paint`) +} + +// Before the first activation, so its catalog comes from the desktop and the store. +export function startPageLocaleCatalogs(current: CatalogSource): void { + publishPageLocaleCatalogLoader((locale) => loadCatalog(current, locale)) +} diff --git a/mobile/src/mobile-web-shell/ShellDownloadProgress.tsx b/mobile/src/mobile-web-shell/ShellDownloadProgress.tsx index 02b19f0caa6..46093799133 100644 --- a/mobile/src/mobile-web-shell/ShellDownloadProgress.tsx +++ b/mobile/src/mobile-web-shell/ShellDownloadProgress.tsx @@ -1,4 +1,4 @@ -import { useHostDisplay, type HostDisplaySource } from '../transport/hosts/use-host-display' +import { useHostDisplay, type HostDisplaySource } from '../host-descriptors/use-host-display' import type { DownloadProgressFeed } from './download-progress-feed' import { DownloadProgressBar, downloadedMegabytesCaption } from './DownloadProgressBar' import type { MobileWebShellSessionState } from './mobile-web-shell-session-contract' diff --git a/mobile/src/mobile-web-shell/ShellStateScreen.tsx b/mobile/src/mobile-web-shell/ShellStateScreen.tsx index 01617b74903..d22e7e2c055 100644 --- a/mobile/src/mobile-web-shell/ShellStateScreen.tsx +++ b/mobile/src/mobile-web-shell/ShellStateScreen.tsx @@ -6,7 +6,7 @@ import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import type { Translate } from '../i18n/mobile-i18n' import { useMobileTranslation } from '../i18n/use-mobile-translation' -import type { HostDisplaySource } from '../transport/hosts/use-host-display' +import type { HostDisplaySource } from '../host-descriptors/use-host-display' import type { DownloadProgressFeed } from './download-progress-feed' import type { MobileWebShellFailureCause, diff --git a/mobile/src/mobile-web-shell/bridge-host-init.test.ts b/mobile/src/mobile-web-shell/bridge-host-init.test.ts index 0a64fd9585f..a2add44e7ad 100644 --- a/mobile/src/mobile-web-shell/bridge-host-init.test.ts +++ b/mobile/src/mobile-web-shell/bridge-host-init.test.ts @@ -48,6 +48,7 @@ describe('init and state', () => { expect(init.grants.native).toEqual([ BRIDGE_FAULT_GRANT, BRIDGE_RPC_ENVELOPE_GRANT, + 'hostCatalogs', 'navigate', 'storage', 'haptics', diff --git a/mobile/src/mobile-web-shell/bridge-host-native-verbs.test.ts b/mobile/src/mobile-web-shell/bridge-host-native-verbs.test.ts index 87e837fc66b..3a9655cfb7b 100644 --- a/mobile/src/mobile-web-shell/bridge-host-native-verbs.test.ts +++ b/mobile/src/mobile-web-shell/bridge-host-native-verbs.test.ts @@ -205,7 +205,7 @@ describe('grants scoped to the route the page was opened for', () => { const bridge = harness({ routeGrants: navigationOnly, ready: true }) expect(bridge.last()).toMatchObject({ type: 'init', - grants: { native: ['fault', 'rpcEnvelope', 'navigate', 'storage'] } + grants: { native: ['fault', 'rpcEnvelope', 'hostCatalogs', 'navigate', 'storage'] } }) for (const [method, params] of [ ['native.clipboard.read', { mime: 'text' }], diff --git a/mobile/src/mobile-web-shell/bridge-host.ts b/mobile/src/mobile-web-shell/bridge-host.ts index 914b25c9b70..166f3e791d3 100644 --- a/mobile/src/mobile-web-shell/bridge-host.ts +++ b/mobile/src/mobile-web-shell/bridge-host.ts @@ -7,9 +7,8 @@ import { BridgeHostRequests } from './bridge-host-requests' import { BridgeHostSubscriptions } from './bridge-host-subscriptions' import { createBridgeHostStreamFrames } from './bridge-host-stream-frames' import { - BRIDGE_FAULT_GRANT, BRIDGE_PROTOCOL_VERSION, - BRIDGE_RPC_ENVELOPE_GRANT, + BRIDGE_SHELL_GRANTS, readBridgeClientMessage, type BridgeClientMessage } from './bridge/bridge-envelope' @@ -30,11 +29,7 @@ export type { BridgeHost, BridgeHostDiagnostic, BridgeHostOptions } from './brid // Bind the host structurally to one RPC client; the protocol lets pages name no other host. Native origin checks reject expired sessions, and init caps are enforced here. export function createBridgeHost(options: BridgeHostOptions): BridgeHost { const { client, buildId, sessionId, pageRoutes, host } = options - const granted: readonly string[] = [ - BRIDGE_FAULT_GRANT, - BRIDGE_RPC_ENVELOPE_GRANT, - ...options.routeGrants - ] + const granted: readonly string[] = [...BRIDGE_SHELL_GRANTS, ...options.routeGrants] // Validate manifest pairs like the route: a pair refused by the page would invalidate the entire init. const parsedRouteGrants = options.pageRouteGrants === undefined diff --git a/mobile/src/mobile-web-shell/bridge/bridge-envelope.ts b/mobile/src/mobile-web-shell/bridge/bridge-envelope.ts index 8f8e8946fa9..5af16b2a9b3 100644 --- a/mobile/src/mobile-web-shell/bridge/bridge-envelope.ts +++ b/mobile/src/mobile-web-shell/bridge/bridge-envelope.ts @@ -32,10 +32,12 @@ export { BRIDGE_EXTERNAL_LINK_GRANT, BRIDGE_FAULT_GRANT, BRIDGE_FOREGROUND_NUDGE_REASONS, + BRIDGE_HOST_CATALOGS_GRANT, BRIDGE_ID_PATTERN, BRIDGE_NAVIGATE_BACK_NOTIFY, BRIDGE_PROTOCOL_VERSION, - BRIDGE_RPC_ENVELOPE_GRANT + BRIDGE_RPC_ENVELOPE_GRANT, + BRIDGE_SHELL_GRANTS } from './bridge-frame-fields' import { isPageStorageKey, diff --git a/mobile/src/mobile-web-shell/bridge/bridge-frame-fields.ts b/mobile/src/mobile-web-shell/bridge/bridge-frame-fields.ts index 9fea8193746..0a2ce887cd6 100644 --- a/mobile/src/mobile-web-shell/bridge/bridge-frame-fields.ts +++ b/mobile/src/mobile-web-shell/bridge/bridge-frame-fields.ts @@ -25,3 +25,13 @@ export const BRIDGE_EXTERNAL_LINK_GRANT = 'externalLink' // Negotiate rpcEnvelope before relying on extra wire fields; older shells drop unknown fields and could execute without caller-required metadata. export const BRIDGE_RPC_ENVELOPE_GRANT = 'rpcEnvelope' + +// The shell skipped the build's language-catalog chunks, so the page reads its catalog from the desktop. +export const BRIDGE_HOST_CATALOGS_GRANT = 'hostCatalogs' + +// Granted to every page this shell opens, whatever its route declares. +export const BRIDGE_SHELL_GRANTS: readonly string[] = [ + BRIDGE_FAULT_GRANT, + BRIDGE_RPC_ENVELOPE_GRANT, + BRIDGE_HOST_CATALOGS_GRANT +] diff --git a/mobile/src/mobile-web-shell/bridge/bridge-init-frame.ts b/mobile/src/mobile-web-shell/bridge/bridge-init-frame.ts index 2c5208dfe6f..ac4c256e67f 100644 --- a/mobile/src/mobile-web-shell/bridge/bridge-init-frame.ts +++ b/mobile/src/mobile-web-shell/bridge/bridge-init-frame.ts @@ -6,9 +6,8 @@ import { import { BRIDGE_MAX_PENDING_REQUESTS, BRIDGE_MAX_SUBSCRIPTIONS } from './bridge-caps' import { MOBILE_WEB_SHELL_GRANTS } from '../page-route-policy' import { - BRIDGE_FAULT_GRANT, BRIDGE_PROTOCOL_VERSION, - BRIDGE_RPC_ENVELOPE_GRANT, + BRIDGE_SHELL_GRANTS, type BridgeColorScheme, type BridgeConnectionSnapshot, type BridgeHostMessage, @@ -27,8 +26,7 @@ import type { HostStatusReply } from '../../transport/hosts/host-status-reply-sc // Grant the intersection of implemented capabilities and route declarations, plus protocol fault. Use the same resolved list for init and enforcement; new names need no version bump. export const BRIDGE_NATIVE_GRANTS: readonly string[] = [ - BRIDGE_FAULT_GRANT, - BRIDGE_RPC_ENVELOPE_GRANT, + ...BRIDGE_SHELL_GRANTS, ...MOBILE_WEB_SHELL_GRANTS ] diff --git a/mobile/src/mobile-web-shell/generation-manifest-read.ts b/mobile/src/mobile-web-shell/generation-manifest-read.ts index 875cf4966d5..71c6ee09e18 100644 --- a/mobile/src/mobile-web-shell/generation-manifest-read.ts +++ b/mobile/src/mobile-web-shell/generation-manifest-read.ts @@ -1,5 +1,6 @@ import { MobileWebBundleManifestReadSchema, + shellDownloadedAssets, type MobileWebBundleManifestRead } from '../transport/web-bundle/mobile-web-bundle-reply-schemas' import { MANIFEST_FILE_NAME } from './generation-asset-path' @@ -33,12 +34,13 @@ export async function readWaitingStage( } // Deletes are not atomic, so a kill mid-delete can leave the manifest without its assets; only every byte counts as whole. const sizes = new Map() - for (const folder of new Set(manifest.assets.map((asset) => parentOf(asset.path)))) { + const assets = shellDownloadedAssets(manifest) + for (const folder of new Set(assets.map((asset) => parentOf(asset.path)))) { for (const file of await fs.listFiles(folder === '' ? directory : joinUri(directory, folder))) { sizes.set(folder === '' ? file.name : `${folder}/${file.name}`, file.size) } } - const whole = manifest.assets.every((asset) => sizes.get(asset.path) === asset.byteLength) + const whole = assets.every((asset) => sizes.get(asset.path) === asset.byteLength) return whole ? { hostKey, buildId, directory, manifest } : null } diff --git a/mobile/src/mobile-web-shell/generation-staging-assets.ts b/mobile/src/mobile-web-shell/generation-staging-assets.ts index ded1033d931..cd61866c2b4 100644 --- a/mobile/src/mobile-web-shell/generation-staging-assets.ts +++ b/mobile/src/mobile-web-shell/generation-staging-assets.ts @@ -1,10 +1,11 @@ import type { MobileWebBundleFetchResult } from '../transport/web-bundle/mobile-web-bundle-fetch' +import { shellDownloadedAssets } from '../transport/web-bundle/mobile-web-bundle-reply-schemas' import { requireStorablePath } from './generation-asset-path' import { joinUri } from './generation-cache-uri' export function stagingAssetsOf(directory: string, result: MobileWebBundleFetchResult) { const reused = new Set(result.reused) - return result.manifest.assets.map((asset) => ({ + return shellDownloadedAssets(result.manifest).map((asset) => ({ asset, uri: joinUri(directory, requireStorablePath(asset.path)), bytes: reused.has(asset.path) ? null : requireExactBytes(result.assets.get(asset.path), asset) diff --git a/mobile/src/mobile-web-shell/generation-store.test.ts b/mobile/src/mobile-web-shell/generation-store.test.ts index b14db27039d..7b5ebd20edf 100644 --- a/mobile/src/mobile-web-shell/generation-store.test.ts +++ b/mobile/src/mobile-web-shell/generation-store.test.ts @@ -13,7 +13,7 @@ const HOST = deriveHostCacheKey('host-a') function buildResult(options: { buildId?: string - assets?: readonly { path: string; byteLength: number }[] + assets?: readonly { path: string; byteLength: number; kind?: string }[] bytes?: ReadonlyMap }): MobileWebBundleFetchResult { const listed = options.assets ?? [ @@ -24,7 +24,8 @@ function buildResult(options: { path: asset.path, sha256: String(index).repeat(64).slice(0, 64), byteLength: asset.byteLength, - contentType: 'text/html; charset=utf-8' + contentType: 'text/html; charset=utf-8', + ...(asset.kind === undefined ? {} : { kind: asset.kind }) })) const totalBytes = assets.reduce((sum, asset) => sum + asset.byteLength, 0) return { @@ -75,4 +76,25 @@ describe('an update staged behind a live page', () => { expect((await store.readActiveGeneration(HOST))?.buildId).toBe(SECOND_BUILD) expect(await fs.fileExists(`${ROOT}/${HOST}/generations/${BUILD}/index.html`)).toBe(false) }) + + it('stages a build without its language-catalog chunks and still commits it at the next launch', async () => { + const fs = createFakeFileSystem() + const first = createGenerationStore({ fileSystem: fs }) + await activate(first, HOST) + first.holdGeneration(HOST) + const withCatalog = buildResult({ + assets: [ + { path: 'index.html', byteLength: 8 }, + { path: 'assets/de.js', byteLength: 3, kind: 'catalog' } + ], + bytes: new Map([['index.html', new Uint8Array(8)]]) + }) + first.deferCommit(await first.stageGeneration(HOST, withCatalog)) + + const relaunched = createGenerationStore({ fileSystem: fs }) + + const build = withCatalog.manifest.buildId + expect((await relaunched.readActiveGeneration(HOST))?.buildId).toBe(build) + expect(await fs.fileExists(`${ROOT}/${HOST}/generations/${build}/assets/de.js`)).toBe(false) + }) }) diff --git a/mobile/src/mobile-web-shell/host-area-page-storage.test.tsx b/mobile/src/mobile-web-shell/host-area-page-storage.test.tsx index 1d12bae7036..fe9a34b9777 100644 --- a/mobile/src/mobile-web-shell/host-area-page-storage.test.tsx +++ b/mobile/src/mobile-web-shell/host-area-page-storage.test.tsx @@ -26,7 +26,7 @@ vi.mock('@react-native-async-storage/async-storage', () => ({ ) } })) -vi.mock('../transport/hosts/host-store', () => ({ +vi.mock('../host-catalog/host-store', () => ({ loadHosts: async () => [{ id: 'host-a', name: 'Host', endpoint: 'ws://h', lastConnected: 1 }] })) diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-session-contract.ts b/mobile/src/mobile-web-shell/mobile-web-shell-session-contract.ts index 1a6f608e8c2..656adbf6b78 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-session-contract.ts +++ b/mobile/src/mobile-web-shell/mobile-web-shell-session-contract.ts @@ -25,6 +25,7 @@ export type MobileWebShellGates = { export type MobileWebShellManifestFacts = MobileWebBundleCompatManifest & { readonly buildId: string + // A download with nothing to reuse, in fetch progress's unit (each brotli copy where read, else asset bytes). readonly totalBytes: number readonly totalAssets: number // Older desktops omit routes and serve none. diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-session-effects.ts b/mobile/src/mobile-web-shell/mobile-web-shell-session-effects.ts index c5995d21023..a8d939a15d7 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-session-effects.ts +++ b/mobile/src/mobile-web-shell/mobile-web-shell-session-effects.ts @@ -1,8 +1,10 @@ import { fetchMobileWebBundle } from '../transport/web-bundle/mobile-web-bundle-fetch' +import { mobileWebBundleStreamBytes } from '../transport/web-bundle/mobile-web-bundle-window-reader' import { mobileWebBundleManifestRead } from '../transport/web-bundle/mobile-web-bundle-operations' -import type { - MobileWebBundleManifestRead, - MobileWebBundleManifestReply +import { + shellDownloadedAssets, + type MobileWebBundleManifestRead, + type MobileWebBundleManifestReply } from '../transport/web-bundle/mobile-web-bundle-reply-schemas' import type { MobileWebBundleCompatManifest } from '../transport/web-bundle/mobile-web-bundle-compat' import { runRpcOperation } from '../transport/rpc/rpc-operation' @@ -87,8 +89,9 @@ export async function readManifest( manifest: { ...compatOf(manifest), buildId: manifest.buildId, - totalBytes: manifest.totalBytes, - totalAssets: manifest.assets.length, + // What this shell downloads: wire bytes, without the catalog chunks it skips. + totalBytes: mobileWebBundleStreamBytes(opened), + totalAssets: shellDownloadedAssets(manifest).length, routes: manifest.routes } }) diff --git a/mobile/src/mobile-web-shell/use-app-leaves-foreground.ts b/mobile/src/mobile-web-shell/use-app-leaves-foreground.ts index 41306009caa..2a439d8c3fc 100644 --- a/mobile/src/mobile-web-shell/use-app-leaves-foreground.ts +++ b/mobile/src/mobile-web-shell/use-app-leaves-foreground.ts @@ -1,5 +1,5 @@ import { useEffect, useLayoutEffect, useRef } from 'react' -import { AppState } from 'react-native' +import { AppState, Platform } from 'react-native' /** * Calls `onLeave` once each time the app leaves the foreground, before the system can kill it from the app @@ -29,13 +29,17 @@ export function useAppLeavesForeground(onLeave: () => void): void { const change = AppState.addEventListener('change', (next) => next === 'active' ? back() : leave() ) - // Window focus events come from Android only. - const blur = AppState.addEventListener('blur', leave) - const focus = AppState.addEventListener('focus', back) + // Window focus events come from Android only; iOS's RCTAppState rejects the subscription outright + // (a red screen in development), so it is never made there. + const windowFocus = + Platform.OS === 'android' + ? [AppState.addEventListener('blur', leave), AppState.addEventListener('focus', back)] + : [] return () => { change.remove() - blur.remove() - focus.remove() + for (const subscription of windowFocus) { + subscription.remove() + } } }, []) } diff --git a/mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx b/mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx index 98b98f51dfd..0d9b3956c08 100644 --- a/mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx +++ b/mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx @@ -46,7 +46,7 @@ vi.mock('@react-native-async-storage/async-storage', () => ({ getItem: async (key: string) => doubles.store.get(key) ?? null } })) -vi.mock('../transport/hosts/host-store', () => ({ +vi.mock('../host-catalog/host-store', () => ({ loadHosts: async () => { if (doubles.hostsReject) { throw new Error('the keychain would not answer') diff --git a/mobile/src/mobile-web-shell/use-page-host-snapshot.ts b/mobile/src/mobile-web-shell/use-page-host-snapshot.ts index bfba2b5ac7f..55a30b6e8bb 100644 --- a/mobile/src/mobile-web-shell/use-page-host-snapshot.ts +++ b/mobile/src/mobile-web-shell/use-page-host-snapshot.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { loadHosts } from '../transport/hosts/host-store' +import { loadHosts } from '../host-catalog/host-store' import type { BridgeInitHost } from './bridge/bridge-envelope' import { hydrateMirroredStorage, diff --git a/mobile/src/new-workspace/NewWorkspaceHostPicker.tsx b/mobile/src/new-workspace/NewWorkspaceHostPicker.tsx index 4fdc22c2acf..b9d5cb446bf 100644 --- a/mobile/src/new-workspace/NewWorkspaceHostPicker.tsx +++ b/mobile/src/new-workspace/NewWorkspaceHostPicker.tsx @@ -3,9 +3,9 @@ import { useRouter } from 'expo-router' import { PickerModal } from '../components/PickerModal' import { useMobileTranslation } from '../i18n/use-mobile-translation' import { hostPickerOptions } from '../components/use-connected-host-picker' -import { selectConnectableHostProfiles } from '../transport/hosts/host-catalog-selection' -import { loadHostCatalog } from '../transport/hosts/host-store' -import type { HostProfile } from '../transport/hosts/host-profile' +import { selectConnectableHostProfiles } from '../host-catalog/host-catalog-selection' +import { loadHostCatalog } from '../host-catalog/host-store' +import type { HostProfile } from '../host-catalog/host-profile' // Keep picker and loading drawer in the overlay’s window so the load can start as selection leaves. export function NewWorkspaceHostPicker({ diff --git a/mobile/src/notifications/host-push-registration-state.ts b/mobile/src/notifications/host-push-registration-state.ts index bd7385d568b..30c739f318f 100644 --- a/mobile/src/notifications/host-push-registration-state.ts +++ b/mobile/src/notifications/host-push-registration-state.ts @@ -1,8 +1,8 @@ -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' export type HostPushState = { connection: { - client: RpcOperationSender | null + client: UnvalidatedRpcRequestPort | null support: ReturnType | null } // An unanswered probe is unknown, not unsupported. diff --git a/mobile/src/notifications/mobile-notifications.ts b/mobile/src/notifications/mobile-notifications.ts index a8bee3af321..1200887b82b 100644 --- a/mobile/src/notifications/mobile-notifications.ts +++ b/mobile/src/notifications/mobile-notifications.ts @@ -4,7 +4,7 @@ import type { DismissNotificationEvent } from './desktop-notification-events' import type { RpcClient } from '../transport/rpc/rpc-client-contract' import { waitForPushFallback } from './push-fallback' import { getHostPushSupported, getHostPushSupportCompletion } from './push-registration' -import { loadHostCatalog } from '../transport/hosts/host-store' +import { loadHostCatalog } from '../host-catalog/host-store' import { deriveHostFingerprint } from './push-host-fingerprint' import { presentForegroundPush } from './android-foreground-push' import { diff --git a/mobile/src/notifications/mobile-push-registration-operations.ts b/mobile/src/notifications/mobile-push-registration-operations.ts index c9ff7f995e9..6c7f76ae8dd 100644 --- a/mobile/src/notifications/mobile-push-registration-operations.ts +++ b/mobile/src/notifications/mobile-push-registration-operations.ts @@ -1,3 +1,9 @@ +import type { + MobilePushFilter, + MobilePushRegisterInput +} from '../../../src/shared/mobile-push-contract' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' +import type { MobilePushToken } from './push-token' import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc/rpc-operation' import { rpcResultVariant } from '../transport/rpc/rpc-operation-result-reader' import { @@ -26,3 +32,36 @@ export const pushRouteUnregister = bindDeferredRpcOperation( read: rpcResultVariant('push-unregistered', notificationUnreadReplySchema) }) ) + +const REQUEST_TIMEOUT_MS = 5_000 + +export async function sendRegister( + client: UnvalidatedRpcRequestPort, + token: MobilePushToken, + filter: MobilePushFilter +): Promise { + const params: Omit = { + platform: token.platform, + token: token.token, + ...(token.apnsEnvironment ? { apnsEnvironment: token.apnsEnvironment } : {}), + filter + } + const reply = await pushRouteRegister + .request(client, params, { timeoutMs: REQUEST_TIMEOUT_MS, failWhenDisconnected: true }) + .catch(() => null) + const registration = reply && pushRouteRegister.interpret(reply) + if (!registration?.accepted) { + return false + } + return registration.value?.registered === true +} + +export async function sendUnregister( + client: UnvalidatedRpcRequestPort, + timeoutMs: number +): Promise { + const reply = await pushRouteUnregister + .request(client, null, { timeoutMs, failWhenDisconnected: true }) + .catch(() => null) + return reply !== null && pushRouteUnregister.interpret(reply).accepted +} diff --git a/mobile/src/notifications/notification-reply-schema.test.ts b/mobile/src/notifications/notification-reply-schema.test.ts deleted file mode 100644 index 78f4f1e6fc2..00000000000 --- a/mobile/src/notifications/notification-reply-schema.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { z } from 'zod' -import { - missedNotificationsSchema, - notificationUnreadReplySchema, - PUSH_REGISTER_REFUSAL_REASONS, - PUSH_TEST_REFUSAL_REASONS, - pushDeliveryTestResultSchema, - pushRouteRegistrationSchema -} from './notification-reply-schema' - -function reads(schema: z.ZodType, value: unknown): T { - const parsed = schema.safeParse(value) - if (!parsed.success) { - throw new Error(`expected a readable reply: ${parsed.error.message}`) - } - return parsed.data -} - -function refuses(schema: z.ZodType, value: unknown): boolean { - return !schema.safeParse(value).success -} - -describe('notification replies tolerate the shapes their call sites guard', () => { - it('reads a test-push reply the screen reaches through optional chaining', () => { - expect(reads(pushDeliveryTestResultSchema, { accepted: true })?.accepted).toBe(true) - expect(reads(pushDeliveryTestResultSchema, null)).toBe(null) - expect(reads(pushDeliveryTestResultSchema, undefined)).toBe(undefined) - expect(reads(pushDeliveryTestResultSchema, { error: 'refused' })?.accepted).toBe(undefined) - }) - - // Schema refusal would replace the screen’s generic send-failure copy with a parser error. - it('reads a non-object test-push result as absent, which takes the generic copy', () => { - for (const value of ['garbage', 7, true, []]) { - expect(refuses(pushDeliveryTestResultSchema, value)).toBe(false) - const result = reads(pushDeliveryTestResultSchema, value) - expect(result?.accepted).toBe(undefined) - expect(result?.reason).toBe(undefined) - } - }) - - it('reads a registration reply the reconciler reaches through optional chaining', () => { - expect(reads(pushRouteRegistrationSchema, { registered: true })?.registered).toBe(true) - expect(reads(pushRouteRegistrationSchema, null)).toBe(null) - expect(reads(pushRouteRegistrationSchema, { registered: 'yes' })?.registered).toBe(undefined) - }) - - it('takes every reply at the two sites whose body nothing reads', () => { - expect(refuses(notificationUnreadReplySchema, undefined)).toBe(false) - expect(refuses(notificationUnreadReplySchema, null)).toBe(false) - expect(refuses(notificationUnreadReplySchema, 'anything')).toBe(false) - }) -}) - -describe('closed enums degrade to the copy main showed', () => { - it('keeps a reason the screen branches on and drops one it does not know', () => { - expect( - reads(pushDeliveryTestResultSchema, { accepted: false, reason: 'rate_limited' })?.reason - ).toBe('rate_limited') - expect( - reads(pushDeliveryTestResultSchema, { accepted: false, reason: 'not_registered' })?.reason - ).toBe('not_registered') - expect(reads(pushDeliveryTestResultSchema, { accepted: false, reason: 'quota' })?.reason).toBe( - undefined - ) - }) - - it('keeps a registration reason and drops an unknown one', () => { - const reply = { registered: false, reason: 'throttled' } - expect(reads(pushRouteRegistrationSchema, reply)?.reason).toBe('throttled') - expect( - reads(pushRouteRegistrationSchema, { registered: false, reason: 'moon-phase' })?.reason - ).toBe(undefined) - }) - - // Host-pinned unions cover every arm; these loops prove each survives parsing. - it('keeps every refusal reason the host declares for either route', () => { - for (const reason of PUSH_TEST_REFUSAL_REASONS) { - expect(reads(pushDeliveryTestResultSchema, { accepted: false, reason })?.reason).toBe(reason) - } - for (const reason of PUSH_REGISTER_REFUSAL_REASONS) { - expect(reads(pushRouteRegistrationSchema, { registered: false, reason })?.reason).toBe(reason) - } - }) -}) - -describe('dismissal rows stay opaque', () => { - it('keeps every row for readPushNotificationIdentity to judge', () => { - const rows = [{ notificationId: 'n-1' }, 'not-a-row', 7] - expect(reads(missedNotificationsSchema, { dismissedPushes: rows })?.dismissedPushes).toEqual( - rows - ) - }) - - it('drops a dismissedPushes that is not an array, as Array.isArray did', () => { - expect(reads(missedNotificationsSchema, { dismissedPushes: 'all' })?.dismissedPushes).toBe( - undefined - ) - expect(reads(missedNotificationsSchema, null)).toBe(null) - }) -}) diff --git a/mobile/src/notifications/notification-routing.ts b/mobile/src/notifications/notification-routing.ts index 51a81548b0a..a083798e327 100644 --- a/mobile/src/notifications/notification-routing.ts +++ b/mobile/src/notifications/notification-routing.ts @@ -1,7 +1,7 @@ import { parseExecutionHostId } from '../../../src/shared/execution-host' import type { HostStackRouteTarget } from '../navigation/host-stack-navigation' import { mobileSessionRouteTarget } from '../session/mobile-session-route' -import type { HostCredentialStatus } from '../transport/hosts/host-profile' +import type { HostCredentialStatus } from '../host-catalog/host-profile' export type NotificationNavigationOptions = { knownHostIds?: ReadonlySet diff --git a/mobile/src/notifications/push-dismissal-reconciliation.ts b/mobile/src/notifications/push-dismissal-reconciliation.ts index 6f5c0a5457a..5a77d259d17 100644 --- a/mobile/src/notifications/push-dismissal-reconciliation.ts +++ b/mobile/src/notifications/push-dismissal-reconciliation.ts @@ -1,6 +1,6 @@ import * as Notifications from 'expo-notifications' import { pushMissedSinceRead, type MobilePushDismissalRpcSender } from './push-dismissal-operations' -import { loadHostCatalog } from '../transport/hosts/host-store' +import { loadHostCatalog } from '../host-catalog/host-store' import { resolveHostIdForFingerprint } from './push-host-fingerprint' import { readNativeNotificationData } from './native-notification-data' import { readOrcaPushPayload, type OrcaPushPayload } from './push-payload' diff --git a/mobile/src/notifications/push-receive.test.ts b/mobile/src/notifications/push-receive.test.ts index 9c1b8832c9c..e7d93268bb5 100644 --- a/mobile/src/notifications/push-receive.test.ts +++ b/mobile/src/notifications/push-receive.test.ts @@ -4,8 +4,8 @@ import { setNotificationViewingWorkspace } from './notification-viewing-policy' vi.mock('./push-tray-dismissal', () => ({ dismissPresentedPushNotification: vi.fn() })) import { beforeEach, describe, expect, it, vi } from 'vitest' import { sha256 } from '@noble/hashes/sha256' -import { loadHostCatalog } from '../transport/hosts/host-store' -import type { HostCatalogEntry } from '../transport/hosts/host-profile' +import { loadHostCatalog } from '../host-catalog/host-store' +import type { HostCatalogEntry } from '../host-catalog/host-profile' import { foregroundNotificationBehavior, @@ -19,7 +19,7 @@ async function shouldSuppressForegroundPush(data: unknown): Promise { } vi.mock('react-native', () => ({ AppState: { currentState: 'background' } })) -vi.mock('../transport/hosts/host-store', () => ({ loadHostCatalog: vi.fn() })) +vi.mock('../host-catalog/host-store', () => ({ loadHostCatalog: vi.fn() })) const storage = vi.hoisted(() => new Map()) vi.mock('@react-native-async-storage/async-storage', () => ({ default: { diff --git a/mobile/src/notifications/push-receive.ts b/mobile/src/notifications/push-receive.ts index 44a64e05e09..15d07022d14 100644 --- a/mobile/src/notifications/push-receive.ts +++ b/mobile/src/notifications/push-receive.ts @@ -2,7 +2,7 @@ import { wasPushDismissed } from './push-dismissal-watermarks' import { dismissPresentedPushNotification } from './push-tray-dismissal' import { shouldSuppressNotificationWhileViewing } from './notification-viewing-policy' import { loadPushNotificationsEnabled } from '../storage/preferences' -import { loadHostCatalog } from '../transport/hosts/host-store' +import { loadHostCatalog } from '../host-catalog/host-store' import { resolveHostIdForFingerprint } from './push-host-fingerprint' import { readOrcaPushPayload, type OrcaPushPayload } from './push-payload' import type { NotificationBehavior } from 'expo-notifications' diff --git a/mobile/src/notifications/push-registration.ts b/mobile/src/notifications/push-registration.ts index 5c8267a49d4..853f0329894 100644 --- a/mobile/src/notifications/push-registration.ts +++ b/mobile/src/notifications/push-registration.ts @@ -13,14 +13,10 @@ import { saveNotificationDeliveryPreferences, type NotificationDeliveryPreferences } from './notification-delivery-preferences' -import type { - MobilePushFilter, - MobilePushRegisterInput -} from '../../../src/shared/mobile-push-contract' import { NOTIFICATIONS_REMOTE_PUSH_RUNTIME_CAPABILITY } from '../../../src/shared/protocol-version' -import type { RpcOperationSender as PushClient } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { startRuntimeCapabilityProbe } from '../transport/connection/runtime-capability-probe' -import { pushRouteRegister, pushRouteUnregister } from './mobile-push-registration-operations' +import { sendRegister, sendUnregister } from './mobile-push-registration-operations' import { loadPushNotificationsEnabled, loadRemotePushHostRegistrations, @@ -44,7 +40,7 @@ export function getHostPushSupported(hostId: string): boolean | null { } export function getHostPushSupportCompletion( hostId: string, - client: PushClient + client: UnvalidatedRpcRequestPort ): HostPushSupportCompletion | null { const connection = hostsById.get(hostId)?.connection return connection?.client === client ? connection.support : null @@ -98,34 +94,6 @@ async function currentToken(): Promise { return tokenPromise } -async function sendRegister( - client: PushClient, - token: MobilePushToken, - filter: MobilePushFilter -): Promise { - const params: Omit = { - platform: token.platform, - token: token.token, - ...(token.apnsEnvironment ? { apnsEnvironment: token.apnsEnvironment } : {}), - filter - } - const reply = await pushRouteRegister - .request(client, params, { timeoutMs: REQUEST_TIMEOUT_MS, failWhenDisconnected: true }) - .catch(() => null) - const registration = reply && pushRouteRegister.interpret(reply) - if (!registration?.accepted) { - return false - } - return registration.value?.registered === true -} - -async function sendUnregister(client: PushClient, timeoutMs: number): Promise { - const reply = await pushRouteUnregister - .request(client, null, { timeoutMs, failWhenDisconnected: true }) - .catch(() => null) - return reply !== null && pushRouteUnregister.interpret(reply).accepted -} - async function reconcileHost(hostId: string): Promise { const state = hostsById.get(hostId) const client = state?.connection.client @@ -222,7 +190,10 @@ async function reconcileAllHosts(): Promise { } // Keep tracked host intent across disconnects; this disposer detaches only its current connected client. -export function attachPushRegistration(hostId: string, client: PushClient): () => void { +export function attachPushRegistration( + hostId: string, + client: UnvalidatedRpcRequestPort +): () => void { const state = hostState(hostId) if (state.connection.client !== client) { state.capabilityProbeStop?.() diff --git a/mobile/src/notifications/push-socket-dismissal.ts b/mobile/src/notifications/push-socket-dismissal.ts index 16a3b560db2..84f548684b8 100644 --- a/mobile/src/notifications/push-socket-dismissal.ts +++ b/mobile/src/notifications/push-socket-dismissal.ts @@ -1,4 +1,4 @@ -import { loadHostCatalog } from '../transport/hosts/host-store' +import { loadHostCatalog } from '../host-catalog/host-store' import { deriveHostFingerprint } from './push-host-fingerprint' import { dismissPresentedPushNotification } from './push-tray-dismissal' import type { DismissNotificationEvent } from './desktop-notification-events' diff --git a/mobile/src/notifications/use-remote-push-capable-hosts.ts b/mobile/src/notifications/use-remote-push-capable-hosts.ts index db86e9d08b2..f7ff991cff1 100644 --- a/mobile/src/notifications/use-remote-push-capable-hosts.ts +++ b/mobile/src/notifications/use-remote-push-capable-hosts.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react' -import { loadHostCatalog } from '../transport/hosts/host-store' +import { loadHostCatalog } from '../host-catalog/host-store' import type { RpcClient } from '../transport/rpc/rpc-client-contract' import { startRuntimeCapabilityProbe } from '../transport/connection/runtime-capability-probe' import { useAllHostClients } from '../transport/connection/use-all-host-clients' diff --git a/mobile/src/pairing/mobile-relay-pairing-journal-store.ts b/mobile/src/pairing/mobile-relay-pairing-journal-store.ts index bc885969b67..cbd96e1289c 100644 --- a/mobile/src/pairing/mobile-relay-pairing-journal-store.ts +++ b/mobile/src/pairing/mobile-relay-pairing-journal-store.ts @@ -11,7 +11,7 @@ import { readPairingKeychainItem, resetPairingKeychainForTests, writePairingKeychainItem -} from '../transport/hosts/pairing-keychain' +} from '../host-credentials/pairing-keychain' const JOURNAL_STORAGE_KEY = 'orca:mobile-relay:pairing-journal:v1' const JOURNAL_SECRET_KEY = 'orca.mobile-relay.pairing-journal.v1' diff --git a/mobile/src/pairing/mobile-relay-pairing-recovery.test.ts b/mobile/src/pairing/mobile-relay-pairing-recovery.test.ts index 648a53fd90b..7ad28633fff 100644 --- a/mobile/src/pairing/mobile-relay-pairing-recovery.test.ts +++ b/mobile/src/pairing/mobile-relay-pairing-recovery.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { MobileRelayCredentialBundle } from '../transport/hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../host-credentials/mobile-relay-credential-bundle' import { createMobileRelayPairingJournal } from './mobile-relay-pairing-journal' import { recoverMobileRelayPairing, @@ -10,7 +10,7 @@ import type { connectMobileRelayForPairing, PairingCandidateClient } from './mobile-relay-physical-client' -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import type { RpcResponse } from '../transport/rpc/rpc-wire-types' import type { PairingOffer } from '../../../src/shared/remote-runtime/pairing/mobile-relay-pairing-offer' diff --git a/mobile/src/pairing/mobile-relay-pairing-recovery.ts b/mobile/src/pairing/mobile-relay-pairing-recovery.ts index 03fc772c3c0..e375bab0604 100644 --- a/mobile/src/pairing/mobile-relay-pairing-recovery.ts +++ b/mobile/src/pairing/mobile-relay-pairing-recovery.ts @@ -4,13 +4,13 @@ import type { PairingGetEndpointsResult } from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import type { PairingRelay } from '../../../src/shared/remote-runtime/pairing/mobile-relay-pairing-offer' -import { loadHosts, savePairedHost } from '../transport/hosts/host-store' +import { loadHosts, savePairedHost } from '../host-catalog/host-store' import { promotePairingJournalCredential, readMobileRelayCredentialBundle, writeMobileRelayCredentialBundle, type MobileRelayCredentialBundle -} from '../transport/hosts/mobile-relay-credential-bundle' +} from '../host-credentials/mobile-relay-credential-bundle' import { resolvePairingInviteThroughDirector } from './mobile-relay-invite-director' import type { MobileRelayPairingJournal } from './mobile-relay-pairing-journal' import { diff --git a/mobile/src/pairing/pairing-relay-host.ts b/mobile/src/pairing/pairing-relay-host.ts index ff50da77e7f..a6cd7017273 100644 --- a/mobile/src/pairing/pairing-relay-host.ts +++ b/mobile/src/pairing/pairing-relay-host.ts @@ -3,7 +3,7 @@ import type { MobileRelayEndpoint } from '../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import type { MobileRelayPairingJournal } from './mobile-relay-pairing-journal' -import type { HostProfile } from '../transport/hosts/host-profile' +import type { HostProfile } from '../host-catalog/host-profile' import type { PairingOffer } from '../../../src/shared/remote-runtime/pairing/mobile-relay-pairing-offer' export function directHost( diff --git a/mobile/src/pairing/pairing.ts b/mobile/src/pairing/pairing.ts index d4641651478..4f3379efe29 100644 --- a/mobile/src/pairing/pairing.ts +++ b/mobile/src/pairing/pairing.ts @@ -1,4 +1,4 @@ -import '../transport/hosts/host-profile' +import '../host-catalog/host-profile' import { PairingOfferSchema, type PairingOffer diff --git a/mobile/src/pairing/pre-profile-pairing-coordinator.ts b/mobile/src/pairing/pre-profile-pairing-coordinator.ts index f7f8f426ad7..e88d92b7e04 100644 --- a/mobile/src/pairing/pre-profile-pairing-coordinator.ts +++ b/mobile/src/pairing/pre-profile-pairing-coordinator.ts @@ -1,6 +1,6 @@ import { Platform } from 'react-native' import { connect, type ConnectOptions } from '../transport/rpc/direct-rpc-connect' -import { resolvePairingHostIdentity, savePairedHost } from '../transport/hosts/host-store' +import { resolvePairingHostIdentity, savePairedHost } from '../host-catalog/host-store' import type { PairingOffer } from '../../../src/shared/remote-runtime/pairing/mobile-relay-pairing-offer' import { isPairingRelayRpcUnavailable } from './pairing-relay-rpc-unavailable' import { @@ -21,7 +21,7 @@ import { settleMobileRelayPairingRecovery } from './mobile-relay-pairing-recover import { promotePairingJournalCredential, writeMobileRelayCredentialBundle -} from '../transport/hosts/mobile-relay-credential-bundle' +} from '../host-credentials/mobile-relay-credential-bundle' import { connectMobileRelayForPairing, type PairingCandidateClient @@ -33,7 +33,7 @@ import { createRecoveringPairingRelayCandidate } from './pairing-relay-candidate import { createPairingRelayLogger } from '../diagnostics/connection/pairing-relay-log' import { redactSocketEndpoint } from '../diagnostics/connection/socket-event-debug' import { assertCommittedInstall, directHost, relayHost } from './pairing-relay-host' -import { recordHostDescriptorFromStatus } from '../transport/hosts/host-descriptor-recorder' +import { recordHostDescriptorFromStatus } from '../host-descriptors/host-descriptor-recorder' import { adoptExchangedToken, exchangePairingTicket } from './pairing-ticket-exchange' import type { HostStatusReply } from '../transport/hosts/host-status-reply-schema' diff --git a/mobile/src/pairing/unpaired-host-credential-deletion.ts b/mobile/src/pairing/unpaired-host-credential-deletion.ts index a7e056764bd..1df85f8f740 100644 --- a/mobile/src/pairing/unpaired-host-credential-deletion.ts +++ b/mobile/src/pairing/unpaired-host-credential-deletion.ts @@ -1,9 +1,9 @@ -import { deleteHostDeviceToken } from '../transport/hosts/host-device-token-store' +import { deleteHostDeviceToken } from '../host-credentials/host-device-token-store' import { clearHostCredentialWriteRevision, getHostCredentialWriteRevision -} from '../transport/hosts/host-credential-write-revision' -import { deleteMobileRelayCredentialBundle } from '../transport/hosts/mobile-relay-credential-bundle' +} from '../host-credentials/host-credential-write-revision' +import { deleteMobileRelayCredentialBundle } from '../host-credentials/mobile-relay-credential-bundle' import { deleteMobileRelayDirectUpgradeJournal } from '../transport/relay/mobile-relay-direct-upgrade-journal' type DeletionDependencies = { diff --git a/mobile/src/query/first-paint-query-keys.ts b/mobile/src/query/first-paint-query-keys.ts index 47654004af7..8305d983bb4 100644 --- a/mobile/src/query/first-paint-query-keys.ts +++ b/mobile/src/query/first-paint-query-keys.ts @@ -16,7 +16,7 @@ import { hostProjectSectionsQuery } from '../host-screen/use-host-project-sectio import { sessionTabsFamily, sessionTabsKey } from '../session/tabs/mobile-session-last-tabs' import type { MobileSessionTab } from '../session/tabs/mobile-session-tabs' import { activeSessionTabOf } from '../session/tabs/active-session-tab' -import { storedTabChatTranscriptIdentity } from '../session/native-chat/mobile-native-chat-transcript-store' +import { storedTabChatTranscriptIdentity } from '../session/native-chat/transcript/mobile-native-chat-transcript-store' import { gitStatusQuery } from '../source-control/mobile-git-status-query' import { firstPaintTaskKeys } from '../tasks/task-query-storage' import { hostQueryKey } from './host-query-key' diff --git a/mobile/src/query/host-query.ts b/mobile/src/query/host-query.ts index e7b0a36706c..7a03ae8efb4 100644 --- a/mobile/src/query/host-query.ts +++ b/mobile/src/query/host-query.ts @@ -24,9 +24,6 @@ import { import { holdsRestoredHostQueryData, type HostQueryPersist } from './host-query-persistence' import { hostReadClient } from './host-read-client' -// Exported with the query hooks so the router lands in their page chunk, not a chunk of its own. -export { useHostEventRouter } from './host-event-router' - export type HostQuery = { family: string // Calls the domain's loader. `previous` is the cached data a revalidation may build on; live reads pass none. diff --git a/mobile/src/query/page-query-persistence.ts b/mobile/src/query/page-query-persistence.ts index d0dad07998c..0e3eadf7368 100644 --- a/mobile/src/query/page-query-persistence.ts +++ b/mobile/src/query/page-query-persistence.ts @@ -1,6 +1,6 @@ import type { QueryClient } from '@tanstack/react-query' import type { PageDeviceStore } from '../mobile-web-shell/bridge/page-device-store' -import { primeStoredChatTranscripts } from '../session/native-chat/mobile-native-chat-transcript-store' +import { primeStoredChatTranscripts } from '../session/native-chat/transcript/mobile-native-chat-transcript-store' import { firstPaintChatTranscriptIdentity, firstPaintQueryKeys } from './first-paint-query-keys' import { createHostQueryPersistence } from './host-query-persistence' diff --git a/mobile/src/session/MobileNativeChatForkProvider.tsx b/mobile/src/session/MobileNativeChatForkProvider.tsx index 44f80dff88a..6607dae2a36 100644 --- a/mobile/src/session/MobileNativeChatForkProvider.tsx +++ b/mobile/src/session/MobileNativeChatForkProvider.tsx @@ -2,7 +2,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef } from 'react' import { useHostProtocolGates } from '../components/host-protocol-gates-context' import type { TuiAgent } from '../../../src/shared/agent-launch/catalog/tui-agent' import type { StructuredAgentSessionForkSource } from '../../../src/shared/agent-session/structured-agent-session-create' -import { MobileNativeChatForkedFromContext } from './mobile-native-chat-notice-context' +import { MobileNativeChatForkedFromContext } from './native-chat/transcript/mobile-native-chat-notice-context' import { forkMobileStructuredChat } from './mobile-structured-chat-fork' import { MobileNativeChatForkContext, diff --git a/mobile/src/session/MobileSessionActiveContent.tsx b/mobile/src/session/MobileSessionActiveContent.tsx index 06861569071..0400353b3ca 100644 --- a/mobile/src/session/MobileSessionActiveContent.tsx +++ b/mobile/src/session/MobileSessionActiveContent.tsx @@ -25,8 +25,8 @@ import { useMobileSessionContentKeyboardLift } from '../terminal/session/use-mob import { useOpenUrlInOrca } from '../browser/use-open-url-in-orca' import { useWorkspaceExecutionHost } from '../navigation/workspace-execution-host' import { useOpenHostAccounts } from '../accounts/use-open-mobile-accounts' -import { useHostProfile } from '../transport/hosts/use-host-profile' -import { useHostDisplay } from '../transport/hosts/use-host-display' +import { useHostProfile } from '../host-catalog/use-host-profile' +import { useHostDisplay } from '../host-descriptors/use-host-display' import { FileReader } from '../files/session-documents/MobileSessionFileReader' import { MobileSessionContentState } from './MobileSessionContentState' import { MarkdownReader } from '../files/session-documents/MobileSessionMarkdownReader' diff --git a/mobile/src/session/MobileSessionHeader.tsx b/mobile/src/session/MobileSessionHeader.tsx index c7593258e42..9020e7d7486 100644 --- a/mobile/src/session/MobileSessionHeader.tsx +++ b/mobile/src/session/MobileSessionHeader.tsx @@ -6,7 +6,7 @@ import { NavBar, NavBarRefreshLine } from '../components/nav-bar/NavBar' import { useRefreshHint } from '../query/refresh-hint' import { NavBarButton } from '../components/nav-bar/NavBarButton' import { MobileSessionHeaderMoreMenu } from './MobileSessionHeaderMoreMenu' -import { MobileSessionTabStrip } from './tabs/MobileSessionTabStrip' +import { MobileSessionTabBar } from './tabs/MobileSessionTabBar' import { MobileSessionTitleBlock } from './MobileSessionTitleBlock' import type { SessionTitleCard } from './tabs/use-session-title-card' import { styles } from './mobile-session-styles' @@ -58,11 +58,11 @@ export function MobileSessionHeader({ // Why: a sideways phone is ~390pt tall, so the tabs share the top bar instead of taking a second row. const hasTabs = visibleTabs.length > 0 const tabsInTopBar = (layout.isLandscapePhone || sideRail) && hasTabs - // Bar buttons otherwise leave less than two readable tabs on a narrow header. + // Bar buttons otherwise leave the tab bar too narrow to read on a narrow header. const actionsCollapsed = tabsInTopBar && (headerWidth ?? layout.width) < 600 const sourceControlInBar = !isFloatingWorkspaceRoute && !actionsCollapsed const tabStrip = hasTabs ? ( - - { - setShowCreateTabDrawer(false) - void handleCreateTerminal() - } - }, - // Why: browser/markdown creation resolve a real worktree on the host - // (browser.tabCreate, files.createFile); the floating sentinel is - // terminal-only over RPC, so those options hide there. - ...(isFloatingWorkspaceRoute - ? [] - : [ - { - id: 'browser', - label: t('session.sheets.browser', 'Browser'), - icon: Globe, - closeBeforePress: true, - onPress: () => { - if (browserScreencastSupported !== true) { - showToast( - t( - 'session.sheets.browserUpdateRequired', - 'Desktop update required for mobile browser streaming' - ), - 1600 - ) - return - } - setShowCreateBrowserModal(true) - } - }, - ...(onPairedHost - ? [ - { - id: 'markdownNote', - label: t('session.sheets.markdownNote', 'Markdown Note'), - icon: FileText, - onPress: () => { - setShowCreateTabDrawer(false) - void handleCreateMarkdownNote() - } - } - ] - : []) - ]) - ]} + workspaceName={worktreeName} + agentOptions={createTabAgentOptions} + agentStatusRow={createTabAgentStatusRow} + // Why: browser/markdown creation resolve a real worktree on the host + // (browser.tabCreate, files.createFile); the floating sentinel is + // terminal-only over RPC, so those options hide there. + canCreateBrowser={!isFloatingWorkspaceRoute} + canCreateMarkdownNote={!isFloatingWorkspaceRoute && onPairedHost} onClose={() => setShowCreateTabDrawer(false)} + onCreateAgent={(agent) => void handleCreateTerminal(agent)} + onCreateTerminal={() => void handleCreateTerminal()} + onCreateBrowser={() => { + if (browserScreencastSupported !== true) { + showToast( + t( + 'session.sheets.browserUpdateRequired', + 'Desktop update required for mobile browser streaming' + ), + 1600 + ) + return + } + setShowCreateBrowserModal(true) + }} + onCreateMarkdownNote={() => void handleCreateMarkdownNote()} /> ({ }) })) // The chat's per-connection CI setting read isn't a prompt write; these tests count only those. -vi.mock('../use-mobile-native-chat-ci-result-lines', () => ({ +vi.mock('../native-chat/preferences/use-mobile-native-chat-ci-result-lines', () => ({ useMobileNativeChatCiResultLines: () => false })) vi.mock('../native-chat/use-mobile-native-chat-session-lane', () => ({ diff --git a/mobile/src/session/agent-launch/mobile-agent-detection-operations.ts b/mobile/src/session/agent-launch/mobile-agent-detection-operations.ts index 443525aca2d..3587dbbb98e 100644 --- a/mobile/src/session/agent-launch/mobile-agent-detection-operations.ts +++ b/mobile/src/session/agent-launch/mobile-agent-detection-operations.ts @@ -1,14 +1,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' -import '../../terminal/terminal-reply-schema' import { detectedAgentsSchema, refreshedAgentsSchema } from './mobile-agent-detection-reply-schema' -import '../../files/session-documents/mobile-markdown-tab-reply-schema' -import '../../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../../terminal/session/mobile-session-terminal-reply-schema' -import '../../worktree/session-worktree-record-reply-schema' -import '../native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../../terminal/quick-commands/mobile-quick-command-reply-schema' -import '../../files/workspace-file-paths-reply-schema' // Launch detection surfaces host refusals; advisory task-drawer readers intentionally skip them. export const preflightDetectAgentsRead = bindDeferredRpcOperation( diff --git a/mobile/src/session/agent-launch/mobile-existing-agent-launch.ts b/mobile/src/session/agent-launch/mobile-existing-agent-launch.ts index 6b3be4fb6b9..1d2c17eb236 100644 --- a/mobile/src/session/agent-launch/mobile-existing-agent-launch.ts +++ b/mobile/src/session/agent-launch/mobile-existing-agent-launch.ts @@ -22,9 +22,9 @@ import { isAgentLaunchReservationTakenRefusal } from '../../../../src/shared/agent-launch/contracts/agent-launch-replay-refusal' import { sendReplayingAmbiguousDelivery } from '../../tasks/replay-on-ambiguous-delivery' -import { structuredSessionOperationId } from '../structured-session-operation-id' +import { structuredSessionOperationId } from '../native-chat/agent-session/structured-session-operation-id' import { randomV4Uuid } from '../../platform/random-v4-uuid' -import { rememberMobileCreatedStructuredSession } from '../mobile-created-structured-sessions' +import { rememberMobileCreatedStructuredSession } from '../native-chat/agent-session/mobile-created-structured-sessions' // Why: the host waits up to 60s for a terminal agent to become ready before pasting the prompt, // so a prompted launch must outlive that wait or the phone reports a launch that is still running. diff --git a/mobile/src/session/agent-launch/mobile-new-tab-settings-operations.ts b/mobile/src/session/agent-launch/mobile-new-tab-settings-operations.ts index 1a0627c3289..f7a8ecd5f50 100644 --- a/mobile/src/session/agent-launch/mobile-new-tab-settings-operations.ts +++ b/mobile/src/session/agent-launch/mobile-new-tab-settings-operations.ts @@ -1,6 +1,5 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' import type { RpcCompatibleReader } from '../../transport/rpc/rpc-operation-contract' -import '../../../../src/shared/source-control/listing/source-control-group-order' import { settingsMember } from '../../settings/mobile-settings-projection' // New-tab checks the sibling's refusal before touching settings, but after its own refusal. diff --git a/mobile/src/session/agent-launch/mobile-runtime-repo-operations.ts b/mobile/src/session/agent-launch/mobile-runtime-repo-operations.ts index cb885ee3fc6..0980441d230 100644 --- a/mobile/src/session/agent-launch/mobile-runtime-repo-operations.ts +++ b/mobile/src/session/agent-launch/mobile-runtime-repo-operations.ts @@ -1,14 +1,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' -import '../../terminal/terminal-reply-schema' import { runtimeRepoListSchema } from './mobile-agent-detection-reply-schema' -import '../../files/session-documents/mobile-markdown-tab-reply-schema' -import '../../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../../terminal/session/mobile-session-terminal-reply-schema' -import '../../worktree/session-worktree-record-reply-schema' -import '../native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../../terminal/quick-commands/mobile-quick-command-reply-schema' -import '../../files/workspace-file-paths-reply-schema' export type MobileRuntimeRepoSummary = { id: string diff --git a/mobile/src/session/agent-session-write-notice-copy.test.ts b/mobile/src/session/agent-session-write-notice-copy.test.ts deleted file mode 100644 index 5788ba290c2..00000000000 --- a/mobile/src/session/agent-session-write-notice-copy.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { agentSessionWriteNoticeParts } from '../../../src/shared/agent-session/failures/agent-session-refusal-notice' -import { agentSessionRefusalFailure } from '../../../src/shared/agent-session/failures/agent-session-write-failure' -import { mobileAgentSessionWriteNoticeText } from './agent-session-write-notice-copy' - -describe('mobileAgentSessionWriteNoticeText', () => { - it('names the attachment an expired refusal lists, as its card labels it', () => { - const failure = agentSessionRefusalFailure({ - code: 'agent_session_operation_invalid', - details: { - reason: 'attachmentExpired', - expiredAttachments: [{ uploadId: 'upload-1', name: 'notes.pdf' }] - } - }) - expect( - mobileAgentSessionWriteNoticeText(agentSessionWriteNoticeParts(failure, 'composer-send')) - ).toBe('notes.pdf expired. Your message was not sent. Remove it and attach it again.') - }) -}) diff --git a/mobile/src/session/floating-workspace.test.ts b/mobile/src/session/floating-workspace.test.ts deleted file mode 100644 index 8f29646b49b..00000000000 --- a/mobile/src/session/floating-workspace.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - FLOATING_WORKSPACE_WORKTREE_ID, - floatingWorkspaceSessionPath, - isFloatingWorkspaceWorktreeId -} from './floating-workspace' - -describe('floating workspace routing', () => { - it('matches only the desktop sentinel id', () => { - expect(isFloatingWorkspaceWorktreeId('global-floating-terminal')).toBe(true) - expect(isFloatingWorkspaceWorktreeId('repo-1::/worktree')).toBe(false) - expect(isFloatingWorkspaceWorktreeId('folder:group-1')).toBe(false) - expect(isFloatingWorkspaceWorktreeId(undefined)).toBe(false) - expect(isFloatingWorkspaceWorktreeId(null)).toBe(false) - }) - - it('builds the session route with an explicit title seed', () => { - expect(floatingWorkspaceSessionPath('host-1')).toBe( - `/h/host-1/session/${FLOATING_WORKSPACE_WORKTREE_ID}?name=Floating%20Workspace` - ) - }) -}) diff --git a/mobile/src/session/mobile-closed-tab-dictation-lifetime.test.tsx b/mobile/src/session/mobile-closed-tab-dictation-lifetime.test.tsx deleted file mode 100644 index f5c97c0cedb..00000000000 --- a/mobile/src/session/mobile-closed-tab-dictation-lifetime.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, expect, it, vi } from 'vitest' -import { MobileNativeChatComposer } from './native-chat/composer/MobileNativeChatComposer' -import { captureMobileNativeChatTextDestination } from './native-chat/composer/mobile-native-chat-draft-inputs' -import { - closeMobileNativeChatTabDraft, - readMobileNativeChatDraftEditGeneration, - resetMobileNativeChatDraftStoreForTests, - setMobileNativeChatDraftText -} from './native-chat/composer/mobile-native-chat-draft-store' -import { findByHostType } from '../test-support/react-test-renderer-host-nodes' - -vi.mock('../components/MobileSwitch', () => ({ MobileSwitch: 'Switch' })) -vi.mock('./native-chat/composer/MobileNativeChatContextUsageRing', () => ({ - useMobileNativeChatContextRing: () => ({ barProps: {}, ring: null }) -})) -// BottomDrawer also carries the option pager, which loads Reanimated at import. -vi.mock('react-native-reanimated', async () => - (await import('../test-support/reanimated-pager-stub')).reanimatedPagerStub() -) -vi.mock('react-native', async () => { - const React = await import('react') - return { - ...(await import('../test-support/react-native-motion-stub')).reactNativeMotionStub(), - ActivityIndicator: 'ActivityIndicator', - Image: 'Image', - Keyboard: { dismiss: vi.fn() }, - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - ScrollView: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('ScrollView', props, children), - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - Text: 'Text', - TextInput: 'TextInput', - View: 'View' - } -}) -vi.mock('lucide-react-native', () => ({ - ArrowUp: 'ArrowUp', - ListEnd: 'ListEnd', - CornerDownRight: 'CornerDownRight', - Check: 'Check', - ChevronDown: 'ChevronDown', - ChevronLeft: 'ChevronLeft', - ChevronRight: 'ChevronRight', - FileText: 'FileText', - Mic: 'Mic', - Package: 'Package', - Plus: 'Plus', - ShieldAlert: 'ShieldAlert', - Square: 'Square', - Terminal: 'Terminal', - X: 'X' -})) -vi.mock('../components/bottom-drawer/mounted-bottom-drawer', () => ({ - MountedBottomDrawer: 'BottomDrawer' -})) -vi.mock('../components/bottom-drawer/bottom-drawer-modal-host-scope', () => ({ - useBottomDrawerModalHost: () => null -})) - -let renderer: ReactTestRenderer | undefined -afterEach(async () => { - await act(async () => renderer?.unmount()) - renderer = undefined - resetMobileNativeChatDraftStoreForTests() -}) - -it.each([false, true])( - 'rejects captured dictation after tab retirement with custom insert=%s', - async (customInsert) => { - const scope = `retired-dictation-${customInsert}` - const insertDictation = vi.fn(() => true) - setMobileNativeChatDraftText(scope, 'old text') - await act(async () => { - renderer = create( - createElement(MobileNativeChatComposer, { - value: 'old text', - draftScopeKey: scope, - insertDictation: customInsert ? insertDictation : undefined, - onChangeText: vi.fn(), - onSend: async () => true, - sendSurfaceId: scope, - getSendCompletionGeneration: () => 0, - getComposerEditGeneration: () => 0 - }) - ) - }) - act(() => findByHostType(renderer!.root, 'TextInput').props.onFocus()) - const insert = captureMobileNativeChatTextDestination()?.insert - if (!insert) { - throw new Error('composer did not register dictation') - } - act(() => { - insert('live dictation') - }) - if (customInsert) { - expect(insertDictation).toHaveBeenCalledOnce() - } else { - expect(readMobileNativeChatDraftEditGeneration(scope)).toBe(2) - } - insertDictation.mockClear() - await act(async () => closeMobileNativeChatTabDraft(scope)) - expect(insert('late dictation')).toBe(false) - expect(insertDictation).not.toHaveBeenCalled() - expect(readMobileNativeChatDraftEditGeneration(scope)).toBe(0) - } -) diff --git a/mobile/src/session/mobile-dictation-session-outcomes.test.tsx b/mobile/src/session/mobile-dictation-session-outcomes.test.tsx index cfbde9cb924..46d24d9a1d9 100644 --- a/mobile/src/session/mobile-dictation-session-outcomes.test.tsx +++ b/mobile/src/session/mobile-dictation-session-outcomes.test.tsx @@ -4,7 +4,7 @@ import { act } from 'react-test-renderer' import { createWithQueryClient } from '../test-support/query-client-test-renderer' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createFakeRpcClient, type FakeRpcClient } from '../mobile-web-shell/bridge-host-test-fakes' -import type * as NativeChatSendError from './native-chat/use-mobile-native-chat-send-error' +import type * as NativeChatSendError from './native-chat/composer/use-mobile-native-chat-send-error' import type * as NativeChatDraftStore from './native-chat/composer/mobile-native-chat-draft-store' type ChatView = { shown: boolean; scope: string | null } @@ -14,8 +14,6 @@ const seen = vi.hoisted(() => { return { toasts: new Array(), errorHaptics: 0, - setupSheetOpens: 0, - micPermissionRequests: 0, clipboard: new Array(), inputs: new Array(), chat, @@ -30,7 +28,7 @@ vi.mock('react-native', () => ({ Platform: { OS: 'ios' } })) // The computer's name only words announcements; the stored host profile pulls in secure storage. -vi.mock('../transport/hosts/use-host-profile', () => ({ useHostProfile: () => null })) +vi.mock('../host-catalog/use-host-profile', () => ({ useHostProfile: () => null })) vi.mock('@orca/expo-two-way-audio', () => ({ addExpoTwoWayAudioEventListener: (name: string, listener: (event: { data: string }) => void) => { seen.audioListeners.set(name, listener) @@ -38,7 +36,6 @@ vi.mock('@orca/expo-two-way-audio', () => ({ }, initialize: () => Promise.resolve(true), requestMicrophonePermissionsAsync: () => { - seen.micPermissionRequests += 1 return Promise.resolve({ granted: true }) }, tearDown: () => {}, @@ -69,7 +66,7 @@ vi.mock('../platform/clipboard', () => { }) // Stub off-path native chat dependencies to keep Expo out of this mic test. -vi.mock('./native-chat/use-mobile-native-chat-send-error', async (importOriginal) => ({ +vi.mock('./native-chat/composer/use-mobile-native-chat-send-error', async (importOriginal) => ({ ...(await importOriginal()), useMobileNativeChatSendError: () => ({ message: null, @@ -82,7 +79,7 @@ vi.mock('./native-chat/use-mobile-native-chat-send-error', async (importOriginal vi.mock('./native-chat/transcript/use-mobile-native-chat-readability', () => ({ useMobileNativeChatReadability: () => false })) -vi.mock('./native-chat/use-mobile-native-chat-input-lease', () => ({ +vi.mock('./native-chat/composer/use-mobile-native-chat-input-lease', () => ({ useMobileNativeChatInputLease: () => ({ ready: true, readyRef: { current: true }, @@ -159,11 +156,7 @@ function mount( activeSessionTab: { id: 'tab1', type: 'terminal', terminal: 't1', title: 'Claude Code' }, diffComments: [], diffCommentsRef: useRef([]), - setShowDictationSetup: (next: boolean) => { - if (next) { - seen.setupSheetOpens += 1 - } - }, + setShowDictationSetup: () => {}, setDictationMode: () => {}, dictationEnabled: true, setDictationEnabled: () => {}, @@ -259,8 +252,6 @@ async function startRecording( beforeEach(() => { seen.toasts.length = 0 seen.errorHaptics = 0 - seen.setupSheetOpens = 0 - seen.micPermissionRequests = 0 seen.clipboard.length = 0 seen.inputs.length = 0 seen.chat = { shown: false, scope: null } @@ -268,70 +259,6 @@ beforeEach(() => { seen.audioListeners.clear() }) -describe('a mic tap after the host reported dictation is not set up', () => { - it('opens setup without asking for the microphone, then lets the host judge the tap after the sheet closes', async () => { - const rpc = createFakeRpcClient() - const mounted = mount(rpc) - await act(async () => { - const read = mounted.current().refreshDictationMode() - for (const request of rpc.requests.splice(0)) { - request.resolve({ - id: 'desktop', - ok: true, - result: { enabled: false, selectedModelId: '', dictationMode: 'toggle', models: [] } - }) - } - await read - }) - act(() => mounted.current().handleDictationToggle()) - expect(seen.setupSheetOpens).toBe(1) - expect(seen.micPermissionRequests).toBe(0) - expect(rpc.requests).toEqual([]) - - // Closing the sheet hands the next tap to the host until a fresh read lands. - act(() => mounted.current().closeDictationSetup()) - act(() => mounted.current().handleDictationToggle()) - await settle(rpc, '') - expect(seen.setupSheetOpens).toBe(1) - expect(seen.micPermissionRequests).toBe(1) - }) -}) - -describe('an audio interruption while recording', () => { - it('finishes the dictation and keeps what was said', async () => { - const rpc = createFakeRpcClient() - await startRecording(rpc) - act(() => seen.audioListeners.get('onAudioInterruption')?.({ data: 'began' })) - const methods = await settle(rpc, 'call me back') - expect(methods).toContain('speech.dictation.finish') - expect(methods).not.toContain('speech.dictation.cancel') - expect(seen.inputs).toEqual(['call me back']) - expect(seen.toasts).toEqual(['Dictation inserted']) - }) -}) - -describe('leaving the session while recording', () => { - it('stops and inserts what was said instead of leaving, and lets the next leave go', async () => { - const rpc = createFakeRpcClient() - const mounted = await startRecording(rpc) - const held: boolean[] = [] - act(() => { - held.push(mounted.current().dictationLeaveHold.hold()) - }) - // Back is released while transcribing, so a swipe can leave too. - expect(mounted.current().dictationLeaveHold.holdsLeave).toBe(false) - act(() => { - held.push(mounted.current().dictationLeaveHold.hold()) - }) - expect(held).toEqual([true, false]) - const methods = await settle(rpc, 'one more thing') - expect(methods.filter((method) => method === 'speech.dictation.finish')).toHaveLength(1) - expect(methods).not.toContain('speech.dictation.cancel') - expect(seen.inputs).toEqual(['one more thing']) - expect(seen.toasts).toEqual(['Dictation inserted']) - }) -}) - describe('a mic tap while the transcript is processing', () => { it('keeps the transcript that comes back', async () => { const rpc = createFakeRpcClient() @@ -367,19 +294,3 @@ describe('a dictation that ends after switching between chat and terminal', () = expect(seen.inputs).toEqual([]) }) }) - -describe('a dictation whose session screen was left', () => { - it('keeps recording, then puts its words in the chat it started in with a toast that opens it', async () => { - seen.chat = { shown: true, scope: 'chat-a' } - const rpc = createFakeRpcClient() - const mounted = await startRecording(rpc) - mounted.leave() - expect(mounted.host().getDictation().isRecording).toBe(true) - expect(mounted.host().pill?.away).toBe(true) - act(() => mounted.host().stop()) - await settle(rpc, 'ship it') - expect(seen.chatWrites).toEqual([['chat-a', 'ship it']]) - expect(mounted.host().pill?.toast?.message).toBe('Inserted in Claude Code') - expect(mounted.host().pill?.toast?.open).toBeTypeOf('function') - }) -}) diff --git a/mobile/src/session/mobile-native-chat-picker-scaling.test.ts b/mobile/src/session/mobile-native-chat-picker-scaling.test.ts deleted file mode 100644 index 8a3adb1e363..00000000000 --- a/mobile/src/session/mobile-native-chat-picker-scaling.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { afterEach, expect, it, vi } from 'vitest' -import capture from '../../../src/shared/__fixtures__/omp-native-commands.json' -import * as catalog from '../../../src/shared/native-chat/input/native-chat-slash-commands' -import type { NativeChatPickerItem } from '../../../src/shared/native-chat/input/native-chat-picker-items' -import { deriveComposerAutocomplete } from '../../../src/shared/native-chat/input/native-chat-composer-autocomplete' -import { - mobileComposerCatalog, - boundedMenuItems -} from './native-chat/composer/mobile-native-chat-composer-menu' - -afterEach(() => vi.restoreAllMocks()) - -it('bounds native OMP picker name reads at N/10N while preserving first duplicate descriptions', () => { - const reportedSkills = catalog.sessionReportedSkills - let visits = 0 - vi.spyOn(catalog, 'sessionReportedSkills').mockImplementation((...args) => - reportedSkills(...args).map((skill) => ({ - ...skill, - get name() { - visits += 1 - return skill.name - } - })) - ) - const native = capture.enabled.availableCommands.find((row) => row.name === 'skill:plan')! - for (const surface of ['desktop', 'mobile'] as const) { - const counts = [10, 100].map((size) => { - const reported = [ - ...Array.from({ length: size }, (_, i) => ({ - name: `${native.name}-${i}`, - kind: 'command' as const, - description: i === 0 ? undefined : native.description - })), - { name: `${native.name}-0`, kind: 'command' as const, description: 'Later duplicate' }, - { name: `${native.name}-1`, kind: 'command' as const, description: 'Later duplicate' } - ] - visits = 0 - let rows: NativeChatPickerItem[] - if (surface === 'desktop') { - const picker = deriveComposerAutocomplete( - '/plan', - 5, - catalog.sessionSlashCommandSuggestions('omp', reported), - [], - null, - { status: 'ready', skills: [] }, - null, - catalog.sessionReportedSkills(reported, 'omp') - ) - expect(picker.mode).toBe('slash') - if (picker.mode !== 'slash') { - throw new Error('Missing native picker') - } - rows = picker.items - } else { - const menu = mobileComposerCatalog({ - agent: 'omp', - structured: { reported, conversationCommands: [] } - }) - const picker = deriveComposerAutocomplete( - '/plan', - 5, - menu.commands, - menu.discovery.skills, - menu.profile, - menu.discovery, - null, - menu.sessionSkills - ) - expect(picker.mode).toBe('slash') - rows = picker.mode === 'slash' ? boundedMenuItems(picker.items) : [] - } - const skills = rows.filter((row) => row.kind === 'skill') - expect(skills).toHaveLength(Math.min(size, surface === 'desktop' ? 50 : 10)) - expect(new Set(skills.map((row) => row.token)).size).toBe(skills.length) - expect(skills.find((row) => row.token === '/skill:plan-0')?.description).toBeNull() - expect(skills.find((row) => row.token === '/skill:plan-1')?.description).toBe( - native.description - ) - expect(visits).toBeGreaterThanOrEqual(reported.length) - expect(visits).toBeLessThanOrEqual(10 * reported.length) - return visits - }) - expect(counts[1]).toBeLessThanOrEqual(12 * counts[0]!) - } -}) diff --git a/mobile/src/session/mobile-queued-edit-hook.test-fixture.tsx b/mobile/src/session/mobile-queued-edit-hook.test-fixture.tsx deleted file mode 100644 index fdf2e5e21d0..00000000000 --- a/mobile/src/session/mobile-queued-edit-hook.test-fixture.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import { QueuedMessageUpdateParams } from '../../../src/shared/rpc-contract/structured-agent-session-queued-edit-params' -import { useMobileNativeChatComposerMenu } from './native-chat/composer/use-mobile-native-chat-composer-menu' -import { - resetMobileNativeChatDraftStoreForTests, - useMobileNativeChatDraft -} from './native-chat/composer/mobile-native-chat-draft-store' -import { - updateMobileNativeChatDraftInputs, - resetMobileNativeChatDraftInputsForTests -} from './native-chat/composer/mobile-native-chat-draft-inputs' -import { createElement, type ReactNode } from 'react' -import { act, type ReactTestRenderer } from 'react-test-renderer' -import { createWithQueryClient } from '../test-support/query-client-test-renderer' -import { afterEach, expect, vi } from 'vitest' -import type { - AgentSessionQueuedMessage, - AgentSessionQueuedMessageEditHoldResult -} from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import { agentSessionSendBodyFingerprint } from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' -import { queuedMessageWithEdit } from '../../../src/shared/queued-message-edit' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { RpcResponse } from '../transport/rpc/rpc-wire-types' -import { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session' -import { - CAPABLE, - SESSION_ID, - fieldsOf, - mutationOk, - ok, - snapshotEvent -} from './native-chat/agent-session/use-mobile-structured-agent-session-queued.test-fixture' - -export function withText(body: AgentSessionQueuedMessage['body'], text: string) { - const edited = queuedMessageWithEdit(body, { text }) - if (!edited.ok) { - throw new Error(edited.reason) - } - return edited.body -} - -export function editHoldReply(params: unknown): RpcResponse { - const result: AgentSessionQueuedMessageEditHoldResult = - fieldsOf(params).action === 'release' - ? { status: 'released' } - : { status: 'held', fingerprint: 'fp', leaseDurationMs: 120_000, remainingMs: 120_000 } - return ok(result) -} - -export function readOrHoldReply(method: string, params: unknown): RpcResponse { - return method === 'agentSession.queuedMessageEditHold' - ? editHoldReply(params) - : ok({ models: [], current: {} }) -} - -vi.mock('react-native', async () => ({ - ...(await import('../test-support/react-native-motion-stub')).reactNativeMotionStub(), - Text: 'Text', - View: 'View', - Pressable: 'Pressable', - ScrollView: 'ScrollView', - TextInput: 'TextInput', - ActivityIndicator: 'ActivityIndicator', - Image: 'Image', - Keyboard: { dismiss: vi.fn() }, - Platform: { OS: 'ios' }, - useWindowDimensions: () => ({ fontScale: 1 }), - StyleSheet: { create: (styles: unknown) => styles } -})) -vi.mock('lucide-react-native', () => ({ - FileText: 'FileText', - Package: 'Package', - Terminal: 'Terminal', - ArrowUp: 'ArrowUp', - AtSign: 'AtSign', - AlertCircle: 'AlertCircle', - Image: 'Image', - Paperclip: 'Paperclip', - Check: 'Check', - ChevronDown: 'ChevronDown', - ChevronLeft: 'ChevronLeft', - ChevronRight: 'ChevronRight', - CornerDownRight: 'CornerDownRight', - ListEnd: 'ListEnd', - MoreHorizontal: 'MoreHorizontal', - Mic: 'Mic', - Pencil: 'Pencil', - Play: 'Play', - Plus: 'Plus', - Send: 'Send', - ShieldAlert: 'ShieldAlert', - Square: 'Square', - Trash2: 'Trash2', - X: 'X' -})) -vi.mock('@react-native-async-storage/async-storage', () => ({ - default: { getItem: async () => null, setItem: async () => {}, removeItem: async () => {} } -})) - -export function mobileQueuedEditHarness() { - const state: { - renderer?: ReactTestRenderer - bottomText: string - hook?: ReturnType - menu?: ReturnType - listener?: (data: unknown) => void - } = { bottomText: '' } - const append = vi.fn(() => true) - const error = vi.fn() - const notice = vi.fn() - const resolved = vi.fn() - const sendRequest = vi.fn() - const client: RpcClient = { - sendRequest, - subscribe: (_method, _params, onData) => { - if (_method === 'agentSession.subscribe') { - state.listener = onData - } - return () => {} - }, - updateTerminalSubscriptionViewport: () => {}, - getState: () => 'connected', - getReconnectAttempt: () => 0, - getLastConnectedAt: () => null, - onStateChange: () => () => {}, - notifyForeground: () => {}, - close: () => {} - } - function Harness({ - sessionId = SESSION_ID, - editSupport = true, - enabled = true, - connected = true, - transport = client, - composerScopeKey, - imagesSupport = false, - skillsSupport = false, - agent = 'codex', - renderContent - }: { - sessionId?: string - editSupport?: boolean | null - enabled?: boolean - connected?: boolean - transport?: RpcClient | null - composerScopeKey?: string - imagesSupport?: boolean - skillsSupport?: boolean - agent?: string - renderContent?: (hook: ReturnType) => ReactNode - }): ReactNode { - state.bottomText = useMobileNativeChatDraft('original-bottom') - state.hook = useMobileStructuredAgentSession({ - client: transport, - pairedHostClient: transport, - sessionId, - sourceIdentity: 'host-a', - enabled, - connected, - agent, - hostSupport: - editSupport === null - ? null - : { - ...CAPABLE, - sendSkills: true, - queuedMessages: false, - queuedMessageImageEdit: imagesSupport, - queuedMessageSkillsEdit: skillsSupport, - queuedMessageEdit: editSupport - }, - composerScopeKey, - appendComposerText: append, - onSendError: error, - onQueuedEditNotice: notice, - onActionResolved: resolved - }) - state.menu = useMobileNativeChatComposerMenu({ - value: state.hook.queued.editor?.text ?? '', - onChangeText: (text) => state.hook?.queued.editor?.change(text), - agent, - structuredCatalog: { - reported: [{ name: 'review', kind: 'skill', path: '/B/SKILL.md' }], - conversationCommands: [] - }, - filePaths: [], - focusField: () => {}, - editing: true, - // Mirrors the real composer: the menu hands over its row; the draft keeps name and path. - onPickSkill: ({ token, path }) => { - const key = state.hook?.queued.editor?.scopeKey - if (key && path) { - updateMobileNativeChatDraftInputs(key, (before) => ({ - ...before, - skills: [{ name: token.slice(1), path }] - })) - } - } - }) - return renderContent?.(state.hook) ?? null - } - function requestedFingerprint(message: AgentSessionQueuedMessage) { - const params = sendRequest.mock.calls.findLast( - ([method]) => method === 'agentSession.queuedMessageUpdate' - )?.[1] - const fields = QueuedMessageUpdateParams.parse(params) - const edited = queuedMessageWithEdit(message.body, fields) - if (!edited.ok) { - throw new Error(edited.reason) - } - return agentSessionSendBodyFingerprint(SESSION_ID, edited.body) - } - async function mount( - message: AgentSessionQueuedMessage, - editSupport: boolean | null = true, - agent = 'codex', - sessionId = SESSION_ID - ) { - vi.clearAllMocks() - resetMobileNativeChatDraftInputsForTests() - resetMobileNativeChatDraftStoreForTests() - sendRequest.mockImplementation(async (method, params) => - method === 'agentSession.queuedMessageUpdate' - ? mutationOk({ - status: 'updated', - messageId: message.messageId, - fingerprint: requestedFingerprint(message) - }) - : readOrHoldReply(method, params) - ) - await act(async () => { - state.renderer = createWithQueryClient( - createElement(Harness, { editSupport, agent, sessionId }) - ) - }) - const snapshot = snapshotEvent({ queuedMessages: [message], runningTurn: true }) - act(() => state.listener?.({ ...snapshot, sessionId, page: { ...snapshot.page, sessionId } })) - if (editSupport !== null) { - await act(async () => { - expect(await state.hook!.queued.edit!(message.messageId)).toBe(true) - }) - } - } - afterEach(async () => { - await act(async () => state.renderer?.unmount()) - vi.useRealTimers() - vi.unstubAllGlobals() - }) - - return { - state, - Harness, - mount, - client, - sendRequest, - append, - error, - notice, - resolved, - requestedFingerprint - } -} diff --git a/mobile/src/session/mobile-session-appearance-startup.test.tsx b/mobile/src/session/mobile-session-appearance-startup.test.tsx deleted file mode 100644 index afa67b42404..00000000000 --- a/mobile/src/session/mobile-session-appearance-startup.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { MobileSessionRouteScreen } from './MobileSessionRouteScreen' - -// Mocked react-native components are string tags that ReactTestInstance's ElementType omits. -function hostType(node: ReactTestInstance): unknown { - return node.type -} - -const state = vi.hoisted(() => ({ - terminalPreferencesReady: false, - accessoryPreferencesReady: false, - nativeChatController: { sessionViewReady: false } -})) -const mounts = vi.hoisted(() => ({ count: 0 })) -vi.mock('react-native', () => ({ - ActivityIndicator: 'ActivityIndicator', - Text: 'Text', - View: 'View' -})) -vi.mock('./mobile-session-styles', () => ({ styles: {} })) -vi.mock('./use-mobile-session-controller', () => ({ useMobileSessionController: () => state })) -vi.mock('./MobileSessionSurface', async () => { - const react = await import('react') - return { - MobileSessionSurface: () => { - react.useEffect(() => { - mounts.count += 1 - }, []) - return react.createElement('ReadySession') - } - } -}) - -let renderer: ReactTestRenderer | null = null -afterEach(() => { - act(() => renderer?.unmount()) - renderer = null -}) - -describe('first visible session appearance', () => { - it.each(['terminal', 'accessory', 'chat'] as const)( - 'waits for the saved %s setting before mounting the session', - (waiting) => { - state.terminalPreferencesReady = waiting !== 'terminal' - state.accessoryPreferencesReady = waiting !== 'accessory' - state.nativeChatController.sessionViewReady = waiting !== 'chat' - mounts.count = 0 - const render = () => - act(() => { - if (renderer) { - renderer.update(createElement(MobileSessionRouteScreen)) - } else { - renderer = create(createElement(MobileSessionRouteScreen)) - } - }) - const expectPreparing = () => { - expect(renderer?.root.findAll((node) => hostType(node) === 'ReadySession')).toHaveLength(0) - expect( - renderer?.root.findAll((node) => hostType(node) === 'ActivityIndicator') - ).toHaveLength(1) - expect(mounts.count).toBe(0) - } - render() - expectPreparing() - state.terminalPreferencesReady = true - state.accessoryPreferencesReady = true - state.nativeChatController.sessionViewReady = true - render() - expect(renderer?.root.findAll((node) => hostType(node) === 'ReadySession')).toHaveLength(1) - expect(mounts.count).toBe(1) - render() - expect(mounts.count).toBe(1) - } - ) -}) diff --git a/mobile/src/session/mobile-session-frame-styles.ts b/mobile/src/session/mobile-session-frame-styles.ts index 524a265cc29..352a639e607 100644 --- a/mobile/src/session/mobile-session-frame-styles.ts +++ b/mobile/src/session/mobile-session-frame-styles.ts @@ -58,108 +58,6 @@ export const mobileSessionFrameStyles = themedStyleSheet(() => ({ color: colors.textSecondary, fontSize: typography.metaSize }, - tabBar: { - flexDirection: 'row', - alignItems: 'center', - borderTopWidth: 1, - borderTopColor: colors.borderSubtle - }, - // Cancels the top bar's vertical padding so tab borders run the bar's full height. - tabBarInTopBar: { - flex: 1, - minWidth: 0, - flexDirection: 'row', - alignItems: 'center', - alignSelf: 'stretch', - marginVertical: -spacing.xs, - marginLeft: spacing.md, - borderLeftWidth: 1, - borderLeftColor: colors.borderSubtle - }, - tabScrollFrame: { - flex: 1, - minWidth: 0, - flexDirection: 'row', - alignItems: 'center', - alignSelf: 'stretch' - }, - tabEdgeHint: { - position: 'absolute', - top: 0, - bottom: 0, - justifyContent: 'center', - paddingHorizontal: spacing.xs, - backgroundColor: colors.bgPanel - }, - tabEdgeHintLeft: { - left: 0, - borderRightWidth: 1, - borderRightColor: colors.borderSubtle - }, - tabEdgeHintRight: { - right: 0, - borderLeftWidth: 1, - borderLeftColor: colors.borderSubtle - }, - // Opaque like the chrome behind it: see the chat page scroll (#36553). - tabScroll: { - flex: 1, - maxHeight: 44, - backgroundColor: colors.bgPanel - }, - tabScrollInTopBar: { - alignSelf: 'stretch', - maxHeight: 44 - }, - tabContent: { - paddingLeft: spacing.md, - paddingRight: spacing.sm - }, - tabContentInTopBar: { - paddingLeft: 0 - }, - tab: { - minWidth: 44, - maxWidth: 128, - minHeight: 44, - alignItems: 'center', - justifyContent: 'center', - paddingHorizontal: spacing.sm, - paddingVertical: spacing.sm, - borderBottomWidth: 2, - borderBottomColor: 'transparent', - borderRightWidth: 1, - borderRightColor: colors.borderSubtle - }, - tabFirst: { - borderLeftWidth: 1, - borderLeftColor: colors.borderSubtle - }, - tabActive: { - borderBottomColor: colors.textSecondary, - backgroundColor: colors.bgRaised - }, - tabPressed: { - backgroundColor: colors.translucentFill - }, - tabLabelRow: { - maxWidth: '100%', - flexDirection: 'row', - alignItems: 'center', - gap: spacing.xs - }, - // Prevent web SVG shrink before the title ellipsizes. - tabIcon: { - flexShrink: 0 - }, - tabText: { - flexShrink: 1, - color: colors.textSecondary, - fontSize: 13 - }, - tabTextActive: { - color: colors.textPrimary - }, newTerminalButtonPressed: { backgroundColor: colors.bgRaised }, diff --git a/mobile/src/session/mobile-session-status-result.ts b/mobile/src/session/mobile-session-status-result.ts deleted file mode 100644 index 722e62ab480..00000000000 --- a/mobile/src/session/mobile-session-status-result.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type RuntimeStatusResult = { - capabilities?: string[] -} diff --git a/mobile/src/session/mobile-structured-agent-prompts-free-text.test.ts b/mobile/src/session/mobile-structured-agent-prompts-free-text.test.ts deleted file mode 100644 index 950672cc3c7..00000000000 --- a/mobile/src/session/mobile-structured-agent-prompts-free-text.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { AgentJournalFreeTextInput } from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import { formatQuestionFreeTextAnswer } from './native-chat/input/mobile-native-chat-question' -import { - projectStructuredQuestion, - structuredQuestionResponseTarget, - type StructuredQuestionItem -} from './mobile-structured-agent-prompts' - -function prompt(freeTextInput?: AgentJournalFreeTextInput): StructuredQuestionItem { - return { - itemId: 'item-1', - revision: 3, - sequence: 1, - observedAt: 1, - body: { - kind: 'question', - question: 'Edit the draft', - options: [], - freeTextQuestionId: 'q1', - ...(freeTextInput ? { freeTextInput } : {}), - resolution: { state: 'pending', selectedOptionId: null, resolvedBy: null, resolvedAt: null } - } - } -} - -describe('mobile structured free-text questions', () => { - it('accepts empty and whitespace text only for the matching allowed prompt', () => { - const current = prompt({ allowEmpty: true }) - const projected = projectStructuredQuestion(current)! - for (const answer of ['', ' \n ']) { - const response = formatQuestionFreeTextAnswer(projected, answer) - expect(structuredQuestionResponseTarget(response, current)).toEqual({ - itemId: 'item-1', - expectedRevision: 3, - answer: { questionId: 'q1', optionIds: [], other: answer } - }) - expect(structuredQuestionResponseTarget(response, prompt())).toBeNull() - } - expect( - structuredQuestionResponseTarget(formatQuestionFreeTextAnswer(projected, ''), { - ...current, - revision: 4 - }) - ).toBeNull() - }) - - it('keeps legacy trimming for ordinary free-text answers', () => { - const current = prompt() - const projected = projectStructuredQuestion(current)! - expect( - structuredQuestionResponseTarget( - formatQuestionFreeTextAnswer(projected, ' draft '), - current - ) - ).toMatchObject({ answer: { other: 'draft' } }) - expect(structuredQuestionResponseTarget('', current)).toBeNull() - }) -}) diff --git a/mobile/src/session/mobile-structured-chat-fork.ts b/mobile/src/session/mobile-structured-chat-fork.ts index ff26e8eb7c7..009bde60332 100644 --- a/mobile/src/session/mobile-structured-chat-fork.ts +++ b/mobile/src/session/mobile-structured-chat-fork.ts @@ -7,7 +7,7 @@ import type { StructuredAgentSessionForkSource } from '../../../src/shared/agent-session/structured-agent-session-create' import { AGENT_SESSION_CREATE_TAB_PLACEMENT_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session/agent-session-create-capabilities' -import { createMobileStructuredAgentSession } from './mobile-structured-agent-session-launch' +import { createMobileStructuredAgentSession } from './native-chat/agent-session/mobile-structured-agent-session-launch' import { launchedSelection, withLaunchReply, diff --git a/mobile/src/session/native-chat/MobileNativeChatDiffCard.test.tsx b/mobile/src/session/native-chat/MobileNativeChatDiffCard.test.tsx deleted file mode 100644 index 42da6626f1e..00000000000 --- a/mobile/src/session/native-chat/MobileNativeChatDiffCard.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import './transcript/mobile-agent-message-renderer.test-fixture' -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, expect, it } from 'vitest' -import { findAllByHostType } from '../../test-support/react-test-renderer-host-nodes' -import { Transcript, textIn } from './transcript/mobile-native-chat-turn-diff.test-fixture' - -let renderer: ReactTestRenderer | null = null -afterEach(() => { - act(() => renderer?.unmount()) - renderer = null -}) - -it('shows finished-turn files outside folded work and opens a patch with one tap', () => { - act(() => { - renderer = create(createElement(Transcript, { working: true })) - }) - expect(textIn(renderer!.root)).toContain('greeting.ts') - act(() => { - renderer!.update(createElement(Transcript)) - }) - expect(textIn(renderer!.root)).toContain('greeting.ts') - expect(textIn(renderer!.root)).not.toContain('diagnostic-output') - expect(textIn(renderer!.root)).not.toContain('helloNew()') - const file = findAllByHostType(renderer!.root, 'Pressable').find( - (node) => node.props.accessibilityLabel === '/repo/greeting.ts' - )! - act(() => file.props.onPress()) - expect(textIn(renderer!.root)).toContain('helloNew()') - expect(textIn(renderer!.root)).not.toContain('statusNew()') - act(() => renderer!.unmount()) - act(() => { - renderer = create(createElement(Transcript)) - }) - expect(textIn(renderer!.root)).toContain('status.ts') -}) diff --git a/mobile/src/session/native-chat/MobileNativeChatOverlay.tsx b/mobile/src/session/native-chat/MobileNativeChatOverlay.tsx index 649307a056f..398c127dd31 100644 --- a/mobile/src/session/native-chat/MobileNativeChatOverlay.tsx +++ b/mobile/src/session/native-chat/MobileNativeChatOverlay.tsx @@ -1,4 +1,4 @@ -import { MobileQueuedMessageComposerContext } from '../mobile-queued-message-composer-context' +import { MobileQueuedMessageComposerContext } from './input/mobile-queued-message-composer-context' import { Activity, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import { StyleSheet, View } from 'react-native' import { MobileNativeChatView, type MobileNativeChatInputLockReason } from './MobileNativeChatView' @@ -6,11 +6,11 @@ import { useMobileNativeChatProjection } from './transcript/use-mobile-native-ch import { nativeChatDockedTaskList } from '../../../../src/shared/native-chat/input/native-chat-task-list-state' import type { MobileNativeChatImageAttachments } from './composer/use-mobile-native-chat-image-attachments' import type { MobileNativeChatController } from './mobile-native-chat-controller-contract' -import type { MobileNativeChatStructuredComposer } from './composer/MobileNativeChatComposer' +import type { MobileNativeChatStructuredComposer } from './composer/mobile-native-chat-structured-composer' import { useMobileNativeChatComposerTray } from './composer/use-mobile-native-chat-composer-tray' import { useMobileNativeChatVisualRenderer } from './visual/MobileNativeChatVisual' import { MobileNativeChatVisualContext } from './visual/mobile-native-chat-visual-context' -import { MobileNativeChatRootItemSourceContext } from './mobile-native-chat-item-read' +import { MobileNativeChatRootItemSourceContext } from './transcript/mobile-native-chat-item-read' import { useMobileNativeChatMessageDetail } from './transcript/use-mobile-native-chat-message-detail' import { MobileNativeChatReplyRevealsContext, @@ -19,7 +19,7 @@ import { import { MobileNativeChatRowOverlaysHost } from './transcript/MobileNativeChatRowOverlaysHost' import { nativeChatAskRequiresTerminal } from '../../../../src/shared/native-chat/native-chat-agent-support' import { tuiAgentDisplayName } from '../../../../src/shared/agent-launch/catalog/tui-agent-display-names' -import { MobileNativeChatNoticeContext } from '../mobile-native-chat-notice-context' +import { MobileNativeChatNoticeContext } from './transcript/mobile-native-chat-notice-context' import type { MobileNativeChatImageSource } from './visual/mobile-native-chat-image-preview' import type { MobileNativeChatHostOutage } from './mobile-native-chat-host-outage' import { useMobileNativeChatMarkdownLinks } from './use-mobile-native-chat-markdown-links' diff --git a/mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts b/mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts index 6e7ba0d0431..d2438307f36 100644 --- a/mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts +++ b/mobile/src/session/native-chat/MobileNativeChatView.failure-banner.test.ts @@ -1,14 +1,9 @@ import { describe, expect, it, vi } from 'vitest' import { installChatViewHooks, - assistantTurn, - banners, bannerText, - hostType, - listIds, pressSend, render, - root, update } from './MobileNativeChatView.harness.test-fixture' @@ -51,35 +46,6 @@ vi.mock('./composer/MobileNativeChatComposer', async () => { describe('MobileNativeChatView: send and read failure banners', () => { installChatViewHooks() - it('leaves only the words of a read that failed for good, over a loaded transcript', async () => { - const loaded = [assistantTurn('m1', 'earlier reply')] - const words = 'This chat was saved by a newer Orca. Update Orca to open it.' - const shown = (text: string) => - root().findAll((node) => hostType(node) === 'Text' && node.props.children === text) - const composers = () => root().findAll((node) => hostType(node) === 'Composer') - const lists = () => root().findAll((node) => hostType(node) === 'FlatList') - const failure = { - messages: loaded, - folded: loaded, - status: 'error' as const, - error: words, - canStop: true, - permission: { title: 'Approve?', options: [{ label: 'Allow', send: '1' }] }, - sendErrorMessage: 'Message unconfirmed — check chat before retrying' - } - await render({ ...failure, readFailedFinally: true }) - expect(shown(words)).toHaveLength(1) - expect(composers()).toHaveLength(0) - expect(lists()).toHaveLength(0) - expect(banners()).toHaveLength(0) - expect(root().findAllByProps({ accessibilityLabel: 'Stop the agent' })).toHaveLength(0) - - await update({ ...failure, error: "Orca couldn't open this chat's history right now." }) - expect(listIds()).toEqual(['m1']) - expect(composers()).toHaveLength(1) - expect(banners()).toHaveLength(1) - }) - it('retires the previous banner before sending and preserves the new delivery notice', async () => { const onClearSendError = vi.fn() await render({ @@ -99,6 +65,6 @@ describe('MobileNativeChatView: send and read failure banners', () => { }) }) -vi.mock('../MobileQueuedMessageComposer', () => ({ +vi.mock('./input/MobileQueuedMessageComposer', () => ({ MobileQueuedMessageComposer: 'QueuedMessageComposer' })) diff --git a/mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts b/mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts deleted file mode 100644 index e905f481014..00000000000 --- a/mobile/src/session/native-chat/MobileNativeChatView.prompt-presentation.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import './MobileNativeChatView.harness.test-fixture' -import { createElement, type ComponentProps } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { Pressable } from 'react-native' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { MobileNativeChatView } from './MobileNativeChatView' -import { useMobileNativeChatPromptPresentation } from './input/use-mobile-native-chat-prompt-presentation' -import type { MobileChatPermission } from './input/mobile-native-chat-permission' -import { setMobileNativeChatDraftText } from './composer/mobile-native-chat-draft-store' -vi.mock('react-native', async () => { - const React = await import('react') - return { - ...(await import('../../test-support/react-native-motion-stub')).reactNativeMotionStub(), - ActivityIndicator: 'ActivityIndicator', - Image: 'Image', - Platform: { OS: 'ios' }, - Keyboard: { dismiss: vi.fn() }, - FlatList: React.forwardRef((props, ref) => { - React.useImperativeHandle(ref, () => ({ scrollToEnd: vi.fn(), scrollToOffset: vi.fn() }), []) - return React.createElement('FlatList', props) - }), - Pressable: 'Pressable', - ScrollView: 'ScrollView', - TextInput: 'TextInput', - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - Text: 'Text', - View: 'View' - } -}) -vi.mock('react-native-safe-area-context', () => ({ - useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }) -})) -vi.mock('react-native-gesture-handler', () => { - type GestureChain = { - runOnJS: () => GestureChain - onStart: () => GestureChain - onUpdate: () => GestureChain - } - const chain: GestureChain = { runOnJS: () => chain, onStart: () => chain, onUpdate: () => chain } - return { - Gesture: { Simultaneous: () => ({}), Native: () => ({}), Pinch: () => chain }, - GestureDetector: 'GestureDetector', - GestureHandlerRootView: 'GestureHandlerRootView' - } -}) -vi.mock('lucide-react-native', () => - Object.fromEntries( - [ - 'ArrowDown', - 'ArrowUp', - 'ListEnd', - 'CornerDownRight', - 'ChevronDown', - 'ChevronUp', - 'ChevronsDownUp', - 'ChevronsUpDown', - 'ShieldQuestion', - 'ImagePlus', - 'Package', - 'Plus', - 'Mic', - 'Square', - 'X' - ].map((x) => [x, x]) - ) -) -vi.mock('./transcript/MobileNativeChatMessage', () => ({ - MobileNativeChatMessage: 'ChatMessage' -})) -vi.mock('./transcript/MobileNativeChatLiveLine', () => ({ - MobileNativeChatLiveLine: 'LiveStatus' -})) -vi.mock('./input/MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' })) -vi.mock('./input/MobileNativeChatQuestion', () => ({ - MobileNativeChatQuestion: 'ChatQuestion' -})) -vi.mock('./input/MobileNativeChatSessionOptionPickers', () => ({ - MobileNativeChatSessionOptionPickers: 'SessionOptions' -})) -vi.mock('./composer/MobileNativeChatComposerMenu', () => ({ - MobileNativeChatComposerSuggestions: 'Suggestions' -})) -vi.mock('../../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' })) -vi.mock('../../components/MobileMarkdown', () => ({ MobileMarkdown: 'Markdown' })) -let tree: ReactTestRenderer | null = null -setMobileNativeChatDraftText('terminal-1', 'no, do X instead') -const props = { - messages: [], - folded: [], - status: 'ready' as const, - streaming: null, - pending: [], - onSend: vi.fn(async () => true), - sendSurfaceId: 'terminal-1', - getSendCompletionGeneration: () => 0, - getComposerEditGeneration: () => 0, - draftKey: 'terminal-1', - onComposerTextChange: vi.fn() -} -const permission: MobileChatPermission = { - title: 'Claude wants to use Bash', - options: [{ label: 'Deny', send: String.fromCharCode(27) }] -} -type Overrides = Partial> & { - waitStartedAt?: number - sessionKey?: string -} -function Harness(overrides: Overrides) { - const shown = useMobileNativeChatPromptPresentation({ - permission: overrides.permission ?? null, - waitStartedAt: overrides.waitStartedAt ?? 10, - scopeKey: 'tab-1', - sessionKey: overrides.sessionKey ?? 'session-1', - observing: true, - respondPermission: overrides.onRespondPermission ?? (async () => false) - }) - return createElement(MobileNativeChatView, { - ...props, - ...overrides, - permission: shown.permission, - promptKey: shown.occurrenceKey, - onRespondPermission: shown.respondPermission - }) -} -const render = async (overrides: Overrides) => { - await act(async () => { - const element = createElement(Harness, overrides) - if (tree) { - tree.update(element) - } else { - tree = create(element) - } - }) -} -const sendButton = () => tree!.root.findByProps({ accessibilityLabel: 'Send message' }) -const approvalAction = () => - tree!.root.findByProps({ testID: 'native-chat-approval-actions' }).findByType(Pressable) -afterEach(() => { - act(() => tree?.unmount()) - tree = null - vi.unstubAllGlobals() -}) -describe('terminal prompt presentation with the production view, card and composer', () => { - it('a late answer cannot reshow an answered replacement occurrence', async () => { - let finishOld: (accepted: boolean) => void = () => {} - const onRespondPermission = vi - .fn<(send: string) => Promise>() - .mockReturnValueOnce( - new Promise((resolve) => { - finishOld = resolve - }) - ) - .mockResolvedValueOnce(true) - await render({ permission, onRespondPermission }) - act(() => { - void approvalAction().props.onPress() - }) - await render({ permission, onRespondPermission, waitStartedAt: 20 }) - await act(async () => { - await approvalAction().props.onPress() - }) - expect(sendButton().props.disabled).toBe(false) - await act(async () => finishOld(true)) - expect(sendButton().props.disabled).toBe(false) - }) - - it('a late answer cannot dismiss a replacement session', async () => { - let finishOld: (accepted: boolean) => void = () => {} - const onRespondPermission = vi.fn( - () => - new Promise((resolve) => { - finishOld = resolve - }) - ) - await render({ permission, onRespondPermission }) - act(() => { - void approvalAction().props.onPress() - }) - await render({ permission, onRespondPermission, sessionKey: 'session-2' }) - await act(async () => finishOld(true)) - expect(sendButton().props.disabled).toBe(true) - expect(approvalAction().props.disabled).toBe(false) - }) -}) - -vi.mock('../MobileQueuedMessageComposer', () => ({ - MobileQueuedMessageComposer: 'QueuedMessageComposer' -})) diff --git a/mobile/src/session/native-chat/MobileNativeChatView.tsx b/mobile/src/session/native-chat/MobileNativeChatView.tsx index b3eead5e801..5e701c297d3 100644 --- a/mobile/src/session/native-chat/MobileNativeChatView.tsx +++ b/mobile/src/session/native-chat/MobileNativeChatView.tsx @@ -22,19 +22,19 @@ import { useMobileTranslation } from '../../i18n/use-mobile-translation' import { MobileNativeChatInputLockStatus } from './composer/MobileNativeChatInputLockStatus' import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' import { styles } from './mobile-native-chat-view-styles' -import { MobileNativeChatEmptyStateView } from '../MobileNativeChatEmptyStateView' -import { mobileNativeChatListFooter } from './mobile-native-chat-list-footer' -import { mobileNativeChatListHeader } from './mobile-native-chat-list-header' +import { MobileNativeChatEmptyStateView } from './transcript/MobileNativeChatEmptyStateView' +import { mobileNativeChatListFooter } from './transcript/mobile-native-chat-list-footer' +import { mobileNativeChatListHeader } from './transcript/mobile-native-chat-list-header' import { mobileNativeChatEmptyState, type MobileNativeChatPendingItem -} from './mobile-native-chat-render-data' +} from './transcript/mobile-native-chat-render-data' import { useMobileNativeChatTransientData } from './transcript/use-mobile-native-chat-transient-data' import { useMobileNativeChatRowKeys } from './transcript/mobile-native-chat-row-keys' import { useMobileNativeChatPinchGesture } from './transcript/use-mobile-native-chat-pinch-gesture' import { useMobileNativeChatTailFollow } from './transcript/use-mobile-native-chat-tail-follow' import { useMobileNativeChatTurnDisclosure } from './transcript/use-mobile-native-chat-turn-disclosure' -import { mobileNativeChatComposerPlaceholder } from './use-mobile-native-chat-input-lease' +import { mobileNativeChatComposerPlaceholder } from './composer/use-mobile-native-chat-input-lease' import { MobileNativeChatLiveLine } from './transcript/MobileNativeChatLiveLine' import { MobileNativeChatAwaitingInputRow } from './transcript/MobileNativeChatAwaitingInputRow' import type { NativeChatAwaitingInput } from '../../../../src/shared/native-chat/native-chat-terminal-turn' @@ -43,9 +43,9 @@ import type { PendingNativeChatImage } from './composer/mobile-native-chat-image import type { MobilePastedImages } from './composer/mobile-native-chat-composer-paste' import { MobileNativeChatDraftComposer, - type MobileNativeChatSendProps, - type MobileNativeChatStructuredComposer + type MobileNativeChatSendProps } from './composer/MobileNativeChatComposer' +import type { MobileNativeChatStructuredComposer } from './composer/mobile-native-chat-structured-composer' import { MobileNativeChatPromptCard } from './input/MobileNativeChatPromptCard' import type { MobileNativeChatMicProps } from './composer/MobileNativeChatComposerMic' import { useMobileNativeChatPromptInputOwner } from './input/use-mobile-native-chat-prompt-input-owner' @@ -54,11 +54,11 @@ import { type ComposerTrayProps } from './composer/use-mobile-native-chat-composer-tray' import type { MobileNativeChatSessionOptionPickersProps } from './input/MobileNativeChatSessionOptionPickers' -import { MobileNativeChatScrollRootsBoundary } from '../MobileNativeChatScrollRootsBoundary' +import { MobileNativeChatScrollRootsBoundary } from './transcript/MobileNativeChatScrollRootsBoundary' import { MobileNativeChatRowList } from './transcript/MobileNativeChatRowList' import { useMobileNativeChatRowRenderer } from './transcript/use-mobile-native-chat-row-renderer' -import type { MobileNativeChatStatus } from './use-mobile-native-chat-session' -import type { OlderPagePhase } from '../use-mobile-older-page-loader' +import type { MobileNativeChatStatus } from './mobile-native-chat-session-status' +import type { OlderPagePhase } from './transcript/use-mobile-older-page-loader' import { MobileNativeChatOpenFileContext } from './mobile-native-chat-open-file-context' import { MobileNativeChatLowerRegion } from './MobileNativeChatLowerRegion' import { MobileNativeChatOfflineQueue } from './input/MobileNativeChatOfflineQueue' diff --git a/mobile/src/session/native-chat/MobileNativeChatView.turn-status.test.ts b/mobile/src/session/native-chat/MobileNativeChatView.turn-status.test.ts deleted file mode 100644 index 6fa4e9c347b..00000000000 --- a/mobile/src/session/native-chat/MobileNativeChatView.turn-status.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import './MobileNativeChatView.harness.test-fixture' -import { createElement, type ReactNode } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import { MobileNativeChatView } from './MobileNativeChatView' - -const scrollToEnd = vi.hoisted(() => vi.fn()) -const scrollToOffset = vi.hoisted(() => vi.fn()) - -vi.mock('react-native', async () => { - const React = await import('react') - return { - ...(await import('../../test-support/react-native-motion-stub')).reactNativeMotionStub(), - ActivityIndicator: 'ActivityIndicator', - FlatList: React.forwardRef< - unknown, - { - data: NativeChatMessage[] - renderItem: (row: { item: NativeChatMessage; index: number }) => ReactNode - ListFooterComponent: ReactNode - } - >((props, ref) => { - React.useImperativeHandle(ref, () => ({ scrollToEnd, scrollToOffset }), []) - return React.createElement( - 'FlatList', - props, - props.data.map((item, index) => - React.createElement(React.Fragment, { key: item.id }, props.renderItem({ item, index })) - ), - props.ListFooterComponent - ) - }), - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - Text: 'Text', - View: 'View' - } -}) - -vi.mock('react-native-safe-area-context', () => ({ - useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }) -})) - -vi.mock('react-native-gesture-handler', () => { - const chain = { - runOnJS: () => chain, - onStart: () => chain, - onUpdate: () => chain - } - return { - Gesture: { Simultaneous: () => ({}), Native: () => ({}), Pinch: () => chain }, - GestureDetector: 'GestureDetector', - GestureHandlerRootView: 'GestureHandlerRootView' - } -}) - -vi.mock('lucide-react-native', () => ({ - ArrowDown: 'ArrowDown', - ChevronsDownUp: 'ChevronsDownUp', - ChevronsUpDown: 'ChevronsUpDown', - Square: 'Square' -})) - -vi.mock('./transcript/MobileNativeChatMessage', () => ({ - MobileNativeChatMessage: 'ChatMessage' -})) -vi.mock('./transcript/MobileNativeChatLiveLine', () => ({ - MobileNativeChatLiveLine: 'LiveStatus' -})) -vi.mock('./input/MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' })) -vi.mock('./input/MobileNativeChatPermission', () => ({ - MobileNativeChatPermission: 'ChatPermission' -})) -vi.mock('./input/MobileNativeChatQuestion', () => ({ - MobileNativeChatQuestion: 'ChatQuestion' -})) -vi.mock('../../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' })) - -vi.mock('./composer/MobileNativeChatComposer', async () => { - const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture') - return composerStub() -}) - -type Overrides = { - messages?: Parameters[0]['messages'] - folded?: Parameters[0]['folded'] - streaming?: string | null - sendErrorMessage?: string | null - onClearSendError?: () => void - inputLockReason?: 'disconnected' | 'waiting' | null - onSend?: (text: string) => Promise - pending?: Parameters[0]['pending'] - structuredActivityUi?: boolean - turnIndicator?: Parameters[0]['turnIndicator'] - agentWorking?: boolean - canStop?: boolean - ask?: Parameters[0]['ask'] - question?: Parameters[0]['question'] - permission?: Parameters[0]['permission'] - sendSurfaceId?: string - keyboardInset?: number - hasMore?: boolean - onLoadEarlier?: () => void -} - -function chatViewElement(overrides: Overrides): ReturnType { - return createElement(MobileNativeChatView, { - messages: [], - folded: [], - status: 'ready', - streaming: null, - onSend: vi.fn().mockResolvedValue(true), - sendSurfaceId: 'tab-a', - getSendCompletionGeneration: () => 0, - getComposerEditGeneration: () => 0, - pending: [], - draftKey: null, - onComposerTextChange: vi.fn(), - ...overrides - }) -} - -describe('MobileNativeChatView', () => { - let renderer: ReactTestRenderer | null = null - - beforeEach(() => { - vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => - setTimeout(() => callback(0), 0) - ) - vi.stubGlobal('cancelAnimationFrame', (handle: ReturnType) => - clearTimeout(handle) - ) - }) - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - scrollToEnd.mockReset() - scrollToOffset.mockReset() - vi.unstubAllGlobals() - }) - - async function render(overrides: Overrides = {}): Promise { - await act(async () => { - renderer = create(chatViewElement(overrides)) - }) - } - - async function update(overrides: Overrides = {}): Promise { - await act(async () => { - renderer?.update(chatViewElement(overrides)) - }) - } - - function renderedRow(id: string): ReturnType { - return createElement( - 'Row', - renderer!.root.find( - (node) => String(node.type) === 'ChatMessage' && node.props.message.id === id - ).props - ) - } - - describe('structured turn status wiring', () => { - const userTurn = (id: string, text: string): NativeChatMessage => ({ - id, - role: 'user', - blocks: [{ type: 'text', text }], - timestamp: 0, - source: 'transcript' - }) - - function rowProps(id: string): Record { - return (renderedRow(id) as { props: Record }).props - } - - it('does not carry a running turn clock across chat surfaces', async () => { - vi.useFakeTimers() - try { - vi.setSystemTime(1_000) - const firstTab = [userTurn('u1', 'first')] - await render({ - messages: firstTab, - folded: firstTab, - structuredActivityUi: true, - agentWorking: true, - sendSurfaceId: 'host\0worktree\0tab-a' - }) - expect(rowProps('u1').turnStatus).toMatchObject({ startedAt: 1_000 }) - - vi.setSystemTime(12_000) - const secondTab = [userTurn('u2', 'second')] - await update({ - messages: secondTab, - folded: secondTab, - structuredActivityUi: true, - agentWorking: true, - sendSurfaceId: 'host\0worktree\0tab-b' - }) - - expect(rowProps('u2').turnStatus).toMatchObject({ startedAt: 12_000 }) - } finally { - vi.useRealTimers() - } - }) - }) -}) - -vi.mock('../MobileQueuedMessageComposer', () => ({ - MobileQueuedMessageComposer: 'QueuedMessageComposer' -})) diff --git a/mobile/src/session/agent-session-failure-copy.ts b/mobile/src/session/native-chat/agent-session/agent-session-failure-copy.ts similarity index 96% rename from mobile/src/session/agent-session-failure-copy.ts rename to mobile/src/session/native-chat/agent-session/agent-session-failure-copy.ts index 541b5a76698..d7779789684 100644 --- a/mobile/src/session/agent-session-failure-copy.ts +++ b/mobile/src/session/native-chat/agent-session/agent-session-failure-copy.ts @@ -2,9 +2,9 @@ import type { AgentSessionFailureCopyId, AgentSessionFailureCopyValues, AgentSessionFailureSay -} from '../../../src/shared/agent-session/failures/agent-session-failure-copy' -import { translate } from '../i18n/mobile-i18n' -import { AGENT_SESSION_FAILURE_COPY } from '../../../src/shared/agent-session/failures/agent-session-failure-copy' +} from '../../../../../src/shared/agent-session/failures/agent-session-failure-copy' +import { translate } from '../../../i18n/mobile-i18n' +import { AGENT_SESSION_FAILURE_COPY } from '../../../../../src/shared/agent-session/failures/agent-session-failure-copy' import { START_FAILURE_COPY } from './agent-session-start-failure-copy' import { SEND_FAILURE_COPY } from './agent-session-send-failure-copy' diff --git a/mobile/src/session/agent-session-send-failure-copy.ts b/mobile/src/session/native-chat/agent-session/agent-session-send-failure-copy.ts similarity index 96% rename from mobile/src/session/agent-session-send-failure-copy.ts rename to mobile/src/session/native-chat/agent-session/agent-session-send-failure-copy.ts index 0a10fcac96f..4efaf079a87 100644 --- a/mobile/src/session/agent-session-send-failure-copy.ts +++ b/mobile/src/session/native-chat/agent-session/agent-session-send-failure-copy.ts @@ -1,9 +1,9 @@ -import { AGENT_SESSION_FAILURE_COPY } from '../../../src/shared/agent-session/failures/agent-session-failure-copy' +import { AGENT_SESSION_FAILURE_COPY } from '../../../../../src/shared/agent-session/failures/agent-session-failure-copy' import type { AgentSessionFailureCopyId, AgentSessionFailureCopyValues -} from '../../../src/shared/agent-session/failures/agent-session-failure-copy' -import { translate } from '../i18n/mobile-i18n' +} from '../../../../../src/shared/agent-session/failures/agent-session-failure-copy' +import { translate } from '../../../i18n/mobile-i18n' export const SEND_FAILURE_COPY = { unconfirmed: (values) => diff --git a/mobile/src/session/agent-session-start-failure-copy.ts b/mobile/src/session/native-chat/agent-session/agent-session-start-failure-copy.ts similarity index 98% rename from mobile/src/session/agent-session-start-failure-copy.ts rename to mobile/src/session/native-chat/agent-session/agent-session-start-failure-copy.ts index 036264d4a0a..924ff47be42 100644 --- a/mobile/src/session/agent-session-start-failure-copy.ts +++ b/mobile/src/session/native-chat/agent-session/agent-session-start-failure-copy.ts @@ -1,8 +1,8 @@ import type { AgentSessionFailureCopyId, AgentSessionFailureCopyValues -} from '../../../src/shared/agent-session/failures/agent-session-failure-copy' -import { translate } from '../i18n/mobile-i18n' +} from '../../../../../src/shared/agent-session/failures/agent-session-failure-copy' +import { translate } from '../../../i18n/mobile-i18n' export const START_FAILURE_COPY = { savedConversationMissing: (values) => diff --git a/mobile/src/session/agent-session-write-notice-copy.ts b/mobile/src/session/native-chat/agent-session/agent-session-write-notice-copy.ts similarity index 96% rename from mobile/src/session/agent-session-write-notice-copy.ts rename to mobile/src/session/native-chat/agent-session/agent-session-write-notice-copy.ts index ffab10462a5..ca95e2529ca 100644 --- a/mobile/src/session/agent-session-write-notice-copy.ts +++ b/mobile/src/session/native-chat/agent-session/agent-session-write-notice-copy.ts @@ -1,10 +1,10 @@ -import { expiredAttachmentsSentence } from '../../../src/shared/agent-session/failures/agent-session-attachment-failure-words' -import { agentSessionFailureSentence } from '../../../src/shared/agent-session/failures/agent-session-failure-words' +import { expiredAttachmentsSentence } from '../../../../../src/shared/agent-session/failures/agent-session-attachment-failure-words' +import { agentSessionFailureSentence } from '../../../../../src/shared/agent-session/failures/agent-session-failure-words' import type { AgentSessionWriteNoticePart, AgentSessionWriteNoticeSentence -} from '../../../src/shared/agent-session/failures/agent-session-write-notice-copy' -import { translate } from '../i18n/mobile-i18n' +} from '../../../../../src/shared/agent-session/failures/agent-session-write-notice-copy' +import { translate } from '../../../i18n/mobile-i18n' import { sayMobileAgentSessionFailure } from './agent-session-failure-copy' const SENTENCES: Record string> = { diff --git a/mobile/src/session/mobile-created-structured-sessions.ts b/mobile/src/session/native-chat/agent-session/mobile-created-structured-sessions.ts similarity index 100% rename from mobile/src/session/mobile-created-structured-sessions.ts rename to mobile/src/session/native-chat/agent-session/mobile-created-structured-sessions.ts diff --git a/mobile/src/session/native-chat/agent-session/mobile-native-chat-page-read-operations.ts b/mobile/src/session/native-chat/agent-session/mobile-native-chat-page-read-operations.ts index b91d5156f2d..f7a712d7aba 100644 --- a/mobile/src/session/native-chat/agent-session/mobile-native-chat-page-read-operations.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-native-chat-page-read-operations.ts @@ -2,13 +2,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../../transport import { rpcResultVariant } from '../../../transport/rpc/rpc-operation-result-reader' import '../../../terminal/terminal-reply-schema' import { sessionForwardedReplySchema } from '../../../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../../agent-launch/mobile-agent-detection-reply-schema' -import '../../../files/session-documents/mobile-markdown-tab-reply-schema' -import '../../../terminal/session/mobile-session-terminal-reply-schema' -import '../../../worktree/session-worktree-record-reply-schema' -import '../composer/mobile-native-chat-skill-reply-schema' -import '../../../terminal/quick-commands/mobile-quick-command-reply-schema' -import '../../../files/workspace-file-paths-reply-schema' // Retain the live window on refusal and let the caller offer retry; read the whole legacy error/window union before selecting messages. export const nativeChatSessionPageRead = bindDeferredRpcOperation( diff --git a/mobile/src/session/mobile-structured-agent-session-cancel.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-cancel.ts similarity index 89% rename from mobile/src/session/mobile-structured-agent-session-cancel.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-cancel.ts index e7678dfbed5..849b252116f 100644 --- a/mobile/src/session/mobile-structured-agent-session-cancel.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-cancel.ts @@ -1,18 +1,18 @@ import { useCallback, useRef } from 'react' import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' -import type { AgentSessionCancelResult } from '../../../src/shared/agent-session/agent-session-wire' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' -import { runningStructuredAgentSessionTurnId } from '../../../src/shared/agent-session/view/structured-agent-session-live-turn' +import type { AgentSessionCancelResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import { runningStructuredAgentSessionTurnId } from '../../../../../src/shared/agent-session/view/structured-agent-session-live-turn' import { joinStructuredAgentSessionStop, structuredAgentSessionStopIsJoinable -} from '../../../src/shared/agent-session/requests/structured-agent-session-stop-join' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +} from '../../../../../src/shared/agent-session/requests/structured-agent-session-stop-join' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { requestStructuredAgentSessionMutation, type StructuredAgentSessionMutationCallResult } from './mobile-structured-agent-session-rpc' -import { actionableStructuredPrompts } from './mobile-structured-actionable-prompt' +import { actionableStructuredPrompts } from '../input/mobile-structured-actionable-prompt' type PromptIdentity = { itemId: string; expectedRevision: number } diff --git a/mobile/src/session/mobile-structured-agent-session-contract.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-contract.ts similarity index 63% rename from mobile/src/session/mobile-structured-agent-session-contract.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-contract.ts index f3d9f5284fd..f869e25c360 100644 --- a/mobile/src/session/mobile-structured-agent-session-contract.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-contract.ts @@ -1,36 +1,36 @@ -import type { AgentSessionSlashCommand } from '../../../src/shared/agent-session/agent-session-wire' -import type { NativeChatSendFollowUp } from '../../../src/shared/native-chat/input/native-chat-follow-up-behavior' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { AgentSessionSlashCommand } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { NativeChatSendFollowUp } from '../../../../../src/shared/native-chat/input/native-chat-follow-up-behavior' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' import type { MobileNativeChatSendIntent, MobileNativeChatSendOutcome -} from './native-chat/mobile-native-chat-send' -import type { MobileNativeChatVisualSource } from './native-chat/visual/mobile-native-chat-visual-read' -import type { MobileChatPermission } from './native-chat/input/mobile-native-chat-permission' -import type { MobileChatQuestion } from './native-chat/input/mobile-native-chat-question' -import type { MobileNativeChatSession } from './native-chat/use-mobile-native-chat-session' -import type { NativeChatLiveTurnIndicator } from '../../../src/shared/native-chat/transcript/native-chat-turn-status' -import type { NativeChatResolvedPrompt } from '../../../src/shared/native-chat/native-chat-resolution-receipt' -import type { useMobileStructuredAgentOptions } from './use-mobile-structured-agent-options' -import type { useMobileStructuredAgentTurnTiming } from './use-mobile-structured-agent-turn-timing' -import type { StructuredMobileSendAttachment } from './use-mobile-structured-send-with-outcome' +} from '../composer/mobile-native-chat-send' +import type { MobileNativeChatVisualSource } from '../visual/mobile-native-chat-visual-read' +import type { MobileChatPermission } from '../input/mobile-native-chat-permission' +import type { MobileChatQuestion } from '../input/mobile-native-chat-question' +import type { MobileNativeChatSession } from '../use-mobile-native-chat-session' +import type { NativeChatLiveTurnIndicator } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-status' +import type { NativeChatResolvedPrompt } from '../../../../../src/shared/native-chat/native-chat-resolution-receipt' +import type { useMobileStructuredAgentOptions } from '../input/use-mobile-structured-agent-options' +import type { useMobileStructuredAgentTurnTiming } from '../transcript/use-mobile-structured-agent-turn-timing' +import type { StructuredMobileSendAttachment } from '../composer/use-mobile-structured-send-with-outcome' import type { AgentJournalSkillRef, AgentJournalSubmission -} from '../../../src/shared/agent-session/journal/agent-session-journal-types' +} from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' import type { MobileNativeChatCommandRefusalCauses, MobileNativeChatSendErrorReporter -} from './native-chat/use-mobile-native-chat-send-error' -import type { MobileStructuredQueuedMessageControls } from './use-mobile-structured-queued-message-controls' -import type { MobileStructuredBackgroundTasks } from './use-mobile-structured-background-tasks' -import type { NativeChatContextUsageSummary } from '../../../src/shared/native-chat/native-chat-context-usage-summary' -import type { MobileStructuredInterruptedContinue } from './use-mobile-structured-interrupted-continue' -import type { MobileStructuredThreadGoal } from './use-mobile-structured-thread-goal' -import type { MobileStructuredSends } from './use-mobile-structured-sends' -import type { AgentSessionRewindSurface } from '../../../src/shared/agent-session/lifecycle/agent-session-rewind-request' -import type { MobileNativeChatUsageLimit } from './native-chat/use-mobile-native-chat-usage-limit' +} from '../composer/use-mobile-native-chat-send-error' +import type { MobileStructuredQueuedMessageControls } from '../input/use-mobile-structured-queued-message-controls' +import type { MobileStructuredBackgroundTasks } from '../input/use-mobile-structured-background-tasks' +import type { NativeChatContextUsageSummary } from '../../../../../src/shared/native-chat/native-chat-context-usage-summary' +import type { MobileStructuredInterruptedContinue } from '../composer/use-mobile-structured-interrupted-continue' +import type { MobileStructuredThreadGoal } from '../input/use-mobile-structured-thread-goal' +import type { MobileStructuredSends } from '../composer/use-mobile-structured-sends' +import type { AgentSessionRewindSurface } from '../../../../../src/shared/agent-session/lifecycle/agent-session-rewind-request' +import type { MobileNativeChatUsageLimit } from '../use-mobile-native-chat-usage-limit' export type StructuredMobileSession = Omit< ReturnType, diff --git a/mobile/src/session/mobile-structured-agent-session-host-support.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-host-support.ts similarity index 90% rename from mobile/src/session/mobile-structured-agent-session-host-support.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-host-support.ts index af40170fc5b..ab43f813b57 100644 --- a/mobile/src/session/mobile-structured-agent-session-host-support.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-host-support.ts @@ -2,12 +2,12 @@ import { AGENT_SESSION_COMMANDS_ON_REQUEST_CAPABILITY, AGENT_SESSION_MODEL_EFFORT_DISCOVERY_CAPABILITY, AGENT_SESSION_COHERENT_MODEL_CATALOG_CAPABILITY -} from '../../../src/shared/agent-session/structured-agent-session-surface-capabilities' +} from '../../../../../src/shared/agent-session/structured-agent-session-surface-capabilities' import { AGENT_SESSION_CONVERSATION_STOP_RUNTIME_CAPABILITY, AGENT_SESSION_PROMPT_CANCEL_RUNTIME_CAPABILITY, AGENT_SESSION_REPEATED_STOP_RUNTIME_CAPABILITY -} from '../../../src/shared/agent-session/lifecycle/agent-session-stop-capabilities' +} from '../../../../../src/shared/agent-session/lifecycle/agent-session-stop-capabilities' import { AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY, AGENT_SESSION_SEND_RECORDED_RUNTIME_CAPABILITY, @@ -22,21 +22,21 @@ import { AGENT_SESSION_PHONE_REWIND_RUNTIME_CAPABILITY, AGENT_SESSION_PHONE_USAGE_LIMIT_CONTINUE_CANCEL_RUNTIME_CAPABILITY, AGENT_SESSION_QUEUED_MESSAGE_SKILLS_EDIT_RUNTIME_CAPABILITY -} from '../../../src/shared/agent-session/agent-session-runtime-capabilities' -import { AGENT_SESSION_STATUS_FEED_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session/agent-session-attention-capabilities' +} from '../../../../../src/shared/agent-session/agent-session-runtime-capabilities' +import { AGENT_SESSION_STATUS_FEED_RUNTIME_CAPABILITY } from '../../../../../src/shared/agent-session/agent-session-attention-capabilities' import { AGENT_SESSION_SEND_SELECTION_RUNTIME_CAPABILITY, AGENT_SESSION_SEND_OPTIONS_RUNTIME_CAPABILITY, AGENT_SESSION_SEND_SKILLS_RUNTIME_CAPABILITY, AGENT_SESSION_SEND_USAGE_LIMIT_CONTINUATION_RUNTIME_CAPABILITY, AGENT_SESSION_USAGE_LIMIT_AUTO_CONTINUE_RUNTIME_CAPABILITY -} from '../../../src/shared/agent-session/agent-session-create-capabilities' +} from '../../../../../src/shared/agent-session/agent-session-create-capabilities' import { AGENT_SESSION_PERMISSION_MODES_RUNTIME_CAPABILITY, AGENT_SESSION_PERMISSION_OWN_RUNTIME_CAPABILITY -} from '../../../src/shared/agent-session/agent-session-permission-modes-capability' -import { SKILL_DISCOVER_WORKSPACE_CAPABILITY } from '../../../src/shared/skill-install-capability' -import { AGENT_SESSION_PHONE_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session/agent-session-continue-interrupted-capability' +} from '../../../../../src/shared/agent-session/agent-session-permission-modes-capability' +import { SKILL_DISCOVER_WORKSPACE_CAPABILITY } from '../../../../../src/shared/skill-install-capability' +import { AGENT_SESSION_PHONE_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } from '../../../../../src/shared/agent-session/agent-session-continue-interrupted-capability' export type StructuredAgentSessionHostSupport = { commandsOnRequest?: boolean diff --git a/mobile/src/session/mobile-structured-agent-session-launch.test.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.test.ts similarity index 95% rename from mobile/src/session/mobile-structured-agent-session-launch.test.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.test.ts index 457d3fb3c2e..13c056860b2 100644 --- a/mobile/src/session/mobile-structured-agent-session-launch.test.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { markRpcDeliveryUnknown } from '../transport/rpc/rpc-delivery-ambiguity' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { markRpcDeliveryUnknown } from '../../../transport/rpc/rpc-delivery-ambiguity' import { createMobileStructuredAgentSession } from './mobile-structured-agent-session-launch' import './mobile-created-structured-sessions' diff --git a/mobile/src/session/mobile-structured-agent-session-launch.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts similarity index 88% rename from mobile/src/session/mobile-structured-agent-session-launch.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts index d7cb85e2a31..dda006a7c68 100644 --- a/mobile/src/session/mobile-structured-agent-session-launch.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts @@ -1,30 +1,30 @@ -import { agentSessionForkSourceFailureWords } from '../../../src/shared/agent-session/failures/agent-session-failure-copy' +import { agentSessionForkSourceFailureWords } from '../../../../../src/shared/agent-session/failures/agent-session-failure-copy' import { sayMobileAgentSessionFailure } from './agent-session-failure-copy' -import type { TuiAgent } from '../../../src/shared/agent-launch/catalog/tui-agent' +import type { TuiAgent } from '../../../../../src/shared/agent-launch/catalog/tui-agent' import type { AgentSessionAttachResult, AgentSessionMutationResult -} from '../../../src/shared/agent-session/agent-session-wire' +} from '../../../../../src/shared/agent-session/agent-session-wire' import { isDefinitiveAgentSessionCreateRefusal, isAgentSessionForkSourceAdmissionRefusal, isAgentSessionCreateAdmissionRefusal -} from '../../../src/shared/agent-session/failures/agent-session-definitive-refusal' +} from '../../../../../src/shared/agent-session/failures/agent-session-definitive-refusal' import { createStructuredAgentSessionId, structuredAgentSessionCreateParams, type StructuredAgentSessionCreateParams, type StructuredAgentSessionForkSource -} from '../../../src/shared/agent-session/structured-agent-session-create' -import type { StructuredAgentCreateSupportReply } from '../../../src/shared/agent-session/structured-agent-create-support-reply' -import { TUI_AGENT_DISPLAY_NAMES } from '../../../src/shared/agent-launch/catalog/tui-agent-display-names' -import { hasRuntimeRpcErrorCode } from '../../../src/shared/runtime-rpc-error-code' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +} from '../../../../../src/shared/agent-session/structured-agent-session-create' +import type { StructuredAgentCreateSupportReply } from '../../../../../src/shared/agent-session/structured-agent-create-support-reply' +import { TUI_AGENT_DISPLAY_NAMES } from '../../../../../src/shared/agent-launch/catalog/tui-agent-display-names' +import { hasRuntimeRpcErrorCode } from '../../../../../src/shared/runtime-rpc-error-code' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { structuredAgentSessionCreate, structuredAgentSupportProbe -} from './native-chat/agent-session/mobile-structured-create-operations' -import { randomV4Uuid } from '../platform/random-v4-uuid' +} from './mobile-structured-create-operations' +import { randomV4Uuid } from '../../../platform/random-v4-uuid' import { rememberMobileCreatedStructuredSession } from './mobile-created-structured-sessions' // Partial: the reply is read before it is checked. diff --git a/mobile/src/session/mobile-structured-agent-session-rpc.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-rpc.ts similarity index 86% rename from mobile/src/session/mobile-structured-agent-session-rpc.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-rpc.ts index 15cd4a7c1bc..e43935f46ac 100644 --- a/mobile/src/session/mobile-structured-agent-session-rpc.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-rpc.ts @@ -1,11 +1,11 @@ -import type { AgentSessionMutationResult } from '../../../src/shared/agent-session/agent-session-wire' +import type { AgentSessionMutationResult } from '../../../../../src/shared/agent-session/agent-session-wire' import type { AgentSessionWireRefusalCode, AgentSessionRefusalReference -} from '../../../src/shared/agent-session/failures/agent-session-wire-refusals' -import type { AgentJournalCursor } from '../../../src/shared/agent-session/journal/agent-session-journal-identity' -import { structuredAgentSessionPayloadFingerprint } from '../../../src/shared/agent-session/lifecycle/structured-agent-session-mutation' -import { agentSessionWriteNoticeParts } from '../../../src/shared/agent-session/failures/agent-session-refusal-notice' +} from '../../../../../src/shared/agent-session/failures/agent-session-wire-refusals' +import type { AgentJournalCursor } from '../../../../../src/shared/agent-session/journal/agent-session-journal-identity' +import { structuredAgentSessionPayloadFingerprint } from '../../../../../src/shared/agent-session/lifecycle/structured-agent-session-mutation' +import { agentSessionWriteNoticeParts } from '../../../../../src/shared/agent-session/failures/agent-session-refusal-notice' import { agentSessionRefusalFailure, agentSessionThrownFailure, @@ -13,14 +13,14 @@ import { readAgentSessionErrorRefusal, type AgentSessionWriteFailure, type AgentSessionWriteKind -} from '../../../src/shared/agent-session/failures/agent-session-write-failure' +} from '../../../../../src/shared/agent-session/failures/agent-session-write-failure' import { mobileAgentSessionWriteNoticeText } from './agent-session-write-notice-copy' import { structuredSessionOperationId } from './structured-session-operation-id' -import { isRpcDeliveryUnknown } from '../transport/rpc/rpc-delivery-ambiguity' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import '../transport/connection/stable-logical-rpc-client' -import { isLogicalClientCutoverError } from '../transport/connection/logical-client-cutover-error' -import { MOBILE_NATIVE_CHAT_MIN_WRITE_TIMEOUT_MS } from './native-chat/mobile-native-chat-send' +import { isRpcDeliveryUnknown } from '../../../transport/rpc/rpc-delivery-ambiguity' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import '../../../transport/connection/stable-logical-rpc-client' +import { isLogicalClientCutoverError } from '../../../transport/connection/logical-client-cutover-error' +import { MOBILE_NATIVE_CHAT_MIN_WRITE_TIMEOUT_MS } from '../composer/mobile-native-chat-send' export const STRUCTURED_SEND_TIMEOUT_MS = 15_000 diff --git a/mobile/src/session/mobile-structured-agent-session-send.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-send.ts similarity index 74% rename from mobile/src/session/mobile-structured-agent-session-send.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-send.ts index 76fad9815a5..6fd2d8fda9a 100644 --- a/mobile/src/session/mobile-structured-agent-session-send.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-send.ts @@ -1,21 +1,21 @@ -import type { AgentSessionSendResult } from '../../../src/shared/agent-session/agent-session-wire' -import type { AgentJournalCursor } from '../../../src/shared/agent-session/journal/agent-session-journal-identity' -import type { AgentJournalSkillRef } from '../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentSessionSendResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { AgentJournalCursor } from '../../../../../src/shared/agent-session/journal/agent-session-journal-identity' +import type { AgentJournalSkillRef } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' import { structuredAgentSessionSendBody, type StructuredAgentSessionAttachment -} from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' -import { structuredAgentSessionBlocksTooLarge } from '../../../src/shared/rpc-contract/structured-agent-session-message-params' -import { AGENT_SESSION_MESSAGE_TOO_LARGE_PARTS } from '../../../src/shared/agent-session/failures/agent-session-refusal-notice' +} from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' +import { structuredAgentSessionBlocksTooLarge } from '../../../../../src/shared/rpc-contract/structured-agent-session-message-params' +import { AGENT_SESSION_MESSAGE_TOO_LARGE_PARTS } from '../../../../../src/shared/agent-session/failures/agent-session-refusal-notice' import { mobileAgentSessionWriteNoticeText } from './agent-session-write-notice-copy' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { MobileNativeChatSendOutcome } from './native-chat/mobile-native-chat-send' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { MobileNativeChatSendOutcome } from '../composer/mobile-native-chat-send' import { requestStructuredAgentSessionMutation, timeoutForDeadline } from './mobile-structured-agent-session-rpc' -import { mobileStructuredSendDelivery } from './mobile-structured-send-delivery' -import type { MobileNativeChatSendErrorReporter } from './native-chat/use-mobile-native-chat-send-error' +import { mobileStructuredSendDelivery } from '../composer/mobile-structured-send-delivery' +import type { MobileNativeChatSendErrorReporter } from '../composer/use-mobile-native-chat-send-error' export async function sendMobileStructuredAgentSessionMessage(input: { client: RpcClient diff --git a/mobile/src/session/mobile-structured-agent-session-working.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-working.ts similarity index 61% rename from mobile/src/session/mobile-structured-agent-session-working.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-working.ts index 1394f856d48..1d4d7986736 100644 --- a/mobile/src/session/mobile-structured-agent-session-working.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-working.ts @@ -1,9 +1,9 @@ -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' import { runningStructuredAgentSessionTurnId, structuredAgentSessionHostSaysWorking -} from '../../../src/shared/agent-session/view/structured-agent-session-live-turn' -import { isStructuredAgentSessionMainAgentWorking } from '../../../src/shared/agent-session/view/structured-agent-session-main-agent-working' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-live-turn' +import { isStructuredAgentSessionMainAgentWorking } from '../../../../../src/shared/agent-session/view/structured-agent-session-main-agent-working' export function mobileStructuredAgentSessionWorking(state: StructuredAgentSessionState): boolean { return structuredAgentSessionHostSaysWorking(state.working, () => diff --git a/mobile/src/session/mobile-structured-host-clock-offset.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-host-clock-offset.ts similarity index 79% rename from mobile/src/session/mobile-structured-host-clock-offset.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-host-clock-offset.ts index 1a0e9abcf3c..70142d96979 100644 --- a/mobile/src/session/mobile-structured-host-clock-offset.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-host-clock-offset.ts @@ -1,4 +1,4 @@ -import type { StructuredAgentHostClock } from '../../../src/shared/agent-session/structured-agent-session-reducer' +import type { StructuredAgentHostClock } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' // Ignore delivery jitter below the precision of displayed clocks. const OFFSET_GRAIN_MS = 1_000 diff --git a/mobile/src/session/mobile-structured-session-open-message.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-session-open-message.ts similarity index 90% rename from mobile/src/session/mobile-structured-session-open-message.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-session-open-message.ts index 0557d24bbb6..b70c8f837e8 100644 --- a/mobile/src/session/mobile-structured-session-open-message.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-session-open-message.ts @@ -1,6 +1,6 @@ -import { formatAgentTypeLabel } from '../../../src/shared/agent-status/presentation/agent-type-label' -import type { StructuredSessionOpenFailure } from '../../../src/shared/open-structured-agent-session' -import { translate } from '../i18n/mobile-i18n' +import { formatAgentTypeLabel } from '../../../../../src/shared/agent-status/presentation/agent-type-label' +import type { StructuredSessionOpenFailure } from '../../../../../src/shared/open-structured-agent-session' +import { translate } from '../../../i18n/mobile-i18n' export function mobileStructuredSessionOpenMessage( reason: StructuredSessionOpenFailure['reason'], diff --git a/mobile/src/session/mobile-structured-session-open-rpc.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-session-open-rpc.ts similarity index 88% rename from mobile/src/session/mobile-structured-session-open-rpc.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-session-open-rpc.ts index b0afd135322..4a6af221381 100644 --- a/mobile/src/session/mobile-structured-session-open-rpc.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-session-open-rpc.ts @@ -1,7 +1,7 @@ import * as z from 'zod' -import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc/rpc-operation' -import { rpcResultVariant } from '../transport/rpc/rpc-operation-result-reader' -import type { RpcMethodName } from '../../../src/shared/rpc-contract/rpc-params-catalog.generated' +import { bindDeferredRpcOperation, defineRpcOperation } from '../../../transport/rpc/rpc-operation' +import { rpcResultVariant } from '../../../transport/rpc/rpc-operation-result-reader' +import type { RpcMethodName } from '../../../../../src/shared/rpc-contract/rpc-params-catalog.generated' function openingRead( method: Method, diff --git a/mobile/src/session/mobile-structured-session-status-feed.test.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-session-status-feed.test.ts similarity index 84% rename from mobile/src/session/mobile-structured-session-status-feed.test.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-session-status-feed.test.ts index 170c101788f..892df6a0497 100644 --- a/mobile/src/session/mobile-structured-session-status-feed.test.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-session-status-feed.test.ts @@ -1,10 +1,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { AgentSessionStatusSummary } from '../../../src/shared/agent-session/agent-session-wire' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { ConnectionState } from '../transport/connection/connection-state' -import '../transport/connection/stable-logical-rpc-client' +import type { AgentSessionStatusSummary } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { ConnectionState } from '../../../transport/connection/connection-state' +import '../../../transport/connection/stable-logical-rpc-client' import { mobileStructuredSessionStatusFeed } from './mobile-structured-session-status-feed' -import { stubRpcClient } from '../transport/rpc/rpc-client-test-stub' +import { stubRpcClient } from '../../../transport/rpc/rpc-client-test-stub' function summary(stopping: boolean): AgentSessionStatusSummary { return { diff --git a/mobile/src/session/mobile-structured-session-status-feed.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-session-status-feed.ts similarity index 91% rename from mobile/src/session/mobile-structured-session-status-feed.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-session-status-feed.ts index 8617b8afd29..841ef3995af 100644 --- a/mobile/src/session/mobile-structured-session-status-feed.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-session-status-feed.ts @@ -5,14 +5,14 @@ import { foldAgentSessionStatusEvent, revokeAgentSessionStatusLive, type AgentSessionStatusSnapshot -} from '../../../src/shared/agent-session/view/agent-session-status-snapshot-fold' -import type { AgentSessionStatusEvent } from '../../../src/shared/agent-session/agent-session-wire' +} from '../../../../../src/shared/agent-session/view/agent-session-status-snapshot-fold' +import type { AgentSessionStatusEvent } from '../../../../../src/shared/agent-session/agent-session-wire' import { foldAgentSessionConversationNames, type AgentSessionConversationNames -} from '../../../src/shared/agent-session/agent-session-conversation-names' -import { isMobileMethodUnavailableError } from '../transport/rpc/mobile-method-unavailable' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +} from '../../../../../src/shared/agent-session/agent-session-conversation-names' +import { isMobileMethodUnavailableError } from '../../../transport/rpc/mobile-method-unavailable' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' export type MobileStructuredSessionStatusFeed = { subscribe: (listener: () => void) => () => void diff --git a/mobile/src/session/mobile-structured-transcript-subscription.ts b/mobile/src/session/native-chat/agent-session/mobile-structured-transcript-subscription.ts similarity index 83% rename from mobile/src/session/mobile-structured-transcript-subscription.ts rename to mobile/src/session/native-chat/agent-session/mobile-structured-transcript-subscription.ts index 2d07c89e144..24402f13bd0 100644 --- a/mobile/src/session/mobile-structured-transcript-subscription.ts +++ b/mobile/src/session/native-chat/agent-session/mobile-structured-transcript-subscription.ts @@ -1,6 +1,6 @@ -import type { AgentSessionSubscribeEvent } from '../../../src/shared/agent-session/agent-session-wire' -import type { StructuredAgentSessionLiveSubscribe } from '../../../src/shared/agent-session/structured-agent-session-live-read' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { AgentSessionSubscribeEvent } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { StructuredAgentSessionLiveSubscribe } from '../../../../../src/shared/agent-session/structured-agent-session-live-read' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' function isSubscribeEvent(value: unknown): value is AgentSessionSubscribeEvent { if (typeof value !== 'object' || value === null || !('type' in value)) { diff --git a/mobile/src/session/open-mobile-structured-session.ts b/mobile/src/session/native-chat/agent-session/open-mobile-structured-session.ts similarity index 91% rename from mobile/src/session/open-mobile-structured-session.ts rename to mobile/src/session/native-chat/agent-session/open-mobile-structured-session.ts index 346aa09264a..9f37d080a8a 100644 --- a/mobile/src/session/open-mobile-structured-session.ts +++ b/mobile/src/session/native-chat/agent-session/open-mobile-structured-session.ts @@ -4,17 +4,17 @@ import { type StructuredSessionOpenSource, type StructuredSessionOpenTab, type StructuredSessionOpenTarget -} from '../../../src/shared/open-structured-agent-session' -import { isStructuredAgentId } from '../../../src/shared/agent-session/lifecycle/agent-session-provider-handle-encoding' -import { structuredAgentSessionPaneKey } from '../../../src/shared/agent-session/view/structured-agent-session-projection' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +} from '../../../../../src/shared/open-structured-agent-session' +import { isStructuredAgentId } from '../../../../../src/shared/agent-session/lifecycle/agent-session-provider-handle-encoding' +import { structuredAgentSessionPaneKey } from '../../../../../src/shared/agent-session/view/structured-agent-session-projection' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { structuredOpenLocation, structuredOpenParty, structuredOpenReveal, structuredOpenTabs } from './mobile-structured-session-open-rpc' -import { sessionTabActivate } from './tabs/mobile-session-tab-write-operations' +import { sessionTabActivate } from '../../tabs/mobile-session-tab-write-operations' /** The workspace client already carries executionHost; a single budget includes connect waits. */ export async function openMobileStructuredSession(args: { diff --git a/mobile/src/session/structured-session-operation-id.ts b/mobile/src/session/native-chat/agent-session/structured-session-operation-id.ts similarity index 69% rename from mobile/src/session/structured-session-operation-id.ts rename to mobile/src/session/native-chat/agent-session/structured-session-operation-id.ts index 434a0f19b5b..80c298a94fb 100644 --- a/mobile/src/session/structured-session-operation-id.ts +++ b/mobile/src/session/native-chat/agent-session/structured-session-operation-id.ts @@ -1,7 +1,7 @@ // Keep id generation dependency-free so create/tasks callers do not pull in the native-chat write graph. -import { createStructuredAgentSessionOperationId } from '../../../src/shared/agent-session/lifecycle/structured-agent-session-mutation' -import { randomV4Uuid } from '../platform/random-v4-uuid' +import { createStructuredAgentSessionOperationId } from '../../../../../src/shared/agent-session/lifecycle/structured-agent-session-mutation' +import { randomV4Uuid } from '../../../platform/random-v4-uuid' export function structuredSessionOperationId(now: number = Date.now()): string { return createStructuredAgentSessionOperationId(randomV4Uuid, now) diff --git a/mobile/src/session/use-mobile-structured-agent-mutation.ts b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-mutation.ts similarity index 90% rename from mobile/src/session/use-mobile-structured-agent-mutation.ts rename to mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-mutation.ts index bf684cf71f3..9ccefcba3fd 100644 --- a/mobile/src/session/use-mobile-structured-agent-mutation.ts +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-mutation.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { requestStructuredAgentSessionMutation, type StructuredAgentSessionMutationResult diff --git a/mobile/src/session/use-mobile-structured-agent-rewind.ts b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-rewind.ts similarity index 78% rename from mobile/src/session/use-mobile-structured-agent-rewind.ts rename to mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-rewind.ts index 51e9f65a2d1..3200bd020ab 100644 --- a/mobile/src/session/use-mobile-structured-agent-rewind.ts +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-rewind.ts @@ -1,24 +1,24 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' -import { AGENT_SESSION_CONVERSATION_COMMAND_TIMEOUT_MS } from '../../../src/shared/agent-session/agent-session-conversation-command' -import { AGENT_SESSION_PHONE_REWIND_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session/agent-session-runtime-capabilities' -import type { AgentSessionRewindResult } from '../../../src/shared/agent-session/lifecycle/agent-session-rewind' +import { AGENT_SESSION_CONVERSATION_COMMAND_TIMEOUT_MS } from '../../../../../src/shared/agent-session/agent-session-conversation-command' +import { AGENT_SESSION_PHONE_REWIND_RUNTIME_CAPABILITY } from '../../../../../src/shared/agent-session/agent-session-runtime-capabilities' +import type { AgentSessionRewindResult } from '../../../../../src/shared/agent-session/lifecycle/agent-session-rewind' import { createAgentSessionRewindHook, type AgentSessionRewindInput, type AgentSessionRewindSendOutcome -} from '../../../src/shared/agent-session/lifecycle/agent-session-rewind-request' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' -import { actionableStructuredPrompts } from './mobile-structured-actionable-prompt' -import { useRouteRelaysCapability } from '../worktree/workspace-server-context' -import { sayMobileNativeChatRewind } from './native-chat/mobile-native-chat-rewind-copy' -import type { MobileNativeChatSendOutcome } from './native-chat/mobile-native-chat-send' +} from '../../../../../src/shared/agent-session/lifecycle/agent-session-rewind-request' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import { actionableStructuredPrompts } from '../input/mobile-structured-actionable-prompt' +import { useRouteRelaysCapability } from '../../../worktree/workspace-server-context' +import { sayMobileNativeChatRewind } from '../mobile-native-chat-rewind-copy' +import type { MobileNativeChatSendOutcome } from '../composer/mobile-native-chat-send' import { requestStructuredAgentSessionMutation } from './mobile-structured-agent-session-rpc' -import { returnMobileNativeChatMessageToComposer } from './mobile-native-chat-message-hand-back' -import type { useMobileStructuredAgentOptions } from './use-mobile-structured-agent-options' +import { returnMobileNativeChatMessageToComposer } from '../composer/mobile-native-chat-message-hand-back' +import type { useMobileStructuredAgentOptions } from '../input/use-mobile-structured-agent-options' import type { MobileStructuredAgentSessionArgs } from './mobile-structured-agent-session-contract' -import type { MobileStructuredBackgroundTasks } from './use-mobile-structured-background-tasks' -import type { MobileStructuredSendsOut } from './use-mobile-structured-stop-offer' -import { tuiAgentDisplayName } from '../../../src/shared/agent-launch/catalog/tui-agent-display-names' +import type { MobileStructuredBackgroundTasks } from '../input/use-mobile-structured-background-tasks' +import type { MobileStructuredSendsOut } from '../composer/use-mobile-structured-stop-offer' +import { tuiAgentDisplayName } from '../../../../../src/shared/agent-launch/catalog/tui-agent-display-names' const useAgentSessionRewind = createAgentSessionRewindHook( { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState }, diff --git a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test-fixture.ts b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test-fixture.ts index f4934fb7f57..03221a2d5cf 100644 --- a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test-fixture.ts +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test-fixture.ts @@ -8,7 +8,7 @@ import type { } from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' import type { AgentSessionSubscribeEvent } from '../../../../../src/shared/agent-session/agent-session-wire' import type { RpcResponse } from '../../../transport/rpc/rpc-wire-types' -import type { StructuredAgentSessionHostSupport } from '../../mobile-structured-agent-session-host-support' +import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' export const SESSION_ID = 'session-1' diff --git a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test.tsx b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test.tsx index cae9208f26d..d9e72dd6b4f 100644 --- a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test.tsx +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session-queued.test.tsx @@ -7,8 +7,8 @@ import type { AgentSessionSubscribeEvent } from '../../../../../src/shared/agent import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import type { RpcResponse } from '../../../transport/rpc/rpc-wire-types' import { markRpcDeliveryUnknown } from '../../../transport/rpc/rpc-delivery-ambiguity' -import type { StructuredAgentSessionHostSupport } from '../../mobile-structured-agent-session-host-support' -import { useMobileStructuredAgentSession } from '../../use-mobile-structured-agent-session' +import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' +import { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session' import type { RpcClientContextValue } from '../../../transport/connection/rpc-client-context-contract' diff --git a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.clear.test.tsx b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.clear.test.tsx index ba720a54a09..3c48b99c6fe 100644 --- a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.clear.test.tsx +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.clear.test.tsx @@ -6,7 +6,7 @@ import type { AgentSessionSubscribeEvent } from '../../../../../src/shared/agent import { createWithQueryClient } from '../../../test-support/query-client-test-renderer' import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import type { RpcResponse } from '../../../transport/rpc/rpc-wire-types' -import { useMobileStructuredAgentSession } from '../../use-mobile-structured-agent-session' +import { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session' import { SESSION_ID, mutationOk, @@ -29,25 +29,6 @@ const row = ( observedAt: sequence, body: { kind: 'message', role, blocks: [{ type: 'text', text }] } }) -const divider = (sequence: number): AgentJournalRenderItem => ({ - ...row(sequence, 'assistant', ''), - body: { - kind: 'status', - text: 'Context cleared', - presentation: 'context-cleared', - contextClear: { operationId: `clear-${sequence}`, afterFence: 3, clearedAt: sequence } - } -}) -const dividerLands = (sequence: number): AgentSessionSubscribeEvent => ({ - type: 'batch', - sessionId: SESSION_ID, - batch: { - cursor: { epoch: 'epoch-1', sequence }, - items: [divider(sequence)], - removedItemIds: [], - submissions: [] - } -}) describe('useMobileStructuredAgentSession /clear', () => { let renderer: ReactTestRenderer | null = null @@ -132,22 +113,6 @@ describe('useMobileStructuredAgentSession /clear', () => { } } - it('hides the old transcript from the press until the divider lands', async () => { - const chat = await mount() - expect(chat.shown()).toEqual({ text: ['question', 'reply'], clearing: false }) - - const { outcome } = await chat.sendClear() - expect(chat.shown()).toEqual({ text: [], clearing: true }) - - // The host's reply can reach the phone before the divider it already wrote. - await chat.reply({ command: 'clear', state: 'completed' }) - await expect(outcome).resolves.toBe('accepted') - expect(chat.shown()).toEqual({ text: [], clearing: true }) - - chat.push(dividerLands(3)) - expect(chat.shown()).toEqual({ text: ['Context cleared'], clearing: false }) - }) - it('shows the old transcript again when the host refuses the /clear', async () => { const chat = await mount() const { outcome } = await chat.sendClear() diff --git a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.interrupted-continue.test.tsx b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.interrupted-continue.test.tsx index 8030186142d..2778f4aa07a 100644 --- a/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.interrupted-continue.test.tsx +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.interrupted-continue.test.tsx @@ -7,12 +7,12 @@ import { agentJournalItemKey } from '../../../../../src/shared/agent-session/jou import { AGENT_SESSION_PHONE_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } from '../../../../../src/shared/agent-session/agent-session-continue-interrupted-capability' import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import type { RpcResponse } from '../../../transport/rpc/rpc-wire-types' -import { structuredAgentSessionHostSupport } from '../../mobile-structured-agent-session-host-support' +import { structuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' import { snapshotEvent, SESSION_ID } from './use-mobile-structured-agent-session-queued.test-fixture' -import { useMobileStructuredAgentSession } from '../../use-mobile-structured-agent-session' +import { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session' vi.mock('@react-native-async-storage/async-storage', () => ({ default: { getItem: vi.fn(async () => null), setItem: vi.fn(), removeItem: vi.fn() } diff --git a/mobile/src/session/use-mobile-structured-agent-session.test.tsx b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.test.tsx similarity index 91% rename from mobile/src/session/use-mobile-structured-agent-session.test.tsx rename to mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.test.tsx index 327436f803b..73c4f8898d9 100644 --- a/mobile/src/session/use-mobile-structured-agent-session.test.tsx +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.test.tsx @@ -4,16 +4,16 @@ import { fieldsOf, mutationOk, ok -} from './native-chat/agent-session/use-mobile-structured-agent-session-queued.test-fixture' +} from './use-mobile-structured-agent-session-queued.test-fixture' import { act, type ReactTestRenderer } from 'react-test-renderer' -import { createWithQueryClient } from '../test-support/query-client-test-renderer' +import { createWithQueryClient } from '../../../test-support/query-client-test-renderer' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { AgentJournalRenderItem } from '../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentJournalRenderItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { ConnectionState } from '../transport/connection/connection-state' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { ConnectionState } from '../../../transport/connection/connection-state' -import { structuredSendResultFixture } from './structured-agent-send-result.test-fixture' +import { structuredSendResultFixture } from '../../structured-agent-send-result.test-fixture' import { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session' const asyncStorage = vi.hoisted(() => ({ diff --git a/mobile/src/session/use-mobile-structured-agent-session.ts b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.ts similarity index 79% rename from mobile/src/session/use-mobile-structured-agent-session.ts rename to mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.ts index 62c4cd9d9cd..9672b413618 100644 --- a/mobile/src/session/use-mobile-structured-agent-session.ts +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-session.ts @@ -1,57 +1,57 @@ -import { asyncQuestionSourceScope as questionSourceScope } from '../../../src/shared/native-chat/input/native-chat-ask' -import { structuredChatTranscriptIdentity } from './native-chat/mobile-native-chat-transcript-store' +import { asyncQuestionSourceScope as questionSourceScope } from '../../../../../src/shared/native-chat/input/native-chat-ask' +import { structuredChatTranscriptIdentity } from '../transcript/mobile-native-chat-transcript-store' import { useMemo, useRef } from 'react' -import { tuiAgentDisplayName } from '../../../src/shared/agent-launch/catalog/tui-agent-display-names' +import { tuiAgentDisplayName } from '../../../../../src/shared/agent-launch/catalog/tui-agent-display-names' import { mobileStructuredAgentSessionWorking } from './mobile-structured-agent-session-working' -import { isFinalAgentSessionReadRefusal } from '../../../src/shared/agent-session/failures/structured-agent-session-read-refusal' +import { isFinalAgentSessionReadRefusal } from '../../../../../src/shared/agent-session/failures/structured-agent-session-read-refusal' import { runningStructuredAgentSessionTurnId, structuredAgentSessionQueueSendsNext -} from '../../../src/shared/agent-session/view/structured-agent-session-live-turn' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-live-turn' import { actionableStructuredApproval, actionableStructuredQuestion -} from './mobile-structured-actionable-prompt' +} from '../input/mobile-structured-actionable-prompt' import { projectStructuredPermission, projectStructuredQuestion -} from './mobile-structured-agent-prompts' +} from '../input/mobile-structured-agent-prompts' import type { MobileStructuredAgentSessionArgs, StructuredMobileSession } from './mobile-structured-agent-session-contract' -import type { MobileNativeChatVisualSource } from './native-chat/visual/mobile-native-chat-visual-read' +import type { MobileNativeChatVisualSource } from '../visual/mobile-native-chat-visual-read' import { useMobileStructuredAgentState } from './use-mobile-structured-agent-state' -import { useMobileStructuredStopPress } from './use-mobile-structured-stop-press' +import { useMobileStructuredStopPress } from '../composer/use-mobile-structured-stop-press' import { useMobileStructuredSessionHostStopping } from './use-mobile-structured-session-host-stopping' -import { useMobileStructuredPromptResponses } from './use-mobile-structured-prompt-responses' -import { useMobileStructuredAgentOptions } from './use-mobile-structured-agent-options' -import { useHostModelCatalogCache } from './mobile-host-model-catalog-query' -import { useMobileStructuredCommandController } from './use-mobile-structured-command-controller' -import { useMobileStructuredSessionOptionStamp } from './use-mobile-structured-session-option-stamp' -import { useMobileStructuredAgentTurnTiming } from './use-mobile-structured-agent-turn-timing' +import { useMobileStructuredPromptResponses } from '../input/use-mobile-structured-prompt-responses' +import { useMobileStructuredAgentOptions } from '../input/use-mobile-structured-agent-options' +import { useHostModelCatalogCache } from '../input/mobile-host-model-catalog-query' +import { useMobileStructuredCommandController } from '../composer/use-mobile-structured-command-controller' +import { useMobileStructuredSessionOptionStamp } from '../input/use-mobile-structured-session-option-stamp' +import { useMobileStructuredAgentTurnTiming } from '../transcript/use-mobile-structured-agent-turn-timing' import { pendingStructuredPromptIdentity, useMobileStructuredRequestCancel } from './mobile-structured-agent-session-cancel' import { useMobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' -import { useMobileStructuredSendWithOutcome } from './use-mobile-structured-send-with-outcome' -import { useMobileStructuredQueuedMessageControls } from './use-mobile-structured-queued-message-controls' -import { useMobileStructuredBackgroundTasks } from './use-mobile-structured-background-tasks' -import { useMobileStructuredSends } from './use-mobile-structured-sends' -import { useMobileStructuredSendRecorded } from './mobile-structured-send-recorded-read' -import { useMobileNativeChatQueueFollowUps as useQueueFollowUps } from './use-mobile-native-chat-queue-follow-ups' -import { useMobileStructuredStopOffer } from './use-mobile-structured-stop-offer' -import { useMobileStructuredTurnIndicator } from './use-mobile-structured-turn-indicator' -import { useMobileStructuredContextUsage } from './use-mobile-structured-context-usage' -import { useMobileStructuredInterruptedContinue } from './use-mobile-structured-interrupted-continue' +import { useMobileStructuredSendWithOutcome } from '../composer/use-mobile-structured-send-with-outcome' +import { useMobileStructuredQueuedMessageControls } from '../input/use-mobile-structured-queued-message-controls' +import { useMobileStructuredBackgroundTasks } from '../input/use-mobile-structured-background-tasks' +import { useMobileStructuredSends } from '../composer/use-mobile-structured-sends' +import { useMobileStructuredSendRecorded } from '../composer/mobile-structured-send-recorded-read' +import { useMobileNativeChatQueueFollowUps as useQueueFollowUps } from '../preferences/use-mobile-native-chat-queue-follow-ups' +import { useMobileStructuredStopOffer } from '../composer/use-mobile-structured-stop-offer' +import { useMobileStructuredTurnIndicator } from '../transcript/use-mobile-structured-turn-indicator' +import { useMobileStructuredContextUsage } from '../composer/use-mobile-structured-context-usage' +import { useMobileStructuredInterruptedContinue } from '../composer/use-mobile-structured-interrupted-continue' import { useMobileStructuredThreadGoal, useMobileStructuredThreadGoalSend -} from './use-mobile-structured-thread-goal' -import { useMobileStructuredTranscript } from './use-mobile-structured-transcript' +} from '../input/use-mobile-structured-thread-goal' +import { useMobileStructuredTranscript } from '../transcript/use-mobile-structured-transcript' import { useMobileStructuredAgentRewind } from './use-mobile-structured-agent-rewind' -import { useMobileNativeChatUsageLimit } from './native-chat/use-mobile-native-chat-usage-limit' +import { useMobileNativeChatUsageLimit } from '../use-mobile-native-chat-usage-limit' export function useMobileStructuredAgentSession( args: MobileStructuredAgentSessionArgs diff --git a/mobile/src/session/use-mobile-structured-agent-state.ts b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-state.ts similarity index 87% rename from mobile/src/session/use-mobile-structured-agent-state.ts rename to mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-state.ts index c2bcae0c88e..7a847917079 100644 --- a/mobile/src/session/use-mobile-structured-agent-state.ts +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-agent-state.ts @@ -1,45 +1,48 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' -import { isRootAgentJournalItem } from '../../../src/shared/agent-session/journal/agent-session-journal-producer' +import { isRootAgentJournalItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-producer' import type { AgentSessionQueuedMessage, AgentSessionQueuePause -} from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +} from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' import { type AgentSessionHistoryResult, AGENT_SESSION_HISTORY_MAX_LIMIT -} from '../../../src/shared/agent-session/agent-session-wire' -import { structuredAgentSessionHolderId } from '../../../src/shared/agent-session/view/structured-agent-session-holder' -import { startStructuredAgentSessionLiveRead } from '../../../src/shared/agent-session/structured-agent-session-live-read' +} from '../../../../../src/shared/agent-session/agent-session-wire' +import { structuredAgentSessionHolderId } from '../../../../../src/shared/agent-session/view/structured-agent-session-holder' +import { startStructuredAgentSessionLiveRead } from '../../../../../src/shared/agent-session/structured-agent-session-live-read' import { type AgentSessionTextAppendCapabilities, agentSessionTextAppendRequest -} from '../../../src/shared/agent-session/agent-session-text-append' -import { isFinalAgentSessionReadRefusal } from '../../../src/shared/agent-session/failures/structured-agent-session-read-refusal' +} from '../../../../../src/shared/agent-session/agent-session-text-append' +import { isFinalAgentSessionReadRefusal } from '../../../../../src/shared/agent-session/failures/structured-agent-session-read-refusal' import { EMPTY_STRUCTURED_AGENT_SESSION, oldestStructuredAgentSessionCursor, type StructuredAgentSessionAction, type StructuredAgentSessionState -} from '../../../src/shared/agent-session/structured-agent-session-reducer' -import { subscribeConnectionRevival } from '../diagnostics/connection/connection-revival-signal' +} from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import { subscribeConnectionRevival } from '../../../diagnostics/connection/connection-revival-signal' import { readRuntimeStatusProbe, recordRuntimeStatusProbe -} from '../transport/connection/runtime-status-probe' -import { decodeHostStatusReply, readHostStatusReply } from '../query/host-status-query' -import { hostStatusSchema } from '../transport/hosts/host-status-reply-schema' +} from '../../../transport/connection/runtime-status-probe' +import { decodeHostStatusReply, readHostStatusReply } from '../../../query/host-status-query' +import { hostStatusSchema } from '../../../transport/hosts/host-status-reply-schema' import { AGENT_SESSION_TEXT_APPEND_RUNTIME_CAPABILITY, AGENT_SESSION_TOOL_OUTPUT_APPEND_RUNTIME_CAPABILITY -} from '../../../src/shared/agent-session/agent-session-runtime-capabilities' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +} from '../../../../../src/shared/agent-session/agent-session-runtime-capabilities' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { agentSessionReadFailureRefusal, agentSessionReadRefusalText, callAgentSession } from './mobile-structured-agent-session-rpc' import { subscribeTranscriptAfterHold } from './mobile-structured-transcript-subscription' -import { useMobileOlderPageLoader, type OlderPagePhase } from './use-mobile-older-page-loader' +import { + useMobileOlderPageLoader, + type OlderPagePhase +} from '../transcript/use-mobile-older-page-loader' import { mobileStructuredAgentSessionWorking } from './mobile-structured-agent-session-working' import { reduceStructuredChatState, @@ -47,8 +50,8 @@ import { storedStructuredSessionState, storedStructuredCopy, unconfirmedStructuredStateSavedAt -} from './native-chat/mobile-native-chat-transcript-writer' -import { useStoredChatTranscript } from './native-chat/use-stored-chat-transcript' +} from '../transcript/mobile-native-chat-transcript-writer' +import { useStoredChatTranscript } from '../transcript/use-stored-chat-transcript' const MAX_RETAINED_SESSION_STATES = 32 diff --git a/mobile/src/session/use-mobile-structured-session-host-stopping.ts b/mobile/src/session/native-chat/agent-session/use-mobile-structured-session-host-stopping.ts similarity index 92% rename from mobile/src/session/use-mobile-structured-session-host-stopping.ts rename to mobile/src/session/native-chat/agent-session/use-mobile-structured-session-host-stopping.ts index a1d3db4174a..866b1873358 100644 --- a/mobile/src/session/use-mobile-structured-session-host-stopping.ts +++ b/mobile/src/session/native-chat/agent-session/use-mobile-structured-session-host-stopping.ts @@ -1,5 +1,5 @@ import { useCallback, useSyncExternalStore } from 'react' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { mobileStructuredSessionStatusFeed } from './mobile-structured-session-status-feed' const noSubscription = (): (() => void) => () => {} diff --git a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.keyboard.web.test.tsx b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.keyboard.web.test.tsx index e1cee3abee1..3facb1e14f7 100644 --- a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.keyboard.web.test.tsx +++ b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.keyboard.web.test.tsx @@ -5,7 +5,6 @@ import type * as ReactNative from 'react-native' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { MobileNativeChatComposer } from './MobileNativeChatComposer' import { MobileNativeChatLowerRegion } from '../MobileNativeChatLowerRegion.web' -import { NATIVE_CHAT_STEER_CHORD_HOLD_MS } from '../../../../../src/shared/native-chat/input/use-native-chat-steer-chord-preview' vi.mock('react-native', () => vi.importActual('react-native-web')) vi.mock( @@ -189,31 +188,6 @@ describe('mobile composer modifier submit in the paired web page', () => { expect(field().value).toBe(' hello ') }) - it('holding Cmd alone on an empty composer shows Steer queued in Send, lights the head card and sends it on a press', async () => { - vi.spyOn(document, 'hasFocus').mockReturnValue(true) - const steerHead = { steers: () => true, run: vi.fn(() => true), light: vi.fn() } - await render({ value: '', sendFollowUp: { action: 'queue', offersChoice: true }, steerHead }) - const preview = () => container.querySelector('[data-preview]') - vi.useFakeTimers() - try { - await act(async () => { - field().dispatchEvent(new KeyboardEvent('keydown', { key: 'Meta', bubbles: true })) - }) - await act(async () => vi.advanceTimersByTime(NATIVE_CHAT_STEER_CHORD_HOLD_MS)) - } finally { - vi.useRealTimers() - } - expect(preview()?.dataset.preview).toBe('steer-queued') - expect(steerHead.light).toHaveBeenLastCalledWith(true) - await act(async () => preview()?.click()) - expect(steerHead.run).toHaveBeenCalledExactlyOnceWith(true) - await act(async () => { - field().dispatchEvent(new KeyboardEvent('keyup', { key: 'Meta', bubbles: true })) - }) - expect(preview()).toBeNull() - expect(steerHead.light).toHaveBeenLastCalledWith(false) - }) - it.each([ { value: '@rea', expected: '@README.md ', filePaths: ['README.md', 'src/read.ts'] }, { value: '/comp', expected: '/compact ', filePaths: [] } diff --git a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.structured.test.ts b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.structured.test.ts index c04552d7585..29fc29234d0 100644 --- a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.structured.test.ts +++ b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.structured.test.ts @@ -7,17 +7,17 @@ import { updateMobileNativeChatDraftInputs } from './mobile-native-chat-draft-inputs' import './use-mobile-native-chat-drafts' -import '../../use-mobile-structured-native-chat-send-bridge' +import './use-mobile-structured-native-chat-send-bridge' import '../mobile-native-chat-scope-key' -import '../../use-mobile-structured-agent-session' +import '../agent-session/use-mobile-structured-agent-session' import '../agent-session/use-mobile-structured-agent-session-queued.test-fixture' import '../../structured-agent-send-result.test-fixture' import { findByHostType } from '../../../test-support/react-test-renderer-host-nodes' -import '../../mobile-structured-agent-session-host-support' +import '../agent-session/mobile-structured-agent-session-host-support' import '../../../../../src/shared/agent-session/agent-session-runtime-capabilities' -import '../../use-mobile-structured-agent-options' -import '../../use-mobile-structured-agent-options.test-fixture' +import '../input/use-mobile-structured-agent-options' +import '../input/use-mobile-structured-agent-options.test-fixture' import { createElement, type ComponentProps } from 'react' diff --git a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx index b58cdede682..07f31ca25c1 100644 --- a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx +++ b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx @@ -39,7 +39,7 @@ import { composerInputStyles as inputStyles } from '../../../components/composer import { MobileNativeChatComposerSendSlot } from './MobileNativeChatComposerSendSlot' import { MobileNativeChatEditActions } from './MobileNativeChatEditActions' import { useMobileNativeChatContextRing } from './MobileNativeChatContextUsageRing' -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' +import type { MobileNativeChatSendIntent } from './mobile-native-chat-send' import { MobileNativeChatComposerMic } from './MobileNativeChatComposerMic' import type { MobileNativeChatMicProps } from './MobileNativeChatComposerMic' import { MobileNativeChatDictationLeading } from './MobileNativeChatDictationLeading' @@ -51,8 +51,6 @@ const NO_FILE_PATHS: string[] = [] const NO_ATTACHMENTS: PendingNativeChatImage[] = [] const NO_FILES: PendingNativeChatFile[] = [] -export type { MobileNativeChatStructuredComposer } from './mobile-native-chat-structured-composer' - type Props = MobileNativeChatMicProps & { // Applies dictation's edit to the draft this composer shows; false when it can no longer take it. insertDictation?: (insert: (draft: string) => string) => boolean | void diff --git a/mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachButton.tsx b/mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachButton.tsx index d9760401fca..45d89b53131 100644 --- a/mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachButton.tsx +++ b/mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachButton.tsx @@ -4,7 +4,7 @@ import { Plus } from 'lucide-react-native' import { colors } from '../../../theme/mobile-theme' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { ActionSheetModal } from '../../../components/ActionSheetModal' -import { mobileNativeChatAttachActions } from '../mobile-native-chat-attach-actions' +import { mobileNativeChatAttachActions } from './mobile-native-chat-attach-actions' import { COMPOSER_CONTROL_HIT_SLOP, composerInputStyles as inputStyles diff --git a/mobile/src/session/native-chat/composer/MobileNativeChatComposerSendSlot.tsx b/mobile/src/session/native-chat/composer/MobileNativeChatComposerSendSlot.tsx index cc4dd16223b..4651ce6c6d8 100644 --- a/mobile/src/session/native-chat/composer/MobileNativeChatComposerSendSlot.tsx +++ b/mobile/src/session/native-chat/composer/MobileNativeChatComposerSendSlot.tsx @@ -6,7 +6,7 @@ import type { NativeChatSendFollowUp } from '../../../../../src/shared/native-ch import type { SessionOptionDescriptor } from '../../../../../src/shared/native-chat/input/native-chat-session-options' import { MobileNativeChatStopButton } from '../input/MobileNativeChatStopButton' import { MobileNativeChatControlSwap } from '../input/mobile-native-chat-control-swap' -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' +import type { MobileNativeChatSendIntent } from './mobile-native-chat-send' import { mobileNativeChatComposerShowsStop } from './mobile-native-chat-composer-layout' import type { MobileNativeChatStructuredComposer } from './mobile-native-chat-structured-composer' import { readMobileNativeChatPickedSkills } from './mobile-native-chat-draft-store' diff --git a/mobile/src/session/native-chat/composer/MobileNativeChatSendButton.tsx b/mobile/src/session/native-chat/composer/MobileNativeChatSendButton.tsx index cde9442e07c..af5a2a79dc9 100644 --- a/mobile/src/session/native-chat/composer/MobileNativeChatSendButton.tsx +++ b/mobile/src/session/native-chat/composer/MobileNativeChatSendButton.tsx @@ -16,7 +16,7 @@ import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { useKeyboardPersistingTaps } from '../../../platform/keyboard-persisting-taps' import { colors } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' +import type { MobileNativeChatSendIntent } from './mobile-native-chat-send' const FOLLOW_UP_ICON = { queue: ListEnd, steer: CornerDownRight } as const diff --git a/mobile/src/session/native-chat/composer/mobile-draft-inputs-scaling.test.ts b/mobile/src/session/native-chat/composer/mobile-draft-inputs-scaling.test.ts deleted file mode 100644 index 8155332bd37..00000000000 --- a/mobile/src/session/native-chat/composer/mobile-draft-inputs-scaling.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { createElement } from 'react' -import { act, create } from 'react-test-renderer' -import { afterEach, expect, it, vi } from 'vitest' -import { useMobileNativeChatFileAttachments } from './use-mobile-native-chat-file-attachments' -import { useMobileNativeChatImageAttachments } from './use-mobile-native-chat-image-attachments' -import { - useMobileNativeChatDraft, - setMobileNativeChatDraftText, - resetMobileNativeChatDraftStoreForTests -} from './mobile-native-chat-draft-store' -import { - subscribeMobileNativeChatDraftInputs, - updateMobileNativeChatDraftInputs, - closeMobileNativeChatEditInputs, - useMobileNativeChatDraftInputs -} from './mobile-native-chat-draft-inputs' - -const picker = vi.hoisted(() => ({ pickFiles: vi.fn(), pickImages: vi.fn() })) -vi.mock('../../../platform/media-picker', () => ({ useMediaPicker: () => picker })) -afterEach(resetMobileNativeChatDraftStoreForTests) - -it.each([20, 200])( - 'isolates mounted text, input, image and file consumers across %i other scopes', - async (count) => { - const scopes = [ - 'own-host:target', - ...Array.from({ length: count }, (_, i) => `other-host:${i}`) - ] - const own = scopes[0] - const renders = scopes.map(() => ({ text: 0, input: 0, image: 0, file: 0 })) - const values: { - text: string - pending: number - images: readonly string[] - files: readonly string[] - }[] = scopes.map(() => ({ text: '', pending: 0, images: [], files: [] })) - const upstream = scopes.map(() => vi.fn()) - const releases = scopes.map((scope, i) => - subscribeMobileNativeChatDraftInputs(scope, upstream[i]) - ) - function Text({ index }: { index: number }) { - values[index].text = useMobileNativeChatDraft(scopes[index]) - renders[index].text++ - return null - } - function Input({ index }: { index: number }) { - values[index].pending = useMobileNativeChatDraftInputs(scopes[index]).pending ?? 0 - renders[index].input++ - return null - } - function File({ index }: { index: number }) { - const file = useMobileNativeChatFileAttachments({ - client: null, - sessionId: null, - scopeKey: scopes[index], - enabled: false, - showToast: vi.fn() - }) - values[index].files = file.files.map((entry) => entry.path ?? '') - renders[index].file++ - return null - } - function Image({ index }: { index: number }) { - const image = useMobileNativeChatImageAttachments({ - client: null, - agent: 'codex', - activeHandleRef: { current: null }, - deviceTokenRef: { current: null }, - getActiveWorktreeConnectionId: async () => null, - connState: 'disconnected', - scopeKey: scopes[index], - enabled: false, - structuredNativeChat: true, - showToast: vi.fn(), - onSendError: vi.fn(), - baseSend: async () => 'rejected', - readSeededLaunchDraft: () => null - }) - values[index].images = image.attachments.map((entry) => entry.path) - renders[index].image++ - return null - } - let view: ReturnType | undefined - await act(async () => { - view = create( - createElement( - 'fixture', - {}, - scopes.flatMap((_, index) => [ - createElement(Text, { key: `t${index}`, index }), - createElement(Input, { key: `p${index}`, index }), - createElement(File, { key: `f${index}`, index }), - createElement(Image, { key: `i${index}`, index }) - ]) - ) - ) - }) - renders.forEach((entry) => { - entry.text = entry.input = entry.image = entry.file = 0 - }) - try { - await act(async () => setMobileNativeChatDraftText(own, 'own text')) - expect(values[0].text).toBe('own text') - expect(renders[0]).toEqual({ text: 1, input: 0, image: 0, file: 0 }) - await act(async () => - updateMobileNativeChatDraftInputs(own, (current) => ({ ...current, pending: 1 })) - ) - expect(values[0].pending).toBe(1) - await act(async () => - updateMobileNativeChatDraftInputs(own, (current) => ({ - ...current, - images: [{ id: 'image', path: '/saved.png', previewUri: 'file:///saved.png' }] - })) - ) - expect(values[0].images).toEqual(['/saved.png']) - await act(async () => - updateMobileNativeChatDraftInputs(own, (current) => ({ - ...current, - files: [ - { id: 'file', path: '/saved.txt', name: 'saved.txt', mime: 'text/plain', byteLength: 1 } - ] - })) - ) - expect(values[0].files).toEqual(['/saved.txt']) - expect(renders[0]).toEqual({ text: 1, input: 3, image: 2, file: 1 }) - expect(upstream[0]).toHaveBeenCalledTimes(3) - for (let index = 1; index < scopes.length; index++) { - expect(renders[index]).toEqual({ text: 0, input: 0, image: 0, file: 0 }) - expect(upstream[index]).not.toHaveBeenCalled() - } - } finally { - await act(async () => view?.unmount()) - releases.forEach((release) => release()) - } - await act(async () => - updateMobileNativeChatDraftInputs(own, (current) => ({ ...current, pending: 0 })) - ) - expect(renders[0]).toEqual({ text: 1, input: 3, image: 2, file: 1 }) - expect(upstream[0]).toHaveBeenCalledTimes(3) - } -) -it.each([20, 200])( - 'notifies only affected scopes across %i retained input subscribers and retires listeners', - (count) => { - const idle = vi.fn() - const releases = Array.from({ length: count }, (_, index) => - subscribeMobileNativeChatDraftInputs(`idle-${index}`, idle) - ) - const source = vi.fn() - const destination = vi.fn() - releases.push( - subscribeMobileNativeChatDraftInputs('queued-edit:one', source), - subscribeMobileNativeChatDraftInputs('bottom', destination) - ) - updateMobileNativeChatDraftInputs('queued-edit:one', (inputs) => ({ ...inputs, pending: 1 })) - expect(source).toHaveBeenCalledTimes(1) - expect(idle).not.toHaveBeenCalled() - closeMobileNativeChatEditInputs('queued-edit:one', 'bottom') - expect(source).toHaveBeenCalledTimes(2) - expect(destination).toHaveBeenCalledTimes(1) - expect(idle).not.toHaveBeenCalled() - releases.forEach((release) => release()) - updateMobileNativeChatDraftInputs('bottom', (inputs) => ({ ...inputs, pending: 0 })) - expect(destination).toHaveBeenCalledTimes(1) - } -) diff --git a/mobile/src/session/native-chat/mobile-native-chat-attach-actions.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-attach-actions.ts similarity index 93% rename from mobile/src/session/native-chat/mobile-native-chat-attach-actions.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-attach-actions.ts index 4b855587cfb..aae916a7d71 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-attach-actions.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-attach-actions.ts @@ -1,6 +1,6 @@ import { AtSign, Image, Package, Paperclip, Terminal } from 'lucide-react-native' -import type { ActionSheetAction } from '../../components/ActionSheetModal' -import { translate } from '../../i18n/mobile-i18n' +import type { ActionSheetAction } from '../../../components/ActionSheetModal' +import { translate } from '../../../i18n/mobile-i18n' // Dismiss the sheet before opening pickers because iOS cannot present them over its Modal. export function mobileNativeChatAttachActions(args: { diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.ts index ecac1d211d2..ea5597fd311 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-composer-menu.ts @@ -1,6 +1,7 @@ import type { AgentSessionConversationCommand } from '../../../../../src/shared/agent-session/agent-session-conversation-command' import type { AgentSessionSlashCommand } from '../../../../../src/shared/agent-session/agent-session-wire' import { + getAgentAnsweredNativeChatCommands, getNativeChatAgentProfile, type NativeChatAgentProfile } from '../../../../../src/shared/native-chat/native-chat-agent-profiles' @@ -19,7 +20,6 @@ import type { SlashCommandSuggestion } from '../../../../../src/shared/native-chat/input/native-chat-slash-commands' import { structuredSessionSlashCommands } from '../../../../../src/shared/agent-session/view/structured-agent-session-composer' -import { getMobileNativeChatCommands } from '../mobile-native-chat-send-classification' // Why: the menu is a plain ScrollView right above a streaming transcript, so it mounts a bounded // number of rows; typing narrows the rest. @@ -66,7 +66,7 @@ export function mobileComposerCatalog({ structured.compactInstructions, interactionModeSupported ) - : getMobileNativeChatCommands(agent) + : getAgentAnsweredNativeChatCommands(agent) return { profile: getNativeChatAgentProfile(agent), commands: nativeChatPickerCommands(agent, commands, discovery.skills), diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-inputs.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-inputs.ts index 337c2995584..c6f5e4d0f6a 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-inputs.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-inputs.ts @@ -225,13 +225,6 @@ export function captureMobileNativeChatTextDestination(): return focusedScope ? textDestinations.get(focusedScope)?.() : undefined } -export function resetMobileNativeChatDraftInputsForTests(): void { - drafts.clear() - destinations.clear() - textDestinations.clear() - focusedScope = null -} - export function pruneMobileNativeChatDraftSkills( scope: string, text: string, diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-persistence.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-persistence.ts index 80a77014ac6..68c3989edac 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-persistence.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-persistence.ts @@ -19,7 +19,6 @@ import { deviceStoreDraftBackend, durableDraftRow, durableDraftRowKey, - resetDurableDraftRowsForTests, type DurableDraftRow, type DurableDraftRowBackend } from '../../../drafts/durable-draft-rows' @@ -204,8 +203,3 @@ export function closeSavedMobileNativeChatDraft(scope: string): void { tab.workspace.row.save('immediate') } } - -export function resetMobileNativeChatDraftPersistenceForTests(): void { - resetDurableDraftRowsForTests() - workspaces.clear() -} diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.ts index 8ecab906278..5c5a85fad74 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-reconcile.ts @@ -21,10 +21,6 @@ export type UnconfirmedSend = { deadline: ReturnType | null } -export function normalizedUserText(message: NativeChatMessage): string | null { - return normalizedNativeChatUserMessageText(message) -} - export type PendingImagePreviewEcho = Pick< NativeChatPendingSend, 'id' | 'text' | 'sentAt' | 'afterMessageId' | 'matchingOccurrence' @@ -161,7 +157,7 @@ export function findLandedImagePreviewEchoes( return false } if (targetText) { - const text = normalizedUserText(message) + const text = normalizedNativeChatUserMessageText(message) if (text === null) { return false } @@ -199,7 +195,9 @@ export function findLandedUnconfirmedSends( if (message.role !== 'user') { continue } - const key = isImageSourceUserTurn(message) ? '' : (normalizedUserText(message) ?? '') + const key = isImageSourceUserTurn(message) + ? '' + : (normalizedNativeChatUserMessageText(message) ?? '') const current = userMessagesByText.get(key) ?? [] current.push({ id: message.id, index }) userMessagesByText.set(key, current) diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts index c4bf647ba36..1ea977b8fdc 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.test.ts @@ -1,13 +1,20 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' import { - resetMobileNativeChatDraftStoreForTests, + closeMobileNativeChatTabDraft, useMobileNativeChatDraft } from './mobile-native-chat-draft-store' -import { resetMobileNativeChatDraftInputsForTests } from './mobile-native-chat-draft-inputs' + +import { loadMobileNativeChatDrafts } from './mobile-native-chat-draft-persistence' + +vi.mock('@react-native-async-storage/async-storage', async () => { + const { createInMemoryAsyncStorage } = + await import('../../../test-support/in-memory-async-storage') + return { default: createInMemoryAsyncStorage() } +}) type DraftState = ReturnType & { composerText: string } @@ -15,10 +22,12 @@ describe('mobile native-chat draft store', () => { let renderer: ReactTestRenderer | null = null let state: DraftState | null = null - afterEach(() => { + afterEach(async () => { act(() => renderer?.unmount()) - resetMobileNativeChatDraftStoreForTests() - resetMobileNativeChatDraftInputsForTests() + await loadMobileNativeChatDrafts('host', 'worktree') + closeMobileNativeChatTabDraft('host\0worktree\0a') + closeMobileNativeChatTabDraft('host\0worktree\0b') + await loadMobileNativeChatDrafts('host', 'worktree') renderer = null state = null }) diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.ts index 565990929b3..ba17d73c0d9 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-draft-store.ts @@ -2,7 +2,6 @@ import { useSyncExternalStore, type SetStateAction } from 'react' import type { AgentJournalSkillRef } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' import { appendReturnedDraftText } from '../../../../../src/shared/returned-draft-text' import { - resetMobileNativeChatDraftInputsForTests, returnMobileNativeChatDraftAttachments, closeMobileNativeChatEditInputs, mobileNativeChatDraftDestination, @@ -18,7 +17,6 @@ import { readMobileNativeChatInFlightSend, readMobileNativeChatInFlightSendsByScope, registerSavedMobileNativeChatInFlightSends, - resetMobileNativeChatInFlightSendsForTests, retireMobileNativeChatInFlightSends, restoredImages, restoredFiles, @@ -28,13 +26,11 @@ import { import { dropMobileNativeChatOfflineSends, mobileNativeChatOfflineSendScopes, - resetMobileNativeChatOfflineSendsForTests, restoreMobileNativeChatOfflineSends, savedMobileNativeChatOfflineSends } from './mobile-native-chat-offline-sends' import { closeSavedMobileNativeChatDraft, - resetMobileNativeChatDraftPersistenceForTests, retainSavedMobileNativeChatDrafts, saveMobileNativeChatDraft, setMobileNativeChatDraftSource @@ -306,15 +302,6 @@ export function useMobileNativeChatDraft(draftKey: string | null): string { return useSyncExternalStore(subscribe, read, read) } -export function resetMobileNativeChatDraftStoreForTests(): void { - resetMobileNativeChatDraftInputsForTests() - drafts.clear() - resetMobileNativeChatInFlightSendsForTests() - resetMobileNativeChatOfflineSendsForTests() - mobileNativeChatLaunchDraftSeeds.clear() - resetMobileNativeChatDraftPersistenceForTests() -} - export type MobileComposerWriter = ((text: string) => boolean) & { captureOrigin?: () => (text: string) => boolean } diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-file-attachment.test.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-file-attachment.test.ts index c6a04295615..6a373e8674f 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-file-attachment.test.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-file-attachment.test.ts @@ -1,26 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - MobileFileTooLargeError, - type PickedMobileFile -} from '../../../platform/media-picker-contract' -import { - nativeChatTextWithFileReferences, - uploadPickedNativeChatFiles, - type PendingNativeChatFile -} from './mobile-native-chat-file-attachment' - -function picked(name: string): PickedMobileFile { - return { name, mime: 'text/plain', byteLength: 1, base64: 'YQ==' } -} - -async function* pickedFiles(files: PickedMobileFile[], thenThrow?: Error) { - for (const file of files) { - yield file - } - if (thenThrow) { - throw thenThrow - } -} +import { nativeChatTextWithFileReferences } from './mobile-native-chat-file-attachment' describe('nativeChatTextWithFileReferences', () => { it('references each stored file on a line after the text', () => { @@ -39,64 +18,3 @@ describe('nativeChatTextWithFileReferences', () => { expect(nativeChatTextWithFileReferences('hi', [])).toBe('hi') }) }) - -describe('uploadPickedNativeChatFiles', () => { - function run(files: AsyncIterable, failName?: string) { - const chips = new Map() - const errors: string[] = [] - const pickedChips: PendingNativeChatFile[] = [] - let id = 0 - const done = uploadPickedNativeChatFiles({ - picked: files, - upload: async (file) => { - if (file.name === failName) { - throw new Error('host refused') - } - return { path: `/store/${file.name}`, name: file.name, byteLength: 1 } - }, - nextId: () => `file-${(id += 1)}`, - onPicked: (file) => { - pickedChips.push(file) - chips.set(file.id, file) - }, - onStored: (fileId, stored) => { - const chip = chips.get(fileId) - if (chip) { - chips.set(fileId, { ...chip, path: stored.path }) - } - }, - onFailed: (fileId) => chips.delete(fileId), - onError: (message) => errors.push(message) - }) - return { chips, errors, pickedChips, done } - } - - it('keeps every stored file as a chip with its host path', async () => { - const { chips, errors, pickedChips, done } = run( - pickedFiles([picked('a.txt'), picked('b.txt')]) - ) - await done - expect(pickedChips).toEqual([ - { id: 'file-1', name: 'a.txt', mime: 'text/plain', byteLength: 1, path: null }, - { id: 'file-2', name: 'b.txt', mime: 'text/plain', byteLength: 1, path: null } - ]) - expect([...chips.values()].map((chip) => chip.path)).toEqual(['/store/a.txt', '/store/b.txt']) - expect(errors).toEqual([]) - }) - - it('drops the chip of a failed upload, says which file, and carries on', async () => { - const { chips, errors, done } = run(pickedFiles([picked('a.txt'), picked('b.txt')]), 'a.txt') - await done - expect([...chips.values()].map((chip) => chip.name)).toEqual(['b.txt']) - expect(errors).toEqual(["Couldn't upload a.txt"]) - }) - - it('keeps what was stored before the picker refused an oversized file', async () => { - const { chips, errors, done } = run( - pickedFiles([picked('a.txt')], new MobileFileTooLargeError('clip.mov')) - ) - await done - expect([...chips.values()].map((chip) => chip.name)).toEqual(['a.txt']) - expect(errors).toEqual([new MobileFileTooLargeError('clip.mov').message]) - }) -}) diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-image-scope-state.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-image-scope-state.ts deleted file mode 100644 index 3429717ed67..00000000000 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-image-scope-state.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { PendingNativeChatImage } from './mobile-native-chat-image-attachment' - -export const NO_NATIVE_CHAT_IMAGE_ATTACHMENTS: PendingNativeChatImage[] = [] - -export type MobileNativeChatImagesByScope = Record - -export function withScopeAttachments( - byScope: Readonly>, - scope: string, - next: List -): Record { - if (next.length > 0) { - return { ...byScope, [scope]: next } - } - const remaining = { ...byScope } - delete remaining[scope] - return remaining -} diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts index 130b89692b6..f94eeab180b 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts @@ -5,9 +5,9 @@ import { MOBILE_NATIVE_CHAT_MIN_WRITE_TIMEOUT_MS, openMobileNativeChatSendBudget, readMobileNativeChatInputClear -} from '../mobile-native-chat-send' +} from './mobile-native-chat-send' import { nativeChatTerminalWrite } from '../../../terminal/session/mobile-session-terminal-operations' -import type { MobileNativeChatRpcSender } from '../mobile-native-chat-send' +import type { MobileNativeChatRpcSender } from './mobile-native-chat-send' // Wait for the TUI to register bracketed image attachments before text/Enter arrives. export const MOBILE_NATIVE_CHAT_IMAGE_SETTLE_MS = 300 diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-in-flight-sends.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-in-flight-sends.ts index 08d94b1b7cc..d979ebb5d35 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-in-flight-sends.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-in-flight-sends.ts @@ -5,8 +5,8 @@ import { updateMobileNativeChatDraftInputs } from './mobile-native-chat-draft-inputs' import type { PendingNativeChatFile } from './mobile-native-chat-file-attachment' -import type { MobileNativeChatSendOrigin } from '../mobile-native-chat-pending-echo' -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' +import type { MobileNativeChatSendOrigin } from '../transcript/mobile-native-chat-pending-echo' +import type { MobileNativeChatSendIntent } from './mobile-native-chat-send' import { saveMobileNativeChatDraft } from './mobile-native-chat-draft-persistence' import type { MobileNativeChatInFlightSend, @@ -215,7 +215,3 @@ export function retireMobileNativeChatInFlightSends(scope: string): void { } } } - -export function resetMobileNativeChatInFlightSendsForTests(): void { - inFlightSends.clear() -} diff --git a/mobile/src/session/mobile-native-chat-message-hand-back.test.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-message-hand-back.test.ts similarity index 88% rename from mobile/src/session/mobile-native-chat-message-hand-back.test.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-message-hand-back.test.ts index 697c0dd00af..bb086a110ab 100644 --- a/mobile/src/session/mobile-native-chat-message-hand-back.test.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-message-hand-back.test.ts @@ -5,19 +5,19 @@ import { returnMobileNativeChatMessageToComposer } from './mobile-native-chat-me import { readMobileNativeChatDraftInputs, updateMobileNativeChatDraftInputs -} from './native-chat/composer/mobile-native-chat-draft-inputs' +} from './mobile-native-chat-draft-inputs' import { - resetMobileNativeChatDraftStoreForTests, + closeMobileNativeChatTabDraft, setMobileNativeChatDraftText, useMobileNativeChatDraft -} from './native-chat/composer/mobile-native-chat-draft-store' +} from './mobile-native-chat-draft-store' let renderer: ReactTestRenderer | null = null let text = '' afterEach(() => { act(() => renderer?.unmount()) - resetMobileNativeChatDraftStoreForTests() + closeMobileNativeChatTabDraft('scope') renderer = null }) diff --git a/mobile/src/session/mobile-native-chat-message-hand-back.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-message-hand-back.ts similarity index 73% rename from mobile/src/session/mobile-native-chat-message-hand-back.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-message-hand-back.ts index 7391481887f..a3a288a70e7 100644 --- a/mobile/src/session/mobile-native-chat-message-hand-back.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-message-hand-back.ts @@ -1,12 +1,12 @@ -import type { AgentJournalMessageItem } from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import { queuedEditTextForKeptImages } from '../../../src/shared/queued-message-edit' -import { appendReturnedDraftText } from '../../../src/shared/returned-draft-text' -import { mobileDraftImageOfBlock } from './mobile-queued-message-edit-draft' +import type { AgentJournalMessageItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import { queuedEditTextForKeptImages } from '../../../../../src/shared/queued-message-edit' +import { appendReturnedDraftText } from '../../../../../src/shared/returned-draft-text' +import { mobileDraftImageOfBlock } from '../input/mobile-queued-message-edit-draft' import { readMobileNativeChatDraftInputs, updateMobileNativeChatDraftInputs -} from './native-chat/composer/mobile-native-chat-draft-inputs' -import { setMobileNativeChatDraftText } from './native-chat/composer/mobile-native-chat-draft-store' +} from './mobile-native-chat-draft-inputs' +import { setMobileNativeChatDraftText } from './mobile-native-chat-draft-store' /** Append a sent message's text and images after what is typed; stable chip ids keep a repeat from doubling them. */ export function returnMobileNativeChatMessageToComposer( diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-offline-sends.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-offline-sends.ts index 53ab4be9eb2..e2e74e4acc8 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-offline-sends.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-offline-sends.ts @@ -186,7 +186,3 @@ export function useMobileNativeChatOfflineSends( }, []) return useSyncExternalStore(subscribe, () => readMobileNativeChatOfflineSends(scope)) } - -export function resetMobileNativeChatOfflineSendsForTests(): void { - queues.clear() -} diff --git a/mobile/src/session/native-chat/mobile-native-chat-send-classification.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-send-classification.ts similarity index 62% rename from mobile/src/session/native-chat/mobile-native-chat-send-classification.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-send-classification.ts index 77febaf434c..e228d61446f 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-send-classification.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-send-classification.ts @@ -3,17 +3,13 @@ import { getAgentAnsweredNativeChatCommands, getNativeChatAgentProfile -} from '../../../../src/shared/native-chat/native-chat-agent-profiles' +} from '../../../../../src/shared/native-chat/native-chat-agent-profiles' import { classifyNativeChatSend, - type NativeChatSendClassification, - type SlashCommandSuggestion -} from '../../../../src/shared/native-chat/input/native-chat-slash-commands' + type NativeChatSendClassification +} from '../../../../../src/shared/native-chat/input/native-chat-slash-commands' // Terminal mobile chat lacks desktop-owned command answers and reply rows. -export function getMobileNativeChatCommands(agent: string): readonly SlashCommandSuggestion[] { - return getAgentAnsweredNativeChatCommands(agent) -} export function classifyMobileNativeChatSend( agent: string | null, @@ -26,7 +22,7 @@ export function classifyMobileNativeChatSend( const profile = getNativeChatAgentProfile(agent) return classifyNativeChatSend( text, - getMobileNativeChatCommands(agent), + getAgentAnsweredNativeChatCommands(agent), pickerSkillOriginToken, profile?.skillPrefix ?? null ) diff --git a/mobile/src/session/native-chat/mobile-native-chat-send.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-send.ts similarity index 88% rename from mobile/src/session/native-chat/mobile-native-chat-send.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-send.ts index c91e15906ee..9c980127a97 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-send.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-send.ts @@ -1,22 +1,22 @@ -import type { RpcClient } from '../../transport/rpc/rpc-client-contract' -import type { PendingNativeChatImage } from './composer/mobile-native-chat-image-attachment' -import type { PendingNativeChatFile } from './composer/mobile-native-chat-file-attachment' -import { isRpcDeliveryUnknown } from '../../transport/rpc/rpc-delivery-ambiguity' -import '../../transport/connection/stable-logical-rpc-client' -import { isLogicalClientCutoverError } from '../../transport/connection/logical-client-cutover-error' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { PendingNativeChatImage } from './mobile-native-chat-image-attachment' +import type { PendingNativeChatFile } from './mobile-native-chat-file-attachment' +import { isRpcDeliveryUnknown } from '../../../transport/rpc/rpc-delivery-ambiguity' +import '../../../transport/connection/stable-logical-rpc-client' +import { isLogicalClientCutoverError } from '../../../transport/connection/logical-client-cutover-error' import { nativeChatTerminalWrite, nativeChatTerminalDraftRead -} from '../../terminal/session/mobile-session-terminal-operations' -import { typeAgentTuiCommand } from '../../../../src/shared/agent-launch/startup/agent-tui-command-typing' -import { readTerminalSendAcknowledgment } from '../../../../src/shared/terminal/pty/lifecycle/terminal-send-acknowledgment' +} from '../../../terminal/session/mobile-session-terminal-operations' +import { typeAgentTuiCommand } from '../../../../../src/shared/agent-launch/startup/agent-tui-command-typing' +import { readTerminalSendAcknowledgment } from '../../../../../src/shared/terminal/pty/lifecycle/terminal-send-acknowledgment' import { AGENT_TUI_CLEAR_MAX_LINES, countAgentTuiInputLines, buildAgentTuiClearInputForText -} from '../../../../src/shared/agent-launch/startup/agent-tui-input-clear' +} from '../../../../../src/shared/agent-launch/startup/agent-tui-input-clear' -import type { AgentJournalSkillRef } from '../../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentJournalSkillRef } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' export type MobileNativeChatRpcSender = Parameters[0] diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-skill-operations.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-skill-operations.ts index fc06f8e0be0..de98372945e 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-skill-operations.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-skill-operations.ts @@ -2,13 +2,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../../transport import { rpcResultVariant } from '../../../transport/rpc/rpc-operation-result-reader' import '../../../terminal/terminal-reply-schema' import { skillDiscoveryReplySchema } from './mobile-native-chat-skill-reply-schema' -import '../../agent-launch/mobile-agent-detection-reply-schema' -import '../../../files/session-documents/mobile-markdown-tab-reply-schema' -import '../../../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../../../terminal/session/mobile-session-terminal-reply-schema' -import '../../../worktree/session-worktree-record-reply-schema' -import '../../../terminal/quick-commands/mobile-quick-command-reply-schema' -import '../../../files/workspace-file-paths-reply-schema' // Mobile names the workspace and lets the host resolve and scan it; see SKILL_DISCOVER_WORKSPACE_CAPABILITY. export const nativeChatSkillDiscoveryRead = bindDeferredRpcOperation( diff --git a/mobile/src/session/native-chat/mobile-native-chat-stale-input.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts similarity index 96% rename from mobile/src/session/native-chat/mobile-native-chat-stale-input.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts index f0885dd157d..7762f7762ea 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-stale-input.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts @@ -1,4 +1,4 @@ -import { pasteMobileNativeChatImagePaths } from './composer/mobile-native-chat-image-send' +import { pasteMobileNativeChatImagePaths } from './mobile-native-chat-image-send' import type { MobileNativeChatRpcSender } from './mobile-native-chat-send' // The condition tracked here — a bracketed image paste left sitting on the agent's diff --git a/mobile/src/session/native-chat/composer/mobile-native-chat-structured-composer.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-structured-composer.ts index 5efa1fe1f80..27de249feb3 100644 --- a/mobile/src/session/native-chat/composer/mobile-native-chat-structured-composer.ts +++ b/mobile/src/session/native-chat/composer/mobile-native-chat-structured-composer.ts @@ -1,6 +1,6 @@ import type { MobileComposerStructuredCatalog } from './mobile-native-chat-composer-menu' import type { PendingNativeChatFile } from './mobile-native-chat-file-attachment' -import type { MobileStructuredThreadGoal } from '../../use-mobile-structured-thread-goal' +import type { MobileStructuredThreadGoal } from '../input/use-mobile-structured-thread-goal' export type MobileNativeChatStructuredComposer = { catalog: MobileComposerStructuredCatalog diff --git a/mobile/src/session/native-chat/mobile-native-chat-terminal-write-lock.test.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-terminal-write-lock.test.ts similarity index 100% rename from mobile/src/session/native-chat/mobile-native-chat-terminal-write-lock.test.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-terminal-write-lock.test.ts diff --git a/mobile/src/session/native-chat/mobile-native-chat-terminal-write-lock.ts b/mobile/src/session/native-chat/composer/mobile-native-chat-terminal-write-lock.ts similarity index 100% rename from mobile/src/session/native-chat/mobile-native-chat-terminal-write-lock.ts rename to mobile/src/session/native-chat/composer/mobile-native-chat-terminal-write-lock.ts diff --git a/mobile/src/session/mobile-structured-composer-command.ts b/mobile/src/session/native-chat/composer/mobile-structured-composer-command.ts similarity index 85% rename from mobile/src/session/mobile-structured-composer-command.ts rename to mobile/src/session/native-chat/composer/mobile-structured-composer-command.ts index 04d80c271fb..55c0ea4432f 100644 --- a/mobile/src/session/mobile-structured-composer-command.ts +++ b/mobile/src/session/native-chat/composer/mobile-structured-composer-command.ts @@ -2,26 +2,26 @@ import { agentSessionConversationCommandFields, type AgentSessionConversationCommand, type AgentSessionConversationCommandResult -} from '../../../src/shared/agent-session/agent-session-conversation-command' -import type { AgentSessionFailureFact } from '../../../src/shared/agent-session/failures/agent-session-failure' +} from '../../../../../src/shared/agent-session/agent-session-conversation-command' +import type { AgentSessionFailureFact } from '../../../../../src/shared/agent-session/failures/agent-session-failure' import { dispatchStructuredAgentSessionComposerCommand, isStructuredAgentSessionHostCommand, type StructuredAgentSessionCommandRefusalCause, type StructuredAgentSessionComposerOptions -} from '../../../src/shared/agent-session/view/structured-agent-session-composer' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-composer' import { conversationCommandReply, heldConversationCommandParts, structuredConversationCommandHold -} from '../../../src/shared/structured-conversation-command-hold' -import { mobileAgentSessionWriteNoticeText } from './agent-session-write-notice-copy' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { translate } from '../i18n/mobile-i18n' -import { sayMobileAgentSessionFailure } from './agent-session-failure-copy' -import type { MobileNativeChatSendOutcome } from './native-chat/mobile-native-chat-send' -import { requestStructuredAgentSessionMutation } from './mobile-structured-agent-session-rpc' -import type { MobileNativeChatSendErrorReporter } from './native-chat/use-mobile-native-chat-send-error' +} from '../../../../../src/shared/structured-conversation-command-hold' +import { mobileAgentSessionWriteNoticeText } from '../agent-session/agent-session-write-notice-copy' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { translate } from '../../../i18n/mobile-i18n' +import { sayMobileAgentSessionFailure } from '../agent-session/agent-session-failure-copy' +import type { MobileNativeChatSendOutcome } from './mobile-native-chat-send' +import { requestStructuredAgentSessionMutation } from '../agent-session/mobile-structured-agent-session-rpc' +import type { MobileNativeChatSendErrorReporter } from './use-mobile-native-chat-send-error' /** What the phone shows at keypress that holds a command; `background` also counts the strip mid-turn. */ export type MobileStructuredCommandHolds = Readonly< diff --git a/mobile/src/session/mobile-structured-send-delivery.ts b/mobile/src/session/native-chat/composer/mobile-structured-send-delivery.ts similarity index 77% rename from mobile/src/session/mobile-structured-send-delivery.ts rename to mobile/src/session/native-chat/composer/mobile-structured-send-delivery.ts index d9f3eb2c58f..129006f77ca 100644 --- a/mobile/src/session/mobile-structured-send-delivery.ts +++ b/mobile/src/session/native-chat/composer/mobile-structured-send-delivery.ts @@ -1,9 +1,9 @@ -import type { AgentSessionSendResult } from '../../../src/shared/agent-session/agent-session-wire' -import { agentSessionRefusalOperationState } from '../../../src/shared/agent-session/failures/agent-session-refusal-retry' -import type { MobileNativeChatSendOutcome } from './native-chat/mobile-native-chat-send' -import type { StructuredAgentSessionMutationCallResult } from './mobile-structured-agent-session-rpc' -import { STRUCTURED_AGENT_SESSION_SUBMISSION_MISSING } from '../../../src/shared/agent-session/requests/structured-agent-session-send-evidence' -import { translate } from '../i18n/mobile-i18n' +import type { AgentSessionSendResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import { agentSessionRefusalOperationState } from '../../../../../src/shared/agent-session/failures/agent-session-refusal-retry' +import type { MobileNativeChatSendOutcome } from './mobile-native-chat-send' +import type { StructuredAgentSessionMutationCallResult } from '../agent-session/mobile-structured-agent-session-rpc' +import { STRUCTURED_AGENT_SESSION_SUBMISSION_MISSING } from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-evidence' +import { translate } from '../../../i18n/mobile-i18n' export type MobileStructuredSendDelivery = { outcome: MobileNativeChatSendOutcome diff --git a/mobile/src/session/mobile-structured-send-queue-rule.ts b/mobile/src/session/native-chat/composer/mobile-structured-send-queue-rule.ts similarity index 67% rename from mobile/src/session/mobile-structured-send-queue-rule.ts rename to mobile/src/session/native-chat/composer/mobile-structured-send-queue-rule.ts index 12065e7d0a2..8a5f9dd97c0 100644 --- a/mobile/src/session/mobile-structured-send-queue-rule.ts +++ b/mobile/src/session/native-chat/composer/mobile-structured-send-queue-rule.ts @@ -1,9 +1,9 @@ -import { selectAgentSessionCurrentRequests } from '../../../src/shared/agent-session/requests/agent-session-current-requests' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' +import { selectAgentSessionCurrentRequests } from '../../../../../src/shared/agent-session/requests/agent-session-current-requests' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' import { structuredAgentSessionFollowUpsQueue, structuredAgentSessionQueueEnabled -} from '../../../src/shared/agent-session/view/structured-agent-session-queued-cards' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-queued-cards' /** The desktop's rule: the alternate flips the setting first, then the queue's own rules decide. */ export function mobileStructuredSendQueues( diff --git a/mobile/src/session/mobile-structured-send-recorded-read.ts b/mobile/src/session/native-chat/composer/mobile-structured-send-recorded-read.ts similarity index 85% rename from mobile/src/session/mobile-structured-send-recorded-read.ts rename to mobile/src/session/native-chat/composer/mobile-structured-send-recorded-read.ts index 17e47b2199e..8fec033fadb 100644 --- a/mobile/src/session/mobile-structured-send-recorded-read.ts +++ b/mobile/src/session/native-chat/composer/mobile-structured-send-recorded-read.ts @@ -1,9 +1,9 @@ import { useLayoutEffect, useMemo, useRef } from 'react' import * as z from 'zod' -import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc/rpc-operation' -import { rpcResultVariant } from '../transport/rpc/rpc-operation-result-reader' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' +import { bindDeferredRpcOperation, defineRpcOperation } from '../../../transport/rpc/rpc-operation' +import { rpcResultVariant } from '../../../transport/rpc/rpc-operation-result-reader' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { StructuredAgentSessionHostSupport } from '../agent-session/mobile-structured-agent-session-host-support' const sendRecordedRead = bindDeferredRpcOperation( defineRpcOperation({ diff --git a/mobile/src/session/native-chat/unconfirmed-send-deadlines.ts b/mobile/src/session/native-chat/composer/unconfirmed-send-deadlines.ts similarity index 100% rename from mobile/src/session/native-chat/unconfirmed-send-deadlines.ts rename to mobile/src/session/native-chat/composer/unconfirmed-send-deadlines.ts diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-goal-mode.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-goal-mode.ts index 91d7ce33131..5c3abd01481 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-goal-mode.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-goal-mode.ts @@ -3,8 +3,8 @@ import { Goal } from 'lucide-react-native' import type { NativeChatSendFollowUp } from '../../../../../src/shared/native-chat/input/native-chat-follow-up-behavior' import { isBareStructuredAgentSessionGoalCommand } from '../../../../../src/shared/agent-session/view/structured-agent-session-composer' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' -import type { MobileStructuredThreadGoal } from '../../use-mobile-structured-thread-goal' -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' +import type { MobileStructuredThreadGoal } from '../input/use-mobile-structured-thread-goal' +import type { MobileNativeChatSendIntent } from './mobile-native-chat-send' import { MobileNativeChatThreadGoalStrip } from '../input/MobileNativeChatThreadGoalStrip' import { MobileNativeChatComposerModeChip } from './MobileNativeChatComposerModeChip' diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.ts index 2b8005f6de8..4dfbcf863ee 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-composer-tray.ts @@ -8,7 +8,7 @@ import { mobileNativeChatChordHeadId, type MobileNativeChatQueuedMessagesProps } from '../input/MobileNativeChatQueuedMessages' -import type { MobileStructuredBackgroundTasks } from '../../use-mobile-structured-background-tasks' +import type { MobileStructuredBackgroundTasks } from '../input/use-mobile-structured-background-tasks' import { useMobileNativeChatQueuedEditFocus } from '../input/use-mobile-native-chat-queued-edit-focus' import { createMobileQueuedHeadLight } from '../input/mobile-queued-head-light' import { diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-glued-pending.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-glued-pending.test.ts index 09542d51dfb..1db30d2be50 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-glued-pending.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-glued-pending.test.ts @@ -1,24 +1,28 @@ import { createElement } from 'react' import { act, create } from 'react-test-renderer' -import { afterEach, describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' -import { buildMobileNativeChatTransientData } from '../mobile-native-chat-render-data' + import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' import { - resetMobileNativeChatDraftStoreForTests, + closeMobileNativeChatTabDraft, useMobileNativeChatDraft } from './mobile-native-chat-draft-store' +import { loadMobileNativeChatDrafts } from './mobile-native-chat-draft-persistence' + +vi.mock('@react-native-async-storage/async-storage', async () => { + const { createInMemoryAsyncStorage } = + await import('../../../test-support/in-memory-async-storage') + return { default: createInMemoryAsyncStorage() } +}) + type DraftState = ReturnType & { composerText: string } type TestRenderer = { unmount(): void update(element: ReturnType): void } -function userTurn(id: string, text: string, timestamp: number): NativeChatMessage { - return { id, role: 'user', blocks: [{ type: 'text', text }], timestamp, source: 'transcript' } -} - function assistantTurn(id: string, text: string, timestamp: number): NativeChatMessage { return { id, @@ -39,27 +43,15 @@ function imageTurn(id: string, path: string, timestamp: number): NativeChatMessa } } -function renderedPendingTexts( - messages: NativeChatMessage[], - state: DraftState | null -): (string | undefined)[] { - const { data } = buildMobileNativeChatTransientData({ - folded: messages, - streaming: null, - pending: state?.pending ?? [] - }) - return data - .filter((message) => !messages.some((existing) => existing.id === message.id)) - .map((message) => message.blocks.find((block) => block.type === 'text')?.text) -} - describe('useMobileNativeChatDrafts glued pending sends', () => { let renderer: TestRenderer | null = null let state: DraftState | null = null - afterEach(() => { + afterEach(async () => { act(() => renderer?.unmount()) - resetMobileNativeChatDraftStoreForTests() + await loadMobileNativeChatDrafts('host', 'worktree') + closeMobileNativeChatTabDraft('host\0worktree\0tab') + await loadMobileNativeChatDrafts('host', 'worktree') renderer = null state = null }) @@ -109,23 +101,6 @@ describe('useMobileNativeChatDrafts glued pending sends', () => { // Between the older history rows and the rows that land after a send, on the host's clock. const SENT_AT = 3000 - function send(text: string): void { - const origin = state?.captureSendOrigin(text, SENT_AT) - if (origin) { - state?.acceptSend(origin, text) - } - } - - function rapidSend(first: string, second: string): void { - act(() => { - send(first) - send(second) - }) - } - - const pendingTexts = (): (string | undefined)[] | undefined => - state?.pending.map((item) => item.text) - // Hydrating rows give no boundary, so these sends match only rows newer than their send time. describe('sends issued while the transcript is still hydrating', () => { // An image retires only by preview binding; excluding its own echo would strand both the photo and later glued sends. @@ -146,18 +121,4 @@ describe('useMobileNativeChatDrafts glued pending sends', () => { }) // Failed-read emptiness is unknown history, not an empty conversation; old rows must not claim sends against it. - describe('sends issued before any read settled', () => { - it('keeps both bubbles when the late read carries only an older glue-alike', async () => { - await mount([], false, false) - rapidSend('fix the', 'bug') - - const olderIdentical = [ - userTurn('m1', 'fix the bug', 1000), - assistantTurn('m2', 'fixed', 1100) - ] - await update(olderIdentical) - expect(pendingTexts()).toEqual(['fix the', 'bug']) - expect(renderedPendingTexts(olderIdentical, state)).toEqual(['fix the', 'bug']) - }) - }) }) diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-launch-draft.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-launch-draft.test.ts index a6b9e635071..34fbb34186e 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-launch-draft.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-launch-draft.test.ts @@ -1,14 +1,22 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' import { - resetMobileNativeChatDraftStoreForTests, + closeMobileNativeChatTabDraft, useMobileNativeChatDraft } from './mobile-native-chat-draft-store' +import { loadMobileNativeChatDrafts } from './mobile-native-chat-draft-persistence' + +vi.mock('@react-native-async-storage/async-storage', async () => { + const { createInMemoryAsyncStorage } = + await import('../../../test-support/in-memory-async-storage') + return { default: createInMemoryAsyncStorage() } +}) + type DraftState = ReturnType & { composerText: string } function userTextMessage(id: string, text: string): NativeChatMessage { @@ -25,9 +33,12 @@ describe('useMobileNativeChatDrafts launch draft', () => { let renderer: ReactTestRenderer | null = null let state: DraftState | null = null - afterEach(() => { + afterEach(async () => { act(() => renderer?.unmount()) - resetMobileNativeChatDraftStoreForTests() + await loadMobileNativeChatDrafts('host', 'worktree') + closeMobileNativeChatTabDraft('host\0worktree\0a') + closeMobileNativeChatTabDraft('host\0worktree\0b') + await loadMobileNativeChatDrafts('host', 'worktree') renderer = null state = null }) diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-queued-hold.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-queued-hold.test.ts index 69def691118..1bea79e1632 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-queued-hold.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts-queued-hold.test.ts @@ -1,19 +1,29 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' import { - resetMobileNativeChatDraftStoreForTests, + closeMobileNativeChatTabDraft, useMobileNativeChatDraft } from './mobile-native-chat-draft-store' +import { loadMobileNativeChatDrafts } from './mobile-native-chat-draft-persistence' + +vi.mock('@react-native-async-storage/async-storage', async () => { + const { createInMemoryAsyncStorage } = + await import('../../../test-support/in-memory-async-storage') + return { default: createInMemoryAsyncStorage() } +}) + describe('useMobileNativeChatDrafts with queued cards', () => { let renderer: ReactTestRenderer | null = null let state: (ReturnType & { composerText: string }) | null = null - afterEach(() => { + afterEach(async () => { act(() => renderer?.unmount()) - resetMobileNativeChatDraftStoreForTests() + await loadMobileNativeChatDrafts('host', 'worktree') + closeMobileNativeChatTabDraft('host\0worktree\0a') + await loadMobileNativeChatDrafts('host', 'worktree') renderer = null state = null }) diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.test.ts index 3545d6e078a..f5bd3751aae 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.test.ts @@ -4,10 +4,18 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts' import { - resetMobileNativeChatDraftStoreForTests, + closeMobileNativeChatTabDraft, useMobileNativeChatDraft } from './mobile-native-chat-draft-store' +import { loadMobileNativeChatDrafts } from './mobile-native-chat-draft-persistence' + +vi.mock('@react-native-async-storage/async-storage', async () => { + const { createInMemoryAsyncStorage } = + await import('../../../test-support/in-memory-async-storage') + return { default: createInMemoryAsyncStorage() } +}) + type DraftState = ReturnType & { composerText: string } function userTextMessage(id: string, text: string): NativeChatMessage { @@ -34,11 +42,15 @@ describe('useMobileNativeChatDrafts', () => { let renderer: ReactTestRenderer | null = null let state: DraftState | null = null - afterEach(() => { + afterEach(async () => { act(() => renderer?.unmount()) - resetMobileNativeChatDraftStoreForTests() + await loadMobileNativeChatDrafts('host', 'worktree') + closeMobileNativeChatTabDraft('host\0worktree\0a') + closeMobileNativeChatTabDraft('host\0worktree\0b') + await loadMobileNativeChatDrafts('host', 'worktree') renderer = null state = null + vi.useRealTimers() }) function Harness({ @@ -195,7 +207,8 @@ describe('useMobileNativeChatDrafts', () => { ) expect(state?.composerText).toBe('') } finally { - vi.useRealTimers() + act(() => renderer?.unmount()) + renderer = null } }) @@ -354,52 +367,6 @@ describe('useMobileNativeChatDrafts', () => { expect(state?.composerText).toBe(' /clear') }) - it('reconciles an image-only unconfirmed send against the next user turn (no false warning)', async () => { - vi.useFakeTimers() - try { - await mount('a') - await act(async () => - renderer?.update( - createElement(Harness, { tabId: 'a', messages: [assistantTextMessage('a1', 'hi')] }) - ) - ) - const origin = state?.captureSendOrigin('') - const onUnconfirmed = vi.fn() - act(() => { - if (origin) { - state?.holdUnconfirmedSend(origin, '', onUnconfirmed) - } - }) - - await act(async () => - renderer?.update( - createElement(Harness, { - tabId: 'a', - messages: [assistantTextMessage('a1', 'hi'), assistantTextMessage('a2', 'ok')] - }) - ) - ) - await act(async () => - renderer?.update( - createElement(Harness, { - tabId: 'a', - messages: [ - assistantTextMessage('a1', 'hi'), - assistantTextMessage('a2', 'ok'), - userTextMessage('u1', '') - ] - }) - ) - ) - act(() => { - vi.advanceTimersByTime(30_000) - }) - expect(onUnconfirmed).not.toHaveBeenCalled() - } finally { - vi.useRealTimers() - } - }) - it('clears image-only echoes one per landed user turn, not all at once', async () => { await mount('a') await act(async () => @@ -430,59 +397,6 @@ describe('useMobileNativeChatDrafts', () => { expect(state?.pending.map((pending) => pending.images)).toEqual([['file:///b.jpg']]) }) - it('registers no deadline when the transcript echo beat the ambiguous RPC rejection', async () => { - vi.useFakeTimers() - try { - await mount('a') - const origin = state?.captureSendOrigin('ping') - const onUnconfirmed = vi.fn() - - await act(async () => - renderer?.update( - createElement(Harness, { tabId: 'a', messages: [userTextMessage('m1', 'ping')] }) - ) - ) - act(() => { - if (origin) { - state?.holdUnconfirmedSend(origin, 'ping', onUnconfirmed) - } - }) - - expect(vi.getTimerCount()).toBe(0) - act(() => { - vi.advanceTimersByTime(30_000) - }) - expect(onUnconfirmed).not.toHaveBeenCalled() - } finally { - vi.useRealTimers() - } - }) - - it('surfaces uncertainty when no echo lands before the deadline', async () => { - vi.useFakeTimers() - try { - await mount('a') - const origin = state?.captureSendOrigin('ping') - const onUnconfirmed = vi.fn() - act(() => { - if (origin) { - state?.holdUnconfirmedSend(origin, 'ping', onUnconfirmed) - } - }) - - act(() => { - vi.advanceTimersByTime(19_999) - }) - expect(onUnconfirmed).not.toHaveBeenCalled() - act(() => { - vi.advanceTimersByTime(1) - }) - expect(onUnconfirmed).toHaveBeenCalledTimes(1) - } finally { - vi.useRealTimers() - } - }) - it('does not confirm an unconfirmed send against an older identical turn', async () => { vi.useFakeTimers() try { @@ -516,7 +430,8 @@ describe('useMobileNativeChatDrafts', () => { }) expect(onUnconfirmed).toHaveBeenCalledTimes(1) } finally { - vi.useRealTimers() + act(() => renderer?.unmount()) + renderer = null } }) @@ -552,64 +467,8 @@ describe('useMobileNativeChatDrafts', () => { }) expect(onUnconfirmed).toHaveBeenCalledTimes(1) } finally { - vi.useRealTimers() - } - }) - - it('requires one new transcript echo per repeated unconfirmed send', async () => { - vi.useFakeTimers() - try { - await mount('a') - act(() => state?.setComposerText('ping')) - const origin = state?.captureSendOrigin('ping') - const firstUnconfirmed = vi.fn() - const secondUnconfirmed = vi.fn() - act(() => { - if (origin) { - state?.holdUnconfirmedSend(origin, 'ping', firstUnconfirmed) - state?.holdUnconfirmedSend(origin, 'ping', secondUnconfirmed) - } - }) - - await act(async () => - renderer?.update( - createElement(Harness, { tabId: 'a', messages: [userTextMessage('echo-1', 'ping')] }) - ) - ) - act(() => { - vi.advanceTimersByTime(30_000) - }) - - expect(firstUnconfirmed).not.toHaveBeenCalled() - expect(secondUnconfirmed).toHaveBeenCalledTimes(1) - } finally { - vi.useRealTimers() - } - }) - - it('does not retain a deadline when an ambiguous send settles after unmount', async () => { - vi.useFakeTimers() - try { - await mount('a') - const origin = state?.captureSendOrigin('ping') - const holdUnconfirmedSend = state?.holdUnconfirmedSend - const onUnconfirmed = vi.fn() act(() => renderer?.unmount()) renderer = null - - act(() => { - if (origin) { - holdUnconfirmedSend?.(origin, 'ping', onUnconfirmed) - } - }) - - expect(vi.getTimerCount()).toBe(0) - act(() => { - vi.advanceTimersByTime(30_000) - }) - expect(onUnconfirmed).not.toHaveBeenCalled() - } finally { - vi.useRealTimers() } }) @@ -661,7 +520,8 @@ describe('useMobileNativeChatDrafts', () => { }) expect(onUnconfirmed).toHaveBeenCalledTimes(1) } finally { - vi.useRealTimers() + act(() => renderer?.unmount()) + renderer = null } }) diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.ts index 814f11e6d90..456058a6867 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-drafts.ts @@ -18,7 +18,7 @@ import { normalizeImageTranscriptMessages, normalizeNativeChatUserText as normalizeReconcileText } from '../../../../../src/shared/native-chat/transcript/native-chat-image-transcript-markers' -import { useMobileNativeChatUnconfirmedSends } from '../use-mobile-native-chat-unconfirmed-sends' +import { useMobileNativeChatUnconfirmedSends } from './use-mobile-native-chat-unconfirmed-sends' import { appendMobileNativeChatPending, combineMobileNativeChatPending, @@ -28,7 +28,7 @@ import { visibleMobileNativeChatPending, type MobileNativeChatPendingMessage, type MobileNativeChatSendOrigin -} from '../mobile-native-chat-pending-echo' +} from '../transcript/mobile-native-chat-pending-echo' import { mobileNativeChatScopeKey } from '../mobile-native-chat-scope-key' import { mobileNativeChatDraftDestination } from './mobile-native-chat-draft-inputs' import { useMobileNativeChatLaunchDraftSeed } from './use-mobile-native-chat-launch-draft-seed' diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts index f9ed1c439a8..cdae2e0d6e9 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts @@ -1,12 +1,12 @@ -import { resetMobileNativeChatDraftInputsForTests } from './mobile-native-chat-draft-inputs' +import { closeMobileNativeChatEditInputs } from './mobile-native-chat-draft-inputs' import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' import '../../../../../src/shared/agent-launch/startup/agent-tui-input-clear' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import type { RpcResponse, RpcSuccess } from '../../../transport/rpc/rpc-wire-types' -import { resetMobileNativeChatStaleInputForTests } from '../mobile-native-chat-stale-input' -import { resetMobileNativeChatTerminalWritesForTests } from '../mobile-native-chat-terminal-write-lock' +import { resetMobileNativeChatStaleInputForTests } from './mobile-native-chat-stale-input' +import { resetMobileNativeChatTerminalWritesForTests } from './mobile-native-chat-terminal-write-lock' import { useMobileNativeChatImageAttachments } from './use-mobile-native-chat-image-attachments' // Stub the media seam so direct Expo imports would expose unsupported Flow syntax and fail the transform. @@ -79,6 +79,8 @@ function baseArgs(overrides: Partial & Pick): Hook describe('useMobileNativeChatImageAttachments', () => { let renderer: ReactTestRenderer | null = null let hook: Hook | null = null + let releaseConnection: ((id: string | null) => void) | null = null + let firstAttach: Promise | null = null function Harness({ args }: { args: HookArgs }): null { hook = useMobileNativeChatImageAttachments(args) @@ -86,14 +88,20 @@ describe('useMobileNativeChatImageAttachments', () => { } beforeEach(() => { - resetMobileNativeChatDraftInputsForTests() ids.next = 0 pick.mockReset() resetMobileNativeChatStaleInputForTests() resetMobileNativeChatTerminalWritesForTests() }) - afterEach(() => { + afterEach(async () => { act(() => renderer?.unmount()) + closeMobileNativeChatEditInputs(SCOPE_A) + await act(async () => { + releaseConnection?.(null) + await firstAttach + }) + releaseConnection = null + firstAttach = null renderer = null hook = null }) @@ -106,7 +114,6 @@ describe('useMobileNativeChatImageAttachments', () => { it('keeps isAttaching true when a cancelled pick overlaps a genuine in-flight upload', async () => { // Hold a real upload after its counter increment; a concurrent cancelled pick must not decrement work it never started. - let releaseConnection: ((id: string | null) => void) | null = null const client = makeClient([methodNotFound('start'), ok('save', '/tmp/a.png')]) const args = baseArgs({ client: client as unknown as RpcClient, @@ -118,7 +125,6 @@ describe('useMobileNativeChatImageAttachments', () => { mount(args) pick.mockResolvedValue([{ base64: 'AAAA', uri: 'file:///a.jpg' }]) - let firstAttach: Promise | null = null await act(async () => { firstAttach = hook!.attachImage('library') for (let i = 0; i < 50 && !releaseConnection; i++) { diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.ts index b3cfdc53aa1..c5533f5492d 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.ts @@ -21,20 +21,20 @@ import { openMobileNativeChatSendBudget, type MobileNativeChatSendIntent, type MobileNativeChatSendOutcome -} from '../mobile-native-chat-send' +} from './mobile-native-chat-send' import { clearMobileNativeChatInputStale, healMobileNativeChatStaleInput, isMobileNativeChatInputStale, markMobileNativeChatInputStale -} from '../mobile-native-chat-stale-input' +} from './mobile-native-chat-stale-input' import { acquireMobileNativeChatTerminalWrite, releaseMobileNativeChatTerminalWrite -} from '../mobile-native-chat-terminal-write-lock' +} from './mobile-native-chat-terminal-write-lock' import { useMobileNativeChatImageUpload } from './use-mobile-native-chat-image-upload' import type { PendingNativeChatFile } from './mobile-native-chat-file-attachment' -import type { StructuredMobileSendAttachment } from '../../use-mobile-structured-send-with-outcome' +import type { StructuredMobileSendAttachment } from './use-mobile-structured-send-with-outcome' type CurrentRef = { readonly current: T } type ShowToast = (message: string, durationMs?: number) => void diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-input-lease.test.ts similarity index 57% rename from mobile/src/session/native-chat/use-mobile-native-chat-input-lease.test.ts rename to mobile/src/session/native-chat/composer/use-mobile-native-chat-input-lease.test.ts index 8b99f5736b4..0126af0b4c0 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-input-lease.test.ts @@ -1,10 +1,7 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { - useMobileNativeChatInputLease, - useSettledMobileNativeChatInputLock -} from './use-mobile-native-chat-input-lease' +import { afterEach, describe, expect, it } from 'vitest' +import { useMobileNativeChatInputLease } from './use-mobile-native-chat-input-lease' type Lease = ReturnType @@ -65,47 +62,3 @@ describe('useMobileNativeChatInputLease', () => { expect(lease?.clear()).toBe(true) }) }) - -describe('useSettledMobileNativeChatInputLock', () => { - let renderer: ReactTestRenderer | null = null - let settled: ReturnType | undefined - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - vi.useRealTimers() - }) - - function Harness({ reason }: { reason: 'waiting' | 'disconnected' | null }): null { - settled = useSettledMobileNativeChatInputLock(reason) - return null - } - - it('holds each edge until the lease has stopped flapping', () => { - vi.useFakeTimers() - act(() => { - renderer = create(createElement(Harness, { reason: 'waiting' })) - }) - expect(settled).toBeNull() - act(() => { - vi.advanceTimersByTime(600) - }) - expect(settled).toBe('waiting') - - act(() => renderer?.update(createElement(Harness, { reason: null }))) - act(() => { - vi.advanceTimersByTime(300) - }) - act(() => renderer?.update(createElement(Harness, { reason: 'disconnected' }))) - act(() => { - vi.advanceTimersByTime(600) - }) - expect(settled).toBe('disconnected') - - act(() => renderer?.update(createElement(Harness, { reason: null }))) - act(() => { - vi.advanceTimersByTime(600) - }) - expect(settled).toBeNull() - }) -}) diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-input-lease.ts similarity index 93% rename from mobile/src/session/native-chat/use-mobile-native-chat-input-lease.ts rename to mobile/src/session/native-chat/composer/use-mobile-native-chat-input-lease.ts index 9dd2bd8b078..417500e8ea2 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-input-lease.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { nativeChatTurnStatusCopy } from './native-chat-turn-status-copy' -import { translate, type Translate } from '../../i18n/mobile-i18n' -import type { MobileNativeChatInputLockReason } from './MobileNativeChatView' +import { nativeChatTurnStatusCopy } from '../transcript/native-chat-turn-status-copy' +import { translate, type Translate } from '../../../i18n/mobile-i18n' +import type { MobileNativeChatInputLockReason } from '../MobileNativeChatView' export function useMobileNativeChatInputLease(args: { activeHandle: string | null diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-launch-draft-seed.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-launch-draft-seed.ts index acb46ca7679..b5e03cb117e 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-launch-draft-seed.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-launch-draft-seed.ts @@ -5,7 +5,7 @@ import { nativeChatLaunchDraftTurnBaseline, type NativeChatLaunchDraftTurnBaseline } from '../../../../../src/shared/native-chat/transcript/native-chat-launch-draft-resolution' -import { normalizedUserText } from './mobile-native-chat-draft-reconcile' +import { normalizedNativeChatUserMessageText } from '../../../../../src/shared/native-chat/transcript/native-chat-image-transcript-markers' import { mobileNativeChatLaunchDraftSeeds } from './mobile-native-chat-draft-store' import { mobileNativeChatDraftDestination } from './mobile-native-chat-draft-inputs' @@ -22,7 +22,7 @@ function launchDraftResolved( ): boolean { // Old hosts send no creation time, so any user turn retires the draft. if (createdAt === null) { - return messages.some((message) => normalizedUserText(message) !== null) + return messages.some((message) => normalizedNativeChatUserMessageText(message) !== null) } return launchDraftResolvedByTranscript({ createdAt }, messages, baseline) } diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.test.ts index 72d5595f366..a47686107f4 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.test.ts @@ -1,18 +1,23 @@ import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' import { afterEach, describe, expect, it, vi } from 'vitest' -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' +import type { MobileNativeChatSendIntent } from './mobile-native-chat-send' import type { MobileNativeChatInputLockReason } from '../MobileNativeChatView' -import { resetMobileNativeChatDraftStoreForTests } from './mobile-native-chat-draft-store' -import { - readMobileNativeChatOfflineSends, - resetMobileNativeChatOfflineSendsForTests -} from './mobile-native-chat-offline-sends' +import { closeMobileNativeChatTabDraft } from './mobile-native-chat-draft-store' +import { readMobileNativeChatOfflineSends } from './mobile-native-chat-offline-sends' import { useMobileNativeChatOfflineQueue, type MobileNativeChatOfflineQueue } from './use-mobile-native-chat-offline-sends' +import { loadMobileNativeChatDrafts } from './mobile-native-chat-draft-persistence' + +vi.mock('@react-native-async-storage/async-storage', async () => { + const { createInMemoryAsyncStorage } = + await import('../../../test-support/in-memory-async-storage') + return { default: createInMemoryAsyncStorage() } +}) + const SCOPE = 'host\0worktree\0tab' type Send = (text: string, intent?: MobileNativeChatSendIntent) => Promise @@ -20,6 +25,8 @@ type Send = (text: string, intent?: MobileNativeChatSendIntent) => Promise { let renderer: ReactTestRenderer | null = null let queue: MobileNativeChatOfflineQueue | null = null + let releasePending: (() => void) | null = null + let pendingSend: Promise | null = null function Harness({ lock, send }: { lock: MobileNativeChatInputLockReason | null; send: Send }) { queue = useMobileNativeChatOfflineQueue({ @@ -41,12 +48,19 @@ describe('useMobileNativeChatOfflineQueue', () => { await act(async () => renderer?.update(createElement(Harness, { lock: null, send }))) } - afterEach(() => { + afterEach(async () => { act(() => renderer?.unmount()) renderer = null queue = null - resetMobileNativeChatOfflineSendsForTests() - resetMobileNativeChatDraftStoreForTests() + await loadMobileNativeChatDrafts('host', 'worktree') + closeMobileNativeChatTabDraft('host\0worktree\0tab') + await act(async () => { + releasePending?.() + await pendingSend + }) + releasePending = null + pendingSend = null + await loadMobileNativeChatDrafts('host', 'worktree') }) it('pauses a queued message whose send fails and holds later sends behind it until Resume', async () => { @@ -84,13 +98,16 @@ describe('useMobileNativeChatOfflineQueue', () => { it('keeps a pending send single across remounts and drains later messages when it settles', async () => { const delivered: string[] = [] let finishFirst: ((sent: boolean) => void) | undefined - const send: Send = async (text) => { + const send: Send = (text) => { delivered.push(text) - return text === 'first' - ? new Promise((resolve) => { - finishFirst = resolve - }) - : true + pendingSend = + text === 'first' + ? new Promise((resolve) => { + finishFirst = resolve + releasePending = () => resolve(false) + }) + : Promise.resolve(true) + return pendingSend } await mount('disconnected', send) await act(async () => { diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.ts index 21f629a0aec..24ea22aef03 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-offline-sends.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { appendReturnedDraftText } from '../../../../../src/shared/returned-draft-text' -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' +import type { MobileNativeChatSendIntent } from './mobile-native-chat-send' import type { MobileNativeChatInputLockReason } from '../MobileNativeChatView' -import { useSettledMobileNativeChatInputLock } from '../use-mobile-native-chat-input-lease' +import { useSettledMobileNativeChatInputLock } from './use-mobile-native-chat-input-lease' import { mobileNativeChatDraftDestination, readMobileNativeChatDraftInputs, diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-send-error.test.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-send-error.test.ts similarity index 77% rename from mobile/src/session/native-chat/use-mobile-native-chat-send-error.test.ts rename to mobile/src/session/native-chat/composer/use-mobile-native-chat-send-error.test.ts index d0db1f8c01e..825c6960288 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-send-error.test.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-send-error.test.ts @@ -100,54 +100,6 @@ describe('useMobileNativeChatSendError', () => { expect(api().message).toBe('Background tasks are still running.') }) - it('holds a failure for four seconds, then drops it', async () => { - await render() - await act(async () => api().show('a')) - expect(api().message).toBe('a') - - await act(async () => { - vi.advanceTimersByTime(3999) - }) - expect(api().message).toBe('a') - await act(async () => { - vi.advanceTimersByTime(1) - }) - expect(api().message).toBeNull() - }) - - it('restarts the hold when a second failure lands mid-hold', async () => { - await render() - await act(async () => api().show('a')) - await act(async () => { - vi.advanceTimersByTime(3000) - }) - await act(async () => api().show('b')) - - await act(async () => { - vi.advanceTimersByTime(3000) - }) - expect(api().message).toBe('b') - - await act(async () => { - vi.advanceTimersByTime(1000) - }) - expect(api().message).toBeNull() - }) - - it('clears immediately and cancels the pending hold', async () => { - await render() - await act(async () => api().show('a')) - await act(async () => api().clear()) - expect(api().message).toBeNull() - - await act(async () => api().show('b')) - await act(async () => api().clear()) - await act(async () => { - vi.advanceTimersByTime(10_000) - }) - expect(api().message).toBeNull() - }) - it('drops a held failure when the scope changes', async () => { await render('terminal-1') await act(async () => api().show('a')) @@ -210,13 +162,4 @@ describe('useMobileNativeChatSendError', () => { expect(showToast).toHaveBeenCalledWith('Delivery unconfirmed', 1600) }) - - it('cancels the pending hold timer on unmount', async () => { - await render() - await act(async () => api().show('a')) - expect(vi.getTimerCount()).toBe(1) - act(() => renderer?.unmount()) - renderer = null - expect(vi.getTimerCount()).toBe(0) - }) }) diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-send-error.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-send-error.ts similarity index 97% rename from mobile/src/session/native-chat/use-mobile-native-chat-send-error.ts rename to mobile/src/session/native-chat/composer/use-mobile-native-chat-send-error.ts index db9fc4dab94..fbc0b4ed6f0 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-send-error.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-send-error.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState, type MutableRefObject } from 'react' -import type { StructuredAgentSessionCommandRefusalCause } from '../../../../src/shared/agent-session/view/structured-agent-session-composer' +import type { StructuredAgentSessionCommandRefusalCause } from '../../../../../src/shared/agent-session/view/structured-agent-session-composer' // Keep command-refusal causes only while the chat still shows the work that caused them. export type MobileNativeChatSendErrorDetails = { diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-stop.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-stop.ts similarity index 88% rename from mobile/src/session/native-chat/use-mobile-native-chat-stop.ts rename to mobile/src/session/native-chat/composer/use-mobile-native-chat-stop.ts index 0adce2c38b9..b6411fd0036 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-stop.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-stop.ts @@ -1,13 +1,13 @@ import { useCallback, useEffect, useRef, type MutableRefObject } from 'react' -import { translate } from '../../i18n/mobile-i18n' -import type { RpcClient } from '../../transport/rpc/rpc-client-contract' -import { isRpcDeliveryUnknown } from '../../transport/rpc/rpc-delivery-ambiguity' -import '../../transport/connection/stable-logical-rpc-client' -import { isLogicalClientCutoverError } from '../../transport/connection/logical-client-cutover-error' -import { nativeChatTerminalWrite } from '../../terminal/session/mobile-session-terminal-operations' +import { translate } from '../../../i18n/mobile-i18n' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { isRpcDeliveryUnknown } from '../../../transport/rpc/rpc-delivery-ambiguity' +import '../../../transport/connection/stable-logical-rpc-client' +import { isLogicalClientCutoverError } from '../../../transport/connection/logical-client-cutover-error' +import { nativeChatTerminalWrite } from '../../../terminal/session/mobile-session-terminal-operations' import { openMobileNativeChatSendBudget } from './mobile-native-chat-send' -import { nativeChatStopSendsSecondEscape } from '../../../../src/shared/native-chat/native-chat-agent-support' -import { NATIVE_CHAT_QUESTION_STEP_MS } from '../../../../src/shared/native-chat/input/native-chat-answer-stepping' +import { nativeChatStopSendsSecondEscape } from '../../../../../src/shared/native-chat/native-chat-agent-support' +import { NATIVE_CHAT_QUESTION_STEP_MS } from '../../../../../src/shared/native-chat/input/native-chat-answer-stepping' // Resolves false only when no Escape could have landed, so the chat may offer Stop again. export function useMobileNativeChatStop(args: { diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-structured-composer.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-structured-composer.ts index a0a7b6efb54..80d1e453980 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-structured-composer.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-structured-composer.ts @@ -1,9 +1,9 @@ import { useMemo } from 'react' import type { AgentSessionConversationCommand } from '../../../../../src/shared/agent-session/agent-session-conversation-command' -import type { MobileNativeChatStructuredComposer } from './MobileNativeChatComposer' +import type { MobileNativeChatStructuredComposer } from './mobile-native-chat-structured-composer' import type { MobileNativeChatFileAttachments } from './use-mobile-native-chat-file-attachments' -import type { useMobileStructuredSessionCommands } from '../../use-mobile-structured-session-commands' -import type { MobileStructuredThreadGoal } from '../../use-mobile-structured-thread-goal' +import type { useMobileStructuredSessionCommands } from './use-mobile-structured-session-commands' +import type { MobileStructuredThreadGoal } from '../input/use-mobile-structured-thread-goal' const NO_COMMANDS: readonly AgentSessionConversationCommand[] = [] diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-unconfirmed-sends.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-unconfirmed-sends.ts similarity index 94% rename from mobile/src/session/native-chat/use-mobile-native-chat-unconfirmed-sends.ts rename to mobile/src/session/native-chat/composer/use-mobile-native-chat-unconfirmed-sends.ts index c62e77df380..1adf0877a0b 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-unconfirmed-sends.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-unconfirmed-sends.ts @@ -1,19 +1,19 @@ // Lost acknowledgements may hide delivered sends: hold them quietly until the transcript confirms them, or hand them back at the deadline. Structured sends own a separate hold. import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { findLandedUnconfirmedSends, type UnconfirmedSend -} from './composer/mobile-native-chat-draft-reconcile' -import { returnMobileNativeChatInFlightSend } from './composer/mobile-native-chat-draft-store' +} from './mobile-native-chat-draft-reconcile' +import { returnMobileNativeChatInFlightSend } from './mobile-native-chat-draft-store' import { claimMobileNativeChatInFlightSends, releaseMobileNativeChatInFlightSends, settleMobileNativeChatInFlightSend, useMobileNativeChatSavedInFlightVersion -} from './composer/mobile-native-chat-in-flight-sends' -import type { MobileNativeChatSendOrigin } from './mobile-native-chat-pending-echo' +} from './mobile-native-chat-in-flight-sends' +import type { MobileNativeChatSendOrigin } from '../transcript/mobile-native-chat-pending-echo' import { createUnconfirmedSendDeadlines } from './unconfirmed-send-deadlines' export const UNCONFIRMED_SEND_MESSAGE = 'Delivery unconfirmed — check chat before retrying' diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-workspace-catalog.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-workspace-catalog.ts index 75a0b920379..eb5c6b79b61 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-workspace-catalog.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-workspace-catalog.ts @@ -1,5 +1,5 @@ import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' -import type { StructuredAgentSessionHostSupport } from '../../mobile-structured-agent-session-host-support' +import type { StructuredAgentSessionHostSupport } from '../agent-session/mobile-structured-agent-session-host-support' import { useMobileNativeChatFileSearch } from './use-mobile-native-chat-file-search' import { useMobileNativeChatSkills } from './use-mobile-native-chat-skills' diff --git a/mobile/src/session/use-mobile-structured-command-controller.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-command-controller.ts similarity index 88% rename from mobile/src/session/use-mobile-structured-command-controller.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-command-controller.ts index d86f4cdb9f7..2bc205eb14e 100644 --- a/mobile/src/session/use-mobile-structured-command-controller.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-command-controller.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import type { useMobileStructuredAgentOptions } from './use-mobile-structured-agent-options' +import type { useMobileStructuredAgentOptions } from '../input/use-mobile-structured-agent-options' type MobileStructuredAgentOptions = ReturnType diff --git a/mobile/src/session/use-mobile-structured-context-usage.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-context-usage.ts similarity index 64% rename from mobile/src/session/use-mobile-structured-context-usage.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-context-usage.ts index c381c955bec..1c69e420732 100644 --- a/mobile/src/session/use-mobile-structured-context-usage.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-context-usage.ts @@ -1,11 +1,11 @@ import { useMemo } from 'react' -import type { AgentJournalRenderItem } from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import type { AgentSessionOptionsResult } from '../../../src/shared/agent-session/options/agent-session-options-result' -import { selectStructuredAgentSessionContextUsage } from '../../../src/shared/agent-session/structured-agent-session-context-usage' +import type { AgentJournalRenderItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentSessionOptionsResult } from '../../../../../src/shared/agent-session/options/agent-session-options-result' +import { selectStructuredAgentSessionContextUsage } from '../../../../../src/shared/agent-session/structured-agent-session-context-usage' import { summarizeContextUsage, type NativeChatContextUsageSummary -} from '../../../src/shared/native-chat/native-chat-context-usage-summary' +} from '../../../../../src/shared/native-chat/native-chat-context-usage-summary' /** The composer ring's readout, from desktop's rule; an older host's missing answer leaves the loaded window's facts. */ export function useMobileStructuredContextUsage( diff --git a/mobile/src/session/use-mobile-structured-interrupted-continue.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-interrupted-continue.ts similarity index 82% rename from mobile/src/session/use-mobile-structured-interrupted-continue.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-interrupted-continue.ts index 4b0e38729ce..315272016b2 100644 --- a/mobile/src/session/use-mobile-structured-interrupted-continue.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-interrupted-continue.ts @@ -2,20 +2,20 @@ import { useCallback, useMemo, useState } from 'react' import type { AgentJournalRenderItem, AgentJournalSubmission -} from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import { latestNativeChatOrcaStopCut } from '../../../src/shared/native-chat/native-chat-orca-stop-cut' +} from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import { latestNativeChatOrcaStopCut } from '../../../../../src/shared/native-chat/native-chat-orca-stop-cut' import { failNativeChatInterruptedContinue, nativeChatInterruptedContinueState, NO_NATIVE_CHAT_INTERRUPTED_CONTINUE_ATTEMPT, nativeChatInterruptedContinueAccepted, type NativeChatInterruptedContinueAttempt -} from '../../../src/shared/native-chat/native-chat-interrupted-continue' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { callAgentSession } from './mobile-structured-agent-session-rpc' -import { useRouteRelaysCapability } from '../worktree/workspace-server-context' -import { AGENT_SESSION_PHONE_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session/agent-session-continue-interrupted-capability' -import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' +} from '../../../../../src/shared/native-chat/native-chat-interrupted-continue' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { callAgentSession } from '../agent-session/mobile-structured-agent-session-rpc' +import { useRouteRelaysCapability } from '../../../worktree/workspace-server-context' +import { AGENT_SESSION_PHONE_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } from '../../../../../src/shared/agent-session/agent-session-continue-interrupted-capability' +import type { StructuredAgentSessionHostSupport } from '../agent-session/mobile-structured-agent-session-host-support' export type MobileStructuredInterruptedContinue = { continueAvailable: boolean diff --git a/mobile/src/session/use-mobile-structured-native-chat-send-bridge.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-native-chat-send-bridge.ts similarity index 85% rename from mobile/src/session/use-mobile-structured-native-chat-send-bridge.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-native-chat-send-bridge.ts index 255bb98d849..c1fde649e55 100644 --- a/mobile/src/session/use-mobile-structured-native-chat-send-bridge.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-native-chat-send-bridge.ts @@ -1,26 +1,26 @@ import { useCallback } from 'react' -import type { AgentSessionSlashCommand } from '../../../src/shared/agent-session/agent-session-wire' -import type { StructuredAgentId } from '../../../src/shared/agent-session/lifecycle/agent-session-provider-handle' +import type { AgentSessionSlashCommand } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { StructuredAgentId } from '../../../../../src/shared/agent-session/lifecycle/agent-session-provider-handle' import type { MobileNativeChatSendIntent, MobileNativeChatSendOutcome -} from './native-chat/mobile-native-chat-send' -import type { MobileNativeChatSendOrigin } from './native-chat/mobile-native-chat-pending-echo' +} from './mobile-native-chat-send' +import type { MobileNativeChatSendOrigin } from '../transcript/mobile-native-chat-pending-echo' import type { StructuredMobileSendAttachment } from './use-mobile-structured-send-with-outcome' -import type { AgentJournalSkillRef } from '../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentJournalSkillRef } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' import { readMobileNativeChatPickedSkills, restoreMobileNativeChatPickedSkills -} from './native-chat/composer/mobile-native-chat-draft-store' +} from './mobile-native-chat-draft-store' import { beginMobileNativeChatInFlightSend, settleMobileNativeChatInFlightSend -} from './native-chat/composer/mobile-native-chat-in-flight-sends' +} from './mobile-native-chat-in-flight-sends' import type { MobileStructuredSends } from './use-mobile-structured-sends' -import { structuredSessionOperationId } from './structured-session-operation-id' -import { isSlashCommandDraft } from '../../../src/shared/native-chat/input/native-chat-slash-commands' -import { UNCONFIRMED_SEND_RESTORED_MESSAGE } from './native-chat/use-mobile-native-chat-unconfirmed-sends' +import { structuredSessionOperationId } from '../agent-session/structured-session-operation-id' +import { isSlashCommandDraft } from '../../../../../src/shared/native-chat/input/native-chat-slash-commands' +import { UNCONFIRMED_SEND_RESTORED_MESSAGE } from './use-mobile-native-chat-unconfirmed-sends' export function useMobileStructuredNativeChatSendBridge(args: { agent: StructuredAgentId | undefined diff --git a/mobile/src/session/use-mobile-structured-send-with-outcome.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-send-with-outcome.ts similarity index 80% rename from mobile/src/session/use-mobile-structured-send-with-outcome.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-send-with-outcome.ts index be4dd2ea649..5adf512760d 100644 --- a/mobile/src/session/use-mobile-structured-send-with-outcome.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-send-with-outcome.ts @@ -1,46 +1,46 @@ -import { agentSessionInteractionCommand } from '../../../src/shared/agent-session/options/agent-session-interaction-mode' -import { selectAgentSessionCurrentRequests } from '../../../src/shared/agent-session/requests/agent-session-current-requests' +import { agentSessionInteractionCommand } from '../../../../../src/shared/agent-session/options/agent-session-interaction-mode' +import { selectAgentSessionCurrentRequests } from '../../../../../src/shared/agent-session/requests/agent-session-current-requests' import { useCallback } from 'react' -import { runningStructuredAgentSessionTurnId } from '../../../src/shared/agent-session/view/structured-agent-session-live-turn' -import { mobileStructuredAgentSessionWorking } from './mobile-structured-agent-session-working' -import { structuredSessionBackgroundTasksView } from '../../../src/shared/structured-session-background-tasks-view' -import { agentSessionVisibleFailureFacts } from '../../../src/shared/agent-session/failures/agent-session-visible-failures' -import { tuiAgentDisplayName } from '../../../src/shared/agent-launch/catalog/tui-agent-display-names' -import { pendingPromptsAllUnanswerableHere } from '../../../src/shared/agent-session/agent-session-approval-subject' +import { runningStructuredAgentSessionTurnId } from '../../../../../src/shared/agent-session/view/structured-agent-session-live-turn' +import { mobileStructuredAgentSessionWorking } from '../agent-session/mobile-structured-agent-session-working' +import { structuredSessionBackgroundTasksView } from '../../../../../src/shared/structured-session-background-tasks-view' +import { agentSessionVisibleFailureFacts } from '../../../../../src/shared/agent-session/failures/agent-session-visible-failures' +import { tuiAgentDisplayName } from '../../../../../src/shared/agent-launch/catalog/tui-agent-display-names' +import { pendingPromptsAllUnanswerableHere } from '../../../../../src/shared/agent-session/agent-session-approval-subject' import { SkillRefLimitError, structuredAgentSessionSendBody, type StructuredAgentSessionAttachment, type StructuredAgentSessionSendControl -} from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' -import type { AgentJournalSkillRef } from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import { MAX_SKILL_REFS } from '../../../src/shared/rpc-contract/structured-agent-session-message-params' +} from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' +import type { AgentJournalSkillRef } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import { MAX_SKILL_REFS } from '../../../../../src/shared/rpc-contract/structured-agent-session-message-params' import { structuredAgentSessionGoalObjective, type StructuredAgentSessionComposerOptions -} from '../../../src/shared/agent-session/view/structured-agent-session-composer' -import { translate } from '../i18n/mobile-i18n' -import type { AgentSessionSlashCommand } from '../../../src/shared/agent-session/agent-session-wire' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-composer' +import { translate } from '../../../i18n/mobile-i18n' +import type { AgentSessionSlashCommand } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' import { mobileStructuredSendQueues } from './mobile-structured-send-queue-rule' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import type { MobileNativeChatSendIntent, MobileNativeChatSendOutcome -} from './native-chat/mobile-native-chat-send' +} from './mobile-native-chat-send' import { dispatchMobileStructuredCommand } from './mobile-structured-composer-command' -import type { MobileNativeChatSendErrorReporter } from './native-chat/use-mobile-native-chat-send-error' -import { sendMobileStructuredAgentSessionMessage } from './mobile-structured-agent-session-send' +import type { MobileNativeChatSendErrorReporter } from './use-mobile-native-chat-send-error' +import { sendMobileStructuredAgentSessionMessage } from '../agent-session/mobile-structured-agent-session-send' import type { MobileStructuredSends } from './use-mobile-structured-sends' import type { MobileStructuredSendsOut } from './use-mobile-structured-stop-offer' -import { structuredSessionOperationId } from './structured-session-operation-id' -import { UNCONFIRMED_SEND_MESSAGE } from './native-chat/use-mobile-native-chat-unconfirmed-sends' -import { timeoutForDeadline } from './mobile-structured-agent-session-rpc' -import { nativeChatTextWithFileReferences } from './native-chat/composer/mobile-native-chat-file-attachment' +import { structuredSessionOperationId } from '../agent-session/structured-session-operation-id' +import { UNCONFIRMED_SEND_MESSAGE } from './use-mobile-native-chat-unconfirmed-sends' +import { timeoutForDeadline } from '../agent-session/mobile-structured-agent-session-rpc' +import { nativeChatTextWithFileReferences } from './mobile-native-chat-file-attachment' import { actionableStructuredApproval, actionableStructuredQuestion -} from './mobile-structured-actionable-prompt' +} from '../input/mobile-structured-actionable-prompt' export type StructuredMobileSendAttachment = | (StructuredAgentSessionAttachment & { kind?: 'image'; id?: string }) diff --git a/mobile/src/session/use-mobile-structured-sends.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-sends.ts similarity index 89% rename from mobile/src/session/use-mobile-structured-sends.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-sends.ts index 670fc66e312..8764a9b0e3a 100644 --- a/mobile/src/session/use-mobile-structured-sends.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-sends.ts @@ -1,21 +1,21 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' -import type { AgentJournalCursor } from '../../../src/shared/agent-session/journal/agent-session-journal-identity' -import type { AgentJournalSubmission } from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import { agentJournalSubmissionKey } from '../../../src/shared/agent-session/journal/agent-session-journal-item-key' -import type { StructuredAgentSessionOptimisticMessage } from '../../../src/shared/agent-session/view/structured-agent-session-message-projection' +import type { AgentJournalCursor } from '../../../../../src/shared/agent-session/journal/agent-session-journal-identity' +import type { AgentJournalSubmission } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import { agentJournalSubmissionKey } from '../../../../../src/shared/agent-session/journal/agent-session-journal-item-key' +import type { StructuredAgentSessionOptimisticMessage } from '../../../../../src/shared/agent-session/view/structured-agent-session-message-projection' import { structuredAgentSessionReadCoversSend, structuredAgentSessionSettledSends -} from '../../../src/shared/agent-session/requests/structured-agent-session-send-settlement' -import { mergeSentImagePreviews } from './native-chat/composer/mobile-native-chat-draft-reconcile' -import type { AgentSessionQueuedMessage } from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import type { MobileNativeChatSendErrorReporter } from './native-chat/use-mobile-native-chat-send-error' +} from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-settlement' +import { mergeSentImagePreviews } from './mobile-native-chat-draft-reconcile' +import type { AgentSessionQueuedMessage } from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +import type { MobileNativeChatSendErrorReporter } from './use-mobile-native-chat-send-error' import { UNCONFIRMED_SEND_MESSAGE, UNCONFIRMED_SEND_RESTORED_MESSAGE -} from './native-chat/use-mobile-native-chat-unconfirmed-sends' -import { createUnconfirmedSendDeadlines } from './native-chat/unconfirmed-send-deadlines' -import { returnMobileNativeChatInFlightSend } from './native-chat/composer/mobile-native-chat-draft-store' +} from './use-mobile-native-chat-unconfirmed-sends' +import { createUnconfirmedSendDeadlines } from './unconfirmed-send-deadlines' +import { returnMobileNativeChatInFlightSend } from './mobile-native-chat-draft-store' import { claimMobileNativeChatInFlightSends, mobileNativeChatInFlightSendIds, @@ -23,7 +23,7 @@ import { releaseMobileNativeChatInFlightSends, settleMobileNativeChatInFlightSend, useMobileNativeChatSavedInFlightVersion -} from './native-chat/composer/mobile-native-chat-in-flight-sends' +} from './mobile-native-chat-in-flight-sends' // Ordinary in-memory input outlives its request; exact host IDs retire it, and a lost reply hands it back at the deadline. type MobileStructuredSend = StructuredAgentSessionOptimisticMessage diff --git a/mobile/src/session/use-mobile-structured-session-commands.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-session-commands.ts similarity index 89% rename from mobile/src/session/use-mobile-structured-session-commands.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-session-commands.ts index 3c5b002a17e..a86b674b3d7 100644 --- a/mobile/src/session/use-mobile-structured-session-commands.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-session-commands.ts @@ -1,14 +1,14 @@ -import { startAgentSessionCommandMenuQuery } from '../../../src/shared/agent-session/agent-session-command-menu-query' +import { startAgentSessionCommandMenuQuery } from '../../../../../src/shared/agent-session/agent-session-command-menu-query' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useQueryClient } from '@tanstack/react-query' import * as z from 'zod' -import type { AgentSessionSlashCommand } from '../../../src/shared/agent-session/agent-session-wire' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc/rpc-operation' -import { rpcResultVariant } from '../transport/rpc/rpc-operation-result-reader' -import { defineHostQuery, useHostQuery } from '../query/host-query' -import { hostQueryKey } from '../query/host-query-key' -import { useWorkspaceExecutionHost } from '../navigation/workspace-execution-host' +import type { AgentSessionSlashCommand } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { bindDeferredRpcOperation, defineRpcOperation } from '../../../transport/rpc/rpc-operation' +import { rpcResultVariant } from '../../../transport/rpc/rpc-operation-result-reader' +import { defineHostQuery, useHostQuery } from '../../../query/host-query' +import { hostQueryKey } from '../../../query/host-query-key' +import { useWorkspaceExecutionHost } from '../../../navigation/workspace-execution-host' type Reported = { client: RpcClient diff --git a/mobile/src/session/use-mobile-structured-stop-offer.test.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-stop-offer.test.ts similarity index 100% rename from mobile/src/session/use-mobile-structured-stop-offer.test.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-stop-offer.test.ts diff --git a/mobile/src/session/use-mobile-structured-stop-offer.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-stop-offer.ts similarity index 94% rename from mobile/src/session/use-mobile-structured-stop-offer.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-stop-offer.ts index bc0f6261b86..d13e4e32cfe 100644 --- a/mobile/src/session/use-mobile-structured-stop-offer.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-structured-stop-offer.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo, useRef, useState } from 'react' -import { structuredAgentSessionCanStop } from '../../../src/shared/agent-session/view/structured-agent-session-stop-control' +import { structuredAgentSessionCanStop } from '../../../../../src/shared/agent-session/view/structured-agent-session-stop-control' type Send = (...args: Args) => Promise diff --git a/mobile/src/session/use-mobile-structured-stop-press.test.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-stop-press.test.ts similarity index 100% rename from mobile/src/session/use-mobile-structured-stop-press.test.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-stop-press.test.ts diff --git a/mobile/src/session/use-mobile-structured-stop-press.ts b/mobile/src/session/native-chat/composer/use-mobile-structured-stop-press.ts similarity index 100% rename from mobile/src/session/use-mobile-structured-stop-press.ts rename to mobile/src/session/native-chat/composer/use-mobile-structured-stop-press.ts diff --git a/mobile/src/session/native-chat/input/MobileNativeChatAsk.previews.test.tsx b/mobile/src/session/native-chat/input/MobileNativeChatAsk.previews.test.tsx new file mode 100644 index 00000000000..6abc1652572 --- /dev/null +++ b/mobile/src/session/native-chat/input/MobileNativeChatAsk.previews.test.tsx @@ -0,0 +1,165 @@ +import { createElement, type ReactNode } from 'react' +import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' +import { afterEach, expect, it, vi, type Mock } from 'vitest' +import type { AskPrompt } from '../../../../../src/shared/native-chat/input/native-chat-ask-types' +import { MobileNativeChatAsk } from './MobileNativeChatAsk' + +vi.mock('react-native', () => ({ + Keyboard: { dismiss: vi.fn() }, + Platform: { select: (options: { ios: unknown }) => options.ios }, + Pressable: 'Pressable', + ScrollView: 'ScrollView', + Text: 'Text', + View: 'View', + StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 } +})) +vi.mock('lucide-react-native', () => ({ Check: 'Check' })) +vi.mock('../../../components/composer-input/MessageBox', () => ({ MessageBox: 'MessageBox' })) +vi.mock('../transcript/MobileNativeChatQuestionText', () => ({ + MobileNativeChatQuestionText: 'QuestionText' +})) +vi.mock('./MobileNativeChatCardHeaderAction', () => ({ + MobileNativeChatCardHeaderAction: () => null +})) +vi.mock('../../../components/bottom-drawer/keyed-bottom-drawer', () => ({ + KeyedBottomDrawer: ({ + sheet, + children + }: { + sheet: number | null + children: (index: number) => ReactNode + }) => (sheet === null ? null : children(sheet)) +})) +vi.mock('../../../components/nav-bar/SheetHeader', () => ({ + SheetHeader: ({ onClose }: { onClose: () => void }) => + createElement('button', { accessibilityLabel: 'Close viewer', onPress: onClose }) +})) + +let renderer: ReactTestRenderer +const compactedPreview = `${'| panes |'.repeat(450)}…` +function prompt(multiSelect = false): AskPrompt { + return { + questions: [ + { + question: 'Which layout?', + header: 'Layout', + multiSelect, + options: [ + { label: 'First', preview: 'A\t|\n𠀀\t|' }, + { label: 'No preview' }, + { label: 'Last', preview: compactedPreview } + ] + } + ] + } +} +function buttons(): ReactTestInstance[] { + return renderer.root.findAll( + (node) => String(node.type) === 'Pressable' || String(node.type) === 'button' + ) +} +function button(label: string): ReactTestInstance { + return buttons().find((node) => node.props.accessibilityLabel === label)! +} +function row(index: number): ReactTestInstance { + return buttons().filter((node) => ['radio', 'checkbox'].includes(node.props.accessibilityRole))[ + index + ]! +} +function textAction(label: string): ReactTestInstance { + return buttons().find((node) => + node + .findAll((child) => String(child.type) === 'Text') + .some((text) => text.children.includes(label)) + )! +} +function press(node: ReactTestInstance): void { + act(() => node.props.onPress()) +} +type Answer = Parameters[0]['onAnswer'] +function mount(value: AskPrompt): Mock { + const onAnswer = vi.fn(async () => true) + act(() => { + renderer = create(createElement(MobileNativeChatAsk, { prompt: value, onAnswer })) + }) + return onAnswer +} + +afterEach(() => { + act(() => renderer?.unmount()) + vi.useRealTimers() +}) + +it('inspects every preview without choosing, and keeps the original option when choosing and submitting', async () => { + vi.useFakeTimers() + const onAnswer = mount(prompt()) + expect(button('First preview')).toBeDefined() + expect(button('Last preview')).toBeDefined() + expect(row(2).props.accessibilityState.checked).toBe(false) + press(button('First preview')) + expect(renderer.root.findByProps({ selectable: true }).props.children).toBe('A\t|\n𠀀\t|') + // Paging crosses the option with no preview; inspection does not change the answer. + press( + buttons().find( + (node) => node.props.accessibilityLabel === 'Last preview' && node.props.accessibilityState + )! + ) + expect( + renderer.root + .findAll((node) => String(node.type) === 'Text') + .some((node) => node.children.includes(compactedPreview)) + ).toBe(true) + press(button('Close viewer')) + act(() => { + vi.runAllTimers() + }) + expect(onAnswer).not.toHaveBeenCalled() + expect(row(2).props.accessibilityState.checked).toBe(false) + press(button('Last preview')) + press(button('Choose this')) + act(() => { + vi.runAllTimers() + }) + expect(row(2).props.accessibilityState.checked).toBe(true) + expect(onAnswer).not.toHaveBeenCalled() + press(button('Last preview')) + press(button('Chosen')) + press(row(2)) + expect(row(2).props.accessibilityState.checked).toBe(true) + await act(async () => textAction('Submit').props.onPress()) + expect(onAnswer).toHaveBeenCalledWith([{ indices: [2] }], expect.any(Function)) +}) + +it('preserves generic multi choices while inspecting and choosing, with row checkbox removal', async () => { + const onAnswer = mount(prompt(true)) + press(row(0)) + press(button('Last preview')) + press(button('Choose this')) + press(button('Last preview')) + press(button('Chosen')) + expect(row(0).props.accessibilityState.checked).toBe(true) + expect(row(2).props.accessibilityState.checked).toBe(true) + press(row(0)) + await act(async () => textAction('Submit').props.onPress()) + expect(onAnswer).toHaveBeenCalledWith([{ indices: [2] }], expect.any(Function)) +}) + +it('retains automatic answer for an ordinary question and disables the viewer choice while pending', async () => { + vi.useFakeTimers() + const plain = prompt() + plain.questions[0]!.options = [{ label: 'Plain' }] + const onAnswer = mount(plain) + press(row(0)) + await act(async () => { + vi.runAllTimers() + }) + expect(onAnswer).toHaveBeenCalledWith([{ indices: [0] }], expect.any(Function)) + act(() => renderer.unmount()) + act(() => { + renderer = create( + createElement(MobileNativeChatAsk, { prompt: prompt(), answerPending: true, onAnswer }) + ) + }) + press(button('Last preview')) + expect(button('Choose this').props.disabled).toBe(true) +}) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatAsk.styles.ts b/mobile/src/session/native-chat/input/MobileNativeChatAsk.styles.ts new file mode 100644 index 00000000000..cc895c5404f --- /dev/null +++ b/mobile/src/session/native-chat/input/MobileNativeChatAsk.styles.ts @@ -0,0 +1,95 @@ +import { StyleSheet } from 'react-native' +import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' +import { themedStyleSheet } from '../../../theme/themed-styles' + +export const mobileNativeChatAskStyles = themedStyleSheet(() => ({ + card: { + flexShrink: 1, + minHeight: 0, + backgroundColor: colors.bgPanel, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.borderSubtle + }, + tabs: { + flexGrow: 0, + paddingTop: spacing.sm, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: colors.borderSubtle + }, + tabsContent: { + paddingHorizontal: spacing.sm, + gap: spacing.xs, + alignItems: 'center' + }, + tab: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + minHeight: 36, + paddingHorizontal: spacing.sm, + paddingVertical: spacing.xs, + borderBottomWidth: 2, + borderBottomColor: 'transparent' + }, + tabActive: { + borderBottomColor: colors.statusGreen + }, + tabText: { + color: colors.textSecondary, + fontSize: typography.metaSize, + fontWeight: '600' + }, + tabTextActive: { + color: colors.textPrimary + }, + scroll: { + paddingHorizontal: spacing.md + }, + questionRow: { flexDirection: 'row', alignItems: 'flex-start', gap: spacing.sm }, + questionText: { + flex: 1, + marginVertical: spacing.sm + }, + other: { + marginBottom: spacing.xs + }, + footer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + padding: spacing.md, + gap: spacing.sm, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: colors.borderSubtle + }, + cancel: { + paddingVertical: spacing.sm, + paddingHorizontal: spacing.sm + }, + cancelText: { + color: colors.textSecondary, + fontSize: typography.bodySize, + fontWeight: '600' + }, + progress: { + color: colors.textMuted, + fontSize: typography.metaSize + }, + next: { + paddingVertical: spacing.sm, + paddingHorizontal: spacing.lg, + borderRadius: radii.control, + backgroundColor: colors.textPrimary + }, + nextSkip: { + backgroundColor: colors.bgRaised + }, + nextText: { + color: colors.bgBase, + fontSize: typography.bodySize, + fontWeight: '700' + }, + nextSkipText: { + color: colors.textMuted + } +})) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx b/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx index 2ad2426c74b..3ec989ff1ee 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx @@ -1,5 +1,5 @@ import { useMemo, useRef, useState } from 'react' -import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native' +import { Keyboard, Pressable, ScrollView, Text, View } from 'react-native' import { Check } from 'lucide-react-native' import { nativeChatAnswerOperationEvidence, @@ -17,16 +17,17 @@ import { askTypedText, toggleAskOption } from '../../../../../src/shared/native-chat/input/native-chat-ask-card-rules' -import { colors, radii, spacing, typography, pressedStyle } from '../../../theme/mobile-theme' -import { themedStyleSheet } from '../../../theme/themed-styles' +import { colors, pressedStyle } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import type { Translate } from '../../../i18n/mobile-i18n' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { MessageBox } from '../../../components/composer-input/MessageBox' -import { MobileNativeChatCardHeaderAction } from '../MobileNativeChatCardHeaderAction' -import { MobileNativeChatQuestionText } from '../MobileNativeChatQuestionText' +import { MobileNativeChatCardHeaderAction } from './MobileNativeChatCardHeaderAction' +import { MobileNativeChatQuestionText } from '../transcript/MobileNativeChatQuestionText' import { useMobileNativeChatAskAutoAdvance } from './use-mobile-native-chat-ask-auto-advance' import { MobileNativeChatAskOptionRow } from './MobileNativeChatAskOptionRow' +import { MobileNativeChatOptionPreviewSheet } from './MobileNativeChatOptionPreviewSheet' +import { mobileNativeChatAskStyles as styles } from './MobileNativeChatAsk.styles' import { useMobileNativeChatReplyScroll } from './use-mobile-native-chat-reply-scroll' type Props = { @@ -66,9 +67,9 @@ export function MobileNativeChatAsk({ }: Props): React.JSX.Element { useMobileColorScheme() const t = useMobileTranslation() - const { scrollRef, inputRef, revealReply, scrollViewProps, revealContent } = - useMobileNativeChatReplyScroll() + const { scrollRef, inputRef, revealReply, onViewportLayout } = useMobileNativeChatReplyScroll() const [index, setIndex] = useState(0) + const [viewedOption, setViewedOption] = useState(null) const [selections, setSelections] = useState(() => prompt.questions.map(() => [])) const [otherText, setOtherText] = useState(() => prompt.questions.map((_, qi) => freeTextInputs?.[qi]?.initialValue ?? '') @@ -93,6 +94,7 @@ export function MobileNativeChatAsk({ const autoAdvance = useMobileNativeChatAskAutoAdvance(submitting) // Open each step at its question and end the last step's edit, so no typing lands out of view. const showStep = (next: (current: number) => number): void => { + setViewedOption(null) inputRef.current?.blur() scrollRef.current?.scrollTo({ y: 0, animated: false }) setIndex(next) @@ -105,17 +107,17 @@ export function MobileNativeChatAsk({ askAcceptsEmptyAnswer(prompt.questions[qi], freeTextInputs?.[qi]) // Delay single-select advance to show the choice; Other still needs text. - const toggle = (qi: number, optIndex: number, multi: boolean): void => { + const toggle = (qi: number, optIndex: number, multi: boolean, choosing = false): void => { // Block same-frame taps before disabled state renders. if (isLocalPending(operationRef.current) || answerPending) { return } const cur = selections[qi] ?? [] - // A pick reveals its preview, so a re-tap keeps it and the user confirms. + // Preview choices wait for Submit so inspecting alternatives never commits an answer. const keep = optIndex !== OTHER && askQuestionShowsPreviews(prompt.questions[qi]) const picked = !cur.includes(optIndex) const next = selections.map((s, i) => - i === qi ? toggleAskOption(cur, optIndex, multi, keep) : s + i === qi ? toggleAskOption(cur, optIndex, multi, keep || choosing) : s ) setSelections(next) autoAdvance.cancel() @@ -252,7 +254,12 @@ export function MobileNativeChatAsk({ ) : null} - + { + autoAdvance.cancel() + inputRef.current?.blur() + Keyboard.dismiss() + setViewedOption(optIndex) + }} selected={(selections[index] ?? []).includes(optIndex)} multi={q.multiSelect} disabled={submitting} @@ -333,98 +345,18 @@ export function MobileNativeChatAsk({ + setViewedOption(null)} + onChoose={(optionIndex) => { + setViewedOption(null) + toggle(index, optionIndex, q.multiSelect, true) + }} + /> ) } - -const styles = themedStyleSheet(() => ({ - card: { - flexShrink: 1, - minHeight: 0, - backgroundColor: colors.bgPanel, - borderTopWidth: StyleSheet.hairlineWidth, - borderTopColor: colors.borderSubtle - }, - tabs: { - flexGrow: 0, - paddingTop: spacing.sm, - borderBottomWidth: StyleSheet.hairlineWidth, - borderBottomColor: colors.borderSubtle - }, - tabsContent: { - paddingHorizontal: spacing.sm, - gap: spacing.xs, - alignItems: 'center' - }, - tab: { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - minHeight: 36, - paddingHorizontal: spacing.sm, - paddingVertical: spacing.xs, - borderBottomWidth: 2, - borderBottomColor: 'transparent' - }, - tabActive: { - borderBottomColor: colors.statusGreen - }, - tabText: { - color: colors.textSecondary, - fontSize: typography.metaSize, - fontWeight: '600' - }, - tabTextActive: { - color: colors.textPrimary - }, - scroll: { - paddingHorizontal: spacing.md - }, - questionRow: { flexDirection: 'row', alignItems: 'flex-start', gap: spacing.sm }, - questionText: { - flex: 1, - marginVertical: spacing.sm - }, - other: { - marginBottom: spacing.xs - }, - footer: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - padding: spacing.md, - gap: spacing.sm, - borderTopWidth: StyleSheet.hairlineWidth, - borderTopColor: colors.borderSubtle - }, - cancel: { - paddingVertical: spacing.sm, - paddingHorizontal: spacing.sm - }, - cancelText: { - color: colors.textSecondary, - fontSize: typography.bodySize, - fontWeight: '600' - }, - progress: { - color: colors.textMuted, - fontSize: typography.metaSize - }, - next: { - paddingVertical: spacing.sm, - paddingHorizontal: spacing.lg, - borderRadius: radii.control, - backgroundColor: colors.textPrimary - }, - nextSkip: { - backgroundColor: colors.bgRaised - }, - nextText: { - color: colors.bgBase, - fontSize: typography.bodySize, - fontWeight: '700' - }, - nextSkipText: { - color: colors.textMuted - } -})) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatAskOptionRow.tsx b/mobile/src/session/native-chat/input/MobileNativeChatAskOptionRow.tsx index 4a57a60103c..8734fd41ce0 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatAskOptionRow.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatAskOptionRow.tsx @@ -1,5 +1,6 @@ -import { Pressable, ScrollView, Text, View, type LayoutChangeEvent } from 'react-native' +import { Pressable, Text, View } from 'react-native' import { Check } from 'lucide-react-native' +import { MobileNativeChatOptionThumbnail } from './MobileNativeChatOptionThumbnail' import { colors, concentricRadius, @@ -15,7 +16,7 @@ export function MobileNativeChatAskOptionRow({ label, description, preview, - onPreviewLayout, + onPreview, selected, multi, disabled, @@ -23,16 +24,16 @@ export function MobileNativeChatAskOptionRow({ }: { label: string description?: string - /** Shown under the row while it is selected, as the agent wrote it. */ + /** Read-only inspection is separate from choosing the option. */ preview?: string - /** The preview's edges in the card's scroll content; the row renders as its direct children. */ - onPreviewLayout?: (top: number, bottom: number) => void + onPreview?: () => void selected: boolean multi?: boolean disabled: boolean onPress: () => void }): React.JSX.Element { useMobileColorScheme() + const hasPreview = Boolean(preview && onPreview) const row = ( [ styles.option, selected && styles.optionSelected, + hasPreview && styles.optionWithPreview, pressed && pressedStyle ]} disabled={disabled} @@ -65,29 +67,15 @@ export function MobileNativeChatAskOptionRow({ ) - // One root shape whether or not the preview shows, so selecting never remounts the row. - return ( - <> + return hasPreview ? ( + {row} - {preview && selected ? ( - { - const { y, height } = event.nativeEvent.layout - onPreviewLayout?.(y, y + height) - }} - > - - {/* Mockups are drawn in columns, so they scroll sideways instead of wrapping at phone width. */} - - - {preview} - - - - - ) : null} - + + + + + ) : ( + row ) } @@ -137,18 +125,17 @@ const styles = themedStyleSheet(() => ({ color: colors.textSecondary, fontSize: typography.metaSize }, - preview: { - backgroundColor: colors.bgPanel, + optionCard: { borderWidth: 1, borderColor: colors.borderSubtle, borderRadius: concentricRadius(radii.surface, spacing.xs), - padding: spacing.sm, - marginBottom: spacing.xs, - maxHeight: 220 + backgroundColor: colors.bgRaised, + marginBottom: spacing.xs }, - previewText: { - color: colors.textPrimary, - fontFamily: typography.monoFamily, - fontSize: typography.metaSize + optionWithPreview: { borderWidth: 0, marginBottom: 0 }, + previewInset: { + marginLeft: 18 + spacing.sm * 2, + marginRight: spacing.sm, + marginBottom: spacing.sm } })) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.test.tsx b/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.test.tsx index 8e39cddc41d..55bd4868066 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.test.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.test.tsx @@ -52,7 +52,6 @@ type Props = Parameters[0] type StopResult = Awaited> const REJECTED: StopResult = { status: 'rejected' } -const UNCONFIRMED: StopResult = { status: 'unknown' } const STOPPED: StopResult = { status: 'accepted', value: { cancelled: true }, sameFence: true } const NOTHING_STOPPED: StopResult = { status: 'accepted', @@ -98,20 +97,6 @@ describe('MobileNativeChatBackgroundTasks', () => { return mounted } - function styleOf(node: ReactTestInstance): Record { - const rendered: unknown = - typeof node.props.style === 'function' - ? node.props.style({ pressed: false }) - : node.props.style - const parts = Array.isArray(rendered) ? rendered : [rendered] - return Object.assign( - {}, - ...parts.filter( - (part): part is Record => typeof part === 'object' && part !== null - ) - ) - } - function header(mounted: ReactTestRenderer): ReactTestInstance { return mounted.root.findAll( (node) => @@ -132,31 +117,6 @@ describe('MobileNativeChatBackgroundTasks', () => { ) } - it('stops one row by its provider id and holds its button while the Stop is on its way', async () => { - let finish: (result: StopResult) => void = () => {} - const stop = vi.fn(() => new Promise((resolve) => (finish = resolve))) - const mounted = mount( - tasksFor( - { - state: 'monitoring', - supportsTaskStop: true, - children: [view('a'), view('b', { stoppable: false })] - }, - { stop } - ) - ) - expand(mounted) - const buttons = stopButtons(mounted) - expect(buttons.map((button) => button.props.accessibilityLabel)).toEqual(['Stop review a']) - act(() => buttons[0]!.props.onPress()) - act(() => buttons[0]!.props.onPress()) - expect(stop).toHaveBeenCalledTimes(1) - expect(stop).toHaveBeenCalledWith('task-a') - expect(stopButtons(mounted)[0]!.props.disabled).toBe(true) - await act(async () => finish(UNCONFIRMED)) - expect(stopButtons(mounted)[0]!.props.disabled).toBe(false) - }) - it('keeps a confirmed Stop held until its row leaves, and offers it again if the row comes back', async () => { let finish: (result: StopResult) => void = () => {} const stop = vi.fn(() => new Promise((resolve) => (finish = resolve))) @@ -206,30 +166,4 @@ describe('MobileNativeChatBackgroundTasks', () => { }) expect(stopButtons(mounted)[0]!.props.disabled).toBe(false) }) - - it('offers Stop all only to a host with no per-row stop that still accepts one', async () => { - let finish: (result: StopResult) => void = () => {} - const stop = vi.fn(() => new Promise((resolve) => (finish = resolve))) - const fallback = mount(tasksFor({ state: 'monitoring', children: [view('a')] }, { stop })) - expand(fallback) - expect(stopButtons(fallback).map((button) => button.props.accessibilityLabel)).toEqual([ - 'Stop background tasks' - ]) - const button = stopButtons(fallback)[0]! - expect(styleOf(button)).toMatchObject({ minHeight: 32, minWidth: 44 }) - expect(button.props.hitSlop).toBe(6) - expect(Number(styleOf(button).minHeight) + 2 * button.props.hitSlop).toBe(44) - act(() => button.props.onPress()) - act(() => button.props.onPress()) - expect(stop).toHaveBeenCalledTimes(1) - expect(stop).toHaveBeenCalledWith(undefined) - expect(stopButtons(fallback)[0]!.props.disabled).toBe(true) - await act(async () => finish(STOPPED)) - expect(stopButtons(fallback)[0]!.props.disabled).toBe(false) - const none = mount( - tasksFor({ state: 'monitoring', supportsStopAll: false, children: [view('a')] }) - ) - expand(none) - expect(stopButtons(none)).toEqual([]) - }) }) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.tsx b/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.tsx index 2776c6113d7..d35363724d4 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatBackgroundTasks.tsx @@ -18,7 +18,7 @@ import { } from 'lucide-react-native' import type { AgentChildRowModel } from '../../../../../src/shared/agent-status/presentation/agent-child-row-model' import type { AgentSessionBackgroundTask } from '../../../../../src/shared/agent-session/agent-session-background-task-wire' -import { sayMobileBackgroundTask as say } from '../../mobile-background-task-copy' +import { sayMobileBackgroundTask as say } from './mobile-background-task-copy' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { backgroundTasksHeaderContent, @@ -38,11 +38,11 @@ import { useNow } from '../../../hooks/use-now' import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' -import type { MobileStructuredBackgroundTasks } from '../../use-mobile-structured-background-tasks' +import type { MobileStructuredBackgroundTasks } from './use-mobile-structured-background-tasks' import { useMobileBackgroundTaskStops, type MobileBackgroundTaskStopping as Stopping -} from '../../use-mobile-background-task-stops' +} from './use-mobile-background-task-stops' import { mobileAgentChildRowName, mobileAgentChildRowText diff --git a/mobile/src/session/native-chat/MobileNativeChatCardHeaderAction.tsx b/mobile/src/session/native-chat/input/MobileNativeChatCardHeaderAction.tsx similarity index 86% rename from mobile/src/session/native-chat/MobileNativeChatCardHeaderAction.tsx rename to mobile/src/session/native-chat/input/MobileNativeChatCardHeaderAction.tsx index 564a5fc6930..6f479175fb6 100644 --- a/mobile/src/session/native-chat/MobileNativeChatCardHeaderAction.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatCardHeaderAction.tsx @@ -1,8 +1,8 @@ import { Keyboard, Pressable, StyleSheet } from 'react-native' import { ChevronDown, X } from 'lucide-react-native' -import { colors, pressedStyle } from '../../theme/mobile-theme' -import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' -import { useMobileTranslation } from '../../i18n/use-mobile-translation' +import { colors, pressedStyle } from '../../../theme/mobile-theme' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' export function MobileNativeChatCardHeaderAction({ prompt, diff --git a/mobile/src/session/native-chat/input/MobileNativeChatOfflineQueue.tsx b/mobile/src/session/native-chat/input/MobileNativeChatOfflineQueue.tsx index 3a2b2e747c2..0db837cc17a 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatOfflineQueue.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatOfflineQueue.tsx @@ -1,10 +1,10 @@ import { useContext, useMemo } from 'react' import { queuedMessageCardLine } from '../../../../../src/shared/agent-session/view/structured-agent-session-queued-cards' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' -import type { MobileQueuedMessageCard } from '../../mobile-structured-queued-message-cards' +import type { MobileQueuedMessageCard } from './mobile-structured-queued-message-cards' import type { MobileNativeChatOfflineQueue as Queue } from '../composer/use-mobile-native-chat-offline-sends' import { MobileNativeChatQueuedMessages } from './MobileNativeChatQueuedMessages' -import { MobileNativeChatNoticeContext } from '../../mobile-native-chat-notice-context' +import { MobileNativeChatNoticeContext } from '../transcript/mobile-native-chat-notice-context' const PAUSED = { reason: 'failed' } as const diff --git a/mobile/src/session/native-chat/input/MobileNativeChatOptionPreviewSheet.tsx b/mobile/src/session/native-chat/input/MobileNativeChatOptionPreviewSheet.tsx new file mode 100644 index 00000000000..3328570d533 --- /dev/null +++ b/mobile/src/session/native-chat/input/MobileNativeChatOptionPreviewSheet.tsx @@ -0,0 +1,129 @@ +import { useMemo } from 'react' +import { Pressable, ScrollView, Text, View } from 'react-native' +import type { AskQuestion } from '../../../../../src/shared/native-chat/input/native-chat-ask-types' +import { KeyedBottomDrawer } from '../../../components/bottom-drawer/keyed-bottom-drawer' +import { SheetHeader } from '../../../components/nav-bar/SheetHeader' +import { SheetPrimaryButton } from '../../../components/SheetPrimaryButton' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' +import { colors, radii, spacing, typography, pressedStyle } from '../../../theme/mobile-theme' +import { themedStyleSheet } from '../../../theme/themed-styles' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { optionPreviewFont } from './mobile-native-chat-option-preview' + +const sheetKey = () => 'option-preview' + +export function MobileNativeChatOptionPreviewSheet({ + options, + viewedIndex, + selected, + disabled, + onView, + onChoose, + onClose +}: { + options: AskQuestion['options'] + viewedIndex: number | null + selected: readonly number[] + disabled: boolean + onView: (index: number) => void + onChoose: (index: number) => void + onClose: () => void +}): React.JSX.Element { + useMobileColorScheme() + const t = useMobileTranslation() + const previews = useMemo( + () => options.flatMap((option, index) => (option.preview ? [{ option, index }] : [])), + [options] + ) + return ( + + {(index) => { + const option = options[index]! + return ( + <> + item.index === index) + 1, + total: previews.length + })} + onClose={onClose} + /> + + {previews.length > 1 ? ( + + {previews.map((item) => ( + onView(item.index)} + style={({ pressed }) => [styles.page, pressed && pressedStyle]} + > + + + ))} + + ) : null} + onChoose(index)} + /> + + ) + }} + + ) +} + +function ReadablePreview({ preview }: { preview: string }): React.JSX.Element { + useMobileColorScheme() + return ( + + + + {preview} + + + + ) +} + +const styles = themedStyleSheet(() => ({ + preview: { flex: 1, minHeight: 0, backgroundColor: colors.bgPanel }, + text: { + padding: spacing.md, + color: colors.textPrimary, + fontFamily: optionPreviewFont, + fontSize: typography.bodySize, + lineHeight: typography.bodySize * 1.5 + }, + pages: { flexGrow: 0, marginVertical: spacing.xs }, + pageContent: { flexGrow: 1, justifyContent: 'center' }, + page: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' }, + dot: { width: 6, height: 6, borderRadius: radii.pill, backgroundColor: colors.borderSubtle }, + currentDot: { backgroundColor: colors.textPrimary } +})) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatOptionThumbnail.tsx b/mobile/src/session/native-chat/input/MobileNativeChatOptionThumbnail.tsx new file mode 100644 index 00000000000..1399a26db65 --- /dev/null +++ b/mobile/src/session/native-chat/input/MobileNativeChatOptionThumbnail.tsx @@ -0,0 +1,117 @@ +import { useState } from 'react' +import { Platform, Pressable, Text, View, type TextStyle } from 'react-native' +import { MobileEdgeFade } from '../../../components/MobileEdgeFade' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' +import { colors, radii, spacing, typography, pressedStyle } from '../../../theme/mobile-theme' +import { themedStyleSheet } from '../../../theme/themed-styles' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { optionPreviewFont } from './mobile-native-chat-option-preview' + +const MAX_HEIGHT = 96 - spacing.sm * 2 + +export function MobileNativeChatOptionThumbnail({ + label, + preview, + onPress +}: { + label: string + preview: string + onPress: () => void +}): React.JSX.Element { + return ( + + ) +} + +function MeasuredPreviewThumbnail({ + label, + preview, + onPress +}: { + label: string + preview: string + onPress: () => void +}): React.JSX.Element { + useMobileColorScheme() + const t = useMobileTranslation() + const [width, setWidth] = useState(0) + const [bounds, setBounds] = useState<{ width: number; height: number } | null>(null) + const scale = bounds && width > 0 ? Math.min(1, width / Math.max(1, bounds.width)) : 0 + const height = bounds && width > 0 ? Math.min(MAX_HEIGHT, bounds.height * scale) : MAX_HEIGHT + + return ( + [styles.thumbnail, pressed && pressedStyle]} + > + setWidth(event.nativeEvent.layout.width)} + style={[styles.image, { height }]} + accessibilityElementsHidden + importantForAccessibility="no-hide-descendants" + > + { + const { width, height } = event.nativeEvent.layout + setBounds((previous) => + previous?.width === width && previous.height === height ? previous : { width, height } + ) + }} + style={[styles.text, { transform: [{ scale }] }]} + > + {preview} + + {height >= MAX_HEIGHT ? : null} + + {t('chat.ask.viewPreview', 'View')} + + ) +} + +const styles = themedStyleSheet(() => ({ + thumbnail: { + minHeight: 44, + padding: spacing.sm, + borderWidth: 1, + borderColor: colors.borderSubtle, + borderRadius: radii.control, + backgroundColor: colors.bgPanel, + overflow: 'hidden' + }, + // Native rows measure absolute Text intrinsically; the web uses its explicit CSS width below. + image: { position: 'relative', flexDirection: 'row', overflow: 'hidden' }, + text: { + position: 'absolute', + ...(Platform.OS === 'web' + ? { + whiteSpace: 'pre' as const, + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: RN Web accepts intrinsic CSS widths; this branch never reaches native. + width: 'max-content' as TextStyle['width'] + } + : {}), + top: 0, + left: 0, + transformOrigin: 'top left', + fontFamily: optionPreviewFont, + fontSize: typography.metaSize, + lineHeight: typography.metaSize * 1.25, + color: colors.textPrimary, + includeFontPadding: false + }, + view: { + alignSelf: 'flex-end', + color: colors.textSecondary, + fontSize: typography.metaSize, + fontWeight: '600', + position: 'absolute', + right: spacing.xs, + bottom: spacing.xs, + paddingHorizontal: spacing.sm, + borderRadius: radii.pill, + backgroundColor: colors.bgRaised + } +})) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx b/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx index 960d7128b3c..f8fffdcc0bd 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx @@ -3,8 +3,8 @@ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native' import { ShieldQuestion } from 'lucide-react-native' import { approvalDetailPreview } from '../../../../../src/shared/agent-session/agent-session-approval-detail-preview' import { approvalBlockedPathToShow } from '../../../../../src/shared/agent-session/agent-session-approval-blocked-path' -import { MobileNativeChatCardHeaderAction } from '../MobileNativeChatCardHeaderAction' -import { MobileNativeChatMarkdown } from '../MobileNativeChatMarkdown' +import { MobileNativeChatCardHeaderAction } from './MobileNativeChatCardHeaderAction' +import { MobileNativeChatMarkdown } from '../transcript/MobileNativeChatMarkdown' import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' diff --git a/mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx b/mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx index 8de47622917..43c6bd612b3 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx @@ -1,8 +1,8 @@ import { useMemo, useRef, useState } from 'react' import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native' import { Check, CircleHelp } from 'lucide-react-native' -import { MobileNativeChatCardHeaderAction } from '../MobileNativeChatCardHeaderAction' -import { MobileNativeChatQuestionText } from '../MobileNativeChatQuestionText' +import { MobileNativeChatCardHeaderAction } from './MobileNativeChatCardHeaderAction' +import { MobileNativeChatQuestionText } from '../transcript/MobileNativeChatQuestionText' import { colors, concentricRadius, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' diff --git a/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.test.tsx b/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.test.tsx index b7cdfdd5a3c..2aa3100d182 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.test.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.test.tsx @@ -1,46 +1,14 @@ // @vitest-environment happy-dom import { activateMobileUiLocale } from '../../../i18n/mobile-i18n' -import { - mobileQueuedMessageCards, - type MobileQueuedMessageCard -} from '../../mobile-structured-queued-message-cards' +import type { MobileQueuedMessageCard } from './mobile-structured-queued-message-cards' import { createElement } from 'react' -import type * as QueuedCardRowModule from '../../MobileQueuedMessageCardRow' -import { MobileNativeChatLowerRegion } from '../MobileNativeChatLowerRegion.web' -import { - useMobileNativeChatLowerGeometry, - useMobileNativeChatLowerRegion -} from '../mobile-native-chat-lower-region-context' import { act, create, type ReactTestRenderer } from 'react-test-renderer' import { afterEach, describe, expect, it, vi } from 'vitest' import { MobileNativeChatQueuedMessages } from './MobileNativeChatQueuedMessages' -import { - useMobileNativeChatComposerTray, - type MobileNativeChatComposerTray -} from '../composer/use-mobile-native-chat-composer-tray' -import { useMobileQueuedMessageComposerActions } from '../../use-mobile-queued-message-composer-actions' -import type { MobileStructuredAgentMutate } from '../../use-mobile-structured-agent-mutation' -import type { - AgentSessionQueuedMessage, - AgentSessionQueuedMessageMoveResult -} from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import type { StructuredAgentSessionMutationResult } from '../../mobile-structured-agent-session-rpc' -const rowRenders = vi.hoisted(() => ({ count: 0 })) -vi.mock('../../MobileQueuedMessageCardRow', async (importOriginal) => { - const original = await importOriginal() - return { - MobileQueuedMessageCardRow: ( - props: Parameters[0] - ) => { - rowRenders.count++ - return createElement(original.MobileQueuedMessageCardRow, props) - } - } -}) let fontScale = 1 vi.mock('../../../components/DragReorderList', () => ({ DragReorderList: 'DragReorderList' })) vi.mock('react-native-reanimated', async () => { @@ -89,7 +57,7 @@ vi.mock('./MobileNativeChatDockedTaskList', () => ({ MobileNativeChatDockedTaskList: 'DockedTaskList' })) -vi.mock('../../MobileQueuedMessageComposer', () => ({ +vi.mock('./MobileQueuedMessageComposer', () => ({ MobileQueuedMessageComposer: 'QueuedMessageComposer' })) @@ -162,82 +130,6 @@ describe('MobileNativeChatQueuedMessages', () => { expect(sheet(mounted).props.visible).toBe(false) }) - it('the empty chord presses only the head card where its own button would, and offers it only while that press would act, across chat switches', async () => { - let tray!: MobileNativeChatComposerTray - function Tray(props: Parameters[0]['queued']) { - tray = useMobileNativeChatComposerTray({ - queued: props, - taskList: null, - backgroundTasks: null - }) - return createElement('View', null, tray.content) - } - const settles: ((accepted: boolean) => void)[] = [] - const onSend = vi.fn(() => new Promise((resolve) => settles.push(resolve))) - const chatA = { sessionKey: 'a', cards: [card({ messageId: 'a1' }), card({ messageId: 'a2' })] } - const chatB = { sessionKey: 'b', cards: [card({ messageId: 'b1' })] } - const show = async ( - chat: typeof chatA, - props: Partial[0]> = {} - ): Promise => { - await act(async () => { - renderer ??= create(createElement('View')) - renderer.update(createElement(Tray, { ...chat, agentWorking: true, onSend, ...props })) - }) - } - const press = (alternateSteers = true) => { - let pressed = false - act(() => { - pressed = tray.steerHead!.run(alternateSteers) - }) - return pressed - } - - await show(chatA) - expect(tray.steerHead!.steers(true)).toBe(true) - expect(press()).toBe(true) - expect(onSend).toHaveBeenLastCalledWith('a1') - // On its way: neither offered nor admitted again, like a second tap. - expect(tray.steerHead!.steers(true)).toBe(false) - expect(press()).toBe(false) - - // Another chat's head is its own; A settling later must not clear B's hold. - await show(chatB) - expect(tray.steerHead!.steers(true)).toBe(true) - expect(press()).toBe(true) - await act(async () => settles[0]!(true)) - expect(tray.steerHead!.steers(true)).toBe(false) - expect(press()).toBe(false) - await act(async () => settles[1]!(true)) - expect(tray.steerHead!.steers(true)).toBe(true) - - // Back to A, whose list remounts: an action started there still holds until it settles. - await show(chatA) - expect(press()).toBe(true) - await show(chatB) - await show(chatA) - expect(tray.steerHead!.steers(true)).toBe(false) - expect(press()).toBe(false) - await act(async () => settles[2]!(true)) - expect(tray.steerHead!.steers(true)).toBe(true) - - // No turn running (the paused queue after Stop): the head card's own Send. - await show(chatA, { agentWorking: false }) - expect(tray.steerHead!.steers(false)).toBe(true) - const refusals: [Partial[0]>, boolean][] = [ - [{}, false], - [{ steerHeld: true }, true], - [{ cards: [card({ messageId: 'a1', command: true }), card({ messageId: 'a2' })] }, true] - ] - for (const [props, alternateSteers] of refusals) { - await show(chatA, props) - expect(tray.steerHead!.steers(alternateSteers)).toBe(false) - const sends = onSend.mock.calls.length - expect(press(alternateSteers)).toBe(false) - expect(onSend).toHaveBeenCalledTimes(sends) - } - }) - it('runs one action per card at a time, however fast its controls are tapped', async () => { let answer: (done: boolean) => void = () => undefined const onSend = vi.fn(() => new Promise((resolve) => (answer = resolve))) @@ -270,71 +162,6 @@ describe('MobileNativeChatQueuedMessages', () => { }) } - it.each([100, 1000])('keeps pending work linear at %i cards', async (size) => { - let counting = false - let reads = 0 - let memberships = 0 - const ids = Array.from({ length: size }, (_, index) => `perf-card-${index}`) - const cards = ids.map((messageId) => - Object.defineProperty(card({ messageId }), 'messageId', { - get: () => { - reads += Number(counting) - return messageId - } - }) - ) - const has = Set.prototype.has - const membership = vi.spyOn(Set.prototype, 'has') - membership.mockImplementation(function (this: Set, key: unknown) { - if (counting && typeof key === 'string' && key.startsWith('perf-card-')) { - memberships++ - } - return has.call(this, key) - }) - const send = Promise.withResolvers() - const clear = Promise.withResolvers() - const onClearAll = vi.fn(() => clear.promise) - const onResume = vi.fn(async () => true) - const onSend = vi.fn(() => send.promise) - const mounted = await mountPaused({ - cards, - pause: { reason: 'stopped' }, - onClearAll, - onResume, - onSend - }) - const labels = ['Delete every queued message', 'Resume sending the queued messages'] - const steer = 'Send this message' - const trigger = async (action: () => void) => { - counting = true - await act(async () => action()) - counting = false - } - // The last card forces the complete conflict scan; admission and settlement each rerender. - await trigger(() => control(mounted, steer, size - 1).props.onPress()) - expect(onSend).toHaveBeenCalledExactlyOnceWith(ids.at(-1)) - expect(control(mounted, labels[0]!).props.disabled).toBe(true) - await trigger(() => send.resolve(false)) - // Same frame, before disabled state renders: the shared admission check refuses the rest. - await trigger(() => { - control(mounted, labels[0]!).props.onPress() - control(mounted, labels[1]!).props.onPress() - control(mounted, steer).props.onPress() - }) - expect(onClearAll).toHaveBeenCalledExactlyOnceWith(ids) - expect([onResume.mock.calls.length, onSend.mock.calls.length]).toEqual([0, 1]) - const greyed = () => [...labels, steer].map((label) => control(mounted, label).props.disabled) - expect(greyed()).toEqual([true, true, true]) - await trigger(() => clear.resolve(false)) - expect(greyed()).toEqual([false, false, false]) - // Four pending/settled renders plus admission: fixed linear budgets, independent of N. - expect(reads).toBeGreaterThanOrEqual(4 * size) - expect(reads).toBeLessThanOrEqual(36 * size) - expect(memberships).toBeGreaterThanOrEqual(size) - expect(memberships).toBeLessThanOrEqual(18 * size) - console.info('queue operation counts', { cards: size, reads, memberships }) - }) - function resumeButton(mounted: ReactTestRenderer) { return control(mounted, 'Resume sending the queued messages') } @@ -355,193 +182,4 @@ describe('MobileNativeChatQueuedMessages', () => { expect(onResume).toHaveBeenCalledTimes(2) }) }) - - it('admits one queue move synchronously, preserves unrelated actions and releases after settlement', async () => { - const messages: AgentSessionQueuedMessage[] = ['a', 'b', 'c', 'd'].map( - (messageId, position) => ({ - messageId, - position, - state: 'waiting', - body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: messageId }] } - }) - ) - messages[2]!.body.from = { kind: 'agent', senders: [], orchestration: null } - type MoveReply = StructuredAgentSessionMutationResult - const first = Promise.withResolvers() - const second = Promise.withResolvers() - const mutate = vi - .fn<(...args: Parameters) => Promise>() - .mockReturnValueOnce(first.promise) - .mockReturnValueOnce(second.promise) - const send = vi.fn(async () => true) - function Harness({ published = messages }: { published?: AgentSessionQueuedMessage[] }) { - const queue = useMobileQueuedMessageComposerActions({ - messages: published, - sessionId: 's', - sessionKey: 's', - reorderCapable: true, - enabled: true, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Vitest erases the generic result signature; this hook only requests queuedMessageMove, and both deferred replies are checked as MoveReply. - mutate: mutate as MobileStructuredAgentMutate, - onSendError: () => {} - }) - return createElement(MobileNativeChatQueuedMessages, { - cards: mobileQueuedMessageCards(queue.messages, []), - onMove: queue.move, - onSend: send - }) - } - await act(async () => { - renderer = create(createElement(Harness)) - }) - const list = () => renderer!.root.find((node) => String(node.type) === 'DragReorderList') - const ids = () => list().props.items.map((entry: MobileQueuedMessageCard) => entry.messageId) - const queuedMove = list().props.onMove - act(() => { - expect(queuedMove({ movedId: 'c', beforeId: 'a' })).toBe(true) - expect(queuedMove({ movedId: 'b', afterId: 'd' })).toBe(false) - }) - expect(mutate).toHaveBeenCalledTimes(1) - expect(ids()).toEqual(['c', 'a', 'b', 'd']) - expect(list().props.canMove).toBeUndefined() - expect(list().props.items[0].from).toBeDefined() - expect( - list().props.items.every((entry: MobileQueuedMessageCard) => - list().props.isMoveDisabled(entry) - ) - ).toBe(true) - const movedRow = list().props.renderRow(list().props.items[0]) - act(() => movedRow.props.onSend()) - expect(send).not.toHaveBeenCalled() - const otherRow = list().props.renderRow(list().props.items[1]) - await act(async () => otherRow.props.onSend()) - expect(send).toHaveBeenCalledExactlyOnceWith('a') - await act(async () => { - first.resolve({ - status: 'accepted', - value: { messageId: 'c', status: 'stale' }, - sameFence: true - }) - await first.promise - }) - expect(ids()).toEqual(['a', 'b', 'c', 'd']) - expect( - list().props.items.every( - (entry: MobileQueuedMessageCard) => !list().props.isMoveDisabled(entry) - ) - ).toBe(true) - act(() => expect(list().props.onMove({ movedId: 'b', afterId: 'd' })).toBe(true)) - // The host republishes the moved order with fresh positions; cards sort by position. - const published = [0, 2, 3, 1].map((index, position) => ({ ...messages[index]!, position })) - await act(async () => renderer!.update(createElement(Harness, { published }))) - expect(ids()).toEqual(['a', 'c', 'd', 'b']) - await act(async () => renderer!.unmount()) - renderer = null - await act(async () => { - second.resolve({ - status: 'accepted', - value: { messageId: 'b', status: 'moved' }, - sameFence: true - }) - await second.promise - }) - expect(mutate).toHaveBeenCalledTimes(2) - }) }) - -for (const population of [10, 100]) { - it(`does no queue population work on geometry-only updates with ${population} cards`, async () => { - const root = document.createElement('div') - root.dataset.testid = 'native-chat-view' - const viewport = document.createElement('div') - const content = document.createElement('div') - root.append(viewport) - viewport.append(content) - document.body.append(root) - let height = 180 - Object.defineProperties(viewport, { - clientHeight: { get: () => height }, - clientWidth: { value: 300 }, - scrollHeight: { value: 300 } - }) - vi.spyOn(root, 'getBoundingClientRect').mockImplementation(() => new DOMRect(0, 0, 300, 500)) - let visits = 0 - let operationRenders = 0 - let observedHeight = 0 - function GeometryConsumer() { - observedHeight = useMobileNativeChatLowerGeometry().viewportHeight - return null - } - function OperationConsumer() { - useMobileNativeChatLowerRegion() - operationRenders++ - return null - } - const cards = Array.from({ length: population }, (_, index) => { - const item = card({ messageId: `queued-${index}` }) - Object.defineProperty(item, 'messageId', { - get: () => { - visits++ - return `queued-${index}` - } - }) - return item - }) - let mounted: ReactTestRenderer | undefined - const paint = () => new Promise((resolve) => requestAnimationFrame(() => resolve())) - const render = (items: MobileQueuedMessageCard[]) => ( - - - - - - ) - try { - await act(async () => { - mounted = create(render(cards), { - createNodeMock: (element) => { - const props = element.props - if ( - typeof props === 'object' && - props !== null && - 'testID' in props && - props.testID === 'native-chat-lower-scroll' - ) { - return { getScrollableNode: () => viewport } - } - return element.type === 'ScrollView' ? { scrollToEnd: () => {} } : content - } - }) - }) - await act(paint) - await act(paint) - expect(observedHeight).toBe(180) - visits = 0 - operationRenders = 0 - rowRenders.count = 0 - await act(async () => { - height = 120 - window.dispatchEvent(new Event('resize')) - await paint() - }) - expect(observedHeight).toBe(120) - expect.soft(operationRenders).toBe(0) - expect.soft(rowRenders.count).toBe(0) - expect.soft(visits).toBe(0) - await act(async () => - mounted!.update( - render([...cards, card({ messageId: 'appended', text: 'A real new message' })]) - ) - ) - expect(rowRenders.count).toBeGreaterThan(0) - expect(visits).toBeGreaterThan(0) - expect( - mounted!.root.findAllByProps({ children: 'A real new message' }).length - ).toBeGreaterThan(0) - } finally { - await act(async () => mounted?.unmount()) - root.remove() - vi.restoreAllMocks() - } - }) -} diff --git a/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx b/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx index 40d45fd502b..b2a8d678a9e 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx @@ -9,7 +9,7 @@ import { colors } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { useLargeSystemText } from '../../../theme/use-large-system-text' -import type { MobileQueuedMessageCard } from '../../mobile-structured-queued-message-cards' +import type { MobileQueuedMessageCard } from './mobile-structured-queued-message-cards' import type { MobileQueuedHeadLight } from './mobile-queued-head-light' import { createMobileQueuedCardActionStore, @@ -18,10 +18,10 @@ import { } from './mobile-queued-card-actions' import { headSteerableQueuedMessageCard } from '../../../../../src/shared/agent-session/view/structured-agent-session-queued-cards' import type { AgentSessionQueuePause } from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import type { MobileQueuedMessageEdit } from '../../use-mobile-structured-queued-message-controls' -import type { MobileQueuedMessageInlineEditor } from '../../mobile-queued-message-edit-draft' -import { MobileQueuedMessageCardRow } from '../../MobileQueuedMessageCardRow' -import { MobileQueuedMessageScrollList } from '../../MobileQueuedMessageScrollList' +import type { MobileQueuedMessageEdit } from './use-mobile-structured-queued-message-controls' +import type { MobileQueuedMessageInlineEditor } from './mobile-queued-message-edit-draft' +import { MobileQueuedMessageCardRow } from './MobileQueuedMessageCardRow' +import { MobileQueuedMessageScrollList } from './MobileQueuedMessageScrollList' import { queuedMessageStyles as styles } from './mobile-native-chat-queued-message-styles' import { useMobileNativeChatLowerRegion } from '../mobile-native-chat-lower-region-context' diff --git a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.test.ts b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.test.ts index 676636c583d..f43f8762553 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.test.ts +++ b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.test.ts @@ -1,15 +1,3 @@ -import type { AgentSessionOptionsResult } from '../../../../../src/shared/agent-session/options/agent-session-options-result' -import { useMobileNativeChatSessionOptionController } from './use-mobile-native-chat-session-option-controller' -import { - OPTIONS, - BASE, - deferred, - accepted, - optionsClient, - recordingMutate, - mountOptions, - settle -} from '../../use-mobile-structured-agent-options.test-fixture' import { createElement, useState, type ReactNode } from 'react' import { act, type ReactTestRenderer } from 'react-test-renderer' import { createWithQueryClient } from '../../../test-support/query-client-test-renderer' @@ -180,124 +168,6 @@ describe('MobileNativeChatSessionOptionPickers', () => { await act(async () => rowByText('Full access').props.onPress()) expect(setOption).toHaveBeenCalledExactlyOnceWith('permissionMode', 'full-access') }) - - it('picks the shown Codex default during a reply and captures it only for the next message', async () => { - const pending = deferred() - let reads = 0 - const rpc = optionsClient(() => - ++reads === 1 - ? Promise.resolve({ - ...OPTIONS, - listedModelIds: OPTIONS.models.map((row) => row.id), - current: { model: 'gpt-fast', acceptedInputSources: { model: 'saved' }, defaulted: [] } - }) - : pending.promise - ) - const { calls, mutate } = recordingMutate(async () => - accepted({ key: 'effort', value: 'low', options: { effort: 'low' } }, true) - ) - const view = await mountOptions({ ...BASE, client: rpc.client, mutate, sendSelection: true }) - const snapshot = () => [...view.current().optionSnapshot] - try { - const update = mount(snapshot(), true) - const running = view.current().sendSelection() - expect(running).toEqual({ selection: { model: 'gpt-fast' } }) - await act(async () => pill('Model').props.onPress()) - setOption.mockImplementation(view.current().setStructuredOption) - await act(async () => pill('Low').props.onPress()) - await settle() - expect(calls.map((call) => call.fields)).toEqual([{ key: 'effort', value: 'low' }]) - expect(view.current().sendSelection()).toEqual({ - selection: { model: 'gpt-fast', effort: 'low' } - }) - expect(running).toEqual({ selection: { model: 'gpt-fast' } }) - update(true, snapshot()) - await act(async () => pill('Low').props.onPress()) - expect(setOption).toHaveBeenCalledTimes(1) - } finally { - await view.unmount() - } - }) - - it('renders actual settled permission/reset picks without busy state and locks ordinary pending model work', async () => { - const idle = deferred() - let reads = 0 - const rpc = optionsClient(() => - ++reads === 1 - ? Promise.resolve({ ...OPTIONS, current: { ...OPTIONS.current, permissionMode: 'ask' } }) - : idle.promise - ) - const replies = Array.from({ length: 3 }, () => deferred>()) - const { calls, mutate } = recordingMutate((index) => replies[index].promise) - const view = await mountOptions({ ...BASE, client: rpc.client, mutate, permissionOwn: true }) - function ActualPickers() { - const options = view.current() - const { nativeChatSessionOptions } = useMobileNativeChatSessionOptionController({ - client: rpc.client, - activeChatStructured: true, - activeSessionTabId: BASE.sessionId, - agent: 'codex', - dispatchCommand: async () => 'accepted', - hostId: 'phone-picker', - isTabChatView: () => true, - isWorking: true, - reportedModel: null, - structured: { - snapshot: options.optionSnapshot, - pendingId: options.pendingOptionId, - setOption: options.setStructuredOption, - invokeAction: options.invokeStructuredOption - }, - toggleTabChatView: () => {}, - worktreeId: 'workspace-1' - }) - return createElement(MobileNativeChatSessionOptionPickers, { - ...nativeChatSessionOptions!, - presentation: { page: 'model', refocus: false }, - onDrawerOpen: () => {}, - onNavigate: () => {} - }) - } - try { - await act(async () => { - renderer = createWithQueryClient(createElement(ActualPickers)) - }) - for (const [index, value] of ['full-access', 'own'].entries()) { - let pick!: Promise - act(() => { - pick = view.current().setStructuredOption('permissionMode', value) - }) - expect(calls[index]?.fields).toEqual({ key: 'permissionMode', value }) - expect(view.current().pendingOptionId).toBeNull() - await act(async () => renderer!.update(createElement(ActualPickers))) - expect(renderer!.root.findAllByProps({ children: 'Applying…' })).toHaveLength(0) - expect(rowByText('GPT Fast').props.accessibilityState?.disabled).toBe(false) - await act(async () => - replies[index].resolve( - accepted({ key: 'permissionMode', value, options: { permissionMode: value } }, true) - ) - ) - await expect(pick).resolves.toBe(true) - expect(view.current().pendingOptionId).toBeNull() - await act(async () => renderer!.update(createElement(ActualPickers))) - expect(renderer!.root.findAllByProps({ children: 'Applying…' })).toHaveLength(0) - expect(rowByText('GPT Fast').props.accessibilityState?.disabled).toBe(false) - } - act(() => { - void view.current().setStructuredOption('model', 'gpt-fast') - }) - await act(async () => renderer!.update(createElement(ActualPickers))) - expect(rowByText('GPT Live').props.accessibilityState?.disabled).toBe(true) - expect(renderer!.root.findAllByProps({ children: 'Applying…' })).toHaveLength(1) - expect(calls.map((call) => call.fields)).toEqual([ - { key: 'permissionMode', value: 'full-access' }, - { key: 'permissionMode', value: 'own' }, - { key: 'model', value: 'gpt-fast' } - ]) - } finally { - await view.unmount() - } - }) }) const PERMISSION_CHOICES: SessionOptionSelectChoice[] = [ diff --git a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx index 64360176bd6..973ee4010c1 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx @@ -36,7 +36,8 @@ import { descriptorRowElements, ModelPill, SessionOptionCaption, - SessionOptionSummaryRow + SessionOptionSummaryRow, + showsSessionOptionAsSwitch } from './MobileNativeChatSessionOptionRows' import { SessionOptionSegments, @@ -383,7 +384,7 @@ function OptionRows({ {descriptors.flatMap((descriptor) => { const isPermission = descriptor === permission const disabled = locked(descriptor) || !descriptor.settable - if (descriptor.kind.type === 'boolean' && !descriptor.action) { + if (showsSessionOptionAsSwitch(descriptor)) { return descriptorRowElements( { descriptor, diff --git a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionRows.tsx b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionRows.tsx index e8dfb017a3d..63c69893624 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionRows.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionRows.tsx @@ -17,6 +17,10 @@ import { nativeChatSessionOptionActionLabel, nativeChatSessionOptionLabel } from '../../../../../src/shared/native-chat/input/native-chat-session-option-labels' +import { + loneFastTier, + speedButtonTiers +} from '../../../../../src/shared/native-chat/input/native-chat-session-speed-tiers' import { sayMobileNativeChatSessionOption as say } from './native-chat-session-option-copy' import { MobileSwitch } from '../../../components/MobileSwitch' @@ -131,6 +135,20 @@ type DescriptorRowsProps = { moreChoices?: { shown: boolean; onToggle: () => void } } +/** A Fast-only speed, which toggles on one row like Claude's Fast mode. */ +function fastSpeedSwitch(descriptor: SessionOptionDescriptor) { + const speed = speedButtonTiers(descriptor) + const fast = speed ? loneFastTier(speed.tiers) : undefined + return speed && fast ? { standard: speed.standard, fast } : null +} + +export function showsSessionOptionAsSwitch(descriptor: SessionOptionDescriptor): boolean { + return ( + !descriptor.action && + (descriptor.kind.type === 'boolean' || fastSpeedSwitch(descriptor) !== null) + ) +} + /** One descriptor's rows as an array, so a ListGroup can draw the separators between them. */ export function descriptorRowElements( { @@ -156,8 +174,10 @@ export function descriptorRowElements( ] } // An unknown baseline still renders a switch; the source marker prevents implying the value was confirmed. - if (descriptor.kind.type === 'boolean') { - const label = nativeChatSessionOptionLabel(descriptor, say) + const fastSpeed = fastSpeedSwitch(descriptor) + if (descriptor.kind.type === 'boolean' || fastSpeed) { + const { kind } = descriptor + const label = fastSpeed ? say('fastMode') : nativeChatSessionOptionLabel(descriptor, say) const marker = sessionOptionValueMarker(descriptor) return [ onSetOption(next)} + value={ + kind.type === 'boolean' + ? kind.currentValue + : kind.currentValue === fastSpeed?.fast.value + } + onValueChange={(next) => + onSetOption( + fastSpeed ? (next ? fastSpeed.fast.value : fastSpeed.standard.value) : next + ) + } disabled={locked} /> } diff --git a/mobile/src/session/native-chat/input/MobileNativeChatThreadGoalStrip.tsx b/mobile/src/session/native-chat/input/MobileNativeChatThreadGoalStrip.tsx index 9d73bb6da3f..d1aa5793cd7 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatThreadGoalStrip.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatThreadGoalStrip.tsx @@ -17,7 +17,7 @@ import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { COMPOSER_TUCK } from '../composer/mobile-native-chat-composer-styles' import { queuedMessageStyles } from './mobile-native-chat-queued-message-styles' -import type { MobileStructuredThreadGoal } from '../../use-mobile-structured-thread-goal' +import type { MobileStructuredThreadGoal } from './use-mobile-structured-thread-goal' type Translate = ReturnType diff --git a/mobile/src/session/MobileQueuedCardText.tsx b/mobile/src/session/native-chat/input/MobileQueuedCardText.tsx similarity index 85% rename from mobile/src/session/MobileQueuedCardText.tsx rename to mobile/src/session/native-chat/input/MobileQueuedCardText.tsx index c0af69cf12f..d97d8e1beeb 100644 --- a/mobile/src/session/MobileQueuedCardText.tsx +++ b/mobile/src/session/native-chat/input/MobileQueuedCardText.tsx @@ -1,10 +1,10 @@ import { useState } from 'react' import { Pressable, Text, View } from 'react-native' -import { colors, typography, pressedStyle } from '../theme/mobile-theme' -import { themedStyleSheet } from '../theme/themed-styles' -import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' -import { queuedMessageStyles } from './native-chat/input/mobile-native-chat-queued-message-styles' -import { useMobileTranslation } from '../i18n/use-mobile-translation' +import { colors, typography, pressedStyle } from '../../../theme/mobile-theme' +import { themedStyleSheet } from '../../../theme/themed-styles' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { queuedMessageStyles } from './mobile-native-chat-queued-message-styles' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' // Expand clipped queued text before Steer/Delete; use the queue’s capped scroller rather than nesting another scroll box. export function MobileQueuedCardText({ diff --git a/mobile/src/session/MobileQueuedMessageCardRow.tsx b/mobile/src/session/native-chat/input/MobileQueuedMessageCardRow.tsx similarity index 91% rename from mobile/src/session/MobileQueuedMessageCardRow.tsx rename to mobile/src/session/native-chat/input/MobileQueuedMessageCardRow.tsx index 66460db018e..d7bb5553a1b 100644 --- a/mobile/src/session/MobileQueuedMessageCardRow.tsx +++ b/mobile/src/session/native-chat/input/MobileQueuedMessageCardRow.tsx @@ -10,19 +10,19 @@ import { Send, Trash2 } from 'lucide-react-native' -import { colors, pressedStyle } from '../theme/mobile-theme' -import { queuedMessageCardSteers } from '../../../src/shared/agent-session/view/structured-agent-session-queued-cards' -import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { colors, pressedStyle } from '../../../theme/mobile-theme' +import { queuedMessageCardSteers } from '../../../../../src/shared/agent-session/view/structured-agent-session-queued-cards' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import type { MobileQueuedMessageCard } from './mobile-structured-queued-message-cards' -import type { MobileQueuedMessageInlineEditor } from './use-mobile-structured-queued-edit' +import type { MobileQueuedMessageInlineEditor } from './mobile-queued-message-edit-draft' import { MobileQueuedMessageComposer } from './MobileQueuedMessageComposer' import { MobileQueuedCardText } from './MobileQueuedCardText' -import type { MobileQueuedHeadLight } from './native-chat/input/mobile-queued-head-light' +import type { MobileQueuedHeadLight } from './mobile-queued-head-light' const subscribeNever = () => () => undefined -import { MobileAgentMessageSenders } from './native-chat/transcript/MobileAgentMessageSenders' -import { queuedMessageStyles as styles } from './native-chat/input/mobile-native-chat-queued-message-styles' -import { useMobileTranslation } from '../i18n/use-mobile-translation' +import { MobileAgentMessageSenders } from '../transcript/MobileAgentMessageSenders' +import { queuedMessageStyles as styles } from './mobile-native-chat-queued-message-styles' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' type QueuedMessageCardRowProps = { dragHandle?: ReactNode diff --git a/mobile/src/session/MobileQueuedMessageComposer.tsx b/mobile/src/session/native-chat/input/MobileQueuedMessageComposer.tsx similarity index 82% rename from mobile/src/session/MobileQueuedMessageComposer.tsx rename to mobile/src/session/native-chat/input/MobileQueuedMessageComposer.tsx index 6ec1670fbff..94bec868eb4 100644 --- a/mobile/src/session/MobileQueuedMessageComposer.tsx +++ b/mobile/src/session/native-chat/input/MobileQueuedMessageComposer.tsx @@ -1,21 +1,21 @@ import { useContext, useEffect, useMemo, useRef } from 'react' import { MobileQueuedMessageComposerContext } from './mobile-queued-message-composer-context' import { Text, type TextInput, View } from 'react-native' -import { MobileNativeChatComposer } from './native-chat/composer/MobileNativeChatComposer' -import type { MobileQueuedMessageInlineEditor } from './use-mobile-structured-queued-edit' -import { useMobileNativeChatFileAttachments } from './native-chat/composer/use-mobile-native-chat-file-attachments' -import { useMobileNativeChatImageUpload } from './native-chat/composer/use-mobile-native-chat-image-upload' +import { MobileNativeChatComposer } from '../composer/MobileNativeChatComposer' +import type { MobileQueuedMessageInlineEditor } from './mobile-queued-message-edit-draft' +import { useMobileNativeChatFileAttachments } from '../composer/use-mobile-native-chat-file-attachments' +import { useMobileNativeChatImageUpload } from '../composer/use-mobile-native-chat-image-upload' import { updateMobileNativeChatDraftInputs, useMobileNativeChatDraftInputs -} from './native-chat/composer/mobile-native-chat-draft-inputs' -import { prepareQueuedMessageEditText } from '../../../src/shared/queued-message-edit-command' -import type { AgentMessageSource } from '../../../src/shared/agent-session/agent-session-message-source' -import { MobileAgentMessageSenders } from './native-chat/transcript/MobileAgentMessageSenders' -import { queuedMessageStyles as styles } from './native-chat/input/mobile-native-chat-queued-message-styles' -import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' -import { createUuidV4 } from '../../../src/shared/uuid-v4' -import { useMobileTranslation } from '../i18n/use-mobile-translation' +} from '../composer/mobile-native-chat-draft-inputs' +import { prepareQueuedMessageEditText } from '../../../../../src/shared/queued-message-edit-command' +import type { AgentMessageSource } from '../../../../../src/shared/agent-session/agent-session-message-source' +import { MobileAgentMessageSenders } from '../transcript/MobileAgentMessageSenders' +import { queuedMessageStyles as styles } from './mobile-native-chat-queued-message-styles' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { createNonSecureContextUuid as createUuidV4 } from '../../../../../src/shared/non-secure-context-uuid' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' export function MobileQueuedMessageComposer({ editor, diff --git a/mobile/src/session/MobileQueuedMessageScrollList.tsx b/mobile/src/session/native-chat/input/MobileQueuedMessageScrollList.tsx similarity index 95% rename from mobile/src/session/MobileQueuedMessageScrollList.tsx rename to mobile/src/session/native-chat/input/MobileQueuedMessageScrollList.tsx index fac8c9e1e8f..c7a05ffed9d 100644 --- a/mobile/src/session/MobileQueuedMessageScrollList.tsx +++ b/mobile/src/session/native-chat/input/MobileQueuedMessageScrollList.tsx @@ -6,9 +6,9 @@ import Animated, { type AnimatedRef, type SharedValue } from 'react-native-reanimated' -import { spacing } from '../theme/mobile-theme' -import { MobileEdgeFade } from '../components/MobileEdgeFade' -import { useMobileNativeChatLowerRegion } from './native-chat/mobile-native-chat-lower-region-context' +import { spacing } from '../../../theme/mobile-theme' +import { MobileEdgeFade } from '../../../components/MobileEdgeFade' +import { useMobileNativeChatLowerRegion } from '../mobile-native-chat-lower-region-context' /** Desktop's `max-h-40`: three one-line cards and part of a fourth, whose cut-off edge is the * sign that the list scrolls. */ diff --git a/mobile/src/session/mobile-background-task-copy.ts b/mobile/src/session/native-chat/input/mobile-background-task-copy.ts similarity index 98% rename from mobile/src/session/mobile-background-task-copy.ts rename to mobile/src/session/native-chat/input/mobile-background-task-copy.ts index e204e2e8005..52cd8474413 100644 --- a/mobile/src/session/mobile-background-task-copy.ts +++ b/mobile/src/session/native-chat/input/mobile-background-task-copy.ts @@ -1,8 +1,8 @@ import type { BackgroundTaskCopyId, BackgroundTaskSay -} from '../../../src/shared/background-task-copy' -import { translate } from '../i18n/mobile-i18n' +} from '../../../../../src/shared/background-task-copy' +import { translate } from '../../../i18n/mobile-i18n' export const sayMobileBackgroundTask: BackgroundTaskSay = (id, values) => { const count = values?.count diff --git a/mobile/src/session/mobile-host-model-catalog-query.ts b/mobile/src/session/native-chat/input/mobile-host-model-catalog-query.ts similarity index 82% rename from mobile/src/session/mobile-host-model-catalog-query.ts rename to mobile/src/session/native-chat/input/mobile-host-model-catalog-query.ts index 04e3eb307f4..eb6eb1ff7d1 100644 --- a/mobile/src/session/mobile-host-model-catalog-query.ts +++ b/mobile/src/session/native-chat/input/mobile-host-model-catalog-query.ts @@ -1,18 +1,18 @@ import { useMemo } from 'react' import { useQueryClient } from '@tanstack/react-query' -import type { AgentSessionModelCatalogResult } from '../../../src/shared/agent-session/agent-session-wire' +import type { AgentSessionModelCatalogResult } from '../../../../../src/shared/agent-session/agent-session-wire' import { hostReadsSavedModelCatalogsOnly, selectHostModelCatalogSnapshot, withHostModelCatalogAnswer, type HostModelCatalogSnapshot -} from '../../../src/shared/agent-session/options/host-model-catalog-snapshot-rules' -import { defineHostQuery, useHostQuery } from '../query/host-query' -import { hostQueryKey } from '../query/host-query-key' -import { useWorkspaceExecutionHost } from '../navigation/workspace-execution-host' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { readRuntimeStatusProbe } from '../transport/connection/runtime-status-probe' -import { callAgentSession } from './mobile-structured-agent-session-rpc' +} from '../../../../../src/shared/agent-session/options/host-model-catalog-snapshot-rules' +import { defineHostQuery, useHostQuery } from '../../../query/host-query' +import { hostQueryKey } from '../../../query/host-query-key' +import { useWorkspaceExecutionHost } from '../../../navigation/workspace-execution-host' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { readRuntimeStatusProbe } from '../../../transport/connection/runtime-status-probe' +import { callAgentSession } from '../agent-session/mobile-structured-agent-session-rpc' import type { HostModelCatalogCache } from './use-mobile-host-model-catalog-upgrade' // The host's account-level lists per agent, which a chat's first frame shows before its own read answers. diff --git a/mobile/src/session/native-chat/input/mobile-native-chat-answer-stepping.test.ts b/mobile/src/session/native-chat/input/mobile-native-chat-answer-stepping.test.ts deleted file mode 100644 index ed8d3c730ea..00000000000 --- a/mobile/src/session/native-chat/input/mobile-native-chat-answer-stepping.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - mobileNativeChatQuestionOffsets, - scheduleMobileClaudeAnswer, - MOBILE_NATIVE_CHAT_ADVANCE_BUFFER_MS, - MOBILE_NATIVE_CHAT_SUBMIT_DELAY_MS -} from './mobile-native-chat-answer-stepping' - -describe('mobileNativeChatQuestionOffsets', () => { - it('paces each question a full step apart, Enter 500ms after its body', () => { - expect(mobileNativeChatQuestionOffsets(0)).toEqual({ bodyAt: 0, enterAt: 500 }) - expect(mobileNativeChatQuestionOffsets(1)).toEqual({ bodyAt: 1000, enterAt: 1500 }) - expect(mobileNativeChatQuestionOffsets(2)).toEqual({ bodyAt: 2000, enterAt: 2500 }) - }) -}) - -describe('scheduleMobileClaudeAnswer', () => { - beforeEach(() => vi.useFakeTimers()) - afterEach(() => vi.useRealTimers()) - - it('writes each question body then its Enter, paced per step, one Enter per line', () => { - const events: string[] = [] - const timers = scheduleMobileClaudeAnswer( - ['', 'b', 'c'], - (line) => events.push(`body:${line}`), - () => events.push('enter') - ) - expect(timers).toHaveLength(6) - - vi.advanceTimersByTime(0) - expect(events).toEqual(['body:']) - vi.advanceTimersByTime(MOBILE_NATIVE_CHAT_SUBMIT_DELAY_MS - 1) - expect(events).toEqual(['body:']) - vi.advanceTimersByTime(1) - expect(events).toEqual(['body:', 'enter']) - vi.advanceTimersByTime(MOBILE_NATIVE_CHAT_ADVANCE_BUFFER_MS) - expect(events).toEqual(['body:', 'enter', 'body:b']) - - vi.runAllTimers() - expect(events).toEqual(['body:', 'enter', 'body:b', 'enter', 'body:c', 'enter']) - }) - - it('cancelling the returned timers stops all pending writes', () => { - const events: string[] = [] - const timers = scheduleMobileClaudeAnswer( - ['a', 'b'], - (line) => events.push(`body:${line}`), - () => events.push('enter') - ) - for (const timer of timers) { - clearTimeout(timer) - } - vi.runAllTimers() - expect(events).toEqual([]) - }) -}) diff --git a/mobile/src/session/native-chat/input/mobile-native-chat-answer-stepping.ts b/mobile/src/session/native-chat/input/mobile-native-chat-answer-stepping.ts deleted file mode 100644 index ad105d55a74..00000000000 --- a/mobile/src/session/native-chat/input/mobile-native-chat-answer-stepping.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { - nativeChatQuestionOffsets, - scheduleNativeChatAnswer, - NATIVE_CHAT_ADVANCE_BUFFER_MS, - NATIVE_CHAT_QUESTION_STEP_MS, - NATIVE_CHAT_SUBMIT_DELAY_MS -} from '../../../../../src/shared/native-chat/input/native-chat-answer-stepping' - -export const MOBILE_NATIVE_CHAT_ADVANCE_BUFFER_MS = NATIVE_CHAT_ADVANCE_BUFFER_MS -export const MOBILE_NATIVE_CHAT_QUESTION_STEP_MS = NATIVE_CHAT_QUESTION_STEP_MS -export const MOBILE_NATIVE_CHAT_SUBMIT_DELAY_MS = NATIVE_CHAT_SUBMIT_DELAY_MS -export const mobileNativeChatQuestionOffsets = nativeChatQuestionOffsets -export const scheduleMobileClaudeAnswer = scheduleNativeChatAnswer diff --git a/mobile/src/session/native-chat/input/mobile-native-chat-answered-prompts.test-fixture.ts b/mobile/src/session/native-chat/input/mobile-native-chat-answered-prompts.test-fixture.ts deleted file mode 100644 index 829ffd49bda..00000000000 --- a/mobile/src/session/native-chat/input/mobile-native-chat-answered-prompts.test-fixture.ts +++ /dev/null @@ -1,261 +0,0 @@ -// A structured Claude chat on the desktop, read back with `agentSession.history` after its owner -// approved a Bash call and answered an AskUserQuestion (Claude Code 2.1.300, 2026-10-15). - -import type { AgentJournalRenderItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' - -export const ANSWERED_PROMPT_ITEMS: AgentJournalRenderItem[] = [ - { - itemId: 'orca:1792024009236-87ed41e5430c47008ef7978ca5d60505', - revision: 0, - body: { - kind: 'message', - role: 'user', - blocks: [ - { - type: 'text', - text: 'Run the bash command: echo receipt-check > receipt.txt' - } - ] - }, - sequence: 43, - observedAt: 1792024010442, - turnScope: { - kind: 'thread' - } - }, - { - itemId: - 'orca:claude-tool%3A2ee61cec-1207-4f44-a915-b6adbcbf169b%3Atoolu_01FhhC72BuRiD3d9hXkAsURD', - revision: 2, - body: { - kind: 'tool-call', - name: 'Bash', - input: { - command: 'echo receipt-check > receipt.txt', - description: 'Write receipt-check to receipt.txt' - }, - callId: 'toolu_01FhhC72BuRiD3d9hXkAsURD', - state: 'completed', - output: { - head: '(Bash completed with no output)', - byteLength: 31, - digest: 'cac1685585590361647e7a27b58a25a590f335bfcd0ab70fcdc0424e40c20843', - truncated: false - } - }, - sequence: 46, - observedAt: 1792024014854, - turnScope: { - kind: 'turn', - turnItemId: - 'legacy:claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:turn-lifecycle%3A6a80bc01-4be4-466f-bd98-5cae8368ed1d' - } - }, - { - itemId: - 'orca:claude-prompt%3Aclaude_c65b4304_2acf_4eec_a749_e06876328e0c%3A79c45210-730e-482c-82bd-db0708c992e5', - revision: 2, - body: { - kind: 'approval', - title: 'Allow Bash?', - displayName: 'Bash', - description: 'Write receipt-check to receipt.txt', - blockedPath: '/private/tmp/mnw-scratch/receipt.txt', - detail: - '{\n "command": "echo receipt-check > receipt.txt",\n "description": "Write receipt-check to receipt.txt"\n}', - options: [ - { - id: 'allow', - label: 'Allow' - }, - { - id: 'allowForSession', - label: 'Allow for this session' - }, - { - id: 'deny', - label: 'Deny' - } - ], - resolution: { - state: 'resolved', - selectedOptionId: 'allow', - resolvedBy: 'desktop-renderer', - resolvedAt: 1792024037937 - } - }, - sequence: 48, - observedAt: 1792024014910, - turnScope: { - kind: 'turn', - turnItemId: - 'legacy:claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:turn-lifecycle%3A6a80bc01-4be4-466f-bd98-5cae8368ed1d' - } - }, - { - itemId: 'claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:4408cf7b-bbe2-4a5d-aa80-6471db6d9c18', - revision: 2, - body: { - kind: 'message', - role: 'assistant', - blocks: [ - { - type: 'text', - text: 'I ran the command. `receipt.txt` in `/private/tmp/mnw-scratch` now holds the line `receipt-check`.' - } - ] - }, - sequence: 51, - observedAt: 1792024040901, - turnScope: { - kind: 'turn', - turnItemId: - 'legacy:claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:turn-lifecycle%3A6a80bc01-4be4-466f-bd98-5cae8368ed1d' - } - }, - { - itemId: 'orca:1792024048049-e58989ce33eb411a970225d03d2e4a18', - revision: 0, - body: { - kind: 'message', - role: 'user', - blocks: [ - { - type: 'text', - text: 'Use the AskUserQuestion tool to ask me whether I prefer tea or coffee, then reply with my answer in one short sentence.' - } - ] - }, - sequence: 57, - observedAt: 1792024048075, - turnScope: { - kind: 'thread' - } - }, - { - itemId: - 'orca:claude-tool%3A2ee61cec-1207-4f44-a915-b6adbcbf169b%3Atoolu_01WByTgphD4HFByq9zPKXn2J', - revision: 2, - body: { - kind: 'tool-call', - name: 'AskUserQuestion', - input: { - questions: [ - { - question: 'Do you prefer tea or coffee?', - header: 'Drink', - multiSelect: false, - options: [ - { - label: 'Tea', - description: 'You prefer tea.' - }, - { - label: 'Coffee', - description: 'You prefer coffee.' - } - ] - } - ] - }, - callId: 'toolu_01WByTgphD4HFByq9zPKXn2J', - state: 'completed', - output: { - head: 'Your questions have been answered: "Do you prefer tea or coffee?"="Coffee". You can now continue with these answers in mind.', - byteLength: 124, - digest: '65dfb0af315c9ab1c70fc26c6c6bfb4ef0138216e35c89cf332927d10cbb629d', - truncated: false - } - }, - sequence: 60, - observedAt: 1792024052205, - turnScope: { - kind: 'turn', - turnItemId: - 'legacy:claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:turn-lifecycle%3A1ea7cf09-10c6-4635-93bb-8d4b16fb1c60' - } - }, - { - itemId: - 'orca:claude-prompt%3Aclaude_c65b4304_2acf_4eec_a749_e06876328e0c%3A6fd8be6a-830f-44b4-a3ef-a7f044cd4a4f', - revision: 2, - body: { - kind: 'question', - question: 'Do you prefer tea or coffee?', - options: [ - { - id: 'q1:choice-1', - label: 'Tea', - description: 'You prefer tea.' - }, - { - id: 'q1:choice-2', - label: 'Coffee', - description: 'You prefer coffee.' - } - ], - freeTextQuestionId: 'q1', - questions: [ - { - id: 'q1', - question: 'Do you prefer tea or coffee?', - header: 'Drink', - multiSelect: false, - options: [ - { - id: 'q1:choice-1', - label: 'Tea', - description: 'You prefer tea.' - }, - { - id: 'q1:choice-2', - label: 'Coffee', - description: 'You prefer coffee.' - } - ], - freeTextQuestionId: 'q1' - } - ], - resolution: { - state: 'resolved', - selectedOptionId: 'question-group:[{"questionId":"q1","optionIds":["q1:choice-2"]}]', - answers: [ - { - questionId: 'q1', - optionIds: ['q1:choice-2'] - } - ], - resolvedBy: 'desktop-renderer', - resolvedAt: 1792024081980 - } - }, - sequence: 62, - observedAt: 1792024052228, - turnScope: { - kind: 'turn', - turnItemId: - 'legacy:claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:turn-lifecycle%3A1ea7cf09-10c6-4635-93bb-8d4b16fb1c60' - } - }, - { - itemId: 'claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:32c8a879-b68c-41ba-9a9e-4b844fa07890', - revision: 2, - body: { - kind: 'message', - role: 'assistant', - blocks: [ - { - type: 'text', - text: 'You prefer coffee.' - } - ] - }, - sequence: 65, - observedAt: 1792024084176, - turnScope: { - kind: 'turn', - turnItemId: - 'legacy:claude:2ee61cec-1207-4f44-a915-b6adbcbf169b:turn-lifecycle%3A1ea7cf09-10c6-4635-93bb-8d4b16fb1c60' - } - } -] diff --git a/mobile/src/session/native-chat/input/mobile-native-chat-option-preview.ts b/mobile/src/session/native-chat/input/mobile-native-chat-option-preview.ts new file mode 100644 index 00000000000..e39b7d52635 --- /dev/null +++ b/mobile/src/session/native-chat/input/mobile-native-chat-option-preview.ts @@ -0,0 +1,7 @@ +import { Platform } from 'react-native' +import { typography } from '../../../theme/mobile-theme' + +export const optionPreviewFont = Platform.select({ + ios: 'Menlo', + default: typography.monoFamily +}) diff --git a/mobile/src/session/native-chat/input/mobile-native-chat-permission-send.ts b/mobile/src/session/native-chat/input/mobile-native-chat-permission-send.ts index 8a63b5555c7..f40820cae03 100644 --- a/mobile/src/session/native-chat/input/mobile-native-chat-permission-send.ts +++ b/mobile/src/session/native-chat/input/mobile-native-chat-permission-send.ts @@ -3,11 +3,11 @@ import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { sendMobileNativeChatMessageWithOutcome, type MobileNativeChatSendOutcome -} from '../mobile-native-chat-send' +} from '../composer/mobile-native-chat-send' import { acquireMobileNativeChatTerminalWrite, releaseMobileNativeChatTerminalWrite -} from '../mobile-native-chat-terminal-write-lock' +} from '../composer/mobile-native-chat-terminal-write-lock' export function sendMobileNativeChatPermissionResponse(args: { client: RpcClient diff --git a/mobile/src/session/mobile-prompt-controller.test.ts b/mobile/src/session/native-chat/input/mobile-prompt-controller.test.ts similarity index 90% rename from mobile/src/session/mobile-prompt-controller.test.ts rename to mobile/src/session/native-chat/input/mobile-prompt-controller.test.ts index 8b82f60e47a..c863cdd5e25 100644 --- a/mobile/src/session/mobile-prompt-controller.test.ts +++ b/mobile/src/session/native-chat/input/mobile-prompt-controller.test.ts @@ -1,11 +1,11 @@ import { act } from 'react-test-renderer' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import './native-chat/input/mobile-native-chat-prompt-dismissals' -import './mobile-structured-send-delivery' -import './structured-agent-send-result.test-fixture' -import type { RpcResponse } from '../transport/rpc/rpc-wire-types' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { NativeChatMessage } from '../../../src/shared/native-chat/native-chat-types' +import './mobile-native-chat-prompt-dismissals' +import '../composer/mobile-structured-send-delivery' +import '../../structured-agent-send-result.test-fixture' +import type { RpcResponse } from '../../../transport/rpc/rpc-wire-types' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { session, @@ -20,7 +20,7 @@ import { permissionAction, getController, askAnswer -} from './__mocks__/mobile-prompt-controller' +} from '../../__mocks__/mobile-prompt-controller' const client: RpcClient = { sendRequest: vi.fn(), diff --git a/mobile/src/session/mobile-queued-message-composer-context.ts b/mobile/src/session/native-chat/input/mobile-queued-message-composer-context.ts similarity index 80% rename from mobile/src/session/mobile-queued-message-composer-context.ts rename to mobile/src/session/native-chat/input/mobile-queued-message-composer-context.ts index c4e6382b5d6..9bfeea6fd03 100644 --- a/mobile/src/session/mobile-queued-message-composer-context.ts +++ b/mobile/src/session/native-chat/input/mobile-queued-message-composer-context.ts @@ -1,6 +1,6 @@ -import type { NativeChatSkillDiscoverySnapshot } from '../../../src/shared/native-chat/input/native-chat-picker-items' +import type { NativeChatSkillDiscoverySnapshot } from '../../../../../src/shared/native-chat/input/native-chat-picker-items' import { createContext } from 'react' -import type { MobileNativeChatStructuredComposer } from './native-chat/composer/MobileNativeChatComposer' +import type { MobileNativeChatStructuredComposer } from '../composer/mobile-native-chat-structured-composer' export const MobileQueuedMessageComposerContext = createContext<{ agent: string | null diff --git a/mobile/src/session/mobile-queued-message-edit-draft.ts b/mobile/src/session/native-chat/input/mobile-queued-message-edit-draft.ts similarity index 86% rename from mobile/src/session/mobile-queued-message-edit-draft.ts rename to mobile/src/session/native-chat/input/mobile-queued-message-edit-draft.ts index dc794e4dba1..81bf996551b 100644 --- a/mobile/src/session/mobile-queued-message-edit-draft.ts +++ b/mobile/src/session/native-chat/input/mobile-queued-message-edit-draft.ts @@ -1,5 +1,8 @@ -import type { QueuedEditLeaseLoss } from '../../../src/shared/queued-message-edit-lease' -import type { MobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' +import type { + QueuedEditLease, + QueuedEditLeaseLoss +} from '../../../../../src/shared/queued-message-edit-lease' +import type { MobileStructuredAgentMutate } from '../agent-session/use-mobile-structured-agent-mutation' import type { MobileQueuedMessageCard } from './mobile-structured-queued-message-cards' import { mobileQueuedEditValueKey, @@ -11,23 +14,22 @@ import { closeMobileNativeChatEditInputs, readMobileNativeChatDraftInputs, updateMobileNativeChatDraftInputs -} from './native-chat/composer/mobile-native-chat-draft-inputs' -import type { AgentSessionQueuedMessage } from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import type { AgentJournalSubmission } from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { MobileQueuedEditLease } from './mobile-queued-message-edit-lease' -import { pickedSkillsInText } from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' +} from '../composer/mobile-native-chat-draft-inputs' +import type { AgentSessionQueuedMessage } from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +import type { AgentJournalSubmission } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { pickedSkillsInText } from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' import { queuedEditKeptImages, queuedEditTextForKeptImages, type QueuedMessageImageBlock -} from '../../../src/shared/queued-message-edit' -import { appendReturnedDraftText } from '../../../src/shared/returned-draft-text' +} from '../../../../../src/shared/queued-message-edit' +import { appendReturnedDraftText } from '../../../../../src/shared/returned-draft-text' import { setMobileNativeChatDraftText, type MobileComposerWriter -} from './native-chat/composer/mobile-native-chat-draft-store' -import { nativeChatTextWithFileReferences } from './native-chat/composer/mobile-native-chat-file-attachment' +} from '../composer/mobile-native-chat-draft-store' +import { nativeChatTextWithFileReferences } from '../composer/mobile-native-chat-file-attachment' export type MobileQueuedMessageInlineEditor = { messageId: string @@ -84,7 +86,7 @@ export type MobileQueuedEditDraft = { attempt?: MobileQueuedEditAttempt closed: boolean pendingClose: boolean - lease: MobileQueuedEditLease + lease: QueuedEditLease } export function closeMobileQueuedEditDraft( diff --git a/mobile/src/session/mobile-queued-message-edit-lease.ts b/mobile/src/session/native-chat/input/mobile-queued-message-edit-lease.ts similarity index 66% rename from mobile/src/session/mobile-queued-message-edit-lease.ts rename to mobile/src/session/native-chat/input/mobile-queued-message-edit-lease.ts index 424f19e43a4..616a64e4c74 100644 --- a/mobile/src/session/mobile-queued-message-edit-lease.ts +++ b/mobile/src/session/native-chat/input/mobile-queued-message-edit-lease.ts @@ -1,12 +1,10 @@ import { startQueuedEditLease, type QueuedEditLeaseLoss -} from '../../../src/shared/queued-message-edit-lease' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { callAgentSession } from './mobile-structured-agent-session-rpc' -import { structuredSessionOperationId } from './structured-session-operation-id' - -export type { QueuedEditLease as MobileQueuedEditLease } from '../../../src/shared/queued-message-edit-lease' +} from '../../../../../src/shared/queued-message-edit-lease' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { callAgentSession } from '../agent-session/mobile-structured-agent-session-rpc' +import { structuredSessionOperationId } from '../agent-session/structured-session-operation-id' export function startMobileQueuedEditLease(args: { getClient: () => RpcClient | null diff --git a/mobile/src/session/mobile-queued-message-edit-open.ts b/mobile/src/session/native-chat/input/mobile-queued-message-edit-open.ts similarity index 83% rename from mobile/src/session/mobile-queued-message-edit-open.ts rename to mobile/src/session/native-chat/input/mobile-queued-message-edit-open.ts index cbb6d597434..5a5d550ad2a 100644 --- a/mobile/src/session/mobile-queued-message-edit-open.ts +++ b/mobile/src/session/native-chat/input/mobile-queued-message-edit-open.ts @@ -1,16 +1,16 @@ -import type { QueuedEditLeaseLoss } from '../../../src/shared/queued-message-edit-lease' +import type { QueuedEditLeaseLoss } from '../../../../../src/shared/queued-message-edit-lease' import { queuedMessageEditableText, queuedEditKeptImages -} from '../../../src/shared/queued-message-edit' -import { agentSessionSendBodyFingerprint } from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' -import { createUuidV4 } from '../../../src/shared/uuid-v4' +} from '../../../../../src/shared/queued-message-edit' +import { agentSessionSendBodyFingerprint } from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' +import { createNonSecureContextUuid as createUuidV4 } from '../../../../../src/shared/non-secure-context-uuid' import { startMobileQueuedEditLease } from './mobile-queued-message-edit-lease' import { mobileQueuedEditValueKey } from './mobile-queued-message-edit-value' import { mobileNativeChatDraftDestination, readMobileNativeChatDraftInputs -} from './native-chat/composer/mobile-native-chat-draft-inputs' +} from '../composer/mobile-native-chat-draft-inputs' import { seedMobileQueuedEditInputs, type MobileQueuedEditDraft as Edit, diff --git a/mobile/src/session/mobile-queued-message-edit-outcome.ts b/mobile/src/session/native-chat/input/mobile-queued-message-edit-outcome.ts similarity index 89% rename from mobile/src/session/mobile-queued-message-edit-outcome.ts rename to mobile/src/session/native-chat/input/mobile-queued-message-edit-outcome.ts index ef37efd9d30..13d8ab65330 100644 --- a/mobile/src/session/mobile-queued-message-edit-outcome.ts +++ b/mobile/src/session/native-chat/input/mobile-queued-message-edit-outcome.ts @@ -1,8 +1,8 @@ -import type { AgentSessionQueuedMessageUpdateResult } from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import { agentSessionSendBodyFingerprint } from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' +import type { AgentSessionQueuedMessageUpdateResult } from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +import { agentSessionSendBodyFingerprint } from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' import type { MobileQueuedEditDraft } from './mobile-queued-message-edit-draft' -import type { StructuredAgentSessionMutationResult } from './mobile-structured-agent-session-rpc' -import { readMobileNativeChatDraftInputs } from './native-chat/composer/mobile-native-chat-draft-inputs' +import type { StructuredAgentSessionMutationResult } from '../agent-session/mobile-structured-agent-session-rpc' +import { readMobileNativeChatDraftInputs } from '../composer/mobile-native-chat-draft-inputs' import { mobileQueuedEditValueKey } from './mobile-queued-message-edit-value' type Outcome = 'close' | 'rebase' | 'gone' | 'changed' | 'refused' | 'stay' diff --git a/mobile/src/session/mobile-queued-message-edit-value.ts b/mobile/src/session/native-chat/input/mobile-queued-message-edit-value.ts similarity index 78% rename from mobile/src/session/mobile-queued-message-edit-value.ts rename to mobile/src/session/native-chat/input/mobile-queued-message-edit-value.ts index 173aacd67b2..ebf12bbbf00 100644 --- a/mobile/src/session/mobile-queued-message-edit-value.ts +++ b/mobile/src/session/native-chat/input/mobile-queued-message-edit-value.ts @@ -1,5 +1,5 @@ -import type { MobileNativeChatDraftInputs } from './native-chat/composer/mobile-native-chat-draft-inputs' -import { pickedSkillsInText } from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' +import type { MobileNativeChatDraftInputs } from '../composer/mobile-native-chat-draft-inputs' +import { pickedSkillsInText } from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' type Image = MobileNativeChatDraftInputs['images'][number] export function sameMobileQueuedEditImage(left: Image, right: Image): boolean { diff --git a/mobile/src/session/mobile-structured-actionable-prompt.ts b/mobile/src/session/native-chat/input/mobile-structured-actionable-prompt.ts similarity index 78% rename from mobile/src/session/mobile-structured-actionable-prompt.ts rename to mobile/src/session/native-chat/input/mobile-structured-actionable-prompt.ts index 71b34891229..1a79e5a192d 100644 --- a/mobile/src/session/mobile-structured-actionable-prompt.ts +++ b/mobile/src/session/native-chat/input/mobile-structured-actionable-prompt.ts @@ -1,5 +1,5 @@ -import { pendingStructuredSessionPrompts } from '../../../src/shared/agent-session/view/structured-agent-session-message-projection' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' +import { pendingStructuredSessionPrompts } from '../../../../../src/shared/agent-session/view/structured-agent-session-message-projection' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' import type { StructuredApprovalItem, StructuredQuestionItem diff --git a/mobile/src/session/mobile-structured-agent-prompts-permission.test.ts b/mobile/src/session/native-chat/input/mobile-structured-agent-prompts-permission.test.ts similarity index 100% rename from mobile/src/session/mobile-structured-agent-prompts-permission.test.ts rename to mobile/src/session/native-chat/input/mobile-structured-agent-prompts-permission.test.ts diff --git a/mobile/src/session/mobile-structured-agent-prompts.ts b/mobile/src/session/native-chat/input/mobile-structured-agent-prompts.ts similarity index 94% rename from mobile/src/session/mobile-structured-agent-prompts.ts rename to mobile/src/session/native-chat/input/mobile-structured-agent-prompts.ts index 93a3bb5d7ed..ace186b66e5 100644 --- a/mobile/src/session/mobile-structured-agent-prompts.ts +++ b/mobile/src/session/native-chat/input/mobile-structured-agent-prompts.ts @@ -1,11 +1,11 @@ -import type { AgentJournalRenderItem } from '../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentJournalRenderItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' import { agentSessionPromptQuestions, type AgentSessionQuestionAnswer -} from '../../../src/shared/agent-session/agent-session-question-answer' -import { chatApprovalFromJournal } from '../../../src/shared/native-chat/input/native-chat-interactive-prompt' -import type { MobileChatPermission } from './native-chat/input/mobile-native-chat-permission' -import type { MobileChatQuestion } from './native-chat/input/mobile-native-chat-question' +} from '../../../../../src/shared/agent-session/agent-session-question-answer' +import { chatApprovalFromJournal } from '../../../../../src/shared/native-chat/input/native-chat-interactive-prompt' +import type { MobileChatPermission } from './mobile-native-chat-permission' +import type { MobileChatQuestion } from './mobile-native-chat-question' export type StructuredApprovalItem = AgentJournalRenderItem & { body: Extract diff --git a/mobile/src/session/mobile-structured-queued-message-cards.ts b/mobile/src/session/native-chat/input/mobile-structured-queued-message-cards.ts similarity index 78% rename from mobile/src/session/mobile-structured-queued-message-cards.ts rename to mobile/src/session/native-chat/input/mobile-structured-queued-message-cards.ts index 42d009d89bd..3730aa220fe 100644 --- a/mobile/src/session/mobile-structured-queued-message-cards.ts +++ b/mobile/src/session/native-chat/input/mobile-structured-queued-message-cards.ts @@ -1,18 +1,18 @@ -import { stripSentImagePromptMarkers } from '../../../src/shared/native-chat/transcript/native-chat-image-transcript-markers' -import type { AgentSessionFailureFact } from '../../../src/shared/agent-session/failures/agent-session-failure' -import { mobileAgentSessionWriteNoticeText } from './agent-session-write-notice-copy' -import type { AgentJournalSubmission } from '../../../src/shared/agent-session/journal/agent-session-journal-types' +import { stripSentImagePromptMarkers } from '../../../../../src/shared/native-chat/transcript/native-chat-image-transcript-markers' +import type { AgentSessionFailureFact } from '../../../../../src/shared/agent-session/failures/agent-session-failure' +import { mobileAgentSessionWriteNoticeText } from '../agent-session/agent-session-write-notice-copy' +import type { AgentJournalSubmission } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' import { QUEUED_MESSAGE_PAUSED_SEND_FAILED, type AgentSessionQueuedMessage -} from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +} from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' import { projectQueuedMessageCards, queuedMessageCardLine, queuedMessageCardNeedsAttention, queuedMessageReturnedNotice, type QueuedMessageCard -} from '../../../src/shared/agent-session/view/structured-agent-session-queued-cards' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-queued-cards' // The shared projection decides each card's hold; this layer words it for the phone. export type MobileQueuedMessageCard = QueuedMessageCard & { diff --git a/mobile/src/session/use-mobile-background-task-stops.ts b/mobile/src/session/native-chat/input/use-mobile-background-task-stops.ts similarity index 97% rename from mobile/src/session/use-mobile-background-task-stops.ts rename to mobile/src/session/native-chat/input/use-mobile-background-task-stops.ts index 7f70d3c67d3..f9031d89e2b 100644 --- a/mobile/src/session/use-mobile-background-task-stops.ts +++ b/mobile/src/session/native-chat/input/use-mobile-background-task-stops.ts @@ -2,7 +2,7 @@ import { useRef, useState } from 'react' import { structuredSessionConfirmedStopsStillListed, structuredSessionListedTaskRun -} from '../../../src/shared/structured-session-background-tasks-view' +} from '../../../../../src/shared/structured-session-background-tasks-view' import type { MobileStructuredBackgroundTasks } from './use-mobile-structured-background-tasks' export type MobileBackgroundTaskStopping = { taskIds: ReadonlySet; all: boolean } diff --git a/mobile/src/session/use-mobile-bridge-chat-prompt-writes.ts b/mobile/src/session/native-chat/input/use-mobile-bridge-chat-prompt-writes.ts similarity index 74% rename from mobile/src/session/use-mobile-bridge-chat-prompt-writes.ts rename to mobile/src/session/native-chat/input/use-mobile-bridge-chat-prompt-writes.ts index 07739f5ef3e..d2d918cc4f8 100644 --- a/mobile/src/session/use-mobile-bridge-chat-prompt-writes.ts +++ b/mobile/src/session/native-chat/input/use-mobile-bridge-chat-prompt-writes.ts @@ -1,10 +1,10 @@ import type { MutableRefObject } from 'react' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { useMobileNativeChatPermissionSend } from './native-chat/input/mobile-native-chat-permission-send' -import { useMobileNativeChatAnswerSend } from './native-chat/input/use-mobile-native-chat-answer-send' -import { useMobileNativeChatCancelAsk } from './native-chat/input/use-mobile-native-chat-cancel-ask' -import { useMobileNativeChatStop } from './native-chat/use-mobile-native-chat-stop' -import type { MobileNativeChatAnswerSend } from './native-chat/input/use-mobile-native-chat-answer-send' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { useMobileNativeChatPermissionSend } from './mobile-native-chat-permission-send' +import { useMobileNativeChatAnswerSend } from './use-mobile-native-chat-answer-send' +import { useMobileNativeChatCancelAsk } from './use-mobile-native-chat-cancel-ask' +import { useMobileNativeChatStop } from '../composer/use-mobile-native-chat-stop' +import type { MobileNativeChatAnswerSend } from './use-mobile-native-chat-answer-send' // Share the bridge write gate and cancel pending answer groups before interrupting; structured prompts use RPC instead. export function useMobileBridgeChatPromptWrites(args: { diff --git a/mobile/src/session/use-mobile-host-model-catalog-upgrade.ts b/mobile/src/session/native-chat/input/use-mobile-host-model-catalog-upgrade.ts similarity index 87% rename from mobile/src/session/use-mobile-host-model-catalog-upgrade.ts rename to mobile/src/session/native-chat/input/use-mobile-host-model-catalog-upgrade.ts index abb401f3d43..8075148cddf 100644 --- a/mobile/src/session/use-mobile-host-model-catalog-upgrade.ts +++ b/mobile/src/session/native-chat/input/use-mobile-host-model-catalog-upgrade.ts @@ -1,16 +1,16 @@ import { useCallback, useEffect, useRef, useState, type MutableRefObject } from 'react' -import type { AgentSessionModelCatalogResult } from '../../../src/shared/agent-session/agent-session-wire' -import type { AgentSessionOptionCatalog } from '../../../src/shared/agent-session/options/agent-session-option-catalog-types' -import type { StructuredAgentSessionOptionRecord } from '../../../src/shared/agent-session/options/structured-agent-session-option-state' -import type { StructuredAgentSessionOptionState } from '../../../src/shared/agent-session/options/structured-agent-session-options' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { AgentSessionModelCatalogResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { AgentSessionOptionCatalog } from '../../../../../src/shared/agent-session/options/agent-session-option-catalog-types' +import type { StructuredAgentSessionOptionRecord } from '../../../../../src/shared/agent-session/options/structured-agent-session-option-state' +import type { StructuredAgentSessionOptionState } from '../../../../../src/shared/agent-session/options/structured-agent-session-options' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { isHostModelListingWaitInFlight, joinHostModelListingWait -} from '../../../src/shared/host-model-listing-waits' -import { callAgentSession } from './mobile-structured-agent-session-rpc' -import { usesModelSpecificAcpEfforts } from '../../../src/shared/agent-session/options/agent-model-catalog-ownership' -import type { HostModelCatalogLaunch } from '../../../src/shared/agent-session/options/host-model-catalog-snapshot-rules' +} from '../../../../../src/shared/host-model-listing-waits' +import { callAgentSession } from '../agent-session/mobile-structured-agent-session-rpc' +import { usesModelSpecificAcpEfforts } from '../../../../../src/shared/agent-session/options/agent-model-catalog-ownership' +import type { HostModelCatalogLaunch } from '../../../../../src/shared/agent-session/options/host-model-catalog-snapshot-rules' /** This world's last host lists for one chat's agent (`useHostModelCatalogCache`). A port, so this hook's * module graph stays free of the query layer for the root-only tests that bundle it. */ diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.ts index b5ba3e86b04..c14ccdac4fd 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-answer-send.ts @@ -8,16 +8,16 @@ import { import type { AskAnswerSelection } from '../../../../../src/shared/native-chat/input/native-chat-ask-answer' import type { AskPrompt } from '../../../../../src/shared/native-chat/input/native-chat-ask-types' import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' -import { MOBILE_NATIVE_CHAT_QUESTION_STEP_MS } from './mobile-native-chat-answer-stepping' +import { NATIVE_CHAT_QUESTION_STEP_MS } from '../../../../../src/shared/native-chat/input/native-chat-answer-stepping' import { openMobileNativeChatSendBudget, sendMobileNativeChatMessageWithOutcome -} from '../mobile-native-chat-send' -import { healMobileNativeChatStaleInput } from '../mobile-native-chat-stale-input' +} from '../composer/mobile-native-chat-send' +import { healMobileNativeChatStaleInput } from '../composer/mobile-native-chat-stale-input' import { acquireMobileNativeChatTerminalWrite, releaseMobileNativeChatTerminalWrite -} from '../mobile-native-chat-terminal-write-lock' +} from '../composer/mobile-native-chat-terminal-write-lock' import { nativeChatAskRequiresTerminal, resolveNativeChatTranscriptAgent, @@ -229,11 +229,11 @@ export function useMobileNativeChatAnswerSend(args: { return fail() } if (index < groups.length - 1) { - if (!(await wait(MOBILE_NATIVE_CHAT_QUESTION_STEP_MS))) { + if (!(await wait(NATIVE_CHAT_QUESTION_STEP_MS))) { return false } // Pacing is deliberate, not transport latency — don't charge it to the budget. - deadline += MOBILE_NATIVE_CHAT_QUESTION_STEP_MS + deadline += NATIVE_CHAT_QUESTION_STEP_MS } } return groups.length > 0 && writeTurnsRef.current.get(handle) === turn diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.ts index ea463e43a4e..64edb4f80de 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-cancel-ask.ts @@ -1,7 +1,7 @@ import { useCallback, type MutableRefObject } from 'react' import { nativeChatAskRequiresTerminal } from '../../../../../src/shared/native-chat/native-chat-agent-support' import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' -import { sendMobileNativeChatMessageWithOutcome } from '../mobile-native-chat-send' +import { sendMobileNativeChatMessageWithOutcome } from '../composer/mobile-native-chat-send' export function useMobileNativeChatCancelAsk(args: { client: RpcClient | null diff --git a/mobile/src/session/use-mobile-native-chat-model-discovery.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-model-discovery.ts similarity index 86% rename from mobile/src/session/use-mobile-native-chat-model-discovery.ts rename to mobile/src/session/native-chat/input/use-mobile-native-chat-model-discovery.ts index 4eb3bd930b6..fbdcb937d30 100644 --- a/mobile/src/session/use-mobile-native-chat-model-discovery.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-model-discovery.ts @@ -1,12 +1,12 @@ import { useEffect, useState } from 'react' import * as z from 'zod' -import { getAgentSessionOptionCatalog } from '../../../src/shared/agent-session/options/agent-session-option-catalog' -import type { CatalogModel } from '../../../src/shared/agent-session/options/agent-session-option-catalog-types' +import { getAgentSessionOptionCatalog } from '../../../../../src/shared/agent-session/options/agent-session-option-catalog' +import type { CatalogModel } from '../../../../../src/shared/agent-session/options/agent-session-option-catalog-types' import { catalogModelsFromModelDiscovery, mergeDiscoveredCatalogModels -} from '../../../src/shared/native-chat/input/native-chat-discovered-catalog-models' -import { defineRpcOperation, runRpcOperation } from '../transport/rpc/rpc-operation' +} from '../../../../../src/shared/native-chat/input/native-chat-discovered-catalog-models' +import { defineRpcOperation, runRpcOperation } from '../../../transport/rpc/rpc-operation' const discoveredModel = z.object({ id: z.string().min(1), diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-prompt-cards.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-prompt-cards.ts index 77342854952..1a1e15afdb8 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-prompt-cards.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-prompt-cards.ts @@ -1,6 +1,6 @@ import type { MobileNativeChatController } from '../mobile-native-chat-controller-contract' -import type { useMobileStructuredAgentSession } from '../../use-mobile-structured-agent-session' -import { useNativeChatAcceptedAction } from '../../use-native-chat-action-outcomes' +import type { useMobileStructuredAgentSession } from '../agent-session/use-mobile-structured-agent-session' +import { useNativeChatAcceptedAction } from './use-native-chat-action-outcomes' import { useMobileNativeChatPromptPresentation } from './use-mobile-native-chat-prompt-presentation' export function useMobileNativeChatPromptCards({ diff --git a/mobile/src/session/use-mobile-native-chat-prompt-controller.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-prompt-controller.ts similarity index 81% rename from mobile/src/session/use-mobile-native-chat-prompt-controller.ts rename to mobile/src/session/native-chat/input/use-mobile-native-chat-prompt-controller.ts index 27590badf9f..9d4b146565c 100644 --- a/mobile/src/session/use-mobile-native-chat-prompt-controller.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-prompt-controller.ts @@ -1,14 +1,14 @@ -import { useMobileAsyncQuestionHides } from './native-chat/input/use-mobile-native-chat-prompt-dismiss' -import type { AskPrompt } from '../../../src/shared/native-chat/input/native-chat-ask-types' -import type { AskAnswerSelection } from '../../../src/shared/native-chat/input/native-chat-ask-answer' -import type { AgentStatusEntry } from '../../../src/shared/agent-status/contracts/agent-status-types' -import type { MobileNativeChatController } from './native-chat/mobile-native-chat-controller-contract' +import { useMobileAsyncQuestionHides } from './use-mobile-native-chat-prompt-dismiss' +import type { AskPrompt } from '../../../../../src/shared/native-chat/input/native-chat-ask-types' +import type { AskAnswerSelection } from '../../../../../src/shared/native-chat/input/native-chat-ask-answer' +import type { AgentStatusEntry } from '../../../../../src/shared/agent-status/contracts/agent-status-types' +import type { MobileNativeChatController } from '../mobile-native-chat-controller-contract' import type { useMobileBridgeChatPromptWrites } from './use-mobile-bridge-chat-prompt-writes' -import { useMobileNativeChatAskDismiss } from './native-chat/input/use-mobile-native-chat-ask-dismiss' -import { useMobileNativeChatPromptCards } from './native-chat/input/use-mobile-native-chat-prompt-cards' -import { useMobileNativeChatPrompts } from './native-chat/input/use-mobile-native-chat-prompts' -import type { useMobileNativeChatSession } from './native-chat/use-mobile-native-chat-session' -import type { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session' +import { useMobileNativeChatAskDismiss } from './use-mobile-native-chat-ask-dismiss' +import { useMobileNativeChatPromptCards } from './use-mobile-native-chat-prompt-cards' +import { useMobileNativeChatPrompts } from './use-mobile-native-chat-prompts' +import type { useMobileNativeChatSession } from '../use-mobile-native-chat-session' +import type { useMobileStructuredAgentSession } from '../agent-session/use-mobile-structured-agent-session' import { useNativeChatAcceptedAction } from './use-native-chat-action-outcomes' // Read the chat's ask and permission/question prompts and route their answers to the active lane. diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-prompts.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-prompts.ts index 06d37d90677..309e699626d 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-prompts.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-prompts.ts @@ -14,7 +14,7 @@ import type { NativeChatMessage } from '../../../../../src/shared/native-chat/na import { useMobileStructuredAsyncQuestion, type MobileStructuredAskSession -} from '../../use-mobile-structured-async-question' +} from './use-mobile-structured-async-question' export type MobileNativeChatPrompts = { /** A terminal chat's approval; a structured chat's journal prompts are its own cards. */ diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-queued-edit-focus.test.tsx b/mobile/src/session/native-chat/input/use-mobile-native-chat-queued-edit-focus.test.tsx deleted file mode 100644 index 2afa78903dc..00000000000 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-queued-edit-focus.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { useMobileNativeChatQueuedEditFocus } from './use-mobile-native-chat-queued-edit-focus' -import type { MobileQueuedMessageEdit } from '../../use-mobile-structured-queued-message-controls' - -type Focus = ReturnType - -describe('useMobileNativeChatQueuedEditFocus', () => { - let renderer: ReactTestRenderer | null = null - let latest: Focus | null = null - - function Harness({ onEdit }: { onEdit?: MobileQueuedMessageEdit }): null { - latest = useMobileNativeChatQueuedEditFocus(onEdit) - return null - } - - beforeEach(() => { - vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => - setTimeout(() => callback(0), 0) - ) - }) - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - latest = null - vi.unstubAllGlobals() - }) - - it('focuses the composer after Edit copies a queued message into it', async () => { - const onEdit = vi.fn(async (_messageId, onCopied) => { - onCopied?.() - return false - }) - await act(async () => { - renderer = create(createElement(Harness, { onEdit })) - }) - const focus = vi.fn() - const inputRef: { current: unknown } = latest!.composerInputRef - inputRef.current = { focus } - let edited: boolean | undefined - await act(async () => { - edited = await latest!.editQueuedMessage!('queued-1') - await new Promise((resolve) => setTimeout(resolve, 0)) - }) - expect(onEdit).toHaveBeenCalledWith('queued-1', expect.any(Function)) - // A failed Delete still leaves the copied text in the composer, so focus regardless. - expect(focus).toHaveBeenCalledTimes(1) - expect(edited).toBe(false) - }) - - it('leaves focus alone when Edit copied nothing', async () => { - const onEdit = vi.fn(async () => false) - await act(async () => { - renderer = create(createElement(Harness, { onEdit })) - }) - const focus = vi.fn() - const inputRef: { current: unknown } = latest!.composerInputRef - inputRef.current = { focus } - await act(async () => { - await latest!.editQueuedMessage!('queued-1') - await new Promise((resolve) => setTimeout(resolve, 0)) - }) - expect(focus).not.toHaveBeenCalled() - }) -}) diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-queued-edit-focus.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-queued-edit-focus.ts index 730c71eeb63..79777e6a83e 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-queued-edit-focus.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-queued-edit-focus.ts @@ -1,6 +1,6 @@ import { useMemo, useRef, type RefObject } from 'react' import type { TextInput } from 'react-native' -import type { MobileQueuedMessageEdit } from '../../use-mobile-structured-queued-message-controls' +import type { MobileQueuedMessageEdit } from './use-mobile-structured-queued-message-controls' // Focus after a queued Edit actually copies text; empty copies preserve current focus. export function useMobileNativeChatQueuedEditFocus(onEdit: MobileQueuedMessageEdit | undefined): { diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-reply-scroll.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-reply-scroll.ts index 9226a6f7f2f..fe9fd376050 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-reply-scroll.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-reply-scroll.ts @@ -1,18 +1,11 @@ import { useRef } from 'react' -import type { - LayoutChangeEvent, - NativeScrollEvent, - NativeSyntheticEvent, - ScrollView, - TextInput -} from 'react-native' +import type { LayoutChangeEvent, ScrollView, TextInput } from 'react-native' // Keep a focused reply in view when its card shrinks above the keyboard. export function useMobileNativeChatReplyScroll() { const scrollRef = useRef(null) const inputRef = useRef(null) const viewportHeight = useRef(0) - const scrollOffset = useRef(0) const revealReply = (): void => { if (inputRef.current?.isFocused()) { scrollRef.current?.scrollToEnd({ animated: false }) @@ -27,31 +20,5 @@ export function useMobileNativeChatReplyScroll() { revealReply() } } - const onScroll = (event: NativeSyntheticEvent): void => { - scrollOffset.current = event.nativeEvent.contentOffset.y - } - // Scroll just enough to show content that opened below the visible edge, never past its top. - const revealContent = (top: number, bottom: number): void => { - const hidden = bottom - (scrollOffset.current + viewportHeight.current) - if (viewportHeight.current > 0 && hidden > 0) { - scrollRef.current?.scrollTo({ - y: Math.min(scrollOffset.current + hidden, top), - animated: true - }) - } - } - const scrollViewProps = { - ref: scrollRef, - onLayout: onViewportLayout, - onScroll, - scrollEventThrottle: 16 - } - return { - scrollRef, - inputRef, - revealReply, - onViewportLayout, - scrollViewProps, - revealContent - } + return { scrollRef, inputRef, revealReply, onViewportLayout } } diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-session-option-controller.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-session-option-controller.ts index 66d2403ae64..fc87fe07d76 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-session-option-controller.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-session-option-controller.ts @@ -1,5 +1,5 @@ import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' -import { useMobileNativeChatModelDiscovery } from '../../use-mobile-native-chat-model-discovery' +import { useMobileNativeChatModelDiscovery } from './use-mobile-native-chat-model-discovery' import { writeMobileNativeChatSessionOptions } from './mobile-native-chat-session-option-persistence' import { getAgentSessionOptionCatalog } from '../../../../../src/shared/agent-session/options/agent-session-option-catalog' import type { AgentSessionOptionCatalog } from '../../../../../src/shared/agent-session/options/agent-session-option-catalog-types' @@ -12,7 +12,7 @@ import type { SessionOptionsSurface } from '../../../../../src/shared/native-chat/input/native-chat-session-options' import { mobileNativeChatScopeKey } from '../mobile-native-chat-scope-key' -import type { MobileNativeChatSendOutcome } from '../mobile-native-chat-send' +import type { MobileNativeChatSendOutcome } from '../composer/mobile-native-chat-send' import type { MobileNativeChatSessionOptionPickersProps } from './MobileNativeChatSessionOptionPickers' import { useMobileNativeChatSessionOptions, diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.test.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.test.ts index 6a856612617..94978c32671 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.test.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.test.ts @@ -1,18 +1,10 @@ +import { randomUUID } from 'node:crypto' import { createElement } from 'react' import { act, create, type ReactTestRenderer } from 'react-test-renderer' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { MobileNativeChatSendOutcome } from '../mobile-native-chat-send' +import type { MobileNativeChatSendOutcome } from '../composer/mobile-native-chat-send' + import { - applyNativeChatSessionOptionSettingsMutation, - resolveNativeChatSessionOptionDefaults -} from '../../../../../src/shared/native-chat/input/native-chat-session-option-defaults' -import { resolveAgentSessionOptionLaunch } from '../../../../../src/shared/agent-session/options/agent-session-option-launch' -import type { - PersistedNativeChatSessionOptions, - NativeChatSessionOptionSettingsMutation -} from '../../../../../src/shared/native-chat/input/native-chat-session-options' -import { - clearMobileSessionOptionRecordsForTests, useMobileNativeChatSessionOptions, type MobileNativeChatSessionOptionsController } from './use-mobile-native-chat-session-options' @@ -23,6 +15,8 @@ describe('useMobileNativeChatSessionOptions', () => { let renderer: ReactTestRenderer | null = null let api: MobileNativeChatSessionOptionsController | null = null let hookArgs: HookArgs + let hostId: string + const scopeKey = (tabId: string): string => `${hostId}\0worktree\0${tabId}` const dispatchCommand = vi.fn() const onAgentPicker = vi.fn() @@ -34,7 +28,7 @@ describe('useMobileNativeChatSessionOptions', () => { const mount = (overrides: Partial = {}): void => { hookArgs = { agent: 'claude', - scopeKey: 'host\0worktree\0tab', + scopeKey: scopeKey('tab'), reportedModel: null, dispatchCommand, onAgentPicker, @@ -53,7 +47,7 @@ describe('useMobileNativeChatSessionOptions', () => { } beforeEach(() => { - clearMobileSessionOptionRecordsForTests() + hostId = `mobile-session-options-test-${randomUUID()}` dispatchCommand.mockReset() dispatchCommand.mockResolvedValue('accepted') onAgentPicker.mockReset() @@ -66,55 +60,6 @@ describe('useMobileNativeChatSessionOptions', () => { api = null }) - it.each([ - ['grok', true], - ['grok', false], - ['omp', true], - ['omp', false] - ] as const)( - '%s discovery cached=%s writes only an explicit owning-host pick', - async (agent, cached) => { - let persisted: PersistedNativeChatSessionOptions = { [agent]: { model: 'retired' } } - const writeOwner = vi.fn((mutation: NativeChatSessionOptionSettingsMutation) => { - persisted = applyNativeChatSessionOptionSettingsMutation(persisted, mutation) ?? persisted - }) - const writeOther = vi.fn() - const model = agent === 'grok' ? 'grok-4.7' : 'deepseek/deepseek-v4-pro' - const discoveredModels = [{ id: model, label: model, options: [] }] - mount({ - agent, - scopeKey: 'owner\0worktree\0tab', - reportedModel: 'retired', - modelSwitchCommand: 'orca-model', - writeSettings: writeOwner, - discoveredModels: cached ? discoveredModels : null - }) - if (!cached) { - update({ discoveredModels }) - } - expect(writeOwner).not.toHaveBeenCalled() - expect( - resolveAgentSessionOptionLaunch( - agent, - resolveNativeChatSessionOptionDefaults(persisted, agent) - ).args - ).toContain('retired') - await act(async () => { - expect(await api!.setOption('model', model)).toBe(true) - }) - expect(writeOwner).toHaveBeenCalledOnce() - expect( - resolveAgentSessionOptionLaunch( - agent, - resolveNativeChatSessionOptionDefaults(persisted, agent) - ).args - ).toContain(model) - update({ scopeKey: 'other\0worktree\0tab', writeSettings: writeOther }) - expect(writeOwner).toHaveBeenCalledOnce() - expect(writeOther).not.toHaveBeenCalled() - } - ) - it('starts over when the tab runs another agent and then a fresh session', async () => { mount({ reportedModel: 'claude-sonnet-5' }) await act(async () => { @@ -164,8 +109,8 @@ describe('useMobileNativeChatSessionOptions', () => { picked = api!.setOption('model', 'opus') await Promise.resolve() }) - update({ scopeKey: 'host\0worktree\0other-tab' }) - update({ scopeKey: 'host\0worktree\0tab' }) + update({ scopeKey: scopeKey('other-tab') }) + update({ scopeKey: scopeKey('tab') }) await act(async () => { resolveModel('accepted') await picked @@ -200,8 +145,8 @@ describe('useMobileNativeChatSessionOptions', () => { }) expect(api!.snapshot[0]!.kind).toMatchObject({ currentValue: 'opus' }) // Re-entering repeats session-start evidence that predates later user picks. - update({ scopeKey: 'host\0worktree\0other' }) - update({ scopeKey: 'host\0worktree\0tab' }) + update({ scopeKey: scopeKey('other') }) + update({ scopeKey: scopeKey('tab') }) expect(api!.snapshot[0]).toMatchObject({ valueSource: 'dispatched' }) expect(api!.snapshot[0]!.kind).toMatchObject({ currentValue: 'opus' }) }) @@ -213,8 +158,8 @@ describe('useMobileNativeChatSessionOptions', () => { }) // Repeatedly touch the live tab while exceeding capacity so insertion-order eviction would lose it. for (let index = 0; index < 40; index += 1) { - update({ scopeKey: `host\0worktree\0overflow-${index}` }) - update({ scopeKey: 'host\0worktree\0tab' }) + update({ scopeKey: scopeKey(`overflow-${index}`) }) + update({ scopeKey: scopeKey('tab') }) } expect(api!.snapshot[0]).toMatchObject({ valueSource: 'dispatched' }) expect(api!.snapshot[0]!.kind).toMatchObject({ currentValue: 'opus' }) @@ -266,7 +211,7 @@ describe('useMobileNativeChatSessionOptions', () => { queued = api!.setOption('model', 'opus') await Promise.resolve() }) - update({ scopeKey: 'host\0worktree\0other-tab' }) + update({ scopeKey: scopeKey('other-tab') }) await act(async () => { resolveFirst('accepted') await first diff --git a/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.ts b/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.ts index 0158d5b3fc8..f5351f62cfa 100644 --- a/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.ts +++ b/mobile/src/session/native-chat/input/use-mobile-native-chat-session-options.ts @@ -19,13 +19,12 @@ import type { SessionOptionDescriptor, SessionOptionValue } from '../../../../../src/shared/native-chat/input/native-chat-session-options' -import type { MobileNativeChatSendOutcome } from '../mobile-native-chat-send' +import type { MobileNativeChatSendOutcome } from '../composer/mobile-native-chat-send' import { createNativeChatPtySessionOptions, type NativeChatPtySessionOptionsSurface } from '../../../../../src/shared/native-chat/input/native-chat-pty-session-options' import { - clearNativeChatSessionOptionCacheForTests, readNativeChatSessionOptionCache, writeNativeChatSessionOptionCache } from '../../../../../src/shared/native-chat/input/native-chat-session-option-cache' @@ -84,11 +83,6 @@ const getEmptySnapshot = (): SessionOptionDescriptor[] => EMPTY_SNAPSHOT // Kept across remounts: a repeated hook model is not fresh evidence, and reapplying it would revert a newer pick. const appliedReportByScope = new Map() -export function clearMobileSessionOptionRecordsForTests(): void { - clearNativeChatSessionOptionCacheForTests() - appliedReportByScope.clear() -} - /** A tab that runs another agent starts over: a new session there carries none of the old one's picks. */ function startScopeOverForAgent(scopeKey: string, agent: string): void { const cachedAgent = readNativeChatSessionOptionCache(scopeKey)?.agent diff --git a/mobile/src/session/use-mobile-queued-message-composer-actions.ts b/mobile/src/session/native-chat/input/use-mobile-queued-message-composer-actions.ts similarity index 91% rename from mobile/src/session/use-mobile-queued-message-composer-actions.ts rename to mobile/src/session/native-chat/input/use-mobile-queued-message-composer-actions.ts index ac28c9ed593..482e2cb4b70 100644 --- a/mobile/src/session/use-mobile-queued-message-composer-actions.ts +++ b/mobile/src/session/native-chat/input/use-mobile-queued-message-composer-actions.ts @@ -2,9 +2,9 @@ import { useCallback, useLayoutEffect, useRef, useState } from 'react' import type { AgentSessionQueuedMessage, AgentSessionQueuedMessageMoveResult -} from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import type { DragReorderMove } from '../components/drag-reorder-positions' -import type { MobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' +} from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +import type { DragReorderMove } from '../../../components/drag-reorder-positions' +import type { MobileStructuredAgentMutate } from '../agent-session/use-mobile-structured-agent-mutation' type QueuedMessages = readonly AgentSessionQueuedMessage[] | null | undefined diff --git a/mobile/src/session/use-mobile-structured-agent-options.before-attach.test.ts b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.before-attach.test.ts similarity index 91% rename from mobile/src/session/use-mobile-structured-agent-options.before-attach.test.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-agent-options.before-attach.test.ts index 92b9d7c8fc0..83752c125af 100644 --- a/mobile/src/session/use-mobile-structured-agent-options.before-attach.test.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.before-attach.test.ts @@ -1,7 +1,7 @@ import { act } from 'react-test-renderer' import { describe, expect, it, vi } from 'vitest' -import type { AgentSessionOptionResult } from '../../../src/shared/agent-session/agent-session-wire' -import type { StructuredAgentSessionMutationResult } from './mobile-structured-agent-session-rpc' +import type { AgentSessionOptionResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { StructuredAgentSessionMutationResult } from '../agent-session/mobile-structured-agent-session-rpc' import { OPTIONS, deferred, diff --git a/mobile/src/session/use-mobile-structured-agent-options.test-fixture.ts b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.test-fixture.ts similarity index 92% rename from mobile/src/session/use-mobile-structured-agent-options.test-fixture.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-agent-options.test-fixture.ts index 936278b71b4..a679f3e3884 100644 --- a/mobile/src/session/use-mobile-structured-agent-options.test-fixture.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.test-fixture.ts @@ -1,18 +1,18 @@ import { createElement, useLayoutEffect } from 'react' import { act, type ReactTestRenderer } from 'react-test-renderer' -import { createWithQueryClient } from '../test-support/query-client-test-renderer' +import { createWithQueryClient } from '../../../test-support/query-client-test-renderer' import { vi } from 'vitest' import type { AgentSessionModelCatalogResult, AgentSessionOptionResult -} from '../../../src/shared/agent-session/agent-session-wire' -import type { AgentSessionOptionsResult } from '../../../src/shared/agent-session/options/agent-session-options-result' -import type { SessionOptionDescriptor } from '../../../src/shared/native-chat/input/native-chat-session-options' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +} from '../../../../../src/shared/agent-session/agent-session-wire' +import type { AgentSessionOptionsResult } from '../../../../../src/shared/agent-session/options/agent-session-options-result' +import type { SessionOptionDescriptor } from '../../../../../src/shared/native-chat/input/native-chat-session-options' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import type { StructuredAgentSessionMutate, StructuredAgentSessionMutationResult -} from './mobile-structured-agent-session-rpc' +} from '../agent-session/mobile-structured-agent-session-rpc' import { useMobileStructuredAgentOptions } from './use-mobile-structured-agent-options' const OPTIONS: AgentSessionOptionsResult = { diff --git a/mobile/src/session/use-mobile-structured-agent-options.test.ts b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.test.ts similarity index 65% rename from mobile/src/session/use-mobile-structured-agent-options.test.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-agent-options.test.ts index 6cc5bdc886b..7c813e1409e 100644 --- a/mobile/src/session/use-mobile-structured-agent-options.test.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.test.ts @@ -1,16 +1,15 @@ -import { structuredAgentSessionOptionStamp } from '../../../src/shared/agent-session/options/structured-agent-session-option-state' +import { structuredAgentSessionOptionStamp } from '../../../../../src/shared/agent-session/options/structured-agent-session-option-state' import { act } from 'react-test-renderer' import { describe, expect, it, vi } from 'vitest' import type { AgentSessionModelCatalogResult, AgentSessionOptionResult -} from '../../../src/shared/agent-session/agent-session-wire' -import type { AgentSessionOptionsResult } from '../../../src/shared/agent-session/options/agent-session-options-result' -import type { StructuredAgentSessionMutationResult } from './mobile-structured-agent-session-rpc' -import { rememberMobileCreatedStructuredSession } from './mobile-created-structured-sessions' +} from '../../../../../src/shared/agent-session/agent-session-wire' +import type { AgentSessionOptionsResult } from '../../../../../src/shared/agent-session/options/agent-session-options-result' +import type { StructuredAgentSessionMutationResult } from '../agent-session/mobile-structured-agent-session-rpc' +import { rememberMobileCreatedStructuredSession } from '../agent-session/mobile-created-structured-sessions' import { - type Controller, settle, OPTIONS, FAST_OPTIONS, @@ -26,48 +25,6 @@ import { BASE } from './use-mobile-structured-agent-options.test-fixture' -describe('useMobileStructuredAgentOptions generation fencing', () => { - it('drops an options read a later write superseded', async () => { - const stale = deferred() - const first = optionsClient(queuedReads(FAST_OPTIONS)) - // Reconnect replaces the client without staling the retained record; generation must fence its prior read. - let reads = 0 - const reconnected = optionsClient(() => - reads++ === 0 - ? stale.promise - : Promise.resolve({ - ...FAST_OPTIONS, - current: { ...FAST_OPTIONS.current, model: 'gpt-fast' } - }) - ) - const { mutate } = recordingMutate(async () => - accepted({ key: 'model', value: 'gpt-fast', options: { model: 'gpt-fast' } }, true) - ) - const harness = await mountOptions({ ...BASE, client: first.client, mutate }) - - expect(currentValueOf(harness.current().optionSnapshot, 'model')).toBe('gpt-live') - await harness.rerender({ client: reconnected.client }) - expect(reconnected.optionReads()).toBe(1) - - let outcome: boolean | null = null - await act(async () => { - outcome = await harness.current().setStructuredOption('model', 'gpt-fast') - }) - await settle() - expect(outcome).toBe(true) - expect(currentValueOf(harness.current().optionSnapshot, 'model')).toBe('gpt-fast') - - await act(async () => { - stale.resolve({ ...FAST_OPTIONS, conversationCommands: ['compact'] }) - }) - await settle() - - expect(currentValueOf(harness.current().optionSnapshot, 'model')).toBe('gpt-fast') - expect(harness.current().conversationCommands).toEqual([]) - await harness.unmount() - }) -}) - describe('useMobileStructuredAgentOptions images', () => { it('keeps the answer through a pick made during the read', async () => { const stale = deferred() @@ -246,79 +203,6 @@ describe('useMobileStructuredAgentOptions host catalog', () => { }) }) -describe('useMobileStructuredAgentOptions after a lost pick reply', () => { - const tiers = (value: string, label: string) => [{ value, label }] - const answer = (model: string, serviceTier: string): AgentSessionOptionsResult => ({ - models: [ - { - id: 'm1', - label: 'M1', - isDefault: true, - efforts: [], - serviceTiers: tiers('priority', 'Fast') - }, - { id: 'm2', label: 'M2', isDefault: false, efforts: [], serviceTiers: tiers('flex', 'Flex') } - ], - listedModelIds: ['m1', 'm2'], - current: { model, serviceTier, confirmed: ['model'] } - }) - const speed = (controller: Controller) => { - const kind = descriptorFor(controller.optionSnapshot, 'serviceTier')?.kind - return kind?.type === 'select' - ? { value: kind.currentValue, choices: kind.choices.map((choice) => choice.value) } - : null - } - - it('draws the tier the host reconciled to, stays usable through a failed read, and takes the reconnect read', async () => { - const pendingRead = deferred() - const reads = [ - () => Promise.resolve(answer('m1', 'priority')), - () => Promise.reject(new Error('terminal reconciliation offline')), - () => Promise.reject(new Error('status reconciliation offline')), - () => pendingRead.promise, - () => Promise.resolve(answer('m2', 'flex')) - ] - const client = optionsClient(() => - (reads.shift() ?? (() => Promise.resolve(answer('m2', 'flex'))))() - ) - const { mutate } = recordingMutate(async () => ({ status: 'unknown' })) - const harness = await mountOptions({ ...BASE, client: client.client, mutate }) - expect(speed(harness.current())?.value).toBe('priority') - - // The host switched and set Standard, but its reply never arrived. - await act(async () => { - await harness.current().setStructuredOption('model', 'm2') - }) - expect(currentValueOf(harness.current().optionSnapshot, 'model')).toBe('m2') - expect(speed(harness.current())).toEqual({ value: 'default', choices: ['default', 'flex'] }) - expect(harness.current().pendingOptionId).toBeNull() - - expect(client.optionReads()).toBe(2) // The terminal wake consumed its failed query. - // The status-triggered read fails independently: nothing changes. - await harness.rerender({ optionStamp: 'status-1' }) - expect(speed(harness.current())?.value).toBe('default') - - // A read in flight when the user picks Flex answers for the old state and is dropped. - await harness.rerender({ optionStamp: 'status-2' }) - let picked = false - await act(async () => { - picked = await harness.current().setStructuredOption('serviceTier', 'flex') - }) - expect(picked).toBe(true) - await act(async () => pendingRead.resolve(answer('m1', 'priority'))) - await settle() - expect(currentValueOf(harness.current().optionSnapshot, 'model')).toBe('m2') - expect(speed(harness.current())?.value).toBe('flex') - - // Reconnected, the host's answer stands. - await harness.rerender({ optionStamp: 'status-3' }) - expect(currentValueOf(harness.current().optionSnapshot, 'model')).toBe('m2') - expect(speed(harness.current())?.value).toBe('flex') - expect(client.optionReads()).toBe(6) - await harness.unmount() - }) -}) - it('joins the post-receipt menu read with an unchanged phone option stamp', async () => { const initial: AgentSessionOptionsResult = { models: [{ id: 'family', label: 'Family', isDefault: false, efforts: [], effortsKnown: false }], @@ -485,57 +369,6 @@ it('accepts a typed literal at an attached fence while options refresh with no m await view.unmount() }) -import type { PiRefreshFixtureModule } from '../../../src/shared/agent-session/options/structured-agent-session-option-refresh.test-fixture-contract' -const { piOptionRefreshContract } = await vi.importActual( - '../../../src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-option-refresh.test-fixture' -) - -describe('Pi catalog refresh completion', () => { - piOptionRefreshContract( - async (host) => { - const first = optionsClient( - () => host.options(), - () => host.catalog() - ) - const second = optionsClient( - () => host.options(), - () => host.catalog() - ) - const { mutate } = recordingMutate(async (_call, fields) => { - if (typeof fields.value !== 'string') { - throw new Error('missing effort') - } - return accepted(await host.acceptEffort(fields.value), true) - }) - const harness = await mountOptions({ - ...BASE, - agent: 'pi', - sendSelection: true, - sessionId: host.record.sessionId, - client: first.client, - mutate - }) - return { - snapshot: () => harness.current().optionSnapshot, - capture: () => harness.current().sendSelection().selection, - demand: () => harness.current().optionSurface.demandModelCatalog?.(), - pick: (value) => harness.current().setStructuredOption('effort', value), - replace: async (fence, otherHost) => - harness.rerender({ fence, client: otherHost ? second.client : first.client }), - close: () => harness.unmount() - } - }, - async (work) => { - await act(async () => { - work() - await Promise.resolve() - await Promise.resolve() - }) - await settle() - } - ) -}) - it('refreshes saved planning intent from the same host status stamp without remounting', async () => { let interactionMode: 'default' | 'plan' = 'default' const client = optionsClient(async () => ({ diff --git a/mobile/src/session/use-mobile-structured-agent-options.ts b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.ts similarity index 87% rename from mobile/src/session/use-mobile-structured-agent-options.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-agent-options.ts index 321ba47ba1a..2fe2c786134 100644 --- a/mobile/src/session/use-mobile-structured-agent-options.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-agent-options.ts @@ -1,31 +1,31 @@ -import type { AgentSessionConversationCommand } from '../../../src/shared/agent-session/agent-session-conversation-command' +import type { AgentSessionConversationCommand } from '../../../../../src/shared/agent-session/agent-session-conversation-command' import { useMobileStructuredOptionState } from './use-mobile-structured-option-state' import { useCallback, useLayoutEffect, useMemo, useRef } from 'react' -import { isAgentSessionHandleProvider } from '../../../src/shared/agent-session/lifecycle/agent-session-provider-handle' -import { structuredAgentSessionSeedCatalog } from '../../../src/shared/agent-session/options/structured-agent-session-seed-catalog' +import { isAgentSessionHandleProvider } from '../../../../../src/shared/agent-session/lifecycle/agent-session-provider-handle' +import { structuredAgentSessionSeedCatalog } from '../../../../../src/shared/agent-session/options/structured-agent-session-seed-catalog' import { structuredAgentSessionOptionView, structuredAgentSessionSendSelection, structuredAgentSessionMessageSelection, structuredAgentSessionShownSnapshot -} from '../../../src/shared/agent-session/options/structured-agent-session-option-view' -import { agentChatPermissionModes } from '../../../src/shared/agent-launch/permissions/agent-chat-permission-support' -import { legacyHostPermissionDescriptor } from '../../../src/shared/agent-session/options/structured-agent-session-permission-option' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' -import type { MobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' +} from '../../../../../src/shared/agent-session/options/structured-agent-session-option-view' +import { agentChatPermissionModes } from '../../../../../src/shared/agent-launch/permissions/agent-chat-permission-support' +import { legacyHostPermissionDescriptor } from '../../../../../src/shared/agent-session/options/structured-agent-session-permission-option' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { StructuredAgentSessionHostSupport } from '../agent-session/mobile-structured-agent-session-host-support' +import type { MobileStructuredAgentMutate } from '../agent-session/use-mobile-structured-agent-mutation' import { useMobileStructuredHeldOptionPicks } from './use-mobile-structured-held-option-picks' import { useMobileHostModelCatalogUpgrade, type HostModelCatalogCache } from './use-mobile-host-model-catalog-upgrade' -import { mobileCreatedStructuredSession } from './mobile-created-structured-sessions' +import { mobileCreatedStructuredSession } from '../agent-session/mobile-created-structured-sessions' import { structuredAgentSessionOptionAvailability, createStructuredAgentSessionOptionsWriteOwner -} from '../../../src/shared/agent-session/options/structured-agent-session-options-write-owner' +} from '../../../../../src/shared/agent-session/options/structured-agent-session-options-write-owner' import { useMobileStructuredOptionSurface } from './use-mobile-structured-option-surface' -import { lockedStructuredAgentSessionOptionSnapshot } from '../../../src/shared/agent-session/options/structured-agent-session-option-snapshot' +import { lockedStructuredAgentSessionOptionSnapshot } from '../../../../../src/shared/agent-session/options/structured-agent-session-option-snapshot' const NO_COMMANDS: readonly AgentSessionConversationCommand[] = [] diff --git a/mobile/src/session/use-mobile-structured-async-question.ts b/mobile/src/session/native-chat/input/use-mobile-structured-async-question.ts similarity index 78% rename from mobile/src/session/use-mobile-structured-async-question.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-async-question.ts index 3ce76cd2aba..19f632ed561 100644 --- a/mobile/src/session/use-mobile-structured-async-question.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-async-question.ts @@ -2,11 +2,11 @@ import { useCallback } from 'react' import { formatAskAnswer, type AskAnswerSelection -} from '../../../src/shared/native-chat/input/native-chat-ask-answer' -import { nativeChatAnswerSendIntent } from '../../../src/shared/native-chat/input/native-chat-follow-up-behavior' -import type { AskPrompt } from '../../../src/shared/native-chat/input/native-chat-ask-types' -import type { QueuedMessageCard } from '../../../src/shared/agent-session/view/structured-agent-session-queued-cards' -import type { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session' +} from '../../../../../src/shared/native-chat/input/native-chat-ask-answer' +import { nativeChatAnswerSendIntent } from '../../../../../src/shared/native-chat/input/native-chat-follow-up-behavior' +import type { AskPrompt } from '../../../../../src/shared/native-chat/input/native-chat-ask-types' +import type { QueuedMessageCard } from '../../../../../src/shared/agent-session/view/structured-agent-session-queued-cards' +import type { useMobileStructuredAgentSession } from '../agent-session/use-mobile-structured-agent-session' export type MobileStructuredAskSession = Pick< ReturnType, diff --git a/mobile/src/session/use-mobile-structured-background-tasks.ts b/mobile/src/session/native-chat/input/use-mobile-structured-background-tasks.ts similarity index 66% rename from mobile/src/session/use-mobile-structured-background-tasks.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-background-tasks.ts index fa81de570ff..3f30e490e0e 100644 --- a/mobile/src/session/use-mobile-structured-background-tasks.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-background-tasks.ts @@ -1,16 +1,16 @@ import { useCallback, useMemo } from 'react' -import type { AgentChildRowContext } from '../../../src/shared/agent-status/presentation/agent-child-row-model' -import type { AgentSessionCancelResult } from '../../../src/shared/agent-session/agent-session-wire' -import { agentChildRowContextForSessionStream } from '../../../src/shared/agent-status/presentation/agent-child-row-stream-context' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' +import type { AgentChildRowContext } from '../../../../../src/shared/agent-status/presentation/agent-child-row-model' +import type { AgentSessionCancelResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import { agentChildRowContextForSessionStream } from '../../../../../src/shared/agent-status/presentation/agent-child-row-stream-context' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' import { structuredSessionBackgroundTasksView, type StructuredSessionBackgroundTasksView -} from '../../../src/shared/structured-session-background-tasks-view' -import { structuredSessionForegroundCommands } from '../../../src/shared/structured-session-foreground-commands' -import type { StructuredAgentSessionMutationResult } from './mobile-structured-agent-session-rpc' -import type { MobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' -import { mobileStructuredHostClockOffsetMs } from './mobile-structured-host-clock-offset' +} from '../../../../../src/shared/structured-session-background-tasks-view' +import { structuredSessionForegroundCommands } from '../../../../../src/shared/structured-session-foreground-commands' +import type { StructuredAgentSessionMutationResult } from '../agent-session/mobile-structured-agent-session-rpc' +import type { MobileStructuredAgentMutate } from '../agent-session/use-mobile-structured-agent-mutation' +import { mobileStructuredHostClockOffsetMs } from '../agent-session/mobile-structured-host-clock-offset' export type MobileStructuredBackgroundTasks = { // Scope strip expansion and stopping state to the conversation. diff --git a/mobile/src/session/use-mobile-structured-held-option-picks.ts b/mobile/src/session/native-chat/input/use-mobile-structured-held-option-picks.ts similarity index 81% rename from mobile/src/session/use-mobile-structured-held-option-picks.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-held-option-picks.ts index 245fb1c5d08..71ea9355013 100644 --- a/mobile/src/session/use-mobile-structured-held-option-picks.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-held-option-picks.ts @@ -1,16 +1,16 @@ import { useEffect, useLayoutEffect, useRef } from 'react' -import type { AgentSessionOptionResult } from '../../../src/shared/agent-session/agent-session-wire' -import { acceptedStructuredSessionSelectionOutcome } from '../../../src/shared/agent-session/options/structured-agent-session-options-write-owner' -import type { StructuredSessionSelectionController } from '../../../src/shared/agent-session/options/structured-agent-session-selection-operations' -import type { SessionOptionDescriptor } from '../../../src/shared/native-chat/input/native-chat-session-options' +import type { AgentSessionOptionResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import { acceptedStructuredSessionSelectionOutcome } from '../../../../../src/shared/agent-session/options/structured-agent-session-options-write-owner' +import type { StructuredSessionSelectionController } from '../../../../../src/shared/agent-session/options/structured-agent-session-selection-operations' +import type { SessionOptionDescriptor } from '../../../../../src/shared/native-chat/input/native-chat-session-options' import { nativeChatModelPillLabel, nativeChatSessionOptionLabel -} from '../../../src/shared/native-chat/input/native-chat-session-option-labels' -import { translate } from '../i18n/mobile-i18n' -import { mobileSessionOptionSummaryValue } from './native-chat/input/mobile-native-chat-session-option-labels' -import { sayMobileNativeChatSessionOption as say } from './native-chat/input/native-chat-session-option-copy' -import type { MobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' +} from '../../../../../src/shared/native-chat/input/native-chat-session-option-labels' +import { translate } from '../../../i18n/mobile-i18n' +import { mobileSessionOptionSummaryValue } from './mobile-native-chat-session-option-labels' +import { sayMobileNativeChatSessionOption as say } from './native-chat-session-option-copy' +import type { MobileStructuredAgentMutate } from '../agent-session/use-mobile-structured-agent-mutation' function pickLabel(descriptor: SessionOptionDescriptor | undefined): string | null { if (!descriptor) { diff --git a/mobile/src/session/use-mobile-structured-option-state.ts b/mobile/src/session/native-chat/input/use-mobile-structured-option-state.ts similarity index 85% rename from mobile/src/session/use-mobile-structured-option-state.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-option-state.ts index 2be67786423..095dcb90908 100644 --- a/mobile/src/session/use-mobile-structured-option-state.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-option-state.ts @@ -1,19 +1,19 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react' -import { mobileStructuredSessionStatusFeed } from './mobile-structured-session-status-feed' -import { projectRuntimePermission } from '../../../src/shared/agent-session/options/structured-agent-session-permission-option' +import { mobileStructuredSessionStatusFeed } from '../agent-session/mobile-structured-session-status-feed' +import { projectRuntimePermission } from '../../../../../src/shared/agent-session/options/structured-agent-session-permission-option' import { useMobileStructuredSelectionController } from './use-mobile-structured-selection-controller' -import type { AgentSessionRewindSupport } from '../../../src/shared/agent-session/lifecycle/agent-session-rewind' -import type { AgentSessionOptionsResult } from '../../../src/shared/agent-session/options/agent-session-options-result' -import { callAgentSession } from './mobile-structured-agent-session-rpc' -import { forgetMobileCreatedStructuredSession } from './mobile-created-structured-sessions' -import { createStructuredAgentSessionOptionsReadOwner } from '../../../src/shared/agent-session/options/structured-agent-session-options-read-owner' -import { BoundedMap } from '../../../src/shared/bounded-map' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { AgentSessionOptionCatalog } from '../../../src/shared/agent-session/options/agent-session-option-catalog-types' -import { usesInstalledAgentModelCatalog } from '../../../src/shared/agent-session/options/agent-model-catalog-ownership' -import { structuredAgentSessionFirstFrameOptionState } from '../../../src/shared/agent-session/options/structured-agent-session-first-frame' +import type { AgentSessionRewindSupport } from '../../../../../src/shared/agent-session/lifecycle/agent-session-rewind' +import type { AgentSessionOptionsResult } from '../../../../../src/shared/agent-session/options/agent-session-options-result' +import { callAgentSession } from '../agent-session/mobile-structured-agent-session-rpc' +import { forgetMobileCreatedStructuredSession } from '../agent-session/mobile-created-structured-sessions' +import { createStructuredAgentSessionOptionsReadOwner } from '../../../../../src/shared/agent-session/options/structured-agent-session-options-read-owner' +import { BoundedMap } from '../../../../../src/shared/bounded-map' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { AgentSessionOptionCatalog } from '../../../../../src/shared/agent-session/options/agent-session-option-catalog-types' +import { usesInstalledAgentModelCatalog } from '../../../../../src/shared/agent-session/options/agent-model-catalog-ownership' +import { structuredAgentSessionFirstFrameOptionState } from '../../../../../src/shared/agent-session/options/structured-agent-session-first-frame' import type { HostModelCatalogCache } from './use-mobile-host-model-catalog-upgrade' -import { structuredAgentSessionReportsAtFence } from '../../../src/shared/agent-session/options/structured-agent-session-runtime-reports' +import { structuredAgentSessionReportsAtFence } from '../../../../../src/shared/agent-session/options/structured-agent-session-runtime-reports' // Matches the transcripts this view retains per chat. const MAX_RETAINED_CHAT_READERS = 32 diff --git a/mobile/src/session/use-mobile-structured-option-surface.ts b/mobile/src/session/native-chat/input/use-mobile-structured-option-surface.ts similarity index 95% rename from mobile/src/session/use-mobile-structured-option-surface.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-option-surface.ts index d1d96d6d118..4fed7c5eaf8 100644 --- a/mobile/src/session/use-mobile-structured-option-surface.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-option-surface.ts @@ -3,7 +3,7 @@ import type { SessionOptionDescriptor, SessionOptionsSurface, SessionOptionValue -} from '../../../src/shared/native-chat/input/native-chat-session-options' +} from '../../../../../src/shared/native-chat/input/native-chat-session-options' export function useMobileStructuredOptionSurface(args: { admits: NonNullable diff --git a/mobile/src/session/use-mobile-structured-prompt-responses.ts b/mobile/src/session/native-chat/input/use-mobile-structured-prompt-responses.ts similarity index 89% rename from mobile/src/session/use-mobile-structured-prompt-responses.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-prompt-responses.ts index cbc0a1d764f..b80ff2ecbae 100644 --- a/mobile/src/session/use-mobile-structured-prompt-responses.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-prompt-responses.ts @@ -1,14 +1,14 @@ import { useCallback } from 'react' -import type { AgentSessionPromptResult } from '../../../src/shared/agent-session/agent-session-wire' -import type { AgentJournalQuestionItem } from '../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentSessionPromptResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { AgentJournalQuestionItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' import { AGENT_SESSION_RESPONSE_OPTION_ID_MAX_LENGTH, decodeAgentSessionQuestionAnswers, isValidAgentSessionQuestionAnswers, legacyAgentSessionSelectedOptionId, type AgentSessionQuestionAnswer -} from '../../../src/shared/agent-session/agent-session-question-answer' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' +} from '../../../../../src/shared/agent-session/agent-session-question-answer' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' import { actionableStructuredApproval, actionableStructuredQuestion @@ -20,8 +20,8 @@ import { import type { StructuredAgentSessionMutate, StructuredAgentSessionMutationResult -} from './mobile-structured-agent-session-rpc' -import type { MobileChatQuestion } from './native-chat/input/mobile-native-chat-question' +} from '../agent-session/mobile-structured-agent-session-rpc' +import type { MobileChatQuestion } from './mobile-native-chat-question' export function useMobileStructuredPromptResponses(args: { stateRef: { readonly current: StructuredAgentSessionState } diff --git a/mobile/src/session/use-mobile-structured-queued-edit.ts b/mobile/src/session/native-chat/input/use-mobile-structured-queued-edit.ts similarity index 93% rename from mobile/src/session/use-mobile-structured-queued-edit.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-queued-edit.ts index 200a5608cb8..73db331e222 100644 --- a/mobile/src/session/use-mobile-structured-queued-edit.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-queued-edit.ts @@ -1,4 +1,4 @@ -import type { QueuedEditLeaseLoss } from '../../../src/shared/queued-message-edit-lease' +import type { QueuedEditLeaseLoss } from '../../../../../src/shared/queued-message-edit-lease' import { createMobileQueuedEdit } from './mobile-queued-message-edit-open' import { useCallback, @@ -9,21 +9,21 @@ import { useRef, useState } from 'react' -import type { AgentSessionQueuedMessageUpdateResult } from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import { agentSessionSendBodyFingerprint } from '../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' -import { queuedMessageWithEdit } from '../../../src/shared/queued-message-edit' +import type { AgentSessionQueuedMessageUpdateResult } from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +import { agentSessionSendBodyFingerprint } from '../../../../../src/shared/agent-session/requests/structured-agent-session-send-mutation' +import { queuedMessageWithEdit } from '../../../../../src/shared/queued-message-edit' import { mobileQueuedEditValueKey, snapshotMobileQueuedEditInputs } from './mobile-queued-message-edit-value' import { settleMobileQueuedEdit } from './mobile-queued-message-edit-outcome' -import { queuedEditPublicationKey } from '../../../src/shared/queued-message-edit-publication' +import { queuedEditPublicationKey } from '../../../../../src/shared/queued-message-edit-publication' import { startMobileQueuedEditLease } from './mobile-queued-message-edit-lease' import { useMobileNativeChatDraftInputs, readMobileNativeChatDraftInputs, mobileNativeChatDraftDestination -} from './native-chat/composer/mobile-native-chat-draft-inputs' +} from '../composer/mobile-native-chat-draft-inputs' import { closeMobileQueuedEditDraft, mobileQueuedEditSaving, @@ -35,7 +35,6 @@ import { type MobileStructuredQueuedEditArgs, type MobileQueuedMessageInlineEditor } from './mobile-queued-message-edit-draft' -export type { MobileQueuedMessageInlineEditor } from './mobile-queued-message-edit-draft' export function useMobileStructuredQueuedEdit(args: MobileStructuredQueuedEditArgs) { const { sessionKey, sessionId, client, mutate, onSendError, onActionResolved } = args diff --git a/mobile/src/session/use-mobile-structured-queued-message-controls.ts b/mobile/src/session/native-chat/input/use-mobile-structured-queued-message-controls.ts similarity index 87% rename from mobile/src/session/use-mobile-structured-queued-message-controls.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-queued-message-controls.ts index 4013522fce1..e4026afd448 100644 --- a/mobile/src/session/use-mobile-structured-queued-message-controls.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-queued-message-controls.ts @@ -1,31 +1,29 @@ -import type { DragReorderMove } from '../components/drag-reorder-positions' +import type { DragReorderMove } from '../../../components/drag-reorder-positions' import { useMobileQueuedMessageComposerActions } from './use-mobile-queued-message-composer-actions' import { useCallback, useMemo } from 'react' -import { useMobileTranslation } from '../i18n/use-mobile-translation' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import type { AgentJournalRenderItem, AgentJournalSubmission -} from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import { agentSessionVisibleFailureFacts } from '../../../src/shared/agent-session/failures/agent-session-visible-failures' -import { mapSettledWithConcurrency } from '../../../src/shared/map-with-concurrency' +} from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import { agentSessionVisibleFailureFacts } from '../../../../../src/shared/agent-session/failures/agent-session-visible-failures' +import { mapSettledWithConcurrency } from '../../../../../src/shared/map-with-concurrency' import type { AgentSessionQueuedMessage, AgentSessionQueuedMessageDeleteResult, AgentSessionQueuedMessagesResumeResult, AgentSessionQueuePause -} from '../../../src/shared/agent-session/requests/agent-session-queued-message-wire' -import type { AgentSessionSendResult } from '../../../src/shared/agent-session/agent-session-wire' -import { queuedMessagesQueuePause } from '../../../src/shared/agent-session/view/structured-agent-session-queued-cards' +} from '../../../../../src/shared/agent-session/requests/agent-session-queued-message-wire' +import type { AgentSessionSendResult } from '../../../../../src/shared/agent-session/agent-session-wire' +import { queuedMessagesQueuePause } from '../../../../../src/shared/agent-session/view/structured-agent-session-queued-cards' import { mobileQueuedMessageCards, type MobileQueuedMessageCard } from './mobile-structured-queued-message-cards' -import type { MobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { - useMobileStructuredQueuedEdit, - type MobileQueuedMessageInlineEditor -} from './use-mobile-structured-queued-edit' +import type { MobileStructuredAgentMutate } from '../agent-session/use-mobile-structured-agent-mutation' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { useMobileStructuredQueuedEdit } from './use-mobile-structured-queued-edit' +import type { MobileQueuedMessageInlineEditor } from './mobile-queued-message-edit-draft' // The host owns queued drafts; render its published cards even when it cannot queue new sends. const CLEAR_DELETE_CONCURRENCY = 8 diff --git a/mobile/src/session/use-mobile-structured-selection-controller.ts b/mobile/src/session/native-chat/input/use-mobile-structured-selection-controller.ts similarity index 85% rename from mobile/src/session/use-mobile-structured-selection-controller.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-selection-controller.ts index f4a53b8aad3..82f26744f1e 100644 --- a/mobile/src/session/use-mobile-structured-selection-controller.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-selection-controller.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo, useSyncExternalStore } from 'react' -import { createStructuredSessionSelectionController } from '../../../src/shared/agent-session/options/structured-agent-session-selection-operations' -import { persistMobileStructuredOptionPicks } from './native-chat/input/mobile-native-chat-session-option-persistence' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import { createStructuredSessionSelectionController } from '../../../../../src/shared/agent-session/options/structured-agent-session-selection-operations' +import { persistMobileStructuredOptionPicks } from './mobile-native-chat-session-option-persistence' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' /** Temporary operations belong to one phone authority, above its queried host answer. */ export function useMobileStructuredSelectionController(args: { diff --git a/mobile/src/session/use-mobile-structured-session-option-stamp.ts b/mobile/src/session/native-chat/input/use-mobile-structured-session-option-stamp.ts similarity index 73% rename from mobile/src/session/use-mobile-structured-session-option-stamp.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-session-option-stamp.ts index 3e64e9e9198..0dec257c4d6 100644 --- a/mobile/src/session/use-mobile-structured-session-option-stamp.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-session-option-stamp.ts @@ -1,7 +1,7 @@ import { useCallback, useSyncExternalStore } from 'react' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { structuredAgentSessionOptionStamp } from '../../../src/shared/agent-session/options/structured-agent-session-option-state' -import { mobileStructuredSessionStatusFeed } from './mobile-structured-session-status-feed' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import { structuredAgentSessionOptionStamp } from '../../../../../src/shared/agent-session/options/structured-agent-session-option-state' +import { mobileStructuredSessionStatusFeed } from '../agent-session/mobile-structured-session-status-feed' const noSubscription = (): (() => void) => () => {} diff --git a/mobile/src/session/use-mobile-structured-thread-goal.ts b/mobile/src/session/native-chat/input/use-mobile-structured-thread-goal.ts similarity index 81% rename from mobile/src/session/use-mobile-structured-thread-goal.ts rename to mobile/src/session/native-chat/input/use-mobile-structured-thread-goal.ts index b273b2d9780..a0145274ad1 100644 --- a/mobile/src/session/use-mobile-structured-thread-goal.ts +++ b/mobile/src/session/native-chat/input/use-mobile-structured-thread-goal.ts @@ -2,23 +2,23 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import { AGENT_OWNED_THREAD_GOAL_CLEAR_COMMAND, agentSessionThreadGoalReport -} from '../../../src/shared/agent-session/agent-session-thread-goal-report' +} from '../../../../../src/shared/agent-session/agent-session-thread-goal-report' import type { AgentSessionThreadGoalChange, AgentSessionThreadGoalResult -} from '../../../src/shared/agent-session/agent-session-thread-goal-wire' -import { agentSessionWriteNoticeParts } from '../../../src/shared/agent-session/failures/agent-session-refusal-notice' -import type { AgentJournalThreadGoal } from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import type { AgentSessionOptionsResult } from '../../../src/shared/agent-session/options/agent-session-options-result' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' -import type { StructuredAgentSessionComposerOptions } from '../../../src/shared/agent-session/view/structured-agent-session-composer' -import { mobileAgentSessionWriteNoticeText } from './agent-session-write-notice-copy' -import type { MobileStructuredAgentSessionArgs } from './mobile-structured-agent-session-contract' -import { mobileStructuredHostClockOffsetMs } from './mobile-structured-host-clock-offset' -import type { MobileNativeChatSendOutcome } from './native-chat/mobile-native-chat-send' -import type { MobileStructuredAgentMutate } from './use-mobile-structured-agent-mutation' -import { AGENT_SESSION_PHONE_THREAD_GOAL_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session/agent-session-runtime-capabilities' -import { useRouteRelaysCapability } from '../worktree/workspace-server-context' +} from '../../../../../src/shared/agent-session/agent-session-thread-goal-wire' +import { agentSessionWriteNoticeParts } from '../../../../../src/shared/agent-session/failures/agent-session-refusal-notice' +import type { AgentJournalThreadGoal } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { AgentSessionOptionsResult } from '../../../../../src/shared/agent-session/options/agent-session-options-result' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import type { StructuredAgentSessionComposerOptions } from '../../../../../src/shared/agent-session/view/structured-agent-session-composer' +import { mobileAgentSessionWriteNoticeText } from '../agent-session/agent-session-write-notice-copy' +import type { MobileStructuredAgentSessionArgs } from '../agent-session/mobile-structured-agent-session-contract' +import { mobileStructuredHostClockOffsetMs } from '../agent-session/mobile-structured-host-clock-offset' +import type { MobileNativeChatSendOutcome } from '../composer/mobile-native-chat-send' +import type { MobileStructuredAgentMutate } from '../agent-session/use-mobile-structured-agent-mutation' +import { AGENT_SESSION_PHONE_THREAD_GOAL_RUNTIME_CAPABILITY } from '../../../../../src/shared/agent-session/agent-session-runtime-capabilities' +import { useRouteRelaysCapability } from '../../../worktree/workspace-server-context' export type MobileStructuredThreadGoal = { /** Scopes the strip's expansion to the conversation. */ diff --git a/mobile/src/session/use-native-chat-action-outcomes.ts b/mobile/src/session/native-chat/input/use-native-chat-action-outcomes.ts similarity index 100% rename from mobile/src/session/use-native-chat-action-outcomes.ts rename to mobile/src/session/native-chat/input/use-native-chat-action-outcomes.ts diff --git a/mobile/src/session/native-chat/mobile-native-chat-controller-contract.ts b/mobile/src/session/native-chat/mobile-native-chat-controller-contract.ts index a1b142c8dda..247fcbc108e 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-controller-contract.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-controller-contract.ts @@ -1,7 +1,7 @@ import type { NativeChatSkillDiscoverySnapshot } from '../../../../src/shared/native-chat/input/native-chat-picker-items' import type { AgentSessionRewindSurface } from '../../../../src/shared/agent-session/lifecycle/agent-session-rewind-request' import type { NativeChatSendFollowUp } from '../../../../src/shared/native-chat/input/native-chat-follow-up-behavior' -import type { StructuredMobileSendAttachment } from '../use-mobile-structured-send-with-outcome' +import type { StructuredMobileSendAttachment } from './composer/use-mobile-structured-send-with-outcome' import type { MobileSessionView } from '../../storage/session-view-preferences' import type { Dispatch, MutableRefObject, SetStateAction } from 'react' import type { @@ -22,18 +22,18 @@ import type { NativeChatResolvedPrompt } from '../../../../src/shared/native-cha import type { MobileNativeChatSendIntent, MobileNativeChatSendOutcome -} from './mobile-native-chat-send' -import type { MobileNativeChatCommandRefusalCauses } from './use-mobile-native-chat-send-error' -import type { MobileStructuredQueuedMessageControls } from '../use-mobile-structured-queued-message-controls' -import type { MobileStructuredThreadGoal } from '../use-mobile-structured-thread-goal' -import type { MobileStructuredBackgroundTasks } from '../use-mobile-structured-background-tasks' -import type { MobileStructuredInterruptedContinue } from '../use-mobile-structured-interrupted-continue' -import type { MobileNativeChatPendingMessage } from './mobile-native-chat-pending-echo' +} from './composer/mobile-native-chat-send' +import type { MobileNativeChatCommandRefusalCauses } from './composer/use-mobile-native-chat-send-error' +import type { MobileStructuredQueuedMessageControls } from './input/use-mobile-structured-queued-message-controls' +import type { MobileStructuredThreadGoal } from './input/use-mobile-structured-thread-goal' +import type { MobileStructuredBackgroundTasks } from './input/use-mobile-structured-background-tasks' +import type { MobileStructuredInterruptedContinue } from './composer/use-mobile-structured-interrupted-continue' +import type { MobileNativeChatPendingMessage } from './transcript/mobile-native-chat-pending-echo' import type { useMobileNativeChatSession } from './use-mobile-native-chat-session' import type { MobileNativeChatSessionOptionPickersProps } from './input/MobileNativeChatSessionOptionPickers' import type { MobileNativeChatVisualSource } from './visual/mobile-native-chat-visual-read' import type { NativeChatContextUsageSummary } from '../../../../src/shared/native-chat/native-chat-context-usage-summary' -import type { useMobileStructuredSessionCommands } from '../use-mobile-structured-session-commands' +import type { useMobileStructuredSessionCommands } from './composer/use-mobile-structured-session-commands' import type { MobileNativeChatUsageLimit } from './use-mobile-native-chat-usage-limit' export type MobileNativeChatController = { diff --git a/mobile/src/session/native-chat/mobile-native-chat-diff.test.ts b/mobile/src/session/native-chat/mobile-native-chat-diff.test.ts deleted file mode 100644 index cb2936e8c51..00000000000 --- a/mobile/src/session/native-chat/mobile-native-chat-diff.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { diffFromText } from '../../../../src/shared/native-chat/tools/native-chat-diff' - -describe('diffFromText', () => { - it('caps large diffs before creating render rows', () => { - const text = Array.from( - { length: 1_000 }, - (_unused, index) => `${index % 2 === 0 ? '+' : '-'}line-${index}` - ).join('\n') - const lines = diffFromText(text) - - expect(lines).toHaveLength(120) - expect(lines?.at(-1)).toEqual({ kind: 'meta', text: '… diff truncated …' }) - }) - - it('honors a smaller caller-owned aggregate row budget', () => { - const text = Array.from( - { length: 100 }, - (_unused, index) => `${index % 2 === 0 ? '+' : '-'}line-${index}` - ).join('\n') - - expect(diffFromText(text, 20)).toHaveLength(20) - }) -}) diff --git a/mobile/src/session/native-chat/mobile-native-chat-lower-region-context.ts b/mobile/src/session/native-chat/mobile-native-chat-lower-region-context.ts index d0a7e8a5591..7f509992cf1 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-lower-region-context.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-lower-region-context.ts @@ -1,5 +1,4 @@ -import { createContext, useContext, useLayoutEffect, useRef } from 'react' -import type { View } from 'react-native' +import { createContext, useContext } from 'react' export type MobileNativeChatLowerRegion = { getViewport: () => unknown @@ -30,17 +29,3 @@ export const MobileNativeChatComposerPresenceContext = createContext(true) export function useMobileNativeChatLowerGeometry() { return useContext(MobileNativeChatLowerGeometryContext) } - -export function useMobileNativeChatComposerRegion(shown: boolean, focused: boolean) { - const groupRef = useRef(null) - const lowerRegion = useMobileNativeChatLowerRegion() - const menuWasShown = useRef(false) - useLayoutEffect(() => { - lowerRegion?.layoutChanged() - if (shown !== menuWasShown.current && focused) { - lowerRegion?.reveal(groupRef.current, shown ? 'menu' : 'edit') - } - menuWasShown.current = shown - }) - return groupRef -} diff --git a/mobile/src/session/native-chat/mobile-native-chat-lower-scroll.ts b/mobile/src/session/native-chat/mobile-native-chat-lower-scroll.ts index 6aca979c534..d0194b0689c 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-lower-scroll.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-lower-scroll.ts @@ -1,5 +1,5 @@ -import { createMobileNativeChatReadingHold } from './mobile-native-chat-reading-hold' -import { connectMobileNativeChatScrollGesture } from './mobile-native-chat-scroll-gesture' +import { createMobileNativeChatReadingHold } from './transcript/mobile-native-chat-reading-hold' +import { connectMobileNativeChatScrollGesture } from './transcript/mobile-native-chat-scroll-gesture' import { externalAllowance, composerGroupOf as groupOf, diff --git a/mobile/src/session/native-chat/mobile-native-chat-merge.test.ts b/mobile/src/session/native-chat/mobile-native-chat-merge.test.ts deleted file mode 100644 index 3b922e822f0..00000000000 --- a/mobile/src/session/native-chat/mobile-native-chat-merge.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { mergeNativeChatMessages } from '../../../../src/shared/native-chat/native-chat-merge' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' - -function msg(id: string, overrides: Partial = {}): NativeChatMessage { - return { - id, - role: 'assistant', - blocks: [{ type: 'text', text: id }], - timestamp: 0, - source: 'transcript', - ...overrides - } -} - -describe('mergeNativeChatMessages', () => { - it('appends new ids in arrival order', () => { - const merged = mergeNativeChatMessages([msg('a')], [msg('b'), msg('c')]) - expect(merged.map((m) => m.id)).toEqual(['a', 'b', 'c']) - }) - - it('replaces a re-emitted id in place without reordering', () => { - const merged = mergeNativeChatMessages( - [msg('a'), msg('b'), msg('c')], - [msg('b', { blocks: [{ type: 'text', text: 'updated' }] })] - ) - expect(merged.map((m) => m.id)).toEqual(['a', 'b', 'c']) - expect(merged[1]!.blocks).toEqual([{ type: 'text', text: 'updated' }]) - }) - - it('does not let a lower-priority source overwrite a higher one', () => { - const merged = mergeNativeChatMessages( - [msg('a', { source: 'transcript', blocks: [{ type: 'text', text: 'real' }] })], - [msg('a', { source: 'scrape', blocks: [{ type: 'text', text: 'scraped' }] })] - ) - expect(merged[0]!.blocks).toEqual([{ type: 'text', text: 'real' }]) - }) - - it('returns the existing array unchanged for an empty batch', () => { - const existing = [msg('a')] - expect(mergeNativeChatMessages(existing, [])).toBe(existing) - }) - - it('does not mutate the existing array', () => { - const existing = [msg('a')] - mergeNativeChatMessages(existing, [msg('b')]) - expect(existing.map((m) => m.id)).toEqual(['a']) - }) -}) diff --git a/mobile/src/session/native-chat/mobile-native-chat-merger-oracle.test.ts b/mobile/src/session/native-chat/mobile-native-chat-merger-oracle.test.ts deleted file mode 100644 index d0f7b928f97..00000000000 --- a/mobile/src/session/native-chat/mobile-native-chat-merger-oracle.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import { - applyAppend, - createNativeChatMerger, - mergeNativeChatMessages, - replaceList -} from '../../../../src/shared/native-chat/native-chat-merge' - -function msg(id: string, overrides: Partial = {}): NativeChatMessage { - return { - id, - role: 'assistant', - blocks: [{ type: 'text', text: id }], - timestamp: 0, - source: 'transcript', - ...overrides - } -} - -// Compare each adversarial prefix with the pure merger so cached indexes cannot diverge from a full rebuild. -describe('mobile stateful merger — oracle vs pure merge', () => { - const base = [msg('a'), msg('b', { source: 'hook' })] - const batches: NativeChatMessage[][] = [ - [msg('c')], - [], - [msg('b', { source: 'transcript', blocks: [{ type: 'text', text: 'final' }] })], - [msg('b', { source: 'scrape' })], - [msg('a')], - [msg('d'), msg('e')] - ] - - it('matches the pure merge for every prefix', () => { - const merger = createNativeChatMerger() - replaceList(merger, base) - let pure = mergeNativeChatMessages(base, []) - expect(merger.list).toEqual(pure) - for (const batch of batches) { - const out = applyAppend(merger, batch) - pure = mergeNativeChatMessages(pure, batch) - expect(out).toEqual(pure) - } - }) - - it('keeps prior row identity on a pure tail append', () => { - const merger = createNativeChatMerger() - replaceList(merger, base) - const before = merger.list - const tail = msg('z') - const after = applyAppend(merger, [tail]) - expect(after).not.toBe(before) - expect(after[0]).toBe(before[0]) - expect(after[after.length - 1]).toBe(tail) - }) - - it('bounds live appends and rebuilds the cached indexes for the retained tail', () => { - const merger = createNativeChatMerger() - replaceList(merger, [msg('a'), msg('b'), msg('c')]) - - expect(applyAppend(merger, [msg('d'), msg('e')], 3).map((m) => m.id)).toEqual(['c', 'd', 'e']) - expect(merger.indexById.has('a')).toBe(false) - expect(merger.indexById.get('c')).toBe(0) - expect( - applyAppend(merger, [msg('d', { blocks: [{ type: 'text', text: 'updated' }] })], 3)[1] - ).toMatchObject({ id: 'd', blocks: [{ type: 'text', text: 'updated' }] }) - }) -}) diff --git a/mobile/src/session/native-chat/mobile-native-chat-reading-place.test.tsx b/mobile/src/session/native-chat/mobile-native-chat-reading-place.test.tsx deleted file mode 100644 index 9cd4aa4d9f9..00000000000 --- a/mobile/src/session/native-chat/mobile-native-chat-reading-place.test.tsx +++ /dev/null @@ -1,652 +0,0 @@ -import { - Activity, - createElement as h, - Fragment, - useMemo, - type ReactElement, - type ReactNode -} from 'react' -import { - act, - create, - type ReactTestRenderer, - type ReactTestRendererJSON as Node -} from 'react-test-renderer' -import { afterEach, expect, it, vi } from 'vitest' -import { splitNativeChatBlocks } from '../../../../src/shared/native-chat/tools/native-chat-tool-fold' -import type { - NativeChatBlock, - NativeChatMessage -} from '../../../../src/shared/native-chat/native-chat-types' -import { foldMobileNativeChatMessages } from './mobile-native-chat-render-data' -import { - useMobileNativeChatRowKeys, - type MobileNativeChatRowKeys -} from './transcript/mobile-native-chat-row-keys' -import { mobileNativeChatRowId } from '../mobile-native-chat-reading-place' -import { MobileNativeChatPageList } from './transcript/MobileNativeChatPageList' -import { ToolRun } from './transcript/MobileNativeChatToolRun' -import { useMobileNativeChatTurnDisclosure } from './transcript/use-mobile-native-chat-turn-disclosure' - -const platform = vi.hoisted(() => ({ OS: 'web' })) -const scrollable = vi.hoisted(() => { - const held: { node: unknown } = { node: null } - return held -}) - -vi.mock('react-native', async () => { - const React = await import('react') - class ScrollView extends React.Component<{ children?: ReactNode }> { - scrollToEnd = (): void => {} - scrollTo = (): void => {} - getScrollableNode = (): unknown => scrollable.node - getInnerViewNode = (): object => ({}) - render(): ReactNode { - return React.createElement('div', null, this.props.children) - } - } - const settled = () => ({ start: (): void => {}, stop: (): void => {} }) - return { - Platform: platform, - Text: 'span', - View: 'div', - Pressable: 'button', - ScrollView, - Animated: { - Text: 'span', - Value: class { - setValue(): void {} - }, - loop: (animation: unknown) => animation, - sequence: settled, - timing: settled - }, - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - useWindowDimensions: () => ({ width: 400, height: 800 }) - } -}) -vi.mock('lucide-react-native', () => { - const icon = () => null - const names = ['Bot', 'Brain', 'Check', 'ChevronRight', 'Circle', 'CircleCheck', 'CircleDot'] - const more = ['Eye', 'FileMinus2', 'FilePen', 'FilePlus2', 'Folder', 'Globe', 'ListChecks'] - const rest = ['MessageSquareMore', 'Pencil', 'Plug', 'Search', 'SquareTerminal', 'Wrench'] - return Object.fromEntries([...names, ...more, ...rest].map((name) => [name, icon])) -}) -vi.mock('./transcript/mobile-native-chat-tool-styles', () => ({ - toolStyles: {}, - toolType: () => ({ row: {}, meta: {}, code: {} }) -})) -vi.mock('../../components/MobileSourceCopyButton', () => ({ MobileSourceCopyButton: () => null })) -vi.mock('../../platform/external-link', () => ({ openExternalLink: vi.fn() })) -vi.mock('../../components/inline-text-selection', () => ({ INLINE_TEXT_SELECTION: false })) - -// The browser's resize step: after layout, before paint. -const observers = new Map<() => void, Set>() -const resized = () => act(() => observers.forEach((_, deliver) => deliver())) -// Only observers watching `target` hear about it, as in the browser. -const resizedOnly = (target: unknown) => - act(() => observers.forEach((targets, deliver) => targets.has(target) && deliver())) -vi.stubGlobal( - 'ResizeObserver', - class { - constructor(private readonly deliver: () => void) { - observers.set(deliver, new Set()) - } - observe(target: unknown): void { - observers.get(this.deliver)?.add(target) - } - unobserve(target: unknown): void { - observers.get(this.deliver)?.delete(target) - } - disconnect(): void { - observers.delete(this.deliver) - } - } -) - -const mountedRenderers = new Set() - -afterEach(() => { - act(() => { - for (const renderer of mountedRenderers) { - renderer.unmount() - } - }) - mountedRenderers.clear() - scrollable.node = null - vi.useRealTimers() - vi.restoreAllMocks() -}) - -const say = (id: string, role: 'user' | 'assistant', text: string): NativeChatMessage => ({ - id, - role, - timestamp: null, - source: 'transcript', - blocks: [{ type: 'text', text }] -}) -const edit = (n: number): NativeChatBlock => { - const lines = (word: string): string => Array.from({ length: 30 }, () => word).join('\n') - const input = { file_path: `f${n}.ts`, old_string: lines('old'), new_string: lines('new') } - return { type: 'tool-call', name: 'Edit', callId: `c${n}`, input } -} -const run = (n: number): NativeChatMessage[] => [ - { ...say(`a${n}`, 'assistant', ''), blocks: [edit(n)] }, - { ...say(`r${n}`, 'user', ''), blocks: [{ type: 'tool-result', callId: `c${n}`, output: 'ok' }] } -] -const chat = [say('u0', 'user', 'first'), say('x0', 'assistant', 'answer'), say('u1', 'user', 'go')] - .concat({ - ...say('a1', 'assistant', 'On it'), - blocks: [{ type: 'text', text: 'On it' }, edit(1)] - }) - .concat(run(1).slice(1), ...Array.from({ length: 39 }, (_, i) => run(i + 2))) - -type Box = { id: string; top: number; bottom: number; end: number; parent: number; rows: boolean } -type Element = { - getAttribute: () => string - getBoundingClientRect: () => { top: number; bottom: number } - querySelectorAll: (selector: string) => Element[] - children: Element[] - nextElementSibling: Element | null -} - -const oldHostTail = (): NativeChatMessage[] => JSON.parse(JSON.stringify(chat)) -const from = (id: string) => oldHostTail().slice(chat.findIndex((m) => m.id === id)) - -// Model end-anchored scrollTop=0 at the tail and negative offsets above; prepending history must not shift the view. -function page(tree: () => Node | Node[] | null) { - const boxes = (): Box[] => { - const out: Box[] = [] - let y = 0 - const walk = (node: Node | string, parent: number): void => { - if (typeof node === 'string' || node.type === 'span') { - y += typeof node === 'string' ? 0 : 20 - return - } - const id = String(node.props.testID ?? '') - const rows = node.props['data-chat-rows'] !== undefined - const at = out.push({ id, top: y, bottom: y, end: 0, parent, rows }) - 1 - node.children?.forEach((child) => walk(child, at)) - out[at] = { ...out[at]!, bottom: y, end: out.length } - } - walk({ type: 'page', props: {}, children: [tree() ?? []].flat() }, -1) - return out - } - const element = (all: Box[], at: number): Element => { - const box = all[at]! - const siblings = (parent: number): Element[] => - all.flatMap((other, i) => (other.parent === parent ? [element(all, i)] : [])) - return { - getAttribute: () => box.id, - getBoundingClientRect: () => ({ - top: box.top - node.viewTop(), - bottom: box.bottom - node.viewTop() - }), - querySelectorAll: (inner: string) => select(all, at + 1, box.end, inner), - get children() { - return siblings(at) - }, - get nextElementSibling() { - const next = all.findIndex((other, i) => i > at && other.parent === box.parent) - return next === -1 ? null : element(all, next) - } - } - } - const select = (all: Box[], from: number, to: number, selector: string): Element[] => { - const prefix = /\^="([^"]+)"/.exec(selector)?.[1] ?? '?' - return all.flatMap((box, at) => - at >= from && at < to && box.id.startsWith(prefix) ? [element(all, at)] : [] - ) - } - let scrollTop = 0 - const listeners = new Map void>() - const node = { - writes: 0, - get scrollTop(): number { - return scrollTop - }, - set scrollTop(value: number) { - node.writes += 1 - scrollTop = value - }, - clientHeight: 200, - get scrollHeight(): number { - return boxes()[0]?.bottom ?? 0 - }, - viewTop: (): number => Math.max(0, node.scrollHeight - node.clientHeight) + node.scrollTop, - showFrom: (y: number): void => { - node.scrollTop = y - Math.max(0, node.scrollHeight - node.clientHeight) - }, - getAttribute: () => null, - getBoundingClientRect: () => ({ top: 0, bottom: node.clientHeight }), - addEventListener: (type: string, listener: () => void): void => { - listeners.set(type, listener) - }, - removeEventListener: (type: string): void => { - listeners.delete(type) - }, - touch: (type: 'touchstart' | 'touchend'): void => listeners.get(type)?.(), - querySelectorAll: (selector: string): Element[] => select(boxes(), 0, Infinity, selector), - querySelector: (): Element | null => { - const all = boxes() - const at = all.findIndex((box) => box.rows) - return at === -1 ? null : element(all, at) - }, - top: (id: string): number => - (boxes().find((box) => box.id === id)?.top ?? Number.NaN) - node.viewTop() - } - return node -} - -function Row({ - message, - rowKey, - joined = 0, - workRun, - joinsWorkRun -}: { - message: NativeChatMessage - rowKey: string - joined?: number - workRun?: readonly NativeChatMessage[] - joinsWorkRun?: boolean -}): ReactElement | null { - if (joinsWorkRun) { - return null - } - const { prose, tools } = splitNativeChatBlocks((workRun ?? [message]).flatMap((m) => m.blocks)) - const words = prose - .map((block, i) => h('span', { key: i }, block.type === 'text' ? block.text : '')) - .concat(Array.from({ length: joined }, (_, i) => h('span', { key: `joined:${i}` }, 'joined'))) - const run = tools.length > 0 && h(ToolRun, { blocks: tools }) - return h('div', { testID: mobileNativeChatRowId(rowKey) }, words, run) -} - -let shownKeys!: MobileNativeChatRowKeys - -function Chat(props: { - messages: NativeChatMessage[] - following: boolean - live: boolean - joined?: number - runs?: boolean - status?: boolean -}): ReactElement { - const folded = useMemo( - () => foldMobileNativeChatMessages(props.messages, 'claude'), - [props.messages] - ) - const turns = useMobileNativeChatTurnDisclosure({ - messages: folded, - enabled: props.runs === true, - isWorking: false, - scopeKey: 'chat' - }) - const keys = useMobileNativeChatRowKeys(folded, props.messages, 'chat', turns.presentation) - shownKeys = keys - return h(MobileNativeChatPageList, { - ref: null, - rows: turns.listMessages, - rowKeys: keys, - runs: turns.presentation.workRuns.heads, - isFollowing: () => props.following, - onEndDistance: () => {}, - renderItem: ({ item, index }) => { - const { workRun, joinsWorkRun } = turns.resolveRow(index, item) - const row = h(Row, { - message: item, - rowKey: keys.keyOf(item), - joined: item === folded.at(-1) ? props.joined : 0, - workRun, - joinsWorkRun - }) - // Turn status draws beside its row, untagged. - return props.status - ? h( - Fragment, - null, - row, - ...Array.from({ length: 5 }, (_, i) => h('span', { key: i }, 'Done')) - ) - : row - }, - layoutKey: 'layout', - contentContainerStyle: null, - header: h('div', { testID: 'header' }, h('span', null, 'Loading')), - footer: props.live ? h('span', null, 'Working') : null, - emptyStateView: null, - onScroll: () => {} - }) -} - -function mount( - messages: NativeChatMessage[], - options: { following: boolean; live: boolean; runs?: boolean; status?: boolean } = { - following: false, - live: false - } -) { - let renderer!: ReactTestRenderer - act(() => { - renderer = create(h('div')) - mountedRenderers.add(renderer) - }) - const scroller = page(() => renderer.toJSON()) - scrollable.node = scroller - let mode: 'visible' | 'hidden' = 'visible' - let last: { messages: NativeChatMessage[]; joined: number } = { messages, joined: 0 } - const show = (next: NativeChatMessage[], joined = 0): number => { - const before = scroller.writes - last = { messages: next, joined } - act(() => renderer.update({h(Chat, { ...options, ...last })})) - return scroller.writes - before - } - // Hide or show the list the way the chat overlay does behind its terminal. - const display = (next: 'visible' | 'hidden') => { - mode = next - show(last.messages, last.joined) - } - vi.useFakeTimers({ now: 10_000, toFake: ['Date', 'setTimeout', 'clearTimeout'] }) - const scrolled = () => - act(() => - renderer.root.find((n) => n.props.onScroll && n.props.scrollEventThrottle).props.onScroll({}) - ) - const rest = () => - act(() => { - vi.advanceTimersByTime(400) - }) - const read = (move: () => void) => { - move() - scrolled() - rest() - } - show(messages) - const find = (id: string) => renderer.root.find((n) => n.props.testID === id) - const press = (id: string) => act(() => find(id).findAllByType('button')[0]!.props.onPress()) - const drawn = (id: string) => renderer.root.findAll((n) => n.props.testID === id).length - const lines = (id: string) => find(id).findAllByType('span').length - const rows = () => - renderer.root - .findAll((n) => n.type === 'div' && String(n.props.testID).startsWith('chat-row:')) - .map((n) => n.props.testID) - return { scroller, show, display, press, drawn, lines, find, rows, scrolled, rest, read } -} - -it('keeps the line a reader was on, its run open, when older messages join the run from an old host', () => { - // Exercise anchoring both inside a run and at Load earlier when older calls absorb the visible orphan result. - for (const atTop of [false, true]) { - const chat0 = mount(from('r31')) - chat0.press(mobileNativeChatRowId('a32')) - chat0.press('chat-member:call:c34') - const opened = chat0.lines('chat-member:call:c34') - chat0.read(() => (chat0.scroller.scrollTop += chat0.scroller.top('chat-member:call:c34'))) - // Old hosts can replay the whole tail as new objects with unchanged ids. - for (const [older, first] of [ - ['r21', 'c32'], - ['u0', 'c22'] - ] as const) { - if (atTop) { - chat0.read(() => chat0.scroller.showFrom(0)) - } - const line = `chat-member:call:${atTop ? first : 'c34'}` - const top = chat0.scroller.top(line) - expect(chat0.show(from(older))).toBeLessThanOrEqual(1) - expect(chat0.scroller.top(line)).toBe(top) - } - - expect(chat0.rows().slice(0, 3)).toEqual(['u0', 'x0', 'u1'].map(mobileNativeChatRowId)) - expect(chat0.lines('chat-member:call:c34')).toBe(opened) - expect(chat0.drawn('chat-member:call:c1')).toBe(1) - expect(shownKeys.current('r21')).toBe('a32') - - chat0.show([...oldHostTail(), say('done', 'assistant', 'Done'), say('ask', 'user', 'next')]) - expect(chat0.drawn(mobileNativeChatRowId('a32'))).toBe(1) - expect(chat0.drawn('chat-member:call:c34')).toBe(1) - } - - // Both collapsed headers and open lines must stay anchored as older calls join above. - const later = Array.from({ length: 10 }, (_, i) => say(`t${i}`, 'user', 'later')) - for (const open of [false, true]) { - const tail = mount([...from('r21'), ...later]) - if (open) { - tail.press(mobileNativeChatRowId('a22')) - tail.read(() => (tail.scroller.scrollTop += tail.scroller.top('chat-member:call:c27') + 40)) - } - const before = tail.scroller.top(mobileNativeChatRowId('t0')) - tail.show([...oldHostTail(), ...later]) - expect(tail.scroller.top(mobileNativeChatRowId('t0'))).toBe(before) - } -}) - -it('holds back a change below a scrolling reader until they rest, then keeps their line', () => { - const rows = Array.from({ length: 12 }, (_, i) => say(`m${i}`, i % 2 ? 'assistant' : 'user', 'x')) - const chat0 = mount(rows) - chat0.scroller.showFrom(40) - chat0.scrolled() - // A correction mid-fling would stop iOS momentum; hold the below-reader reply until rest. - const line = chat0.scroller.top(mobileNativeChatRowId('m4')) - expect(chat0.show([...rows, say('n1', 'assistant', 'reply')])).toBe(0) - expect(chat0.drawn(mobileNativeChatRowId('n1'))).toBe(0) - expect(chat0.scroller.top(mobileNativeChatRowId('m4'))).toBe(line) - chat0.rest() - expect(chat0.drawn(mobileNativeChatRowId('n1'))).toBe(1) - expect(chat0.scroller.top(mobileNativeChatRowId('m4'))).toBe(line) -}) - -it('a parked reader stays parked when React Native Web repeats the scroll event of its own correction', () => { - const rows = Array.from({ length: 12 }, (_, i) => say(`m${i}`, i % 2 ? 'assistant' : 'user', 'x')) - const chat0 = mount(rows) - chat0.read(() => chat0.scroller.showFrom(40)) - const line = chat0.scroller.top(mobileNativeChatRowId('m4')) - const n1 = say('n1', 'assistant', 'reply') - chat0.show([...rows, n1]) - chat0.scrolled() - // React Native Web sends the last scroll event again 100 ms after scrolling stops. - chat0.scrolled() - chat0.show([...rows, n1, say('n2', 'assistant', 'more')]) - expect(chat0.drawn(mobileNativeChatRowId('n2'))).toBe(1) - expect(chat0.scroller.top(mobileNativeChatRowId('m4'))).toBe(line) -}) - -it('releases a held change once a chat hidden mid-touch is shown again and the reader rests', () => { - const rows = Array.from({ length: 12 }, (_, i) => say(`m${i}`, i % 2 ? 'assistant' : 'user', 'x')) - const chat0 = mount(rows) - chat0.scroller.touch('touchstart') - chat0.scroller.showFrom(40) - chat0.scrolled() - chat0.show([...rows, say('n1', 'assistant', 'reply')]) - chat0.display('hidden') - // The hidden list hears no touchend. - chat0.scroller.touch('touchend') - chat0.display('visible') - chat0.rest() - expect(chat0.drawn(mobileNativeChatRowId('n1'))).toBe(1) -}) - -it('keeps a parked reader on their line when the list viewport changes height, as when the composer grows', () => { - const rows = Array.from({ length: 12 }, (_, i) => say(`m${i}`, i % 2 ? 'assistant' : 'user', 'x')) - const chat0 = mount(rows) - chat0.read(() => chat0.scroller.showFrom(40)) - const line = chat0.scroller.top(mobileNativeChatRowId('m4')) - - chat0.scroller.clientHeight = 120 - resizedOnly(chat0.scroller) - - expect(chat0.scroller.top(mobileNativeChatRowId('m4'))).toBe(line) -}) - -it("holds a row's look with its messages: what the chat's state adds lands once the reader rests", () => { - const rows = Array.from({ length: 12 }, (_, i) => say(`m${i}`, i % 2 ? 'assistant' : 'user', 'x')) - const chat0 = mount(rows) - chat0.scroller.showFrom(40) - chat0.scrolled() - const line = chat0.scroller.top(mobileNativeChatRowId('m4')) - expect(chat0.show(rows, 3)).toBe(0) - expect(chat0.lines(mobileNativeChatRowId('m11'))).toBe(1) - expect(chat0.scroller.top(mobileNativeChatRowId('m4'))).toBe(line) - chat0.rest() - expect(chat0.lines(mobileNativeChatRowId('m11'))).toBe(4) - expect(chat0.scroller.top(mobileNativeChatRowId('m4'))).toBe(line) -}) - -it('draws a run once when an older page brings in its start while the reader moves', () => { - const call = (id: string): NativeChatMessage => ({ - ...say(id, 'assistant', ''), - blocks: [{ type: 'tool-call', name: 'Bash', callId: id, input: { command: id } }] - }) - const thought: NativeChatMessage = { ...say('t', 'assistant', 'weighing it'), role: 'reasoning' } - const later = Array.from({ length: 12 }, (_, i) => - say(`m${i}`, i % 2 ? 'assistant' : 'user', 'x') - ) - const chat0 = mount([thought, call('b'), ...later], { following: false, live: false, runs: true }) - chat0.scroller.showFrom(40) - chat0.scrolled() - chat0.show([say('u', 'user', 'go'), call('a'), thought, call('b'), ...later]) - const once = ['u', 'a', 'm0'].map(mobileNativeChatRowId) - expect(chat0.rows().slice(0, 3)).toEqual(once) - expect(chat0.drawn('chat-member:run')).toBe(1) - chat0.rest() - expect(chat0.rows().slice(0, 3)).toEqual(once) - expect(chat0.drawn('chat-member:run')).toBe(1) -}) - -it('draws a bounded tail again once a reader who scrolled back through history returns to it', () => { - // Rows and their status lines are sized so a cut that drops a row's status would draw it back. - const messages = Array.from({ length: 200 }, (_, i) => ({ - ...say(`m${i}`, i % 2 ? 'assistant' : 'user', ''), - blocks: [0, 1].map((line) => ({ type: 'text' as const, text: `message ${i}.${line}` })) - })) - const options = { following: true, live: false, status: true } - const chat0 = mount(messages, options) - const tail = chat0.rows().length - options.following = false - chat0.show(messages) - for (let page = 0; page < 8; page += 1) { - chat0.read(() => chat0.scroller.showFrom(0)) - } - expect(chat0.rows().length).toBeGreaterThan(tail * 2) - - options.following = true - chat0.show(messages) - chat0.read(() => (chat0.scroller.scrollTop = 0)) - expect(chat0.rows().length).toBeLessThanOrEqual(tail) - expect(chat0.rows().at(-1)).toBe(mobileNativeChatRowId('m199')) -}) - -it('holds a row the reader taps open in place, and a reply below waits out a flick', () => { - const later = Array.from({ length: 10 }, (_, i) => say(`t${i}`, 'user', 'later')) - const chat0 = mount([...from('r21'), ...later]) - const header = mobileNativeChatRowId('a22') - chat0.read(() => (chat0.scroller.scrollTop += chat0.scroller.top(header) - 40)) - chat0.scroller.touch('touchstart') - chat0.scroller.touch('touchend') - const before = chat0.scroller.top(header) - chat0.press(header) - resized() - expect(chat0.scroller.top(header)).toBe(before) - - // Touch release can start a fling; it is not evidence the list is at rest. - chat0.scroller.touch('touchstart') - chat0.scroller.scrollTop -= 30 - chat0.scrolled() - chat0.scroller.touch('touchend') - expect(chat0.show([...from('r21'), ...later, say('n1', 'assistant', 'reply')])).toBe(0) - expect(chat0.drawn(mobileNativeChatRowId('n1'))).toBe(0) -}) - -it('at the cap, a trim and an append of equal height hold a parked reader and leave a following one to follow', () => { - const rows = Array.from({ length: 13 }, (_, i) => - say(`m${i}`, i % 2 ? 'assistant' : 'user', `message ${i}`) - ) - for (const options of [ - { following: true, live: false }, - { following: true, live: true }, - { following: false, live: false } - ]) { - const chat0 = mount(rows.slice(0, 12), options) - chat0.read(() => - options.following ? (chat0.scroller.scrollTop = 0) : chat0.scroller.showFrom(50) - ) - const before = { - scrollTop: chat0.scroller.scrollTop, - m4: chat0.scroller.top(mobileNativeChatRowId('m4')) - } - - const writes = chat0.show(rows.slice(1)) - - expect(writes).toBe(options.following ? 0 : 1) - if (options.following) { - expect(chat0.scroller.scrollTop).toBe(before.scrollTop) - } else { - expect(chat0.scroller.top(mobileNativeChatRowId('m4'))).toBe(before.m4) - } - } - - // Sequential and parallel trims split results to opposite sides; orphan results must never inherit a run’s key. - const output = (n: number): NativeChatMessage => ({ - ...say(`o${n}`, 'user', ''), - role: 'tool', - blocks: [{ type: 'tool-result', callId: `c${n}`, output: 'ok' }] - }) - const six = [1, 2, 3, 4, 5, 6] - const calls = (id: string) => - six.map((n) => ({ ...say(`${id}${n}`, 'assistant', ''), blocks: [edit(n)] })) - const outputs = { ...output(1), id: 'U', blocks: six.flatMap((n) => output(n).blocks) } - for (const [source, head, orphan, last, absorbed] of [ - [chat.slice(3, 15), 'a1', 'r1', 11, []], - [calls('p').concat(six.map(output)), 'p1', 'o1', 6, ['o1', 'o2', 'o3', 'o4']], - [calls('q').concat(outputs), 'q1', 'U', 6, ['U']] - ] as const) { - const top = mount([...source, ...rows]) - top.press(mobileNativeChatRowId(head)) - top.read(() => (top.scroller.scrollTop += top.scroller.top('chat-member:call:c6') - 10)) - const trim = (trimmed: number) => { - top.show([...source.slice(trimmed), ...rows, say(`done${trimmed}`, 'assistant', 'Done')]) - const run = top.find(mobileNativeChatRowId(head)) - expect(run.findAll((n) => n.props.testID === 'chat-member:call:c6')).toHaveLength(1) - expect(top.scroller.top('chat-member:call:c6')).toBe(10) - } - trim(1) - expect(top.drawn(mobileNativeChatRowId(orphan))).toBe(1) - trim(2) - trim(4) - top.show([...source, ...rows, say('done4', 'assistant', 'Done')]) - expect(absorbed.map(shownKeys.current)).toEqual(absorbed.map(() => head)) - top.show([...source.slice(last), ...rows, say('done', 'assistant', 'Done')]) - expect(top.drawn(mobileNativeChatRowId(head))).toBe(0) - } - - const reader = mount([...chat.slice(3, 15), ...rows]) - reader.press(mobileNativeChatRowId('a1')) - reader.read(() => (reader.scroller.scrollTop += reader.scroller.top('chat-member:call:c1'))) - const next = reader.scroller.top('chat-member:call:c2') - expect(next).toBeGreaterThan(0) - reader.show([...chat.slice(4, 15), ...rows, say('done', 'assistant', 'Done')]) - expect(reader.scroller.top('chat-member:call:c2')).toBe(next) -}) - -it('a roster keeps its key when the fold moves it, and the rows the reader saw after it stay put', () => { - const sibling = (id: string): NativeChatMessage => ({ - ...say(id, 'user', ''), - role: 'system', - blocks: [ - { type: 'text', text: `Ran subagent ${id}` }, - { type: 'subagent', id: `agent-${id}`, label: id, state: 'completed', groupId: 'turn-1' } - ] - }) - const said = (name: string) => - Array.from({ length: 12 }, (_, i) => say(`${name}${i}`, i % 2 ? 'assistant' : 'user', name)) - const all = [...said('old'), sibling('s1'), ...said('mid'), sibling('s2'), ...said('new')] - const chat0 = mount(all.slice(25)) - const tops = (ids: string[]) => ids.map((id) => chat0.scroller.top(mobileNativeChatRowId(id))) - chat0.read(() => chat0.scroller.showFrom(20)) - const below = tops(['new0', 'new5']) - expect(chat0.show(all)).toBeLessThanOrEqual(1) - expect(tops(['new0', 'new5'])).toEqual(below) - expect(chat0.drawn(mobileNativeChatRowId('s2'))).toBe(1) - chat0.read(() => (chat0.scroller.scrollTop += chat0.scroller.top(mobileNativeChatRowId('s2')))) - const after = tops(['mid0', 'mid5']) - expect(chat0.show(all.slice(13))).toBeLessThanOrEqual(1) - expect(tops(['mid0', 'mid5'])).toEqual(after) - expect(chat0.drawn(mobileNativeChatRowId('s2'))).toBe(1) -}) diff --git a/mobile/src/session/native-chat/mobile-native-chat-send-classification.test.ts b/mobile/src/session/native-chat/mobile-native-chat-send-classification.test.ts deleted file mode 100644 index a916038e77a..00000000000 --- a/mobile/src/session/native-chat/mobile-native-chat-send-classification.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - classifyMobileNativeChatSend, - getMobileNativeChatCommands -} from './mobile-native-chat-send-classification' - -describe('classifyMobileNativeChatSend', () => { - it('recognizes OMP selectors and context commands without claiming generic help', () => { - expect(classifyMobileNativeChatSend('omp', '/switch')).toBe('command') - expect(classifyMobileNativeChatSend('omp', '/compact focus on tests')).toBe('command') - expect(classifyMobileNativeChatSend('omp', '/help')).toBe('unknown-token') - expect(classifyMobileNativeChatSend('omp', '/smol')).toBe('unknown-token') - }) - - it('classifies catalog commands per agent', () => { - expect(classifyMobileNativeChatSend('claude', '/clear')).toBe('command') - expect(classifyMobileNativeChatSend('claude', '/compact')).toBe('command') - expect(classifyMobileNativeChatSend('codex', '/model')).toBe('command') - expect(classifyMobileNativeChatSend('codex', '/permissions')).toBe('command') - }) - - it('treats slash tokens outside the agent catalog as unknown, never chat', () => { - expect(classifyMobileNativeChatSend('claude', '/model sonnet')).toBe('unknown-token') - expect(classifyMobileNativeChatSend('claude', '/cost')).toBe('unknown-token') - expect(classifyMobileNativeChatSend('claude', '/diff')).toBe('unknown-token') - }) - - it('leaves /context off mobile, which has no way to show its answer', () => { - for (const agent of ['openclaude', 'omp']) { - expect(classifyMobileNativeChatSend(agent, '/context')).toBe('unknown-token') - expect(classifyMobileNativeChatSend(agent, '/compact')).toBe('command') - expect(getMobileNativeChatCommands(agent).map(({ name }) => name)).not.toContain('context') - } - }) - - it('keeps prose as chat, including leading-whitespace slash text', () => { - expect(classifyMobileNativeChatSend('claude', 'hello there')).toBe('chat') - expect(classifyMobileNativeChatSend('claude', ' /clear is a command')).toBe('chat') - expect(classifyMobileNativeChatSend('claude', '/usr/bin/python is missing')).toBe( - 'unknown-token' - ) - }) - - it('treats $ tokens as skill grammar only for Codex', () => { - expect(classifyMobileNativeChatSend('codex', '$deploy now')).toBe('unknown-token') - expect(classifyMobileNativeChatSend('claude', '$PATH is empty')).toBe('chat') - }) - - it('defaults to chat when no agent is resolved', () => { - expect(classifyMobileNativeChatSend(null, '/clear')).toBe('chat') - }) -}) diff --git a/mobile/src/session/native-chat/mobile-native-chat-unsent-notices.ts b/mobile/src/session/native-chat/mobile-native-chat-unsent-notices.ts index fb40c24539c..d3416e91524 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-unsent-notices.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-unsent-notices.ts @@ -1,5 +1,5 @@ import { agentJournalSubmissionKey } from '../../../../src/shared/agent-session/journal/agent-session-journal-item-key' -import { mobileAgentSessionWriteNoticeText } from '../agent-session-write-notice-copy' +import { mobileAgentSessionWriteNoticeText } from './agent-session/agent-session-write-notice-copy' import { agentSessionVisibleFailureFacts } from '../../../../src/shared/agent-session/failures/agent-session-visible-failures' import type { NativeChatTurnJournal } from '../../../../src/shared/native-chat/transcript/native-chat-turn-membership' import { dispatchWasWithdrawn } from '../../../../src/shared/agent-session/requests/structured-agent-session-dispatch-rejection' diff --git a/mobile/src/session/mobile-native-chat-host-preference-operations.ts b/mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference-operations.ts similarity index 82% rename from mobile/src/session/mobile-native-chat-host-preference-operations.ts rename to mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference-operations.ts index 459aff6fdc0..d33af3fd738 100644 --- a/mobile/src/session/mobile-native-chat-host-preference-operations.ts +++ b/mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference-operations.ts @@ -1,7 +1,6 @@ -import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc/rpc-operation' -import type { RpcCompatibleReader } from '../transport/rpc/rpc-operation-contract' -import '../../../src/shared/source-control/listing/source-control-group-order' -import { settingsMember, settingsField } from '../settings/mobile-settings-projection' +import { bindDeferredRpcOperation, defineRpcOperation } from '../../../transport/rpc/rpc-operation' +import type { RpcCompatibleReader } from '../../../transport/rpc/rpc-operation-contract' +import { settingsMember, settingsField } from '../../../settings/mobile-settings-projection' // Missing reports let the caller tell an older host's absence from a reported value. function reportedBooleanSettingReader( diff --git a/mobile/src/session/mobile-native-chat-host-preference.test.tsx b/mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference.test.tsx similarity index 65% rename from mobile/src/session/mobile-native-chat-host-preference.test.tsx rename to mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference.test.tsx index b19ba1fbb4b..83b0adec268 100644 --- a/mobile/src/session/mobile-native-chat-host-preference.test.tsx +++ b/mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference.test.tsx @@ -2,9 +2,9 @@ import { act, create, type ReactTestRenderer } from 'react-test-renderer' import { afterEach, expect, it, vi } from 'vitest' import { useMobileNativeChatQueueFollowUps } from './use-mobile-native-chat-queue-follow-ups' import { useMobileNativeChatCiResultLines } from './use-mobile-native-chat-ci-result-lines' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import type { ConnectionState } from '../transport/connection/connection-state' -import type { RpcResponse } from '../transport/rpc/rpc-wire-types' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' +import type { ConnectionState } from '../../../transport/connection/connection-state' +import type { RpcResponse } from '../../../transport/rpc/rpc-wire-types' function connection() { let state: ConnectionState = 'connected' @@ -82,71 +82,6 @@ afterEach(() => { renderers.clear() }) -it('re-renders only the consumers of the preference and desktop that changed', async () => { - const a = connection(), - b = connection() - const renders = new Map() - const values = new Map() - const count = (id: string, value: boolean) => { - values.set(id, value) - renders.set(id, (renders.get(id) ?? 0) + 1) - } - function CiLines({ id, client }: { id: string; client: RpcClient }) { - count(id, useMobileNativeChatCiResultLines(client, true)) - return null - } - function FollowUps({ id, client }: { id: string; client: RpcClient }) { - count(id, useMobileNativeChatQueueFollowUps(client, true, id, client, true)) - return null - } - await act(async () => { - renderers.add( - create( - <> - - - - {Array.from({ length: 9 }, (_, index) => ( - - ))} - - ) - ) - }) - await act(async () => {}) - // Ten chats on B share one read and one stream. - expect(b.client.sendRequest).toHaveBeenCalledTimes(1) - expect(b.client.subscribe).toHaveBeenCalledTimes(1) - // B executes the chat and reports CI lines on; A, the paired desktop, hasn't reported. - await act(async () => b.emit({ type: 'nativeChatCiResultLinesChanged', ciResultLines: true })) - expect([values.get('ci-a'), values.get('follow-ups-a'), values.get('ci-b')]).toEqual([ - false, - true, - true - ]) - const settled = new Map(renders) - await act(async () => a.emit({ type: 'nativeChatCiResultLinesChanged', ciResultLines: true })) - expect(values.get('ci-a')).toBe(true) - expect(renders).toEqual(new Map([...settled, ['ci-a', settled.get('ci-a')! + 1]])) - await act(async () => a.emit({ type: 'nativeChatQueueFollowUpsChanged', queueFollowUps: false })) - expect(values.get('follow-ups-a')).toBe(false) - expect(renders).toEqual( - new Map([ - ...settled, - ['ci-a', settled.get('ci-a')! + 1], - ['follow-ups-a', settled.get('follow-ups-a')! + 1] - ]) - ) - const beforeOther = new Map(renders) - await act(async () => b.emit({ type: 'nativeChatCiResultLinesChanged', ciResultLines: false })) - expect(values.get('ci-b')).toBe(false) - expect(renders.get('ci-a')).toBe(beforeOther.get('ci-a')) - expect(renders.get('follow-ups-a')).toBe(beforeOther.get('follow-ups-a')) - // A snapshot-less ready re-reads once for the connection, not once per chat. - await act(async () => b.emit({ type: 'ready', subscriptionId: 'legacy' })) - expect(b.client.sendRequest).toHaveBeenCalledTimes(2) -}) - it('shows CI lines from the last value the paired desktop reported, hidden until it reports one', async () => { const desktop = connection() const values: boolean[] = [] diff --git a/mobile/src/session/mobile-native-chat-host-preference.ts b/mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference.ts similarity index 95% rename from mobile/src/session/mobile-native-chat-host-preference.ts rename to mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference.ts index a25db4207c0..5da3241c9fc 100644 --- a/mobile/src/session/mobile-native-chat-host-preference.ts +++ b/mobile/src/session/native-chat/preferences/mobile-native-chat-host-preference.ts @@ -2,12 +2,12 @@ import { nativeChatCiResultLinesRead, nativeChatQueueFollowUpsRead } from './mobile-native-chat-host-preference-operations' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { subscribeSharedClientEvents, type SharedClientEvent -} from '../transport/connection/shared-client-events' -import { nativeChatQueueFollowUps } from '../../../src/shared/native-chat/input/native-chat-follow-up-behavior' +} from '../../../transport/connection/shared-client-events' +import { nativeChatQueueFollowUps } from '../../../../../src/shared/native-chat/input/native-chat-follow-up-behavior' /** A desktop chat setting the host pushes live: its read, its change event and its ready-snapshot field. */ export type MobileNativeChatHostPreferenceKind = { diff --git a/mobile/src/session/use-mobile-native-chat-ci-result-lines.ts b/mobile/src/session/native-chat/preferences/use-mobile-native-chat-ci-result-lines.ts similarity index 91% rename from mobile/src/session/use-mobile-native-chat-ci-result-lines.ts rename to mobile/src/session/native-chat/preferences/use-mobile-native-chat-ci-result-lines.ts index 8684aa95eb3..7a189a6d890 100644 --- a/mobile/src/session/use-mobile-native-chat-ci-result-lines.ts +++ b/mobile/src/session/native-chat/preferences/use-mobile-native-chat-ci-result-lines.ts @@ -1,5 +1,5 @@ import { useCallback, useSyncExternalStore } from 'react' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { CI_RESULT_LINES_PREFERENCE, mobileNativeChatHostPreference diff --git a/mobile/src/session/use-mobile-native-chat-queue-follow-ups.ts b/mobile/src/session/native-chat/preferences/use-mobile-native-chat-queue-follow-ups.ts similarity index 89% rename from mobile/src/session/use-mobile-native-chat-queue-follow-ups.ts rename to mobile/src/session/native-chat/preferences/use-mobile-native-chat-queue-follow-ups.ts index 882b158db00..201c398ef02 100644 --- a/mobile/src/session/use-mobile-native-chat-queue-follow-ups.ts +++ b/mobile/src/session/native-chat/preferences/use-mobile-native-chat-queue-follow-ups.ts @@ -1,6 +1,6 @@ import { useCallback, useSyncExternalStore } from 'react' -import { nativeChatQueueFollowUps } from '../../../src/shared/native-chat/input/native-chat-follow-up-behavior' -import type { RpcClient } from '../transport/rpc/rpc-client-contract' +import { nativeChatQueueFollowUps } from '../../../../../src/shared/native-chat/input/native-chat-follow-up-behavior' +import type { RpcClient } from '../../../transport/rpc/rpc-client-contract' import { mobileNativeChatHostPreference, QUEUE_FOLLOW_UPS_PREFERENCE diff --git a/mobile/src/session/MobileLinkActionSheet.tsx b/mobile/src/session/native-chat/transcript/MobileLinkActionSheet.tsx similarity index 83% rename from mobile/src/session/MobileLinkActionSheet.tsx rename to mobile/src/session/native-chat/transcript/MobileLinkActionSheet.tsx index 81db7d52d06..6ab27dc905c 100644 --- a/mobile/src/session/MobileLinkActionSheet.tsx +++ b/mobile/src/session/native-chat/transcript/MobileLinkActionSheet.tsx @@ -1,10 +1,10 @@ import { useState } from 'react' import { Copy, ExternalLink, Globe } from 'lucide-react-native' -import { ActionSheetModal, type ActionSheetAction } from '../components/ActionSheetModal' -import { openExternalLink } from '../platform/external-link' -import { useClipboardWriter } from '../platform/clipboard' -import { triggerError, triggerSuccess } from '../platform/haptics' -import { useMobileTranslation } from '../i18n/use-mobile-translation' +import { ActionSheetModal, type ActionSheetAction } from '../../../components/ActionSheetModal' +import { openExternalLink } from '../../../platform/external-link' +import { useClipboardWriter } from '../../../platform/clipboard' +import { triggerError, triggerSuccess } from '../../../platform/haptics' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' // Choices for a tapped chat link: Orca's browser on the host, the phone's browser, or the clipboard. export function MobileLinkActionSheet(props: { diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatAgentMessageCard.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatAgentMessageCard.tsx index f3e0575224e..a7c2b263ac4 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatAgentMessageCard.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatAgentMessageCard.tsx @@ -11,7 +11,7 @@ import { type AgentMessageHeading } from '../../../../../src/shared/agent-session/agent-message-presentation' import { mobileNativeChatDisplayCopyText } from './mobile-native-chat-display-copy' -import { MobileNativeChatMarkdown } from '../MobileNativeChatMarkdown' +import { MobileNativeChatMarkdown } from './MobileNativeChatMarkdown' import { MobileAgentIcon } from '../../../components/MobileAgentIcon' import { AgentStateDot } from '../../../components/AgentStateDot' import { MobileSourceCopyButton } from '../../../components/MobileSourceCopyButton' diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx index f29c25359ef..9d607c9658b 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx @@ -8,7 +8,7 @@ import { colors, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { scaledType } from './mobile-native-chat-message-styles' -import { MobileNativeChatQuestionText } from '../MobileNativeChatQuestionText' +import { MobileNativeChatQuestionText } from './MobileNativeChatQuestionText' const ICON_SIZE = 15 diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatBackgroundTaskRun.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatBackgroundTaskRun.tsx index 029bb7f7076..0e72502a666 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatBackgroundTaskRun.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatBackgroundTaskRun.tsx @@ -6,7 +6,7 @@ import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection import { colors, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' -import { sayMobileBackgroundTask } from '../../mobile-background-task-copy' +import { sayMobileBackgroundTask } from '../input/mobile-background-task-copy' import { BackgroundTaskKindIcon } from '../input/MobileNativeChatBackgroundTasks' import { scaledType } from './mobile-native-chat-message-styles' diff --git a/mobile/src/session/native-chat/MobileNativeChatDiffCard.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatDiffCard.tsx similarity index 85% rename from mobile/src/session/native-chat/MobileNativeChatDiffCard.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatDiffCard.tsx index 261373b2835..fa7d1eeecb6 100644 --- a/mobile/src/session/native-chat/MobileNativeChatDiffCard.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatDiffCard.tsx @@ -1,25 +1,25 @@ import { useMemo } from 'react' import { Pressable, Text, View } from 'react-native' -import { MobileNativeChatOwnedScrollView } from './transcript/MobileNativeChatOwnedScrollView' +import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView' import { ChevronRight, FileMinus2, FilePen, FilePlus2 } from 'lucide-react-native' -import { nativeChatEditFilePatchText } from '../../../../src/shared/native-chat/tools/native-chat-edit-cards' +import { nativeChatEditFilePatchText } from '../../../../../src/shared/native-chat/tools/native-chat-edit-cards' import { nativeChatEditFileBaseName, nativeChatEditGutterChars, unifiedLineNumber, type NativeChatEditFile, type NativeChatEditLine -} from '../../../../src/shared/native-chat/tools/native-chat-edit-model' -import { nativeChatToolRowVerbCopy } from './transcript/native-chat-tool-row-verb-copy' -import { nativeChatDiffCardCopy } from '../../source-control/review/native-chat-diff-card-copy' -import { useMobileTranslation } from '../../i18n/use-mobile-translation' -import { MobileSourceCopyButton } from '../../components/MobileSourceCopyButton' -import { colors, pressedStyle } from '../../theme/mobile-theme' -import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' -import { useMobileNativeChatOpenedDetail } from './visual/mobile-native-chat-opened-detail' -import { useMobileNativeChatDisclosure } from './transcript/mobile-native-chat-disclosure-store' -import { toolStyles as styles, toolType } from './transcript/mobile-native-chat-tool-styles' -import { useMobileNativeChatOpenFile } from './mobile-native-chat-open-file-context' +} from '../../../../../src/shared/native-chat/tools/native-chat-edit-model' +import { nativeChatToolRowVerbCopy } from './native-chat-tool-row-verb-copy' +import { nativeChatDiffCardCopy } from '../../../source-control/review/native-chat-diff-card-copy' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' +import { MobileSourceCopyButton } from '../../../components/MobileSourceCopyButton' +import { colors, pressedStyle } from '../../../theme/mobile-theme' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileNativeChatOpenedDetail } from '../visual/mobile-native-chat-opened-detail' +import { useMobileNativeChatDisclosure } from './mobile-native-chat-disclosure-store' +import { toolStyles as styles, toolType } from './mobile-native-chat-tool-styles' +import { useMobileNativeChatOpenFile } from '../mobile-native-chat-open-file-context' function VerbIcon({ kind, size }: { kind: NativeChatEditFile['changeKind']; size: number }) { useMobileColorScheme() diff --git a/mobile/src/session/MobileNativeChatEmptyStateView.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatEmptyStateView.tsx similarity index 64% rename from mobile/src/session/MobileNativeChatEmptyStateView.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatEmptyStateView.tsx index 69e33b7ccad..e58ef3da95d 100644 --- a/mobile/src/session/MobileNativeChatEmptyStateView.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatEmptyStateView.tsx @@ -1,6 +1,6 @@ import { Text, View } from 'react-native' -import type { NativeChatEmptyStateCopy } from '../../../src/shared/native-chat/native-chat-empty-state' -import { styles } from './native-chat/mobile-native-chat-view-styles' +import type { NativeChatEmptyStateCopy } from '../../../../../src/shared/native-chat/native-chat-empty-state' +import { styles } from '../mobile-native-chat-view-styles' export function MobileNativeChatEmptyStateView({ copy }: { copy: NativeChatEmptyStateCopy }) { return ( diff --git a/mobile/src/session/native-chat/MobileNativeChatFullContent.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatFullContent.tsx similarity index 87% rename from mobile/src/session/native-chat/MobileNativeChatFullContent.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatFullContent.tsx index 537902265eb..aab95c37457 100644 --- a/mobile/src/session/native-chat/MobileNativeChatFullContent.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatFullContent.tsx @@ -1,27 +1,27 @@ import { useContext, useEffect, useMemo, useState } from 'react' import { Pressable, Text, View, type TextStyle } from 'react-native' -import { truncateToolDetail } from '../../../../src/shared/native-chat/tools/native-chat-tool-summary' +import { truncateToolDetail } from '../../../../../src/shared/native-chat/tools/native-chat-tool-summary' import { isNativeChatToolOutputClipped, nativeChatToolFullPayload, nativeChatToolOutputPartialSizes -} from '../../../../src/shared/native-chat/tools/native-chat-tool-output' +} from '../../../../../src/shared/native-chat/tools/native-chat-tool-output' import type { NativeChatMessage, NativeChatToolResultBlock -} from '../../../../src/shared/native-chat/native-chat-types' -import type { AgentJournalBoundedPayload } from '../../../../src/shared/agent-session/journal/agent-session-journal-types' -import { INLINE_TEXT_SELECTION } from '../../components/inline-text-selection' -import { styles } from './transcript/mobile-native-chat-message-styles' -import { toolStyles } from './transcript/mobile-native-chat-tool-styles' -import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' -import { useMobileTranslation } from '../../i18n/use-mobile-translation' -import { MobileNativeChatOwnedScrollView } from './transcript/MobileNativeChatOwnedScrollView' +} from '../../../../../src/shared/native-chat/native-chat-types' +import type { AgentJournalBoundedPayload } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' +import { styles } from './mobile-native-chat-message-styles' +import { toolStyles } from './mobile-native-chat-tool-styles' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' +import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView' import { MobileNativeChatItemSourceContext, readMobileNativeChatItem } from './mobile-native-chat-item-read' -import { pressedStyle } from '../../theme/mobile-theme' +import { pressedStyle } from '../../../theme/mobile-theme' // Keep full-content state per row so live transcript revisions cannot overwrite the reader’s requested expansion. diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.android.test.ts b/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.android.test.ts index 8a7b534e6d5..1a0fcdacd7a 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.android.test.ts +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.android.test.ts @@ -28,7 +28,7 @@ vi.mock('./MobileNativeChatRewindDialog', () => ({ MobileNativeChatRewindDialog: vi.mock('./MobileNativeChatMessageActionsSheet', () => ({ MobileNativeChatMessageActionsSheet: 'MessageActionsSheet' })) -vi.mock('../../MobileLinkActionSheet', () => ({ MobileLinkActionSheet: 'LinkActionSheet' })) +vi.mock('./MobileLinkActionSheet', () => ({ MobileLinkActionSheet: 'LinkActionSheet' })) vi.mock('../visual/MobileNativeChatImageViewer', () => ({ MobileNativeChatImageViewer: 'ImageViewer' })) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx index 4b0c5ce1df1..c08be0a7f06 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react' import { Pressable, Text, View } from 'react-native' import { ChevronRight } from 'lucide-react-native' import { nativeChatReasoningDisclosureKey } from '../../../../../src/shared/native-chat/transcript/native-chat-reasoning-row' -import { formatMobileNativeChatActiveTurnLabel } from '../native-chat-turn-status-copy' +import { formatMobileNativeChatActiveTurnLabel } from './native-chat-turn-status-copy' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' import { LoadingSpinner } from '../../../components/LoadingSpinner' diff --git a/mobile/src/session/native-chat/MobileNativeChatLongPressContent.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatLongPressContent.tsx similarity index 100% rename from mobile/src/session/native-chat/MobileNativeChatLongPressContent.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatLongPressContent.tsx diff --git a/mobile/src/session/native-chat/MobileNativeChatMarkdown.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatMarkdown.tsx similarity index 73% rename from mobile/src/session/native-chat/MobileNativeChatMarkdown.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatMarkdown.tsx index 399e14a473f..fbd25176b08 100644 --- a/mobile/src/session/native-chat/MobileNativeChatMarkdown.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatMarkdown.tsx @@ -1,6 +1,6 @@ import type { ComponentProps } from 'react' -import { MobileMarkdown } from '../../components/MobileMarkdown' -import { useMobileNativeChatOpenFile } from './mobile-native-chat-open-file-context' +import { MobileMarkdown } from '../../../components/MobileMarkdown' +import { useMobileNativeChatOpenFile } from '../mobile-native-chat-open-file-context' /** Chat markdown: generic markdown with the chat's file opener, so file paths in any chat text open, * and desktop chat's math delimiters. */ diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.status-notices.test.ts b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.status-notices.test.ts deleted file mode 100644 index 8adab75b691..00000000000 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.status-notices.test.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { AgentJournalStatusItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' -import { isAdmissibleAgentJournalItemBody } from '../../../../../src/shared/agent-session/journal/agent-session-journal-schemas' -import { projectStructuredItemToNativeChat } from '../../../../../src/shared/agent-session/view/structured-agent-session-projection' -import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' -import { agentSessionFailureFact } from '../../../../../src/shared/agent-session/failures/agent-session-failure' -import { agentSessionFailureWords } from '../../../../../src/shared/agent-session/failures/agent-session-failure-words' -import { colors } from '../../../theme/mobile-theme' -import { activateMobileUiLocale } from '../../../i18n/mobile-i18n' -import { MarkdownLinkHandlerContext } from '../../../components/markdown-link-handler' -import { - MobileNativeChatNoticeContext, - type MobileNativeChatNoticeView -} from '../../mobile-native-chat-notice-context' - -vi.mock('react-native', async () => { - const React = await import('react') - const Text = ({ children, ...props }: { children?: ReactNode }): unknown => - React.createElement('Text', props, children) - return { - ActivityIndicator: 'ActivityIndicator', - Animated: { - Text, - Value: class { - setValue(): void {} - }, - loop: (animation: unknown) => animation, - sequence: () => ({ start: vi.fn(), stop: vi.fn() }), - timing: () => ({ start: vi.fn(), stop: vi.fn() }) - }, - Image: 'Image', - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - ScrollView: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('ScrollView', props, children), - Text, - View: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('View', props, children), - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, - useWindowDimensions: () => ({ width: 400, height: 800 }) - } -}) -vi.mock('expo-clipboard', () => ({ setStringAsync: vi.fn() })) -vi.mock('lucide-react-native', () => ({ - AlertCircle: 'AlertCircle', - AlertTriangle: 'AlertTriangle', - ArrowUp: 'ArrowUp', - Bot: 'Bot', - Brain: 'Brain', - Check: 'Check', - ChevronDown: 'ChevronDown', - ChevronRight: 'ChevronRight', - CircleCheck: 'CircleCheck', - CircleX: 'CircleX', - Copy: 'Copy', - Eye: 'Eye', - FileMinus2: 'FileMinus2', - FilePen: 'FilePen', - FilePlus2: 'FilePlus2', - Folder: 'Folder', - Globe: 'Globe', - Info: 'Info', - ListChecks: 'ListChecks', - MessageSquareMore: 'MessageSquareMore', - Pencil: 'Pencil', - Plug: 'Plug', - Search: 'Search', - SquareChevronRight: 'SquareChevronRight', - SquareTerminal: 'SquareTerminal', - Wrench: 'Wrench' -})) -vi.mock('../../../components/MobileMarkdown', () => ({ MobileMarkdown: 'MobileMarkdown' })) -vi.mock('./MobileNativeChatMessageActionsSheet', () => ({ - MobileNativeChatMessageActionsSheet: 'MessageActionsSheet' -})) - -import { MobileNativeChatMessage } from './MobileNativeChatMessage' - -describe('MobileNativeChatMessage status notices', () => { - let renderer: ReactTestRenderer | null = null - - afterEach(async () => { - act(() => renderer?.unmount()) - renderer = null - await activateMobileUiLocale('en') - }) - - function render(message: NativeChatMessage): ReactTestRenderer { - act(() => { - renderer = create(createElement(MobileNativeChatMessage, { message })) - }) - return renderer! - } - - const statusRow = (body: AgentJournalStatusItem): NativeChatMessage => - projectStructuredItemToNativeChat({ - itemId: 'status', - revision: 1, - sequence: 2, - observedAt: 2, - body - })! - const renderNotice = (body: AgentJournalStatusItem, names: MobileNativeChatNoticeView) => { - act(() => { - renderer = create( - createElement( - MobileNativeChatNoticeContext.Provider, - { value: names }, - createElement(MobileNativeChatMessage, { message: statusRow(body) }) - ) - ) - }) - return renderer!.root.findAll( - (node) => String(node.type) === 'Text' && node.props.selectable === true - )[0]! - } - - it('localizes a canonical failure and discloses its diagnostic only on request', async () => { - await activateMobileUiLocale('fr') - const diagnostic = 'Internal provider diagnostic: modelNotFound' - const notice = renderNotice( - { - kind: 'status', - tone: 'error', - ...agentSessionFailureWords(agentSessionFailureFact('modelUnavailable'), { - agentName: 'Codex', - surface: 'row' - }), - providerFrame: { - provider: 'codex', - kind: 'notification:error', - payload: { - head: diagnostic, - byteLength: diagnostic.length, - digest: 'fixture', - truncated: false - } - } - }, - { hostName: null, agentName: 'Codex' } - ) - expect(String(notice.props.children)).toContain('le modèle sélectionné') - expect(String(notice.props.children)).not.toContain(diagnostic) - const details = () => - renderer!.root.findAll( - (node) => String(node.type) === 'Text' && String(node.props.children).includes(diagnostic) - ) - expect(details()).toHaveLength(0) - const disclosure = renderer!.root.find( - (node) => String(node.type) === 'Pressable' && node.props['aria-expanded'] === false - ) - act(() => disclosure.props.onPress()) - expect(details().length).toBeGreaterThan(0) - act(() => renderer?.unmount()) - const hostAdvice: unknown = { - kind: 'status', - text: 'Custom host advice: use the enterprise model.', - failure: agentSessionFailureFact('modelUnavailable') - } - if (!isAdmissibleAgentJournalItemBody(hostAdvice) || hostAdvice.kind !== 'status') { - throw new Error('Expected host status row') - } - const custom = renderNotice(hostAdvice, { hostName: null, agentName: 'Codex' }) - expect(custom.props.children).toBe('Custom host advice: use the enterprise model.') - }) - - it('links a CI result line to its pull request and commit through the link sheet', () => { - const openLink = vi.fn() - act(() => { - renderer = create( - createElement( - MarkdownLinkHandlerContext.Provider, - { value: openLink }, - createElement(MobileNativeChatMessage, { - message: { - // The journal key percent-encodes the line's client message id. - id: 'orca:pr-event%3Agithub.com%2Facme%2Frepo%233%3A8d5f74b0c1%3A1', - role: 'system', - blocks: [ - { - type: 'text', - text: 'CI failing on repo#3 at 8d5f74b', - presentation: 'pull-request-event', - tone: 'failure' - } - ], - timestamp: 0, - source: 'transcript' - } - }) - ) - ) - }) - const links = renderer!.root.findAll( - (node) => String(node.type) === 'Text' && node.props.accessibilityRole === 'link' - ) - // The whole lead is the PR's target, so a lead the ellipsis cuts still opens it. - expect(links.map((link) => link.props.accessibilityLabel ?? link.props.children)).toEqual([ - 'CI failing on repo#3', - '8d5f74b' - ]) - links.forEach((link) => act(() => link.props.onPress())) - expect(openLink.mock.calls).toEqual([ - ['https://github.com/acme/repo/pull/3'], - ['https://github.com/acme/repo/commit/8d5f74b0c1'] - ]) - }) - - // Protocol-shaped synthetic frames, not a live-provider reproduction. - it.each([ - [ - 'codex', - 'notification:guardianWarning', - 'warning', - 'Codex reported a safety review warning. View details.', - 'Codex a signalé un avertissement de sécurité. Voir les détails.', - colors.statusAmber - ] - ] as const)( - 'discloses the entire available %s notice without tool-output clipping or recovery', - async (provider, kind, tone, heading, french, color) => { - const text = `${'安全 '.repeat(1500)}EXPLANATION_SENTINEL` - const head = `{"partial":"${'é'.repeat(4500)}PAYLOAD_SENTINEL` - const tree = render( - statusRow({ - kind: 'status', - text, - tone, - providerFrame: { - provider, - kind, - payload: { head, byteLength: 99999, digest: 'synthetic', truncated: true } - } - }) - ) - const button = () => - tree.root.find( - (node) => - String(node.type) === 'Pressable' && - node.props.accessibilityLabel?.includes('View details.') - ) - expect(button().props.accessibilityLabel).toBe(heading) - expect(button().props['aria-expanded']).toBe(false) - const detailTexts = () => - tree.root.findAll( - (node) => - String(node.type) === 'Text' && - node.props.selectable === true && - Array.isArray(node.props.children) && - node.props.children.includes(text) - ) - expect(detailTexts()).toHaveLength(0) - act(() => button().props.onPress()) - expect(button().props['aria-expanded']).toBe(true) - const detail = detailTexts()[0] - expect(detail.props.children.join('')).toContain(text) - expect(detail.props.children.join('')).toContain(head) - expect(detail.props.children.join('')).toContain( - 'Provider response truncated (original size: 99999 bytes).' - ) - expect(Object.assign({}, ...detail.props.style)).toMatchObject({ color }) - expect( - tree.root.findAll( - (node) => - String(node.type) === 'Text' && String(node.props.children).includes('Show full output') - ) - ).toHaveLength(0) - await act(() => activateMobileUiLocale('fr')) - const translated = tree.root.find( - (node) => String(node.type) === 'Pressable' && node.props.accessibilityLabel === french - ) - expect(translated.props['aria-expanded']).toBe(true) - expect(detailTexts()[0].props.children.join('')).toContain( - 'Réponse du fournisseur tronquée (taille d’origine : 99999 octets).' - ) - act(() => translated.props.onPress()) - expect(detailTexts()).toHaveLength(0) - } - ) -}) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tool-result-availability.test.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tool-result-availability.test.tsx index d43988aaf5c..d6b44de0b81 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tool-result-availability.test.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tool-result-availability.test.tsx @@ -1,15 +1,7 @@ -import { createElement, Fragment, type ReactNode } from 'react' +import { createElement, type ReactNode } from 'react' import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' import { afterEach, describe, expect, it, vi } from 'vitest' import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' -import type { - AgentJournalItemBody, - AgentJournalRenderItem -} from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' - -import { projectStructuredItemsToNativeChat } from '../../../../../src/shared/agent-session/view/structured-agent-session-projection' -import { foldMobileNativeChatMessages } from '../mobile-native-chat-render-data' -import { useMobileNativeChatTurnDisclosure } from './use-mobile-native-chat-turn-disclosure' vi.mock('react-native', async () => { const React = await import('react') @@ -118,21 +110,6 @@ describe('mobile tool result availability', () => { vi.restoreAllMocks() }) - function render(row: NativeChatMessage, openRun = true): ReactTestRenderer { - act(() => { - renderer = create(createElement(MobileNativeChatMessage, { message: row })) - }) - if (openRun) { - press( - renderer!.root.find( - (node) => - String(node.type) === 'Pressable' && node.props.accessibilityLiveRegion === 'polite' - ) - ) - } - return renderer! - } - // The page's window unmounts a row scrolled past; older messages joining its fold change its id. it('keeps what the reader opened in a windowed-out row by its row key', () => { const row = message([ @@ -177,175 +154,4 @@ describe('mobile tool result availability', () => { expect(textIn(renderer!.root)).toContain('first output') expect(textIn(renderer!.root)).not.toContain('second output') }) - - it('gives a named completion to its own call past silent calls, not to the first', () => { - const calls: NativeChatMessage['blocks'] = Array.from({ length: 18 }, (_, index) => ({ - type: 'tool-call', - callId: `call-${index}`, - name: 'Bash', - input: { command: `command ${index}` } - })) - const raw = message([ - ...calls, - { type: 'tool-result', callId: 'call-17', output: 'late named output' } - ]) - const [row] = foldMobileNativeChatMessages([raw], 'claude') - const tree = render(row!) - expect(toolLines(tree.root)).toHaveLength(18) - expect(textIn(tree.root)).not.toContain('late named output') - press(button(tree.root, 'command 17')) - expect(textIn(tree.root)).toContain('late named output') - press(button(tree.root, 'command 17')) - expect(button(tree.root, 'command 0').props.disabled).toBe(true) - }) - - it.each([true, false])( - 'draws derived outputs ahead of a prompt queued while stopping (host scopes: %s)', - (hasScopes) => { - function item( - itemId: string, - sequence: number, - body: AgentJournalItemBody - ): AgentJournalRenderItem { - return { - itemId, - sequence, - body, - revision: 1, - observedAt: sequence, - ...(hasScopes - ? { - turnScope: - sequence <= 2 - ? { kind: 'thread' as const } - : { kind: 'turn' as const, turnItemId: 'turn' } - } - : {}) - } - } - const items = [ - item('prompt', 1, { - kind: 'message', - role: 'user', - blocks: [{ type: 'text', text: 'question' }] - }), - item('turn', 2, { kind: 'turn', turnId: 'turn', userItemId: 'prompt', state: 'running' }), - item('working-call', 3, { - kind: 'tool-call', - callId: 'working', - name: 'Bash', - input: 'still working', - state: 'running' - }), - item('queued', 4, { - kind: 'message', - role: 'user', - blocks: [{ type: 'text', text: 'queued question' }] - }), - item('source', 5, { - kind: 'message', - role: 'assistant', - blocks: [ - { type: 'text', text: 'source context' }, - { type: 'tool-result', callId: 'unavailable-a', output: 'output one\nfull one' }, - { type: 'tool-result', callId: 'unavailable-b', output: 'output two\nfull two' } - ] - }) - ] - let drawn: NativeChatMessage[] = [] - let waiting: string[] = [] - let live: string[] = [] - function Phone({ journal }: { journal: AgentJournalRenderItem[] }): React.JSX.Element { - const raw = projectStructuredItemsToNativeChat(journal).map((row) => - row.id === 'queued' ? { ...row, queued: true as const } : row - ) - const rows = foldMobileNativeChatMessages(raw, 'claude') - const disclosure = useMobileNativeChatTurnDisclosure({ - messages: rows, - enabled: true, - isWorking: true, - stopping: true, - turnJournal: { items: journal, submissions: [] }, - scopeKey: 'remote-host\0folder\0tab' - }) - drawn = Array.from(disclosure.listMessages) - waiting = disclosure.waitingRows.map((row) => row.item.id) - live = drawn.flatMap((row, index) => - disclosure.resolveRow(index, row).activeTurnIsWorking ? [row.id] : [] - ) - return createElement( - Fragment, - null, - ...drawn.map((row, index) => - createElement(MobileNativeChatMessage, { - key: row.id, - message: row, - ...disclosure.resolveRow(index, row), - structuredActivityUi: true - }) - ) - ) - } - act(() => { - renderer = create(createElement(Phone, { journal: items })) - }) - const tree = renderer! - const headers = () => - tree.root.findAll( - (node) => - String(node.type) === 'Pressable' && node.props.accessibilityLiveRegion === 'polite' - ) - for (let index = 0; index < headers().length; index += 1) { - press(headers()[index]!) - } - for (let index = 0; index < toolLines(tree.root).length; index += 1) { - press(toolLines(tree.root)[index]!) - } - const orphan = drawn.find((row) => row.role === 'tool')! - expect(orphan.id).not.toBe('source') - expect(orphan.journalPosition).toEqual({ sequence: 5, index: 0 }) - expect(drawn.map((row) => row.id)).toEqual(['prompt', 'working-call', 'source', orphan.id]) - expect(waiting).toEqual(['queued']) - expect(live).toContain(orphan.id) - expect(toolLines(tree.root)).toHaveLength(3) - expect(textIn(tree.root)).toContain('output one\nfull one') - expect(textIn(tree.root)).toContain('output two\nfull two') - const firstOutput = button(tree.root, 'output one') - act(() => tree.update(createElement(Phone, { journal: structuredClone(items) }))) - expect(drawn.find((row) => row.role === 'tool')?.id).toBe(orphan.id) - expect(waiting).toEqual(['queued']) - expect(button(tree.root, 'output one')).toBe(firstOutput) - expect(foldMobileNativeChatMessages(drawn, 'claude')).toEqual(drawn) - } - ) - - it('keeps duplicate-ID rows independently open as results and later rows arrive', () => { - const initial = message([ - { type: 'tool-call', callId: 'duplicate', name: 'Bash', input: 'first input' }, - { type: 'tool-call', callId: 'duplicate', name: 'Bash', input: 'second input' }, - { type: 'tool-result', callId: 'duplicate', output: 'first output' } - ]) - const tree = render(initial) - const first = button(tree.root, 'first input') - const second = button(tree.root, 'second input') - press(first) - expect(textIn(tree.root)).toContain('first output') - const updated = message([ - { type: 'tool-call', callId: 'duplicate', name: 'Bash', input: 'changed first input' }, - { type: 'tool-call', callId: 'duplicate', name: 'Bash', input: 'changed second input' }, - { type: 'tool-result', callId: 'duplicate', output: 'first output' }, - { type: 'tool-result', callId: 'duplicate', output: 'second output' }, - { type: 'tool-call', callId: 'later', name: 'Bash', input: 'later input' } - ]) - act(() => tree.update(createElement(MobileNativeChatMessage, { message: updated }))) - expect(button(tree.root, 'changed first input')).toBe(first) - expect(button(tree.root, 'changed second input')).toBe(second) - expect(textIn(tree.root)).toContain('first output') - expect(textIn(tree.root)).not.toContain('second output') - press(second) - expect(textIn(tree.root)).toContain('second output') - press(first) - expect(textIn(tree.root)).not.toContain('first output') - expect(textIn(tree.root)).toContain('second output') - }) }) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx index 57100b34894..ab29aada0df 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx @@ -1,7 +1,7 @@ import { memo, useCallback, useContext, useLayoutEffect, useMemo, useRef } from 'react' import { Text, View } from 'react-native' import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' -import { MobileNativeChatLongPressContent as Content } from '../MobileNativeChatLongPressContent' +import { MobileNativeChatLongPressContent as Content } from './MobileNativeChatLongPressContent' import { splitNativeChatBlocks } from '../../../../../src/shared/native-chat/tools/native-chat-tool-fold' import { nativeChatToolRunViewedImages } from '../../../../../src/shared/native-chat/tools/native-chat-tool-viewed-images' import { selectActiveToolCall } from '../../../../../src/shared/native-chat/tools/native-chat-tool-activity' @@ -15,7 +15,7 @@ import { MobileNativeChatGroupedThought, MobileNativeChatReasoningRow } from './MobileNativeChatReasoningRow' -import { mobileNativeChatRowId } from '../../mobile-native-chat-reading-place' +import { mobileNativeChatRowId } from './mobile-native-chat-reading-place' import { nativeChatWorkRunEntries } from '../../../../../src/shared/native-chat/native-chat-work-run' import { nativeChatReasoningDisclosureKey } from '../../../../../src/shared/native-chat/transcript/native-chat-reasoning-row' import { MobileNativeChatTurnStatus } from './MobileNativeChatTurnStatus' @@ -27,7 +27,7 @@ import { MobileNativeChatUserMessageFold } from './MobileNativeChatUserMessageFo import { TextAttachmentActions } from './MobileNativeChatTextAttachmentActions' import { MobileNativeChatMessageMeta } from './MobileNativeChatMessageMeta' import { MobileNativeChatAwaitingInputRow } from './MobileNativeChatAwaitingInputRow' -import { MobileNativeChatResolutionReceipt } from '../MobileNativeChatResolutionReceipt' +import { MobileNativeChatResolutionReceipt } from './MobileNativeChatResolutionReceipt' import { nativeChatAskRunBlocks, nativeChatAskRunSubject @@ -41,7 +41,7 @@ import type { NativeChatTurnStatus } from '../../../../../src/shared/native-chat import { styles } from './mobile-native-chat-message-styles' import { MobileNativeChatAgentMessageCard } from './MobileNativeChatAgentMessageCard' import { MobileNativeChatVisualContext } from '../visual/mobile-native-chat-visual-context' -import { useMobileNativeChatFullMessage } from '../MobileNativeChatFullContent' +import { useMobileNativeChatFullMessage } from './MobileNativeChatFullContent' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { MobileNativeChatPacedProse } from './MobileNativeChatPacedProse' diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx index 3d4631781cd..c1b8600e34d 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx @@ -27,8 +27,8 @@ import { MobileNativeChatForkedFromContext, MobileNativeChatNoticeContext, type MobileNativeChatNoticeView -} from '../../mobile-native-chat-notice-context' -import { MobileNativeChatMarkdown } from '../MobileNativeChatMarkdown' +} from './mobile-native-chat-notice-context' +import { MobileNativeChatMarkdown } from './MobileNativeChatMarkdown' import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' @@ -36,7 +36,7 @@ import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { nativeChatHostStatusCopy, nativeChatNoticeCopy } from './native-chat-notice-copy' import { nativeChatOrcaStopSay } from './native-chat-orca-stop-copy' import { nativeChatUsageLimitText } from './native-chat-usage-limit-copy' -import { sayMobileAgentSessionFailure } from '../../agent-session-failure-copy' +import { sayMobileAgentSessionFailure } from '../agent-session/agent-session-failure-copy' import type { Translate } from '../../../i18n/mobile-i18n' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { NATIVE_CHAT_STOPPED_BEFORE_START_PRESENTATION } from '../../../../../src/shared/native-chat/native-chat-stopped-before-start' diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatPageList.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatPageList.tsx index bc8f623b1cb..3ab666638f9 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatPageList.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatPageList.tsx @@ -27,14 +27,14 @@ import { mobileNativeChatRowBoxProps, type MobileNativeChatReadingUnit, type MobileNativeChatScrollNode -} from '../../mobile-native-chat-reading-place' +} from './mobile-native-chat-reading-place' import type { MobileNativeChatListHandle } from './use-mobile-native-chat-tail-follow' import type { MobileNativeChatScrollRoots } from './mobile-native-chat-scroll-roots' -import { scrollWritesStopMomentum } from '../../mobile-native-chat-scroll-motion' +import { scrollWritesStopMomentum } from './mobile-native-chat-scroll-motion' import { createMobileNativeChatGrowthHold, MobileNativeChatGrowthHoldContext -} from '../../mobile-native-chat-growth-hold' +} from './mobile-native-chat-growth-hold' const FIRST_ROWS = 24 const DRAW_BATCH = 12 diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx index 69dc694ba84..f1df5cb06c7 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx @@ -1,7 +1,7 @@ import { useCallback, useContext, useEffect, useState } from 'react' import { Image, Pressable, Text, View } from 'react-native' import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' -import { MobileNativeChatMarkdown } from '../MobileNativeChatMarkdown' +import { MobileNativeChatMarkdown } from './MobileNativeChatMarkdown' import { isImageRefBlock, isTextBlock, diff --git a/mobile/src/session/native-chat/MobileNativeChatQuestionText.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatQuestionText.tsx similarity index 88% rename from mobile/src/session/native-chat/MobileNativeChatQuestionText.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatQuestionText.tsx index ff0069026fb..ccd2cb29a48 100644 --- a/mobile/src/session/native-chat/MobileNativeChatQuestionText.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatQuestionText.tsx @@ -1,7 +1,7 @@ import type { ComponentProps } from 'react' import { View, type StyleProp, type ViewStyle } from 'react-native' -import { styles as markdownStyles } from '../../components/mobile-markdown-styles' -import { typography } from '../../theme/mobile-theme' +import { styles as markdownStyles } from '../../../components/mobile-markdown-styles' +import { typography } from '../../../theme/mobile-theme' import { MobileNativeChatMarkdown } from './MobileNativeChatMarkdown' type Props = Pick< diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatReasoningRow.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatReasoningRow.tsx index d07ead88bed..acda77606de 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatReasoningRow.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatReasoningRow.tsx @@ -1,4 +1,4 @@ -import { mobileNativeChatMemberId } from '../../mobile-native-chat-reading-place' +import { mobileNativeChatMemberId } from './mobile-native-chat-reading-place' import { useState } from 'react' import { Pressable, Text, View } from 'react-native' import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView' @@ -10,11 +10,11 @@ import { } from '../../../../../src/shared/native-chat/transcript/native-chat-reasoning-row' import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { deriveNativeChatRowContent } from '../../../../../src/shared/native-chat/transcript/native-chat-row-content' -import { useMobileNativeChatFullMessage } from '../MobileNativeChatFullContent' -import { MobileNativeChatMarkdown } from '../MobileNativeChatMarkdown' +import { useMobileNativeChatFullMessage } from './MobileNativeChatFullContent' +import { MobileNativeChatMarkdown } from './MobileNativeChatMarkdown' import { colors } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' -import { MobileNativeChatLongPressContent } from '../MobileNativeChatLongPressContent' +import { MobileNativeChatLongPressContent } from './MobileNativeChatLongPressContent' import { CHAT_PROSE_SCALE, styles } from './mobile-native-chat-message-styles' /** A grouped thought keeps its own source recovery when its original transcript row is hidden. */ diff --git a/mobile/src/session/native-chat/MobileNativeChatResolutionReceipt.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatResolutionReceipt.tsx similarity index 82% rename from mobile/src/session/native-chat/MobileNativeChatResolutionReceipt.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatResolutionReceipt.tsx index 5aaab6a6ce0..4f026ad8cfc 100644 --- a/mobile/src/session/native-chat/MobileNativeChatResolutionReceipt.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatResolutionReceipt.tsx @@ -5,17 +5,17 @@ import { nativeChatReceiptDevice, nativeChatReceiptSubject, type NativeChatResolvedPrompt -} from '../../../../src/shared/native-chat/native-chat-resolution-receipt' -import { nativeChatReceiptCopy } from '../native-chat-receipt-copy' -import { useMobileTranslation } from '../../i18n/use-mobile-translation' -import { INLINE_TEXT_SELECTION } from '../../components/inline-text-selection' -import { colors, spacing, typography } from '../../theme/mobile-theme' -import { MobileNativeChatAwaitingInputRow } from './transcript/MobileNativeChatAwaitingInputRow' +} from '../../../../../src/shared/native-chat/native-chat-resolution-receipt' +import { nativeChatReceiptCopy } from './native-chat-receipt-copy' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' +import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' +import { colors, spacing, typography } from '../../../theme/mobile-theme' +import { MobileNativeChatAwaitingInputRow } from './MobileNativeChatAwaitingInputRow' import { MobileNativeChatQuestionText } from './MobileNativeChatQuestionText' -import { scaledType } from './transcript/mobile-native-chat-message-styles' -import { MobileNativeChatMessageTime } from './transcript/MobileNativeChatMessageTime' -import { themedStyleSheet } from '../../theme/themed-styles' -import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' +import { scaledType } from './mobile-native-chat-message-styles' +import { MobileNativeChatMessageTime } from './MobileNativeChatMessageTime' +import { themedStyleSheet } from '../../../theme/themed-styles' +import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' export function MobileNativeChatResolutionReceipt({ body, diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatRowItemSource.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatRowItemSource.tsx index fbe0ff64203..2aca8478910 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatRowItemSource.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatRowItemSource.tsx @@ -7,7 +7,7 @@ import { memo, useContext, useMemo, type ComponentProps } from 'react' import { MobileNativeChatItemSourceContext, MobileNativeChatRootItemSourceContext -} from '../mobile-native-chat-item-read' +} from './mobile-native-chat-item-read' import { MobileNativeChatMessage } from './MobileNativeChatMessage' import { resolveMobileNativeChatRowProps, diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.test.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.test.tsx index b103c43ec36..c6a5a70f8f6 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.test.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.test.tsx @@ -29,7 +29,7 @@ vi.mock('./MobileNativeChatRewindDialog', () => ({ MobileNativeChatRewindDialog: vi.mock('./MobileNativeChatMessageActionsSheet', () => ({ MobileNativeChatMessageActionsSheet: 'MessageActionsSheet' })) -vi.mock('../../MobileLinkActionSheet', () => ({ MobileLinkActionSheet: 'LinkActionSheet' })) +vi.mock('./MobileLinkActionSheet', () => ({ MobileLinkActionSheet: 'LinkActionSheet' })) vi.mock('../visual/MobileImageViewerActions', () => ({ MobileImageViewerActions: 'ImageViewerActions' })) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.tsx index 04c59b46132..49f233a0224 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatRowOverlaysHost.tsx @@ -5,7 +5,7 @@ import { triggerMediumImpact } from '../../../platform/haptics' import { MobileNativeChatImageViewer } from '../visual/MobileNativeChatImageViewer' import { MobileImageViewerActions } from '../visual/MobileImageViewerActions' import { MobileNativeChatMessageActionsSheet } from './MobileNativeChatMessageActionsSheet' -import { MobileLinkActionSheet } from '../../MobileLinkActionSheet' +import { MobileLinkActionSheet } from './MobileLinkActionSheet' import { MarkdownLinkHandlerContext } from '../../../components/markdown-link-handler' import { MobileNativeChatRowOverlaysContext, diff --git a/mobile/src/session/MobileNativeChatScrollRootsBoundary.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatScrollRootsBoundary.tsx similarity index 86% rename from mobile/src/session/MobileNativeChatScrollRootsBoundary.tsx rename to mobile/src/session/native-chat/transcript/MobileNativeChatScrollRootsBoundary.tsx index 3c75a385228..9dc4cf30b88 100644 --- a/mobile/src/session/MobileNativeChatScrollRootsBoundary.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatScrollRootsBoundary.tsx @@ -1,7 +1,7 @@ import { Component } from 'react' import { Platform } from 'react-native' -import type { MobileNativeChatRowKeys } from './native-chat/transcript/mobile-native-chat-row-keys' -import type { MobileNativeChatScrollRoots } from './native-chat/transcript/mobile-native-chat-scroll-roots' +import type { MobileNativeChatRowKeys } from './mobile-native-chat-row-keys' +import type { MobileNativeChatScrollRoots } from './mobile-native-chat-scroll-roots' type Props = { children?: React.ReactNode diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx index 39558247e75..53e3f3e2650 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx @@ -26,7 +26,7 @@ import { colors, pressedStyle } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { highlightMobileCode } from '../../../files/session-documents/mobile-file-syntax' import { MobileNativeChatToolGlyph } from './MobileNativeChatToolIcon' -import { MobileNativeChatToolOutput } from '../MobileNativeChatFullContent' +import { MobileNativeChatToolOutput } from './MobileNativeChatFullContent' import { useMobileNativeChatOpenedDetail } from '../visual/mobile-native-chat-opened-detail' import { useMobileNativeChatDisclosure } from './mobile-native-chat-disclosure-store' import { toolStyles as styles, toolType } from './mobile-native-chat-tool-styles' diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.test.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.test.tsx index 57f217b8611..5bd9a52d583 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.test.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.test.tsx @@ -165,50 +165,6 @@ describe('ToolRun', () => { }) }) - it('follows new work at the end of a live run, but holds still when the reader opens a row', () => { - const live = (blocks: NativeChatBlock[]) => - createElement(ToolRun, { - blocks, - activeTurnIsWorking: true, - trailing: true - }) - const first: NativeChatBlock[] = [ - { type: 'tool-call', name: 'Bash', input: { command: 'ls' }, state: 'completed' }, - { type: 'tool-result', output: 'a.ts' } - ] - act(() => { - renderer = create(live(first)) - }) - openRun(renderer!) - const box = () => renderer!.root.findAll((node) => String(node.type) === 'ScrollView')[0]! - act(() => { - box().props.onContentSizeChange() - }) - scrolls.toEnd.mockClear() - act(() => { - renderer!.root - .findAll((node) => String(node.type) === 'Pressable') - .findLast((node) => textIn(node).includes('ls'))! - .props.onPress() - }) - act(() => { - box().props.onContentSizeChange() - }) - expect(scrolls.toEnd).not.toHaveBeenCalled() - act(() => { - renderer!.update( - live([ - ...first, - { type: 'tool-call', name: 'Bash', input: { command: 'pwd' }, state: 'running' } - ]) - ) - }) - act(() => { - box().props.onContentSizeChange() - }) - expect(scrolls.toEnd).toHaveBeenCalledTimes(1) - }) - it('does not save a follow for a later row opening when new work left the height unchanged', () => { const live = (blocks: NativeChatBlock[]) => createElement(ToolRun, { diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx index 852d0421203..2833156b26b 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx @@ -33,7 +33,7 @@ import { colors, pressedStyle } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { MobileNativeChatToolGlyph } from './MobileNativeChatToolIcon' import { MobileNativeChatToolLine } from './MobileNativeChatToolLine' -import { MobileNativeChatDiffCard } from '../MobileNativeChatDiffCard' +import { MobileNativeChatDiffCard } from './MobileNativeChatDiffCard' import { MobileNativeChatTaskList } from '../input/MobileNativeChatTaskList' import { MobileNativeChatOpenedDetailContext } from '../visual/mobile-native-chat-opened-detail' import { @@ -42,7 +42,7 @@ import { useMobileNativeChatDisclosure } from './mobile-native-chat-disclosure-store' import { toolStyles as styles, toolType } from './mobile-native-chat-tool-styles' -import { mobileNativeChatMemberId } from '../../mobile-native-chat-reading-place' +import { mobileNativeChatMemberId } from './mobile-native-chat-reading-place' import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView' import type { MobileNativeChatToolRunAsides, diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnDiffRollup.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnDiffRollup.tsx index cf68fbc59a4..7ab1d87549a 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnDiffRollup.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnDiffRollup.tsx @@ -6,7 +6,7 @@ import { type NativeChatTurnDiff, type NativeChatTurnDiffFile } from '../../../../../src/shared/native-chat/tools/native-chat-turn-diffs' -import { MobileNativeChatDiffCard } from '../MobileNativeChatDiffCard' +import { MobileNativeChatDiffCard } from './MobileNativeChatDiffCard' import { useMobileNativeChatDisclosure } from './mobile-native-chat-disclosure-store' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.test.ts b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.test.ts deleted file mode 100644 index 52ae2dd631a..00000000000 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { AppStateStatus } from 'react-native' - -const appState = vi.hoisted(() => ({ - current: 'active', - listeners: new Set<(state: AppStateStatus) => void>() -})) - -vi.mock('react-native', async () => { - const React = await import('react') - const Text = ({ children, ...props }: { children?: ReactNode }): unknown => - React.createElement('Text', props, children) - return { - AppState: { - get currentState() { - return appState.current - }, - addEventListener: (_event: string, listener: (state: AppStateStatus) => void) => { - appState.listeners.add(listener) - return { - remove: () => { - appState.listeners.delete(listener) - } - } - } - }, - ActivityIndicator: (props: Record) => - React.createElement('ActivityIndicator', props), - Pressable: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('Pressable', props, children), - Text, - View: ({ children, ...props }: { children?: ReactNode }) => - React.createElement('View', props, children), - StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 } - } -}) -vi.mock('lucide-react-native', () => ({ ChevronRight: 'ChevronRight' })) - -import { MobileNativeChatTurnStatus } from './MobileNativeChatTurnStatus' -import { findAllByHostType } from '../../../test-support/react-test-renderer-host-nodes' - -const labels = (node: ReactTestInstance): string[] => - findAllByHostType(node, 'Text').map((text) => String(text.children.join(''))) - -let renderer: ReactTestRenderer | null = null - -beforeEach(() => { - vi.useFakeTimers() - vi.setSystemTime(new Date('2026-09-04T00:00:00Z')) - appState.current = 'active' - appState.listeners.clear() -}) - -afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - vi.restoreAllMocks() - vi.useRealTimers() -}) - -describe('MobileNativeChatTurnStatus', () => { - function changeAppState(state: AppStateStatus) { - act(() => { - appState.current = state - for (const listener of appState.listeners) { - listener(state) - } - }) - } - function render(props: { - startedAt: number | null - workedSeconds?: number | null - verdict?: 'interruption' | 'cancellation' - expanded?: boolean - onToggleExpanded?: () => void - }): ReactTestRenderer { - act(() => { - renderer = create(createElement(MobileNativeChatTurnStatus, props)) - }) - return renderer! - } - - it('stops clock callbacks in the background and catches up immediately on resume', () => { - const tree = render({ startedAt: Date.now() }) - act(() => { - vi.advanceTimersByTime(2_000) - }) - expect(labels(tree.root)).toEqual(['Working for 2s']) - - changeAppState('background') - const clockReads = vi.spyOn(Date, 'now') - act(() => { - vi.advanceTimersByTime(60_000) - }) - expect(clockReads).not.toHaveBeenCalled() - expect(vi.getTimerCount()).toBe(0) - expect(labels(tree.root)).toEqual(['Working for 2s']) - - changeAppState('active') - expect(labels(tree.root)).toEqual(['Working for 1m 2s']) - expect(vi.getTimerCount()).toBe(1) - act(() => { - vi.advanceTimersByTime(1_000) - }) - expect(labels(tree.root)).toEqual(['Working for 1m 3s']) - }) - - it('keeps the original pre-stamp epoch through background and settlement transitions', () => { - const tree = render({ startedAt: null }) - act(() => { - vi.advanceTimersByTime(3_000) - }) - expect(labels(tree.root)).toEqual(['Working for 3s']) - changeAppState('background') - act(() => { - vi.advanceTimersByTime(10_000) - }) - act(() => - tree.update(createElement(MobileNativeChatTurnStatus, { startedAt: null, workedSeconds: 8 })) - ) - changeAppState('active') - expect(labels(tree.root)).toEqual(['Worked for 8s']) - expect(vi.getTimerCount()).toBe(0) - - act(() => tree.update(createElement(MobileNativeChatTurnStatus, { startedAt: null }))) - expect(labels(tree.root)).toEqual(['Working for 13s']) - expect(vi.getTimerCount()).toBe(1) - act(() => tree.unmount()) - expect(vi.getTimerCount()).toBe(0) - expect(appState.listeners.size).toBe(0) - }) - - it('creates no clock while initially backgrounded and uses a supplied start time on resume', () => { - appState.current = 'background' - const startedAt = Date.now() - 5_000 - const tree = render({ startedAt }) - expect(vi.getTimerCount()).toBe(0) - act(() => { - vi.advanceTimersByTime(15_000) - }) - changeAppState('active') - expect(labels(tree.root)).toEqual(['Working for 20s']) - expect(vi.getTimerCount()).toBe(1) - }) -}) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx index c3066449b9e..0a10c804d48 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx @@ -5,7 +5,7 @@ import { nativeChatElapsedSeconds } from '../../../../../src/shared/native-chat/ import { formatMobileNativeChatTurnStatusLabel, nativeChatTurnStatusCopy -} from '../native-chat-turn-status-copy' +} from './native-chat-turn-status-copy' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import type { AgentTurnOutcome } from '../../../../../src/shared/agent-status/lifecycle/agent-turn-outcome' import { colors, spacing, typography } from '../../../theme/mobile-theme' diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatUsageLimitStatus.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatUsageLimitStatus.tsx index 32644911373..2aabf7eee08 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatUsageLimitStatus.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatUsageLimitStatus.tsx @@ -11,7 +11,7 @@ import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection import { MobileNativeChatNoticePill } from './MobileNativeChatNoticePill' import { scaledType } from './mobile-native-chat-message-styles' import { nativeChatUsageLimitStatusText } from './native-chat-usage-limit-copy' -import { MobileNativeChatNoticeContext } from '../../mobile-native-chat-notice-context' +import { MobileNativeChatNoticeContext } from './mobile-native-chat-notice-context' /** True from the reset on; re-renders once when it passes. */ function useResetPassed(resetsAt: number | undefined): boolean { diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx index 77030a4d412..9a757b6741d 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx @@ -9,7 +9,7 @@ import { colors, spacing, typography, pressedStyle } from '../../../theme/mobile import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { CHAT_PROSE_SCALE } from './mobile-native-chat-message-styles' -import type { useMobileNativeChatFullMessage } from '../MobileNativeChatFullContent' +import type { useMobileNativeChatFullMessage } from './MobileNativeChatFullContent' import { useMobileTranslation } from '../../../i18n/use-mobile-translation' const FOLDED_HEIGHT = diff --git a/mobile/src/session/native-chat/transcript/mobile-agent-message-renderer.test-fixture.ts b/mobile/src/session/native-chat/transcript/mobile-agent-message-renderer.test-fixture.ts index 301d7c57d18..abb586ea7d8 100644 --- a/mobile/src/session/native-chat/transcript/mobile-agent-message-renderer.test-fixture.ts +++ b/mobile/src/session/native-chat/transcript/mobile-agent-message-renderer.test-fixture.ts @@ -63,15 +63,11 @@ vi.mock('lucide-react-native', () => ({ Wrench: 'Wrench', Terminal: 'Terminal' })) -const proseRenders = vi.hoisted(() => ({ count: 0 })) -export function proseRenderCount() { - return proseRenders.count -} + vi.mock('../../../components/MobileMarkdown', async () => { const React = await import('react') return { MobileMarkdown: (props: ComponentProps) => { - proseRenders.count++ return React.createElement('MobileMarkdown', props) } } diff --git a/mobile/src/session/mobile-native-chat-growth-hold.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-growth-hold.ts similarity index 100% rename from mobile/src/session/mobile-native-chat-growth-hold.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-growth-hold.ts diff --git a/mobile/src/session/native-chat/mobile-native-chat-item-read.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-item-read.ts similarity index 77% rename from mobile/src/session/native-chat/mobile-native-chat-item-read.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-item-read.ts index c1dd1209ce7..ac6557bf371 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-item-read.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-item-read.ts @@ -1,11 +1,11 @@ import { createContext, type ContextType } from 'react' import * as z from 'zod' -import { isAdmissibleAgentJournalRenderItem } from '../../../../src/shared/agent-session/journal/agent-session-journal-schemas' -import type { AgentJournalRenderItem } from '../../../../src/shared/agent-session/journal/agent-session-journal-types' -import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' -import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' -import type { MobileNativeChatVisualSource } from './visual/mobile-native-chat-visual-read' -import type { MobileNativeChatMessageDetail } from './transcript/use-mobile-native-chat-message-detail' +import { isAdmissibleAgentJournalRenderItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-schemas' +import type { AgentJournalRenderItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import { bindDeferredRpcOperation, defineRpcOperation } from '../../../transport/rpc/rpc-operation' +import { rpcResultVariant } from '../../../transport/rpc/rpc-operation-result-reader' +import type { MobileNativeChatVisualSource } from '../visual/mobile-native-chat-visual-read' +import type { MobileNativeChatMessageDetail } from './use-mobile-native-chat-message-detail' const itemReplySchema = z.object({ item: z.custom(isAdmissibleAgentJournalRenderItem).nullable() diff --git a/mobile/src/session/native-chat/mobile-native-chat-list-footer.tsx b/mobile/src/session/native-chat/transcript/mobile-native-chat-list-footer.tsx similarity index 85% rename from mobile/src/session/native-chat/mobile-native-chat-list-footer.tsx rename to mobile/src/session/native-chat/transcript/mobile-native-chat-list-footer.tsx index ccfd71a9f9d..5435ee5257e 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-list-footer.tsx +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-list-footer.tsx @@ -1,5 +1,5 @@ import { Fragment, type ReactElement } from 'react' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' type MobileNativeChatListRow = { item: NativeChatMessage; index: number } diff --git a/mobile/src/session/native-chat/mobile-native-chat-list-header.tsx b/mobile/src/session/native-chat/transcript/mobile-native-chat-list-header.tsx similarity index 93% rename from mobile/src/session/native-chat/mobile-native-chat-list-header.tsx rename to mobile/src/session/native-chat/transcript/mobile-native-chat-list-header.tsx index eca153e8ecf..b65201a55d9 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-list-header.tsx +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-list-header.tsx @@ -1,8 +1,8 @@ import { useEffect, useState, type ReactElement, type ReactNode } from 'react' import { ActivityIndicator, Animated, Easing, Platform, Pressable, Text, View } from 'react-native' -import { colors } from '../../theme/mobile-theme' -import { styles } from './mobile-native-chat-view-styles' -import type { OlderPagePhase } from '../use-mobile-older-page-loader' +import { colors } from '../../../theme/mobile-theme' +import { styles } from '../mobile-native-chat-view-styles' +import type { OlderPagePhase } from './use-mobile-older-page-loader' const LOAD_EARLIER_FAILED = "Couldn't load earlier messages" const LOAD_EARLIER_RETRY = 'Try Again' diff --git a/mobile/src/session/mobile-native-chat-notice-context.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-notice-context.ts similarity index 84% rename from mobile/src/session/mobile-native-chat-notice-context.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-notice-context.ts index bcf6ab8ab79..24c84092a51 100644 --- a/mobile/src/session/mobile-native-chat-notice-context.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-notice-context.ts @@ -1,6 +1,6 @@ import { createContext } from 'react' -import type { MobileStructuredInterruptedContinue } from './use-mobile-structured-interrupted-continue' -import type { MobileNativeChatUsageLimit } from './native-chat/use-mobile-native-chat-usage-limit' +import type { MobileStructuredInterruptedContinue } from '../composer/use-mobile-structured-interrupted-continue' +import type { MobileNativeChatUsageLimit } from '../use-mobile-native-chat-usage-limit' /** What a notice row needs: the Orca host behind the chat (null when unnamed), the agent, and Continue for an Orca-stopped turn. */ export type MobileNativeChatNoticeView = { diff --git a/mobile/src/session/native-chat/mobile-native-chat-pending-echo.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-pending-echo.ts similarity index 91% rename from mobile/src/session/native-chat/mobile-native-chat-pending-echo.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-pending-echo.ts index 642696fbac1..00ef55a7594 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-pending-echo.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-pending-echo.ts @@ -1,15 +1,15 @@ -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import type { MobileNativeChatDraftDestination } from './composer/mobile-native-chat-draft-inputs' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' +import type { MobileNativeChatDraftDestination } from '../composer/mobile-native-chat-draft-inputs' import { assignNativeChatPendingOccurrence, nativeChatPendingContentKey -} from '../../../../src/shared/native-chat/transcript/native-chat-pending-occurrence' +} from '../../../../../src/shared/native-chat/transcript/native-chat-pending-occurrence' import { NATIVE_CHAT_PENDING_SEND_LIMIT, prunePendingSends, visibleNativeChatPendingSends, type NativeChatPendingSend -} from '../../../../src/shared/native-chat/transcript/native-chat-pending-sends' +} from '../../../../../src/shared/native-chat/transcript/native-chat-pending-sends' export type MobileNativeChatPendingMessage = NativeChatPendingSend & { // Phone-local preview URIs; these echoes retire only once their preview binds to a transcript row. diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-sources.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-sources.ts index d0e4be11ea1..48f57f9bf12 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-sources.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-sources.ts @@ -7,7 +7,7 @@ import { nativeChatCommandOutputParentId, surfaceNativeChatCommandOutputs } from '../../../../../src/shared/native-chat/tools/native-chat-command-output' -import { isMobileNativeChatSource } from '../mobile-native-chat-render-data' +import { isMobileNativeChatSource } from './mobile-native-chat-render-data' export type MobileNativeChatProjectionSource = { raw: NativeChatMessage diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-tail.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-tail.ts index adc0bc0f0df..dd964726853 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-tail.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-projection-tail.ts @@ -19,7 +19,7 @@ import { groupNativeChatFoldUnitMembers, type NativeChatFoldUnit as Unit } from '../../../../../src/shared/native-chat/transcript/native-chat-fold-units' -import { finishMobileNativeChatMessages } from '../mobile-native-chat-render-data' +import { finishMobileNativeChatMessages } from './mobile-native-chat-render-data' // A tail harness row that is its own image unit and no roster member leaves every earlier unit as it was. function staysInTail(harness: NativeChatMessage): boolean { diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-projection.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-projection.ts index e7b598ab6c2..af6785bfc4d 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-projection.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-projection.ts @@ -12,7 +12,7 @@ import { import { finishMobileNativeChatMessages, foldMobileNativeChatMessages -} from '../mobile-native-chat-render-data' +} from './mobile-native-chat-render-data' import { prepareMobileNativeChatSources, type MobileNativeChatProjectionSource diff --git a/mobile/src/session/native-chat/mobile-native-chat-reading-hold.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-reading-hold.ts similarity index 97% rename from mobile/src/session/native-chat/mobile-native-chat-reading-hold.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-reading-hold.ts index 9f741f237b5..e865435de65 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-reading-hold.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-reading-hold.ts @@ -1,4 +1,4 @@ -import { visible } from './mobile-native-chat-lower-geometry' +import { visible } from '../mobile-native-chat-lower-geometry' export function createMobileNativeChatReadingHold(viewport: HTMLElement, content: HTMLElement) { let hold: { element: HTMLElement; offset: number } | null = null diff --git a/mobile/src/session/mobile-native-chat-reading-place.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-reading-place.ts similarity index 96% rename from mobile/src/session/mobile-native-chat-reading-place.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-reading-place.ts index 14e486b65a5..705e2fad33c 100644 --- a/mobile/src/session/mobile-native-chat-reading-place.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-reading-place.ts @@ -1,9 +1,9 @@ -import type { MobileNativeChatRowKeys } from './native-chat/transcript/mobile-native-chat-row-keys' +import type { MobileNativeChatRowKeys } from './mobile-native-chat-row-keys' import { mobileNativeChatReadingParts, mobileNativeChatScrollRootKey -} from './native-chat/transcript/mobile-native-chat-scroll-root-handle' -import type { MobileNativeChatScrollRoots } from './native-chat/transcript/mobile-native-chat-scroll-roots' +} from './mobile-native-chat-scroll-root-handle' +import type { MobileNativeChatScrollRoots } from './mobile-native-chat-scroll-roots' type Box = { top: number; bottom: number } diff --git a/mobile/src/session/native-chat/mobile-native-chat-render-data.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-render-data.ts similarity index 77% rename from mobile/src/session/native-chat/mobile-native-chat-render-data.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-render-data.ts index 9065b441ab8..bd71aadc79d 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-render-data.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-render-data.ts @@ -1,25 +1,25 @@ -import { formatAgentTypeLabel } from '../../../../src/shared/agent-status/presentation/agent-type-label' -import { describeLoadError } from '../../components/load-error-description' -import type { NativeChatEmptyStateCopy } from '../../../../src/shared/native-chat/native-chat-empty-state' -import { isRootAgentJournalItem } from '../../../../src/shared/agent-session/journal/agent-session-journal-producer' -import { stripNoiseMessages } from '../../../../src/shared/native-chat/native-chat-noise' -import { nativeChatStreamingMessage } from '../../../../src/shared/native-chat/native-chat-streaming' -import { isWordlessProviderFrameMessage } from '../../../../src/shared/native-chat/native-chat-provider-frame-summary' +import { formatAgentTypeLabel } from '../../../../../src/shared/agent-status/presentation/agent-type-label' +import { describeLoadError } from '../../../components/load-error-description' +import type { NativeChatEmptyStateCopy } from '../../../../../src/shared/native-chat/native-chat-empty-state' +import { isRootAgentJournalItem } from '../../../../../src/shared/agent-session/journal/agent-session-journal-producer' +import { stripNoiseMessages } from '../../../../../src/shared/native-chat/native-chat-noise' +import { nativeChatStreamingMessage } from '../../../../../src/shared/native-chat/native-chat-streaming' +import { isWordlessProviderFrameMessage } from '../../../../../src/shared/native-chat/native-chat-provider-frame-summary' import { foldToolMessages, isUndrawnProviderFrameMessage -} from '../../../../src/shared/native-chat/tools/native-chat-tool-fold' -import { projectNativeChatTaskListFrames } from '../../../../src/shared/native-chat/input/native-chat-task-list-frames' -import { foldSubagentItems } from '../../../../src/shared/native-chat/transcript/native-chat-subagent-items' -import { prepareNativeChatMessages } from '../../../../src/shared/native-chat/transcript/native-chat-message-preparation' +} from '../../../../../src/shared/native-chat/tools/native-chat-tool-fold' +import { projectNativeChatTaskListFrames } from '../../../../../src/shared/native-chat/input/native-chat-task-list-frames' +import { foldSubagentItems } from '../../../../../src/shared/native-chat/transcript/native-chat-subagent-items' +import { prepareNativeChatMessages } from '../../../../../src/shared/native-chat/transcript/native-chat-message-preparation' import { isImageRefBlock, type NativeChatMessage -} from '../../../../src/shared/native-chat/native-chat-types' -import type { AgentType } from '../../../../src/shared/agent-status/contracts/agent-status-types' -import type { MobileNativeChatStatus } from './mobile-native-chat-session-status' -import { orderNativeChatDisplayMessages } from '../../../../src/shared/native-chat/transcript/native-chat-message-order' -import { translate } from '../../i18n/mobile-i18n' +} from '../../../../../src/shared/native-chat/native-chat-types' +import type { AgentType } from '../../../../../src/shared/agent-status/contracts/agent-status-types' +import type { MobileNativeChatStatus } from '../mobile-native-chat-session-status' +import { orderNativeChatDisplayMessages } from '../../../../../src/shared/native-chat/transcript/native-chat-message-order' +import { translate } from '../../../i18n/mobile-i18n' function mobileNativeChatEmptyStateCopy( kind: 'notFound' | 'empty' | 'error', diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-row-keys.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-row-keys.ts index 04edddf3975..f9ed5e011dd 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-row-keys.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-row-keys.ts @@ -5,7 +5,7 @@ import { updateMobileNativeChatRunDescriptors, type MobileNativeChatRunHistory, type MobileNativeChatRunPresentation -} from '../mobile-native-chat-run-descriptors' +} from './mobile-native-chat-run-descriptors' import { isSubagentGroupBlock, isToolResultBlock, diff --git a/mobile/src/session/native-chat/mobile-native-chat-run-descriptors.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-run-descriptors.ts similarity index 86% rename from mobile/src/session/native-chat/mobile-native-chat-run-descriptors.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-run-descriptors.ts index 002948aceb0..9eff1ad301e 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-run-descriptors.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-run-descriptors.ts @@ -1,10 +1,10 @@ -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import type { NativeChatResolvedPrompt } from '../../../../src/shared/native-chat/native-chat-resolution-receipt' -import { splitNativeChatBlocks } from '../../../../src/shared/native-chat/tools/native-chat-tool-fold' -import { nativeChatWorkRunEntries } from '../../../../src/shared/native-chat/native-chat-work-run' -import { nativeChatAskRunBlocks } from '../../../../src/shared/native-chat/input/native-chat-ask-row' -import type { MobileNativeChatWorkRuns } from './transcript/use-mobile-native-chat-transcript-rows' -import type { MobileNativeChatRowSources } from './transcript/mobile-native-chat-row-keys' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' +import type { NativeChatResolvedPrompt } from '../../../../../src/shared/native-chat/native-chat-resolution-receipt' +import { splitNativeChatBlocks } from '../../../../../src/shared/native-chat/tools/native-chat-tool-fold' +import { nativeChatWorkRunEntries } from '../../../../../src/shared/native-chat/native-chat-work-run' +import { nativeChatAskRunBlocks } from '../../../../../src/shared/native-chat/input/native-chat-ask-row' +import type { MobileNativeChatWorkRuns } from './use-mobile-native-chat-transcript-rows' +import type { MobileNativeChatRowSources } from './mobile-native-chat-row-keys' export type MobileNativeChatRunPresentation = { rows: readonly NativeChatMessage[] diff --git a/mobile/src/session/mobile-native-chat-scroll-anchoring.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-anchoring.ts similarity index 100% rename from mobile/src/session/mobile-native-chat-scroll-anchoring.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-anchoring.ts diff --git a/mobile/src/session/native-chat/mobile-native-chat-scroll-gesture.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-gesture.ts similarity index 100% rename from mobile/src/session/native-chat/mobile-native-chat-scroll-gesture.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-gesture.ts diff --git a/mobile/src/session/mobile-native-chat-scroll-motion.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-motion.ts similarity index 100% rename from mobile/src/session/mobile-native-chat-scroll-motion.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-motion.ts diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-ownership.test.tsx b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-ownership.test.tsx index bae41606a45..f2796e90993 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-ownership.test.tsx +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-ownership.test.tsx @@ -1,479 +1,6 @@ // @vitest-environment happy-dom -import { - act, - createElement as h, - Fragment, - startTransition, - Suspense, - useLayoutEffect, - useMemo, - type ReactNode -} from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, expect, it, vi } from 'vitest' -import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' -import { foldToolMessages } from '../../../../../src/shared/native-chat/tools/native-chat-tool-fold' -import { MobileNativeChatMessage } from './MobileNativeChatMessage' -import { MobileNativeChatScrollRootsBoundary } from '../../MobileNativeChatScrollRootsBoundary' -import { MobileNativeChatScrollMotion } from '../../mobile-native-chat-scroll-motion' -import { - mobileNativeChatScrollFrame, - mobileNativeChatScrollRootKey, - mobileNativeChatReadingParts, - mobileNativeChatVisible -} from './mobile-native-chat-scroll-root-handle' +import { expect, it } from 'vitest' import { MobileNativeChatScrollRoots } from './mobile-native-chat-scroll-roots' -import { - MobileNativeChatDisclosureContext, - useMobileNativeChatDisclosures, - type MobileNativeChatDisclosureStore -} from './mobile-native-chat-disclosure-store' -import { useMobileNativeChatRowKeys } from './mobile-native-chat-row-keys' -import { useMobileNativeChatTurnDisclosure } from './use-mobile-native-chat-turn-disclosure' - -vi.mock('react-native', async () => { - const React = await import('react') - const View = (p: { - children?: ReactNode - testID?: string - onPress?: () => void - accessibilityLiveRegion?: string - }) => - React.createElement( - 'div', - { 'data-testid': p.testID, onClick: p.onPress, 'data-live': p.accessibilityLiveRegion }, - p.children - ) - const ScrollView = React.forwardRef((p: { children?: ReactNode }, ref) => { - const node = React.useRef(null) - React.useImperativeHandle(ref, () => ({ - getScrollableNode: () => node.current, - getInnerViewNode: () => node.current?.firstElementChild - })) - return React.createElement( - 'div', - { ref: node, className: 'viewport' }, - React.createElement('div', null, p.children) - ) - }) - const animation = () => ({ start() {}, stop() {} }) - return { - AppState: { currentState: 'active', addEventListener: () => ({ remove: () => {} }) }, - View, - Text: View, - Pressable: View, - ScrollView, - Image: View, - Platform: { OS: 'web' }, - StyleSheet: { create: (s: unknown) => s, hairlineWidth: 1 }, - useWindowDimensions: () => ({ width: 400, height: 800 }), - Animated: { - Text: View, - Value: class { - setValue() {} - }, - loop: animation, - sequence: animation, - timing: animation - } - } -}) -vi.mock('lucide-react-native', () => - Object.fromEntries( - 'ArrowUp Bot Brain Check ChevronDown ChevronRight Copy Eye FileMinus2 FilePen FilePlus2 Folder Globe ListChecks MessageSquareMore Pencil Plug Search SquareChevronRight SquareTerminal Wrench Circle CircleCheck CircleDot' - .split(' ') - .map((key) => [key, () => null]) - ) -) -vi.mock('../../../components/MobileMarkdown', () => ({ MobileMarkdown: () => null })) -vi.mock('../../../components/MobileSourceCopyButton', () => ({ - MobileSourceCopyButton: () => null -})) -vi.mock('../../../platform/external-link', () => ({ openExternalLink: vi.fn() })) -vi.mock('../../../components/inline-text-selection', () => ({ INLINE_TEXT_SELECTION: false })) - -let renderer: Root | undefined -let container: HTMLDivElement -let page: HTMLElement -let frameShift = 0 -let roots: MobileNativeChatScrollRoots -let owners: (string | undefined)[] -let projected: (string | undefined)[] -let store: MobileNativeChatDisclosureStore -const abandoned = new Promise(() => {}) -const msg = ( - id: string, - blocks: NativeChatMessage['blocks'], - role: NativeChatMessage['role'] = 'assistant' -): NativeChatMessage => ({ id, blocks, role, timestamp: null, source: 'transcript' }) -const a = msg( - 'a', - Array.from({ length: 6 }, (_, i) => ({ - type: 'tool-call', - callId: `c${i}`, - name: 'Bash', - input: { command: `command ${i} ${'argument '.repeat(20)}` } - })) -) -const thought = msg('r', [{ type: 'text', text: 'reason' }], 'reasoning') -const user = msg('u', [{ type: 'text', text: 'go' }], 'user') -const b = msg('b', [{ type: 'tool-call', callId: 'b', name: 'Bash', input: { command: 'b' } }]) -function reader(node: Element, top: number) { - node.dispatchEvent(new Event('touchstart')) - node.scrollTop = top - node.dispatchEvent(new Event('scroll')) - node.dispatchEvent(new Event('touchend')) -} -function members(root: Element): Element[] { - return Array.from(root.querySelectorAll('[data-testid^="chat-member:"]')).filter( - (node) => !mobileNativeChatScrollFrame(node, root) - ) -} -function rect(node: Element): DOMRect { - const viewport = node.closest('[data-chat-scroll-root]') - let top = 0 - let height = 1000 - if (node === page) { - return DOMRect.fromRect({ y: 100, height: 200, width: 400 }) - } - if (viewport === node) { - const parent = node.parentElement?.closest('[data-chat-scroll-root]') - top = parent - ? rect(node.parentElement!.closest('[data-testid^="chat-member:"]') ?? parent).top + 20 - : 40 + frameShift - page.scrollTop - height = 160 - } else if (viewport) { - const member = node.closest('[data-testid^="chat-member:"]') - top = - rect(viewport).top + - Math.max(0, members(viewport).indexOf(member ?? node)) * 60 - - viewport.scrollTop - height = 60 - } else if (node.closest('[data-live], [data-testid="chat-row:u"]')) { - top = -page.scrollTop - height = 20 - } else { - top = -page.scrollTop - } - return DOMRect.fromRect({ y: top, height, width: 400 }) -} -function Shift({ value }: { value: number }): null { - useLayoutEffect(() => { - frameShift = value - }, [value]) - return null -} -function Chat({ - rows, - open = true, - shown = true, - scope = 'chat', - shift = 0, - suspend = false, - structured = true, - working = false -}: { - rows: NativeChatMessage[] - open?: boolean - shown?: boolean - scope?: string - shift?: number - suspend?: boolean - structured?: boolean - working?: boolean -}) { - const current = useMobileNativeChatDisclosures(scope) - const disclosures = useMemo( - () => - open - ? { - ...current, - read: (key: string) => - current.read(key) ?? (/^(run|line|diff):/.test(key) ? true : undefined) - } - : current, - [current, open] - ) - const drawn = useMemo(() => (structured ? rows : foldToolMessages(rows)), [rows, structured]) - const turns = useMobileNativeChatTurnDisclosure({ - messages: drawn, - enabled: structured, - isWorking: working, - scopeKey: scope - }) - const keys = useMobileNativeChatRowKeys(drawn, rows, scope, turns.presentation) - useLayoutEffect(() => { - store = current - roots = current.roots - owners = drawn.map((row) => keys.runOwners.get(row.id)) - }) - if (suspend) { - projected = drawn.map((row) => keys.runOwners.get(row.id)) - throw abandoned - } - return h( - MobileNativeChatDisclosureContext.Provider, - { value: disclosures }, - h( - MobileNativeChatScrollRootsBoundary, - { - rowKeys: keys, - roots: current.roots, - runPlacements: keys.runPlacements, - layoutKey: '1' - }, - h(Shift, { value: shift }), - h( - Fragment, - { key: scope }, - shown && - drawn.map((message, i) => - h(MobileNativeChatMessage, { - key: keys.keyOf(message), - message, - rowKey: keys.keyOf(message), - ...turns.resolveRow(i, message), - ...(open ? { foldedAway: false } : {}), - runOwner: keys.runOwners.get(message.id), - structuredActivityUi: structured - }) - ) - ) - ) - ) -} -function mount(structured = true) { - vi.useFakeTimers() - vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) - vi.stubGlobal('navigator', { userAgent: 'AppleWebKit Safari' }) - container = document.createElement('div') - page = document.createElement('main') - page.style.overflowY = 'auto' - document.body.append(page) - page.append(container) - vi.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(function (this: Element) { - return rect(this) - }) - vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockReturnValue(160) - vi.spyOn(Element.prototype, 'scrollHeight', 'get').mockImplementation(function (this: Element) { - return this.getAttribute('data-short') - ? 80 - : members(this).length * 60 + - 400 + - this.querySelectorAll('.viewport').length * 60 + - Number(this.getAttribute('data-growth') ?? 0) - }) - renderer = createRoot(container) - return (rows: NativeChatMessage[], rest: Partial[0]> = {}) => { - const render = () => - renderer!.render(h(Suspense, { fallback: null }, h(Chat, { rows, structured, ...rest }))) - act(() => (rest.suspend ? startTransition(render) : render())) - act(() => vi.advanceTimersByTime(20)) - } -} -afterEach(() => { - act(() => renderer?.unmount()) - page?.remove() - renderer = undefined - frameShift = 0 - vi.useRealTimers() - vi.restoreAllMocks() - vi.unstubAllGlobals() -}) - -it('keeps real structured runs and nested offsets under their own viewport across reheading', () => { - const draw = mount() - draw([user, a]) - const owner = owners[1] - const run = container.querySelector('[data-chat-scroll-root]') - if (!(run instanceof HTMLElement)) { - throw new Error('missing run viewport') - } - const detail = run.querySelector('[data-chat-scroll-root]') - if (!(detail instanceof HTMLElement)) { - throw new Error('missing command viewport') - } - const row = run.closest('[data-testid^="chat-row:"]')! - reader(run, 100) - reader(detail, 35) - act(() => vi.advanceTimersByTime(160)) - roots.restoreAll() - const clipped = run.querySelector('[data-testid="chat-member:call:c0"]')! - expect(mobileNativeChatVisible(clipped, page)).toBe(false) - expect(mobileNativeChatReadingParts(row)).toContain(run) - expect(mobileNativeChatReadingParts(row)).not.toContain(clipped) - const anchor = run.querySelector('[data-testid="chat-member:call:c3"]')! - const offset = rect(anchor).top - rect(run).top - draw([user, thought, a], { shift: 80 }) - expect(owners[1]).toBe(owner) - const restored = container.querySelector('[data-chat-scroll-root]')! - expect( - rect(restored.querySelector('[data-testid="chat-member:call:c3"]')!).top - rect(restored).top - ).toBe(offset) - expect(restored.querySelector('[data-chat-scroll-root]')?.scrollTop).toBe(35) - const top = restored.scrollTop - draw([user, thought, a], { shown: false, shift: 80 }) - draw([user, thought, a], { shift: 80 }) - expect(container.querySelector('[data-chat-scroll-root]')?.scrollTop).toBe(top) - const observer = { observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() } - roots.observeWith(observer) - expect(observer.observe).toHaveBeenCalledWith(container.querySelector('[data-chat-scroll-root]')) - const late = document.createElement('div') - const content = document.createElement('div') - late.append(content) - container.append(late) - late.setAttribute('data-short', 'yes') - store.runScroll.set('late', { top: 37, followsEnd: false, member: 'missing', offset: 10 }) - const stop = roots.register('late', late, content, 'late', 'late', false) - expect(late.scrollHeight).toBeLessThan(late.clientHeight) - expect(observer.observe).toHaveBeenCalledWith(content) - roots.capture(new Set(), false) - expect(store.runScroll.get('late')?.top).toBe(37) - late.removeAttribute('data-short') - roots.restoreAll() - expect(late.scrollTop).toBe(37) - expect(store.runScroll.get('late')?.member).toBeUndefined() - const removed = vi.spyOn(late, 'removeEventListener') - const replacement = document.createElement('div') - late.replaceChildren(replacement) - const replaced = roots.register('late', late, replacement, 'late', 'late', false) - observer.unobserve.mockClear() - stop() - expect(roots.frame('late')).toBe(late) - expect(observer.unobserve.mock.calls.some(([node]) => node === late)).toBe(false) - expect(observer.unobserve.mock.calls.some(([node]) => node === replacement)).toBe(false) - late.remove() - roots.restoreAll() - replaced() - expect(roots.frame('late')).toBeUndefined() - expect(observer.unobserve.mock.calls.some(([node]) => node === content)).toBe(true) - expect(removed).toHaveBeenCalledWith('scroll', expect.any(Function)) -}) - -it.each([true, false])( - 'issues fresh displayed owners through split and merge instead of reviving retired choices (structured=%s)', - (structured) => { - const draw = mount(structured) - draw([user, a, b], { working: true }) - const original = owners[1] - const oldKey = `run:${original}` - act(() => store.write(oldKey, false)) - store.runScroll.set(oldKey, { top: 111, followsEnd: false }) - const splitRows = [ - user, - a, - msg('break', [{ type: 'text', text: 'answer' }], structured ? 'assistant' : 'user'), - b - ] - const registry = roots - draw(splitRows, { suspend: true, working: true }) - expect(projected[1]).not.toBe(original) - expect(owners[1]).toBe(original) - expect(roots).toBe(registry) - expect(store.runScroll.get(oldKey)?.top).toBe(111) - const tentative = [...projected] - draw(splitRows, { working: true }) - const split = [owners[1], owners[3]] - expect(new Set([original, ...split]).size).toBe(3) - expect(owners).toEqual(tentative) - const reused = roots.frame(`run:${split[0]}`)! - reader(reused, 40) - draw([user, a, b]) - expect([original, ...split]).not.toContain(owners[1]) - expect(store.read(oldKey)).toBe(false) - expect(store.runScroll.get(oldKey)?.top).toBe(111) - expect(roots.frame(`run:${owners[1]}`)).toBe(reused) - expect(reused.scrollTop).toBe(40) - expect(store.runScroll.get(`run:${owners[1]}`)?.followsEnd).toBe(false) - } -) - -it('releases on close/scope change, and separates work-follow from reader chaining', async () => { - const draw = mount() - draw([user, a]) - const run = container.querySelector('[data-chat-scroll-root]')! - const detail = run.querySelector('[data-chat-scroll-root]')! - vi.stubGlobal('navigator', { userAgent: 'AppleWebKit Chrome' }) - const pageMotion = new MobileNativeChatScrollMotion() - const detach = pageMotion.attach(page) - pageMotion.reconciled(100, 1000) - reader(run, 100) - reader(detail, 35) - await vi.advanceTimersByTimeAsync(160) - reader(run, run.scrollHeight - run.clientHeight - 20) - await vi.advanceTimersByTimeAsync(160) - const parked = run.scrollTop - run.setAttribute('data-growth', '60') - roots.restoreAll() - expect(run.scrollTop).toBe(parked) - draw([user, a, b]) - expect(run.scrollTop).toBe(run.scrollHeight - run.clientHeight) - run.dispatchEvent(new Event('scroll')) - await vi.advanceTimersByTimeAsync(160) - expect(pageMotion.scrolled(100, 1000, () => false).by).toBe('none') - run.dispatchEvent(new WheelEvent('wheel', { deltaY: -10, bubbles: true })) - expect(pageMotion.scrolled(90, 1000, () => false).by).toBe('reader') - pageMotion.release() - act(() => container.querySelector('[data-live]')!.click()) - expect(run.hasAttribute('data-chat-scroll-root')).toBe(false) - draw([user, a], { scope: 'intermediate' }) - draw([user, a], { scope: 'other' }) - vi.stubGlobal('navigator', { userAgent: 'AppleWebKit Safari' }) - draw([user, a], { scope: 'live', open: false, working: true }) - act(() => container.querySelector('[data-live]')!.click()) - const key = `run:${owners[1]}` - const live = roots.frame(key)! - const end = live.scrollTop - expect(end).toBe(live.scrollHeight - live.clientHeight) - reader(live, end - 100) - draw([user, a], { scope: 'live', open: false, working: true, shift: 40 }) - expect(roots.frame(key)).toBe(live) - expect(live.scrollTop).toBe(end - 100) - expect(store.runScroll.get(key)?.followsEnd).toBe(false) - reader(live, live.scrollHeight - live.clientHeight - 20) - await vi.advanceTimersByTimeAsync(160) - draw([user, a, b], { scope: 'live', open: false, working: true, shift: 40 }) - expect(live.scrollTop).toBe(live.scrollHeight - live.clientHeight) - draw([user, a, b], { scope: 'live', open: false, working: true, shown: false, shift: 40 }) - store.runScroll.set(key, { top: 37, followsEnd: false }) - draw([user, a, b], { scope: 'live', open: false, working: true, shift: 40 }) - expect(roots.frame(key)?.scrollTop).toBe(37) - detach() - pageMotion.release() -}) - -it('keeps the actual changed task checklist through eviction, isolated by chat', () => { - const draw = mount() - const tasks = msg( - 'tasks', - [false, true].map((done) => ({ - type: 'tool-call', - callId: String(done), - name: 'TodoWrite', - input: { - todos: [ - { content: 'Remember', status: done ? 'completed' : 'pending', activeForm: 'Remembering' } - ] - } - })) - ) - draw([user, tasks]) - const count = () => container.textContent?.match(/Remember/g)?.length ?? 0 - const before = count() - const disclosure = Array.from(container.querySelectorAll('div')).findLast( - (node) => node.textContent === 'Full task list' - ) - if (!disclosure) { - throw new Error('missing changed-task disclosure') - } - act(() => disclosure.click()) - expect(count()).toBe(before + 1) - draw([user, tasks], { shown: false }) - draw([user, tasks]) - expect(count()).toBe(before + 1) - draw([user, tasks], { scope: 'different' }) - expect(count()).toBe(before) -}) it("forgets a closed tool line's nested command and output positions, not its neighbours'", () => { const at = { top: 30, followsEnd: false } @@ -485,86 +12,3 @@ it("forgets a closed tool line's nested command and output positions, not its ne new MobileNativeChatScrollRoots(memory, 10).forget('line:a') expect([...memory.keys()]).toEqual(['run:o', 'run:o/line:ab:command', 'diff:src/line:a.ts']) }) - -it.each([10, 100])( - 'bounds expanded notice scroll work and releases collapsed/unmounted roots (%i notices)', - (count) => { - const draw = mount() - const notices = Array.from({ length: count }, (_, i) => - msg( - `notice:${i}`, - [ - { - type: 'text', - text: `Diagnostic ${i}`, - tone: i % 2 === 0 ? 'warning' : 'error', - providerFrame: { - provider: i % 2 === 0 ? 'codex' : 'claude', - kind: - i % 2 === 0 ? 'notification:guardianWarning' : 'message:system:permission_denied', - payload: { head: '{}', byteLength: 2, digest: 'synthetic', truncated: false } - } - } - ], - 'system' - ) - ) - const register = vi.spyOn(MobileNativeChatScrollRoots.prototype, 'register') - draw(notices) - expect(register).not.toHaveBeenCalled() - const observer = { observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() } - roots.observeWith(observer) - const toggles = Array.from(container.querySelectorAll('div')).filter( - (node) => node.childElementCount === 0 && node.textContent?.includes('View details.') - ) - expect(toggles).toHaveLength(count) - act(() => toggles.forEach((toggle) => toggle.click())) - expect(register).toHaveBeenCalledTimes(count) - expect(observer.observe).toHaveBeenCalledTimes(2 * count) - const viewports = Array.from(container.querySelectorAll('[data-chat-scroll-root]')) - expect(viewports).toHaveLength(count) - const ids = viewports.map((node) => mobileNativeChatScrollRootKey(node)!) - expect(new Set(ids).size).toBe(count) - draw([...notices]) - viewports.forEach((node) => reader(node, 35)) - act(() => vi.advanceTimersByTime(160)) - - const memoryWrites = vi.spyOn(store.runScroll, 'set') - const queries = vi.spyOn(Element.prototype, 'querySelectorAll') - const geometry = vi.spyOn(Element.prototype, 'getBoundingClientRect') - const capture = vi.spyOn(roots, 'capture') - const restore = vi.spyOn(roots, 'restoreAll') - register.mockClear() - draw([...notices]) - expect(capture).toHaveBeenCalledOnce() - expect(restore).toHaveBeenCalledOnce() - expect(memoryWrites).toHaveBeenCalledTimes(2 * count) - expect(queries.mock.calls.length).toBeLessThanOrEqual(12 * count) - expect(geometry.mock.calls.length).toBeLessThanOrEqual(8 * count) - expect(register.mock.calls.length).toBeLessThanOrEqual(count) - expect(observer.observe).toHaveBeenCalledTimes(2 * count) - expect(viewports.map((node) => node.scrollTop)).toEqual(Array(count).fill(35)) - - act(() => toggles.slice(0, count / 2).forEach((toggle) => toggle.click())) - expect(observer.unobserve).toHaveBeenCalledTimes(count) - ids.forEach((id, i) => expect(roots.frame(id)).toBe(i < count / 2 ? undefined : viewports[i])) - draw(notices, { shown: false }) - expect(observer.unobserve).toHaveBeenCalledTimes(2 * count) - for (const node of viewports) { - expect(node.hasAttribute('data-chat-scroll-root')).toBe(false) - expect(mobileNativeChatScrollRootKey(node)).toBeUndefined() - expect(observer.unobserve).toHaveBeenCalledWith(node) - expect(observer.unobserve).toHaveBeenCalledWith(node.firstElementChild) - } - ids.forEach((id) => expect(roots.frame(id)).toBeUndefined()) - memoryWrites.mockClear() - queries.mockClear() - geometry.mockClear() - roots.capture(new Set(), false) - roots.restoreAll() - viewports.forEach((node) => reader(node, 50)) - expect(memoryWrites).not.toHaveBeenCalled() - expect(queries).not.toHaveBeenCalled() - expect(geometry).not.toHaveBeenCalled() - } -) diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-root-handle.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-root-handle.ts index 4ee32f3c66b..14b3901af0e 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-root-handle.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-root-handle.ts @@ -1,5 +1,5 @@ -import type { MobileNativeChatScrollMotion } from '../../mobile-native-chat-scroll-motion' -import type { MobileNativeChatScrollAnchoring } from '../../mobile-native-chat-scroll-anchoring' +import type { MobileNativeChatScrollMotion } from './mobile-native-chat-scroll-motion' +import type { MobileNativeChatScrollAnchoring } from './mobile-native-chat-scroll-anchoring' export type MobileNativeChatRootPosition = { top: number diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-roots.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-roots.ts index 7d612ef882a..1d46dcb42a5 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-roots.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-scroll-roots.ts @@ -1,8 +1,5 @@ -import { - MobileNativeChatScrollMotion, - scrollKeepsPlace -} from '../../mobile-native-chat-scroll-motion' -import { MobileNativeChatScrollAnchoring } from '../../mobile-native-chat-scroll-anchoring' +import { MobileNativeChatScrollMotion, scrollKeepsPlace } from './mobile-native-chat-scroll-motion' +import { MobileNativeChatScrollAnchoring } from './mobile-native-chat-scroll-anchoring' import { mobileNativeChatScrollRootAttribute as ROOT, mobileNativeChatScrollRootOwners as owners, diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-send-text-owner.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-send-text-owner.ts index 997c1a11062..a075b12f3ed 100644 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-send-text-owner.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-send-text-owner.ts @@ -1,5 +1,5 @@ -import type { MobileNativeChatSendIntent } from '../mobile-native-chat-send' -import type { MobileNativeChatSendOrigin } from '../mobile-native-chat-pending-echo' +import type { MobileNativeChatSendIntent } from '../composer/mobile-native-chat-send' +import type { MobileNativeChatSendOrigin } from './mobile-native-chat-pending-echo' type Restore = ( origin: MobileNativeChatSendOrigin, diff --git a/mobile/src/session/native-chat/mobile-native-chat-stream-frame.test.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-stream-frame.test.ts similarity index 91% rename from mobile/src/session/native-chat/mobile-native-chat-stream-frame.test.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-stream-frame.test.ts index 29a44951f12..59938201fe1 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-stream-frame.test.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-stream-frame.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it } from 'vitest' import { createNativeChatMerger, replaceList -} from '../../../../src/shared/native-chat/native-chat-merge' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' +} from '../../../../../src/shared/native-chat/native-chat-merge' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { applyMobileNativeChatStreamFrame } from './mobile-native-chat-stream-frame' function message(id: string): NativeChatMessage { diff --git a/mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-stream-frame.ts similarity index 91% rename from mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-stream-frame.ts index b80ec6ce347..bc38755de03 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-stream-frame.ts @@ -1,8 +1,8 @@ -import { applyAppend, replaceList } from '../../../../src/shared/native-chat/native-chat-merge' -import { translate } from '../../i18n/mobile-i18n' -import type { NativeChatMerger } from '../../../../src/shared/native-chat/native-chat-merge' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import type { NativeChatTranscriptFrame } from '../../../../src/shared/native-chat/native-chat-transcript-frames' +import { applyAppend, replaceList } from '../../../../../src/shared/native-chat/native-chat-merge' +import { translate } from '../../../i18n/mobile-i18n' +import type { NativeChatMerger } from '../../../../../src/shared/native-chat/native-chat-merge' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' +import type { NativeChatTranscriptFrame } from '../../../../../src/shared/native-chat/native-chat-transcript-frames' type SnapshotFrame = Extract diff --git a/mobile/src/session/native-chat/mobile-native-chat-transcript-cache.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-cache.ts similarity index 85% rename from mobile/src/session/native-chat/mobile-native-chat-transcript-cache.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-cache.ts index 7ea01a40f66..6a913f955b1 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-transcript-cache.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-cache.ts @@ -1,6 +1,6 @@ -import { setBoundedScopeCacheEntry } from '../../../../src/shared/native-chat/native-chat-scope-cache' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import type { NativeChatResumePoint } from '../../../../src/shared/rpc-contract/native-chat-params' +import { setBoundedScopeCacheEntry } from '../../../../../src/shared/native-chat/native-chat-scope-cache' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' +import type { NativeChatResumePoint } from '../../../../../src/shared/rpc-contract/native-chat-params' import { peekStoredChatTranscript } from './mobile-native-chat-transcript-store' const MAX_CACHED_TRANSCRIPTS = 8 diff --git a/mobile/src/session/native-chat/mobile-native-chat-transcript-encode.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-encode.ts similarity index 93% rename from mobile/src/session/native-chat/mobile-native-chat-transcript-encode.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-encode.ts index daeeb767d09..779de03d547 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-transcript-encode.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-encode.ts @@ -2,10 +2,10 @@ import { Gzip, strToU8 } from 'fflate' import { trimRetainedItems, mergeSubmissions -} from '../../../../src/shared/agent-session/view/structured-agent-session-item-retention' -import { agentSessionHasOlderTranscript } from '../../../../src/shared/agent-session/agent-session-context-clear' -import { DEVICE_STORE_MAX_VALUE_LENGTH } from '../../device-store/device-store-value-codec' -import { encodeBase64Bytes } from '../../transport/encryption/base64-byte-codec' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-item-retention' +import { agentSessionHasOlderTranscript } from '../../../../../src/shared/agent-session/agent-session-context-clear' +import { DEVICE_STORE_MAX_VALUE_LENGTH } from '../../../device-store/device-store-value-codec' +import { encodeBase64Bytes } from '../../../transport/encryption/base64-byte-codec' import { STORED_TRANSCRIPT_VERSION, type StoredChatTranscript, diff --git a/mobile/src/session/native-chat/mobile-native-chat-transcript-store.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-store.ts similarity index 89% rename from mobile/src/session/native-chat/mobile-native-chat-transcript-store.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-store.ts index b3c59746c44..fb091e1f10a 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-transcript-store.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-store.ts @@ -1,12 +1,15 @@ import { sha256 } from '@noble/hashes/sha256' import { bytesToHex } from '@noble/hashes/utils' -import { setBoundedScopeCacheEntry } from '../../../../src/shared/native-chat/native-chat-scope-cache' -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import { encodeNativeChatTranscriptIdentity } from '../../../../src/shared/native-chat/transcript/native-chat-transcript-retention' -import type { StructuredAgentSessionState } from '../../../../src/shared/agent-session/structured-agent-session-reducer' -import { decodeDeviceStoreValue } from '../../device-store/device-store-value-codec' -import { readPageDeviceStore } from '../../mobile-web-shell/bridge/page-device-store' -import { resolveMobileNativeChat, type MobileNativeChatTab } from './mobile-native-chat-eligibility' +import { setBoundedScopeCacheEntry } from '../../../../../src/shared/native-chat/native-chat-scope-cache' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' +import { encodeNativeChatTranscriptIdentity } from '../../../../../src/shared/native-chat/transcript/native-chat-transcript-retention' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import { decodeDeviceStoreValue } from '../../../device-store/device-store-value-codec' +import { readPageDeviceStore } from '../../../mobile-web-shell/bridge/page-device-store' +import { + resolveMobileNativeChat, + type MobileNativeChatTab +} from '../mobile-native-chat-eligibility' // Bump when a stored shape changes: other versions read as a miss. export const STORED_TRANSCRIPT_VERSION = 1 diff --git a/mobile/src/session/native-chat/mobile-native-chat-transcript-writer.ts b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-writer.ts similarity index 95% rename from mobile/src/session/native-chat/mobile-native-chat-transcript-writer.ts rename to mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-writer.ts index a500287955f..630532cc6f3 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-transcript-writer.ts +++ b/mobile/src/session/native-chat/transcript/mobile-native-chat-transcript-writer.ts @@ -1,16 +1,16 @@ -import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types' -import { onPageAppInactive } from '../../mobile-web-shell/bridge/bridge-app-inactive' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' +import { onPageAppInactive } from '../../../mobile-web-shell/bridge/bridge-app-inactive' import { EMPTY_STRUCTURED_AGENT_SESSION, reduceStructuredAgentSession, type StructuredAgentSessionAction, type StructuredAgentSessionState -} from '../../../../src/shared/agent-session/structured-agent-session-reducer' +} from '../../../../../src/shared/agent-session/structured-agent-session-reducer' import { MAX_RETAINED_ITEMS, MAX_RETAINED_OWN_ITEMS -} from '../../../../src/shared/agent-session/view/structured-agent-session-item-retention' -import { readPageDeviceStore } from '../../mobile-web-shell/bridge/page-device-store' +} from '../../../../../src/shared/agent-session/view/structured-agent-session-item-retention' +import { readPageDeviceStore } from '../../../mobile-web-shell/bridge/page-device-store' import { chatTranscriptStoreKey, peekStoredChatTranscript, diff --git a/mobile/src/session/native-chat/transcript/mobile-native-chat-turn-diff.test-fixture.tsx b/mobile/src/session/native-chat/transcript/mobile-native-chat-turn-diff.test-fixture.tsx deleted file mode 100644 index 31a7a6e29d1..00000000000 --- a/mobile/src/session/native-chat/transcript/mobile-native-chat-turn-diff.test-fixture.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import './mobile-agent-message-renderer.test-fixture' -import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' -import type { ReactTestInstance } from 'react-test-renderer' -import { vi } from 'vitest' -import { useMobileNativeChatTurnDisclosure } from './use-mobile-native-chat-turn-disclosure' -import { MobileNativeChatRowContent } from './MobileNativeChatRowItemSource' -import { findAllByHostType } from '../../../test-support/react-test-renderer-host-nodes' - -vi.mock('@react-native-async-storage/async-storage', () => ({ - default: { getItem: async () => null, setItem: async () => {}, removeItem: async () => {} } -})) - -export const messages: NativeChatMessage[] = [ - { - id: 'u', - role: 'user', - source: 'transcript', - timestamp: 1, - blocks: [{ type: 'text', text: 'Fix the greeting' }] - }, - { - id: 'edit', - role: 'assistant', - source: 'transcript', - timestamp: 2, - blocks: [ - { - type: 'tool-call', - name: 'Bash', - state: 'completed', - input: { command: 'inspect-diagnostic' } - }, - { type: 'tool-result', output: 'diagnostic-output' }, - { - type: 'tool-call', - name: 'Edit', - state: 'completed', - input: { - file_path: '/repo/greeting.ts', - old_string: 'helloOld()', - new_string: 'helloNew()' - } - }, - { type: 'tool-result', output: 'The file /repo/greeting.ts has been updated successfully.' }, - { type: 'tool-call', name: 'Diff', input: { path: 'status.ts' } }, - { type: 'tool-result', output: '@@ -1 +1 @@\n-statusOld()\n+statusNew()' } - ] - }, - { - id: 'answer', - role: 'assistant', - source: 'transcript', - timestamp: 3, - blocks: [{ type: 'text', text: 'Updated the greeting.' }] - } -] -const settled = new Map([['u', { startedAt: 1, workedSeconds: 2 }]]) -export function Transcript({ - rows = messages, - working = false -}: { - rows?: NativeChatMessage[] - working?: boolean -}) { - const turns = useMobileNativeChatTurnDisclosure({ - messages: rows, - enabled: true, - isWorking: working, - settledTurns: working ? undefined : settled, - scopeKey: 'test' - }) - return ( - <> - {turns.listMessages.map((message, index) => ( - - ))} - - ) -} -export function textIn(node: ReactTestInstance): string[] { - return findAllByHostType(node, 'Text').map((text) => String(text.children.join(''))) -} diff --git a/mobile/src/session/native-chat-receipt-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-receipt-copy.ts similarity index 89% rename from mobile/src/session/native-chat-receipt-copy.ts rename to mobile/src/session/native-chat/transcript/native-chat-receipt-copy.ts index 63093240a36..4dba5e0df58 100644 --- a/mobile/src/session/native-chat-receipt-copy.ts +++ b/mobile/src/session/native-chat/transcript/native-chat-receipt-copy.ts @@ -1,4 +1,4 @@ -import type { Translate } from '../i18n/mobile-i18n' +import type { Translate } from '../../../i18n/mobile-i18n' export function nativeChatReceiptCopy(t: Translate, device?: string) { return { diff --git a/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.test.ts b/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.test.ts deleted file mode 100644 index 3258cc79084..00000000000 --- a/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import type { SupportedUiLocale } from '../../../../../src/shared/ui-locale' -import { - joinNativeChatToolRunClauses, - nativeChatToolRunSentence -} from '../../../../../src/shared/native-chat/tools/native-chat-tool-run-sentence' -import { getMobileUiLocale } from '../../../i18n/mobile-i18n' -import { nativeChatToolRunWords } from './native-chat-tool-run-words' - -vi.mock('../../../i18n/mobile-i18n', () => ({ - getMobileUiLocale: vi.fn(() => 'en') -})) - -const joiners = { - pair: (first: string, second: string) => `${first} | ${second}`, - list: (leading: readonly string[], last: string) => [...leading, last].join(' | ') -} - -function mobileWords(locale: SupportedUiLocale) { - vi.mocked(getMobileUiLocale).mockReturnValue(locale) - return { ...nativeChatToolRunWords((_key, fallback) => fallback), ...joiners } -} - -describe('mobile tool-run sentence continuations', () => { - it('keeps German noun capitalization through the sentence formatter', () => { - const words = mobileWords('de') - expect( - nativeChatToolRunSentence( - [ - { type: 'tool-call', name: 'read', input: {} }, - { type: 'tool-call', name: 'Bash', input: { command: 'orca snapshot --page p1' } } - ], - {}, - { - ...words, - clause: (category) => (category === 'browser' ? 'Browser verwendet' : 'Datei gelesen') - } - ) - ).toBe('Datei gelesen | Browser verwendet') - }) - - it('uses Turkish casing for both dotted and dotless I in later clauses', () => { - expect(joinNativeChatToolRunClauses(['Başladı', 'İnceledi', 'Isındı'], mobileWords('tr'))).toBe( - 'Başladı | inceledi | ısındı' - ) - }) -}) diff --git a/mobile/src/session/native-chat/transcript/native-chat-turn-draw-order-scaling.test.ts b/mobile/src/session/native-chat/transcript/native-chat-turn-draw-order-scaling.test.ts deleted file mode 100644 index 33e322c37ea..00000000000 --- a/mobile/src/session/native-chat/transcript/native-chat-turn-draw-order-scaling.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { expect, it } from 'vitest' -import { nativeChatTurnDrawOrder } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-grouping' -import { nativeChatTurnMembership } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-membership' -import type { - AgentJournalItemBody, - AgentJournalRenderItem -} from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' - -it.each([10, 100])( - 'reads turn ownership linearly for %i queued openers and earlier-turn rows', - (count) => { - const messages = [ - { id: 'first', role: 'user' as const }, - ...Array.from({ length: count }, (_, index) => ({ - id: `queued-${index}`, - role: 'user' as const - })), - ...Array.from({ length: count }, (_, index) => ({ - id: `tail-${index}`, - role: 'assistant' as const - })), - ...Array.from({ length: count }, (_, index) => ({ - id: `answer-${index}`, - role: 'assistant' as const - })) - ] - const keys = [ - 'first', - ...Array.from({ length: count }, (_, index) => `queued-${index}`), - ...Array.from({ length: count }, () => 'first'), - ...Array.from({ length: count }, (_, index) => `queued-${index}`) - ] - const openers = new Set(keys.slice(0, count + 1)) - let reads = 0 - keys.forEach((key, index) => { - Object.defineProperty(keys, index, { - get: () => { - reads += 1 - return key - } - }) - }) - const order = nativeChatTurnDrawOrder(messages, keys, openers) - const visits = reads - expect(order?.map((index) => messages[index]?.id)).toEqual([ - 'first', - ...Array.from({ length: count }, (_, index) => `tail-${index}`), - ...Array.from({ length: count }, (_, index) => [`queued-${index}`, `answer-${index}`]).flat() - ]) - const journalItems: AgentJournalRenderItem[] = [] - const append = (itemId: string, body: AgentJournalItemBody, turnItemId: string) => { - const sequence = journalItems.length + 1 - journalItems.push({ - itemId, - body, - revision: 0, - sequence, - observedAt: sequence, - turnScope: { kind: 'turn', turnItemId } - }) - } - append('first', { kind: 'message', role: 'user', blocks: [] }, 'turn-first') - append( - 'turn-first', - { kind: 'turn', turnId: 'first', state: 'completed', userItemId: 'first' }, - 'turn-first' - ) - for (let index = 0; index < count; index += 1) { - append(`queued-${index}`, { kind: 'message', role: 'user', blocks: [] }, `turn-${index}`) - } - for (let index = 0; index < count; index += 1) { - append(`tail-${index}`, { kind: 'message', role: 'assistant', blocks: [] }, 'turn-first') - } - for (let index = 0; index < count; index += 1) { - append( - `turn-${index}`, - { kind: 'turn', turnId: `t${index}`, state: 'completed', userItemId: `queued-${index}` }, - `turn-${index}` - ) - append(`answer-${index}`, { kind: 'message', role: 'assistant', blocks: [] }, `turn-${index}`) - } - const membership = nativeChatTurnMembership(messages, { items: journalItems, submissions: [] }) - expect(membership.turnKeys).toEqual(keys) - expect(membership.drawOrder).toEqual(order) - expect(visits).toBeLessThanOrEqual(messages.length * 4) - } -) diff --git a/mobile/src/session/native-chat/native-chat-turn-status-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-turn-status-copy.ts similarity index 94% rename from mobile/src/session/native-chat/native-chat-turn-status-copy.ts rename to mobile/src/session/native-chat/transcript/native-chat-turn-status-copy.ts index 4f30ef74bab..27b4d095a10 100644 --- a/mobile/src/session/native-chat/native-chat-turn-status-copy.ts +++ b/mobile/src/session/native-chat/transcript/native-chat-turn-status-copy.ts @@ -1,8 +1,8 @@ -import type { Translate } from '../../i18n/mobile-i18n' +import type { Translate } from '../../../i18n/mobile-i18n' import { describeNativeChatActiveTurnLabel, describeNativeChatTurnStatus -} from '../../../../src/shared/native-chat/transcript/native-chat-turn-status' +} from '../../../../../src/shared/native-chat/transcript/native-chat-turn-status' export function formatMobileNativeChatDuration(seconds: number, t: Translate): string { const totalSeconds = Number.isFinite(seconds) ? Math.max(0, Math.floor(seconds)) : 0 diff --git a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-detail.ts b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-detail.ts index 12fcaca7552..a3fb9ec98f9 100644 --- a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-detail.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-detail.ts @@ -4,7 +4,7 @@ import { type NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { projectStructuredItemToNativeChat } from '../../../../../src/shared/agent-session/view/structured-agent-session-projection' -import { readMobileNativeChatItem } from '../mobile-native-chat-item-read' +import { readMobileNativeChatItem } from './mobile-native-chat-item-read' import type { MobileNativeChatVisualSource } from '../visual/mobile-native-chat-visual-read' export type MobileNativeChatMessageDetail = { diff --git a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.ts b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.ts index 0740f24ceec..c3c2803f169 100644 --- a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-message-send.ts @@ -8,11 +8,11 @@ import { typeMobileNativeChatCommandWithOutcome, type MobileNativeChatSendIntent, type MobileNativeChatSendOutcome -} from '../mobile-native-chat-send' +} from '../composer/mobile-native-chat-send' import type { CatalogCommandDelivery } from '../../../../../src/shared/agent-session/options/agent-session-option-catalog-types' import { isSlashCommandDraft } from '../../../../../src/shared/native-chat/input/native-chat-slash-commands' -import { healMobileNativeChatStaleInput } from '../mobile-native-chat-stale-input' -import { classifyMobileNativeChatSend } from '../mobile-native-chat-send-classification' +import { healMobileNativeChatStaleInput } from '../composer/mobile-native-chat-stale-input' +import { classifyMobileNativeChatSend } from '../composer/mobile-native-chat-send-classification' import { readMobileNativeChatSkillOrigin } from '../composer/mobile-native-chat-draft-store' import { nativeChatSendTextOwner } from './mobile-native-chat-send-text-owner' import { @@ -22,12 +22,12 @@ import { import { acquireMobileNativeChatTerminalWrite, releaseMobileNativeChatTerminalWrite -} from '../mobile-native-chat-terminal-write-lock' -import type { MobileNativeChatSendOrigin } from '../mobile-native-chat-pending-echo' +} from '../composer/mobile-native-chat-terminal-write-lock' +import type { MobileNativeChatSendOrigin } from './mobile-native-chat-pending-echo' import { UNCONFIRMED_SEND_MESSAGE, UNCONFIRMED_SEND_RESTORED_MESSAGE -} from '../use-mobile-native-chat-unconfirmed-sends' +} from '../composer/use-mobile-native-chat-unconfirmed-sends' import type { MobileNativeChatLaunchDraftSeed } from '../composer/use-mobile-native-chat-launch-draft-seed' import { buildNativeChatPasteBytes } from '../../../../../src/shared/native-chat/input/native-chat-paste-bytes' diff --git a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-paced-text.ts b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-paced-text.ts index b6067a1d7f7..e20e514a785 100644 --- a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-paced-text.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-paced-text.ts @@ -13,7 +13,7 @@ import { type NativeChatTextRevealFrames } from '../../../../../src/shared/native-chat/transcript/native-chat-text-reveal' import { MobileNativeChatReplyRevealsContext } from './mobile-native-chat-reply-reveals' -import { MobileNativeChatGrowthHoldContext } from '../../mobile-native-chat-growth-hold' +import { MobileNativeChatGrowthHoldContext } from './mobile-native-chat-growth-hold' /** Phones spread each arrival over less time than desktop, so new words show sooner. */ const REVEAL_MS = 180 diff --git a/mobile/src/session/use-mobile-native-chat-terminal-turn.ts b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-terminal-turn.ts similarity index 86% rename from mobile/src/session/use-mobile-native-chat-terminal-turn.ts rename to mobile/src/session/native-chat/transcript/use-mobile-native-chat-terminal-turn.ts index 73f07177b30..2fda3b75901 100644 --- a/mobile/src/session/use-mobile-native-chat-terminal-turn.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-terminal-turn.ts @@ -1,13 +1,13 @@ import { useCallback, useMemo, useState } from 'react' -import type { AgentStatusEntry } from '../../../src/shared/agent-status/contracts/agent-status-types' -import type { NativeChatMessage } from '../../../src/shared/native-chat/native-chat-types' -import type { NativeChatSettledTurns } from '../../../src/shared/native-chat/transcript/native-chat-turn-status' +import type { AgentStatusEntry } from '../../../../../src/shared/agent-status/contracts/agent-status-types' +import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' +import type { NativeChatSettledTurns } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-status' import { mergeNativeChatLiveSession, nativeChatHookHasWorkingSubagents, resolveNativeChatHookState -} from '../../../src/shared/native-chat/native-chat-live-status' -import { shouldClearNativeChatWorkingSuppression } from '../../../src/shared/native-chat/native-chat-working-suppression' +} from '../../../../../src/shared/native-chat/native-chat-live-status' +import { shouldClearNativeChatWorkingSuppression } from '../../../../../src/shared/native-chat/native-chat-working-suppression' import { nativeChatHookLatestTurnWorkedSeconds, nativeChatHookTurnStartedAt, @@ -16,16 +16,16 @@ import { nativeChatTranscriptSettledTurns, resolveNativeChatTerminalTurn, type NativeChatTerminalTurn -} from '../../../src/shared/native-chat/native-chat-terminal-turn' -import { deriveNativeChatStreamingText } from '../../../src/shared/native-chat/native-chat-streaming' +} from '../../../../../src/shared/native-chat/native-chat-terminal-turn' +import { deriveNativeChatStreamingText } from '../../../../../src/shared/native-chat/native-chat-streaming' import { useThrottledLatestValue } from './use-throttled-latest-value' -import type { MobileNativeChatSession } from './native-chat/use-mobile-native-chat-session' -import type { MobileNativeChatResolution } from './native-chat/mobile-native-chat-eligibility' +import type { MobileNativeChatSession } from '../use-mobile-native-chat-session' +import type { MobileNativeChatResolution } from '../mobile-native-chat-eligibility' import { pendingSendsAsMessages, type NativeChatPendingSend -} from '../../../src/shared/native-chat/transcript/native-chat-pending-sends' +} from '../../../../../src/shared/native-chat/transcript/native-chat-pending-sends' const NO_MESSAGES: NativeChatMessage[] = [] diff --git a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-transient-data.ts b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-transient-data.ts index a99c895969f..359db10fe05 100644 --- a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-transient-data.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-transient-data.ts @@ -2,7 +2,7 @@ import { useState } from 'react' import { buildMobileNativeChatTransientData, type MobileNativeChatPendingItem -} from '../mobile-native-chat-render-data' +} from './mobile-native-chat-render-data' import type { NativeChatMessage } from '../../../../../src/shared/native-chat/native-chat-types' import { orderNativeChatDisplayMessages } from '../../../../../src/shared/native-chat/transcript/native-chat-message-order' diff --git a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-turn-disclosure.ts b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-turn-disclosure.ts index 2ca97644e53..097d5428f36 100644 --- a/mobile/src/session/native-chat/transcript/use-mobile-native-chat-turn-disclosure.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-native-chat-turn-disclosure.ts @@ -23,7 +23,7 @@ import { nativeChatMessagesWaitingBehindLiveTurn } from '../../../../../src/shar import { NATIVE_CHAT_STREAMING_ID } from '../../../../../src/shared/native-chat/native-chat-streaming' import type { NativeChatResolvedPrompt } from '../../../../../src/shared/native-chat/native-chat-resolution-receipt' import { useMobileNativeChatTranscriptRows } from './use-mobile-native-chat-transcript-rows' -import type { MobileNativeChatRunPresentation } from '../mobile-native-chat-run-descriptors' +import type { MobileNativeChatRunPresentation } from './mobile-native-chat-run-descriptors' import { nativeChatRowsInDrawOrder, nativeChatTurnBarRows diff --git a/mobile/src/session/use-mobile-older-page-loader.ts b/mobile/src/session/native-chat/transcript/use-mobile-older-page-loader.ts similarity index 96% rename from mobile/src/session/use-mobile-older-page-loader.ts rename to mobile/src/session/native-chat/transcript/use-mobile-older-page-loader.ts index 7ac4a25ab1b..41c0615413b 100644 --- a/mobile/src/session/use-mobile-older-page-loader.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-older-page-loader.ts @@ -1,5 +1,5 @@ import { useCallback, useRef, useState } from 'react' -import { RPC_REQUEST_MIN_ACK_MS } from '../transport/rpc/rpc-request-budget' +import { RPC_REQUEST_MIN_ACK_MS } from '../../../transport/rpc/rpc-request-budget' /** One tap's whole wait, connect included, shared by every page it reads. */ export const OLDER_PAGE_BUDGET_MS = 15_000 diff --git a/mobile/src/session/use-mobile-structured-agent-turn-timing.ts b/mobile/src/session/native-chat/transcript/use-mobile-structured-agent-turn-timing.ts similarity index 67% rename from mobile/src/session/use-mobile-structured-agent-turn-timing.ts rename to mobile/src/session/native-chat/transcript/use-mobile-structured-agent-turn-timing.ts index 1dddcdde3b5..e010ef7b5ca 100644 --- a/mobile/src/session/use-mobile-structured-agent-turn-timing.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-structured-agent-turn-timing.ts @@ -2,16 +2,16 @@ import { useMemo, useState } from 'react' import type { AgentJournalRenderItem, AgentJournalSubmission -} from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import type { NativeChatSettledTurns } from '../../../src/shared/native-chat/transcript/native-chat-turn-status' -import type { AgentSessionLatestTurn } from '../../../src/shared/agent-session/agent-session-wire' -import type { NativeChatTurnJournal } from '../../../src/shared/native-chat/transcript/native-chat-turn-membership' -import type { StructuredAgentHostClock } from '../../../src/shared/agent-session/structured-agent-session-reducer' -import { selectStructuredAgentTurnBars } from '../../../src/shared/agent-session/view/structured-agent-session-turn-timing' +} from '../../../../../src/shared/agent-session/journal/agent-session-journal-types' +import type { NativeChatSettledTurns } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-status' +import type { AgentSessionLatestTurn } from '../../../../../src/shared/agent-session/agent-session-wire' +import type { NativeChatTurnJournal } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-membership' +import type { StructuredAgentHostClock } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import { selectStructuredAgentTurnBars } from '../../../../../src/shared/agent-session/view/structured-agent-session-turn-timing' import { stepStructuredAgentTurnClock, type StructuredAgentTurnClockLatch -} from '../../../src/shared/structured-agent-turn-clock-anchor' +} from '../../../../../src/shared/structured-agent-turn-clock-anchor' // Use journal durations and latch the host-to-phone start conversion once per turn so counters remain skew-free. export function useMobileStructuredAgentTurnTiming( diff --git a/mobile/src/session/use-mobile-structured-transcript.ts b/mobile/src/session/native-chat/transcript/use-mobile-structured-transcript.ts similarity index 76% rename from mobile/src/session/use-mobile-structured-transcript.ts rename to mobile/src/session/native-chat/transcript/use-mobile-structured-transcript.ts index 4d1e1745023..8023c0336f5 100644 --- a/mobile/src/session/use-mobile-structured-transcript.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-structured-transcript.ts @@ -3,15 +3,15 @@ import { agentSessionContextClearOutstanding, agentSessionTranscriptRows, latestAgentSessionContextClearSequence -} from '../../../src/shared/agent-session/agent-session-context-clear' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' -import type { StructuredAgentSessionOptimisticMessage } from '../../../src/shared/agent-session/view/structured-agent-session-message-projection' -import { createStructuredAgentSessionMessageProjection } from '../../../src/shared/agent-session/view/structured-agent-session-message-stream' -import { tuiAgentDisplayName } from '../../../src/shared/agent-launch/catalog/tui-agent-display-names' -import { withNativeChatCutTurnNotices } from '../../../src/shared/native-chat/transcript/native-chat-cut-turn-notice' -import { omitNativeChatThreadGoalRows } from '../../../src/shared/native-chat/transcript/native-chat-thread-goal-rows' -import { createStructuredQuestionProjection } from '../../../src/shared/structured-agent-question-projection' -import { projectStructuredAgentStartItem } from '../../../src/shared/structured-agent-start-projection' +} from '../../../../../src/shared/agent-session/agent-session-context-clear' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import type { StructuredAgentSessionOptimisticMessage } from '../../../../../src/shared/agent-session/view/structured-agent-session-message-projection' +import { createStructuredAgentSessionMessageProjection } from '../../../../../src/shared/agent-session/view/structured-agent-session-message-stream' +import { tuiAgentDisplayName } from '../../../../../src/shared/agent-launch/catalog/tui-agent-display-names' +import { withNativeChatCutTurnNotices } from '../../../../../src/shared/native-chat/transcript/native-chat-cut-turn-notice' +import { omitNativeChatThreadGoalRows } from '../../../../../src/shared/native-chat/transcript/native-chat-thread-goal-rows' +import { createStructuredQuestionProjection } from '../../../../../src/shared/structured-agent-question-projection' +import { projectStructuredAgentStartItem } from '../../../../../src/shared/structured-agent-start-projection' const mobileStructuredTranscript = createStructuredQuestionProjection( projectStructuredAgentStartItem diff --git a/mobile/src/session/use-mobile-structured-turn-indicator.ts b/mobile/src/session/native-chat/transcript/use-mobile-structured-turn-indicator.ts similarity index 73% rename from mobile/src/session/use-mobile-structured-turn-indicator.ts rename to mobile/src/session/native-chat/transcript/use-mobile-structured-turn-indicator.ts index 6274d7c509c..73be344024e 100644 --- a/mobile/src/session/use-mobile-structured-turn-indicator.ts +++ b/mobile/src/session/native-chat/transcript/use-mobile-structured-turn-indicator.ts @@ -1,14 +1,14 @@ import { useMemo } from 'react' -import type { StructuredAgentSessionState } from '../../../src/shared/agent-session/structured-agent-session-reducer' -import type { NativeChatLiveTurnIndicator } from '../../../src/shared/native-chat/transcript/native-chat-turn-status' -import { isStructuredAgentSessionThinking } from '../../../src/shared/agent-session/view/structured-agent-session-live-turn' -import { selectStructuredAgentTurnActivity } from '../../../src/shared/native-chat/transcript/native-chat-turn-activity' -import { agentStopDisplayStatus } from '../../../src/shared/agent-status/lifecycle/agent-stop-display-status' -import { mobileStructuredSendQueues } from './mobile-structured-send-queue-rule' +import type { StructuredAgentSessionState } from '../../../../../src/shared/agent-session/structured-agent-session-reducer' +import type { NativeChatLiveTurnIndicator } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-status' +import { isStructuredAgentSessionThinking } from '../../../../../src/shared/agent-session/view/structured-agent-session-live-turn' +import { selectStructuredAgentTurnActivity } from '../../../../../src/shared/native-chat/transcript/native-chat-turn-activity' +import { agentStopDisplayStatus } from '../../../../../src/shared/agent-status/lifecycle/agent-stop-display-status' +import { mobileStructuredSendQueues } from '../composer/mobile-structured-send-queue-rule' import { nativeChatSendFollowUp, type NativeChatSendFollowUp -} from '../../../src/shared/native-chat/input/native-chat-follow-up-behavior' +} from '../../../../../src/shared/native-chat/input/native-chat-follow-up-behavior' export function useMobileStructuredTurnIndicator(input: { state: StructuredAgentSessionState diff --git a/mobile/src/session/native-chat/use-stored-chat-transcript.ts b/mobile/src/session/native-chat/transcript/use-stored-chat-transcript.ts similarity index 100% rename from mobile/src/session/native-chat/use-stored-chat-transcript.ts rename to mobile/src/session/native-chat/transcript/use-stored-chat-transcript.ts diff --git a/mobile/src/session/use-throttled-latest-value.ts b/mobile/src/session/native-chat/transcript/use-throttled-latest-value.ts similarity index 100% rename from mobile/src/session/use-throttled-latest-value.ts rename to mobile/src/session/native-chat/transcript/use-throttled-latest-value.ts diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-controller.ts b/mobile/src/session/native-chat/use-mobile-native-chat-controller.ts index ec49ba67457..b90f76fb88f 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-controller.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-controller.ts @@ -3,21 +3,21 @@ import { useCallback, useLayoutEffect, useMemo, useRef, type MutableRefObject } import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import type { ConnectionState } from '../../transport/connection/connection-state' import type { MobileNativeChatTab } from './mobile-native-chat-eligibility' -import type { StructuredAgentSessionHostSupport } from '../mobile-structured-agent-session-host-support' +import type { StructuredAgentSessionHostSupport } from './agent-session/mobile-structured-agent-session-host-support' import { useMobileNativeChatDrafts } from './composer/use-mobile-native-chat-drafts' import { useMobileNativeChatWorkspaceCatalog } from './composer/use-mobile-native-chat-workspace-catalog' import { useMobileNativeChatMessageSend } from './transcript/use-mobile-native-chat-message-send' import { useMobileNativeChatSessionOptionController } from './input/use-mobile-native-chat-session-option-controller' import { useMobileNativeChatSessionLane } from './use-mobile-native-chat-session-lane' -import { useMobileStructuredNativeChatSendBridge } from '../use-mobile-structured-native-chat-send-bridge' -import { useMobileNativeChatTerminalTurn } from '../use-mobile-native-chat-terminal-turn' +import { useMobileStructuredNativeChatSendBridge } from './composer/use-mobile-structured-native-chat-send-bridge' +import { useMobileNativeChatTerminalTurn } from './transcript/use-mobile-native-chat-terminal-turn' import type { MobileNativeChatController } from './mobile-native-chat-controller-contract' -import { useMobileBridgeChatPromptWrites } from '../use-mobile-bridge-chat-prompt-writes' +import { useMobileBridgeChatPromptWrites } from './input/use-mobile-bridge-chat-prompt-writes' import { useMobileNativeChatActiveResolution } from './use-mobile-native-chat-active-resolution' -import { useMobileNativeChatPromptController } from '../use-mobile-native-chat-prompt-controller' +import { useMobileNativeChatPromptController } from './input/use-mobile-native-chat-prompt-controller' import { nativeChatPendingSendPlacement } from '../../../../src/shared/native-chat/transcript/native-chat-pending-sends' import { mobileNativeChatScopeKey } from './mobile-native-chat-scope-key' -import { useMobileNativeChatCiResultLines } from '../use-mobile-native-chat-ci-result-lines' +import { useMobileNativeChatCiResultLines } from './preferences/use-mobile-native-chat-ci-result-lines' export function useMobileNativeChatController(args: { client: RpcClient | null diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts b/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts index 8865ab41830..d8ad6f990d2 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-markdown-links.ts @@ -21,7 +21,7 @@ import { type MobileMarkdownLinks } from '../../components/mobile-markdown-links-context' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' -import type { RpcOperationSender } from '../../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../../transport/rpc/unvalidated-rpc-request-port' import { isMobileMethodUnavailableError } from '../../transport/rpc/mobile-method-unavailable' import { isFilePathCodeSpan } from '../../components/markdown-file-path-detection' import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' @@ -42,7 +42,7 @@ const chatPathsExistRead = bindDeferredRpcOperation( /** Paths in a chat's text, checked in batches against the chat's workspace on its host. */ export function createMobileNativeChatFileLinkExistence( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, worktreePath: string ): NativeChatFileLinkExistence { diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-session-lane.ts b/mobile/src/session/native-chat/use-mobile-native-chat-session-lane.ts index d61614a79a7..0ce1245b017 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-session-lane.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-session-lane.ts @@ -1,13 +1,13 @@ import { useCallback } from 'react' import type { MobileComposerWriter } from './composer/mobile-native-chat-draft-store' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' -import type { StructuredAgentSessionHostSupport } from '../mobile-structured-agent-session-host-support' +import type { StructuredAgentSessionHostSupport } from './agent-session/mobile-structured-agent-session-host-support' import type { ConnectionState } from '../../transport/connection/connection-state' import { useMobileNativeChatSession } from './use-mobile-native-chat-session' -import { useMobileStructuredAgentSession } from '../use-mobile-structured-agent-session' -import { useMobileStructuredSessionCommands } from '../use-mobile-structured-session-commands' +import { useMobileStructuredAgentSession } from './agent-session/use-mobile-structured-agent-session' +import { useMobileStructuredSessionCommands } from './composer/use-mobile-structured-session-commands' import type { MobileNativeChatResolution } from './mobile-native-chat-eligibility' -import type { MobileNativeChatSendErrorReporter } from './use-mobile-native-chat-send-error' +import type { MobileNativeChatSendErrorReporter } from './composer/use-mobile-native-chat-send-error' // Run both hooks in fixed order and starve the inactive lane’s identity so flips preserve cached transcripts. export function useMobileNativeChatSessionLane({ diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-session.test.ts b/mobile/src/session/native-chat/use-mobile-native-chat-session.test.ts index 8d9f0705af2..5a5db4d94a0 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-session.test.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-session.test.ts @@ -5,8 +5,8 @@ import type { NativeChatMessage } from '../../../../src/shared/native-chat/nativ import type { RpcClient } from '../../transport/rpc/rpc-client-contract' import { stubRpcClient } from '../../transport/rpc/rpc-client-test-stub' import { publishPageDeviceStore } from '../../mobile-web-shell/bridge/page-device-store' -import { mobileNativeChatTranscriptCache } from './mobile-native-chat-transcript-cache' -import { encodeStoredChatTranscript } from './mobile-native-chat-transcript-encode' +import { mobileNativeChatTranscriptCache } from './transcript/mobile-native-chat-transcript-cache' +import { encodeStoredChatTranscript } from './transcript/mobile-native-chat-transcript-encode' import { useMobileNativeChatSession, type MobileNativeChatSession @@ -164,62 +164,6 @@ describe('useMobileNativeChatSession', () => { expect(state?.messages.at(-1)?.id).toBe('live-1') }) - it('keeps the rows it shows when a replay window has moved past them', async () => { - const sendRequest = vi.fn(() => new Promise(() => {})) - const window = Array.from({ length: 40 }, (_unused, index) => message(`win-${index}`)) - const subscribe: RpcClient['subscribe'] = vi.fn((_method, _params, onData) => { - emit = onData - onData({ type: 'snapshot', messages: window, hasMore: false, beforeOffset: 0 }) - return () => {} - }) - await mount({ sendRequest, subscribe } as unknown as RpcClient) - - await act(async () => - emit({ - type: 'snapshot', - messages: [...window.slice(1), message('live-1')], - hasMore: true, - beforeOffset: 10 - }) - ) - - expect(state?.messages.map((entry) => entry.id)).toEqual([ - ...window.map((entry) => entry.id), - 'live-1' - ]) - expect(state?.hasMore).toBe(false) - }) - - it('drops paged rows that an authoritative replay says were removed', async () => { - const sendRequest = vi.fn().mockResolvedValue({ - ok: true, - result: { - messages: Array.from({ length: 60 }, (_unused, index) => message(`paged-${index}`)), - hasMore: false, - beforeOffset: 40 - } - }) - const window = Array.from({ length: 40 }, (_unused, index) => message(`win-${index}`)) - const subscribe: RpcClient['subscribe'] = vi.fn((_method, _params, onData) => { - emit = onData - onData({ type: 'snapshot', messages: window, hasMore: true, beforeOffset: 100 }) - return () => {} - }) - await mount({ sendRequest, subscribe } as unknown as RpcClient) - await act(async () => { - state?.loadEarlier() - await Promise.resolve() - }) - expect(state?.messages).toHaveLength(100) - - await act(async () => - emit({ type: 'snapshot', messages: window, hasMore: false, beforeOffset: 0 }) - ) - - expect(state?.messages).toEqual(window) - expect(state?.hasMore).toBe(false) - }) - it('fences an in-flight older page when a merging replay re-cuts the byte cursor', async () => { let resolveEarlier: (response: unknown) => void = () => {} const sendRequest = vi.fn( @@ -272,35 +216,6 @@ describe('useMobileNativeChatSession', () => { expect(state?.messages.map((entry) => entry.id)).toEqual(['early-b', 'base-c']) }) - - it('keeps every row it shows while live output outgrows the first window', async () => { - let resolvePage: (response: unknown) => void = () => {} - const sendRequest = vi - .fn() - .mockImplementationOnce(() => new Promise((resolve) => (resolvePage = resolve))) - .mockImplementation(() => new Promise(() => {})) - const window = Array.from({ length: 40 }, (_unused, index) => message(`window-${index}`)) - const subscribe: RpcClient['subscribe'] = vi.fn((_method, _params, onData) => { - emit = onData - onData({ type: 'snapshot', messages: window, hasMore: true, beforeOffset: 100 }) - return () => {} - }) - await mount({ sendRequest, subscribe } as unknown as RpcClient) - const shown = window.map((entry) => entry.id) - - act(() => state?.loadEarlier()) - await act(async () => emit({ type: 'appended', messages: [message('live-1')] })) - expect(state?.messages.map((entry) => entry.id)).toEqual([...shown, 'live-1']) - - await act(async () => { - resolvePage({ - ok: true, - result: { messages: [message('older-page')], hasMore: true, beforeOffset: 50 } - }) - await Promise.resolve() - }) - expect(state?.messages.map((entry) => entry.id)).toEqual(['older-page', ...shown, 'live-1']) - }) }) describe('useMobileNativeChatSession transcriptLoading', () => { diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-session.ts b/mobile/src/session/native-chat/use-mobile-native-chat-session.ts index 978f859a8f3..0e2d53c95b5 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-session.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-session.ts @@ -15,21 +15,23 @@ import { nativeChatSessionPageRead } from './agent-session/mobile-native-chat-pa import { applyMobileNativeChatStreamFrame, type MobileNativeChatStreamFrame -} from './mobile-native-chat-stream-frame' +} from './transcript/mobile-native-chat-stream-frame' import { randomV4Uuid } from '../../platform/random-v4-uuid' -import { useMobileOlderPageLoader, type OlderPagePhase } from '../use-mobile-older-page-loader' +import { + useMobileOlderPageLoader, + type OlderPagePhase +} from './transcript/use-mobile-older-page-loader' import { cacheMobileNativeChatTranscript, heldMobileNativeChatTranscript, mobileNativeChatResumePoint, mobileNativeChatTranscriptCache -} from './mobile-native-chat-transcript-cache' -import { terminalChatTranscriptIdentity } from './mobile-native-chat-transcript-store' -import { noteChatTranscript } from './mobile-native-chat-transcript-writer' -import { useStoredChatTranscript } from './use-stored-chat-transcript' +} from './transcript/mobile-native-chat-transcript-cache' +import { terminalChatTranscriptIdentity } from './transcript/mobile-native-chat-transcript-store' +import { noteChatTranscript } from './transcript/mobile-native-chat-transcript-writer' +import { useStoredChatTranscript } from './transcript/use-stored-chat-transcript' import type { MobileNativeChatStatus } from './mobile-native-chat-session-status' -export type { MobileNativeChatStatus } from './mobile-native-chat-session-status' export type MobileNativeChatSession = { messages: NativeChatMessage[] diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts b/mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts index daa4e74a737..084b090f152 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-terminal-stream.test.ts @@ -5,19 +5,13 @@ import { useMobileNativeChatTerminalStream } from './use-mobile-native-chat-term describe('useMobileNativeChatTerminalStream', () => { let renderer: ReactTestRenderer | null = null - let harnessRenderCount = 0 const subscriptionsRef = { current: new Map void>() } const subscribingRef = { current: new Set() } const leaseOnlyRef = { current: new Set() } const webReadyRef = { current: new Set(['terminal-1']) } - let subscribeOpensLeaseOnly = false const registerSubscription = (handle: string): boolean => { subscriptionsRef.current.set(handle, () => {}) - if (subscribeOpensLeaseOnly) { - leaseOnlyRef.current.add(handle) - } else { - leaseOnlyRef.current.delete(handle) - } + leaseOnlyRef.current.delete(handle) return true } const subscribe = vi.fn(registerSubscription) @@ -25,7 +19,6 @@ describe('useMobileNativeChatTerminalStream', () => { subscriptionsRef.current.delete(handle) leaseOnlyRef.current.delete(handle) }) - const notifyWebReadyRef = { current: (_handle: string, _wasAlreadyReady: boolean): void => {} } const notifyListedHandlesRef = { current: (_liveHandles: ReadonlySet): void => {} } const hasTabsRecoveryNeedRef = { current: (): boolean => false } @@ -33,9 +26,7 @@ describe('useMobileNativeChatTerminalStream', () => { subscriptionsRef.current = new Map([['terminal-1', () => {}]]) subscribingRef.current = new Set() leaseOnlyRef.current = new Set() - subscribeOpensLeaseOnly = false webReadyRef.current = new Set(['terminal-1']) - harnessRenderCount = 0 subscribe.mockClear() unsubscribe.mockClear() }) @@ -56,7 +47,6 @@ describe('useMobileNativeChatTerminalStream', () => { leaseReady?: boolean streamRevision?: number }): null { - harnessRenderCount += 1 const stream = useMobileNativeChatTerminalStream({ showNativeChat, activeHandle, @@ -70,7 +60,6 @@ describe('useMobileNativeChatTerminalStream', () => { subscribe, unsubscribe }) - notifyWebReadyRef.current = stream.notifyWebReady notifyListedHandlesRef.current = stream.notifyListedHandles hasTabsRecoveryNeedRef.current = stream.hasTabsRecoveryNeed return null @@ -95,244 +84,6 @@ describe('useMobileNativeChatTerminalStream', () => { }) } - // A synchronous tab switch still sees old chat coverage and opens the incoming terminal as lease-only. - function handOffToTerminalTwo(): void { - subscribeOpensLeaseOnly = true - subscribe('terminal-2') - subscribeOpensLeaseOnly = false - subscribe.mockClear() - unsubscribe.mockClear() - } - - it('trades a lease-only stream for output when a chat tab hands off to a terminal tab', async () => { - webReadyRef.current.add('terminal-2') - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - handOffToTerminalTwo() - - await act(async () => { - renderer?.update( - createElement(Harness, { showNativeChat: false, activeHandle: 'terminal-2' }) - ) - }) - - expect(unsubscribe).toHaveBeenCalledWith('terminal-2') - expect(subscribe).toHaveBeenCalledWith('terminal-2') - expect(leaseOnlyRef.current.has('terminal-2')).toBe(false) - }) - - it('repairs a lease-only stream once its WebView reports ready', async () => { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - handOffToTerminalTwo() - await act(async () => { - renderer?.update( - createElement(Harness, { showNativeChat: false, activeHandle: 'terminal-2' }) - ) - }) - - // A cold WebView must wait; its normal ready path skips already-live lease subscriptions. - expect(subscribe).not.toHaveBeenCalled() - - await act(async () => { - webReadyRef.current.add('terminal-2') - notifyWebReadyRef.current('terminal-2', false) - }) - - expect(unsubscribe).toHaveBeenCalledWith('terminal-2') - expect(subscribe).toHaveBeenCalledWith('terminal-2') - }) - - it.each([true, false])( - 'stops rearming a handle whose stream never comes back (covered: %s, #10681)', - async (showNativeChat) => { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat })) - }) - subscribe.mockClear() - // Dead PTYs answer subscribed/end repeatedly; rearm must stop rather than loop through host waits. - for (let revision = 1; revision <= 6; revision += 1) { - subscriptionsRef.current.delete('terminal-1') - await act(async () => { - renderer?.update( - createElement(Harness, { - showNativeChat, - leaseReady: false, - streamRevision: revision - }) - ) - }) - } - - expect(subscribe.mock.calls.length).toBeLessThanOrEqual(3) - } - ) - - it('does not charge the rearm budget for a subscribe its own gates turned away', async () => { - try { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - subscribe.mockClear() - // Locally rejected subscribes never reached the host and must not spend real attempts. - subscribe.mockImplementation(() => false) - for (let revision = 1; revision <= 5; revision += 1) { - subscriptionsRef.current.delete('terminal-1') - await act(async () => { - renderer?.update( - createElement(Harness, { - showNativeChat: true, - leaseReady: false, - streamRevision: revision - }) - ) - }) - } - - expect(subscribe).toHaveBeenCalledTimes(5) - } finally { - subscribe.mockImplementation(registerSubscription) - } - }) - - it('charges the rearm budget for a subscribe the bridge refused before returning', async () => { - try { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: false })) - }) - subscribe.mockClear() - // A synchronous refusal ends the stream inside subscribe, so no ref ever shows it was sent. - subscribe.mockImplementation(() => true) - for (let revision = 1; revision <= 5; revision += 1) { - subscriptionsRef.current.delete('terminal-1') - await act(async () => { - renderer?.update( - createElement(Harness, { - showNativeChat: false, - leaseReady: false, - streamRevision: revision - }) - ) - }) - } - - expect(subscribe).toHaveBeenCalledTimes(3) - } finally { - subscribe.mockImplementation(registerSubscription) - } - }) - - it.each([true, false])( - 'refills the rearm budget when terminal.list reports the handle again (covered: %s, #10681)', - async (showNativeChat) => { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat })) - }) - for (let revision = 1; revision <= 5; revision += 1) { - subscriptionsRef.current.delete('terminal-1') - await act(async () => { - renderer?.update( - createElement(Harness, { - showNativeChat, - leaseReady: false, - streamRevision: revision - }) - ) - }) - } - subscribe.mockClear() - await act(async () => { - renderer?.update( - createElement(Harness, { showNativeChat, leaseReady: false, streamRevision: 6 }) - ) - }) - expect(subscribe).not.toHaveBeenCalled() - - // Still-listed dead handles must not refill on every inventory refresh. - await act(async () => { - notifyListedHandlesRef.current(new Set(['terminal-1'])) - notifyListedHandlesRef.current(new Set(['terminal-1'])) - }) - expect(subscribe).not.toHaveBeenCalled() - - await act(async () => { - notifyListedHandlesRef.current(new Set()) - notifyListedHandlesRef.current(new Set(['terminal-1'])) - }) - - expect(subscribe).toHaveBeenCalledWith('terminal-1') - } - ) - - it('does not let a dead PTY buy a new budget with its own `subscribed` ack', async () => { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - subscribe.mockClear() - // Subscribed is only half a dead-PTY reply; clearing attempts there would refill the loop forever. - for (let revision = 1; revision <= 6; revision += 1) { - await playDeadPtyRoundTrip(revision) - } - - expect(subscribe.mock.calls.length).toBeLessThanOrEqual(3) - }) - - it('refills the budget for a rearmed stream that outlives the teardown window', async () => { - vi.useFakeTimers() - try { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - subscribe.mockClear() - for (let revision = 1; revision <= 3; revision += 1) { - await playDeadPtyRoundTrip(revision) - } - expect(subscribe).toHaveBeenCalledTimes(3) - - subscribe.mockClear() - subscriptionsRef.current.set('terminal-1', () => {}) - await act(async () => { - renderer?.update( - createElement(Harness, { showNativeChat: true, leaseReady: true, streamRevision: 4 }) - ) - }) - await act(async () => { - vi.advanceTimersByTime(5_000) - }) - await playDeadPtyRoundTrip(5) - - expect(subscribe).toHaveBeenCalledWith('terminal-1') - } finally { - vi.useRealTimers() - } - }) - - it('keeps an absence marker observed before the budget ran out', async () => { - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - for (let revision = 1; revision <= 2; revision += 1) { - await playDeadPtyRoundTrip(revision) - } - // Preserve absence markers until exhaustion actually consumes them; early consumption can permanently lock a still-listed handle. - await act(async () => { - notifyListedHandlesRef.current(new Set()) - notifyListedHandlesRef.current(new Set(['terminal-1'])) - }) - await playDeadPtyRoundTrip(3) - subscribe.mockClear() - await playDeadPtyRoundTrip(4) - expect(subscribe).not.toHaveBeenCalled() - - await act(async () => { - notifyListedHandlesRef.current(new Set(['terminal-1'])) - }) - - expect(subscribe).toHaveBeenCalledWith('terminal-1') - }) - it('keeps asking until a tab snapshot replaces the exhausted handle', async () => { await act(async () => { renderer = create(createElement(Harness, { showNativeChat: true })) @@ -364,25 +115,4 @@ describe('useMobileNativeChatTerminalStream', () => { }) expect(hasTabsRecoveryNeedRef.current()).toBe(false) }) - - it('resumes a cold-start lease-only stream when WebView readiness arrives late', async () => { - webReadyRef.current.clear() - await act(async () => { - renderer = create(createElement(Harness, { showNativeChat: true })) - }) - await act(async () => { - renderer?.update(createElement(Harness, { showNativeChat: false })) - }) - - expect(unsubscribe).toHaveBeenCalledOnce() - expect(subscribe).toHaveBeenCalledOnce() - - webReadyRef.current.add('terminal-1') - await act(async () => { - notifyWebReadyRef.current('terminal-1', false) - }) - - expect(unsubscribe).toHaveBeenNthCalledWith(2, 'terminal-1') - expect(subscribe).toHaveBeenNthCalledWith(2, 'terminal-1') - }) }) diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-usage-limit.ts b/mobile/src/session/native-chat/use-mobile-native-chat-usage-limit.ts index 400253d479b..443fbf12000 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-usage-limit.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-usage-limit.ts @@ -14,12 +14,12 @@ import type { } from '../../../../src/shared/agent-session/journal/agent-session-journal-types' import type { AgentSessionOptionsResult } from '../../../../src/shared/agent-session/options/agent-session-options-result' import type { AgentJournalCursor } from '../../../../src/shared/agent-session/journal/agent-session-journal-identity' -import { mobileStructuredSessionStatusFeed } from '../mobile-structured-session-status-feed' -import { callAgentSession } from '../mobile-structured-agent-session-rpc' +import { mobileStructuredSessionStatusFeed } from './agent-session/mobile-structured-session-status-feed' +import { callAgentSession } from './agent-session/mobile-structured-agent-session-rpc' import type { MobileStructuredAgentSessionArgs, StructuredMobileSession -} from '../mobile-structured-agent-session-contract' +} from './agent-session/mobile-structured-agent-session-contract' /** The usage-limit row the person can act on: the latest cut while nothing is running or sending. */ export type MobileNativeChatUsageLimit = { diff --git a/mobile/src/session/structured-create-wire-types.test.ts b/mobile/src/session/structured-create-wire-types.test.ts deleted file mode 100644 index 14813467cb6..00000000000 --- a/mobile/src/session/structured-create-wire-types.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, expect, expectTypeOf, it } from 'vitest' -import type { z } from 'zod' -import { - structuredAgentSessionCreateParams, - type StructuredAgentSessionFirstMessage -} from '../../../src/shared/agent-session/structured-agent-session-create' -import { CreateIntentParams } from '../../../src/shared/rpc-contract/structured-agent-session-params' - -describe('the shared create builder used by mobile', () => { - it('remains assignable to the strict create request without an opening message', () => { - const params = structuredAgentSessionCreateParams({ - sessionId: 'codex_11111111_2222_3333_4444_555555555555', - worktree: 'id:workspace-1', - agent: 'codex', - randomUuid: () => '11111111-2222-3333-4444-555555555555' - }) - expectTypeOf(params).toExtend>() - expect(CreateIntentParams.safeParse(params).success).toBe(true) - expect(params).not.toHaveProperty('firstMessage') - }) - - it('accepts only user-authored opening messages at the type and wire boundaries', () => { - const firstMessage: StructuredAgentSessionFirstMessage = { - clientMessageId: 'opening-message', - body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'hello' }] } - } - const params = structuredAgentSessionCreateParams({ - sessionId: 'codex_11111111_2222_3333_4444_555555555555', - worktree: 'id:workspace-1', - agent: 'codex', - firstMessage, - randomUuid: () => '11111111-2222-3333-4444-555555555555' - }) - expect(params.firstMessage).toEqual(firstMessage) - expect(CreateIntentParams.safeParse(params).success).toBe(true) - - type AssistantBody = { - kind: 'message' - role: 'assistant' - blocks: { type: 'text'; text: string }[] - } - expectTypeOf().not.toExtend() - expectTypeOf().toEqualTypeOf<'user'>() - expect( - CreateIntentParams.shape.firstMessage.safeParse({ - clientMessageId: 'opening-message', - body: { kind: 'message', role: 'assistant', blocks: [{ type: 'text', text: 'forged' }] } - }).success - ).toBe(false) - }) -}) diff --git a/mobile/src/session/tabs/MobileNewTabSheet.tsx b/mobile/src/session/tabs/MobileNewTabSheet.tsx new file mode 100644 index 00000000000..0f7730ddd75 --- /dev/null +++ b/mobile/src/session/tabs/MobileNewTabSheet.tsx @@ -0,0 +1,183 @@ +import { useRef } from 'react' +import { Pressable, Text, View } from 'react-native' +import { FileText, Globe, SquareTerminal } from 'lucide-react-native' +import type { TuiAgent } from '../../../../src/shared/agent-launch/catalog/tui-agent' +import { ActionSheetContent, type ActionSheetAction } from '../../components/ActionSheetModal' +import { BottomDrawer } from '../../components/bottom-drawer/BottomDrawer' +import { + LIST_LEADING_INSET, + ListGroup, + ListRow, + SectionHeader +} from '../../components/list/ListGroup' +import { MobileAgentIcon } from '../../components/MobileAgentIcon' +import { SheetHeader } from '../../components/nav-bar/SheetHeader' +import { useMobileTranslation } from '../../i18n/use-mobile-translation' +import { colors, radii, spacing } from '../../theme/mobile-theme' +import { themedStyleSheet } from '../../theme/themed-styles' +import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' +import type { MobileNewTabAgentOption } from '../agent-launch/mobile-new-tab-agent-options' + +const AGENT_GRID_COLUMNS = 3 + +type Props = { + visible: boolean + workspaceName: string + agentOptions: readonly MobileNewTabAgentOption[] + /** Loading, none, other-server or error; shown instead of the agent grid. */ + agentStatusRow: ActionSheetAction | null + canCreateBrowser: boolean + canCreateMarkdownNote: boolean + onClose: () => void + onCreateAgent: (agent: TuiAgent) => void + onCreateTerminal: () => void + /** Runs after the sheet has gone, so it may open another modal. */ + onCreateBrowser: () => void + onCreateMarkdownNote: () => void +} + +export function MobileNewTabSheet({ + visible, + workspaceName, + agentOptions, + agentStatusRow, + canCreateBrowser, + canCreateMarkdownNote, + onClose, + onCreateAgent, + onCreateTerminal, + onCreateBrowser, + onCreateMarkdownNote +}: Props) { + useMobileColorScheme() + const t = useMobileTranslation() + // Wait for the iOS sheet window to unmount before presenting another native modal. + const afterCloseRef = useRef<(() => void) | null>(null) + const closeThen = (action: () => void) => { + afterCloseRef.current = action + onClose() + } + const closeAndRun = (action: () => void) => { + onClose() + action() + } + const showAgentGrid = agentStatusRow === null && agentOptions.length > 0 + // Short last rows keep empty slots so every tile stays a third of the row wide. + const agentRows: { options: MobileNewTabAgentOption[]; emptySlots: string[] }[] = [] + for (let start = 0; start < agentOptions.length; start += AGENT_GRID_COLUMNS) { + const options = agentOptions.slice(start, start + AGENT_GRID_COLUMNS) + const emptySlots: string[] = [] + for (let column = options.length; column < AGENT_GRID_COLUMNS; column += 1) { + emptySlots.push(`empty:${column}`) + } + agentRows.push({ options, emptySlots }) + } + const iconColor = colors.textSecondary + return ( + { + const run = afterCloseRef.current + afterCloseRef.current = null + run?.() + }} + dragContentToDismiss + > + + {showAgentGrid || agentStatusRow ? ( + + ) : null} + {agentStatusRow ? : null} + {showAgentGrid ? ( + + {agentRows.map((row) => ( + option.agent).join(':')} style={styles.gridRow}> + {row.options.map((option) => ( + closeAndRun(() => onCreateAgent(option.agent))} + accessibilityRole="button" + accessibilityLabel={option.label} + style={styles.tile} + > + {({ pressed }) => ( + <> + {pressed ? : null} + + + {option.label} + + + )} + + ))} + {row.emptySlots.map((slot) => ( + + ))} + + ))} + + ) : null} + + } + onPress={() => closeAndRun(onCreateTerminal)} + /> + {canCreateBrowser ? ( + } + navigates + onPress={() => closeThen(onCreateBrowser)} + /> + ) : null} + {canCreateMarkdownNote ? ( + } + onPress={() => closeAndRun(onCreateMarkdownNote)} + /> + ) : null} + + + ) +} + +const styles = themedStyleSheet(() => ({ + grid: { gap: spacing.sm }, + gridRow: { flexDirection: 'row', gap: spacing.sm }, + tile: { + flex: 1, + minWidth: 0, + height: 78, + borderRadius: radii.surface, + backgroundColor: colors.sheetGroup, + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center', + gap: spacing.sm, + paddingHorizontal: spacing.sm + }, + // Same wash as a pressed ListRow, laid over the tile so its own fill stays underneath. + tilePressed: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: colors.translucentFill + }, + tileLabel: { fontSize: 13.5, fontWeight: '600', color: colors.textPrimary, maxWidth: '100%' }, + // Padded like a tile: flex shares space after padding, so a bare spacer would widen its row's tiles. + tileSpacer: { flex: 1, paddingHorizontal: spacing.sm }, + actions: { marginTop: spacing.lg } +})) diff --git a/mobile/src/session/tabs/MobileSessionTabBar.tsx b/mobile/src/session/tabs/MobileSessionTabBar.tsx new file mode 100644 index 00000000000..a9aeb494c8e --- /dev/null +++ b/mobile/src/session/tabs/MobileSessionTabBar.tsx @@ -0,0 +1,361 @@ +import { useMemo, useRef, useState } from 'react' +import { Pressable, Text, View } from 'react-native' +import { Plus } from 'lucide-react-native' +import { Gesture, GestureDetector } from 'react-native-gesture-handler' +import Animated, { + Easing, + ReduceMotion, + useAnimatedStyle, + useSharedValue, + withSequence, + withTiming +} from 'react-native-reanimated' +import { triggerMediumImpact, triggerSelection } from '../../platform/haptics' +import { useMobileTranslation } from '../../i18n/use-mobile-translation' +import { AgentStateDot } from '../../components/AgentStateDot' +import { ArrivalFade } from '../../components/ArrivalFade' +import { useDrawerReducedMotion } from '../../components/bottom-drawer/bottom-drawer-constants' +import { SHEET_CURVE } from '../../components/sheet-motion-values' +import { useReducedMotionEnabled } from '../../platform/use-reduced-motion' +import { useKeyboardPersistingTaps } from '../../platform/keyboard-persisting-taps' +import { useNow } from '../../hooks/use-now' +import { agentStateLabel, type AgentDotState } from '../../worktree/agent-row-display' +import { colors, feedback, radii, spacing } from '../../theme/mobile-theme' +import { themedStyleSheet } from '../../theme/themed-styles' +import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' +import { buildMobileSessionTabRow, type MobileSessionTabRow } from './mobile-session-tab-rows' +import { MobileSessionTabMark } from './MobileSessionTabMark' +import { MobileSessionTabsSheet } from './MobileSessionTabsSheet' +import type { MobileSessionController } from '../use-mobile-session-controller' + +const SWIPE_SETTLE_MS = 250 +// A tab arriving from the side starts this far out, as a share of the tab cell's width. +const SWIPE_ENTRY_SHARE = 0.4 +const FLICK_VELOCITY = 600 +// With no tab on that side, the tab gives only this share of the drag. +const EDGE_RESISTANCE = 0.25 +const DRAG_CLICK_WINDOW_MS = 400 + +// A tab drawn before it had any agent state (from the phone's store) fades its dot in when one lands. +function TabBarDot({ tabId, state }: { tabId: string; state: AgentDotState | undefined }) { + const [startedEmpty] = useState({ tabId, empty: state === undefined }) + if (startedEmpty.tabId === tabId && startedEmpty.empty) { + return + } + return state && state !== 'idle' ? : null +} + +// Mounted with the bare tab, so the motion preference is read before the dot arrives. +function ArrivingTabBarDot({ state }: { state: AgentDotState | undefined }) { + const reducedMotion = useReducedMotionEnabled() + if (!state || state === 'idle') { + return null + } + return ( + + + + ) +} + +/** The tab you're in as one cell: tap for its actions, swipe for the next one; the count shows every tab, + adds one. */ +export function MobileSessionTabBar({ + controller, + inTopBar, + actionsCollapsed = false +}: { + controller: MobileSessionController + inTopBar: boolean + actionsCollapsed?: boolean +}) { + useMobileColorScheme() + const t = useMobileTranslation() + const { + activeSessionTabId, + connState, + createTabBusy, + handleOpenNewTab, + switchSessionTab, + openSessionTabActionSheetAfterKeyboardDismiss, + visibleTabs, + terminals + } = controller + // Why: the first tap must reach the bar with the keyboard open instead of only dismissing it (#5106). + const keepsKeyboard = useKeyboardPersistingTaps('handled') + const now = useNow(60_000) + const rows = useMemo( + () => visibleTabs.map((tab) => buildMobileSessionTabRow(tab, terminals, now)), + [visibleTabs, terminals, now] + ) + const activeIndex = rows.findIndex((row) => row.tab.id === activeSessionTabId) + const active: MobileSessionTabRow | undefined = rows[activeIndex] + const previous = activeIndex > 0 ? rows[activeIndex - 1] : undefined + const next = activeIndex !== -1 ? rows[activeIndex + 1] : undefined + const othersNeedYou = rows.some( + (row) => row.tab.id !== activeSessionTabId && row.group === 'needs-you' + ) + const [sheetOpen, setSheetOpen] = useState(false) + + const reducedMotion = useDrawerReducedMotion() + const offset = useSharedValue(0) + const width = useSharedValue(0) + // On the web, releasing a mouse drag also clicks the tab. + const dragEndedAtRef = useRef(0) + const settle = (to: number) => + withTiming( + to, + reducedMotion + ? { duration: 0, reduceMotion: ReduceMotion.Never } + : { duration: SWIPE_SETTLE_MS, easing: Easing.bezier(...SHEET_CURVE) } + ) + const step = (target: MobileSessionTabRow | undefined, direction: 1 | -1) => { + if (!target) { + offset.value = settle(0) + return + } + triggerSelection() + switchSessionTab(target.tab) + offset.value = withSequence( + withTiming(-direction * width.value * SWIPE_ENTRY_SHARE, { duration: 0 }), + settle(0) + ) + } + // Callbacks run on JS: the session page is a web document, and stepping switches React state. + const pan = Gesture.Pan() + .runOnJS(true) + .enabled(rows.length > 1) + .activeOffsetX([-12, 12]) + .failOffsetY([-10, 10]) + .onUpdate((event) => { + const target = event.translationX > 0 ? previous : next + offset.value = target ? event.translationX : event.translationX * EDGE_RESISTANCE + }) + .onEnd((event) => { + // oxlint-disable-next-line react/purity -- runs when the drag ends, never during render. + dragEndedAtRef.current = Date.now() + const direction = event.translationX > 0 ? 1 : -1 + const flicked = + Math.abs(event.velocityX) > FLICK_VELOCITY && Math.sign(event.velocityX) === direction + if (Math.abs(event.translationX) > width.value / 2 || flicked) { + step(direction === 1 ? previous : next, direction) + return + } + offset.value = settle(0) + }) + // Web has no worklet plugin; mapper hooks need this explicit dependency list. + const contentStyle = useAnimatedStyle( + () => ({ + transform: [{ translateX: offset.value }], + opacity: 1 - Math.min(Math.abs(offset.value) / Math.max(width.value, 1), 1) * 0.6 + }), + [offset, width] + ) + + const status = active?.dotState && active.dotState !== 'idle' ? active.dotState : undefined + const countLabel = + rows.length === 1 + ? t('session.tabBar.oneTab', '1 tab') + : t('session.tabBar.tabCount', '{{count}} tabs', { count: rows.length }) + const tabLabel = [active?.title, status ? agentStateLabel(status) : null] + .filter(Boolean) + .join(', ') + const countAccessibilityLabel = [ + countLabel, + othersNeedYou ? t('session.tabBar.anotherNeedsYou', 'another tab needs you') : null + ] + .filter(Boolean) + .join(', ') + const createDisabled = createTabBusy || connState !== 'connected' + const openTabActions = () => { + // The click a drag release produces on the web belongs to the drag. + if (!active || Date.now() - dragEndedAtRef.current < DRAG_CLICK_WINDOW_MS) { + return + } + openSessionTabActionSheetAfterKeyboardDismiss(active.tab) + } + + return ( + + {/* pan-y keeps vertical touches with the page on the web. */} + + { + width.value = event.nativeEvent.layout.width + }} + > + [styles.tabPress, pressed && styles.cellPressed]} + onPress={openTabActions} + onLongPress={() => { + triggerMediumImpact() + openTabActions() + }} + delayLongPress={400} + disabled={!active} + accessibilityRole="button" + accessibilityLabel={tabLabel || countLabel} + accessibilityHint={t('session.tabBar.actionsHint', 'Shows actions for this tab')} + accessibilityActions={[ + ...(next ? [{ name: 'nextTab', label: t('session.tabBar.next', 'Next tab') }] : []), + ...(previous + ? [{ name: 'previousTab', label: t('session.tabBar.previous', 'Previous tab') }] + : []) + ]} + onAccessibilityAction={(event) => { + const name = event.nativeEvent.actionName + if (name === 'nextTab') { + step(next, -1) + } else if (name === 'previousTab') { + step(previous, 1) + } + }} + > + {/* Only the contents slide, so the cell stays flush with the screen edge. */} + + {active ? ( + <> + + + + + + ) : null} + + {active?.title ?? countLabel} + + + + + + [ + styles.sideCell, + actionsCollapsed && !inTopBar && styles.lastCell, + pressed && styles.cellPressed + ]} + onPress={() => setSheetOpen(true)} + accessibilityRole="button" + accessibilityLabel={countAccessibilityLabel} + accessibilityHint={t('session.tabBar.hint', 'Shows all tabs')} + > + + {rows.length} + + {othersNeedYou ? : null} + + {/* A collapsed bar's + lives in the header's ⋯ menu. */} + {!actionsCollapsed ? ( + [ + styles.sideCell, + !inTopBar && styles.lastCell, + pressed && styles.cellPressed, + createDisabled && styles.cellDisabled + ]} + disabled={createDisabled} + onPress={handleOpenNewTab} + accessibilityRole="button" + accessibilityLabel={t('session.tabStrip.newTab', 'New tab')} + accessibilityState={{ disabled: createDisabled }} + > + + + ) : null} + setSheetOpen(false)} + /> + + ) +} + +const styles = themedStyleSheet(() => ({ + bar: { + height: 44, + flexDirection: 'row', + alignItems: 'stretch', + borderTopWidth: 1, + borderTopColor: colors.borderSubtle + }, + // Cancels the top bar's vertical padding so the cells run the bar's full height. + barInTopBar: { + flex: 1, + minWidth: 0, + flexDirection: 'row', + alignItems: 'stretch', + alignSelf: 'stretch', + marginVertical: -spacing.xs, + marginLeft: spacing.md, + borderLeftWidth: 1, + borderLeftColor: colors.borderSubtle + }, + // The open tab, drawn like the strip's active tab: raised, with a line under it. + tabCell: { + flex: 1, + minWidth: 0, + backgroundColor: colors.bgRaised, + borderBottomWidth: 2, + borderBottomColor: colors.textSecondary, + borderRightWidth: 1, + borderRightColor: colors.borderSubtle, + overflow: 'hidden' + }, + tabPress: { + flex: 1, + justifyContent: 'center', + paddingLeft: spacing.lg, + paddingRight: spacing.sm + }, + tabContent: { flexDirection: 'row', alignItems: 'center', gap: 6 }, + // Keeps web SVGs from shrinking before the title ellipsizes. + mark: { flexShrink: 0 }, + title: { + flexShrink: 1, + minWidth: 0, + fontSize: 14, + fontWeight: '600', + color: colors.textPrimary + }, + sideCell: { + width: 46, + alignItems: 'center', + justifyContent: 'center', + borderRightWidth: 1, + borderRightColor: colors.borderSubtle + }, + // At the screen edge; in the top bar the hairline stays to part the cells from the round buttons. + lastCell: { borderRightWidth: 0 }, + cellPressed: { backgroundColor: colors.translucentFill }, + cellDisabled: { opacity: feedback.disabledOpacity }, + countBox: { + minWidth: 19, + height: 19, + paddingHorizontal: 3, + borderRadius: 6, + borderWidth: 1.8, + borderColor: colors.textPrimary, + alignItems: 'center', + justifyContent: 'center' + }, + countText: { + fontSize: 11, + fontWeight: '700', + color: colors.textPrimary, + fontVariant: ['tabular-nums'] + }, + // Another tab needs you: a red dot on the count's corner, ringed in the bar's colour. + alert: { + position: 'absolute', + top: 8, + right: 9, + width: 9, + height: 9, + borderRadius: radii.pill, + backgroundColor: colors.statusRed, + borderWidth: 2, + borderColor: colors.bgPanel + } +})) diff --git a/mobile/src/session/tabs/MobileSessionTabMark.tsx b/mobile/src/session/tabs/MobileSessionTabMark.tsx new file mode 100644 index 00000000000..67a1d0b3895 --- /dev/null +++ b/mobile/src/session/tabs/MobileSessionTabMark.tsx @@ -0,0 +1,28 @@ +import { File, FileText, Globe, SquareTerminal } from 'lucide-react-native' +import { MobileAgentIcon } from '../../components/MobileAgentIcon' +import { colors } from '../../theme/mobile-theme' +import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' +import type { MobileSessionTabRow } from './mobile-session-tab-rows' + +/** The tab's agent icon, else an icon for what kind of tab it is. */ +export function MobileSessionTabMark({ + row, + size +}: { + row: Pick + size: number +}) { + useMobileColorScheme() + if (row.agentId) { + return + } + const Icon = + row.tab.type === 'browser' + ? Globe + : row.tab.type === 'markdown' + ? FileText + : row.tab.type === 'file' + ? File + : SquareTerminal + return +} diff --git a/mobile/src/session/tabs/MobileSessionTabStrip.tsx b/mobile/src/session/tabs/MobileSessionTabStrip.tsx deleted file mode 100644 index 6455c9bf6f8..00000000000 --- a/mobile/src/session/tabs/MobileSessionTabStrip.tsx +++ /dev/null @@ -1,327 +0,0 @@ -import { useCallback, useMemo, useRef, useState } from 'react' -import { View, Text, ScrollView, Pressable, StyleSheet } from 'react-native' -import { File, FileText, Globe, Plus, SquareTerminal } from 'lucide-react-native' -import { triggerMediumImpact } from '../../platform/haptics' -import { useMobileTranslation } from '../../i18n/use-mobile-translation' -import { MobileAgentIcon } from '../../components/MobileAgentIcon' -import { AgentStateDot } from '../../components/AgentStateDot' -import { ArrivalFade } from '../../components/ArrivalFade' -import { useReducedMotionEnabled } from '../../platform/use-reduced-motion' -import { useNow } from '../../hooks/use-now' -import { - agentDotState, - agentStateLabel, - type AgentDotState -} from '../../worktree/agent-row-display' -import { - getMobileTerminalTabHostAgentIdentity, - getMobileSessionTabTitle, - resolveMobileTerminalTabAgentId -} from '../../terminal/session/mobile-terminal-tab-agent' -import { colors, spacing } from '../../theme/mobile-theme' -import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' -import { NavBarButton } from '../../components/nav-bar/NavBarButton' -import { - resolveTabStripEdgeHint, - resolveTabStripHiddenTabs, - resolveTabStripTapOffset, - type TabStripHiddenTabs -} from './tab-strip-scroll' -import { styles } from '../mobile-session-styles' -import { useKeyboardPersistingTaps } from '../../platform/keyboard-persisting-taps' -import type { MobileSessionController } from '../use-mobile-session-controller' - -const DOUBLE_TAP_RENAME_MS = 300 - -// A tab drawn before it had any agent state (a strip from the phone's store) fades its dot in when one lands. -function SessionTabAgentDot({ state }: { state: AgentDotState | undefined }) { - const [startedEmpty] = useState(state === undefined) - if (startedEmpty) { - return - } - return state && state !== 'idle' ? : null -} - -// Mounted with the bare tab, so the motion preference is read before the dot arrives. -function ArrivingSessionTabAgentDot({ state }: { state: AgentDotState | undefined }) { - const reducedMotion = useReducedMotionEnabled() - if (!state || state === 'idle') { - return null - } - return ( - - - - ) -} - -export function MobileSessionTabStrip({ - controller, - inTopBar, - actionsCollapsed = false -}: { - controller: MobileSessionController - inTopBar: boolean - actionsCollapsed?: boolean -}) { - useMobileColorScheme() - const t = useMobileTranslation() - const { - activeSessionTabId, - activeSessionTabIdRef, - tabStripRef, - tabStripOffsetRef, - tabStripViewportWidthRef, - tabStripContentWidthRef, - tabLayoutsRef, - connState, - createTabBusy, - handleOpenNewTab, - scrollActiveTabIntoView, - holdTabStripReveal, - cancelHeldTabStripReveal, - switchSessionTab, - openSessionTabActionSheetAfterKeyboardDismiss, - setRenameTarget, - activeHandleRef, - visibleTabs, - terminals - } = controller - const tabBarKeepsKeyboard = useKeyboardPersistingTaps('handled') - const lastTabTapRef = useRef<{ id: string; at: number } | null>(null) - const now = useNow(60_000) - const tabStates = useMemo(() => { - const states = new Map() - for (const tab of visibleTabs) { - // Why: a status restored across a host restart is unconfirmed, so like desktop it shows no live state. - if (tab.type === 'terminal' && tab.agentStatus && !tab.agentStatus.restoredUnconfirmed) { - states.set(tab.id, agentDotState(tab.agentStatus, now)) - } else if (tab.type === 'agent-session' && tab.chatStatus) { - states.set(tab.id, agentDotState(tab.chatStatus, now)) - } - } - return states - }, [visibleTabs, now]) - const [hiddenTabs, setHiddenTabs] = useState({ left: [], right: [] }) - const refreshHiddenTabs = () => { - const next = resolveTabStripHiddenTabs({ - layouts: tabLayoutsRef.current, - viewportWidth: tabStripViewportWidthRef.current, - currentOffset: tabStripOffsetRef.current - }) - // Why: scroll fires every frame; re-render only when a tab crosses an edge. - setHiddenTabs((prev) => - prev.left.join('\n') === next.left.join('\n') && - prev.right.join('\n') === next.right.join('\n') - ? prev - : next - ) - } - const leftHint = resolveTabStripEdgeHint(hiddenTabs.left, tabStates) - const rightHint = resolveTabStripEdgeHint(hiddenTabs.right, tabStates) - const pressTab = useCallback( - (t: (typeof visibleTabs)[number]) => { - const at = Date.now() - const lastTap = lastTabTapRef.current - lastTabTapRef.current = { id: t.id, at } - if ( - lastTap?.id === t.id && - at - lastTap.at < DOUBLE_TAP_RENAME_MS && - t.type === 'terminal' && - typeof t.terminal === 'string' - ) { - lastTabTapRef.current = null - triggerMediumImpact() - setRenameTarget({ - handle: t.terminal, - title: t.title, - isActive: t.terminal === activeHandleRef.current - }) - // The first tap's held reveal still runs once the window closes. - return - } - cancelHeldTabStripReveal() - const nextOffset = resolveTabStripTapOffset({ - tabId: t.id, - tabs: visibleTabs, - layouts: tabLayoutsRef.current, - viewportWidth: tabStripViewportWidthRef.current, - contentWidth: tabStripContentWidthRef.current, - currentOffset: tabStripOffsetRef.current - }) - if (nextOffset !== tabStripOffsetRef.current) { - holdTabStripReveal(nextOffset, DOUBLE_TAP_RENAME_MS) - } - switchSessionTab(t) - }, - [ - activeHandleRef, - cancelHeldTabStripReveal, - holdTabStripReveal, - setRenameTarget, - switchSessionTab, - tabLayoutsRef, - tabStripContentWidthRef, - tabStripOffsetRef, - tabStripRef, - tabStripViewportWidthRef, - visibleTabs - ] - ) - const attachTabStrip = useCallback( - (view: ScrollView | null) => { - if (view) { - tabStripOffsetRef.current = 0 - tabStripViewportWidthRef.current = 0 - tabStripContentWidthRef.current = 0 - tabLayoutsRef.current.clear() - } - tabStripRef.current = view - }, - [ - tabStripRef, - tabStripOffsetRef, - tabStripViewportWidthRef, - tabStripContentWidthRef, - tabLayoutsRef - ] - ) - return ( - - {/* Why: tab taps must register on first press with the keyboard open instead of being eaten by dismissal (#5106). */} - - { - tabStripOffsetRef.current = e.nativeEvent.contentOffset.x - refreshHiddenTabs() - }} - onLayout={(e) => { - tabStripViewportWidthRef.current = e.nativeEvent.layout.width - scrollActiveTabIntoView(activeSessionTabIdRef.current, false) - refreshHiddenTabs() - }} - onContentSizeChange={(width) => { - tabStripContentWidthRef.current = width - scrollActiveTabIntoView(activeSessionTabIdRef.current, false) - refreshHiddenTabs() - }} - > - {visibleTabs.map((tab, index) => { - const tabState = tabStates.get(tab.id) - const hostAgentIdentity = getMobileTerminalTabHostAgentIdentity(tab, terminals) - const title = getMobileSessionTabTitle(tab, hostAgentIdentity) - return ( - [ - styles.tab, - index === 0 && !inTopBar && styles.tabFirst, - tab.id === activeSessionTabId && styles.tabActive, - pressed && styles.tabPressed - ]} - onLayout={(e) => { - const { x, width } = e.nativeEvent.layout - tabLayoutsRef.current.set(tab.id, { x, width }) - if (tab.id === activeSessionTabIdRef.current) { - scrollActiveTabIntoView(tab.id, false) - } - refreshHiddenTabs() - }} - onPress={() => pressTab(tab)} - accessibilityRole="tab" - accessibilityLabel={ - tabState && tabState !== 'idle' - ? t('session.tabStrip.tabStatus', '{{title}}, {{status}}', { - title, - status: agentStateLabel(tabState) - }) - : title - } - accessibilityState={{ selected: tab.id === activeSessionTabId }} - onLongPress={() => { - triggerMediumImpact() - openSessionTabActionSheetAfterKeyboardDismiss(tab) - }} - delayLongPress={400} - > - - - - {tab.type === 'browser' && ( - - )} - {tab.type === 'markdown' && ( - - )} - {tab.type === 'file' && ( - - )} - {tab.type === 'agent-session' && ( - - )} - {tab.type === 'terminal' && - (() => { - const agentId = resolveMobileTerminalTabAgentId(tab, hostAgentIdentity) - return agentId ? ( - - ) : ( - - ) - })()} - - - {title} - - - - ) - })} - - {leftHint ? ( - - - - ) : null} - {rightHint ? ( - - - - ) : null} - - {/* Terminal layout and Quick commands live in the header's ⋯ menu. */} - {!actionsCollapsed ? ( - - - - ) : null} - - ) -} - -// The right inset matches the nav bar's, so the + lines up with the ⋯ above it. -const newTabStyles = StyleSheet.create({ - slot: { - paddingLeft: spacing.sm, - paddingRight: spacing.md - } -}) diff --git a/mobile/src/session/tabs/MobileSessionTabsSheet.tsx b/mobile/src/session/tabs/MobileSessionTabsSheet.tsx new file mode 100644 index 00000000000..f8cd5cd1981 --- /dev/null +++ b/mobile/src/session/tabs/MobileSessionTabsSheet.tsx @@ -0,0 +1,220 @@ +import { Fragment, useRef, useState } from 'react' +import { Pressable, Text, View } from 'react-native' +import { Plus, X } from 'lucide-react-native' +import { BottomDrawer } from '../../components/bottom-drawer/BottomDrawer' +import { ListGroup, SectionHeader } from '../../components/list/ListGroup' +import { NavBarButton } from '../../components/nav-bar/NavBarButton' +import { SheetHeader } from '../../components/nav-bar/SheetHeader' +import { SwipeRowActions } from '../../components/SwipeRowActions' +import { AgentStateDot } from '../../components/AgentStateDot' +import { useMobileTranslation } from '../../i18n/use-mobile-translation' +import { useNow } from '../../hooks/use-now' +import { formatTimeAgo } from '../../worktree/agent-row-display' +import { colors, radii, spacing } from '../../theme/mobile-theme' +import { themedStyleSheet } from '../../theme/themed-styles' +import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme' +import type { MobileSessionTabGroup, MobileSessionTabRow } from './mobile-session-tab-rows' +import { MobileSessionTabMark } from './MobileSessionTabMark' +import type { MobileSessionController } from '../use-mobile-session-controller' + +const TILE_SIZE = 34 +const TILE_DOT_RING = 2 +const GROUP_ORDER: readonly MobileSessionTabGroup[] = ['needs-you', 'working', 'other'] + +/** Every tab in the workspace, what needs you first; tap one to go there, swipe one to close it. */ +export function MobileSessionTabsSheet({ + controller, + rows, + visible, + onClose +}: { + controller: MobileSessionController + rows: readonly MobileSessionTabRow[] + visible: boolean + onClose: () => void +}) { + useMobileColorScheme() + const t = useMobileTranslation() + const now = useNow(60_000) + const { + activeSessionTabId, + worktreeName, + switchSessionTab, + handleCloseSessionTab, + handleOpenNewTab, + createTabBusy, + connState + } = controller + // Sections hold still while the sheet is open, so a row never moves under a finger. + const [frozenGroups, setFrozenGroups] = useState>() + if (visible && frozenGroups === undefined) { + setFrozenGroups(new Map(rows.map((row) => [row.tab.id, row.group]))) + } + const afterCloseRef = useRef<(() => void) | null>(null) + const sections = GROUP_ORDER.map((group) => ({ + group, + rows: rows.filter((row) => (frozenGroups?.get(row.tab.id) ?? row.group) === group) + })).filter((section) => section.rows.length > 0) + const sectionTitles: Record = { + 'needs-you': t('session.tabsSheet.needsYou', 'Needs you'), + working: t('session.tabsSheet.working', 'Working'), + other: t('session.tabsSheet.everythingElse', 'Everything else') + } + const title = + rows.length === 1 + ? t('session.tabBar.oneTab', '1 tab') + : t('session.tabBar.tabCount', '{{count}} tabs', { count: rows.length }) + + const renderRow = (row: MobileSessionTabRow) => { + const current = row.tab.id === activeSessionTabId + const badge = row.dotState && row.dotState !== 'idle' ? row.dotState : undefined + const time = current + ? t('session.tabsSheet.open', 'Open') + : row.timeAt !== null + ? formatTimeAgo(row.timeAt, now) + : '' + return ( + void handleCloseSessionTab(row.tab) + }} + > + {(guardPress) => ( + { + if (!current) { + switchSessionTab(row.tab) + } + onClose() + })} + > + {({ pressed }) => ( + <> + {current || pressed ? : null} + + + {badge ? ( + + + + ) : null} + + + + {row.title} + + + {row.detail} + + + {time ? ( + {time} + ) : null} + + )} + + )} + + ) + } + + return ( + { + setFrozenGroups(undefined) + const afterClose = afterCloseRef.current + afterCloseRef.current = null + afterClose?.() + }} + > + + + + { + // The new-tab sheet is its own drawer; open it once this one is gone. + afterCloseRef.current = handleOpenNewTab + onClose() + }} + /> + + + + {sections.map((section, index) => ( + + {sections.length > 1 ? ( + + ) : null} + + {section.rows.map(renderRow)} + + + ))} + + + ) +} + +const styles = themedStyleSheet(() => ({ + // Over the sheet header's empty trailing side, centred in its 52pt row. + newTab: { position: 'absolute', top: 8, right: spacing.xs }, + body: { paddingBottom: spacing.lg }, + // Opaque so a swipe's action stays hidden until the row slides off it. + row: { + minHeight: 60, + flexDirection: 'row', + alignItems: 'center', + gap: spacing.md, + paddingHorizontal: spacing.lg, + paddingVertical: spacing.sm, + backgroundColor: colors.sheetGroup + }, + // The tab you're in, or a pressed row, washed like a picked segment on a sheet group. + wash: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: colors.translucentFill + }, + tile: { + width: TILE_SIZE, + height: TILE_SIZE, + borderRadius: radii.tile, + backgroundColor: colors.translucentFill, + alignItems: 'center', + justifyContent: 'center' + }, + // The state dot on the tile's corner, ringed in the group's colour. + tileBadge: { + position: 'absolute', + top: -4, + right: -4, + padding: TILE_DOT_RING, + borderRadius: radii.pill, + backgroundColor: colors.sheetGroup + }, + text: { flex: 1, minWidth: 0, gap: 2 }, + title: { fontSize: 15, fontWeight: '600', color: colors.textPrimary }, + detail: { fontSize: 13, color: colors.textSecondary }, + time: { fontSize: 12, color: colors.textMuted, fontVariant: ['tabular-nums'] }, + timeCurrent: { fontWeight: '600', color: colors.accentBlue } +})) diff --git a/mobile/src/session/tabs/mobile-session-tab-read-operations.ts b/mobile/src/session/tabs/mobile-session-tab-read-operations.ts index fe0c2f30db2..c1a9bb6ffc8 100644 --- a/mobile/src/session/tabs/mobile-session-tab-read-operations.ts +++ b/mobile/src/session/tabs/mobile-session-tab-read-operations.ts @@ -2,13 +2,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rp import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' import '../../terminal/terminal-reply-schema' import { sessionForwardedReplySchema } from '../../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../agent-launch/mobile-agent-detection-reply-schema' -import '../../files/session-documents/mobile-markdown-tab-reply-schema' -import '../../terminal/session/mobile-session-terminal-reply-schema' -import '../../worktree/session-worktree-record-reply-schema' -import '../native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../../terminal/quick-commands/mobile-quick-command-reply-schema' -import '../../files/workspace-file-paths-reply-schema' // Leave replies raw until the controller’s generation, barrier and application-revision guards pass. export const sessionTabsListRead = bindDeferredRpcOperation( diff --git a/mobile/src/session/tabs/mobile-session-tab-rows.ts b/mobile/src/session/tabs/mobile-session-tab-rows.ts new file mode 100644 index 00000000000..1b2854512f6 --- /dev/null +++ b/mobile/src/session/tabs/mobile-session-tab-rows.ts @@ -0,0 +1,121 @@ +import { agentListPreview } from '../../agents/agent-list-row-text' +import { agentStatusGroup } from '../../agents/agent-list-rows' +import { translate } from '../../i18n/mobile-i18n' +import { + getMobileSessionTabTitle, + getMobileTerminalTabHostAgentIdentity, + resolveMobileTerminalTabAgentId +} from '../../terminal/session/mobile-terminal-tab-agent' +import { + agentDotState, + agentRowTimeAt, + agentStateLabel, + type AgentDotState +} from '../../worktree/agent-row-display' +import type { MobileSessionTab } from './mobile-session-tabs' + +export type MobileSessionTabGroup = 'needs-you' | 'working' | 'other' + +export type MobileSessionTabRow = { + tab: MobileSessionTab + title: string + /** The agent whose icon marks the tab; null draws the tab kind's icon. */ + agentId: string | null + dotState: AgentDotState | undefined + group: MobileSessionTabGroup + /** The second line: the state, then what the tab is doing or showing. */ + detail: string + timeAt: number | null +} + +export function mobileSessionTabDotState( + tab: MobileSessionTab, + now: number +): AgentDotState | undefined { + // Why: a status restored across a host restart is unconfirmed, so like desktop it shows no live state. + if (tab.type === 'terminal' && tab.agentStatus && !tab.agentStatus.restoredUnconfirmed) { + return agentDotState(tab.agentStatus, now) + } + if (tab.type === 'agent-session' && tab.chatStatus) { + return agentDotState(tab.chatStatus, now) + } + return undefined +} + +function tabGroup(state: AgentDotState | undefined): MobileSessionTabGroup { + if (!state) { + return 'other' + } + const group = agentStatusGroup(state) + return group === 'needs-you' || group === 'working' ? group : 'other' +} + +function tabContent( + tab: MobileSessionTab, + title: string, + dotState: AgentDotState | undefined +): string { + switch (tab.type) { + case 'terminal': { + const status = tab.agentStatus + if (!status || !dotState) { + return '' + } + return agentListPreview(status, agentStatusGroup(dotState), title) + } + case 'browser': + return tab.url === title ? '' : tab.url + case 'markdown': + case 'file': + return tab.relativePath === title ? '' : tab.relativePath + case 'agent-session': + return '' + } +} + +function tabKindLabel(tab: MobileSessionTab): string { + switch (tab.type) { + case 'browser': + return translate('session.tabRows.browser', 'Browser') + case 'markdown': + return translate('session.tabRows.markdown', 'Markdown note') + case 'file': + return translate('session.tabRows.file', 'File') + case 'agent-session': + return translate('session.tabRows.chat', 'Chat') + case 'terminal': + return translate('session.tabRows.terminal', 'Terminal') + } +} + +export function buildMobileSessionTabRow( + tab: MobileSessionTab, + terminals: readonly { handle: string; agentIdentity?: string }[], + now: number +): MobileSessionTabRow { + const hostAgentIdentity = getMobileTerminalTabHostAgentIdentity(tab, terminals) + const title = getMobileSessionTabTitle(tab, hostAgentIdentity) + const dotState = mobileSessionTabDotState(tab, now) + const stateWord = dotState && dotState !== 'idle' ? agentStateLabel(dotState) : '' + const content = tabContent(tab, title, dotState) + const timeAt = + tab.type === 'terminal' && tab.agentStatus && !tab.agentStatus.restoredUnconfirmed + ? agentRowTimeAt(tab.agentStatus) + : tab.type === 'agent-session' && tab.chatStatus + ? tab.chatStatus.updatedAt + : null + return { + tab, + title, + agentId: + tab.type === 'agent-session' + ? tab.agent + : tab.type === 'terminal' + ? resolveMobileTerminalTabAgentId(tab, hostAgentIdentity) + : null, + dotState, + group: tabGroup(dotState), + detail: [stateWord, content].filter(Boolean).join(' · ') || tabKindLabel(tab), + timeAt + } +} diff --git a/mobile/src/session/tabs/mobile-session-tab-write-operations.ts b/mobile/src/session/tabs/mobile-session-tab-write-operations.ts index a450d1f6a6e..856c3323fa8 100644 --- a/mobile/src/session/tabs/mobile-session-tab-write-operations.ts +++ b/mobile/src/session/tabs/mobile-session-tab-write-operations.ts @@ -1,7 +1,5 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' -import '../../files/session-documents/mobile-markdown-tab-reply-schema' -import '../../worktree/session-worktree-record-reply-schema' import '../../terminal/terminal-reply-schema' import { sessionCreatedTerminalTabSchema } from '../../terminal/session/mobile-session-terminal-reply-schema' import { sessionTabCloseReplySchema } from './mobile-session-tab-write-reply-schema' diff --git a/mobile/src/session/tabs/mobile-session-tab-write-reply-schema.ts b/mobile/src/session/tabs/mobile-session-tab-write-reply-schema.ts index 245fe3f5f02..009b0d95835 100644 --- a/mobile/src/session/tabs/mobile-session-tab-write-reply-schema.ts +++ b/mobile/src/session/tabs/mobile-session-tab-write-reply-schema.ts @@ -1,5 +1,4 @@ import * as z from 'zod' -import '../../../../src/shared/zod-salvage' // Bare legacy success and delegated closes do not prove permanent retirement. export const sessionTabCloseReplySchema = z diff --git a/mobile/src/session/tabs/tab-strip-scroll.ts b/mobile/src/session/tabs/tab-strip-scroll.ts index 9b9dd9ce99b..e7d6c85e416 100644 --- a/mobile/src/session/tabs/tab-strip-scroll.ts +++ b/mobile/src/session/tabs/tab-strip-scroll.ts @@ -1,5 +1,3 @@ -import type { AgentDotState } from '../../worktree/agent-row-display' - export type TabStripScrollInput = { tabX: number tabWidth: number @@ -36,98 +34,3 @@ export function resolveTabStripScrollOffset({ return Math.min(Math.max(0, nextOffset), maxOffset) } - -type TabLayouts = ReadonlyMap - -export function resolveTabStripTapOffset(args: { - tabId: string - tabs: readonly { id: string }[] - layouts: TabLayouts - viewportWidth: number - contentWidth: number - currentOffset: number -}): number { - const { tabs, layouts, viewportWidth, contentWidth, currentOffset } = args - const index = tabs.findIndex((tab) => tab.id === args.tabId) - const selected = layouts.get(args.tabId) - if (index === -1 || !selected || viewportWidth <= 0) { - return currentOffset - } - const selectedOnly = resolveTabStripScrollOffset({ - tabX: selected.x, - tabWidth: selected.width, - viewportWidth, - contentWidth, - currentOffset - }) - if (selectedOnly === currentOffset) { - return currentOffset - } - const left = selected.x + selected.width / 2 < currentOffset + viewportWidth / 2 - const neighbor = tabs[index + (left ? -1 : 1)] - const layout = neighbor ? layouts.get(neighbor.id) : undefined - let start = selected.x - let end = selected.x + selected.width - if (layout) { - const gap = left ? selected.x - (layout.x + layout.width) : layout.x - end - // Keep the tapped tab whole while revealing as much of its neighbor as fits within both margins. - const peek = Math.min(layout.width, Math.max(0, viewportWidth - 24 - selected.width - gap)) - if (left) { - start -= gap + peek - } else { - end += gap + peek - } - } - return resolveTabStripScrollOffset({ - tabX: start, - tabWidth: end - start, - viewportWidth, - contentWidth, - currentOffset - }) -} - -export type TabStripHiddenTabs = { left: readonly string[]; right: readonly string[] } - -export function resolveTabStripHiddenTabs(args: { - layouts: TabLayouts - viewportWidth: number - currentOffset: number -}): TabStripHiddenTabs { - const left: string[] = [] - const right: string[] = [] - if (args.viewportWidth > 0) { - for (const [tabId, layout] of args.layouts) { - const center = layout.x + layout.width / 2 - if (center < args.currentOffset) { - left.push(tabId) - } else if (center > args.currentOffset + args.viewportWidth) { - right.push(tabId) - } - } - } - return { left, right } -} - -// Lower ranks win when several hidden tabs share an edge; other states get no edge hint. -const EDGE_HINT_RANK: Partial> = { - blocked: 0, - waiting: 0, - working: 1, - monitoring: 1 -} - -export function resolveTabStripEdgeHint( - tabIds: readonly string[], - tabStates: ReadonlyMap -): AgentDotState | null { - let hint: AgentDotState | null = null - for (const tabId of tabIds) { - const state = tabStates.get(tabId) - const rank = state ? EDGE_HINT_RANK[state] : undefined - if (state && rank !== undefined && (hint === null || rank < (EDGE_HINT_RANK[hint] ?? 0))) { - hint = state - } - } - return hint -} diff --git a/mobile/src/session/tabs/use-mobile-session-close-actions.test.tsx b/mobile/src/session/tabs/use-mobile-session-close-actions.test.tsx index a9077c5e1e9..f7c61750b6f 100644 --- a/mobile/src/session/tabs/use-mobile-session-close-actions.test.tsx +++ b/mobile/src/session/tabs/use-mobile-session-close-actions.test.tsx @@ -14,7 +14,14 @@ import { readMobileNativeChatDraftInputs, updateMobileNativeChatDraftInputs } from '../native-chat/composer/mobile-native-chat-draft-inputs' -import { resetMobileNativeChatDraftStoreForTests } from '../native-chat/composer/mobile-native-chat-draft-store' +import { closeMobileNativeChatTabDraft } from '../native-chat/composer/mobile-native-chat-draft-store' + +import { loadMobileNativeChatDrafts } from '../native-chat/composer/mobile-native-chat-draft-persistence' + +vi.mock('@react-native-async-storage/async-storage', async () => { + const { createInMemoryAsyncStorage } = await import('../../test-support/in-memory-async-storage') + return { default: createInMemoryAsyncStorage() } +}) const initial: Terminal[] = ['a', 'b', 'c'].map((handle) => ({ handle, @@ -24,10 +31,16 @@ const initial: Terminal[] = ['a', 'b', 'c'].map((handle) => ({ describe('legacy terminal close acknowledgments', () => { let renderer: ReactTestRenderer | null = null - afterEach(() => { + let queries: ReturnType | null = null + afterEach(async () => { act(() => renderer?.unmount()) renderer = null - resetMobileNativeChatDraftStoreForTests() + await queries?.end() + queries = null + await loadMobileNativeChatDrafts('host', 'workspace') + closeMobileNativeChatTabDraft('queued-editor') + closeMobileNativeChatTabDraft('host\0workspace\0closed') + await loadMobileNativeChatDrafts('host', 'workspace') }) async function mount(renameTarget: Terminal | null = null) { @@ -127,8 +140,9 @@ describe('legacy terminal close acknowledgments', () => { } return null } + queries = documentQueryClient() await act(async () => { - renderer = create(documentQueryClient().wrap(createElement(Harness))) + renderer = create(queries!.wrap(createElement(Harness))) }) return { read() { diff --git a/mobile/src/session/tabs/use-mobile-session-tab-reconciliation.ts b/mobile/src/session/tabs/use-mobile-session-tab-reconciliation.ts index 58fa5c9e731..5d3e7d7d66b 100644 --- a/mobile/src/session/tabs/use-mobile-session-tab-reconciliation.ts +++ b/mobile/src/session/tabs/use-mobile-session-tab-reconciliation.ts @@ -6,7 +6,7 @@ import { MOBILE_AI_VAULT_CAPABILITY } from '../../agent-history/agent-history-ca import '../../../../src/shared/protocol-version' import { TERMINAL_QUERY_REPLY_INPUT_RUNTIME_CAPABILITY } from '../../../../src/shared/terminal/runtime-policy/terminal-runtime-capabilities' import { PROGRAM_STATUS_RUNTIME_CAPABILITY } from '../../../../src/shared/program-status/protocol' -import { structuredAgentSessionHostSupport } from '../mobile-structured-agent-session-host-support' +import { structuredAgentSessionHostSupport } from '../native-chat/agent-session/mobile-structured-agent-session-host-support' import { runAcceptedMobileSessionTabsEffects } from './mobile-session-tabs-accepted-effects' import type { SessionTabsStreamSource } from './mobile-session-tabs-stream-health' import { useMobileSessionTabsFetchReporting } from './use-mobile-session-tabs-fetch-reporting' diff --git a/mobile/src/session/use-missing-worktree-bounce.test.ts b/mobile/src/session/use-missing-worktree-bounce.test.ts index a697bbfb905..49600fd89d8 100644 --- a/mobile/src/session/use-missing-worktree-bounce.test.ts +++ b/mobile/src/session/use-missing-worktree-bounce.test.ts @@ -1,12 +1,6 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { WorktreeShowResolution } from '../worktree/worktree-show-resolution' +import { describe, expect, it } from 'vitest' import { isSyntheticWorkspaceRoute } from './synthetic-workspace-route' -import { - shouldBounceMissingWorktree, - useMissingWorktreeBounce -} from './use-missing-worktree-bounce' +import { shouldBounceMissingWorktree } from './use-missing-worktree-bounce' describe('shouldBounceMissingWorktree', () => { it('bounces only a host-proven missing worktree', () => { @@ -21,60 +15,3 @@ describe('shouldBounceMissingWorktree', () => { expect(shouldBounceMissingWorktree('global-floating-terminal', 'missing')).toBe(false) }) }) - -describe('useMissingWorktreeBounce', () => { - let renderer: ReactTestRenderer | null = null - const bounce = vi.fn() - - beforeEach(() => { - bounce.mockReset() - renderer = null - }) - - function Harness(props: { worktreeId: string; resolution: WorktreeShowResolution }): null { - useMissingWorktreeBounce({ - hostId: 'host-1', - worktreeId: props.worktreeId, - resolution: props.resolution, - bounce - }) - return null - } - - function render(worktreeId: string, resolution: WorktreeShowResolution): void { - act(() => { - const element = createElement(Harness, { worktreeId, resolution }) - if (renderer) { - renderer.update(element) - } else { - renderer = create(element) - } - }) - } - - it('bounces exactly once per worktree even across re-renders', () => { - render('repo::wt', 'unknown') - expect(bounce).not.toHaveBeenCalled() - - render('repo::wt', 'missing') - expect(bounce).toHaveBeenCalledExactlyOnceWith('host-1') - - // Why: navigation lands after the render, so the pre-unmount renders must not re-fire. - render('repo::wt', 'missing') - render('repo::wt', 'unknown') - render('repo::wt', 'missing') - expect(bounce).toHaveBeenCalledTimes(1) - act(() => renderer?.unmount()) - }) - - it('re-arms for a different worktree on the reused screen', () => { - render('repo::wt-1', 'missing') - expect(bounce).toHaveBeenCalledTimes(1) - - render('repo::wt-2', 'unknown') - expect(bounce).toHaveBeenCalledTimes(1) - render('repo::wt-2', 'missing') - expect(bounce).toHaveBeenCalledTimes(2) - act(() => renderer?.unmount()) - }) -}) diff --git a/mobile/src/session/use-mobile-agent-message-navigation.ts b/mobile/src/session/use-mobile-agent-message-navigation.ts index d054bb3a7a7..fc4916220ae 100644 --- a/mobile/src/session/use-mobile-agent-message-navigation.ts +++ b/mobile/src/session/use-mobile-agent-message-navigation.ts @@ -7,8 +7,8 @@ import { type StructuredSessionOpenFailure } from '../../../src/shared/open-structured-agent-session' import type { RpcClient } from '../transport/rpc/rpc-client-contract' -import { mobileStructuredSessionOpenMessage } from './mobile-structured-session-open-message' -import { openMobileStructuredSession } from './open-mobile-structured-session' +import { mobileStructuredSessionOpenMessage } from './native-chat/agent-session/mobile-structured-session-open-message' +import { openMobileStructuredSession } from './native-chat/agent-session/open-mobile-structured-session' import { openMobileAgentMessageTerminal } from '../terminal/session/mobile-agent-message-terminal' import type { MobileSessionKeyboardStateModel } from '../terminal/session/use-mobile-session-keyboard-state' diff --git a/mobile/src/session/use-mobile-default-session-view-preference.test.ts b/mobile/src/session/use-mobile-default-session-view-preference.test.ts index 084164b6a40..53891f46444 100644 --- a/mobile/src/session/use-mobile-default-session-view-preference.test.ts +++ b/mobile/src/session/use-mobile-default-session-view-preference.test.ts @@ -68,52 +68,6 @@ describe('useMobileDefaultSessionViewPreference', () => { expect(saveDefaultSessionView).toHaveBeenCalledWith('chat') }) - it('submits every change immediately so shared persistence preserves event order', async () => { - const firstSave = deferred() - vi.mocked(saveDefaultSessionView) - .mockImplementationOnce(() => firstSave.promise) - .mockResolvedValue(undefined) - await mount() - - act(() => preference?.setDefaultView('chat')) - await act(async () => { - await Promise.resolve() - }) - act(() => preference?.setDefaultView('terminal')) - act(() => preference?.setDefaultView('chat')) - await act(async () => { - await Promise.resolve() - }) - expect(saveDefaultSessionView).toHaveBeenCalledTimes(3) - expect(saveDefaultSessionView).toHaveBeenNthCalledWith(2, 'terminal') - expect(saveDefaultSessionView).toHaveBeenNthCalledWith(3, 'chat') - - await act(async () => { - firstSave.resolve() - await firstSave.promise - await Promise.resolve() - }) - expect(saveDefaultSessionView).toHaveBeenCalledTimes(3) - }) - - it('reloads the persisted value when the latest write fails', async () => { - vi.mocked(loadDefaultSessionView) - .mockResolvedValueOnce('terminal') - .mockResolvedValueOnce('terminal') - vi.mocked(saveDefaultSessionView).mockRejectedValue(new Error('storage unavailable')) - await mount() - - await act(async () => { - preference?.setDefaultView('chat') - await Promise.resolve() - await Promise.resolve() - await Promise.resolve() - }) - - expect(preference?.defaultView).toBe('terminal') - expect(loadDefaultSessionView).toHaveBeenCalledTimes(2) - }) - it('does not let an older failed write roll back a newer choice', async () => { const recoveryLoad = deferred() vi.mocked(loadDefaultSessionView) @@ -140,27 +94,4 @@ describe('useMobileDefaultSessionViewPreference', () => { expect(preference?.defaultView).toBe('terminal') expect(saveDefaultSessionView).toHaveBeenNthCalledWith(2, 'terminal') }) - - it('submits the latest write before the Settings route unmounts', async () => { - const firstSave = deferred() - vi.mocked(saveDefaultSessionView) - .mockImplementationOnce(() => firstSave.promise) - .mockResolvedValueOnce(undefined) - await mount() - - act(() => preference?.setDefaultView('chat')) - await act(async () => { - await Promise.resolve() - }) - act(() => preference?.setDefaultView('terminal')) - expect(saveDefaultSessionView).toHaveBeenNthCalledWith(2, 'terminal') - act(() => renderer?.unmount()) - renderer = null - - await act(async () => { - firstSave.resolve() - await firstSave.promise - await Promise.resolve() - }) - }) }) diff --git a/mobile/src/session/use-mobile-send-completion-generation.test.ts b/mobile/src/session/use-mobile-send-completion-generation.test.ts deleted file mode 100644 index 0776ca717c6..00000000000 --- a/mobile/src/session/use-mobile-send-completion-generation.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { createElement, Suspense } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { describe, expect, it, vi } from 'vitest' -import { useMobileSendCompletionGeneration } from './use-mobile-send-completion-generation' - -let focusCleanup: (() => void) | undefined - -vi.mock('expo-router', () => ({ - useFocusEffect: (effect: () => void | (() => void)) => { - focusCleanup = effect() ?? undefined - } -})) - -describe('mobile send completion generation', () => { - it('invalidates completions on a surface change and retained-route blur', () => { - const onBlur = vi.fn() - let getGeneration: (() => number) | null = null - let renderer: ReactTestRenderer - - function Harness({ surfaceKey }: { surfaceKey: string }): null { - getGeneration = useMobileSendCompletionGeneration({ onBlur, surfaceKey }) - return null - } - - act(() => { - renderer = create(createElement(Harness, { surfaceKey: 'tab-a' })) - }) - const initialGeneration = getGeneration!() - act(() => { - renderer.update(createElement(Harness, { surfaceKey: 'tab-b' })) - }) - expect(getGeneration!()).toBeGreaterThan(initialGeneration) - - const surfaceGeneration = getGeneration!() - act(() => focusCleanup?.()) - expect(getGeneration!()).toBeGreaterThan(surfaceGeneration) - expect(onBlur).toHaveBeenCalledTimes(1) - - act(() => renderer.unmount()) - }) - - it('does not invalidate the committed surface during a suspended render', () => { - const never = new Promise(() => undefined) - let getGeneration: (() => number) | null = null - let renderer: ReactTestRenderer - - function Harness({ surfaceKey, suspend }: { surfaceKey: string; suspend: boolean }): null { - getGeneration = useMobileSendCompletionGeneration({ onBlur: vi.fn(), surfaceKey }) - if (suspend) { - throw never - } - return null - } - - act(() => { - renderer = create( - createElement( - Suspense, - { fallback: null }, - createElement(Harness, { surfaceKey: 'tab-a', suspend: false }) - ) - ) - }) - const committedGeneration = getGeneration!() - act(() => { - renderer.update( - createElement( - Suspense, - { fallback: null }, - createElement(Harness, { surfaceKey: 'tab-b', suspend: true }) - ) - ) - }) - - expect(getGeneration!()).toBe(committedGeneration) - act(() => renderer.unmount()) - }) -}) diff --git a/mobile/src/session/use-mobile-session-dictation-setup.ts b/mobile/src/session/use-mobile-session-dictation-setup.ts index a1c46e002b8..0a6ee25c923 100644 --- a/mobile/src/session/use-mobile-session-dictation-setup.ts +++ b/mobile/src/session/use-mobile-session-dictation-setup.ts @@ -9,6 +9,11 @@ import { } from '../dictation/mobile-dictation-setup' import type { MobileSessionFeedbackCapabilitiesModel } from './use-mobile-session-feedback-capabilities' import type { DictationTerm } from '../../../src/shared/dictation-dictionary' +import { + DEFAULT_STOP_AFTER_SILENCE_SECONDS, + stopAfterSilenceSeconds, + type StopAfterSilenceSeconds +} from '../../../src/shared/dictation-silence-stop' // The computer's dictation settings as this session reads them: mic shown, mode, filler switch, and whether setup is needed first. export function useMobileSessionDictationSetup(scope: MobileSessionFeedbackCapabilitiesModel) { @@ -19,6 +24,9 @@ export function useMobileSessionDictationSetup(scope: MobileSessionFeedbackCapab // The host's filler switch and this worktree's terms, kept from its last setup read; fillers on until one says otherwise. const [removeFillerWords, setRemoveFillerWords] = useState(true) const [dictationTerms, setDictationTerms] = useState(undefined) + const [silenceSeconds, setSilenceSeconds] = useState( + DEFAULT_STOP_AFTER_SILENCE_SECONDS + ) // Only a host setup read sets this; while false, the host's start refusal decides (older hosts, first tap before the read). const dictationNeedsSetupRef = useRef(false) @@ -35,6 +43,7 @@ export function useMobileSessionDictationSetup(scope: MobileSessionFeedbackCapab setDictationMode(setup.dictationMode) setRemoveFillerWords(setup.removeFillerWords !== false) setDictationTerms(setup.dictationTerms) + setSilenceSeconds(stopAfterSilenceSeconds(setup)) setDictationEnabled(!dictationMicHidden(setup)) dictationNeedsSetupRef.current = dictationNeedsSetup(setup) } @@ -70,6 +79,7 @@ export function useMobileSessionDictationSetup(scope: MobileSessionFeedbackCapab phoneDictation, removeFillerWords, dictationTerms, + silenceSeconds, dictationNeedsSetupRef, closeDictationSetup, refreshDictationMode diff --git a/mobile/src/session/use-mobile-session-dictation.ts b/mobile/src/session/use-mobile-session-dictation.ts index 32159402a3c..4bbdfad37d7 100644 --- a/mobile/src/session/use-mobile-session-dictation.ts +++ b/mobile/src/session/use-mobile-session-dictation.ts @@ -196,6 +196,7 @@ export function useMobileSessionDictation( phoneDictation, removeFillerWords, dictationTerms, + silenceSeconds, dictationNeedsSetupRef, closeDictationSetup, refreshDictationMode @@ -245,6 +246,7 @@ export function useMobileSessionDictation( executionHost ), dictationMode: scope.dictationMode, + silenceSeconds, removeFillerWords, dictationTerms, worktreeId, @@ -266,6 +268,7 @@ export function useMobileSessionDictation( phoneDictation.readyRef, removeFillerWords, dictationTerms, + silenceSeconds, scope.dictationMode, sessionKey, setShowDictationSetup, diff --git a/mobile/src/session/use-mobile-session-feedback-capabilities.ts b/mobile/src/session/use-mobile-session-feedback-capabilities.ts index 7ccc7863841..d7861707160 100644 --- a/mobile/src/session/use-mobile-session-feedback-capabilities.ts +++ b/mobile/src/session/use-mobile-session-feedback-capabilities.ts @@ -3,7 +3,7 @@ import { Animated, Easing, Platform } from 'react-native' import { translate } from '../i18n/mobile-i18n' import { reconcileMobileSessionCreateWarningState } from './tabs/mobile-session-create-warning-state' import type { MobileSessionTerminalRuntimeModel } from '../terminal/session/use-mobile-session-terminal-runtime' -import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support' +import type { StructuredAgentSessionHostSupport } from './native-chat/agent-session/mobile-structured-agent-session-host-support' // A strong ease-out both ways; the presentation turns this fade into a short rise. const TOAST_EASING = Easing.bezier(0.23, 1, 0.32, 1) diff --git a/mobile/src/session/use-mobile-session-foundation.ts b/mobile/src/session/use-mobile-session-foundation.ts index 6ecb5ae9870..cdc21bbabb0 100644 --- a/mobile/src/session/use-mobile-session-foundation.ts +++ b/mobile/src/session/use-mobile-session-foundation.ts @@ -52,6 +52,7 @@ export function useMobileSessionFoundation() { name: worktreeName, path: worktreePath, hostId: worktreeHostId, + branch: worktreeBranch, resolution: worktreeResolution } = useLiveWorktreeName({ client, @@ -115,6 +116,7 @@ export function useMobileSessionFoundation() { worktreeName, worktreePath, worktreeHostId, + worktreeBranch, worktreeResolution, isWideLayout, activePanel, diff --git a/mobile/src/session/use-mobile-session-lifecycle.ts b/mobile/src/session/use-mobile-session-lifecycle.ts index 30dd15eb573..0db31f4d6b9 100644 --- a/mobile/src/session/use-mobile-session-lifecycle.ts +++ b/mobile/src/session/use-mobile-session-lifecycle.ts @@ -4,7 +4,7 @@ import { settleSessionPreferenceRead } from './session-preference-read' import { useEffect, useRef, useCallback, useState } from 'react' import { AppState, type AppStateStatus, Platform } from 'react-native' import { useFocusEffect } from 'expo-router' -import { loadHosts } from '../transport/hosts/host-store' +import { loadHosts } from '../host-catalog/host-store' import { loadTerminalAccessoryLayout, saveTerminalSmartRail diff --git a/mobile/src/session/use-mobile-session-native-chat-dictation.ts b/mobile/src/session/use-mobile-session-native-chat-dictation.ts index d339e2a6c63..4bf5a7279b9 100644 --- a/mobile/src/session/use-mobile-session-native-chat-dictation.ts +++ b/mobile/src/session/use-mobile-session-native-chat-dictation.ts @@ -2,8 +2,8 @@ import { useEffect } from 'react' import { useMobileSessionDictation } from './use-mobile-session-dictation' import { useMobileNativeChatController } from './native-chat/use-mobile-native-chat-controller' import { useMobileNativeChatReadability } from './native-chat/transcript/use-mobile-native-chat-readability' -import { useMobileNativeChatInputLease } from './native-chat/use-mobile-native-chat-input-lease' -import { useMobileNativeChatSendError } from './native-chat/use-mobile-native-chat-send-error' +import { useMobileNativeChatInputLease } from './native-chat/composer/use-mobile-native-chat-input-lease' +import { useMobileNativeChatSendError } from './native-chat/composer/use-mobile-native-chat-send-error' import { mobileNativeChatScopeKey } from './native-chat/mobile-native-chat-scope-key' import { mobileNativeChatOverlayInputLockReason } from './native-chat/mobile-native-chat-overlay-lock-reason' import { useMobileSendCompletionGeneration } from './use-mobile-send-completion-generation' diff --git a/mobile/src/session/use-mobile-session-panel-route-actions.tsx b/mobile/src/session/use-mobile-session-panel-route-actions.tsx index 6cd74e382eb..c4ae529e4a8 100644 --- a/mobile/src/session/use-mobile-session-panel-route-actions.tsx +++ b/mobile/src/session/use-mobile-session-panel-route-actions.tsx @@ -7,7 +7,7 @@ import { shouldShowSessionHeaderChecksAction, panelRouteDescriptor } from './session-panel-host' -import { MobileAgentIcon } from '../components/MobileAgentIcon' +import type { ActionSheetAction } from '../components/ActionSheetModal' import { useMobileTranslation } from '../i18n/use-mobile-translation' import { useWorkspaceExecutionHost, @@ -49,127 +49,81 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati quickCommandsSupported, showToast } = scope - const otherRuntimeAgentRow = { - id: 'otherServerAgents', - label: t('session.usePanelRouteActions.otherServerAgents', 'Agents on Another Server'), - hint: t('session.usePanelRouteActions.pairServerHint', 'Pair that server directly'), - icon: Bot, - disabled: true, - onPress: () => {} - } - const createTabAgentActions = + const agentStatusRow = (errorHint: string): ActionSheetAction | null => createTabAgentLoadState === 'loading' - ? [ - { - id: 'detectingAgents', - label: t('session.usePanelRouteActions.detectingAgents', 'Detecting Agents'), - icon: Bot, - disabled: true, - loading: true, - onPress: () => {} - } - ] + ? { + id: 'detectingAgents', + label: t('session.usePanelRouteActions.detectingAgents', 'Detecting Agents'), + icon: Bot, + disabled: true, + loading: true, + onPress: () => {} + } : createTabAgentOptions.length > 0 - ? createTabAgentOptions.map((option) => ({ - id: `agent:${option.agent}`, - label: option.label, - renderIcon: () => , - onPress: () => { - setShowCreateTabDrawer(false) - void handleCreateTerminal(option.agent) - } - })) + ? null : createTabAgentLoadState === 'loaded' - ? [ - { - id: 'noEnabledAgents', - label: t('session.usePanelRouteActions.noEnabledAgents', 'No Enabled Agents'), + ? { + id: 'noEnabledAgents', + label: t('session.usePanelRouteActions.noEnabledAgents', 'No Enabled Agents'), + icon: Bot, + disabled: true, + onPress: () => {} + } + : createTabAgentLoadState === 'other-runtime' + ? { + id: 'otherServerAgents', + label: t( + 'session.usePanelRouteActions.otherServerAgents', + 'Agents on Another Server' + ), + hint: t('session.usePanelRouteActions.pairServerHint', 'Pair that server directly'), icon: Bot, disabled: true, onPress: () => {} } - ] - : createTabAgentLoadState === 'other-runtime' - ? [otherRuntimeAgentRow] : createTabAgentLoadState === 'error' - ? [ - { - id: 'presetsUnavailable', - label: t( - 'session.usePanelRouteActions.presetsUnavailable', - 'Agent Presets Unavailable' - ), - hint: t( - 'session.usePanelRouteActions.checkHostHint', - 'Check the host connection' - ), - icon: Bot, - disabled: true, - onPress: () => {} - } - ] - : [] - const sendDiffNotesAgentActions = - pendingDiffNotesDelivery === null - ? [] - : createTabAgentLoadState === 'loading' - ? [ - { - id: 'detectingAgents', - label: t('session.usePanelRouteActions.detectingAgents', 'Detecting Agents'), - icon: Bot, - disabled: true, - loading: true, - onPress: () => {} - } - ] - : createTabAgentOptions.length > 0 - ? createTabAgentOptions.map((option) => ({ - id: `agent:${option.agent}`, - label: option.label, - hint: t('session.usePanelRouteActions.newSessionHint', 'New agent session'), - icon: Bot, - onPress: () => { - const delivery = pendingDiffNotesDelivery - setPendingDiffNotesDelivery(null) - if (!delivery) { - return - } - void sendDiffNotesToNewAgent(delivery, async () => { - await handleCreateTerminal(option.agent, { - initialPrompt: delivery.prompt, - onPromptSent: () => void clearDeliveredDiffComments(delivery.comments) - }) - }) - } - })) - : createTabAgentLoadState === 'loaded' - ? [ - { - id: 'noEnabledAgents', - label: t('session.usePanelRouteActions.noEnabledAgents', 'No Enabled Agents'), + ? { + id: 'presetsUnavailable', + label: t( + 'session.usePanelRouteActions.presetsUnavailable', + 'Agent Presets Unavailable' + ), + hint: errorHint, icon: Bot, disabled: true, onPress: () => {} } - ] - : createTabAgentLoadState === 'other-runtime' - ? [otherRuntimeAgentRow] - : createTabAgentLoadState === 'error' - ? [ - { - id: 'presetsUnavailable', - label: t( - 'session.usePanelRouteActions.presetsUnavailable', - 'Agent Presets Unavailable' - ), - hint: t('session.usePanelRouteActions.copyNotesHint', 'Copy notes instead'), - icon: Bot, - disabled: true, - onPress: () => {} - } - ] - : [] + : null + const createTabAgentStatusRow = agentStatusRow( + t('session.usePanelRouteActions.checkHostHint', 'Check the host connection') + ) + const sendDiffNotesStatusRow = agentStatusRow( + t('session.usePanelRouteActions.copyNotesHint', 'Copy notes instead') + ) + const sendDiffNotesAgentActions: ActionSheetAction[] = + pendingDiffNotesDelivery === null + ? [] + : sendDiffNotesStatusRow + ? [sendDiffNotesStatusRow] + : createTabAgentOptions.map((option) => ({ + id: `agent:${option.agent}`, + label: option.label, + hint: t('session.usePanelRouteActions.newSessionHint', 'New agent session'), + icon: Bot, + onPress: () => { + const delivery = pendingDiffNotesDelivery + setPendingDiffNotesDelivery(null) + if (!delivery) { + return + } + void sendDiffNotesToNewAgent(delivery, async () => { + await handleCreateTerminal(option.agent, { + initialPrompt: delivery.prompt, + onPromptSent: () => void clearDeliveredDiffComments(delivery.comments) + }) + }) + } + })) const handleSessionContentRowLayout = useCallback((event: LayoutChangeEvent) => { const width = Math.round(event.nativeEvent.layout.width) @@ -235,7 +189,7 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati ) } return { - createTabAgentActions, + createTabAgentStatusRow, sendDiffNotesAgentActions, handleSessionContentRowLayout, handlePanelTap, diff --git a/mobile/src/session/use-mobile-session-presentation.ts b/mobile/src/session/use-mobile-session-presentation.ts index 374e1db46e8..8f4b3c23331 100644 --- a/mobile/src/session/use-mobile-session-presentation.ts +++ b/mobile/src/session/use-mobile-session-presentation.ts @@ -76,11 +76,8 @@ export function useMobileSessionPresentation(scope: MobileSessionBulkCloseModel) presentedConnState === 'connected' ? showLoadingState && visibleTabs.length === 0 ? t('session.presentation.loadingTabs', 'Loading tabs') - : visibleTabs.length === 1 - ? t('session.presentation.oneTab', '1 tab') - : t('session.presentation.tabCount', '{{tabCount}} tabs', { - tabCount: visibleTabs.length - }) + : // The tab count lives in the tab bar, so the header names the branch instead. + (scope.worktreeBranch ?? MOBILE_SESSION_STATUS_LABELS.connected) : showConnectionRetry ? t('session.presentation.tapToRetry', '{{status}} — tap to retry', { status: verdictDisplayLabel(connectionVerdict) diff --git a/mobile/src/session/use-mobile-session-screen-state.ts b/mobile/src/session/use-mobile-session-screen-state.ts index bde653c4045..45fd4a9d886 100644 --- a/mobile/src/session/use-mobile-session-screen-state.ts +++ b/mobile/src/session/use-mobile-session-screen-state.ts @@ -1,7 +1,7 @@ import { useState, useRef, useMemo } from 'react' import { useTerminalSplitLayout } from '../terminal/session/use-terminal-split-layout' import { createLiveInputCapture } from '../terminal/session/live-input-capture' -import { Animated, type ScrollView } from 'react-native' +import { Animated } from 'react-native' import type { MobileTerminalLinkOpenMode } from '../storage/preferences' import type { TerminalKeyboardAvoidanceMetrics } from '../terminal/terminal-webview-contract' import { @@ -46,11 +46,6 @@ export function useMobileSessionScreenState(scope: MobileSessionFoundationModel) const activeSessionTabIdRef = useRef(null) // Preserve an explicit phone tab pick while a host snapshot is transiently incomplete. const selectedSessionTabIdRef = useRef(null) - const tabStripRef = useRef(null) - const tabStripOffsetRef = useRef(0) - const tabStripViewportWidthRef = useRef(0) - const tabStripContentWidthRef = useRef(0) - const tabLayoutsRef = useRef>(new Map()) const [markdownDocs, setMarkdownDocs] = useState>(new Map()) const markdownDocsRef = useRef>(new Map()) const [fileDocs, setFileDocs] = useState>(new Map()) @@ -134,11 +129,6 @@ export function useMobileSessionScreenState(scope: MobileSessionFoundationModel) setActiveSessionTabId, activeSessionTabIdRef, selectedSessionTabIdRef, - tabStripRef, - tabStripOffsetRef, - tabStripViewportWidthRef, - tabStripContentWidthRef, - tabLayoutsRef, markdownDocs, setMarkdownDocs, markdownDocsRef, diff --git a/mobile/src/session/use-mobile-session-stored-chat-tab.ts b/mobile/src/session/use-mobile-session-stored-chat-tab.ts index 6a88579b9a2..1c17a2c0a94 100644 --- a/mobile/src/session/use-mobile-session-stored-chat-tab.ts +++ b/mobile/src/session/use-mobile-session-stored-chat-tab.ts @@ -7,7 +7,7 @@ import { peekStoredChatTranscript, readStoredChatTranscript, storedTabChatTranscriptIdentity -} from './native-chat/mobile-native-chat-transcript-store' +} from './native-chat/transcript/mobile-native-chat-transcript-store' import type { MobileSessionKeyboardStateModel } from '../terminal/session/use-mobile-session-keyboard-state' /** diff --git a/mobile/src/session/use-mobile-session-view-mode.test.ts b/mobile/src/session/use-mobile-session-view-mode.test.ts index ad3edfa01bb..a0bb2a5fc72 100644 --- a/mobile/src/session/use-mobile-session-view-mode.test.ts +++ b/mobile/src/session/use-mobile-session-view-mode.test.ts @@ -146,19 +146,6 @@ describe('useMobileSessionViewMode', () => { expect(controller?.ready).toBe(true) }) - it('toggles from the effective view and persists the override', async () => { - await mount({ defaultView: 'chat' }) - - await act(async () => { - controller?.toggleTabChatView('t1') - await Promise.resolve() - }) - - expect(updateSessionViewOverride).toHaveBeenLastCalledWith('h', 'w', 't1', 'terminal') - expect(controller?.isTabChatView('t1')).toBe(false) - expect(readSessionViewOverridesPreference).toHaveBeenCalledTimes(1) - }) - it('toggles a terminal-default tab into chat', async () => { await mount({ defaultView: 'terminal' }) @@ -293,108 +280,6 @@ describe('useMobileSessionViewMode', () => { }) }) - it('submits rapid mutations in event order', async () => { - await mount({ defaultView: 'terminal' }) - act(() => controller?.toggleTabChatView('t1')) - act(() => controller?.toggleTabChatView('t2')) - - expect(updateSessionViewOverride).toHaveBeenNthCalledWith(1, 'h', 'w', 't1', 'chat') - expect(updateSessionViewOverride).toHaveBeenNthCalledWith(2, 'h', 'w', 't2', 'chat') - }) - - it('reconciles the latest optimistic override when persistence fails', async () => { - vi.mocked(updateSessionViewOverride).mockRejectedValue(new Error('storage unavailable')) - vi.mocked(readSessionViewOverridesPreference) - .mockResolvedValueOnce({ - overrides: new Map([['t1', 'terminal']]), - loaded: true - }) - .mockResolvedValueOnce({ - overrides: new Map([['t1', 'terminal']]), - loaded: true - }) - await mount({ - defaultView: 'terminal', - overrides: new Map([['t1', 'terminal']]) - }) - - await act(async () => { - controller?.toggleTabChatView('t1') - await Promise.resolve() - await Promise.resolve() - }) - - expect(controller?.isTabChatView('t1')).toBe(false) - expect(readSessionViewOverridesPreference).toHaveBeenCalledTimes(2) - }) - - it('does not let an older failed override roll back a newer choice', async () => { - const recoveryLoad = deferred>() - vi.mocked(updateSessionViewOverride) - .mockRejectedValueOnce(new Error('storage unavailable')) - .mockResolvedValueOnce(undefined) - await mount({ defaultView: 'terminal' }) - vi.mocked(readSessionViewOverridesPreference).mockReturnValueOnce( - recoveryLoad.promise.then((overrides) => ({ overrides, loaded: true })) - ) - - await act(async () => { - controller?.toggleTabChatView('t1') - await Promise.resolve() - }) - act(() => controller?.toggleTabChatView('t1')) - - await act(async () => { - recoveryLoad.resolve(new Map([['t1', 'chat']])) - await recoveryLoad.promise - await Promise.resolve() - }) - - expect(controller?.isTabChatView('t1')).toBe(false) - expect(updateSessionViewOverride).toHaveBeenNthCalledWith(2, 'h', 'w', 't1', 'terminal') - }) - - it('fails closed when both an override write and its recovery read fail', async () => { - vi.mocked(loadDefaultSessionView).mockResolvedValue('chat') - vi.mocked(updateSessionViewOverride).mockRejectedValue(new Error('storage unavailable')) - vi.mocked(readSessionViewOverridesPreference) - .mockResolvedValueOnce({ - overrides: new Map([['t1', 'terminal']]), - loaded: true - }) - .mockResolvedValueOnce({ overrides: new Map(), loaded: false }) - await mount({ - defaultView: 'chat', - overrides: new Map([['t1', 'terminal']]) - }) - - await act(async () => { - controller?.toggleTabChatView('t1') - await Promise.resolve() - await Promise.resolve() - }) - - expect(controller?.isTabChatView('t1')).toBe(false) - expect(readSessionViewOverridesPreference).toHaveBeenCalledTimes(2) - }) - - it('does not reload a failed override after the route unmounts', async () => { - const failedWrite = deferred() - vi.mocked(updateSessionViewOverride).mockReturnValue(failedWrite.promise) - await mount({ defaultView: 'terminal' }) - - act(() => controller?.toggleTabChatView('t1')) - act(() => renderer?.unmount()) - renderer = null - await act(async () => { - failedWrite.reject(new Error('storage unavailable')) - await Promise.resolve() - await Promise.resolve() - }) - - expect(readSessionViewOverridesPreference).toHaveBeenCalledTimes(1) - }) - it('fails closed to terminal when overrides cannot be read under a chat default', async () => { vi.mocked(loadDefaultSessionView).mockResolvedValue('chat') vi.mocked(readSessionViewOverridesPreference).mockResolvedValue({ diff --git a/mobile/src/session/use-mobile-structured-agent-options.chat-return.test.ts b/mobile/src/session/use-mobile-structured-agent-options.chat-return.test.ts deleted file mode 100644 index 0aa3feb9be5..00000000000 --- a/mobile/src/session/use-mobile-structured-agent-options.chat-return.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -// One phone chat view serves every chat tab, so returning to a chat must not wait on its next read. - -import { act } from 'react-test-renderer' -import { expect, it } from 'vitest' -import type { AgentSessionOptionsResult } from '../../../src/shared/agent-session/options/agent-session-options-result' -import { - BASE, - OPTIONS, - accepted, - currentValueOf, - deferred, - mountOptions, - optionsClient, - recordingMutate, - settle -} from './use-mobile-structured-agent-options.test-fixture' - -const COMMAND = 'compact' -const CHAT_A: AgentSessionOptionsResult = { - ...OPTIONS, - acceptsImages: true, - conversationCommands: [COMMAND], - current: { - model: 'gpt-fast', - effort: 'low', - permissionMode: 'own', - reportedPermissionMode: 'ask', - acceptedInputSources: { model: 'saved', effort: 'saved' } - } -} -const CHAT_B: AgentSessionOptionsResult = { - ...OPTIONS, - acceptsImages: false, - current: { model: 'gpt-live', effort: 'high', permissionMode: 'ask' } -} - -/** Host replies per chat; later reads for chat A wait until the test answers them. */ -function chats() { - const later: ReturnType>[] = [] - let readsOfA = 0 - let readingB = false - const client = optionsClient(() => { - if (readingB) { - return Promise.resolve(CHAT_B) - } - if (readsOfA++ === 0) { - return Promise.resolve(CHAT_A) - } - const reply = deferred() - later.push(reply) - return reply.promise - }) - const send = client.client.sendRequest - client.client.sendRequest = (method, params, options) => { - if (method === 'agentSession.options') { - readingB = JSON.stringify(params).includes('chat-b') - } - return send(method, params, options) - } - return { client, later } -} - -it("shows a chat's last answer on return, without its reports, until its new read lands", async () => { - const { client, later } = chats() - const { mutate } = recordingMutate(async () => accepted({ key: 'model', value: 'x' }, true)) - const harness = await mountOptions({ - ...BASE, - sessionId: 'chat-a', - fence: 1, - client: client.client, - mutate, - permissionOwn: true, - sendSelection: true - }) - await harness.rerender({ sessionId: 'chat-b', fence: 7 }) - expect(currentValueOf(harness.current().optionSnapshot, 'model')).toBe('gpt-live') - const commitsOnB = harness.commits.length - await harness.rerender({ sessionId: 'chat-a', fence: 2 }) - expect(later).toHaveLength(1) - // From the first commit on return, not only after a later effect catches up. - for (const commit of harness.commits.slice(commitsOnB)) { - expect(currentValueOf(commit.optionSnapshot, 'model')).toBe('gpt-fast') - } - const shown = harness.current() - expect(currentValueOf(shown.optionSnapshot, 'model')).toBe('gpt-fast') - expect(currentValueOf(shown.optionSnapshot, 'effort')).toBe('low') - expect(shown.conversationCommands).toEqual([COMMAND]) - expect(shown.acceptsImages).toBe(true) - // Saved permission intent survives a restart independently of runtime reports. - expect(currentValueOf(shown.optionSnapshot, 'permissionMode')).toBe('own') - expect(shown.sendSelection()).toEqual({ selection: {} }) - - await act(async () => { - later[0].resolve({ - ...CHAT_A, - conversationCommands: [], - current: { ...CHAT_A.current, model: 'gpt-live' } - }) - }) - await settle() - const answered = harness.current() - expect(currentValueOf(answered.optionSnapshot, 'model')).toBe('gpt-live') - expect(currentValueOf(answered.optionSnapshot, 'permissionMode')).toBe('own') - expect(answered.conversationCommands).toEqual([]) - expect(answered.sendSelection()).toEqual({ selection: { model: 'gpt-live', effort: 'low' } }) - await harness.unmount() -}) diff --git a/mobile/src/session/use-mobile-structured-agent-turn-timing.test.tsx b/mobile/src/session/use-mobile-structured-agent-turn-timing.test.tsx deleted file mode 100644 index c1b7ea7c7e3..00000000000 --- a/mobile/src/session/use-mobile-structured-agent-turn-timing.test.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { - AgentJournalRenderItem, - AgentJournalSubmission, - AgentJournalTurnLifecycle -} from '../../../src/shared/agent-session/journal/agent-session-journal-types' -import { agentJournalTurnBody } from '../../../src/shared/agent-session/lifecycle/agent-session-turn-record' -import { useMobileStructuredAgentTurnTiming } from './use-mobile-structured-agent-turn-timing' - -// Skew the host clock by an hour so unconverted timestamp leakage is obvious. -const HOST_START = 3_600_000_000 -const CLIENT_NOW = 12_345_000 - -function user(itemId: string, sequence: number): AgentJournalRenderItem { - return { - itemId, - revision: 0, - sequence, - observedAt: HOST_START + sequence, - body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: itemId }] } - } -} - -function lifecycle( - turnId: string, - sequence: number, - turn: Omit, - observedAt: number -): AgentJournalRenderItem { - return { - itemId: `lifecycle-${turnId}`, - revision: 1, - sequence, - observedAt, - body: agentJournalTurnBody({ turnId, ...turn }) - } -} - -type Timing = ReturnType -const NO_SUBMISSIONS: readonly AgentJournalSubmission[] = [] - -const SUBMISSIONS: AgentJournalSubmission[] = [ - { - clientMessageId: 'first', - fence: 1, - payloadFingerprint: 'fp', - dispatchState: 'accepted', - providerItemId: 'codex:thread:t1:0', - reason: null, - submittedAt: 1, - resolvedAt: 2 - } -] - -describe('useMobileStructuredAgentTurnTiming', () => { - let renderer: ReactTestRenderer | null = null - let timing: Timing | null = null - - function Harness({ - items, - submissions = NO_SUBMISSIONS, - turnId, - hostClock - }: { - items: readonly AgentJournalRenderItem[] - submissions?: readonly AgentJournalSubmission[] - turnId: string | null - hostClock?: { hostNow: number; receivedAt: number } - }): null { - timing = useMobileStructuredAgentTurnTiming({ items, submissions, hostClock }, turnId) - return null - } - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - timing = null - vi.useRealTimers() - }) - - it('hands settled host durations through and anchors the live counter locally, once per turn', () => { - vi.useFakeTimers() - vi.setSystemTime(CLIENT_NOW) - const items = [ - user('orca:first', 1), - lifecycle( - 't1', - 2, - { - state: 'interrupted', - startedAt: HOST_START, - completedAt: HOST_START + 61_000, - userItemId: 'codex:thread:t1:0' - }, - HOST_START + 5 - ), - user('u2', 3), - lifecycle( - 't2', - 4, - { state: 'running', startedAt: HOST_START + 100_000 }, - HOST_START + 102_500 - ) - ] - const submissions = SUBMISSIONS - act(() => { - renderer = create(createElement(Harness, { items, submissions, turnId: 't2' })) - }) - expect(timing?.workingStartedAt).toBe(CLIENT_NOW - 2_500) - expect([...timing!.settledTurns]).toEqual([ - ['orca:first', { startedAt: HOST_START, workedSeconds: 61, verdict: 'interruption' }], - ['u2', null] - ]) - - vi.setSystemTime(CLIENT_NOW + 30_000) - act(() => - renderer?.update(createElement(Harness, { items: [...items], submissions, turnId: 't2' })) - ) - expect(timing?.workingStartedAt).toBe(CLIENT_NOW - 2_500) - - act(() => renderer?.update(createElement(Harness, { items, turnId: null }))) - expect(timing?.workingStartedAt).toBeNull() - - // Convert host age through elapsed receipt time rather than either clock’s absolute timestamp. - vi.setSystemTime(CLIENT_NOW + 60_000) - const next = [ - ...items, - user('u3', 5), - lifecycle( - 't3', - 6, - { state: 'running', startedAt: HOST_START + 150_000 }, - HOST_START + 150_100 - ) - ] - act(() => - renderer?.update( - createElement(Harness, { - items: next, - turnId: 't3', - hostClock: { hostNow: HOST_START + 185_000, receivedAt: CLIENT_NOW + 55_000 } - }) - ) - ) - expect(timing?.workingStartedAt).toBe(CLIENT_NOW + 60_000 - 40_000) - }) - - it('leaves the anchor null when an older host records no start', () => { - vi.useFakeTimers() - vi.setSystemTime(CLIENT_NOW) - const items = [user('u1', 1), lifecycle('t1', 2, { state: 'running' }, HOST_START)] - act(() => { - renderer = create(createElement(Harness, { items, turnId: 't1' })) - }) - expect(timing?.workingStartedAt).toBeNull() - expect(timing?.settledTurns.size).toBe(0) - }) -}) diff --git a/mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts b/mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts deleted file mode 100644 index 70cac00aeb4..00000000000 --- a/mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { mobileNativeChatDraftDestination } from './native-chat/composer/mobile-native-chat-draft-inputs' -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { StructuredAgentId } from '../../../src/shared/agent-session/lifecycle/agent-session-provider-handle' -import type { MobileNativeChatSendOrigin } from './native-chat/mobile-native-chat-pending-echo' -import type { MobileNativeChatSendIntent } from './native-chat/mobile-native-chat-send' -import { useMobileStructuredNativeChatSendBridge } from './use-mobile-structured-native-chat-send-bridge' -import { - editMobileNativeChatDraft, - recordMobileNativeChatSkillPick, - readMobileNativeChatPickedSkills, - resetMobileNativeChatDraftStoreForTests -} from './native-chat/composer/mobile-native-chat-draft-store' - -const ORIGIN: MobileNativeChatSendOrigin = { - draftKey: 'draft', - draftDestination: mobileNativeChatDraftDestination('draft'), - draftEditGeneration: 0, - pendingKey: 'pending', - normalizedText: 'command', - boundary: { afterMessageId: null }, - sentAt: 0 -} - -describe('useMobileStructuredNativeChatSendBridge', () => { - let renderer: ReactTestRenderer | null = null - let sendWithOutcome: ReturnType['sendWithOutcome'] - const captureSendOrigin = vi.fn(() => ORIGIN) - const clearDraftForSend = vi.fn() - const onSendError = vi.fn() - const restoreRejectedDraft = vi.fn() - const sendStructured = vi.fn() - const holdUnconfirmed = vi.fn<(clientMessageId: string, onUnconfirmed: () => void) => void>() - - function Harness({ agent }: { agent: StructuredAgentId }): null { - sendWithOutcome = useMobileStructuredNativeChatSendBridge({ - agent, - captureSendOrigin, - clearDraftForSend, - onSendError, - restoreRejectedDraft, - sendStructured, - holdUnconfirmed - }).sendWithOutcome - return null - } - - function mount(agent: StructuredAgentId): void { - act(() => { - renderer = create(createElement(Harness, { agent })) - }) - } - - beforeEach(() => { - vi.clearAllMocks() - }) - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - resetMobileNativeChatDraftStoreForTests() - }) - - it('hands back an unconfirmed send with its skills beside newer picks only at the deadline', async () => { - recordMobileNativeChatSkillPick(ORIGIN.draftKey, { - token: '$review', - path: '/skills/review/SKILL.md' - }) - sendStructured.mockImplementation( - async ( - ...args: [unknown, unknown, unknown, unknown, unknown, MobileNativeChatSendIntent] - ) => { - args[5].onCreated?.('lost-id') - editMobileNativeChatDraft(ORIGIN.draftKey, '$newer work') - recordMobileNativeChatSkillPick(ORIGIN.draftKey, { - token: '$newer', - path: '/skills/newer/SKILL.md' - }) - return 'unknown' - } - ) - mount('codex') - - await expect(sendWithOutcome('/review')).resolves.toBe('unknown') - - expect(restoreRejectedDraft).not.toHaveBeenCalled() - expect(onSendError).not.toHaveBeenCalled() - expect(holdUnconfirmed).toHaveBeenCalledWith('lost-id', expect.any(Function)) - act(() => holdUnconfirmed.mock.calls[0]![1]()) - expect(restoreRejectedDraft).toHaveBeenCalledWith(ORIGIN, '/review') - expect(onSendError).toHaveBeenCalledWith('Not confirmed. Restored to the box.') - expect(readMobileNativeChatPickedSkills(ORIGIN.draftKey).map((skill) => skill.name)).toEqual([ - 'newer', - 'review' - ]) - }) - - it('puts a host command whose reply was lost back in the box', async () => { - sendStructured.mockResolvedValue('unknown') - mount('codex') - - await expect(sendWithOutcome('/compact')).resolves.toBe('unknown') - - expect(holdUnconfirmed).not.toHaveBeenCalled() - expect(restoreRejectedDraft).toHaveBeenCalledWith(ORIGIN, '/compact', { - preserveConversationCommands: true - }) - }) - - it('sends a queued message with its own skills and leaves the composer alone when refused', async () => { - recordMobileNativeChatSkillPick(ORIGIN.draftKey, { - token: '$draft', - path: '/skills/draft/SKILL.md' - }) - sendStructured.mockResolvedValue('rejected') - mount('codex') - const queuedSkill = { name: 'review', path: '/skills/review/SKILL.md' } - - await expect( - sendWithOutcome('$review the PR', undefined, undefined, undefined, undefined, { - queued: { - id: 'queued-1', - images: [], - files: [], - skills: [queuedSkill], - skillOrigin: '$review' - } - }) - ).resolves.toBe('rejected') - - expect(sendStructured.mock.calls[0]![4]).toEqual([queuedSkill]) - expect(clearDraftForSend).not.toHaveBeenCalled() - expect(restoreRejectedDraft).not.toHaveBeenCalled() - }) -}) diff --git a/mobile/src/session/use-mobile-structured-session-opening.ts b/mobile/src/session/use-mobile-structured-session-opening.ts index e7932042c0d..d3f03a6e134 100644 --- a/mobile/src/session/use-mobile-structured-session-opening.ts +++ b/mobile/src/session/use-mobile-structured-session-opening.ts @@ -5,9 +5,9 @@ import { useWorkspaceRouteHandoff } from '../navigation/workspace-route-handoff' import { openMobileStructuredSession, mobileStructuredSessionHref -} from './open-mobile-structured-session' +} from './native-chat/agent-session/open-mobile-structured-session' import type { StructuredSessionOpenSource } from '../../../src/shared/open-structured-agent-session' -import { mobileStructuredSessionOpenMessage } from './mobile-structured-session-open-message' +import { mobileStructuredSessionOpenMessage } from './native-chat/agent-session/mobile-structured-session-open-message' import { isStructuredAgentId } from '../../../src/shared/agent-session/lifecycle/agent-session-provider-handle-encoding' import type { MobileSessionTab } from './tabs/mobile-session-tabs' import type { useMobileSessionTabSwitching } from './tabs/use-mobile-session-tab-switching' diff --git a/mobile/src/session/use-throttled-latest-value.test.ts b/mobile/src/session/use-throttled-latest-value.test.ts deleted file mode 100644 index b439f394d77..00000000000 --- a/mobile/src/session/use-throttled-latest-value.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { useThrottledLatestValue } from './use-throttled-latest-value' - -describe('useThrottledLatestValue', () => { - let renderer: ReactTestRenderer | null = null - let latest: string | undefined - - function Harness({ value }: { value: string | undefined }): null { - latest = useThrottledLatestValue(value, 50) - return null - } - - function render(value: string | undefined): void { - act(() => { - renderer = create(createElement(Harness, { value })) - }) - } - - function update(value: string | undefined): void { - act(() => renderer?.update(createElement(Harness, { value }))) - } - - beforeEach(() => { - vi.useFakeTimers() - latest = undefined - }) - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - vi.useRealTimers() - }) - - it('emits the first frame immediately', () => { - render('a') - expect(latest).toBe('a') - }) - - it('holds rapid updates but eventually emits the latest value', () => { - render('a') - update('ab') - update('abc') - expect(latest).toBe('a') - act(() => { - vi.advanceTimersByTime(50) - }) - expect(latest).toBe('abc') - }) - - it('clears immediately and drops the trailing emit when the value goes undefined', () => { - render('a') - update('ab') - expect(latest).toBe('a') - update(undefined) - expect(latest).toBeUndefined() - act(() => { - vi.advanceTimersByTime(50) - }) - expect(latest).toBeUndefined() - }) -}) diff --git a/mobile/src/settings/notification-display-test.tsx b/mobile/src/settings/notification-display-test.tsx index 51280f27047..ecb737e0e5c 100644 --- a/mobile/src/settings/notification-display-test.tsx +++ b/mobile/src/settings/notification-display-test.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import { ListRow } from '../components/list/ListGroup' import { useAllHostClients } from '../transport/connection/use-all-host-clients' -import { loadHostCatalog } from '../transport/hosts/host-store' +import { loadHostCatalog } from '../host-catalog/host-store' import { pushDeliveryTest } from '../notifications/mobile-push-delivery-test-operations' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { useMobileTranslation } from '../i18n/use-mobile-translation' diff --git a/mobile/src/settings/pending-credential-cleanup-card.tsx b/mobile/src/settings/pending-credential-cleanup-card.tsx index af0a91ce081..0ef2d84df8e 100644 --- a/mobile/src/settings/pending-credential-cleanup-card.tsx +++ b/mobile/src/settings/pending-credential-cleanup-card.tsx @@ -9,8 +9,8 @@ import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { loadPendingHostCredentialCleanup, subscribePendingHostCredentialCleanup -} from '../transport/hosts/host-credential-cleanup' -import { retryPendingHostCredentialCleanup } from '../transport/hosts/host-store' +} from '../host-credentials/host-credential-cleanup' +import { retryPendingHostCredentialCleanup } from '../host-catalog/host-store' import { useMobileTranslation } from '../i18n/use-mobile-translation' import { SettingsSection } from './settings-screen-frame' diff --git a/mobile/src/source-control/folder/MobileFolderSourceControlPanel.test.tsx b/mobile/src/source-control/folder/MobileFolderSourceControlPanel.test.tsx deleted file mode 100644 index 102d52bad8b..00000000000 --- a/mobile/src/source-control/folder/MobileFolderSourceControlPanel.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { createElement, type ReactNode } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { FolderListRow } from './mobile-folder-attached-worktrees' - -const seen = vi.hoisted(() => { - const state: { covered: boolean; membersObserving: boolean[]; rowsSubscribed: boolean[] } = { - covered: false, - membersObserving: [], - rowsSubscribed: [] - } - return state -}) -vi.mock('react-native', () => ({ ScrollView: 'ScrollView', View: 'View' })) -vi.mock('lucide-react-native', () => ({ GitBranch: 'GitBranch' })) -vi.mock('expo-router', () => ({ useFocusEffect: () => {} })) -vi.mock('react-native-safe-area-context', () => ({ - SafeAreaView: 'SafeAreaView', - useSafeAreaInsets: () => ({ bottom: 0 }) -})) -vi.mock('../../components/MobileStateView', () => ({ - MobileLoadErrorState: 'MobileLoadErrorState', - MobileOfflineState: 'MobileOfflineState', - MobileStateView: 'MobileStateView' -})) -vi.mock('../../transport/connection/connection-retry-action', () => ({ - connectionRetryAction: () => () => {} -})) -vi.mock('../../transport/connection/host-client-hooks', () => ({ - useForceReconnect: () => () => {} -})) -vi.mock('../../components/bottom-drawer/BottomDrawer', () => ({ - OptionPager: ({ main }: { main: ReactNode }) => main, - useOptionPager: () => ({ page: null, open: () => {}, close: () => {} }) -})) -vi.mock('../../components/list/ListGroup', () => ({ ListGroup: 'ListGroup' })) -vi.mock('../../i18n/use-mobile-translation', () => ({ - useMobileTranslation: () => (_key: string, fallback: string) => fallback -})) -vi.mock('../../navigation/use-back-claim', () => ({ useBackClaim: () => {} })) -vi.mock('../../navigation/workspace-route-handoff', () => ({ - useWorkspaceRouteHandoff: () => ({ back: () => {} }) -})) -vi.mock('../../navigation/workspace-route', () => ({ - WorkspaceExecutionScope: 'WorkspaceExecutionScope' -})) -vi.mock('../../theme/mobile-theme', () => ({ colors: {}, spacing: { lg: 16 } })) -vi.mock('../../theme/themed-styles', () => ({ - themedStyleSheet: (styles: () => unknown) => styles() -})) -vi.mock('../../theme/use-mobile-color-scheme', () => ({ useMobileColorScheme: () => {} })) -vi.mock('../MobileSourceControlHeader', () => ({ - MobileSourceControlHeader: 'MobileSourceControlHeader' -})) -vi.mock('../MobileSourceControlPanel', () => ({ MobileSourceControlPanel: 'Panel' })) -vi.mock('../mobile-source-control-styles', () => ({ styles: {} })) -vi.mock('./MobileFolderEmptyState', () => ({ MobileFolderEmptyState: 'EmptyState' })) -vi.mock('../../components/host-protocol-gates-context', () => ({ - useOptionalHostProtocolGates: () => null -})) -vi.mock('./use-folder-create-flow', () => ({ - useFolderCreateFlow: () => ({ - onCreate: async () => {}, - covered: seen.covered, - composerElement: null, - toastElement: null - }) -})) -vi.mock('./MobileFolderWorktreeRow', () => ({ - MobileFolderWorktreeRow: (props: { subscribed: boolean }) => { - seen.rowsSubscribed.push(props.subscribed) - return null - } -})) -vi.mock('./use-mobile-folder-attached-worktrees', () => ({ - useMobileFolderAttachedWorktrees: (_hostId: string, _folderId: string, observing: boolean) => { - seen.membersObserving.push(observing) - return { - folder: { worktreeId: 'folder:f-1', repoId: 'folder-workspace:f-1', displayName: 'payments' }, - worktrees: [ROW], - desktopCapabilities: [], - serverHosts: [], - connState: 'connected', - loadError: null, - refetch: () => {} - } - } -})) - -import { MobileFolderSourceControlPanel } from './MobileFolderSourceControlPanel' - -const ROW: FolderListRow = { - worktreeId: 'repo-1::/w/payments', - repoId: 'repo-1', - repo: 'orca', - displayName: 'payments', - branch: 'payments' -} - -let renderer: ReactTestRenderer | null = null -afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - seen.covered = false - seen.membersObserving = [] - seen.rowsSubscribed = [] -}) - -describe('MobileFolderSourceControlPanel', () => { - it('pauses row status under the create form while the folder members stay live', () => { - seen.covered = true - act(() => { - renderer = create( - createElement(MobileFolderSourceControlPanel, { - hostId: 'host-1', - folderWorktreeId: 'folder:f-1', - name: 'payments', - embedded: true - }) - ) - }) - - expect(seen.rowsSubscribed.at(-1)).toBe(false) - expect(seen.membersObserving.at(-1)).toBe(true) - }) -}) diff --git a/mobile/src/source-control/folder/folder-source-repos-query.test.ts b/mobile/src/source-control/folder/folder-source-repos-query.test.ts index 523143d4bc8..89c1dcda79d 100644 --- a/mobile/src/source-control/folder/folder-source-repos-query.test.ts +++ b/mobile/src/source-control/folder/folder-source-repos-query.test.ts @@ -1,16 +1,6 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { stubRpcClient } from '../../transport/rpc/rpc-client-test-stub' -import { useHostQuery } from '../../query/host-query' -import { scopeRpcClientToExecutionHost } from '../../worktree/execution-host-scoped-rpc-client' -import { - documentQueryClient, - settleQueryNotifications -} from '../../test-support/rpc-recording/hook-mount' +import { describe, expect, it, vi } from 'vitest' import { FakeSession } from '../../transport/connection/mobile-endpoint-supervisor-test-fakes' import { - folderSourceCatalogQuery, serverFolderSourceCatalogQuery, selectFolderSourceRepos } from './folder-source-repos-query' @@ -64,61 +54,3 @@ describe('folder source repositories', () => { expect(selectFolderSourceRepos(catalog, 'group-1').map((found) => found.id)).toEqual(['api']) }) }) - -describe('folder source catalog reads', () => { - let renderer: ReactTestRenderer | null = null - const queries = documentQueryClient() - afterEach(async () => { - act(() => renderer?.unmount()) - renderer = null - await queries.end() - }) - - it.each([true, false])( - "reads a server folder's catalog on the desktop's one event stream (subscribed: %s)", - async (subscribed) => { - let eventStreams = 0 - const desktop = stubRpcClient({ - getLastConnectedAt: () => 1, - sendRequest: async (method) => ({ - id: '1', - ok: true, - result: method === 'repo.list' ? { repos: [] } : { groups: [GROUP] }, - _meta: { runtimeId: 'r' } - }), - subscribe: (method) => { - if (method === 'runtime.clientEvents.subscribe') { - eventStreams += 1 - } - return () => {} - } - }) - const server = scopeRpcClientToExecutionHost(desktop, 'runtime:env-1') - function Harness(): null { - useHostQuery( - folderSourceCatalogQuery, - { hostId: 'host-1', client: desktop }, - { exec: 'local' }, - {}, - { - subscribed: true - } - ) - useHostQuery( - serverFolderSourceCatalogQuery, - { hostId: 'host-1', client: server }, - { exec: 'runtime:env-1' }, - {}, - { subscribed } - ) - return null - } - await act(async () => { - renderer = create(queries.wrap(createElement(Harness))) - }) - await settleQueryNotifications() - - expect(eventStreams).toBe(1) - } - ) -}) diff --git a/mobile/src/source-control/mobile-branch-base-ref.ts b/mobile/src/source-control/mobile-branch-base-ref.ts index 49723877146..af5338c8344 100644 --- a/mobile/src/source-control/mobile-branch-base-ref.ts +++ b/mobile/src/source-control/mobile-branch-base-ref.ts @@ -3,7 +3,7 @@ import { refusedRpcMessageOrFallback } from '../transport/rpc/rpc-refusal-messag import { getRepoIdFromWorktreeId } from '../../../src/shared/worktree/id' import { isMobileGitUnavailableReply } from './mobile-git-status' import { repoBaseRefListRead, repoDefaultBaseRefRead } from './mobile-repo-base-ref-operations' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import type { RpcResponse } from '../transport/rpc/rpc-wire-types' import { worktreeSummaryRead } from './mobile-worktree-metadata-operations' import { @@ -18,7 +18,7 @@ export type MobileBranchCompareTarget = { } export async function resolveMobileBranchCompareBaseRef( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, compare?: MobileBranchCompareTarget, // A `worktree.show` reply the caller already asked for, so one open reads the worktree once. @@ -36,7 +36,7 @@ export type MobileBranchBaseRefs = { // Desktop's pair: the compare base can be the upstream, while PRs and rebases target the effective base. export async function resolveMobileBranchBaseRefs( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, compare?: MobileBranchCompareTarget, worktreeShow?: Promise @@ -84,7 +84,7 @@ export async function resolveMobileBranchBaseRefs( } async function readDefaultBaseRef( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, repoId: string ): Promise { const defaultReply = await repoDefaultBaseRefRead.request(client, { repo: `id:${repoId}` }) diff --git a/mobile/src/source-control/mobile-branch-compare-outcome.ts b/mobile/src/source-control/mobile-branch-compare-outcome.ts index ae5ea1d6a01..d7294be1806 100644 --- a/mobile/src/source-control/mobile-branch-compare-outcome.ts +++ b/mobile/src/source-control/mobile-branch-compare-outcome.ts @@ -1,5 +1,5 @@ import { translate } from '../i18n/mobile-i18n' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { refusedRpcMessageOrFallback } from '../transport/rpc/rpc-refusal-message' import { resolveMobileBranchBaseRefs, @@ -22,7 +22,7 @@ export type BranchCompareAttempt = BranchCompareOutcome & { // Return failures because callers void this load. export async function readBranchCompareOutcome( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, compare?: MobileBranchCompareTarget ): Promise { @@ -40,7 +40,7 @@ export async function readBranchCompareOutcome( } async function readCompare( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, baseRef: string | null ): Promise { diff --git a/mobile/src/source-control/mobile-commit-message-ai.ts b/mobile/src/source-control/mobile-commit-message-ai.ts index d0710dbd32a..11521f5e667 100644 --- a/mobile/src/source-control/mobile-commit-message-ai.ts +++ b/mobile/src/source-control/mobile-commit-message-ai.ts @@ -5,13 +5,13 @@ import { gitGenerateCommitMessageRun, type MobileGenerateCommitMessageResult } from './mobile-git-mutation-operations' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' export type { MobileGenerateCommitMessageResult } // Collapse refusals and malformed payloads to the same failed-generation verdict. export async function requestMobileCommitMessage( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { const reply = await gitGenerateCommitMessageRun.request(client, { @@ -34,7 +34,7 @@ export async function requestMobileCommitMessage( } export async function cancelMobileCommitMessage( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { const reply = await gitCancelGenerateCommitMessageRun.request(client, { diff --git a/mobile/src/source-control/mobile-git-history.ts b/mobile/src/source-control/mobile-git-history.ts index f51514fa65e..e1a5bf7ce2c 100644 --- a/mobile/src/source-control/mobile-git-history.ts +++ b/mobile/src/source-control/mobile-git-history.ts @@ -7,7 +7,7 @@ import type { } from './git-history-reply-schema' import { refusedRpcMessageOrFallback } from '../transport/rpc/rpc-refusal-message' import { gitHistoryRead } from './mobile-git-read-operations' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' export const MOBILE_GIT_HISTORY_PAGE_SIZE = 50 @@ -89,7 +89,7 @@ export type MobileGitHistoryPageRequest = { } export async function fetchMobileGitHistory( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, page?: MobileGitHistoryPageRequest ): Promise { diff --git a/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts b/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts index 4d663ce8d19..f57c7d4f0e2 100644 --- a/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts +++ b/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts @@ -10,7 +10,7 @@ import { prepareMobileHostedReviewCreateIntent, type MobileHostedReviewCreateIntentProgress } from './mobile-hosted-review-create-intent' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' type RunInput = { branch: string @@ -50,7 +50,7 @@ export function isMobileHostedReviewCommitFailure( } export async function runMobileHostedReviewCreateIntent( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: RunInput ): Promise { diff --git a/mobile/src/source-control/mobile-hosted-review-create-intent.ts b/mobile/src/source-control/mobile-hosted-review-create-intent.ts index 93d8a3bb50c..00e33f2943d 100644 --- a/mobile/src/source-control/mobile-hosted-review-create-intent.ts +++ b/mobile/src/source-control/mobile-hosted-review-create-intent.ts @@ -12,7 +12,7 @@ import { stageMobileHostedReviewPaths } from './mobile-hosted-review-git-preparation' import { applyMobileHostedReviewRemotePrerequisite } from './mobile-hosted-review-remote-prerequisite' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' export { mobileHostedReviewCreateIntentProgressMessage } from './hosted-review-copy' @@ -60,7 +60,7 @@ function hasUnresolvedConflicts(status: MobileGitStatusResult | null): boolean { } async function resolvePrefillFromStatus( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: Pick, status: MobileGitStatusResult | null @@ -74,7 +74,7 @@ async function resolvePrefillFromStatus( } async function ensureLocalChangesCommitted( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: PrepareInput, currentStatus: MobileGitStatusResult | null @@ -211,7 +211,7 @@ async function ensureLocalChangesCommitted( } export async function prepareMobileHostedReviewCreateIntent( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: PrepareInput ): Promise { diff --git a/mobile/src/source-control/mobile-hosted-review-git-preparation.ts b/mobile/src/source-control/mobile-hosted-review-git-preparation.ts index 94c4c652a16..b6d1c40d04c 100644 --- a/mobile/src/source-control/mobile-hosted-review-git-preparation.ts +++ b/mobile/src/source-control/mobile-hosted-review-git-preparation.ts @@ -13,7 +13,7 @@ import { } from './mobile-git-mutation-operations' import { gitStatusProjectionRead } from './mobile-git-read-operations' import type { MobileGitStatusResult } from './mobile-git-status' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' export type MobileHostedReviewStatusReadResult = | { ok: true; status: MobileGitStatusResult | null } @@ -22,7 +22,7 @@ export type MobileHostedReviewStatusReadResult = export type MobileHostedReviewMutationResult = { ok: true } | { ok: false; error: string } export async function readMobileHostedReviewGitStatus( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { const reply = await gitStatusProjectionRead.request(client, { worktree: `id:${worktreeId}` }) @@ -71,7 +71,7 @@ async function settleMobileHostedReviewMutation( } export function pushMobileHostedReviewBranch( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, params: RpcSendParams<'git.push'>, fallback: string ): Promise { @@ -83,7 +83,7 @@ export function pushMobileHostedReviewBranch( } export function fastForwardMobileHostedReviewBranch( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { return settleMobileHostedReviewMutation( @@ -97,7 +97,7 @@ export function fastForwardMobileHostedReviewBranch( } export function stageMobileHostedReviewPaths( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, filePaths: string[] ): Promise { @@ -109,7 +109,7 @@ export function stageMobileHostedReviewPaths( } export async function commitMobileHostedReviewStagedChanges( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, message: string ): Promise { diff --git a/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts b/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts index e3226f56593..d44b7a8acc9 100644 --- a/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts +++ b/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts @@ -8,7 +8,7 @@ import { pushMobileHostedReviewBranch } from './mobile-hosted-review-git-preparation' import { readBranchCompareOutcome } from './mobile-branch-compare-outcome' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' type RemotePrerequisiteInput = { status: MobileGitStatusResult | null @@ -17,7 +17,7 @@ type RemotePrerequisiteInput = { // Desktop publishes only a branch with commits beyond its base; read them only when that decides it. async function readBranchCommitsAhead( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { const compare = await readBranchCompareOutcome(client, worktreeId) @@ -27,7 +27,7 @@ async function readBranchCommitsAhead( } export async function applyMobileHostedReviewRemotePrerequisite( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, prefill: MobilePrPrefill, input: RemotePrerequisiteInput diff --git a/mobile/src/source-control/mobile-hosted-review-service.ts b/mobile/src/source-control/mobile-hosted-review-service.ts index 46f4f35a546..34c5d7111f6 100644 --- a/mobile/src/source-control/mobile-hosted-review-service.ts +++ b/mobile/src/source-control/mobile-hosted-review-service.ts @@ -11,7 +11,7 @@ import { } from './mobile-hosted-review-operations' import { pushMobileHostedReviewBranch } from './mobile-hosted-review-git-preparation' import { linkMobileHostedReview } from './mobile-pr-link' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' // Mobile ids combine repo::path; hosted-review RPCs need separate selectors. export function mobileRepoSelectorFromWorktreeId(worktreeId: string): string { @@ -30,7 +30,7 @@ export type MobileHostedReviewEligibilityInput = { } export async function fetchMobileHostedReviewEligibility( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: MobileHostedReviewEligibilityInput ): Promise { @@ -67,7 +67,7 @@ export type MobileHostedReviewPrefill = { } export async function resolveMobileHostedReviewPrefill( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { branch: string | undefined @@ -166,7 +166,7 @@ export type MobileHostedReviewCreateOutcome = // The form intentionally uses one actionable fallback for all push failures. async function pushMobileBranchBeforeCreate( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise<{ ok: true } | { ok: false; error: string }> { const error = translate( @@ -200,7 +200,7 @@ function formatMobileHostedReviewCreateError( } async function finishMobileHostedReviewCreateSuccess( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: MobileHostedReviewCreateInput, result: { number: number; url: string }, @@ -221,7 +221,7 @@ async function finishMobileHostedReviewCreateSuccess( } export async function createMobileHostedReview( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: MobileHostedReviewCreateInput ): Promise { diff --git a/mobile/src/source-control/mobile-pr-create-draft.ts b/mobile/src/source-control/mobile-pr-create-draft.ts index 66efde5aa68..d2431ffabde 100644 --- a/mobile/src/source-control/mobile-pr-create-draft.ts +++ b/mobile/src/source-control/mobile-pr-create-draft.ts @@ -1,6 +1,6 @@ import { translate } from '../i18n/mobile-i18n' import { refusedRpcMessageOrFallback } from '../transport/rpc/rpc-refusal-message' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { resolveCreateReviewSeedFromCommits } from '../../../src/shared/source-control/hosted-review/hosted-review-create-seed' import { normalizeCreateReviewBaseSearchResults, @@ -41,7 +41,7 @@ const HOSTED_PROVIDERS: readonly HostedReviewProvider[] = [ // The host's prefill decides provider, base and blockers; the branch's commits seed the text. export async function loadMobilePrCreateDraft( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: { branch: string; status: MobileGitStatusResult | null; commitsAhead?: number | null } ): Promise { @@ -84,7 +84,7 @@ function commitBody(message: string | undefined): string { } export async function generateMobilePrFields( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, input: { base: string; title: string; body: string; draft: boolean; provider: string } ): Promise { @@ -116,7 +116,7 @@ export async function generateMobilePrFields( // The host runs one generation per lane, so an abandoned one would delay the next. export async function cancelMobilePrFields( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { const reply = await gitCancelGeneratePullRequestFieldsRun.request(client, { @@ -126,7 +126,7 @@ export async function cancelMobilePrFields( } export async function searchMobilePrBaseRefs( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, query: string ): Promise { diff --git a/mobile/src/source-control/mobile-pr-link.ts b/mobile/src/source-control/mobile-pr-link.ts index ceef26f3649..90b7673f6b8 100644 --- a/mobile/src/source-control/mobile-pr-link.ts +++ b/mobile/src/source-control/mobile-pr-link.ts @@ -2,7 +2,7 @@ import { translate } from '../i18n/mobile-i18n' import type { RpcSendParams } from '../../../src/shared/rpc-contract/rpc-send-params' import { CLEARED_HOSTED_REVIEW_SELECTION_UPDATES } from '../../../src/shared/worktree/hosted-review-links' import { refusedRpcMessageOrFallback } from '../transport/rpc/rpc-refusal-message' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { worktreeLinkSet, worktreeSummaryRead, @@ -54,7 +54,7 @@ export function buildWorktreeSetHostedReviewLinkParams( // Refusal without a message uses screen copy; transport errors retain their own message. async function setWorktreeReviewLink( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, params: RpcSendParams<'worktree.set'>, fallback: string ): Promise { @@ -73,7 +73,7 @@ async function setWorktreeReviewLink( } export function linkMobilePr( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, prNumber: number ): Promise { @@ -88,7 +88,7 @@ export function linkMobilePr( } export async function linkMobileHostedReview( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, provider: string, number: number, @@ -108,7 +108,7 @@ export async function linkMobileHostedReview( // Like desktop, suppress the unlinked PR so branch discovery does not bring it straight back. export function unlinkMobilePr( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, prNumber: number ): Promise { @@ -130,7 +130,7 @@ const NO_GITHUB_PR_LINK: WorktreeGitHubPRLink = { // Desktop's durable clear for a link the branch has moved past; a failed write only repeats the check next load. export async function clearMobileWorktreeReviewLink( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, shouldApply: (worktree: MobileWorktreeSummary) => boolean ): Promise { @@ -151,14 +151,14 @@ export async function clearMobileWorktreeReviewLink( // Persisted links recover closed/merged PRs that branch lookup no longer returns. export async function fetchWorktreeGitHubPRLink( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { return (await readWorktreeSummary(client, worktreeId)) ?? NO_GITHUB_PR_LINK } async function readWorktreeSummary( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise { try { diff --git a/mobile/src/source-control/review/github-pr-draft-mutations.ts b/mobile/src/source-control/review/github-pr-draft-mutations.ts index 0839693ff53..58326da4894 100644 --- a/mobile/src/source-control/review/github-pr-draft-mutations.ts +++ b/mobile/src/source-control/review/github-pr-draft-mutations.ts @@ -1,11 +1,11 @@ -import type { RpcOperationSender } from '../../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../../transport/rpc/unvalidated-rpc-request-port' import { githubPrRequestParams, type GitHubPrRepoSlug } from './github-pr-repo-slug' import { githubPrDraftMark, githubPrReadyForReviewMark } from './github-pr-mutation-operations' import { settleGithubPrMutation, type GitHubPrMutationOutcome } from './github-pr-mutation-outcome' // Ready for review and Mark as draft: the same toggle on GitHub, one RPC each. export function fetchMarkPRReadyForReview( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -23,7 +23,7 @@ export function fetchMarkPRReadyForReview( } export function fetchMarkPRDraft( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; prRepo?: GitHubPrRepoSlug | null } ): Promise { diff --git a/mobile/src/source-control/review/github-pr-mutations.ts b/mobile/src/source-control/review/github-pr-mutations.ts index d31ed5f29f1..0c2ba0cd587 100644 --- a/mobile/src/source-control/review/github-pr-mutations.ts +++ b/mobile/src/source-control/review/github-pr-mutations.ts @@ -24,11 +24,11 @@ import { githubPrRequestParams, type GitHubPrRepoSlug } from './github-pr-repo-slug' -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' export function fetchMergePR( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; method?: GitHubPRMergeMethod; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -47,7 +47,7 @@ export function fetchMergePR( } export function fetchUpdatePRTitle( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; title: string; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -66,7 +66,7 @@ export function fetchUpdatePRTitle( } export function fetchUpdatePRBody( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; body: string; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -81,7 +81,7 @@ export function fetchUpdatePRBody( } export function fetchSetPRAutoMerge( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number @@ -105,7 +105,7 @@ export function fetchSetPRAutoMerge( } export function fetchUpdatePRState( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; state: 'open' | 'closed'; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -123,7 +123,7 @@ export function fetchUpdatePRState( } export function fetchRequestPRReviewers( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; reviewers: string[]; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -141,7 +141,7 @@ export function fetchRequestPRReviewers( } export function fetchRemovePRReviewers( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; reviewers: string[]; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -159,7 +159,7 @@ export function fetchRemovePRReviewers( } export function fetchRerunPRChecks( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number @@ -186,7 +186,7 @@ export function fetchRerunPRChecks( } export function fetchAddPRReviewCommentReply( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number @@ -223,7 +223,7 @@ export function fetchAddPRReviewCommentReply( } export function fetchAddIssueComment( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number; body: string; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -244,7 +244,7 @@ export function fetchAddIssueComment( // Thread resolution returns boolean confirmation; do not apply status-envelope void-success semantics. export function fetchResolveReviewThread( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { threadId: string; resolve: boolean; prRepo?: GitHubPrRepoSlug | null } ): Promise { @@ -269,7 +269,7 @@ export function fetchResolveReviewThread( // Issue comment edits use owner/repo/commentId rather than worktree-scoped params. export function fetchUpdateIssueComment( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, args: { owner: string; repo: string; host?: string; commentId: number; body: string } ): Promise { return settleGithubPrMutation(githubPrIssueCommentEdit, () => @@ -282,7 +282,7 @@ export function fetchUpdateIssueComment( } export function fetchDeleteIssueComment( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, args: { owner: string; repo: string; host?: string; commentId: number } ): Promise { return settleGithubPrMutation(githubPrIssueCommentDelete, () => diff --git a/mobile/src/source-control/review/github-pr-review-line-mutations.ts b/mobile/src/source-control/review/github-pr-review-line-mutations.ts index a7ccdc46fde..574ef37470a 100644 --- a/mobile/src/source-control/review/github-pr-review-line-mutations.ts +++ b/mobile/src/source-control/review/github-pr-review-line-mutations.ts @@ -5,14 +5,14 @@ import { type GitHubPrMutationOutcome } from './github-pr-mutation-outcome' import { githubPrRequestParams, type GitHubPrRepoSlug } from './github-pr-repo-slug' -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' // The diff screen's writes on a PR file: a new line comment and its viewed mark. // A new line comment on the PR's head commit; GitHub only accepts lines inside the file's diff hunks. export function fetchAddPRReviewComment( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number @@ -45,7 +45,7 @@ export function fetchAddPRReviewComment( } export function fetchSetPRFileViewed( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { pullRequestId: string diff --git a/mobile/src/source-control/review/github-pr-rpc.ts b/mobile/src/source-control/review/github-pr-rpc.ts index 0b46be0adc6..5c5e340a562 100644 --- a/mobile/src/source-control/review/github-pr-rpc.ts +++ b/mobile/src/source-control/review/github-pr-rpc.ts @@ -28,7 +28,7 @@ import { } from './github-pr-read-operations' import type { GitHubPrSettleableOperation } from './github-pr-mutation-outcome' import { githubPrRequestParams, type GitHubPrRepoSlug } from './github-pr-repo-slug' -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' export type GitHubPrReadOutcome = { ok: true; result: T } | { ok: false; error: string } @@ -64,7 +64,7 @@ async function settleGithubPrRead( // Gate the PR icon on a GitHub remote independently of whether this branch has an open PR. export function fetchGithubRepoSlug( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string ): Promise> { return settleGithubPrRead(githubPrRepoSlugRead, () => @@ -76,7 +76,7 @@ export function fetchGithubRepoSlug( } export function fetchHostedReviewForBranch( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { branch: string; linkedGitHubPR?: number | null } ): Promise> { @@ -94,7 +94,7 @@ export function fetchHostedReviewForBranch( // Raise decoded upstream-error here so the sidebar retains host text; it is not a protocol decode failure. export async function fetchPRForBranch( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { branch: string @@ -224,7 +224,7 @@ export async function fetchWorkItemDetails( } export function fetchPRChecks( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { prNumber: number @@ -251,7 +251,7 @@ export function fetchPRChecks( } export function fetchPRCheckDetails( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, worktreeId: string, args: { checkRunId?: number diff --git a/mobile/src/source-control/review/mobile-review-notes-operations.ts b/mobile/src/source-control/review/mobile-review-notes-operations.ts index ed7d8688140..3a70976f334 100644 --- a/mobile/src/source-control/review/mobile-review-notes-operations.ts +++ b/mobile/src/source-control/review/mobile-review-notes-operations.ts @@ -1,11 +1,7 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rpc/rpc-operation' import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' import { sessionWorktreeRecordSchema } from '../../worktree/session-worktree-record-reply-schema' -import '../../files/session-documents/mobile-markdown-tab-reply-schema' import '../../terminal/terminal-reply-schema' -import '../../terminal/session/mobile-session-terminal-reply-schema' -import '../../session/tabs/mobile-session-tab-write-reply-schema' -import '../../transport/rpc/rpc-uninterpreted-reply-schemas' import '../../terminal/quick-commands/mobile-quick-command-operations' // Preserve host refusal text for optimistic rollback; the reply's record carries the host's merged notes. diff --git a/mobile/src/source-control/review/pr-check-observation-contract.ts b/mobile/src/source-control/review/pr-check-observation-contract.ts index 432cbd30dab..761c6486c7a 100644 --- a/mobile/src/source-control/review/pr-check-observation-contract.ts +++ b/mobile/src/source-control/review/pr-check-observation-contract.ts @@ -1,7 +1,9 @@ import * as z from 'zod' import type { RpcClient } from '../../transport/rpc/rpc-client-contract' -import type { SendRequestOptions } from '../../transport/rpc/unvalidated-rpc-request-port' -import type { RpcOperationSender } from '../../transport/rpc/rpc-operation-sender' +import type { + SendRequestOptions, + UnvalidatedRpcRequestPort +} from '../../transport/rpc/unvalidated-rpc-request-port' import { captureGitHubInvocation, supportsExactGitHubOperations } from './github-target-invocation' import { readRuntimeStatusProbe, @@ -97,7 +99,7 @@ function statusFor(client: RpcClient, refresh: boolean): Promise { const { worktreeId, prRepo, write } = vars diff --git a/mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts b/mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts index 0850cb6d148..503eec21acc 100644 --- a/mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts +++ b/mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts @@ -11,7 +11,7 @@ import { reviewTerminalListRead, reviewTerminalSendRun } from './mobile-review-t import type { MobileReviewTerminalTab } from './review-terminal-reply-schema' import { launchAgentWithPrompt } from './pr-ai-triage-launch' import { interpretOrThrowRefusalMessage } from '../../transport/rpc/rpc-refusal-message' -import { healMobileNativeChatStaleInput } from '../../session/native-chat/mobile-native-chat-stale-input' +import { healMobileNativeChatStaleInput } from '../../session/native-chat/composer/mobile-native-chat-stale-input' import type { ReviewScreenState } from './mobile-diff-review-screen-model' import type { ReviewSheetIntents } from './mobile-diff-review-sheets' diff --git a/mobile/src/source-control/review/use-mobile-pr-actions.ts b/mobile/src/source-control/review/use-mobile-pr-actions.ts index 2bd5b017177..740864512d4 100644 --- a/mobile/src/source-control/review/use-mobile-pr-actions.ts +++ b/mobile/src/source-control/review/use-mobile-pr-actions.ts @@ -12,7 +12,7 @@ import { } from './github-pr-mutations' import { fetchMarkPRDraft, fetchMarkPRReadyForReview } from './github-pr-draft-mutations' import type { GitHubPrRepoSlug } from './github-pr-repo-slug' -import type { RpcOperationSender } from '../../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../../transport/rpc/unvalidated-rpc-request-port' import { PrActionsEngine, type PrActionBusyKey } from './pr-actions-engine' import './pr-action-mutation-contract' import type { PrActionMutations } from './pr-action-mutation-contract' @@ -28,7 +28,7 @@ export type PrActionsInput = { mutations?: PrActionMutations } -function realMutations(client: RpcOperationSender, worktreeId: string): PrActionMutations { +function realMutations(client: UnvalidatedRpcRequestPort, worktreeId: string): PrActionMutations { return { mergePR: (args) => fetchMergePR(client, worktreeId, args), setPRAutoMerge: (args) => fetchSetPRAutoMerge(client, worktreeId, args), diff --git a/mobile/src/tasks/jira-mobile-connection.ts b/mobile/src/tasks/jira-mobile-connection.ts index 0bd5fefac7b..d461d07d48c 100644 --- a/mobile/src/tasks/jira-mobile-connection.ts +++ b/mobile/src/tasks/jira-mobile-connection.ts @@ -1,5 +1,5 @@ import type { JiraSite, JiraSiteSelection } from '../../../src/shared/jira/jira-types' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import type { RpcResponse } from '../transport/rpc/rpc-wire-types' import { jiraConnectionStatusSchema } from './jira-reply-schema' import { jiraConnectionStatusProbe, jiraConnectionStatusRead } from './mobile-jira-operations' @@ -88,7 +88,7 @@ function resolveJiraSiteSelection( // Explicit refreshes surface failures instead of presenting a setup prompt. export async function readJiraConnection( - client: RpcOperationSender + client: UnvalidatedRpcRequestPort ): Promise { return extractJiraConnection( jiraConnectionStatusRead.interpret(await jiraConnectionStatusRead.request(client)) diff --git a/mobile/src/tasks/jira-mobile-url-lookup.test.ts b/mobile/src/tasks/jira-mobile-url-lookup.test.ts index 22a2163fd24..30c5e2cbee9 100644 --- a/mobile/src/tasks/jira-mobile-url-lookup.test.ts +++ b/mobile/src/tasks/jira-mobile-url-lookup.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import type { JiraIssue, JiraSite } from '../../../src/shared/jira/jira-types' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { lookupJiraIssueByUrl } from './jira-mobile-url-lookup' const acme: JiraSite = { @@ -29,7 +29,7 @@ function issue(overrides: Partial = {}): JiraIssue { } } -function clientReturning(result: unknown, sendRequest = vi.fn()): RpcOperationSender { +function clientReturning(result: unknown, sendRequest = vi.fn()): UnvalidatedRpcRequestPort { sendRequest.mockResolvedValue({ ok: true, result }) return { sendRequest } } diff --git a/mobile/src/tasks/jira-mobile-url-lookup.ts b/mobile/src/tasks/jira-mobile-url-lookup.ts index e2155e6b330..0db57d4197b 100644 --- a/mobile/src/tasks/jira-mobile-url-lookup.ts +++ b/mobile/src/tasks/jira-mobile-url-lookup.ts @@ -4,12 +4,12 @@ import { isResolvedJiraIssueMatch, parseJiraIssueUrl } from '../../../src/shared/jira/jira-issue-url' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { jiraIssueRead } from './mobile-jira-operations' // Match a connected site before resolving keys that may exist on several sites. export async function lookupJiraIssueByUrl( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, value: string, sites: readonly JiraSite[] ): Promise { diff --git a/mobile/src/tasks/mobile-jira-task-source.ts b/mobile/src/tasks/mobile-jira-task-source.ts index ae640eeede6..f0d44602ffd 100644 --- a/mobile/src/tasks/mobile-jira-task-source.ts +++ b/mobile/src/tasks/mobile-jira-task-source.ts @@ -3,7 +3,7 @@ import type { JiraIssueFilter, JiraSiteSelection } from '../../../src/shared/jira/jira-types' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { toJiraDetailComments } from './jira-mobile-issue-read' import { jiraIssueCommentsRead, @@ -18,7 +18,7 @@ const DETAIL_TIMEOUT_MS = 30_000 // Raw JQL replaces the selected preset, matching desktop. export async function fetchJiraTaskIssues( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, args: { jql: string; filter: JiraIssueFilter; siteId: JiraSiteSelection | null } ): Promise { const jql = args.jql.trim() @@ -41,7 +41,7 @@ export async function fetchJiraTaskIssues( } export async function fetchJiraIssueDetail( - client: RpcOperationSender, + client: UnvalidatedRpcRequestPort, args: { key: string; siteId?: string } ): Promise<{ issue: JiraIssue; comments: ReturnType }> { const params = { key: args.key, siteId: args.siteId } diff --git a/mobile/src/tasks/mobile-tasks-host-subtitle.tsx b/mobile/src/tasks/mobile-tasks-host-subtitle.tsx index e6cf9004e64..07d2a20cc03 100644 --- a/mobile/src/tasks/mobile-tasks-host-subtitle.tsx +++ b/mobile/src/tasks/mobile-tasks-host-subtitle.tsx @@ -1,7 +1,7 @@ import { StyleSheet, Text } from 'react-native' import { hostScreenStyles } from '../host-screen/host-screen-styles' -import { useHostProfile } from '../transport/hosts/use-host-profile' -import { useHostDisplay } from '../transport/hosts/use-host-display' +import { useHostProfile } from '../host-catalog/use-host-profile' +import { useHostDisplay } from '../host-descriptors/use-host-display' import { useMobileColorScheme } from './mobile-tasks-dependencies' export function MobileTasksHostSubtitle({ hostId }: { hostId: string }) { diff --git a/mobile/src/tasks/mobile-tasks-list-state.tsx b/mobile/src/tasks/mobile-tasks-list-state.tsx index b190e73ba31..35879dcb433 100644 --- a/mobile/src/tasks/mobile-tasks-list-state.tsx +++ b/mobile/src/tasks/mobile-tasks-list-state.tsx @@ -9,8 +9,8 @@ import { } from '../components/MobileStateView' import { useForceReconnect } from '../transport/connection/host-client-hooks' import { isConnectionOutage } from '../transport/connection/connection-health' -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 { spacing } from '../theme/mobile-theme' import { translate } from '../i18n/mobile-i18n' import type { ConnectionPresentationModel } from './use-mobile-tasks-connection-presentation' diff --git a/mobile/src/tasks/use-task-host-repos.ts b/mobile/src/tasks/use-task-host-repos.ts index 2fa9010fd1a..e91ef7df7d3 100644 --- a/mobile/src/tasks/use-task-host-repos.ts +++ b/mobile/src/tasks/use-task-host-repos.ts @@ -1,7 +1,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import type { ExecutionHostId } from '../../../src/shared/execution-host' import type { HostContextLabelSources } from '../../../src/shared/worktree/host-context-labels' -import { useHostDescriptor } from '../transport/hosts/host-descriptor-store' +import { useHostDescriptor } from '../host-descriptors/host-descriptor-store' import type { RpcClient } from '../transport/rpc/rpc-client-contract' import type { ConnectionState } from '../transport/connection/connection-state' import { withServerHosts } from '../worktree/server-workspaces' diff --git a/mobile/src/tasks/worktree-create-retry.ts b/mobile/src/tasks/worktree-create-retry.ts index e85c4dcd67b..187a68096d6 100644 --- a/mobile/src/tasks/worktree-create-retry.ts +++ b/mobile/src/tasks/worktree-create-retry.ts @@ -20,7 +20,7 @@ import { type WorktreeCreateAgentLaunch } from './agent-launch-request' import { isAgentLaunchReplayUnsupportedRefusal } from '../../../src/shared/agent-launch/contracts/agent-launch-replay-refusal' -import { structuredSessionOperationId } from '../session/structured-session-operation-id' +import { structuredSessionOperationId } from '../session/native-chat/agent-session/structured-session-operation-id' import { WORKTREE_CREATE_TIMEOUT_MS } from './workspace-create-timeout' import type { WorkspaceCreateParams } from './workspace-create-params' import type { diff --git a/mobile/src/terminal/mobile-terminal-query-reply.ts b/mobile/src/terminal/mobile-terminal-query-reply.ts index c6e5d6c1e6a..7273a2225db 100644 --- a/mobile/src/terminal/mobile-terminal-query-reply.ts +++ b/mobile/src/terminal/mobile-terminal-query-reply.ts @@ -1,6 +1,6 @@ import { isTerminalQueryReply } from '../../../src/shared/terminal/protocol/queries/terminal-query-reply' import { PROGRAM_STATUS_OSC_PREFIX } from '../../../src/shared/program-status/protocol' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import { terminalInputSend } from './mobile-terminal-operations' type TerminalSubscriptionRegistry = { @@ -9,7 +9,7 @@ type TerminalSubscriptionRegistry = { type MobileTerminalQueryReplyOptions = { bytes: string - client: RpcOperationSender | null + client: UnvalidatedRpcRequestPort | null clientId: string | null connected: boolean handle: string diff --git a/mobile/src/terminal/quick-commands/QuickCommandRow.test.ts b/mobile/src/terminal/quick-commands/QuickCommandRow.test.ts deleted file mode 100644 index 3728dff5c4f..00000000000 --- a/mobile/src/terminal/quick-commands/QuickCommandRow.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { TerminalQuickCommand } from '../../../../src/shared/terminal/quick-commands/terminal-quick-command-types' -import { QuickCommandRow } from './QuickCommandRow' - -const clipboard = vi.hoisted(() => ({ setStringAsync: vi.fn(() => Promise.resolve(true)) })) -const haptics = vi.hoisted(() => ({ notificationAsync: vi.fn(() => Promise.resolve()) })) - -vi.mock('react-native', () => ({ - Platform: { OS: 'ios' }, - Pressable: 'Pressable', - StyleSheet: { create: (styles: T) => styles, hairlineWidth: 1 }, - Text: 'Text', - View: 'View' -})) - -vi.mock('lucide-react-native', () => ({ - Check: 'Check', - Copy: 'Copy', - Pencil: 'Pencil', - Play: 'Play', - Trash2: 'Trash2' -})) - -vi.mock('expo-clipboard', () => clipboard) - -vi.mock('expo-haptics', () => ({ - ...haptics, - performAndroidHapticsAsync: vi.fn(() => Promise.resolve()), - AndroidHaptics: { Reject: 'reject' }, - NotificationFeedbackType: { Error: 'error', Success: 'success' } -})) - -vi.mock('../../components/MobileAgentIcon', () => ({ MobileAgentIcon: 'MobileAgentIcon' })) - -const COMMAND: TerminalQuickCommand = { - id: 'qc-1', - label: 'Run tests', - command: 'run the tests', - appendEnter: true -} - -// Require visible error feedback and haptics when clipboard writes reject; a failed write must never show Copied. -describe('the quick-command row when the pasteboard refuses the text', () => { - let renderer: ReactTestRenderer | null = null - - beforeEach(() => { - clipboard.setStringAsync.mockReset() - haptics.notificationAsync.mockReset() - haptics.notificationAsync.mockImplementation(() => Promise.resolve()) - }) - - afterEach(() => { - act(() => renderer?.unmount()) - renderer = null - }) - - // Find by every copy-state label so feedback transitions cannot make the control disappear from assertions. - const COPY_LABELS = new Set([ - `Copy ${COMMAND.label}`, - 'Copied', - "Couldn't copy", - 'Nothing to copy' - ]) - - function copyButton() { - const button = renderer!.root - .findAll((node) => node.props.accessibilityRole === 'button') - .find((node) => COPY_LABELS.has(String(node.props.accessibilityLabel))) - if (button === undefined) { - throw new Error('the row has no copy button') - } - return button - } - - function rowProps() { - return { - command: COMMAND, - first: true, - onLaunch: vi.fn(), - onEdit: vi.fn(), - onDelete: vi.fn(), - disabled: false - } - } - - it('emits nothing at all when the row is gone before the refusal arrives', async () => { - // Unmount before rejection; feedback without a surviving row would buzz with no explanation. - let refuse: ((error: Error) => void) | undefined - clipboard.setStringAsync.mockImplementation( - () => - new Promise((_resolve, reject) => { - refuse = reject - }) - ) - await act(async () => { - renderer = create(createElement(QuickCommandRow, rowProps())) - }) - await act(async () => { - copyButton().props.onPress() - }) - act(() => renderer?.unmount()) - renderer = null - await act(async () => { - refuse?.(new Error('pasteboard refused')) - }) - expect(haptics.notificationAsync).not.toHaveBeenCalled() - }) -}) diff --git a/mobile/src/terminal/quick-commands/mobile-quick-command-operations.ts b/mobile/src/terminal/quick-commands/mobile-quick-command-operations.ts index c019aa7e9c2..f3f9eee7fb8 100644 --- a/mobile/src/terminal/quick-commands/mobile-quick-command-operations.ts +++ b/mobile/src/terminal/quick-commands/mobile-quick-command-operations.ts @@ -2,13 +2,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rp import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' import '../terminal-reply-schema' import { terminalQuickCommandsSchema } from './mobile-quick-command-reply-schema' -import '../../session/agent-launch/mobile-agent-detection-reply-schema' -import '../../files/session-documents/mobile-markdown-tab-reply-schema' -import '../../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../session/mobile-session-terminal-reply-schema' -import '../../worktree/session-worktree-record-reply-schema' -import '../../session/native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../../files/workspace-file-paths-reply-schema' export const quickCommandsReader = rpcResultVariant( 'terminal-quick-commands', @@ -26,8 +19,6 @@ export const quickCommandsRead = bindDeferredRpcOperation( }) ) -import '../../session/tabs/mobile-session-tab-write-reply-schema' - export const quickCommandsWrite = bindDeferredRpcOperation( defineRpcOperation({ name: 'settings.quick-commands-write', diff --git a/mobile/src/terminal/quick-commands/mobile-quick-command-reply-schema.ts b/mobile/src/terminal/quick-commands/mobile-quick-command-reply-schema.ts index fb6a6ac66c1..2be3e0b35ca 100644 --- a/mobile/src/terminal/quick-commands/mobile-quick-command-reply-schema.ts +++ b/mobile/src/terminal/quick-commands/mobile-quick-command-reply-schema.ts @@ -1,5 +1,4 @@ import * as z from 'zod' -import '../../../../src/shared/zod-salvage' // Preserve null for unreadable canonical command lists so load/save cannot erase host commands with an invented empty list. export const terminalQuickCommandsSchema = z diff --git a/mobile/src/terminal/session/MobileTerminalRenameModal.tsx b/mobile/src/terminal/session/MobileTerminalRenameModal.tsx index ec26c4d9d95..50408efcc77 100644 --- a/mobile/src/terminal/session/MobileTerminalRenameModal.tsx +++ b/mobile/src/terminal/session/MobileTerminalRenameModal.tsx @@ -2,8 +2,8 @@ import { BottomDrawer } from '../../components/bottom-drawer/BottomDrawer' import { TextInputSheetContent } from '../../components/TextInputModal' import { useDraftRenameSheet } from '../../components/use-draft-rename-sheet' import { useMobileTranslation } from '../../i18n/use-mobile-translation' -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 type { MobileSessionController } from '../../session/use-mobile-session-controller' export function MobileTerminalRenameModal({ diff --git a/mobile/src/session/mobile-chat-fork-create-feedback.ts b/mobile/src/terminal/session/mobile-chat-fork-create-feedback.ts similarity index 74% rename from mobile/src/session/mobile-chat-fork-create-feedback.ts rename to mobile/src/terminal/session/mobile-chat-fork-create-feedback.ts index 940eed30732..ece73f51109 100644 --- a/mobile/src/session/mobile-chat-fork-create-feedback.ts +++ b/mobile/src/terminal/session/mobile-chat-fork-create-feedback.ts @@ -1,11 +1,11 @@ -import { isRpcDeliveryUnknown } from '../transport/rpc/rpc-delivery-ambiguity' -import '../transport/connection/stable-logical-rpc-client' -import { isLogicalClientCutoverError } from '../transport/connection/logical-client-cutover-error' -import { RpcIncompatibleReplyError } from '../transport/rpc/rpc-incompatible-reply-error' -import { PROJECT_RUNTIME_REPAIR_REQUIRED_CODE } from '../../../src/shared/project-runtime-repair-message' -import { SESSION_TABS_CHAT_FORK_CLI_RUNTIME_CAPABILITY } from '../../../src/shared/session-tabs-runtime-capabilities' -import type { RpcResponse } from '../transport/rpc/rpc-wire-types' -import { createdTerminalDesktopWarning } from '../terminal/session/mobile-session-terminal-reply-schema' +import { isRpcDeliveryUnknown } from '../../transport/rpc/rpc-delivery-ambiguity' +import '../../transport/connection/stable-logical-rpc-client' +import { isLogicalClientCutoverError } from '../../transport/connection/logical-client-cutover-error' +import { RpcIncompatibleReplyError } from '../../transport/rpc/rpc-incompatible-reply-error' +import { PROJECT_RUNTIME_REPAIR_REQUIRED_CODE } from '../../../../src/shared/project-runtime-repair-message' +import { SESSION_TABS_CHAT_FORK_CLI_RUNTIME_CAPABILITY } from '../../../../src/shared/session-tabs-runtime-capabilities' +import type { RpcResponse } from '../../transport/rpc/rpc-wire-types' +import { createdTerminalDesktopWarning } from './mobile-session-terminal-reply-schema' export function hostSupportsMobileChatFork( hostCapabilities: readonly string[] | undefined diff --git a/mobile/src/terminal/session/mobile-session-terminal-operations.ts b/mobile/src/terminal/session/mobile-session-terminal-operations.ts index 5de3473f24c..505d6058da8 100644 --- a/mobile/src/terminal/session/mobile-session-terminal-operations.ts +++ b/mobile/src/terminal/session/mobile-session-terminal-operations.ts @@ -2,13 +2,7 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../../transport/rp import { rpcResultVariant } from '../../transport/rpc/rpc-operation-result-reader' import { terminalComposerDraftSchema, terminalSendAcceptedSchema } from '../terminal-reply-schema' import { sessionTerminalInventorySchema } from './mobile-session-terminal-reply-schema' -import '../../session/agent-launch/mobile-agent-detection-reply-schema' -import '../../files/session-documents/mobile-markdown-tab-reply-schema' import { sessionWriteUnreadReplySchema } from '../../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../../worktree/session-worktree-record-reply-schema' -import '../../session/native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../quick-commands/mobile-quick-command-reply-schema' -import '../../files/workspace-file-paths-reply-schema' export const nativeChatTerminalDraftRead = bindDeferredRpcOperation( defineRpcOperation({ @@ -31,7 +25,6 @@ export const sessionTerminalListRead = bindDeferredRpcOperation( }) ) -import '../../session/tabs/mobile-session-tab-write-reply-schema' import '../quick-commands/mobile-quick-command-operations' // Keep chat writes distinct from query/accessory responders: losing a reply here means unknown delivery and forbids automatic retry. diff --git a/mobile/src/terminal/session/use-mobile-session-keyboard-state.ts b/mobile/src/terminal/session/use-mobile-session-keyboard-state.ts index ec532d01bf5..785f282bb4f 100644 --- a/mobile/src/terminal/session/use-mobile-session-keyboard-state.ts +++ b/mobile/src/terminal/session/use-mobile-session-keyboard-state.ts @@ -1,10 +1,9 @@ -import { useEffect, useCallback, useRef } from 'react' +import { useEffect, useCallback } from 'react' import { currentSoftKeyboardHeight, subscribeSoftKeyboard } from '../../platform/keyboard-occlusion' import { useTerminalViewportRefit } from '../terminal-viewport-refit' import { removeCustomKey, type CustomKey } from '../terminal-custom-keys' import { useWorkspaceExecutionHost } from '../../navigation/workspace-execution-host' import { writeLastVisitedWorktree } from '../../worktree/last-visited-worktree-by-host' -import { resolveTabStripScrollOffset } from '../../session/tabs/tab-strip-scroll' import type { MobileSessionLifecycleModel } from '../../session/use-mobile-session-lifecycle' export type MobileSessionKeyboardScope = Pick< @@ -12,7 +11,6 @@ export type MobileSessionKeyboardScope = Pick< | 'activeHandleRef' | 'visibleTerminalHandlesRef' | 'terminalPaneViewportsRef' - | 'activeSessionTabId' | 'clientRef' | 'connState' | 'deviceTokenRef' @@ -22,11 +20,6 @@ export type MobileSessionKeyboardScope = Pick< | 'setShowCustomKeyModal' | 'showNativeChatRef' | 'subscribeToTerminal' - | 'tabLayoutsRef' - | 'tabStripContentWidthRef' - | 'tabStripOffsetRef' - | 'tabStripRef' - | 'tabStripViewportWidthRef' | 'terminalFrameRef' | 'terminalRefs' | 'terminals' @@ -45,12 +38,6 @@ export function useMobileSessionKeyboardState(scope: MobileSessionKeyboardScope) connState, terminals, terminalTextScale, - activeSessionTabId, - tabStripRef, - tabStripOffsetRef, - tabStripViewportWidthRef, - tabStripContentWidthRef, - tabLayoutsRef, setCustomKeys, setShowCustomKeyModal, deviceTokenRef, @@ -98,55 +85,6 @@ export function useMobileSessionKeyboardState(scope: MobileSessionKeyboardScope) ) }, [notifyKeyboardVisibility]) - const heldTabStripRevealRef = useRef | null>(null) - const cancelHeldTabStripReveal = useCallback(() => { - if (heldTabStripRevealRef.current) { - clearTimeout(heldTabStripRevealRef.current) - heldTabStripRevealRef.current = null - } - }, []) - useEffect(() => cancelHeldTabStripReveal, [cancelHeldTabStripReveal]) - // Hold strip movement through double-tap detection so its second tap reaches the same tab. - const holdTabStripReveal = useCallback( - (offset: number, delayMs: number) => { - cancelHeldTabStripReveal() - heldTabStripRevealRef.current = setTimeout(() => { - heldTabStripRevealRef.current = null - if (offset !== tabStripOffsetRef.current) { - tabStripOffsetRef.current = offset - tabStripRef.current?.scrollTo({ x: offset, animated: true }) - } - }, delayMs) - }, - [cancelHeldTabStripReveal] - ) - - const scrollActiveTabIntoView = useCallback((tabId: string | null, animated: boolean) => { - if (!tabId || heldTabStripRevealRef.current) { - return - } - const layout = tabLayoutsRef.current.get(tabId) - if (!layout) { - return - } - const nextOffset = resolveTabStripScrollOffset({ - tabX: layout.x, - tabWidth: layout.width, - viewportWidth: tabStripViewportWidthRef.current, - contentWidth: tabStripContentWidthRef.current, - currentOffset: tabStripOffsetRef.current - }) - if (nextOffset !== tabStripOffsetRef.current) { - tabStripOffsetRef.current = nextOffset - tabStripRef.current?.scrollTo({ x: nextOffset, animated }) - } - }, []) - - useEffect(() => { - const id = requestAnimationFrame(() => scrollActiveTabIntoView(activeSessionTabId, true)) - return () => cancelAnimationFrame(id) - }, [activeSessionTabId, scrollActiveTabIntoView]) - const executionHost = useWorkspaceExecutionHost() useEffect(() => { if (hostId && worktreeId) { @@ -171,9 +109,6 @@ export function useMobileSessionKeyboardState(scope: MobileSessionKeyboardScope) notifyTerminalFrameWidth, notifyKeyboardVisibility, notifyTerminalCellBoxChange, - scrollActiveTabIntoView, - holdTabStripReveal, - cancelHeldTabStripReveal, handleDeleteCustomKey, handleManageShortcuts } diff --git a/mobile/src/terminal/session/use-mobile-session-terminal-create-actions.ts b/mobile/src/terminal/session/use-mobile-session-terminal-create-actions.ts index eef7d2e690e..cb0f22fc01a 100644 --- a/mobile/src/terminal/session/use-mobile-session-terminal-create-actions.ts +++ b/mobile/src/terminal/session/use-mobile-session-terminal-create-actions.ts @@ -11,7 +11,7 @@ import { hostSupportsMobileChatFork, mobileChatForkCreateReplyMessage, mobileChatForkRequestFailureMessage -} from '../../session/mobile-chat-fork-create-feedback' +} from './mobile-chat-fork-create-feedback' import type { MobileSessionTab } from '../../session/tabs/mobile-session-tabs' import type { Terminal } from './mobile-terminal-model' import type { MobileSessionAttachmentsModel } from '../../files/attachments/use-mobile-session-attachments' @@ -23,7 +23,7 @@ import { type MobileSessionTerminalCreateOptions } from './mobile-session-terminal-create-request' import { isAgentSessionHandleProvider } from '../../../../src/shared/agent-session/lifecycle/agent-session-provider-handle' -import { createMobileStructuredAgentSession } from '../../session/mobile-structured-agent-session-launch' +import { createMobileStructuredAgentSession } from '../../session/native-chat/agent-session/mobile-structured-agent-session-launch' import { launchesThroughHost, launchNewTabAgentThroughHost diff --git a/mobile/src/terminal/terminal-live-accessory-raw-send.ts b/mobile/src/terminal/terminal-live-accessory-raw-send.ts index 69dd43a379b..6ee6773c962 100644 --- a/mobile/src/terminal/terminal-live-accessory-raw-send.ts +++ b/mobile/src/terminal/terminal-live-accessory-raw-send.ts @@ -1,12 +1,12 @@ import { getTerminalLiveAccessoryRawSendTarget } from './terminal-live-accessory-raw-send-target' import { buildTerminalSendParams, TERMINAL_INPUT_SEND_OPTIONS } from './terminal-send-request' import { terminalInputSend } from './mobile-terminal-operations' -import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender' +import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port' import type { ConnectionState } from '../transport/connection/connection-state' type TerminalLiveAccessoryRawSendArgs = { readonly sendTerminalUserInput: typeof terminalInputSend.request - readonly client: RpcOperationSender | null + readonly client: UnvalidatedRpcRequestPort | null readonly targetHandle: string readonly activeHandle: string | null readonly activeSessionTabType: string | null diff --git a/mobile/src/test-support/in-memory-async-storage.ts b/mobile/src/test-support/in-memory-async-storage.ts new file mode 100644 index 00000000000..b20d0954d6e --- /dev/null +++ b/mobile/src/test-support/in-memory-async-storage.ts @@ -0,0 +1,16 @@ +import type AsyncStorage from '@react-native-async-storage/async-storage' + +type InMemoryAsyncStorage = Pick + +export function createInMemoryAsyncStorage(): InMemoryAsyncStorage { + const values = new Map() + return { + getItem: async (key: string) => values.get(key) ?? null, + setItem: async (key: string, value: string) => { + values.set(key, value) + }, + removeItem: async (key: string) => { + values.delete(key) + } + } +} diff --git a/mobile/src/test-support/rpc-recording/adapters/diff-review-action-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/diff-review-action-mount-adapters.ts index 28944cd6c22..016531260e4 100644 --- a/mobile/src/test-support/rpc-recording/adapters/diff-review-action-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/diff-review-action-mount-adapters.ts @@ -10,7 +10,7 @@ import type { } from '../../../source-control/review/mobile-diff-review-screen-model' import type { MountAdapter } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type * as NativeChatStaleInput from '../../../session/native-chat/mobile-native-chat-stale-input' +import type * as NativeChatStaleInput from '../../../session/native-chat/composer/mobile-native-chat-stale-input' const WORKSPACE = 'workspace-1' const HOST = 'host-1' @@ -30,7 +30,7 @@ export function diffReviewActionMountAdapters( 'mobile/src/source-control/review/mobile-diff-review-sheets.ts' ) const staleInput = modules.load( - 'mobile/src/session/native-chat/mobile-native-chat-stale-input.ts' + 'mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts' ) staleInput.resetMobileNativeChatStaleInputForTests() const comment: DiffComment = { diff --git a/mobile/src/test-support/rpc-recording/adapters/mounted-operation-modules.ts b/mobile/src/test-support/rpc-recording/adapters/mounted-operation-modules.ts index fb8830d44cb..961b9bcb272 100644 --- a/mobile/src/test-support/rpc-recording/adapters/mounted-operation-modules.ts +++ b/mobile/src/test-support/rpc-recording/adapters/mounted-operation-modules.ts @@ -39,10 +39,7 @@ import { newWorkspaceRepositoryMountAdapters } from './new-workspace-repository- import { pairingJournalMountAdapters } from './pairing-journal-mount-adapters' import { prSidebarMountAdapters } from './pr-sidebar-mount-adapters' import { pushDismissalMountAdapters } from './push-dismissal-mount-adapters' -import { - pushRegistrationMountAdapters, - pushRegistrationMountExposures -} from './push-registration-mount-adapters' +import { pushRegistrationMountAdapters } from './push-registration-mount-adapters' import { relayCredentialMountAdapters } from './relay-credential-mount-adapters' import { sessionNotesMountAdapters } from './session-notes-mount-adapters' import { sessionScreenReadMountAdapters } from './session-screen-read-mount-adapters' @@ -143,8 +140,7 @@ export const MOUNTED_OPERATION_MODULES: readonly MountedOperationModule[] = [ { source: 'push-dismissal-mount-adapters.ts', mounts: pushDismissalMountAdapters }, { source: 'push-registration-mount-adapters.ts', - mounts: pushRegistrationMountAdapters, - exposes: pushRegistrationMountExposures + mounts: pushRegistrationMountAdapters }, { source: 'relay-credential-mount-adapters.ts', mounts: relayCredentialMountAdapters }, { source: 'session-notes-mount-adapters.ts', mounts: sessionNotesMountAdapters }, diff --git a/mobile/src/test-support/rpc-recording/adapters/native-chat-write-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/native-chat-write-mount-adapters.ts index 3ec6c51603e..93186b10ffd 100644 --- a/mobile/src/test-support/rpc-recording/adapters/native-chat-write-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/native-chat-write-mount-adapters.ts @@ -1,7 +1,7 @@ import type { MountAdapter } from '../recording-scenario' import { mountFixture } from '../recorder-fixture-shape' import type { operationModuleLoader } from '../operation-module-loader' -import type * as NativeChatSend from '../../../session/native-chat/mobile-native-chat-send' +import type * as NativeChatSend from '../../../session/native-chat/composer/mobile-native-chat-send' import type * as NativeChatSessionOptionPersistence from '../../../session/native-chat/input/mobile-native-chat-session-option-persistence' const TERMINAL = 'terminal-1' @@ -14,7 +14,7 @@ export function nativeChatWriteMountAdapters( return { 'nativeChat.terminal-write': ({ client }) => { const send = modules.load( - 'mobile/src/session/native-chat/mobile-native-chat-send.ts' + 'mobile/src/session/native-chat/composer/mobile-native-chat-send.ts' ) const outcomes: Record = {} return { diff --git a/mobile/src/test-support/rpc-recording/adapters/push-registration-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/push-registration-mount-adapters.ts index bcb701e5751..d24c73efe26 100644 --- a/mobile/src/test-support/rpc-recording/adapters/push-registration-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/push-registration-mount-adapters.ts @@ -1,14 +1,6 @@ -import type { OperationExposure, operationModuleLoader } from '../operation-module-loader' +import type { operationModuleLoader } from '../operation-module-loader' import type { MountAdapter } from '../recording-scenario' -// Expose private senders to record wire behavior without inventing keychain and device-token storage in substitutes. -export const pushRegistrationMountExposures: readonly OperationExposure[] = [ - [ - 'notifications/push-registration.ts', - '\nexports.sendRegister = sendRegister;\nexports.sendUnregister = sendUnregister;' - ] -] - const REGISTER_TIMEOUT_MS = 5_000 export function pushRegistrationMountAdapters( @@ -19,7 +11,7 @@ export function pushRegistrationMountAdapters( const push = modules.load<{ sendRegister: (client: unknown, token: unknown, filter: unknown) => Promise sendUnregister: (client: unknown, timeoutMs: number) => Promise - }>('mobile/src/notifications/push-registration.ts') + }>('mobile/src/notifications/mobile-push-registration-operations.ts') const results: Record = {} return { action(name, args) { diff --git a/mobile/src/test-support/rpc-recording/adapters/session-screen-read-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/session-screen-read-mount-adapters.ts index 2609c0fc3cd..c7a6cb9e2e4 100644 --- a/mobile/src/test-support/rpc-recording/adapters/session-screen-read-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/session-screen-read-mount-adapters.ts @@ -11,7 +11,7 @@ import { mountFixture } from '../recorder-fixture-shape' import type { MountAdapter } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' import type * as MobileNativeChatReadability from '../../../session/native-chat/transcript/use-mobile-native-chat-readability' -import type * as MobileNativeChatStopModule from '../../../session/native-chat/use-mobile-native-chat-stop' +import type * as MobileNativeChatStopModule from '../../../session/native-chat/composer/use-mobile-native-chat-stop' const WORKSPACE = 'workspace-1' const HANDLE = 'terminal-1' @@ -163,7 +163,7 @@ export function sessionScreenReadMountAdapters( }, 'session.native-chat-stop': ({ client, effect }) => { const useStop = modules.load( - 'mobile/src/session/native-chat/use-mobile-native-chat-stop.ts' + 'mobile/src/session/native-chat/composer/use-mobile-native-chat-stop.ts' ).useMobileNativeChatStop const errors: string[] = [] let stop: () => void diff --git a/mobile/src/test-support/rpc-recording/adapters/structured-agent-launch-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/structured-agent-launch-mount-adapters.ts index 6e5de8860a0..a9a259b4d25 100644 --- a/mobile/src/test-support/rpc-recording/adapters/structured-agent-launch-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/structured-agent-launch-mount-adapters.ts @@ -1,5 +1,6 @@ import type { MountAdapter } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' +import type { createMobileStructuredAgentSession } from '../../../session/native-chat/agent-session/mobile-structured-agent-session-launch' const WORKSPACE = 'workspace-1' @@ -9,10 +10,10 @@ export function structuredAgentLaunchMountAdapters( ): Record { return { 'agentSession.structured-launch': ({ client }) => { - const create = modules.load< - typeof import('../../../session/mobile-structured-agent-session-launch') - >( - 'mobile/src/session/mobile-structured-agent-session-launch.ts' + const create = modules.load<{ + createMobileStructuredAgentSession: typeof createMobileStructuredAgentSession + }>( + 'mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts' ).createMobileStructuredAgentSession let launched: unknown = 'unlaunched' return { diff --git a/mobile/src/test-support/rpc-recording/relay-pairing-fixtures.ts b/mobile/src/test-support/rpc-recording/relay-pairing-fixtures.ts index 9217c4ce6bd..9938506c06b 100644 --- a/mobile/src/test-support/rpc-recording/relay-pairing-fixtures.ts +++ b/mobile/src/test-support/rpc-recording/relay-pairing-fixtures.ts @@ -1,5 +1,5 @@ import type * as MobileRelayCredentialHashModule from '../../transport/relay/mobile-relay-credential-hash' -import type { MobileRelayCredentialBundle } from '../../transport/hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../../host-credentials/mobile-relay-credential-bundle' import type { MobileRelayPairingJournal } from '../../pairing/mobile-relay-pairing-journal' import type { MountContext } from './recording-scenario' import type { operationModuleLoader } from './operation-module-loader' diff --git a/mobile/src/transport/connection/client-context.test.ts b/mobile/src/transport/connection/client-context.test.ts index 8688ab11863..4d0097b6f53 100644 --- a/mobile/src/transport/connection/client-context.test.ts +++ b/mobile/src/transport/connection/client-context.test.ts @@ -23,7 +23,7 @@ vi.mock('./runtime-status-probe', () => ({ startRuntimeStatusProbe: (client: unknown, onStatus: (status: unknown) => void) => descriptorProbe.start(client, onStatus) })) -vi.mock('../hosts/host-descriptor-recorder', () => ({ +vi.mock('../../host-descriptors/host-descriptor-recorder', () => ({ recordHostDescriptorFromStatus: (...args: unknown[]) => descriptorProbe.record(...args) })) @@ -36,7 +36,7 @@ vi.mock('../rpc/direct-rpc-connect', () => ({ vi.mock('./host-logical-client', () => ({ openHostLogicalClient: (...args: unknown[]) => connectMock(...args) })) -vi.mock('../hosts/host-store', () => ({ +vi.mock('../../host-catalog/host-store', () => ({ loadHosts: () => loadHostsMock() })) vi.mock('../../diagnostics/connection/connection-revival-triggers', () => ({ diff --git a/mobile/src/transport/connection/client-context.tsx b/mobile/src/transport/connection/client-context.tsx index 5bb222dd2e9..f368d8136fd 100644 --- a/mobile/src/transport/connection/client-context.tsx +++ b/mobile/src/transport/connection/client-context.tsx @@ -36,7 +36,7 @@ import { type CloseEntryOptions } from './host-client-context-state' import type { ConnectionState } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' import type { RpcClientContextValue } from './rpc-client-context-contract' type StoreEntry = HostClientStoreEntry diff --git a/mobile/src/transport/connection/client-context.web.tsx b/mobile/src/transport/connection/client-context.web.tsx index 547f5c2ee06..b93af825038 100644 --- a/mobile/src/transport/connection/client-context.web.tsx +++ b/mobile/src/transport/connection/client-context.web.tsx @@ -3,7 +3,7 @@ import { createContext, useContext, useMemo, useRef, type ReactNode } from 'reac import type { BridgeRpcClient } from '../../mobile-web-shell/bridge/bridge-rpc-client' import { BRIDGE_PAGE_CLIENT_ID } from '../../mobile-web-shell/bridge/bridge-page-client-identity' import type { ConnectionState } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' import type { RpcClientContextValue } from './rpc-client-context-contract' const Ctx = createContext(null) diff --git a/mobile/src/transport/connection/host-client-context-state.ts b/mobile/src/transport/connection/host-client-context-state.ts index 11732d99f66..879090e7569 100644 --- a/mobile/src/transport/connection/host-client-context-state.ts +++ b/mobile/src/transport/connection/host-client-context-state.ts @@ -4,7 +4,7 @@ import type { HostClientStoreEntry } from './host-entry-opener' import type { RelayHostReachability } from '../relay/relay-host-reachability' import type { MobileConnectionPath, StableLogicalRpcClient } from './stable-logical-rpc-client' import type { ConnectionState } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' export type CloseEntryOptions = { forgetPrimedHost: boolean diff --git a/mobile/src/transport/connection/host-client-hooks.ts b/mobile/src/transport/connection/host-client-hooks.ts index 97822e2f823..77bf0d169b1 100644 --- a/mobile/src/transport/connection/host-client-hooks.ts +++ b/mobile/src/transport/connection/host-client-hooks.ts @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import type { RpcClient } from '../rpc/rpc-client-contract' import type { ConnectionState } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' import type { HostClientAcquisition } from './host-client-acquisition-registry' import type { ForceReconnect } from './rpc-client-context-contract' import { useRpcClientContext } from './client-context' diff --git a/mobile/src/transport/connection/host-entry-opener.ts b/mobile/src/transport/connection/host-entry-opener.ts index 32f15c23b7b..45ef9d3abad 100644 --- a/mobile/src/transport/connection/host-entry-opener.ts +++ b/mobile/src/transport/connection/host-entry-opener.ts @@ -1,18 +1,18 @@ import { attachPushRegistration } from '../../notifications/push-registration' -import { recordHostDescriptorFromStatus } from '../hosts/host-descriptor-recorder' +import { recordHostDescriptorFromStatus } from '../../host-descriptors/host-descriptor-recorder' import { startRuntimeStatusProbe } from './runtime-status-probe' import { connectionLogStore, recordConnectionClientSessionStart } from '../../diagnostics/connection/persisted-connection-log-store' -import { loadHosts } from '../hosts/host-store' +import { loadHosts } from '../../host-catalog/host-store' import { openHostLogicalClient } from './host-logical-client' import type { HostClientOpenRegistry } from './host-client-open-registry' import type { HostOpenRetryScheduler } from './host-open-retry-scheduler' import type { RpcClient } from '../rpc/rpc-client-contract' import type { StableLogicalRpcClient } from './stable-logical-rpc-client' import type { ConnectionState } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' export type HostClientStoreEntry = { client: RpcClient diff --git a/mobile/src/transport/connection/host-logical-client.ts b/mobile/src/transport/connection/host-logical-client.ts index 0474f2f4bc2..33c04a48610 100644 --- a/mobile/src/transport/connection/host-logical-client.ts +++ b/mobile/src/transport/connection/host-logical-client.ts @@ -4,7 +4,7 @@ import { connect } from '../rpc/direct-rpc-connect' import type { RpcClient } from '../rpc/rpc-client-contract' import { createStableLogicalRpcClient } from './stable-logical-rpc-client' import type { ConnectionLogSink } from '../../diagnostics/connection/connection-log-types' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' import { directPathForEndpoint } from '../relay/mobile-direct-endpoint-probe' import { startMobileEndpointLifecycle } from './mobile-endpoint-lifecycle' import { watchHostRevocation } from './host-revocation-watch' diff --git a/mobile/src/transport/connection/host-status-gates.test.ts b/mobile/src/transport/connection/host-status-gates.test.ts index 270066f69de..ca1182cafaf 100644 --- a/mobile/src/transport/connection/host-status-gates.test.ts +++ b/mobile/src/transport/connection/host-status-gates.test.ts @@ -6,13 +6,13 @@ import { useHostStatusGates, type HostStatusGates } from './host-status-gates' const recordHostAppVersionMock = vi.hoisted(() => vi.fn().mockResolvedValue(undefined)) -vi.mock('../hosts/host-app-version-store', () => ({ +vi.mock('../../host-descriptors/host-app-version-store', () => ({ recordHostAppVersion: (...args: unknown[]) => recordHostAppVersionMock(...args) })) const recordDescriptorFromStatusMock = vi.hoisted(() => vi.fn()) -vi.mock('../hosts/host-descriptor-recorder', () => ({ +vi.mock('../../host-descriptors/host-descriptor-recorder', () => ({ recordHostDescriptorFromStatus: (...args: unknown[]) => recordDescriptorFromStatusMock(...args) })) diff --git a/mobile/src/transport/connection/host-status-gates.ts b/mobile/src/transport/connection/host-status-gates.ts index efb53eb46d9..053fe99c095 100644 --- a/mobile/src/transport/connection/host-status-gates.ts +++ b/mobile/src/transport/connection/host-status-gates.ts @@ -3,9 +3,9 @@ import type { RpcClient } from '../rpc/rpc-client-contract' import type { ConnectionState } from './connection-state' import { evaluateCompat, type CompatVerdict } from './protocol-compat' import { hostStatusSchema, type HostStatusReply } from '../hosts/host-status-reply-schema' -import { normalizeHostAppVersion } from '../hosts/host-app-version' -import { recordHostAppVersion } from '../hosts/host-app-version-store' -import { recordHostDescriptorFromStatus } from '../hosts/host-descriptor-recorder' +import { normalizeHostAppVersion } from '../../host-descriptors/host-app-version' +import { recordHostAppVersion } from '../../host-descriptors/host-app-version-store' +import { recordHostDescriptorFromStatus } from '../../host-descriptors/host-descriptor-recorder' import { readConnectionStatusProbe, startRuntimeStatusProbe } from './runtime-status-probe' export type HostStatusGates = { diff --git a/mobile/src/transport/connection/mobile-endpoint-lifecycle.ts b/mobile/src/transport/connection/mobile-endpoint-lifecycle.ts index b635b808ec5..d1e0db84585 100644 --- a/mobile/src/transport/connection/mobile-endpoint-lifecycle.ts +++ b/mobile/src/transport/connection/mobile-endpoint-lifecycle.ts @@ -1,7 +1,7 @@ import * as ExpoCrypto from 'expo-crypto' import type { ConnectionLogSink } from '../../diagnostics/connection/connection-log-types' import type { ForegroundNudgeReason } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' import type { MobileRelayEndpoint } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import { connect } from '../rpc/direct-rpc-connect' import { MobileEndpointSupervisor } from './mobile-endpoint-supervisor' @@ -10,8 +10,8 @@ import { resolveMobileRelayEndpoint } from '../relay/mobile-relay-resume-directo import { readMobileRelayCredentialBundle, writeMobileRelayCredentialBundle -} from '../hosts/mobile-relay-credential-bundle' -import { setRelayRouting } from '../hosts/host-store' +} from '../../host-credentials/mobile-relay-credential-bundle' +import { setRelayRouting } from '../../host-catalog/host-store' import { lastGoodPathWasDirectHere, readLocalNetworkFingerprint, diff --git a/mobile/src/transport/connection/mobile-endpoint-supervisor-contract.ts b/mobile/src/transport/connection/mobile-endpoint-supervisor-contract.ts index 36b984aa29b..ea3b258b000 100644 --- a/mobile/src/transport/connection/mobile-endpoint-supervisor-contract.ts +++ b/mobile/src/transport/connection/mobile-endpoint-supervisor-contract.ts @@ -1,7 +1,7 @@ import type { MobileRelayEndpoint } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' import type { RelayHostCloseReason } from '../../../../src/shared/relay-host-close-reason' import type { HostGoodPath } from './host-path-memory' -import type { MobileRelayCredentialBundle } from '../hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../../host-credentials/mobile-relay-credential-bundle' import type { MobileRelayRpcSession } from '../relay/mobile-relay-rpc-session-contract' import type { resolveMobileRelayEndpoint } from '../relay/mobile-relay-resume-director' import type { RpcClient } from '../rpc/rpc-client-contract' diff --git a/mobile/src/transport/connection/mobile-endpoint-supervisor-test-fakes.ts b/mobile/src/transport/connection/mobile-endpoint-supervisor-test-fakes.ts index 8ba5f3bb97d..2a36822bb9a 100644 --- a/mobile/src/transport/connection/mobile-endpoint-supervisor-test-fakes.ts +++ b/mobile/src/transport/connection/mobile-endpoint-supervisor-test-fakes.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest' -import type { MobileRelayCredentialBundle } from '../hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../../host-credentials/mobile-relay-credential-bundle' import type { MobileRelayRpcSession } from '../relay/mobile-relay-rpc-session-contract' import { RelayDialStageTracker, type RelayDialStage } from '../relay/relay-dial-stage' import { defaultCancelTimer, defaultScheduleTimer } from '../../platform/timer-scheduler' @@ -9,7 +9,7 @@ import type { SendRequestOptions } from '../rpc/unvalidated-rpc-request-port' import type { RelayHostReachability } from '../relay/relay-host-reachability' import type { MobileConnectionPath, StableLogicalRpcClient } from './stable-logical-rpc-client' import type { ConnectionState } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' import type { RpcResponse } from '../rpc/rpc-wire-types' export class FakeSession implements RpcClient { diff --git a/mobile/src/transport/connection/mobile-endpoint-supervisor.ts b/mobile/src/transport/connection/mobile-endpoint-supervisor.ts index afaaf820b14..9df18632cd9 100644 --- a/mobile/src/transport/connection/mobile-endpoint-supervisor.ts +++ b/mobile/src/transport/connection/mobile-endpoint-supervisor.ts @@ -13,7 +13,7 @@ import { mobileRelayCredentialNeedsRotation, rotateMobileRelayCredential } from '../relay/mobile-relay-credential-rotation' -import type { MobileRelayCredentialBundle } from '../hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../../host-credentials/mobile-relay-credential-bundle' import { MobileEndpointNudgeRouter } from './mobile-endpoint-nudge-router' import { MobileRelayDirectGraceTimer } from '../relay/mobile-relay-direct-grace-timer' import { MobileRelaySessionEstablisher } from '../relay/mobile-relay-session-establisher' diff --git a/mobile/src/transport/connection/rpc-client-context-contract.ts b/mobile/src/transport/connection/rpc-client-context-contract.ts index 0db36f943e7..5675916e30b 100644 --- a/mobile/src/transport/connection/rpc-client-context-contract.ts +++ b/mobile/src/transport/connection/rpc-client-context-contract.ts @@ -3,7 +3,7 @@ import type { RpcClient } from '../rpc/rpc-client-contract' import type { RelayHostReachability } from '../relay/relay-host-reachability' import type { MobileConnectionPath } from './stable-logical-rpc-client' import type { ConnectionState } from './connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' /** Re-dials one host. `null` where this document cannot dial: the page's shell owns the connection. */ export type ForceReconnect = ((hostId: string) => Promise) | null diff --git a/mobile/src/transport/hosts/home-host-auto-connect.ts b/mobile/src/transport/hosts/home-host-auto-connect.ts index 08e69879865..53a67400cd8 100644 --- a/mobile/src/transport/hosts/home-host-auto-connect.ts +++ b/mobile/src/transport/hosts/home-host-auto-connect.ts @@ -1,5 +1,5 @@ import type { ConnectionState } from '../connection/connection-state' -import type { HostProfile } from './host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' export const HOME_AUTO_CONNECT_LIMIT = 3 diff --git a/mobile/src/transport/hosts/host-open-recovery.test.tsx b/mobile/src/transport/hosts/host-open-recovery.test.tsx index 782f2c348f9..9ff6210e629 100644 --- a/mobile/src/transport/hosts/host-open-recovery.test.tsx +++ b/mobile/src/transport/hosts/host-open-recovery.test.tsx @@ -20,7 +20,7 @@ vi.mock('../connection/runtime-status-probe', () => ({ vi.mock('../connection/host-logical-client', () => ({ openHostLogicalClient: (...args: unknown[]) => openHostLogicalClientMock(...args) })) -vi.mock('./host-store', () => ({ +vi.mock('../../host-catalog/host-store', () => ({ loadHosts: () => loadHostsMock() })) vi.mock('../../diagnostics/connection/connection-revival-triggers', () => ({ diff --git a/mobile/src/transport/hosts/pairing-keychain.test.ts b/mobile/src/transport/hosts/pairing-keychain.test.ts deleted file mode 100644 index 1bc92703683..00000000000 --- a/mobile/src/transport/hosts/pairing-keychain.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const asyncStorageMock = vi.hoisted(() => ({ - getItem: vi.fn(), - setItem: vi.fn(), - removeItem: vi.fn() -})) - -const secureStoreMock = vi.hoisted(() => ({ - deleteItemAsync: vi.fn(), - getItemAsync: vi.fn(), - setItemAsync: vi.fn() -})) -const platformMock = vi.hoisted(() => ({ OS: 'android' })) - -vi.mock('@react-native-async-storage/async-storage', () => ({ default: asyncStorageMock })) - -vi.mock('expo-secure-store', () => ({ - WHEN_UNLOCKED_THIS_DEVICE_ONLY: 'WHEN_UNLOCKED_THIS_DEVICE_ONLY', - ...secureStoreMock -})) -vi.mock('react-native', () => ({ Platform: platformMock })) - -import { resetPairingKeychainForTests, writePairingKeychainItem } from './pairing-keychain' - -const GENERATION_KEY = 'orca:pairing-keychain-generation' -const TOKEN_KEY = 'orca.host-token.host-1782629088232' - -// Preserve Expo’s null-message Android GeneralSecurityException shape from #6600. -const ENCRYPT_REJECTION = new Error( - `Could not encrypt the value for key '${TOKEN_KEY}' under keychain 'key_v1'. Caused by: unknown` -) - -describe('pairing keychain', () => { - let generationRecord: string | null - - beforeEach(() => { - vi.clearAllMocks() - resetPairingKeychainForTests() - platformMock.OS = 'android' - generationRecord = null - asyncStorageMock.getItem.mockImplementation(async (key: string) => - key === GENERATION_KEY ? generationRecord : null - ) - asyncStorageMock.setItem.mockImplementation(async (key: string, raw: string) => { - if (key === GENERATION_KEY) { - generationRecord = raw - } - }) - secureStoreMock.setItemAsync.mockResolvedValue(undefined) - secureStoreMock.deleteItemAsync.mockResolvedValue(undefined) - secureStoreMock.getItemAsync.mockResolvedValue(null) - }) - - it('surfaces the original error when a rotated write also fails', async () => { - secureStoreMock.setItemAsync.mockRejectedValue(ENCRYPT_REJECTION) - - await expect(writePairingKeychainItem(TOKEN_KEY, 'token')).rejects.toBe(ENCRYPT_REJECTION) - expect(generationRecord).toBe('1:pending') - expect(secureStoreMock.deleteItemAsync).not.toHaveBeenCalled() - }) - - it('does not rotate from a guessed generation when the record is unreadable', async () => { - const storageError = new Error('storage unavailable') - asyncStorageMock.getItem.mockRejectedValueOnce(storageError) - - await expect(writePairingKeychainItem(TOKEN_KEY, 'token')).rejects.toBe(storageError) - - expect(asyncStorageMock.setItem).not.toHaveBeenCalled() - expect(secureStoreMock.setItemAsync).not.toHaveBeenCalled() - }) -}) diff --git a/mobile/src/transport/relay/mobile-relay-credential-rotation.ts b/mobile/src/transport/relay/mobile-relay-credential-rotation.ts index e15157dfdd9..a9ff541f02f 100644 --- a/mobile/src/transport/relay/mobile-relay-credential-rotation.ts +++ b/mobile/src/transport/relay/mobile-relay-credential-rotation.ts @@ -6,7 +6,7 @@ import type { import { MobileRelayCredentialBundleSchema, type MobileRelayCredentialBundle -} from '../hosts/mobile-relay-credential-bundle' +} from '../../host-credentials/mobile-relay-credential-bundle' import { hashMobileRelayCredential } from './mobile-relay-credential-hash' import { relayCredentialProvision, diff --git a/mobile/src/transport/relay/mobile-relay-credential-selection.ts b/mobile/src/transport/relay/mobile-relay-credential-selection.ts index 8d216028ca0..8952401e8b5 100644 --- a/mobile/src/transport/relay/mobile-relay-credential-selection.ts +++ b/mobile/src/transport/relay/mobile-relay-credential-selection.ts @@ -1,4 +1,4 @@ -import type { MobileRelayCredentialBundle } from '../hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../../host-credentials/mobile-relay-credential-bundle' import type { RelayReconnectController } from './mobile-relay-reconnect-controller' // Adopt durable state only when it restores a dialable credential; fresh renewals/re-pairing do not reliably increase version. diff --git a/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.test.ts b/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.test.ts index 2864cb76633..6e6b18fe684 100644 --- a/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.test.ts +++ b/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.test.ts @@ -3,7 +3,7 @@ import { MobileRelayDirectUpgradeController } from './mobile-relay-direct-upgrad import type { MobileRelayDirectUpgradeResult } from './mobile-relay-direct-upgrade' import type { StableLogicalRpcClient } from '../connection/stable-logical-rpc-client' import type { ConnectionState } from '../connection/connection-state' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' const directHost: HostProfile = { id: 'host-direct', diff --git a/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.ts b/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.ts index 28c1d1816ce..81b8fd9a9c2 100644 --- a/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.ts +++ b/mobile/src/transport/relay/mobile-relay-direct-upgrade-controller.ts @@ -1,6 +1,6 @@ import type { MobileRelayDirectUpgradeResult } from './mobile-relay-direct-upgrade' import type { StableLogicalRpcClient } from '../connection/stable-logical-rpc-client' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' type Dependencies = { upgrade: ( diff --git a/mobile/src/transport/relay/mobile-relay-direct-upgrade-journal.ts b/mobile/src/transport/relay/mobile-relay-direct-upgrade-journal.ts index abab5a05ca5..8d963663eff 100644 --- a/mobile/src/transport/relay/mobile-relay-direct-upgrade-journal.ts +++ b/mobile/src/transport/relay/mobile-relay-direct-upgrade-journal.ts @@ -5,8 +5,8 @@ import { deletePairingKeychainItem, readPairingKeychainItem, writePairingKeychainItem -} from '../hosts/pairing-keychain' -import { markHostCredentialWrite } from '../hosts/host-credential-write-revision' +} from '../../host-credentials/pairing-keychain' +import { markHostCredentialWrite } from '../../host-credentials/host-credential-write-revision' const Base64Url32ByteSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/) diff --git a/mobile/src/transport/relay/mobile-relay-direct-upgrade.ts b/mobile/src/transport/relay/mobile-relay-direct-upgrade.ts index 6fc49039c16..f4cb7d6ad6b 100644 --- a/mobile/src/transport/relay/mobile-relay-direct-upgrade.ts +++ b/mobile/src/transport/relay/mobile-relay-direct-upgrade.ts @@ -4,13 +4,13 @@ import type { MobileRelayEndpoint, PairingGetEndpointsResult } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' -import { RelayRoutingHostRemovedError, setRelayRouting } from '../hosts/host-store' +import { RelayRoutingHostRemovedError, setRelayRouting } from '../../host-catalog/host-store' import { MobileRelayCredentialBundleSchema, deleteMobileRelayCredentialBundle, writeMobileRelayCredentialBundle, type MobileRelayCredentialBundle -} from '../hosts/mobile-relay-credential-bundle' +} from '../../host-credentials/mobile-relay-credential-bundle' import { createMobileRelayDirectUpgradeJournal, deleteMobileRelayDirectUpgradeJournal, @@ -23,7 +23,7 @@ import { relayPairingEndpointsRead } from '../../pairing/mobile-relay-pairing-operations' import type { RpcClient } from '../rpc/rpc-client-contract' -import type { HostProfile } from '../hosts/host-profile' +import type { HostProfile } from '../../host-catalog/host-profile' import { isPairingRelayRpcUnavailable } from '../../pairing/pairing-relay-rpc-unavailable' export type MobileRelayDirectUpgradeResult = { diff --git a/mobile/src/transport/relay/mobile-relay-orphan-cleanup.ts b/mobile/src/transport/relay/mobile-relay-orphan-cleanup.ts index 0b909a2940a..9d5a984139e 100644 --- a/mobile/src/transport/relay/mobile-relay-orphan-cleanup.ts +++ b/mobile/src/transport/relay/mobile-relay-orphan-cleanup.ts @@ -1,5 +1,5 @@ -import { scheduleHostCredentialCleanup } from '../hosts/host-credential-cleanup' -import { removeMobileRelayHostRouting } from '../hosts/mobile-relay-host-overlay-store' +import { scheduleHostCredentialCleanup } from '../../host-credentials/host-credential-cleanup' +import { removeMobileRelayHostRouting } from '../../host-catalog/mobile-relay-host-overlay-store' export async function scheduleOrphanedMobileRelayCleanup(args: { hostIds: string[] diff --git a/mobile/src/transport/relay/mobile-relay-recovery-presentation.ts b/mobile/src/transport/relay/mobile-relay-recovery-presentation.ts index e83994e7411..54da4de3edd 100644 --- a/mobile/src/transport/relay/mobile-relay-recovery-presentation.ts +++ b/mobile/src/transport/relay/mobile-relay-recovery-presentation.ts @@ -1,4 +1,4 @@ -import type { MobileRelayCredentialBundle } from '../hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../../host-credentials/mobile-relay-credential-bundle' import type { RelayReconnectController } from './mobile-relay-reconnect-controller' import type { StableLogicalRpcClient } from '../connection/stable-logical-rpc-client' import type { ConnectionState } from '../connection/connection-state' diff --git a/mobile/src/transport/relay/mobile-relay-session-establisher.ts b/mobile/src/transport/relay/mobile-relay-session-establisher.ts index f370b56af89..ab0a580b764 100644 --- a/mobile/src/transport/relay/mobile-relay-session-establisher.ts +++ b/mobile/src/transport/relay/mobile-relay-session-establisher.ts @@ -7,7 +7,7 @@ import { import { encodeBase64Url } from '../encryption/mobile-session-id-encoding' import { persistResumeConfirmation } from './mobile-relay-credential-rotation' import { relayFailureAllowsGraceRetry } from './relay-credential-eligibility' -import type { MobileRelayCredentialBundle } from '../hosts/mobile-relay-credential-bundle' +import type { MobileRelayCredentialBundle } from '../../host-credentials/mobile-relay-credential-bundle' import type { RelayReconnectController } from './mobile-relay-reconnect-controller' import type { StableLogicalRpcClient } from '../connection/stable-logical-rpc-client' import type { MobileRelayEndpoint } from '../../../../src/shared/remote-runtime/relay/mobile-relay-credential-contract' diff --git a/mobile/src/transport/rpc/rpc-operation-contract.ts b/mobile/src/transport/rpc/rpc-operation-contract.ts index d7c49f89ec5..4434da3bfa9 100644 --- a/mobile/src/transport/rpc/rpc-operation-contract.ts +++ b/mobile/src/transport/rpc/rpc-operation-contract.ts @@ -2,8 +2,6 @@ import type { RpcAcceptedResult } from './rpc-accepted-result' import type { RpcMethodName } from '../../../../src/shared/rpc-contract/rpc-params-catalog.generated' import type { RpcFailure, RpcResponse, RpcSuccess } from './rpc-wire-types' -export type { RpcAcceptedResult } - // Fix method, acceptance and interpretation barrier per family so callers cannot drift in decoding or settlement order. export type RpcAcceptanceName = diff --git a/mobile/src/transport/rpc/rpc-operation-sender.ts b/mobile/src/transport/rpc/rpc-operation-sender.ts deleted file mode 100644 index 1d39f8ff9c7..00000000000 --- a/mobile/src/transport/rpc/rpc-operation-sender.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { settingsRead } from '../../settings/mobile-settings-read-operations' - -// Derive the operation’s sender without exposing the raw port or requiring a complete RpcClient in tests. -export type RpcOperationSender = Parameters[0] diff --git a/mobile/src/transport/rpc/rpc-uninterpreted-reply-schemas.ts b/mobile/src/transport/rpc/rpc-uninterpreted-reply-schemas.ts index e99fdbad5bb..87f8b84d347 100644 --- a/mobile/src/transport/rpc/rpc-uninterpreted-reply-schemas.ts +++ b/mobile/src/transport/rpc/rpc-uninterpreted-reply-schemas.ts @@ -1,5 +1,4 @@ import * as z from 'zod' -import '../../../../src/shared/zod-salvage' // Leave unread replies opaque; structured creates/probes require raw-envelope uncertainty checks at their callers. export const sessionLaunchUnreadReplySchema = z.unknown() diff --git a/mobile/src/transport/rpc/unvalidated-rpc-request-port-inventory.ts b/mobile/src/transport/rpc/unvalidated-rpc-request-port-inventory.ts index 55b32fc1059..30679763873 100644 --- a/mobile/src/transport/rpc/unvalidated-rpc-request-port-inventory.ts +++ b/mobile/src/transport/rpc/unvalidated-rpc-request-port-inventory.ts @@ -45,7 +45,10 @@ export const UNVALIDATED_RPC_REQUEST_PORT_PENDING: readonly UnvalidatedRpcReques { file: 'src/accounts/MobileAccountsScreen.tsx', references: 2 }, // callAgentSession accepts dynamic methods and generic results; typed operations require restructuring its callers. - { file: 'src/session/mobile-structured-agent-session-rpc.ts', references: 1 }, + { + file: 'src/session/native-chat/agent-session/mobile-structured-agent-session-rpc.ts', + references: 1 + }, // The recorded create-terminal prompt uses a unique empty-refusal fallback absent from existing terminal.send operations. { file: 'src/session/use-mobile-session-terminal-create-actions.ts', references: 1 }, diff --git a/mobile/src/transport/web-bundle/mobile-web-bundle-brotli-fetch.test.ts b/mobile/src/transport/web-bundle/mobile-web-bundle-brotli-fetch.test.ts index d88a73b0ad5..6561607b409 100644 --- a/mobile/src/transport/web-bundle/mobile-web-bundle-brotli-fetch.test.ts +++ b/mobile/src/transport/web-bundle/mobile-web-bundle-brotli-fetch.test.ts @@ -56,7 +56,6 @@ function brotliHost() { totalBytes: assets.reduce((total, entry) => total + entry.byteLength, 0), assets } - const methods: string[] = [] const answer = (method: string, params: Record): unknown => { if (method === 'mobileWeb.bundle.manifest') { return { manifest, chunkBytes: RANGE_BYTES, rangeBytes: RANGE_BYTES } @@ -79,7 +78,6 @@ function brotliHost() { } const client: RpcClient = { sendRequest: vi.fn(async (method: string, params?: unknown): Promise => { - methods.push(method) const fields: Record = Object(params) return { id: 'rpc-1', ok: true, result: answer(method, fields), _meta: { runtimeId: 'r' } } }), @@ -92,11 +90,11 @@ function brotliHost() { notifyForeground: vi.fn(), close: vi.fn() } - return { client, methods } + return { client } } describe('fetchMobileWebBundle from a host that ships brotli copies', () => { - it('reads the copy where the manifest lists one, inflates it, and reports progress in asset bytes', async () => { + it('reads the copy where the manifest lists one, inflates it, and reports progress in the bytes it moves', async () => { const host = brotliHost() const progress: MobileWebBundleFetchProgress[] = [] @@ -107,10 +105,7 @@ describe('fetchMobileWebBundle from a host that ships brotli copies', () => { expect(new TextDecoder().decode(fetched.assets.get('assets/script.js'))).toBe(SCRIPT) expect(new TextDecoder().decode(fetched.assets.get('index.html'))).toBe(MARK) - expect(host.methods.filter((method) => method === 'mobileWeb.bundle.brotliRange')).toHaveLength( - 3 - ) - expect(host.methods.filter((method) => method === 'mobileWeb.bundle.range')).toHaveLength(1) - expect(progress.at(-1)).toMatchObject({ receivedBytes: 1332, totalBytes: 1332 }) + // The 46-byte copy plus the 12-byte mark, not the 1,332 bytes they inflate to. + expect(progress.at(-1)).toMatchObject({ receivedBytes: 58, totalBytes: 58 }) }) }) diff --git a/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.test.ts b/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.test.ts index 643778934ec..276a5df91e9 100644 --- a/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.test.ts +++ b/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.test.ts @@ -35,6 +35,7 @@ type HostOptions = { buildId?: string intercept?: (call: HostCall) => unknown onInFlight?: (inFlight: number) => void + catalogs?: readonly string[] } /** Box first, so params that are not an object read as absent instead of throwing. */ @@ -51,7 +52,8 @@ function bundleHost(files: Record, options: HostOptions = {}) { path, sha256: toHex(sha256(content)), byteLength: content.byteLength, - contentType: 'text/plain' + contentType: 'text/plain', + ...(options.catalogs?.includes(path) ? { kind: 'catalog' } : {}) })) .sort((left, right) => (left.path < right.path ? -1 : 1)) // Derive fixture ids from final asset lists unless the test explicitly selects a mismatch. @@ -156,4 +158,18 @@ describe('fetchMobileWebBundle', () => { await expect(failed).rejects.toThrow(/index\.html hashed [0-9a-f]{64}, not/) expect(await refusalOf(failed)).toBe('asset-checksum-mismatch') }) + + it('leaves language-catalog chunks on the desktop and fetches the rest', async () => { + const host = bundleHost( + { 'index.html': 'abcdef', 'assets/de.js': 'catalog' }, + { catalogs: ['assets/de.js'] } + ) + + const fetched = await fetchMobileWebBundle({ client: host.client }) + + expect([...fetched.assets.keys()]).toEqual(['index.html']) + expect(host.calls.some((call) => paramField(call.params, 'path') === 'assets/de.js')).toBe( + false + ) + }) }) diff --git a/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.ts b/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.ts index abd4440ef64..60d00a83979 100644 --- a/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.ts +++ b/mobile/src/transport/web-bundle/mobile-web-bundle-fetch.ts @@ -1,10 +1,11 @@ import { CryptoDigestAlgorithm, digest } from 'expo-crypto' import { PrioritySemaphore } from '../../../../src/shared/priority-semaphore' import { mobileWebBundleManifestRead } from './mobile-web-bundle-operations' -import type { - MobileWebBundleAssetRead, - MobileWebBundleManifestRead, - MobileWebBundleManifestReply +import { + shellDownloadedAssets, + type MobileWebBundleAssetRead, + type MobileWebBundleManifestRead, + type MobileWebBundleManifestReply } from './mobile-web-bundle-reply-schemas' import type { RpcClient } from '../rpc/rpc-client-contract' import { MobileWebBundleFetchError } from './mobile-web-bundle-fetch-refusal' @@ -42,6 +43,7 @@ export const MOBILE_WEB_BUNDLE_WINDOW_STALL_MS = 30_000 const WINDOW_READ_BACKSTOP_MS = (MAX_CONCURRENT_WINDOW_READS + 1) * MOBILE_WEB_BUNDLE_WINDOW_STALL_MS +// Bytes are what the reader moves (a brotli copy where it reads one, else the asset); reused assets add none. export type MobileWebBundleFetchProgress = { readonly completedAssets: number readonly totalAssets: number @@ -52,7 +54,7 @@ export type MobileWebBundleFetchProgress = { export type MobileWebBundleFetchResult = { readonly manifest: MobileWebBundleManifestRead readonly assets: ReadonlyMap - // stageGeneration must copy every reused asset or fail rather than stage an incomplete tree. + // stageGeneration must copy every reused asset or fail rather than stage an incomplete tree (skipped catalogs aside). readonly reused: readonly string[] readonly totalBytes: number readonly elapsedMs: number @@ -92,10 +94,12 @@ export async function fetchMobileWebBundle(args: { const manifest = opened.manifest const reader = mobileWebBundleWindowReader(args.client, opened) const connection = connectionReadsOf(args.client) + // Before reuse: a skipped catalog an older generation lists may not be on disk there either. + const wanted = shellDownloadedAssets(manifest) const candidates = new Set( - args.reusable === undefined ? [] : manifest.assets.filter((entry) => args.reusable?.has(entry)) + args.reusable === undefined ? [] : wanted.filter((entry) => args.reusable?.has(entry)) ) - const fetched = manifest.assets.filter((entry) => !candidates.has(entry)) + const fetched = wanted.filter((entry) => !candidates.has(entry)) const assets = new Map() const reused: string[] = [] let receivedBytes = 0 @@ -180,7 +184,7 @@ export async function fetchMobileWebBundle(args: { downloadedAssets += 1 } // Per window, not per asset: the largest asset goes first, so asset completions bunch at the end. - receivedBytes += assetBytesCredited(asset, offset, bytes.byteLength) + receivedBytes += bytes.byteLength reportProgress() } @@ -246,7 +250,7 @@ export async function fetchMobileWebBundle(args: { const countDownload = (entries: readonly MobileWebBundleAssetRead[]): void => { downloadAssets += entries.length - downloadBytes += entries.reduce((sum, entry) => sum + entry.byteLength, 0) + downloadBytes += entries.reduce((sum, entry) => sum + reader.streamLength(entry), 0) } try { @@ -297,18 +301,6 @@ function planWindowReads( }) } -// Progress stays in asset bytes, the unit of the session's total: a window of a brotli copy counts for its -// share of the asset, and an asset's windows add up to exactly its byteLength. -function assetBytesCredited(asset: AssetReassembly, offset: number, byteLength: number): number { - const streamBytes = asset.stream.byteLength - if (streamBytes === asset.entry.byteLength) { - return byteLength - } - const at = (position: number): number => - Math.floor((asset.entry.byteLength * position) / streamBytes) - return at(offset + byteLength) - at(offset) -} - async function verifyReassembledAsset( asset: AssetReassembly, reader: MobileWebBundleWindowReader diff --git a/mobile/src/transport/web-bundle/mobile-web-bundle-reply-schemas.ts b/mobile/src/transport/web-bundle/mobile-web-bundle-reply-schemas.ts index 602d3d5d4ea..cc72d59b214 100644 --- a/mobile/src/transport/web-bundle/mobile-web-bundle-reply-schemas.ts +++ b/mobile/src/transport/web-bundle/mobile-web-bundle-reply-schemas.ts @@ -46,7 +46,9 @@ const assetSchema = z.looseObject({ .nonnegative() .max(MOBILE_WEB_BUNDLE_MAX_ASSET_BYTES) .optional() - .catch(undefined) + .catch(undefined), + // A kind this build cannot read is a plain asset, downloaded like any other. + kind: z.string().optional().catch(undefined) }) // Read unknown schema versions as numbers so compatibility can name the required update. @@ -132,3 +134,11 @@ export const MobileWebBundleBrotliRangeReplySchema = z.looseObject({ export type MobileWebBundleManifestReply = z.output export type MobileWebBundleManifestRead = MobileWebBundleManifestReply['manifest'] export type MobileWebBundleAssetRead = MobileWebBundleManifestRead['assets'][number] + +// Language-catalog chunks stay on the desktop: this shell grants the page hostCatalogs, and the +// page reads its one catalog over RPC. Everything else is the generation, on disk. +export function shellDownloadedAssets(manifest: { + readonly assets: readonly Asset[] +}): Asset[] { + return manifest.assets.filter((asset) => asset.kind !== 'catalog') +} diff --git a/mobile/src/transport/web-bundle/mobile-web-bundle-window-reader.ts b/mobile/src/transport/web-bundle/mobile-web-bundle-window-reader.ts index 7a27848b680..3182c2744a9 100644 --- a/mobile/src/transport/web-bundle/mobile-web-bundle-window-reader.ts +++ b/mobile/src/transport/web-bundle/mobile-web-bundle-window-reader.ts @@ -7,9 +7,10 @@ import { mobileWebBundleRangeRead } from './mobile-web-bundle-operations' import { decodeMobileWebBundleRange } from './mobile-web-bundle-range-decode' -import type { - MobileWebBundleAssetRead, - MobileWebBundleManifestReply +import { + shellDownloadedAssets, + type MobileWebBundleAssetRead, + type MobileWebBundleManifestReply } from './mobile-web-bundle-reply-schemas' import { MobileWebBundleFetchError } from './mobile-web-bundle-fetch-refusal' import type { RpcClient } from '../rpc/rpc-client-contract' @@ -66,16 +67,7 @@ export function mobileWebBundleWindowReader( } } const native = pairedE2EENativeCodec() - // Hermes has no brotli, so without the native codec every asset stays on gzip ranges. - const brotliLengths = new Map( - native === null - ? [] - : opened.manifest.assets.flatMap((asset) => - asset.brotliByteLength !== undefined && asset.brotliByteLength < asset.byteLength - ? [[asset.path, asset.brotliByteLength] as const] - : [] - ) - ) + const brotliLengths = brotliStreamLengths(opened) return { windowBytes: rangeBytes, streamLength: (asset) => brotliLengths.get(asset.path) ?? asset.byteLength, @@ -108,6 +100,34 @@ export function mobileWebBundleWindowReader( } } +// The assets a reader fetches as their brotli copy, by path, with that copy's length. Hermes has no brotli, +// so without the native codec, and on a host with no range grid, every asset stays on gzip ranges or chunks. +function brotliStreamLengths( + opened: Pick +): ReadonlyMap { + if (opened.rangeBytes === undefined || pairedE2EENativeCodec() === null) { + return new Map() + } + return new Map( + opened.manifest.assets.flatMap((asset) => + asset.brotliByteLength !== undefined && asset.brotliByteLength < asset.byteLength + ? [[asset.path, asset.brotliByteLength] as const] + : [] + ) + ) +} + +// Bytes a whole download moves, in the unit of fetch progress: each brotli copy's length where one is read. +export function mobileWebBundleStreamBytes( + opened: Pick +): number { + const brotliLengths = brotliStreamLengths(opened) + return shellDownloadedAssets(opened.manifest).reduce( + (sum, asset) => sum + (brotliLengths.get(asset.path) ?? asset.byteLength), + 0 + ) +} + // Chunks and brotli windows carry their bytes as they are. function rawWindowReply({ dataBase64, diff --git a/mobile/src/worktree/agent-row-display.ts b/mobile/src/worktree/agent-row-display.ts index 5969f45afc0..5fc384ec91d 100644 --- a/mobile/src/worktree/agent-row-display.ts +++ b/mobile/src/worktree/agent-row-display.ts @@ -88,7 +88,7 @@ export function agentDisplayLabel(row: RuntimeWorktreeAgentRow, now: number): st // Mirror desktop lastEnteredDoneAt: date main-agent failures independently while subagents remain active. export function agentRowTimeAt( - row: Pick + row: AgentRowVerdictSource & Pick ): number { if (row.state !== 'done' && row.mainAgent && agentVerdictDisplayMark(row) === 'failed') { return row.mainAgent.stateStartedAt diff --git a/mobile/src/worktree/mobile-agent-child-row-text.ts b/mobile/src/worktree/mobile-agent-child-row-text.ts index f65c9bad302..3b244776904 100644 --- a/mobile/src/worktree/mobile-agent-child-row-text.ts +++ b/mobile/src/worktree/mobile-agent-child-row-text.ts @@ -5,7 +5,7 @@ import { } from '../../../src/shared/agent-status/presentation/agent-child-row-lead-trail' import type { AgentChildRowModel } from '../../../src/shared/agent-status/presentation/agent-child-row-model' import type { AgentChildDisplayState } from '../../../src/shared/agent-status/child-work/agent-status-child-work-display' -import { sayMobileBackgroundTask } from '../session/mobile-background-task-copy' +import { sayMobileBackgroundTask } from '../session/native-chat/input/mobile-background-task-copy' import { translate } from '../i18n/mobile-i18n' import { agentStateLabel } from './agent-row-display' diff --git a/mobile/src/worktree/session-worktree-record-operations.ts b/mobile/src/worktree/session-worktree-record-operations.ts index 5931ca217e6..b2d75f24af1 100644 --- a/mobile/src/worktree/session-worktree-record-operations.ts +++ b/mobile/src/worktree/session-worktree-record-operations.ts @@ -2,13 +2,6 @@ import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc/r import { rpcResultVariant } from '../transport/rpc/rpc-operation-result-reader' import '../terminal/terminal-reply-schema' import { sessionWorktreeRecordSchema } from './session-worktree-record-reply-schema' -import '../session/agent-launch/mobile-agent-detection-reply-schema' -import '../files/session-documents/mobile-markdown-tab-reply-schema' -import '../transport/rpc/rpc-uninterpreted-reply-schemas' -import '../terminal/session/mobile-session-terminal-reply-schema' -import '../session/native-chat/composer/mobile-native-chat-skill-reply-schema' -import '../terminal/quick-commands/mobile-quick-command-reply-schema' -import '../files/workspace-file-paths-reply-schema' // Read the whole worktree for notes and live title; unlike mutation readers, absent records need no error or wrong-host fallback. export const sessionWorktreeRecordRead = bindDeferredRpcOperation( diff --git a/mobile/src/worktree/session-worktree-record-reply-schema.ts b/mobile/src/worktree/session-worktree-record-reply-schema.ts index c5b807e093c..6ac8c10d1b8 100644 --- a/mobile/src/worktree/session-worktree-record-reply-schema.ts +++ b/mobile/src/worktree/session-worktree-record-reply-schema.ts @@ -14,6 +14,7 @@ export const sessionWorktreeRecordSchema = z repo: salvagedOptional('repo', z.string().nullable()), path: salvagedOptional('path', z.string()), hostId: salvagedOptional('hostId', z.string()), + branch: salvagedOptional('branch', z.string().nullable()), diffComments: z.unknown().optional() }) ) @@ -22,6 +23,11 @@ export const sessionWorktreeRecordSchema = z ( reply ): - | (WorktreeDisplayNameSource & { path?: string; hostId?: string; diffComments?: unknown }) + | (WorktreeDisplayNameSource & { + path?: string + hostId?: string + branch?: string | null + diffComments?: unknown + }) | undefined => reply.worktree ) diff --git a/mobile/src/worktree/use-live-worktree-name.ts b/mobile/src/worktree/use-live-worktree-name.ts index 4501b3a1922..3a09dcb5650 100644 --- a/mobile/src/worktree/use-live-worktree-name.ts +++ b/mobile/src/worktree/use-live-worktree-name.ts @@ -30,6 +30,8 @@ export type LiveWorktreeName = { /** Where the worktree lives on its host; null until the host's record arrives. */ path: string | null hostId: string | null + /** The checked-out branch without refs/heads/; null for folders, detached heads and until the record arrives. */ + branch: string | null resolution: WorktreeShowResolution } @@ -48,6 +50,7 @@ export function useLiveWorktreeName({ name: string path?: string | null hostId?: string | null + branch?: string | null }>(() => ({ worktreeId, name: routeNameHint })) // Key verdicts by route so a previous workspace’s deletion cannot escape into the new one. const [resolved, setResolved] = useState<{ @@ -125,13 +128,16 @@ export function useLiveWorktreeName({ if (liveName) { const path = worktree?.path || null const hostId = worktree?.hostId ?? null + // Match desktop formatBranchName by hiding the refs/heads/ prefix. + const branch = worktree?.branch?.replace(/^refs\/heads\//, '') || null setWorktreeName((current) => current.worktreeId === worktreeId && current.name === liveName && current.path === path && - current.hostId === hostId + current.hostId === hostId && + current.branch === branch ? current - : { worktreeId, name: liveName, path, hostId } + : { worktreeId, name: liveName, path, hostId, branch } ) } hasSuccessfulRefresh = true @@ -197,12 +203,19 @@ export function useLiveWorktreeName({ ) if (isFloatingWorkspace) { - return { name: getFloatingWorkspaceTitle(), path: null, hostId: 'local', resolution: 'unknown' } + return { + name: getFloatingWorkspaceTitle(), + path: null, + hostId: 'local', + branch: null, + resolution: 'unknown' + } } return { name: worktreeName.worktreeId === worktreeId ? worktreeName.name : routeNameHint, path: worktreeName.worktreeId === worktreeId ? (worktreeName.path ?? null) : null, hostId: worktreeName.worktreeId === worktreeId ? (worktreeName.hostId ?? null) : null, + branch: worktreeName.worktreeId === worktreeId ? (worktreeName.branch ?? null) : null, resolution: resolved.worktreeId === worktreeId ? resolved.resolution : 'unknown' } } diff --git a/mobile/web-entry/index.tsx b/mobile/web-entry/index.tsx index a2da5aad055..df11b8b7952 100644 --- a/mobile/web-entry/index.tsx +++ b/mobile/web-entry/index.tsx @@ -17,6 +17,7 @@ import { type PageMountTarget } from '../src/mobile-web-shell/bridge/page-bootstrap' import { storageReadResultSchema } from '../src/mobile-web-shell/bridge/bridge-native-verbs' +import { BRIDGE_HOST_CATALOGS_GRANT } from '../src/mobile-web-shell/bridge/bridge-frame-fields' import { publishPageStorage, refreshPageTerminalSettings @@ -33,6 +34,7 @@ import { PageFaultBoundary } from '../src/mobile-web-shell/bridge/page-fault-bou import type { BridgeUiLocale } from '../src/mobile-web-shell/bridge/bridge-envelope' import { BRIDGE_OPEN_ROUTE_PARAM } from '../src/mobile-web-shell/bridge/bridge-route-update' import { activateMobileUiLocale, subscribeMobileUiLanguage } from '../src/i18n/mobile-i18n' +import { startPageLocaleCatalogs } from '../src/i18n/page-locale-catalog-source' import { getUiIntlLocale } from '../src/i18n/ui-intl-locale' import { readDeviceUiLocaleTags, resolveUiLocaleFromTags } from '../src/i18n/system-ui-locale' import { publishPageHostProfile } from '../src/mobile-web-shell/bridge/page-host-profile' @@ -158,6 +160,12 @@ bootstrapShellPage({ // Apply before the first render so no component paints the default dark palette first. applyMobileColorScheme(session.colorScheme) client.onColorSchemeUpdate(applyMobileColorScheme) + const deviceStore = createPageDeviceStore(client) + publishPageDeviceStore(deviceStore) + // A shell that skipped the catalog chunks has the page read its catalog from the desktop; before the first load. + if (session.grants.native.includes(BRIDGE_HOST_CATALOGS_GRANT)) { + startPageLocaleCatalogs({ client, store: deviceStore }) + } // Subscribe before the first catalog load so a switch during startup is not lost; the latest request wins. let uiLocaleActivation = activatePageUiLocale(session.uiLocale) client.onUiLocaleUpdate((locale) => { @@ -187,8 +195,6 @@ bootstrapShellPage({ ).value : null ) - const deviceStore = createPageDeviceStore(client) - publishPageDeviceStore(deviceStore) // An open-target boot paints the shell's target first, so that is the screen to preload and restore. const route = bootRoute(session.route?.params?.[BRIDGE_OPEN_ROUTE_PARAM]) const queryClient = createOrcaQueryClient() diff --git a/mobile/web-entry/web-overrides.json b/mobile/web-entry/web-overrides.json index 954c12892fb..52e4bca5ba5 100644 --- a/mobile/web-entry/web-overrides.json +++ b/mobile/web-entry/web-overrides.json @@ -26,19 +26,23 @@ "reason": "The page is one document standing in for one screen, so a route it does not render has to be handed to the app that does. The native file is the router; this one posts a navigate notify for a target outside the page's routes and pushes locally for one inside them." }, { - "file": "src/transport/hosts/host-store.web.ts", + "file": "src/host-catalog/host-store.web.ts", "reason": "expo-secure-store resolves to {} on web, so the real store answers loadHosts() with an empty array and every screen that looks this host up paints \"Host not found\" over the host the shell just opened. This one serves the profile init carried, without the credential the bridge already holds." }, { - "file": "src/transport/hosts/host-descriptor-persistence.web.ts", + "file": "src/host-catalog/host-connection-history.web.ts", + "reason": "Recency belongs to the native host list. The host-served page retains the existing no-op and imports no native metadata mutation queue." + }, + { + "file": "src/host-descriptors/host-descriptor-persistence.web.ts", "reason": "Last-known descriptors belong to the native host list; the page owns no descriptor persistence." }, { - "file": "src/transport/hosts/host-removal-lifecycle.web.ts", + "file": "src/host-catalog/host-removal-lifecycle.web.ts", "reason": "Removing a host is pairing work and the page holds neither the host list nor the credential, so the real lifecycle's removeHost resolved without doing anything and the screen reported success for a host that was still paired. This one refuses with PageHostRemovalUnavailableError, whose message names the app's host list. It is also the fence that keeps push-registration.ts out of the page bundle: the native file's import of it was that subsystem's only path into a page route, and the page has neither the device token nor the gateway client push needs." }, { - "file": "src/transport/hosts/host-device-token-store.web.ts", + "file": "src/host-credentials/host-device-token-store.web.ts", "reason": "expo-secure-store resolves to {} on web, and the bridge carries the RPC, so the page holds no device token." }, { @@ -198,7 +202,7 @@ "reason": "react-native-web's keydown handler withholds onSubmitEditing whenever the Enter keydown reports a composition — nativeEvent.isComposing, or the Android keyCode 229 that stands for it — which is a soft keyboard's normal state mid-word, so Enter on the page neither sent the line nor cleared the field while native Android's editor action fired as it always does. Both terminal fields reach their send through onSubmitEditing alone and both are bound here: the live input's carriage return, and the buffered command field's draft. The native file binds nothing, because that editor action needs no help; this one listens for beforeinput and acts on insertLineBreak and insertParagraph, the browser's own end-of-line signals. react-native-web calls preventDefault on every keydown it does submit on, so those events exist only in the cases it dropped and never produce a second carriage return; an IME still choosing a candidate reports insertCompositionText and is left alone." }, { - "file": "src/transport/hosts/host-app-version-store.web.ts", + "file": "src/host-descriptors/host-app-version-store.web.ts", "reason": "The page keeps no record of the host's status.get app version because nothing in it reads one — the reader is the native troubleshoot screen's — and a key the page only writes is not page state, so it is not admitted through page-storage-keys.ts and every mount's write was refused and logged instead." }, { diff --git a/package.json b/package.json index f25b0e9565b..784cd2650f5 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "check:repo-host-read-ratchet": "node config/scripts/static-analysis/ratchets/renderer-state/check-repo-host-read-ratchet.mjs", "check:workspace-record-read-ratchet": "node config/scripts/static-analysis/ratchets/renderer-state/check-workspace-record-read-ratchet.mjs", "check:tab-map-seam-ratchet": "node config/scripts/static-analysis/ratchets/renderer-state/check-tab-map-seam-ratchet.mjs", + "check:terminal-parking-ownership-ratchet": "node config/scripts/static-analysis/ratchets/renderer-state/check-terminal-parking-ownership-ratchet.mjs", "check:worktree-id-composer-ratchet": "node config/scripts/static-analysis/ratchets/renderer-state/check-worktree-id-composer-ratchet.mjs", "check:direct-link-open-ratchet": "node config/scripts/static-analysis/ratchets/renderer-state/check-direct-link-open-ratchet.mjs", "check:e2e-typecheck-ratchet": "node config/scripts/typecheck/e2e-admission/check-e2e-typecheck-ratchet.mjs", @@ -141,6 +142,7 @@ "sync:localization-protected-copy": "node config/scripts/localization/catalog-validation/protected-copy-keys.mjs --fix", "verify:localization-extraction": "node config/scripts/localization/source-extraction/verify-localization-extraction.mjs", "verify:localization-coverage": "node config/scripts/localization/source-extraction/audit-localization-coverage.mjs --check", + "verify:startup-worktree-localization-coverage": "node 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", "verify:mobile-localization": "node config/scripts/localization/catalog-validation/verify-mobile-localization.mjs", "sync:mobile-localization": "node config/scripts/localization/catalog-validation/verify-mobile-localization.mjs --fix", "verify:mobile-localization-coverage": "node config/scripts/localization/source-extraction/audit-localization-coverage.mjs --check --source-root mobile/app --source-root mobile/src --source-root mobile/web-entry --allowlist config/mobile-localization-coverage-allowlist.json", diff --git a/resources/plugins/declarative/orca-testing.mjs b/resources/plugins/declarative/orca-testing.mjs index 4b5cb50a469..05e98fa9f45 100644 --- a/resources/plugins/declarative/orca-testing.mjs +++ b/resources/plugins/declarative/orca-testing.mjs @@ -1,11 +1,11 @@ // Generated by config/scripts/plugin-build/declarative-runtime/build-plugin-declarative-runtime.mjs from // src/shared/plugins/declarative/testing/. Do not edit. -var Yy=Object.defineProperty;var We=(e,n)=>{for(var i in n)Yy(e,i,{get:n[i],enumerable:!0})};import{readFileSync as _P}from"node:fs";import{dirname as Jy,join as $P}from"node:path";var l={};We(l,{$brand:()=>Fa,$input:()=>hu,$output:()=>gu,INVALID:()=>me,NEVER:()=>Ga,TimePrecision:()=>Tu,ZodAny:()=>gd,ZodArray:()=>bd,ZodBase64:()=>Yo,ZodBase64URL:()=>Qo,ZodBigInt:()=>En,ZodBigIntFormat:()=>na,ZodBoolean:()=>Tn,ZodCIDRv4:()=>Xo,ZodCIDRv6:()=>qo,ZodCUID:()=>Go,ZodCUID2:()=>Fo,ZodCatch:()=>Md,ZodCodec:()=>Lr,ZodCompileAsyncError:()=>De,ZodCompileUnsupportedError:()=>V,ZodCreditCard:()=>sd,ZodCustom:()=>Ur,ZodCustomStringFormat:()=>Lt,ZodDate:()=>Or,ZodDefault:()=>Dd,ZodDiscriminatedUnion:()=>$d,ZodE164:()=>ea,ZodEmail:()=>Ro,ZodEmoji:()=>Zo,ZodEnum:()=>In,ZodError:()=>vx,ZodExactOptional:()=>aa,ZodFile:()=>Ad,ZodFirstPartyTypeKind:()=>ep,ZodFunction:()=>Xd,ZodGUID:()=>Mo,ZodIBAN:()=>cd,ZodIPv4:()=>Wo,ZodIPv6:()=>Jo,ZodISODate:()=>mt,ZodISODateTime:()=>pt,ZodISODuration:()=>gt,ZodISOTime:()=>ft,ZodInstanceOf:()=>Qd,ZodIntersection:()=>kd,ZodIssueCode:()=>_x,ZodJWT:()=>ta,ZodKSUID:()=>Ko,ZodLazy:()=>Kd,ZodLiteral:()=>Ed,ZodMAC:()=>ad,ZodMap:()=>Pd,ZodNaN:()=>Vd,ZodNanoID:()=>Vo,ZodNever:()=>vd,ZodNonOptional:()=>sa,ZodNull:()=>md,ZodNullable:()=>zd,ZodNumber:()=>Pn,ZodNumberFormat:()=>Ut,ZodObject:()=>zr,ZodOptional:()=>Cr,ZodPipe:()=>jr,ZodPrefault:()=>jd,ZodPreprocess:()=>Gd,ZodPromise:()=>Jd,ZodReadonly:()=>Fd,ZodRealError:()=>xe,ZodRecord:()=>wn,ZodSet:()=>Td,ZodString:()=>Sn,ZodStringFormat:()=>X,ZodSuccess:()=>Rd,ZodSymbol:()=>dd,ZodTemplateLiteral:()=>Hd,ZodTransform:()=>Od,ZodTuple:()=>wd,ZodType:()=>M,ZodULID:()=>Bo,ZodURL:()=>Ar,ZodUUID:()=>Fe,ZodUndefined:()=>pd,ZodUnion:()=>Dr,ZodUnknown:()=>hd,ZodVoid:()=>yd,ZodXID:()=>Ho,ZodXor:()=>_d,_ZodString:()=>Uo,_default:()=>Cd,_function:()=>Ih,any:()=>nh,array:()=>Nr,base64:()=>jg,base64url:()=>Lg,bigint:()=>qg,boolean:()=>ld,catch:()=>Zd,check:()=>Sh,cidrv4:()=>Dg,cidrv6:()=>Cg,clone:()=>K,codec:()=>$h,coerce:()=>np,compile:()=>_u,config:()=>ee,core:()=>Qe,creditCard:()=>Rg,cuid:()=>Sg,cuid2:()=>Pg,currencyCode:()=>Bg,custom:()=>Ph,date:()=>ih,decode:()=>Ql,decodeAsync:()=>td,deepPartial:()=>Mh,describe:()=>Th,discriminatedUnion:()=>lh,e164:()=>Ug,email:()=>hg,emoji:()=>wg,encode:()=>Yl,encodeAsync:()=>ed,endsWith:()=>mn,enum:()=>ia,exactOptional:()=>Nd,file:()=>vh,flattenError:()=>Qn,float32:()=>Kg,float64:()=>Wg,formatError:()=>er,fromJSONSchema:()=>Uh,function:()=>Ih,getDiscriminatedOption:()=>Gc,getErrorMap:()=>kx,globalRegistry:()=>ce,gt:()=>Ve,gte:()=>Ie,guid:()=>vg,hash:()=>Hg,hex:()=>Fg,hostname:()=>Gg,httpUrl:()=>xg,iban:()=>Mg,includes:()=>dn,input:()=>Vh,instanceof:()=>Ah,int:()=>jo,int32:()=>Jg,int64:()=>Yg,intersection:()=>xd,invertCodec:()=>kh,ipv4:()=>Og,ipv6:()=>zg,iso:()=>Rr,json:()=>Nh,jwt:()=>Zg,keyof:()=>oh,ksuid:()=>Ag,lazy:()=>Wd,length:()=>Nt,literal:()=>hh,locales:()=>_r,looseObject:()=>ch,looseRecord:()=>ph,lowercase:()=>un,lt:()=>Ze,lte:()=>we,mac:()=>Ng,map:()=>mh,maxLength:()=>Ot,maxSize:()=>ct,memoizer:()=>yr,meta:()=>Eh,mime:()=>gn,minLength:()=>Ye,minSize:()=>Ge,multipleOf:()=>st,nan:()=>_h,nanoid:()=>Ig,nativeEnum:()=>gh,negative:()=>Io,never:()=>ra,nonnegative:()=>Po,nonoptional:()=>Ud,nonpositive:()=>So,normalize:()=>hn,null:()=>fd,nullable:()=>Er,nullish:()=>yh,number:()=>ud,object:()=>ah,optional:()=>Ct,output:()=>Gh,overwrite:()=>Ce,parse:()=>Wl,parseAsync:()=>Jl,partialRecord:()=>dh,pipe:()=>Lo,positive:()=>wo,prefault:()=>Ld,preprocess:()=>zh,prettifyError:()=>Ja,promise:()=>wh,properties:()=>fn,property:()=>To,readonly:()=>Bd,record:()=>Sd,refine:()=>qd,regex:()=>cn,regexes:()=>Te,registry:()=>Yi,safeDecode:()=>rd,safeDecodeAsync:()=>od,safeEncode:()=>nd,safeEncodeAsync:()=>id,safeParse:()=>Xl,safeParseAsync:()=>ql,set:()=>fh,setErrorMap:()=>$x,size:()=>At,slugify:()=>_n,startsWith:()=>pn,strictObject:()=>sh,string:()=>Tr,stringFormat:()=>Vg,stringbool:()=>Oh,success:()=>bh,superRefine:()=>Yd,symbol:()=>eh,templateLiteral:()=>xh,toJSONSchema:()=>No,toLowerCase:()=>yn,toUpperCase:()=>bn,toZod:()=>Gn,transform:()=>oa,treeifyError:()=>Wa,trim:()=>vn,tuple:()=>Id,uint32:()=>Xg,uint64:()=>Qg,ulid:()=>Tg,undefined:()=>th,union:()=>An,unknown:()=>Dt,uppercase:()=>ln,url:()=>kg,util:()=>_,uuid:()=>yg,uuidv4:()=>bg,uuidv6:()=>_g,uuidv7:()=>$g,validate:()=>tr,validateAsync:()=>nr,void:()=>rh,withParser:()=>Qi,xid:()=>Eg,xor:()=>uh});var Qe={};We(Qe,{$ZodAny:()=>Dc,$ZodArray:()=>Mc,$ZodAsyncError:()=>$e,$ZodBase64:()=>kc,$ZodBase64URL:()=>xc,$ZodBigInt:()=>Ui,$ZodBigIntFormat:()=>Ac,$ZodBoolean:()=>pr,$ZodCIDRv4:()=>_c,$ZodCIDRv6:()=>$c,$ZodCUID:()=>cc,$ZodCUID2:()=>uc,$ZodCatch:()=>ou,$ZodCheck:()=>q,$ZodCheckBigIntFormat:()=>Cs,$ZodCheckEndsWith:()=>Ks,$ZodCheckGreaterThan:()=>ki,$ZodCheckIncludes:()=>Bs,$ZodCheckLengthEquals:()=>Zs,$ZodCheckLessThan:()=>$i,$ZodCheckLowerCase:()=>Gs,$ZodCheckMaxLength:()=>Rs,$ZodCheckMaxSize:()=>js,$ZodCheckMimeType:()=>Xs,$ZodCheckMinLength:()=>Ms,$ZodCheckMinSize:()=>Ls,$ZodCheckMultipleOf:()=>zs,$ZodCheckNumberFormat:()=>Ds,$ZodCheckOverwrite:()=>qs,$ZodCheckProperties:()=>Js,$ZodCheckProperty:()=>Ws,$ZodCheckRegex:()=>Vs,$ZodCheckSizeEquals:()=>Us,$ZodCheckStartsWith:()=>Hs,$ZodCheckStringFormat:()=>nn,$ZodCheckUpperCase:()=>Fs,$ZodCodec:()=>fr,$ZodCreditCard:()=>Ic,$ZodCustom:()=>pu,$ZodCustomStringFormat:()=>Tc,$ZodCyclicError:()=>Bi,$ZodDate:()=>Uc,$ZodDefault:()=>tu,$ZodDiscriminatedUnion:()=>Fc,$ZodE164:()=>wc,$ZodEmail:()=>nc,$ZodEmoji:()=>ac,$ZodEncodeError:()=>ot,$ZodEnum:()=>Jc,$ZodError:()=>Ka,$ZodExactOptional:()=>Qc,$ZodFile:()=>qc,$ZodFunction:()=>lu,$ZodGUID:()=>ec,$ZodIBAN:()=>Sc,$ZodIPv4:()=>vc,$ZodIPv6:()=>yc,$ZodISODate:()=>fc,$ZodISODateTime:()=>mc,$ZodISODuration:()=>hc,$ZodISOTime:()=>gc,$ZodIntersection:()=>Bc,$ZodJWT:()=>Pc,$ZodKSUID:()=>pc,$ZodLazy:()=>gr,$ZodLiteral:()=>Xc,$ZodMAC:()=>bc,$ZodMap:()=>Kc,$ZodNaN:()=>au,$ZodNanoID:()=>sc,$ZodNever:()=>jc,$ZodNonOptional:()=>ru,$ZodNull:()=>zc,$ZodNullable:()=>eu,$ZodNumber:()=>Li,$ZodNumberFormat:()=>Ec,$ZodObject:()=>Im,$ZodObjectJIT:()=>Zc,$ZodOptional:()=>Mi,$ZodPipe:()=>Zi,$ZodPrefault:()=>nu,$ZodPreprocess:()=>su,$ZodPromise:()=>du,$ZodReadonly:()=>cu,$ZodRealError:()=>ke,$ZodRecord:()=>Hc,$ZodRegistry:()=>qi,$ZodSet:()=>Wc,$ZodString:()=>St,$ZodStringFormat:()=>W,$ZodSuccess:()=>iu,$ZodSymbol:()=>Oc,$ZodTemplateLiteral:()=>uu,$ZodTransform:()=>Yc,$ZodTuple:()=>Ri,$ZodType:()=>U,$ZodULID:()=>lc,$ZodURL:()=>oc,$ZodUUID:()=>tc,$ZodUndefined:()=>Nc,$ZodUnion:()=>mr,$ZodUnknown:()=>Cc,$ZodVoid:()=>Lc,$ZodXID:()=>dc,$ZodXor:()=>Vc,$brand:()=>Fa,$constructor:()=>h,$input:()=>hu,$output:()=>gu,Doc:()=>It,INVALID:()=>me,JSONSchema:()=>mg,JSONSchemaGenerator:()=>zo,NEVER:()=>Ga,TimePrecision:()=>Tu,URL_BAD_FORMAT:()=>rc,URL_UNPARSEABLE:()=>sr,ZodCompileAsyncError:()=>De,ZodCompileUnsupportedError:()=>V,_any:()=>Bu,_array:()=>Yu,_base64:()=>_o,_base64url:()=>$o,_bigint:()=>Uu,_boolean:()=>ju,_catch:()=>ox,_check:()=>qf,_cidrv4:()=>yo,_cidrv6:()=>bo,_coercedBigint:()=>Ru,_coercedBoolean:()=>Lu,_coercedDate:()=>Xu,_coercedNumber:()=>Au,_coercedString:()=>wu,_creditCard:()=>Su,_cuid:()=>lo,_cuid2:()=>po,_custom:()=>el,_date:()=>Ju,_decode:()=>li,_decodeAsync:()=>pi,_default:()=>nx,_discriminatedUnion:()=>Fk,_e164:()=>ko,_email:()=>no,_emoji:()=>co,_encode:()=>ui,_encodeAsync:()=>di,_endsWith:()=>mn,_enum:()=>Xk,_file:()=>Qu,_float32:()=>Nu,_float64:()=>zu,_gt:()=>Ve,_gte:()=>Ie,_guid:()=>ro,_iban:()=>Pu,_includes:()=>dn,_int:()=>Ou,_int32:()=>Du,_int64:()=>Mu,_intersection:()=>Bk,_ipv4:()=>ho,_ipv6:()=>vo,_isoDate:()=>xr,_isoDateTime:()=>kr,_isoDuration:()=>Ir,_isoTime:()=>wr,_jwt:()=>xo,_ksuid:()=>go,_lazy:()=>ux,_length:()=>Nt,_literal:()=>Yk,_lowercase:()=>un,_lt:()=>Ze,_lte:()=>we,_mac:()=>Iu,_map:()=>Wk,_max:()=>we,_maxLength:()=>Ot,_maxSize:()=>ct,_mime:()=>gn,_min:()=>Ie,_minLength:()=>Ye,_minSize:()=>Ge,_multipleOf:()=>st,_nan:()=>qu,_nanoid:()=>uo,_nativeEnum:()=>qk,_negative:()=>Io,_never:()=>Ku,_nonnegative:()=>Po,_nonoptional:()=>rx,_nonpositive:()=>So,_normalize:()=>hn,_null:()=>Fu,_nullable:()=>tx,_number:()=>Eu,_optional:()=>ex,_overwrite:()=>Ce,_parse:()=>Yt,_parseAsync:()=>Qt,_pipe:()=>ax,_positive:()=>wo,_promise:()=>lx,_properties:()=>fn,_property:()=>To,_readonly:()=>sx,_record:()=>Kk,_refine:()=>tl,_regex:()=>cn,_safeDecode:()=>fi,_safeDecodeAsync:()=>hi,_safeEncode:()=>mi,_safeEncodeAsync:()=>gi,_safeParse:()=>en,_safeParseAsync:()=>tn,_set:()=>Jk,_size:()=>At,_slugify:()=>_n,_startsWith:()=>pn,_string:()=>xu,_stringFormat:()=>zt,_stringbool:()=>ol,_success:()=>ix,_superRefine:()=>nl,_symbol:()=>Vu,_templateLiteral:()=>cx,_toLowerCase:()=>yn,_toUpperCase:()=>bn,_transform:()=>Qk,_trim:()=>vn,_tuple:()=>Hk,_uint32:()=>Cu,_uint64:()=>Zu,_ulid:()=>mo,_undefined:()=>Gu,_union:()=>Vk,_unknown:()=>Hu,_uppercase:()=>ln,_url:()=>$r,_uuid:()=>io,_uuidv4:()=>oo,_uuidv6:()=>ao,_uuidv7:()=>so,_void:()=>Wu,_xid:()=>fo,_xor:()=>Gk,base64Charset:()=>Ni,base64urlCharset:()=>dr,canParseURL:()=>ic,clone:()=>K,compile:()=>_u,compileFn:()=>eo,config:()=>ee,createStandardJSONSchemaMethod:()=>$n,createToJSONSchemaMethod:()=>sl,decode:()=>Gb,decodeAsync:()=>Bb,describe:()=>rl,encode:()=>Vb,encodeAsync:()=>Fb,extractDefs:()=>lt,finalize:()=>dt,flattenError:()=>Qn,formatError:()=>er,getDiscriminatedOption:()=>Gc,globalConfig:()=>oe,globalRegistry:()=>ce,handleUnrepresentable:()=>re,initializeContext:()=>ut,isBackEdge:()=>Ki,isRecursiveSchema:()=>Hi,isValidBase64:()=>lr,isValidBase64URL:()=>zi,isValidCIDRv6:()=>Oi,isValidCreditCard:()=>Di,isValidIBAN:()=>Ci,isValidIPv6:()=>ur,isValidJWT:()=>ji,locales:()=>_r,memoizer:()=>yr,mergeValues:()=>rn,meta:()=>il,parse:()=>si,parseAsync:()=>ci,parseURLObject:()=>km,prettifyError:()=>Ja,process:()=>J,processSchema:()=>J,regexes:()=>Te,registry:()=>Yi,safeDecode:()=>Kb,safeDecodeAsync:()=>Jb,safeEncode:()=>Hb,safeEncodeAsync:()=>Wb,safeParse:()=>Lb,safeParseAsync:()=>Ub,standardProps:()=>Pi,stripTabAndNewline:()=>cr,toDotPath:()=>tm,toJSONSchema:()=>No,toZod:()=>Gn,treeifyError:()=>Wa,urlHostnameOk:()=>Ei,urlProtocolOk:()=>Ai,util:()=>_,validate:()=>tr,validateAsync:()=>nr,validateURL:()=>Ti,version:()=>Ys,withParser:()=>Qi});var _={};We(_,{BIGINT_FORMAT_RANGES:()=>Wn,CONSTANT_CATCH:()=>oi,Class:()=>za,NUMBER_FORMAT_RANGES:()=>Kn,aborted:()=>pe,allowsEval:()=>ja,assert:()=>rb,assertEqual:()=>Qy,assertIs:()=>tb,assertNever:()=>nb,assertNotEqual:()=>eb,assignProp:()=>se,attachSchema:()=>Jn,base64ToUint8Array:()=>Jp,base64urlToUint8Array:()=>kb,cached:()=>Wt,captureStackTrace:()=>ri,cleanEnum:()=>$b,cleanRegex:()=>Xe,clone:()=>K,cloneDef:()=>ab,codePointLength:()=>xt,constantCatch:()=>Za,createTransparentProxy:()=>pb,defineLazy:()=>Da,defineLazyInternal:()=>Z,derived:()=>Sb,esc:()=>de,escapeRegex:()=>Pe,explicitlyAborted:()=>Ra,extend:()=>gb,finalizeIssue:()=>_e,floatSafeRemainder:()=>Fn,getElementAtPath:()=>sb,getEnumValues:()=>Ht,getLengthableOrigin:()=>qn,getParsedType:()=>db,getSizableOrigin:()=>Xn,hexToUint8Array:()=>wb,hide:()=>Yn,installLazyProp:()=>Ab,isObject:()=>kt,isPlainObject:()=>Se,issue:()=>qt,joinValues:()=>v,jsonStringifyReplacer:()=>Kt,members:()=>Ma,merge:()=>vb,mergeDefs:()=>ze,normalizeParams:()=>S,nullish:()=>ni,numKeys:()=>lb,objectClone:()=>ib,omit:()=>fb,optionalKeys:()=>Ua,own:()=>it,parsedType:()=>k,partial:()=>yb,pick:()=>mb,prefixIssues:()=>be,primitiveTypes:()=>La,promiseAllObject:()=>cb,propertyKeyTypes:()=>Hn,randomString:()=>ub,rawShape:()=>Jt,required:()=>bb,safeExtend:()=>hb,shallowClone:()=>Bn,sizeCaps:()=>Xt,slugify:()=>Ca,stringifyPrimitive:()=>$,toZod:()=>Gn,uint8ArrayToBase64:()=>Xp,uint8ArrayToBase64url:()=>xb,uint8ArrayToHex:()=>Ib,unwrapMessage:()=>Bt});function Qy(e){return e}function eb(e){return e}function Gn(){return e=>e}function tb(e){}function nb(e){throw new Error("Unexpected value in exhaustive check")}function rb(e){}function Ht(e){let n=Object.values(e).filter(o=>typeof o=="number");return Object.entries(e).filter(([o,t])=>n.indexOf(+o)===-1).map(([o,t])=>t)}function v(e,n="|"){return e.map(i=>$(i)).join(n)}function Kt(e,n){return typeof n=="bigint"?n.toString():n}var Na=class{constructor(n){this._getter=n,this._value=void 0}get value(){let n=this._getter;return n!==void 0&&(this._value=n(),this._getter=void 0),this._value}};function Wt(e){return new Na(e)}function ni(e){return e==null}function Xe(e){let n=e.startsWith("^")?1:0,i=e.endsWith("$")?e.length-1:e.length;return e.slice(n,i)}function Fn(e,n){let i=e/n,o=Math.round(i),t=4*Number.EPSILON*Math.max(Math.abs(i),1);return Math.abs(i-o){let s=n._zod.def.shape[r];return o?o(s,r):s}):Kp(e,r,o?o(a.value,r):a.value))}}function ob(e,n){for(let i of Reflect.ownKeys(n)){let o=Object.getOwnPropertyDescriptor(n,i);o.enumerable&&(o.get?Hp(e,i,()=>n[i]):Kp(e,i,o.value))}}function ze(...e){let n={};for(let i of e){let o=Object.getOwnPropertyDescriptors(i);Object.assign(n,o)}return Object.defineProperties({},n)}function ab(e){return ze(e._zod.def)}function sb(e,n){return n?n.reduce((i,o)=>i?.[o],e):e}function cb(e){let n=Object.keys(e),i=n.map(o=>e[o]);return Promise.all(i).then(o=>{let t={};for(let r=0;r{};function kt(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}var ja=Wt(()=>{if(oe.jitless||typeof navigator<"u"&&navigator?.userAgent?.includes("Cloudflare"))return!1;try{let e=Function;return new e(""),!0}catch{return!1}});function Se(e){if(kt(e)===!1)return!1;let n=e.constructor;if(n===void 0||typeof n!="function")return!0;let i=n.prototype;return!(kt(i)===!1||Object.prototype.hasOwnProperty.call(i,"isPrototypeOf")===!1)}function Bn(e){return Se(e)?{...e}:Array.isArray(e)?[...e]:e instanceof Map?new Map(e):e instanceof Set?new Set(e):e}function lb(e){let n=0;for(let i in e)Object.prototype.hasOwnProperty.call(e,i)&&n++;return n}var db=e=>{let n=typeof e;switch(n){case"undefined":return"undefined";case"string":return"string";case"number":return Number.isNaN(e)?"nan":"number";case"boolean":return"boolean";case"function":return"function";case"bigint":return"bigint";case"symbol":return"symbol";case"object":return Array.isArray(e)?"array":e===null?"null":e.then&&typeof e.then=="function"&&e.catch&&typeof e.catch=="function"?"promise":typeof Map<"u"&&e instanceof Map?"map":typeof Set<"u"&&e instanceof Set?"set":typeof Date<"u"&&e instanceof Date?"date":typeof File<"u"&&e instanceof File?"file":"object";default:throw new Error(`Unknown data type: ${n}`)}},Hn=new Set(["string","number","symbol"]),La=new Set(["string","number","bigint","boolean","symbol","undefined"]);function Pe(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function K(e,n,i){let o=new e._zod.constr(n??e._zod.def);return(!n||i?.parent)&&(o._zod.parent=e),o}function S(e){let n=e;if(!n)return{};if(typeof n=="string")return{error:()=>n};if(n?.message!==void 0){if(n?.error!==void 0)throw new Error("Cannot specify both `message` and `error` params");n.error=n.message}return delete n.message,typeof n.error=="string"?{...n,error:()=>n.error}:n}function pb(e){let n;return new Proxy({},{get(i,o,t){return n??(n=e()),Reflect.get(n,o,t)},set(i,o,t,r){return n??(n=e()),Reflect.set(n,o,t,r)},has(i,o){return n??(n=e()),Reflect.has(n,o)},deleteProperty(i,o){return n??(n=e()),Reflect.deleteProperty(n,o)},ownKeys(i){return n??(n=e()),Reflect.ownKeys(n)},getOwnPropertyDescriptor(i,o){return n??(n=e()),Reflect.getOwnPropertyDescriptor(n,o)},defineProperty(i,o,t){return n??(n=e()),Reflect.defineProperty(n,o,t)}})}function $(e){return typeof e=="bigint"?e.toString()+"n":typeof e=="string"?`"${e}"`:`${e}`}function Ua(e){return Object.keys(e).filter(n=>e[n]._zod.optin!==void 0&&e[n]._zod.optout==="optional")}var Kn={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]},Wn={int64:[BigInt("-9223372036854775808"),BigInt("9223372036854775807")],uint64:[BigInt(0),BigInt("18446744073709551615")]};function mb(e,n){let i=e._zod.def,o=i.checks;if(o&&o.length>0)throw new Error(".pick() cannot be used on object schemas containing refinements");let r={};return $t(r,e,ii(e,n)),K(e,ze(i,{shape:r,checks:[]}))}function ii(e,n){let i=Je(e),o=[];for(let t of Reflect.ownKeys(n)){if(!Object.getOwnPropertyDescriptor(i,t)?.enumerable)throw new Error(`Unrecognized key: "${String(t)}"`);n[t]&&o.push(t)}return o}function fb(e,n){let i=e._zod.def,o=i.checks;if(o&&o.length>0)throw new Error(".omit() cannot be used on object schemas containing refinements");let r=new Set(ii(e,n)),a={};return $t(a,e,Reflect.ownKeys(Je(e)).filter(s=>!r.has(s))),K(e,ze(i,{shape:a,checks:[]}))}function gb(e,n){if(!Se(n))throw new Error("Invalid input to extend: expected a plain object");let i=e._zod.def.checks;if(i&&i.length>0){let t=Je(e);for(let r of Reflect.ownKeys(n))if(Object.getOwnPropertyDescriptor(t,r)!==void 0)throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.")}return K(e,ze(e._zod.def,{shape:Wp(e,n)}))}function Wp(e,n){let i={};return $t(i,e,Reflect.ownKeys(Je(e))),ob(i,n),i}function hb(e,n){if(!Se(n))throw new Error("Invalid input to safeExtend: expected a plain object");return K(e,ze(e._zod.def,{shape:Wp(e,n)}))}function vb(e,n){if(!n?._zod?.def)throw new Error("Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`.");if(e._zod.def.checks?.length)throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");let i={};$t(i,e,Reflect.ownKeys(Je(e))),$t(i,n,Reflect.ownKeys(Je(n)));let o=ze(e._zod.def,{shape:i,get catchall(){return n._zod.def.catchall},checks:n._zod.def.checks??[]});return K(e,o)}function yb(e,n,i,o="partial"){let r=n._zod.def.checks;if(r&&r.length>0)throw new Error(`.${o}() cannot be used on object schemas containing refinements`);let s=i?new Set(ii(n,i)):void 0,c={};return $t(c,n,Reflect.ownKeys(Je(n)),e&&((u,d)=>s&&!s.has(d)?u:new e({type:"optional",innerType:u}))),K(n,ze(n._zod.def,{shape:c,checks:[]}))}function bb(e,n,i){let o=i?new Set(ii(n,i)):void 0,t={};return $t(t,n,Reflect.ownKeys(Je(n)),(r,a)=>o&&!o.has(a)?r:new e({type:"nonoptional",innerType:r})),K(n,ze(n._zod.def,{shape:t}))}function pe(e,n=0){if(e.aborted===!0)return!0;for(let i=n;i{var o;return(o=i).path??(o.path=[]),i.path.unshift(e),i})}function Bt(e){return typeof e=="string"?e:e?.message}function Xt(e){let n=[];for(let i of e??[]){let o=i._zod.def;if(!/_(length|size)$|_equals$/.test(o.check))break;let t=o.maximum??o.length??o.size;t>=0&&n.push([t,i])}return n}function Jn(e,n,i){var o;for(let t=n;tNumber.isNaN(Number.parseInt(n,10))).map(n=>n[1])}function Jp(e){let n=atob(e),i=new Uint8Array(n.length);for(let o=0;on.toString(16).padStart(2,"0")).join("")}var za=class{constructor(...n){}};function Ma(e,n){for(let i in n){let o=Object.getOwnPropertyDescriptor(n,i);o.get?Object.defineProperty(e,i,{...o,enumerable:!1}):Pb(e,i,o.value)}}function it(e,n,i,o=!0){return Object.defineProperty(e,n,{configurable:!0,writable:!0,enumerable:o,value:i}),i}function Yn(e,n,i){return it(e,n,i,!1)}function Sb(e,n){for(let i in e){let o=e[i];Object.defineProperty(n,i,{configurable:!0,enumerable:!0,get(){return it(this,i,o(this))},set(t){it(this,i,t)}})}return n}function Pb(e,n,i){Object.defineProperty(e,n,{configurable:!0,get(){return this==null?i:it(this,n,i.bind(this))},set(o){it(this,n,o)}})}function Tb(e,n){let i=Object.getPrototypeOf(e);return n in i?void 0:i}var Oa,rt=!1,Eb={configurable:!0,get(){rt=!0}};function Z(e,n,i){let o=Object.getPrototypeOf(e._zod);if(n in o&&Oa!==e._zod){Oa=void 0;return}Oa=e._zod,Object.defineProperty(o,n,{configurable:!0,get(){Object.defineProperty(this,n,Eb);let t=rt;rt=!1;try{let r=i(this);return rt?delete this[n]:Object.defineProperty(this,n,{configurable:!0,writable:!0,value:r}),rt=rt||t,r}catch(r){throw delete this[n],rt=rt||t,r}},set(t){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:t})}})}function Ab(e,n,i,o){let t=Tb(e,n);t&&Object.defineProperty(t,n,{configurable:!0,get(){let r={configurable:!0,writable:!0,enumerable:o,value:void 0};return Object.defineProperty(this,n,r),r.value=i(this),Object.defineProperty(this,n,r),r.value},set(r){Object.defineProperty(this,n,{configurable:!0,writable:!0,enumerable:o,value:r})}})}var oi="~constantCatch";function Za(e){let n=()=>e;return n[oi]=!0,n}var qp,Ga=Object.freeze({status:"aborted"}),Va={value:void 0,enumerable:!1},Yp="captureStackTrace"in Error?Error:null;function Ob(e){let n=Yp;if(n){let i=n.stackTraceLimit;if(typeof i=="number"){try{n.stackTraceLimit=0}catch{return Yp=null,new e}try{return new e}finally{n.stackTraceLimit=i}}}return new e}function h(e,n,i,o){let t={};function r(m){this.def=m,this.constr=p,this.traits=new Set}r.prototype=t;let a=i,s=a&&new WeakSet;function c(m,f){if(m._zod){if(m._zod.traits.has(e))return}else{Va.value=new r(f);try{Object.defineProperty(m,"_zod",Va)}finally{Va.value=void 0}}if(m._zod.traits.add(e),n(m,f),s){let y=Object.getPrototypeOf(m),x=m._zod.constr.prototype,I=y;for(;I&&I!==x;)I=Object.getPrototypeOf(I);let b=I??y;s.has(b)||(s.add(b),Ma(b,a))}let g=p.prototype;for(let y in g)Object.prototype.hasOwnProperty.call(g,y)&&(y in m||(m[y]=g[y].bind(m)))}let u=o?.Parent??Object;class d extends u{}Object.defineProperty(d,"name",{value:e});function p(m){let f=o?.Parent?Ob(d):this;c(f,m);let g=f._zod.deferred;if(g){for(let x of g)x();f._zod.deferred=void 0}let y=globalThis.__zod_globalConfig?.postProcessor;return y&&y(f),f}return Object.defineProperty(p,"init",{value:c}),Object.defineProperty(p,Symbol.hasInstance,{value:m=>o?.Parent&&m instanceof o.Parent?!0:m?._zod?.traits?.has(e)}),Object.defineProperty(p,"name",{value:e}),p}var Fa=Symbol("zod_brand"),$e=class extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}},ot=class extends Error{constructor(n){super(`Encountered unidirectional transform during encode: ${n}`),this.name="ZodEncodeError"}};(qp=globalThis).__zod_globalConfig??(qp.__zod_globalConfig={});var oe=globalThis.__zod_globalConfig;function ee(e){return e&&Object.assign(oe,e),oe}function Nb(){let e=this._zod;return e.message??(e.message=JSON.stringify(e.def,Kt,2)),e.message}function zb(e){this._zod.message=e}var Db={get:Nb,set:zb,enumerable:!0,configurable:!0},Ha={value:void 0,enumerable:!1},Qp=new WeakSet([Object.prototype,Error.prototype]),em=(e,n)=>{e.name="$ZodError",Ha.value=n,Object.defineProperty(e,"issues",Ha),Ha.value=void 0,Object.defineProperty(e,"message",Db);let i=Object.getPrototypeOf(e);Qp.has(i)||(Qp.add(i),Object.defineProperty(i,"toString",{configurable:!0,enumerable:!1,get(){let o=()=>this.message;return Object.defineProperty(this,"toString",{value:o,configurable:!0,writable:!0}),o},set(o){Object.defineProperty(this,"toString",{value:o,configurable:!0,writable:!0})}}))},Ka=h("$ZodError",em),ke=h("$ZodError",em,void 0,{Parent:Error});function Cb(e,n,i){return Object.prototype.hasOwnProperty.call(e,n)||(n==="__proto__"?Object.defineProperty(e,n,{value:i(),writable:!0,enumerable:!0,configurable:!0}):e[n]=i()),e[n]}function Qn(e,n=i=>i.message){let i={},o=[];for(let t of e.issues)t.path.length>0?Cb(i,t.path[0],()=>[]).push(n(t)):o.push(n(t));return{formErrors:o,fieldErrors:i}}function er(e,n=i=>i.message){let i={_errors:[]},o=(t,r=[])=>{for(let a of t.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(s=>o({issues:s},[...r,...a.path]));else if(a.code==="invalid_key")o({issues:a.issues},[...r,...a.path]);else if(a.code==="invalid_element")o({issues:a.issues},[...r,...a.path]);else{let s=[...r,...a.path];if(s.length===0)i._errors.push(n(a));else{let c=i,u=0;for(;ui.message){let i={errors:[]},o=(t,r=[])=>{var a;for(let s of t.issues)if(s.code==="invalid_union"&&s.errors.length)s.errors.map(c=>o({issues:c},[...r,...s.path]));else if(s.code==="invalid_key")o({issues:s.issues},[...r,...s.path]);else if(s.code==="invalid_element")o({issues:s.issues},[...r,...s.path]);else{let c=[...r,...s.path];if(c.length===0){i.errors.push(n(s));continue}let u=i,d=0;for(;dtypeof o=="object"?o.key:o);for(let o of i)typeof o=="number"?n.push(`[${o}]`):typeof o=="symbol"?n.push(`[${JSON.stringify(String(o))}]`):/[^\w$]/.test(o)?n.push(`[${JSON.stringify(o)}]`):(n.length&&n.push("."),n.push(o));return n.join("")}function Ja(e){let n=[],i=[...e.issues].sort((o,t)=>(o.path??[]).length-(t.path??[]).length);for(let o of i)n.push(`✖ ${o.message}`),o.path?.length&&n.push(` → at ${tm(o.path)}`);return n.join(` -`)}function ai(e,n){return{callee:n?.callee??e,Err:n?.Err}}var Yt=e=>{let n=(i,o,t,r)=>{let a=t?{...t,async:!1}:{async:!1},s=i._zod.run({value:o,issues:[]},a);if(s instanceof Promise)throw new $e;if(s.issues.length){let c=new(r?.Err??e)(s.issues.map(u=>_e(u,a,ee())));throw ri(c,r?.callee??n),c}return s.value};return n},si=Yt(ke),Qt=e=>{let n=async(i,o,t,r)=>{let a=t?{...t,async:!0}:{async:!0},s=i._zod.run({value:o,issues:[]},a);if(s instanceof Promise&&(s=await s),s.issues.length){let c=new(r?.Err??e)(s.issues.map(u=>_e(u,a,ee())));throw ri(c,r?.callee??n),c}return s.value};return n},ci=Qt(ke),en=e=>(n,i,o)=>{let t=o?{...o,async:!1}:{async:!1},r=n._zod.run({value:i,issues:[]},t);if(r instanceof Promise)throw new $e;return r.issues.length?nm(e,r.issues,t):{success:!0,data:r.value}},Lb=en(ke);function nm(e,n,i){let o;return{success:!1,get error(){return o||(o=new e(n.map(t=>_e(t,i,ee()))),n=void 0,i=void 0),o},set error(t){o=t,n=void 0,i=void 0}}}var tn=e=>async(n,i,o)=>{let t=o?{...o,async:!0}:{async:!0},r=n._zod.run({value:i,issues:[]},t);return r instanceof Promise&&(r=await r),r.issues.length?nm(e,r.issues,t):{success:!0,data:r.value}},Ub=tn(ke),Rb=Symbol.for("zod.compile.invalid"),Mb=Symbol.for("zod.compile.fallback"),tr=((e,n,i)=>{let o=e._zod.bag.validator;if(o!==void 0){if(o(n)!==Rb)return!0;if(o.definite===!0&&i===void 0)return!1}return Zb(e,n,i)});function Zb(e,n,i){let o=i?{...i,async:!1,abortEarly:!0}:{async:!1,abortEarly:!0},t=e._zod.bag.fallbackRun,r;if(t?(o[Mb]=!0,r=t({value:n,issues:[]},o)):r=e._zod.run({value:n,issues:[]},o),r instanceof Promise)throw new $e;return r.issues.length===0}var nr=async(e,n,i)=>{let o=i?{...i,async:!0,abortEarly:!0}:{async:!0,abortEarly:!0},t=e._zod.run({value:n,issues:[]},o);return t instanceof Promise&&(t=await t),t.issues.length===0},ui=e=>{let n=Yt(e),i=(o,t,r,a)=>{let s=r?{...r,direction:"backward"}:{direction:"backward"};return n(o,t,s,ai(i,a))};return i},Vb=ui(ke),li=e=>{let n=Yt(e),i=(o,t,r,a)=>n(o,t,r,ai(i,a));return i},Gb=li(ke),di=e=>{let n=Qt(e),i=async(o,t,r,a)=>{let s=r?{...r,direction:"backward"}:{direction:"backward"};return await n(o,t,s,ai(i,a))};return i},Fb=di(ke),pi=e=>{let n=Qt(e),i=async(o,t,r,a)=>await n(o,t,r,ai(i,a));return i},Bb=pi(ke),mi=e=>(n,i,o)=>{let t=o?{...o,direction:"backward"}:{direction:"backward"};return en(e)(n,i,t)},Hb=mi(ke),fi=e=>(n,i,o)=>en(e)(n,i,o),Kb=fi(ke),gi=e=>async(n,i,o)=>{let t=o?{...o,direction:"backward"}:{direction:"backward"};return tn(e)(n,i,t)},Wb=gi(ke),hi=e=>async(n,i,o)=>tn(e)(n,i,o),Jb=hi(ke);var Te={};We(Te,{anyString:()=>ks,base64:()=>ms,base64url:()=>fs,bigint:()=>ws,boolean:()=>Is,browserEmail:()=>r_,cidrv4:()=>ds,cidrv6:()=>ps,creditCard:()=>vi,cuid:()=>qa,cuid2:()=>Ya,currencyCode:()=>ys,date:()=>bs,datetime:()=>$s,domain:()=>hs,duration:()=>is,e164:()=>vs,email:()=>as,emoji:()=>ss,extendedDuration:()=>Xb,guid:()=>os,hex:()=>As,hostname:()=>gs,html5Email:()=>e_,httpProtocol:()=>rr,iban:()=>yi,idnEmail:()=>n_,integer:()=>ir,ipv4:()=>cs,ipv6:()=>us,ksuid:()=>ts,lowercase:()=>Ts,mac:()=>ls,md5_base64:()=>s_,md5_base64url:()=>c_,md5_hex:()=>a_,nanoid:()=>ns,nanoidOfLength:()=>rs,null:()=>Ss,number:()=>qe,rfc5322Email:()=>t_,sha1_base64:()=>l_,sha1_base64url:()=>d_,sha1_hex:()=>u_,sha256_base64:()=>m_,sha256_base64url:()=>f_,sha256_hex:()=>p_,sha384_base64:()=>h_,sha384_base64url:()=>v_,sha384_hex:()=>g_,sha512_base64:()=>b_,sha512_base64url:()=>__,sha512_hex:()=>y_,string:()=>xs,time:()=>_s,ulid:()=>Qa,undefined:()=>Ps,unicodeEmail:()=>rm,uppercase:()=>Es,uuid:()=>wt,uuid4:()=>qb,uuid6:()=>Yb,uuid7:()=>Qb,xid:()=>es});var qa=/^[cC][0-9a-z]{6,}$/,Ya=/^[0-9a-z]+$/,Qa=/^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/,es=/^[0-9a-vA-V]{20}$/,ts=/^[A-Za-z0-9]{27}$/,ns=/^[a-zA-Z0-9_-]{21}$/;function rs(e){return new RegExp(`^[a-zA-Z0-9_-]{${e}}$`)}var is=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,Xb=/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/,os=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,wt=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,qb=wt(4),Yb=wt(6),Qb=wt(7),as=/^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,e_=/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,t_=/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,rm=/^[^\s@"]{1,64}@[^\s@]{1,255}$/u,n_=rm,r_=/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,i_="^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$";function ss(){return new RegExp(i_,"u")}var cs=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,us=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,ls=e=>{let n=Pe(e??":");return new RegExp(`^(?:[0-9A-F]{2}${n}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${n}){5}[0-9a-f]{2}$`)},ds=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,ps=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,ms=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,fs=/^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$/,gs=/^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/,hs=/^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/,rr=/^https?$/,vs=/^\+[1-9]\d{6,14}$/,vi=/^\d(?:[ -]?\d){11,18}$/,ys=/^(?:AED|AFN|ALL|AMD|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLE|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VED|VES|VND|VUV|WST|XAD|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XCG|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWG)$/,yi=/^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/,im="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))";function o_(e){return new RegExp(`^${e}$`)}var bs=o_(im);function Xa(e){let n="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${n}`:e.precision===0?`${n}:[0-5]\\d`:`${n}:[0-5]\\d\\.\\d{${e.precision}}`:e.seconds?`${n}:[0-5]\\d(?:\\.\\d+)?`:`${n}(?::[0-5]\\d(?:\\.\\d+)?)?`}function _s(e){return new RegExp(`^${Xa(e)}$`)}function $s(e){let n=["Z"];e.offset&&n.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");let i=`${Xa({precision:e.precision,seconds:!0})}(?:${n.join("|")})`,o=e.local?`${i}|${Xa({precision:e.precision})}`:i;return new RegExp(`^${im}T(?:${o})$`)}var ks=/^[\s\S]{0,}$/,xs=e=>{let n=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return new RegExp(`^${n}$`)},ws=/^-?\d+n?$/,ir=/^-?\d+$/,qe=/^-?\d+(?:\.\d+)?$/,Is=/^(?:true|false)$/i,Ss=/^null$/i;var Ps=/^undefined$/i;var Ts=/^[^A-Z]*$/,Es=/^[^a-z]*$/,As=/^[0-9a-fA-F]*$/;function or(e,n){return new RegExp(`^[A-Za-z0-9+/]{${e}}${n}$`)}function ar(e){return new RegExp(`^[A-Za-z0-9_-]{${e}}$`)}var a_=/^[0-9a-fA-F]{32}$/,s_=or(22,"=="),c_=ar(22),u_=/^[0-9a-fA-F]{40}$/,l_=or(27,"="),d_=ar(27),p_=/^[0-9a-fA-F]{64}$/,m_=or(43,"="),f_=ar(43),g_=/^[0-9a-fA-F]{96}$/,h_=or(64,""),v_=ar(64),y_=/^[0-9a-fA-F]{128}$/,b_=or(86,"=="),__=ar(86);var q=h("$ZodCheck",(e,n)=>{var i;e._zod??(e._zod={}),e._zod.def=n,(i=e._zod).onattach??(i.onattach=[])}),Os=e=>{let n=e.value;return!ni(n)&&n.size!==void 0},Ns=e=>{let n=e.value;return!ni(n)&&n.length!==void 0},bi={number:"number",bigint:"bigint",object:"date"},$i=h("$ZodCheckLessThan",(e,n)=>{q.init(e,n);let i=bi[typeof n.value];e._zod.check=o=>{(n.inclusive?o.value<=n.value:o.value{q.init(e,n);let i=bi[typeof n.value];e._zod.check=o=>{(n.inclusive?o.value>=n.value:o.value>n.value)||o.issues.push({origin:bi[typeof o.value]??i,code:"too_small",minimum:typeof n.value=="object"?n.value.getTime():n.value,input:o.value,inclusive:n.inclusive,inst:e,continue:!n.abort})}}),zs=h("$ZodCheckMultipleOf",(e,n)=>{q.init(e,n),e._zod.check=i=>{if(typeof i.value!=typeof n.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof i.value=="bigint"?n.value!==BigInt(0)&&i.value%n.value===BigInt(0):Fn(i.value,n.value)===0)||i.issues.push({origin:typeof i.value,code:"not_multiple_of",divisor:n.value,input:i.value,inst:e,continue:!n.abort})}}),Ds=h("$ZodCheckNumberFormat",(e,n)=>{q.init(e,n),n.format=n.format||"float64";let i=n.format?.includes("int"),o=i?"int":"number",[t,r]=Kn[n.format];e._zod.check=a=>{let s=a.value;if(i){if(!Number.isInteger(s)){a.issues.push({expected:o,format:n.format,code:"invalid_type",continue:!1,input:s,inst:e});return}if(!Number.isSafeInteger(s)){s>0?a.issues.push({input:s,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:o,inclusive:!0,continue:!n.abort}):a.issues.push({input:s,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:o,inclusive:!0,continue:!n.abort});return}}sr&&a.issues.push({origin:"number",input:s,code:"too_big",maximum:r,inclusive:!0,inst:e,continue:!n.abort})}}),Cs=h("$ZodCheckBigIntFormat",(e,n)=>{q.init(e,n);let[i,o]=Wn[n.format];e._zod.check=t=>{let r=t.value;ro&&t.issues.push({origin:"bigint",input:r,code:"too_big",maximum:o,inclusive:!0,inst:e,continue:!n.abort})}}),js=h("$ZodCheckMaxSize",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Os),e._zod.check=o=>{let t=o.value;t.size<=n.maximum||o.issues.push({origin:Xn(t),code:"too_big",maximum:n.maximum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Ls=h("$ZodCheckMinSize",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Os),e._zod.check=o=>{let t=o.value;t.size>=n.minimum||o.issues.push({origin:Xn(t),code:"too_small",minimum:n.minimum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Us=h("$ZodCheckSizeEquals",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Os),e._zod.check=o=>{let t=o.value,r=t.size;if(r===n.size)return;let a=r>n.size;o.issues.push({origin:Xn(t),...a?{code:"too_big",maximum:n.size}:{code:"too_small",minimum:n.size},inclusive:!0,exact:!0,input:o.value,inst:e,continue:!n.abort})}}),Rs=h("$ZodCheckMaxLength",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Ns),e._zod.check=o=>{let t=o.value,r=t.length;if((typeof t=="string"&&r>n.maximum?xt(t):r)<=n.maximum)return;let s=qn(t);o.issues.push({origin:s,code:"too_big",maximum:n.maximum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Ms=h("$ZodCheckMinLength",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Ns),e._zod.check=o=>{let t=o.value,r=t.length;if((typeof t=="string"&&r>=n.minimum&&r=n.minimum)return;let s=qn(t);o.issues.push({origin:s,code:"too_small",minimum:n.minimum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Zs=h("$ZodCheckLengthEquals",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Ns),e._zod.check=o=>{let t=o.value,r=t.length,a=typeof t=="string"&&r>=n.length&&r<=n.length*2?xt(t):r;if(a===n.length)return;let s=qn(t),c=a>n.length;o.issues.push({origin:s,...c?{code:"too_big",maximum:n.length}:{code:"too_small",minimum:n.length},inclusive:!0,exact:!0,input:o.value,inst:e,continue:!n.abort})}}),nn=h("$ZodCheckStringFormat",(e,n)=>{var i,o;q.init(e,n),n.pattern?(i=e._zod).check??(i.check=t=>{n.pattern.lastIndex=0,!n.pattern.test(t.value)&&t.issues.push({origin:"string",code:"invalid_format",format:n.format,input:t.value,...n.pattern?{pattern:n.pattern.toString()}:{},inst:e,continue:!n.abort})}):(o=e._zod).check??(o.check=()=>{})}),Vs=h("$ZodCheckRegex",(e,n)=>{nn.init(e,n),e._zod.check=i=>{n.pattern.lastIndex=0,!n.pattern.test(i.value)&&i.issues.push({origin:"string",code:"invalid_format",format:"regex",input:i.value,pattern:n.pattern.toString(),inst:e,continue:!n.abort})}}),Gs=h("$ZodCheckLowerCase",(e,n)=>{n.pattern??(n.pattern=Ts),nn.init(e,n)}),Fs=h("$ZodCheckUpperCase",(e,n)=>{n.pattern??(n.pattern=Es),nn.init(e,n)}),Bs=h("$ZodCheckIncludes",(e,n)=>{q.init(e,n);let i=Pe(n.includes),o=new RegExp(typeof n.position=="number"?`^.{${n.position},}${i}`:i);n.pattern=o,e._zod.check=t=>{t.value.includes(n.includes,n.position)||t.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:n.includes,input:t.value,inst:e,continue:!n.abort})}}),Hs=h("$ZodCheckStartsWith",(e,n)=>{q.init(e,n);let i=new RegExp(`^${Pe(n.prefix)}.*`);n.pattern??(n.pattern=i),e._zod.check=o=>{o.value.startsWith(n.prefix)||o.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:n.prefix,input:o.value,inst:e,continue:!n.abort})}}),Ks=h("$ZodCheckEndsWith",(e,n)=>{q.init(e,n);let i=new RegExp(`.*${Pe(n.suffix)}$`);n.pattern??(n.pattern=i),e._zod.check=o=>{o.value.endsWith(n.suffix)||o.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:n.suffix,input:o.value,inst:e,continue:!n.abort})}});function _i(e,n,i){e.issues.length&&n.issues.push(...be(i,e.issues))}var Ws=h("$ZodCheckProperty",(e,n)=>{q.init(e,n),e._zod.check=i=>{let o=n.schema._zod.run({value:i.value[n.property],issues:[]},{});if(o instanceof Promise)return o.then(t=>_i(t,i,n.property));_i(o,i,n.property)}}),Js=h("$ZodCheckProperties",(e,n)=>{q.init(e,n),Yn(e,Symbol.iterator,function*(){yield e});let i;e._zod.check=o=>{if(o.value==null){o.issues.push({expected:"object",code:"invalid_type",input:o.value,inst:e});return}i??(i=Reflect.ownKeys(n.shape).map(a=>[a,n.shape[a]]));let t=o.value,r;for(let[a,s]of i){let c=s._zod.run({value:t[a],issues:[]},{});c instanceof Promise?(r??(r=[]),r.push(c.then(u=>_i(u,o,a)))):_i(c,o,a)}if(r)return Promise.all(r).then(()=>{})}}),Xs=h("$ZodCheckMimeType",(e,n)=>{q.init(e,n);let i=new Set(n.mime);e._zod.check=o=>{i.has(o.value.type)||o.issues.push({code:"invalid_value",values:n.mime,input:o.value.type,inst:e,continue:!n.abort})}}),qs=h("$ZodCheckOverwrite",(e,n)=>{q.init(e,n),e._zod.check=i=>{i.value=n.tx(i.value)}});var It=class{constructor(n=[],i={}){this.content=[],this.indent=0,this.args=n,this.closed=i}indented(n){this.indent+=1;try{n(this)}finally{this.indent-=1}}write(n){if(typeof n=="function"){n(this,{execution:"sync"}),n(this,{execution:"async"});return}let o=n.split(` +var vb=Object.defineProperty;var Ke=(e,n)=>{for(var i in n)vb(e,i,{get:n[i],enumerable:!0})};import{readFileSync as aT}from"node:fs";import{dirname as fb,join as sT}from"node:path";var l={};Ke(l,{$brand:()=>Ja,$input:()=>$c,$output:()=>_c,INVALID:()=>fe,NEVER:()=>Ka,TimePrecision:()=>zc,ZodAny:()=>_d,ZodArray:()=>wd,ZodBase64:()=>Qo,ZodBase64URL:()=>ea,ZodBigInt:()=>En,ZodBigIntFormat:()=>ra,ZodBoolean:()=>Tn,ZodCIDRv4:()=>qo,ZodCIDRv6:()=>Yo,ZodCUID:()=>Go,ZodCUID2:()=>Bo,ZodCatch:()=>Bd,ZodCodec:()=>jr,ZodCompileAsyncError:()=>De,ZodCompileUnsupportedError:()=>F,ZodCreditCard:()=>pd,ZodCustom:()=>Ur,ZodCustomStringFormat:()=>jt,ZodDate:()=>Or,ZodDefault:()=>Rd,ZodDiscriminatedUnion:()=>Sd,ZodE164:()=>ta,ZodEmail:()=>Mo,ZodEmoji:()=>Fo,ZodEnum:()=>In,ZodError:()=>Lx,ZodExactOptional:()=>sa,ZodFile:()=>Cd,ZodFirstPartyTypeKind:()=>op,ZodFunction:()=>tp,ZodGUID:()=>Zo,ZodIBAN:()=>md,ZodIPv4:()=>Jo,ZodIPv6:()=>Xo,ZodISODate:()=>mt,ZodISODateTime:()=>pt,ZodISODuration:()=>gt,ZodISOTime:()=>ft,ZodInstanceOf:()=>ip,ZodIntersection:()=>Pd,ZodIssueCode:()=>Rx,ZodJWT:()=>na,ZodKSUID:()=>Ko,ZodLazy:()=>Yd,ZodLiteral:()=>Dd,ZodMAC:()=>dd,ZodMap:()=>Nd,ZodNaN:()=>Wd,ZodNanoID:()=>Vo,ZodNever:()=>kd,ZodNonOptional:()=>ua,ZodNull:()=>yd,ZodNullable:()=>Ud,ZodNumber:()=>Pn,ZodNumberFormat:()=>Ut,ZodObject:()=>zr,ZodOptional:()=>Cr,ZodPipe:()=>Lr,ZodPrefault:()=>Zd,ZodPreprocess:()=>Kd,ZodPromise:()=>ep,ZodReadonly:()=>Jd,ZodRealError:()=>xe,ZodRecord:()=>wn,ZodSet:()=>zd,ZodString:()=>Sn,ZodStringFormat:()=>X,ZodSuccess:()=>Gd,ZodSymbol:()=>hd,ZodTemplateLiteral:()=>qd,ZodTransform:()=>Ld,ZodTuple:()=>Ed,ZodType:()=>M,ZodULID:()=>Ho,ZodURL:()=>Ar,ZodUUID:()=>Ge,ZodUndefined:()=>vd,ZodUnion:()=>Dr,ZodUnknown:()=>$d,ZodVoid:()=>xd,ZodXID:()=>Wo,ZodXor:()=>Id,_ZodString:()=>Ro,_default:()=>Md,_function:()=>zh,any:()=>lh,array:()=>Nr,base64:()=>Gg,base64url:()=>Bg,bigint:()=>oh,boolean:()=>gd,catch:()=>Hd,check:()=>Dh,cidrv4:()=>Fg,cidrv6:()=>Vg,clone:()=>W,codec:()=>Eh,coerce:()=>sp,compile:()=>Ic,config:()=>te,core:()=>Qe,creditCard:()=>Wg,cuid:()=>Dg,cuid2:()=>Cg,currencyCode:()=>Qg,custom:()=>Ch,date:()=>ph,decode:()=>id,decodeAsync:()=>ad,deepPartial:()=>Kh,describe:()=>Lh,discriminatedUnion:()=>yh,e164:()=>Hg,email:()=>wg,emoji:()=>Ng,encode:()=>rd,encodeAsync:()=>od,endsWith:()=>mn,enum:()=>oa,exactOptional:()=>jd,file:()=>Ih,flattenError:()=>Qn,float32:()=>th,float64:()=>nh,formatError:()=>er,fromJSONSchema:()=>Hh,function:()=>zh,getDiscriminatedOption:()=>Ku,getErrorMap:()=>Zx,globalRegistry:()=>ue,gt:()=>Fe,gte:()=>Ie,guid:()=>Ig,hash:()=>eh,hex:()=>Yg,hostname:()=>qg,httpUrl:()=>Og,iban:()=>Kg,includes:()=>dn,input:()=>Xh,instanceof:()=>Uh,int:()=>jo,int32:()=>rh,int64:()=>ah,intersection:()=>Td,invertCodec:()=>Ah,ipv4:()=>Rg,ipv6:()=>Zg,iso:()=>Rr,json:()=>Mh,jwt:()=>Jg,keyof:()=>mh,ksuid:()=>Ug,lazy:()=>Qd,length:()=>Nt,literal:()=>wh,locales:()=>_r,looseObject:()=>hh,looseRecord:()=>_h,lowercase:()=>cn,lt:()=>Ze,lte:()=>we,mac:()=>Mg,map:()=>$h,maxLength:()=>Ot,maxSize:()=>ut,memoizer:()=>yr,meta:()=>jh,mime:()=>gn,minLength:()=>Ye,minSize:()=>Ve,multipleOf:()=>st,nan:()=>Th,nanoid:()=>zg,nativeEnum:()=>xh,negative:()=>So,never:()=>ia,nonnegative:()=>To,nonoptional:()=>Vd,nonpositive:()=>Po,normalize:()=>hn,null:()=>bd,nullable:()=>Er,nullish:()=>Sh,number:()=>fd,object:()=>fh,optional:()=>Ct,output:()=>qh,overwrite:()=>Ce,parse:()=>Ql,parseAsync:()=>ed,partialRecord:()=>bh,pipe:()=>Uo,positive:()=>Io,prefault:()=>Fd,preprocess:()=>Zh,prettifyError:()=>es,promise:()=>Nh,properties:()=>fn,property:()=>Eo,readonly:()=>Xd,record:()=>Od,refine:()=>np,regex:()=>un,regexes:()=>Te,registry:()=>Qi,safeDecode:()=>ud,safeDecodeAsync:()=>ld,safeEncode:()=>sd,safeEncodeAsync:()=>cd,safeParse:()=>td,safeParseAsync:()=>nd,set:()=>kh,setErrorMap:()=>Mx,size:()=>At,slugify:()=>_n,startsWith:()=>pn,strictObject:()=>gh,string:()=>Tr,stringFormat:()=>Xg,stringbool:()=>Rh,success:()=>Ph,superRefine:()=>rp,symbol:()=>uh,templateLiteral:()=>Oh,toJSONSchema:()=>zo,toLowerCase:()=>yn,toUpperCase:()=>bn,toZod:()=>Vn,transform:()=>aa,treeifyError:()=>Qa,trim:()=>vn,tuple:()=>Ad,uint32:()=>ih,uint64:()=>sh,ulid:()=>Lg,undefined:()=>ch,union:()=>An,unknown:()=>Dt,uppercase:()=>ln,url:()=>Ag,util:()=>_,uuid:()=>Sg,uuidv4:()=>Pg,uuidv6:()=>Tg,uuidv7:()=>Eg,validate:()=>tr,validateAsync:()=>nr,void:()=>dh,withParser:()=>eo,xid:()=>jg,xor:()=>vh});var Qe={};Ke(Qe,{$ZodAny:()=>Ru,$ZodArray:()=>Bu,$ZodAsyncError:()=>$e,$ZodBase64:()=>Pu,$ZodBase64URL:()=>Tu,$ZodBigInt:()=>Ri,$ZodBigIntFormat:()=>Cu,$ZodBoolean:()=>pr,$ZodCIDRv4:()=>Iu,$ZodCIDRv6:()=>Su,$ZodCUID:()=>mu,$ZodCUID2:()=>fu,$ZodCatch:()=>lc,$ZodCheck:()=>q,$ZodCheckBigIntFormat:()=>Ms,$ZodCheckEndsWith:()=>Ys,$ZodCheckGreaterThan:()=>xi,$ZodCheckIncludes:()=>Xs,$ZodCheckLengthEquals:()=>Hs,$ZodCheckLessThan:()=>ki,$ZodCheckLowerCase:()=>Ks,$ZodCheckMaxLength:()=>Gs,$ZodCheckMaxSize:()=>Zs,$ZodCheckMimeType:()=>tu,$ZodCheckMinLength:()=>Bs,$ZodCheckMinSize:()=>Fs,$ZodCheckMultipleOf:()=>Us,$ZodCheckNumberFormat:()=>Rs,$ZodCheckOverwrite:()=>nu,$ZodCheckProperties:()=>eu,$ZodCheckProperty:()=>Qs,$ZodCheckRegex:()=>Ws,$ZodCheckSizeEquals:()=>Vs,$ZodCheckStartsWith:()=>qs,$ZodCheckStringFormat:()=>nn,$ZodCheckUpperCase:()=>Js,$ZodCodec:()=>fr,$ZodCreditCard:()=>Au,$ZodCustom:()=>vc,$ZodCustomStringFormat:()=>zu,$ZodCyclicError:()=>Hi,$ZodDate:()=>Vu,$ZodDefault:()=>ac,$ZodDiscriminatedUnion:()=>Ju,$ZodE164:()=>Eu,$ZodEmail:()=>su,$ZodEmoji:()=>du,$ZodEncodeError:()=>ot,$ZodEnum:()=>ec,$ZodError:()=>Ya,$ZodExactOptional:()=>ic,$ZodFile:()=>nc,$ZodFunction:()=>gc,$ZodGUID:()=>ou,$ZodIBAN:()=>Ou,$ZodIPv4:()=>ku,$ZodIPv6:()=>xu,$ZodISODate:()=>bu,$ZodISODateTime:()=>yu,$ZodISODuration:()=>$u,$ZodISOTime:()=>_u,$ZodIntersection:()=>Xu,$ZodJWT:()=>Nu,$ZodKSUID:()=>vu,$ZodLazy:()=>gr,$ZodLiteral:()=>tc,$ZodMAC:()=>wu,$ZodMap:()=>Yu,$ZodNaN:()=>dc,$ZodNanoID:()=>pu,$ZodNever:()=>Zu,$ZodNonOptional:()=>uc,$ZodNull:()=>Uu,$ZodNullable:()=>oc,$ZodNumber:()=>Ui,$ZodNumberFormat:()=>Du,$ZodObject:()=>zm,$ZodObjectJIT:()=>Hu,$ZodOptional:()=>Zi,$ZodPipe:()=>Fi,$ZodPrefault:()=>sc,$ZodPreprocess:()=>pc,$ZodPromise:()=>hc,$ZodReadonly:()=>mc,$ZodRealError:()=>ke,$ZodRecord:()=>qu,$ZodRegistry:()=>Yi,$ZodSet:()=>Qu,$ZodString:()=>St,$ZodStringFormat:()=>K,$ZodSuccess:()=>cc,$ZodSymbol:()=>Lu,$ZodTemplateLiteral:()=>fc,$ZodTransform:()=>rc,$ZodTuple:()=>Mi,$ZodType:()=>U,$ZodULID:()=>gu,$ZodURL:()=>lu,$ZodUUID:()=>au,$ZodUndefined:()=>ju,$ZodUnion:()=>mr,$ZodUnknown:()=>Mu,$ZodVoid:()=>Fu,$ZodXID:()=>hu,$ZodXor:()=>Wu,$brand:()=>Ja,$constructor:()=>h,$input:()=>$c,$output:()=>_c,Doc:()=>It,INVALID:()=>fe,JSONSchema:()=>$g,JSONSchemaGenerator:()=>Do,NEVER:()=>Ka,TimePrecision:()=>zc,URL_BAD_FORMAT:()=>uu,URL_UNPARSEABLE:()=>sr,ZodCompileAsyncError:()=>De,ZodCompileUnsupportedError:()=>F,_any:()=>Xc,_array:()=>rl,_base64:()=>$o,_base64url:()=>ko,_bigint:()=>Vc,_boolean:()=>Zc,_catch:()=>wx,_check:()=>og,_cidrv4:()=>bo,_cidrv6:()=>_o,_coercedBigint:()=>Gc,_coercedBoolean:()=>Fc,_coercedDate:()=>tl,_coercedNumber:()=>Cc,_coercedString:()=>Ec,_creditCard:()=>Oc,_cuid:()=>po,_cuid2:()=>mo,_custom:()=>ol,_date:()=>el,_decode:()=>di,_decodeAsync:()=>mi,_default:()=>$x,_discriminatedUnion:()=>cx,_e164:()=>xo,_email:()=>ro,_emoji:()=>co,_encode:()=>li,_encodeAsync:()=>pi,_endsWith:()=>mn,_enum:()=>gx,_file:()=>il,_float32:()=>jc,_float64:()=>Uc,_gt:()=>Fe,_gte:()=>Ie,_guid:()=>io,_iban:()=>Nc,_includes:()=>dn,_int:()=>Lc,_int32:()=>Rc,_int64:()=>Bc,_intersection:()=>lx,_ipv4:()=>vo,_ipv6:()=>yo,_isoDate:()=>xr,_isoDateTime:()=>kr,_isoDuration:()=>Ir,_isoTime:()=>wr,_jwt:()=>wo,_ksuid:()=>ho,_lazy:()=>Tx,_length:()=>Nt,_literal:()=>vx,_lowercase:()=>cn,_lt:()=>Ze,_lte:()=>we,_mac:()=>Ac,_map:()=>mx,_max:()=>we,_maxLength:()=>Ot,_maxSize:()=>ut,_mime:()=>gn,_min:()=>Ie,_minLength:()=>Ye,_minSize:()=>Ve,_multipleOf:()=>st,_nan:()=>nl,_nanoid:()=>lo,_nativeEnum:()=>hx,_negative:()=>So,_never:()=>Yc,_nonnegative:()=>To,_nonoptional:()=>kx,_nonpositive:()=>Po,_normalize:()=>hn,_null:()=>Jc,_nullable:()=>_x,_number:()=>Dc,_optional:()=>bx,_overwrite:()=>Ce,_parse:()=>Yt,_parseAsync:()=>Qt,_pipe:()=>Ix,_positive:()=>Io,_promise:()=>Ex,_properties:()=>fn,_property:()=>Eo,_readonly:()=>Sx,_record:()=>px,_refine:()=>al,_regex:()=>un,_safeDecode:()=>gi,_safeDecodeAsync:()=>vi,_safeEncode:()=>fi,_safeEncodeAsync:()=>hi,_safeParse:()=>en,_safeParseAsync:()=>tn,_set:()=>fx,_size:()=>At,_slugify:()=>_n,_startsWith:()=>pn,_string:()=>Tc,_stringFormat:()=>zt,_stringbool:()=>ll,_success:()=>xx,_superRefine:()=>sl,_symbol:()=>Wc,_templateLiteral:()=>Px,_toLowerCase:()=>yn,_toUpperCase:()=>bn,_transform:()=>yx,_trim:()=>vn,_tuple:()=>dx,_uint32:()=>Mc,_uint64:()=>Hc,_ulid:()=>fo,_undefined:()=>Kc,_union:()=>sx,_unknown:()=>qc,_uppercase:()=>ln,_url:()=>$r,_uuid:()=>oo,_uuidv4:()=>ao,_uuidv6:()=>so,_uuidv7:()=>uo,_void:()=>Qc,_xid:()=>go,_xor:()=>ux,base64Charset:()=>zi,base64urlCharset:()=>dr,canParseURL:()=>cu,clone:()=>W,compile:()=>Ic,compileFn:()=>to,config:()=>te,createStandardJSONSchemaMethod:()=>$n,createToJSONSchemaMethod:()=>pl,decode:()=>u_,decodeAsync:()=>l_,describe:()=>ul,encode:()=>s_,encodeAsync:()=>c_,extractDefs:()=>lt,finalize:()=>dt,flattenError:()=>Qn,formatError:()=>er,getDiscriminatedOption:()=>Ku,globalConfig:()=>ae,globalRegistry:()=>ue,handleUnrepresentable:()=>ie,initializeContext:()=>ct,isBackEdge:()=>Ki,isRecursiveSchema:()=>Wi,isValidBase64:()=>lr,isValidBase64URL:()=>Di,isValidCIDRv6:()=>Ni,isValidCreditCard:()=>Ci,isValidIBAN:()=>Li,isValidIPv6:()=>cr,isValidJWT:()=>ji,locales:()=>_r,memoizer:()=>yr,mergeValues:()=>rn,meta:()=>cl,parse:()=>ui,parseAsync:()=>ci,parseURLObject:()=>Am,prettifyError:()=>es,process:()=>J,processSchema:()=>J,regexes:()=>Te,registry:()=>Qi,safeDecode:()=>p_,safeDecodeAsync:()=>f_,safeEncode:()=>d_,safeEncodeAsync:()=>m_,safeParse:()=>n_,safeParseAsync:()=>r_,standardProps:()=>Ti,stripTabAndNewline:()=>ur,toDotPath:()=>cm,toJSONSchema:()=>zo,toZod:()=>Vn,treeifyError:()=>Qa,urlHostnameOk:()=>Ai,urlProtocolOk:()=>Oi,util:()=>_,validate:()=>tr,validateAsync:()=>nr,validateURL:()=>Ei,version:()=>ru,withParser:()=>eo});var _={};Ke(_,{BIGINT_FORMAT_RANGES:()=>Kn,CONSTANT_CATCH:()=>ai,Class:()=>Ua,NUMBER_FORMAT_RANGES:()=>Wn,aborted:()=>me,allowsEval:()=>Za,assert:()=>kb,assertEqual:()=>yb,assertIs:()=>_b,assertNever:()=>$b,assertNotEqual:()=>bb,assignProp:()=>se,attachSchema:()=>Jn,base64ToUint8Array:()=>rm,base64urlToUint8Array:()=>Zb,cached:()=>Kt,captureStackTrace:()=>ii,cleanEnum:()=>Mb,cleanRegex:()=>Xe,clone:()=>W,cloneDef:()=>Ib,codePointLength:()=>xt,constantCatch:()=>Ha,createTransparentProxy:()=>Ob,defineLazy:()=>Ra,defineLazyInternal:()=>Z,derived:()=>Bb,esc:()=>pe,escapeRegex:()=>Pe,explicitlyAborted:()=>Ga,extend:()=>Db,finalizeIssue:()=>_e,floatSafeRemainder:()=>Gn,getElementAtPath:()=>Sb,getEnumValues:()=>Ht,getLengthableOrigin:()=>qn,getParsedType:()=>Ab,getSizableOrigin:()=>Xn,hexToUint8Array:()=>Vb,hide:()=>Yn,installLazyProp:()=>Jb,isObject:()=>kt,isPlainObject:()=>Se,issue:()=>qt,joinValues:()=>v,jsonStringifyReplacer:()=>Wt,members:()=>Ba,merge:()=>Lb,mergeDefs:()=>ze,normalizeParams:()=>S,nullish:()=>ri,numKeys:()=>Eb,objectClone:()=>xb,omit:()=>zb,optionalKeys:()=>Va,own:()=>it,parsedType:()=>k,partial:()=>jb,pick:()=>Nb,prefixIssues:()=>be,primitiveTypes:()=>Fa,promiseAllObject:()=>Pb,propertyKeyTypes:()=>Hn,randomString:()=>Tb,rawShape:()=>Jt,required:()=>Ub,safeExtend:()=>Cb,shallowClone:()=>Bn,sizeCaps:()=>Xt,slugify:()=>Ma,stringifyPrimitive:()=>$,toZod:()=>Vn,uint8ArrayToBase64:()=>im,uint8ArrayToBase64url:()=>Fb,uint8ArrayToHex:()=>Gb,unwrapMessage:()=>Bt});function yb(e){return e}function bb(e){return e}function Vn(){return e=>e}function _b(e){}function $b(e){throw new Error("Unexpected value in exhaustive check")}function kb(e){}function Ht(e){let n=Object.values(e).filter(o=>typeof o=="number");return Object.entries(e).filter(([o,t])=>n.indexOf(+o)===-1).map(([o,t])=>t)}function v(e,n="|"){return e.map(i=>$(i)).join(n)}function Wt(e,n){return typeof n=="bigint"?n.toString():n}var ja=class{constructor(n){this._getter=n,this._value=void 0}get value(){let n=this._getter;return n!==void 0&&(this._value=n(),this._getter=void 0),this._value}};function Kt(e){return new ja(e)}function ri(e){return e==null}function Xe(e){let n=e.startsWith("^")?1:0,i=e.endsWith("$")?e.length-1:e.length;return e.slice(n,i)}function Gn(e,n){let i=e/n,o=Math.round(i),t=4*Number.EPSILON*Math.max(Math.abs(i),1);return Math.abs(i-o){let s=n._zod.def.shape[r];return o?o(s,r):s}):tm(e,r,o?o(a.value,r):a.value))}}function wb(e,n){for(let i of Reflect.ownKeys(n)){let o=Object.getOwnPropertyDescriptor(n,i);o.enumerable&&(o.get?em(e,i,()=>n[i]):tm(e,i,o.value))}}function ze(...e){let n={};for(let i of e){let o=Object.getOwnPropertyDescriptors(i);Object.assign(n,o)}return Object.defineProperties({},n)}function Ib(e){return ze(e._zod.def)}function Sb(e,n){return n?n.reduce((i,o)=>i?.[o],e):e}function Pb(e){let n=Object.keys(e),i=n.map(o=>e[o]);return Promise.all(i).then(o=>{let t={};for(let r=0;r{};function kt(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}var Za=Kt(()=>{if(ae.jitless||typeof navigator<"u"&&navigator?.userAgent?.includes("Cloudflare"))return!1;try{let e=Function;return new e(""),!0}catch{return!1}});function Se(e){if(kt(e)===!1)return!1;let n=e.constructor;if(n===void 0||typeof n!="function")return!0;let i=n.prototype;return!(kt(i)===!1||Object.prototype.hasOwnProperty.call(i,"isPrototypeOf")===!1)}function Bn(e){return Se(e)?{...e}:Array.isArray(e)?[...e]:e instanceof Map?new Map(e):e instanceof Set?new Set(e):e}function Eb(e){let n=0;for(let i in e)Object.prototype.hasOwnProperty.call(e,i)&&n++;return n}var Ab=e=>{let n=typeof e;switch(n){case"undefined":return"undefined";case"string":return"string";case"number":return Number.isNaN(e)?"nan":"number";case"boolean":return"boolean";case"function":return"function";case"bigint":return"bigint";case"symbol":return"symbol";case"object":return Array.isArray(e)?"array":e===null?"null":e.then&&typeof e.then=="function"&&e.catch&&typeof e.catch=="function"?"promise":typeof Map<"u"&&e instanceof Map?"map":typeof Set<"u"&&e instanceof Set?"set":typeof Date<"u"&&e instanceof Date?"date":typeof File<"u"&&e instanceof File?"file":"object";default:throw new Error(`Unknown data type: ${n}`)}},Hn=new Set(["string","number","symbol"]),Fa=new Set(["string","number","bigint","boolean","symbol","undefined"]);function Pe(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function W(e,n,i){let o=new e._zod.constr(n??e._zod.def);return(!n||i?.parent)&&(o._zod.parent=e),o}function S(e){let n=e;if(!n)return{};if(typeof n=="string")return{error:()=>n};if(n?.message!==void 0){if(n?.error!==void 0)throw new Error("Cannot specify both `message` and `error` params");n.error=n.message}return delete n.message,typeof n.error=="string"?{...n,error:()=>n.error}:n}function Ob(e){let n;return new Proxy({},{get(i,o,t){return n??(n=e()),Reflect.get(n,o,t)},set(i,o,t,r){return n??(n=e()),Reflect.set(n,o,t,r)},has(i,o){return n??(n=e()),Reflect.has(n,o)},deleteProperty(i,o){return n??(n=e()),Reflect.deleteProperty(n,o)},ownKeys(i){return n??(n=e()),Reflect.ownKeys(n)},getOwnPropertyDescriptor(i,o){return n??(n=e()),Reflect.getOwnPropertyDescriptor(n,o)},defineProperty(i,o,t){return n??(n=e()),Reflect.defineProperty(n,o,t)}})}function $(e){return typeof e=="bigint"?e.toString()+"n":typeof e=="string"?`"${e}"`:`${e}`}function Va(e){return Object.keys(e).filter(n=>e[n]._zod.optin!==void 0&&e[n]._zod.optout==="optional")}var Wn={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]},Kn={int64:[BigInt("-9223372036854775808"),BigInt("9223372036854775807")],uint64:[BigInt(0),BigInt("18446744073709551615")]};function Nb(e,n){let i=e._zod.def,o=i.checks;if(o&&o.length>0)throw new Error(".pick() cannot be used on object schemas containing refinements");let r={};return $t(r,e,oi(e,n)),W(e,ze(i,{shape:r,checks:[]}))}function oi(e,n){let i=Je(e),o=[];for(let t of Reflect.ownKeys(n)){if(!Object.getOwnPropertyDescriptor(i,t)?.enumerable)throw new Error(`Unrecognized key: "${String(t)}"`);n[t]&&o.push(t)}return o}function zb(e,n){let i=e._zod.def,o=i.checks;if(o&&o.length>0)throw new Error(".omit() cannot be used on object schemas containing refinements");let r=new Set(oi(e,n)),a={};return $t(a,e,Reflect.ownKeys(Je(e)).filter(s=>!r.has(s))),W(e,ze(i,{shape:a,checks:[]}))}function Db(e,n){if(!Se(n))throw new Error("Invalid input to extend: expected a plain object");let i=e._zod.def.checks;if(i&&i.length>0){let t=Je(e);for(let r of Reflect.ownKeys(n))if(Object.getOwnPropertyDescriptor(t,r)!==void 0)throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.")}return W(e,ze(e._zod.def,{shape:nm(e,n)}))}function nm(e,n){let i={};return $t(i,e,Reflect.ownKeys(Je(e))),wb(i,n),i}function Cb(e,n){if(!Se(n))throw new Error("Invalid input to safeExtend: expected a plain object");return W(e,ze(e._zod.def,{shape:nm(e,n)}))}function Lb(e,n){if(!n?._zod?.def)throw new Error("Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`.");if(e._zod.def.checks?.length)throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");let i={};$t(i,e,Reflect.ownKeys(Je(e))),$t(i,n,Reflect.ownKeys(Je(n)));let o=ze(e._zod.def,{shape:i,get catchall(){return n._zod.def.catchall},checks:n._zod.def.checks??[]});return W(e,o)}function jb(e,n,i,o="partial"){let r=n._zod.def.checks;if(r&&r.length>0)throw new Error(`.${o}() cannot be used on object schemas containing refinements`);let s=i?new Set(oi(n,i)):void 0,u={};return $t(u,n,Reflect.ownKeys(Je(n)),e&&((c,d)=>s&&!s.has(d)?c:new e({type:"optional",innerType:c}))),W(n,ze(n._zod.def,{shape:u,checks:[]}))}function Ub(e,n,i){let o=i?new Set(oi(n,i)):void 0,t={};return $t(t,n,Reflect.ownKeys(Je(n)),(r,a)=>o&&!o.has(a)?r:new e({type:"nonoptional",innerType:r})),W(n,ze(n._zod.def,{shape:t}))}function me(e,n=0){if(e.aborted===!0)return!0;for(let i=n;i{var o;return(o=i).path??(o.path=[]),i.path.unshift(e),i})}function Bt(e){return typeof e=="string"?e:e?.message}function Xt(e){let n=[];for(let i of e??[]){let o=i._zod.def;if(!/_(length|size)$|_equals$/.test(o.check))break;let t=o.maximum??o.length??o.size;t>=0&&n.push([t,i])}return n}function Jn(e,n,i){var o;for(let t=n;tNumber.isNaN(Number.parseInt(n,10))).map(n=>n[1])}function rm(e){let n=atob(e),i=new Uint8Array(n.length);for(let o=0;on.toString(16).padStart(2,"0")).join("")}var Ua=class{constructor(...n){}};function Ba(e,n){for(let i in n){let o=Object.getOwnPropertyDescriptor(n,i);o.get?Object.defineProperty(e,i,{...o,enumerable:!1}):Hb(e,i,o.value)}}function it(e,n,i,o=!0){return Object.defineProperty(e,n,{configurable:!0,writable:!0,enumerable:o,value:i}),i}function Yn(e,n,i){return it(e,n,i,!1)}function Bb(e,n){for(let i in e){let o=e[i];Object.defineProperty(n,i,{configurable:!0,enumerable:!0,get(){return it(this,i,o(this))},set(t){it(this,i,t)}})}return n}function Hb(e,n,i){Object.defineProperty(e,n,{configurable:!0,get(){return this==null?i:it(this,n,i.bind(this))},set(o){it(this,n,o)}})}function Wb(e,n){let i=Object.getPrototypeOf(e);return n in i?void 0:i}var La,rt=!1,Kb={configurable:!0,get(){rt=!0}};function Z(e,n,i){let o=Object.getPrototypeOf(e._zod);if(n in o&&La!==e._zod){La=void 0;return}La=e._zod,Object.defineProperty(o,n,{configurable:!0,get(){Object.defineProperty(this,n,Kb);let t=rt;rt=!1;try{let r=i(this);return rt?delete this[n]:Object.defineProperty(this,n,{configurable:!0,writable:!0,value:r}),rt=rt||t,r}catch(r){throw delete this[n],rt=rt||t,r}},set(t){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:t})}})}function Jb(e,n,i,o){let t=Wb(e,n);t&&Object.defineProperty(t,n,{configurable:!0,get(){let r={configurable:!0,writable:!0,enumerable:o,value:void 0};return Object.defineProperty(this,n,r),r.value=i(this),Object.defineProperty(this,n,r),r.value},set(r){Object.defineProperty(this,n,{configurable:!0,writable:!0,enumerable:o,value:r})}})}var ai="~constantCatch";function Ha(e){let n=()=>e;return n[ai]=!0,n}var om,Ka=Object.freeze({status:"aborted"}),Wa={value:void 0,enumerable:!1},am="captureStackTrace"in Error?Error:null;function Xb(e){let n=am;if(n){let i=n.stackTraceLimit;if(typeof i=="number"){try{n.stackTraceLimit=0}catch{return am=null,new e}try{return new e}finally{n.stackTraceLimit=i}}}return new e}function h(e,n,i,o){let t={};function r(m){this.def=m,this.constr=p,this.traits=new Set}r.prototype=t;let a=i,s=a&&new WeakSet;function u(m,f){if(m._zod){if(m._zod.traits.has(e))return}else{Wa.value=new r(f);try{Object.defineProperty(m,"_zod",Wa)}finally{Wa.value=void 0}}if(m._zod.traits.add(e),n(m,f),s){let y=Object.getPrototypeOf(m),x=m._zod.constr.prototype,I=y;for(;I&&I!==x;)I=Object.getPrototypeOf(I);let b=I??y;s.has(b)||(s.add(b),Ba(b,a))}let g=p.prototype;for(let y in g)Object.prototype.hasOwnProperty.call(g,y)&&(y in m||(m[y]=g[y].bind(m)))}let c=o?.Parent??Object;class d extends c{}Object.defineProperty(d,"name",{value:e});function p(m){let f=o?.Parent?Xb(d):this;u(f,m);let g=f._zod.deferred;if(g){for(let x of g)x();f._zod.deferred=void 0}let y=globalThis.__zod_globalConfig?.postProcessor;return y&&y(f),f}return Object.defineProperty(p,"init",{value:u}),Object.defineProperty(p,Symbol.hasInstance,{value:m=>o?.Parent&&m instanceof o.Parent?!0:m?._zod?.traits?.has(e)}),Object.defineProperty(p,"name",{value:e}),p}var Ja=Symbol("zod_brand"),$e=class extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}},ot=class extends Error{constructor(n){super(`Encountered unidirectional transform during encode: ${n}`),this.name="ZodEncodeError"}};(om=globalThis).__zod_globalConfig??(om.__zod_globalConfig={});var ae=globalThis.__zod_globalConfig;function te(e){return e&&Object.assign(ae,e),ae}function qb(){let e=this._zod;return e.message??(e.message=JSON.stringify(e.def,Wt,2)),e.message}function Yb(e){this._zod.message=e}var Qb={get:qb,set:Yb,enumerable:!0,configurable:!0},qa={value:void 0,enumerable:!1},sm=new WeakSet([Object.prototype,Error.prototype]),um=(e,n)=>{e.name="$ZodError",qa.value=n,Object.defineProperty(e,"issues",qa),qa.value=void 0,Object.defineProperty(e,"message",Qb);let i=Object.getPrototypeOf(e);sm.has(i)||(sm.add(i),Object.defineProperty(i,"toString",{configurable:!0,enumerable:!1,get(){let o=()=>this.message;return Object.defineProperty(this,"toString",{value:o,configurable:!0,writable:!0}),o},set(o){Object.defineProperty(this,"toString",{value:o,configurable:!0,writable:!0})}}))},Ya=h("$ZodError",um),ke=h("$ZodError",um,void 0,{Parent:Error});function e_(e,n,i){return Object.prototype.hasOwnProperty.call(e,n)||(n==="__proto__"?Object.defineProperty(e,n,{value:i(),writable:!0,enumerable:!0,configurable:!0}):e[n]=i()),e[n]}function Qn(e,n=i=>i.message){let i={},o=[];for(let t of e.issues)t.path.length>0?e_(i,t.path[0],()=>[]).push(n(t)):o.push(n(t));return{formErrors:o,fieldErrors:i}}function er(e,n=i=>i.message){let i={_errors:[]},o=(t,r=[])=>{for(let a of t.issues)if(a.code==="invalid_union"&&a.errors.length)a.errors.map(s=>o({issues:s},[...r,...a.path]));else if(a.code==="invalid_key")o({issues:a.issues},[...r,...a.path]);else if(a.code==="invalid_element")o({issues:a.issues},[...r,...a.path]);else{let s=[...r,...a.path];if(s.length===0)i._errors.push(n(a));else{let u=i,c=0;for(;ci.message){let i={errors:[]},o=(t,r=[])=>{var a;for(let s of t.issues)if(s.code==="invalid_union"&&s.errors.length)s.errors.map(u=>o({issues:u},[...r,...s.path]));else if(s.code==="invalid_key")o({issues:s.issues},[...r,...s.path]);else if(s.code==="invalid_element")o({issues:s.issues},[...r,...s.path]);else{let u=[...r,...s.path];if(u.length===0){i.errors.push(n(s));continue}let c=i,d=0;for(;dtypeof o=="object"?o.key:o);for(let o of i)typeof o=="number"?n.push(`[${o}]`):typeof o=="symbol"?n.push(`[${JSON.stringify(String(o))}]`):/[^\w$]/.test(o)?n.push(`[${JSON.stringify(o)}]`):(n.length&&n.push("."),n.push(o));return n.join("")}function es(e){let n=[],i=[...e.issues].sort((o,t)=>(o.path??[]).length-(t.path??[]).length);for(let o of i)n.push(`✖ ${o.message}`),o.path?.length&&n.push(` → at ${cm(o.path)}`);return n.join(` +`)}function si(e,n){return{callee:n?.callee??e,Err:n?.Err}}var Yt=e=>{let n=(i,o,t,r)=>{let a=t?{...t,async:!1}:{async:!1},s=i._zod.run({value:o,issues:[]},a);if(s instanceof Promise)throw new $e;if(s.issues.length){let u=new(r?.Err??e)(s.issues.map(c=>_e(c,a,te())));throw ii(u,r?.callee??n),u}return s.value};return n},ui=Yt(ke),Qt=e=>{let n=async(i,o,t,r)=>{let a=t?{...t,async:!0}:{async:!0},s=i._zod.run({value:o,issues:[]},a);if(s instanceof Promise&&(s=await s),s.issues.length){let u=new(r?.Err??e)(s.issues.map(c=>_e(c,a,te())));throw ii(u,r?.callee??n),u}return s.value};return n},ci=Qt(ke),en=e=>(n,i,o)=>{let t=o?{...o,async:!1}:{async:!1},r=n._zod.run({value:i,issues:[]},t);if(r instanceof Promise)throw new $e;return r.issues.length?lm(e,r.issues,t):{success:!0,data:r.value}},n_=en(ke);function lm(e,n,i){let o;return{success:!1,get error(){return o||(o=new e(n.map(t=>_e(t,i,te()))),n=void 0,i=void 0),o},set error(t){o=t,n=void 0,i=void 0}}}var tn=e=>async(n,i,o)=>{let t=o?{...o,async:!0}:{async:!0},r=n._zod.run({value:i,issues:[]},t);return r instanceof Promise&&(r=await r),r.issues.length?lm(e,r.issues,t):{success:!0,data:r.value}},r_=tn(ke),i_=Symbol.for("zod.compile.invalid"),o_=Symbol.for("zod.compile.fallback"),tr=((e,n,i)=>{let o=e._zod.bag.validator;if(o!==void 0){if(o(n)!==i_)return!0;if(o.definite===!0&&i===void 0)return!1}return a_(e,n,i)});function a_(e,n,i){let o=i?{...i,async:!1,abortEarly:!0}:{async:!1,abortEarly:!0},t=e._zod.bag.fallbackRun,r;if(t?(o[o_]=!0,r=t({value:n,issues:[]},o)):r=e._zod.run({value:n,issues:[]},o),r instanceof Promise)throw new $e;return r.issues.length===0}var nr=async(e,n,i)=>{let o=i?{...i,async:!0,abortEarly:!0}:{async:!0,abortEarly:!0},t=e._zod.run({value:n,issues:[]},o);return t instanceof Promise&&(t=await t),t.issues.length===0},li=e=>{let n=Yt(e),i=(o,t,r,a)=>{let s=r?{...r,direction:"backward"}:{direction:"backward"};return n(o,t,s,si(i,a))};return i},s_=li(ke),di=e=>{let n=Yt(e),i=(o,t,r,a)=>n(o,t,r,si(i,a));return i},u_=di(ke),pi=e=>{let n=Qt(e),i=async(o,t,r,a)=>{let s=r?{...r,direction:"backward"}:{direction:"backward"};return await n(o,t,s,si(i,a))};return i},c_=pi(ke),mi=e=>{let n=Qt(e),i=async(o,t,r,a)=>await n(o,t,r,si(i,a));return i},l_=mi(ke),fi=e=>(n,i,o)=>{let t=o?{...o,direction:"backward"}:{direction:"backward"};return en(e)(n,i,t)},d_=fi(ke),gi=e=>(n,i,o)=>en(e)(n,i,o),p_=gi(ke),hi=e=>async(n,i,o)=>{let t=o?{...o,direction:"backward"}:{direction:"backward"};return tn(e)(n,i,t)},m_=hi(ke),vi=e=>async(n,i,o)=>tn(e)(n,i,o),f_=vi(ke);var Te={};Ke(Te,{anyString:()=>Ps,base64:()=>ys,base64url:()=>bs,bigint:()=>Es,boolean:()=>As,browserEmail:()=>k_,cidrv4:()=>hs,cidrv6:()=>vs,creditCard:()=>yi,cuid:()=>ns,cuid2:()=>rs,currencyCode:()=>xs,date:()=>ws,datetime:()=>Ss,domain:()=>$s,duration:()=>cs,e164:()=>ks,email:()=>ds,emoji:()=>ps,extendedDuration:()=>g_,guid:()=>ls,hex:()=>Cs,hostname:()=>_s,html5Email:()=>b_,httpProtocol:()=>rr,iban:()=>bi,idnEmail:()=>$_,integer:()=>ir,ipv4:()=>ms,ipv6:()=>fs,ksuid:()=>as,lowercase:()=>zs,mac:()=>gs,md5_base64:()=>S_,md5_base64url:()=>P_,md5_hex:()=>I_,nanoid:()=>ss,nanoidOfLength:()=>us,null:()=>Os,number:()=>qe,rfc5322Email:()=>__,sha1_base64:()=>E_,sha1_base64url:()=>A_,sha1_hex:()=>T_,sha256_base64:()=>N_,sha256_base64url:()=>z_,sha256_hex:()=>O_,sha384_base64:()=>C_,sha384_base64url:()=>L_,sha384_hex:()=>D_,sha512_base64:()=>U_,sha512_base64url:()=>R_,sha512_hex:()=>j_,string:()=>Ts,time:()=>Is,ulid:()=>is,undefined:()=>Ns,unicodeEmail:()=>dm,uppercase:()=>Ds,uuid:()=>wt,uuid4:()=>h_,uuid6:()=>v_,uuid7:()=>y_,xid:()=>os});var ns=/^[cC][0-9a-z]{6,}$/,rs=/^[0-9a-z]+$/,is=/^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/,os=/^[0-9a-vA-V]{20}$/,as=/^[A-Za-z0-9]{27}$/,ss=/^[a-zA-Z0-9_-]{21}$/;function us(e){return new RegExp(`^[a-zA-Z0-9_-]{${e}}$`)}var cs=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,g_=/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/,ls=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,wt=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,h_=wt(4),v_=wt(6),y_=wt(7),ds=/^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,b_=/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,__=/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,dm=/^[^\s@"]{1,64}@[^\s@]{1,255}$/u,$_=dm,k_=/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,x_="^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$";function ps(){return new RegExp(x_,"u")}var ms=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,fs=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,gs=e=>{let n=Pe(e??":");return new RegExp(`^(?:[0-9A-F]{2}${n}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${n}){5}[0-9a-f]{2}$`)},hs=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,vs=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,ys=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,bs=/^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$/,_s=/^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/,$s=/^(?=.{1,253}$)([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/,rr=/^https?$/,ks=/^\+[1-9]\d{6,14}$/,yi=/^\d(?:[ -]?\d){11,18}$/,xs=/^(?:AED|AFN|ALL|AMD|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLE|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VED|VES|VND|VUV|WST|XAD|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XCG|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWG)$/,bi=/^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/,pm="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))";function w_(e){return new RegExp(`^${e}$`)}var ws=w_(pm);function ts(e){let n="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${n}`:e.precision===0?`${n}:[0-5]\\d`:`${n}:[0-5]\\d\\.\\d{${e.precision}}`:e.seconds?`${n}:[0-5]\\d(?:\\.\\d+)?`:`${n}(?::[0-5]\\d(?:\\.\\d+)?)?`}function Is(e){return new RegExp(`^${ts(e)}$`)}function Ss(e){let n=["Z"];e.offset&&n.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");let i=`${ts({precision:e.precision,seconds:!0})}(?:${n.join("|")})`,o=e.local?`${i}|${ts({precision:e.precision})}`:i;return new RegExp(`^${pm}T(?:${o})$`)}var Ps=/^[\s\S]{0,}$/,Ts=e=>{let n=e?`[\\s\\S]{${e?.minimum??0},${e?.maximum??""}}`:"[\\s\\S]*";return new RegExp(`^${n}$`)},Es=/^-?\d+n?$/,ir=/^-?\d+$/,qe=/^-?\d+(?:\.\d+)?$/,As=/^(?:true|false)$/i,Os=/^null$/i;var Ns=/^undefined$/i;var zs=/^[^A-Z]*$/,Ds=/^[^a-z]*$/,Cs=/^[0-9a-fA-F]*$/;function or(e,n){return new RegExp(`^[A-Za-z0-9+/]{${e}}${n}$`)}function ar(e){return new RegExp(`^[A-Za-z0-9_-]{${e}}$`)}var I_=/^[0-9a-fA-F]{32}$/,S_=or(22,"=="),P_=ar(22),T_=/^[0-9a-fA-F]{40}$/,E_=or(27,"="),A_=ar(27),O_=/^[0-9a-fA-F]{64}$/,N_=or(43,"="),z_=ar(43),D_=/^[0-9a-fA-F]{96}$/,C_=or(64,""),L_=ar(64),j_=/^[0-9a-fA-F]{128}$/,U_=or(86,"=="),R_=ar(86);var q=h("$ZodCheck",(e,n)=>{var i;e._zod??(e._zod={}),e._zod.def=n,(i=e._zod).onattach??(i.onattach=[])}),Ls=e=>{let n=e.value;return!ri(n)&&n.size!==void 0},js=e=>{let n=e.value;return!ri(n)&&n.length!==void 0},_i={number:"number",bigint:"bigint",object:"date"},ki=h("$ZodCheckLessThan",(e,n)=>{q.init(e,n);let i=_i[typeof n.value];e._zod.check=o=>{(n.inclusive?o.value<=n.value:o.value{q.init(e,n);let i=_i[typeof n.value];e._zod.check=o=>{(n.inclusive?o.value>=n.value:o.value>n.value)||o.issues.push({origin:_i[typeof o.value]??i,code:"too_small",minimum:typeof n.value=="object"?n.value.getTime():n.value,input:o.value,inclusive:n.inclusive,inst:e,continue:!n.abort})}}),Us=h("$ZodCheckMultipleOf",(e,n)=>{q.init(e,n),e._zod.check=i=>{if(typeof i.value!=typeof n.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof i.value=="bigint"?n.value!==BigInt(0)&&i.value%n.value===BigInt(0):Gn(i.value,n.value)===0)||i.issues.push({origin:typeof i.value,code:"not_multiple_of",divisor:n.value,input:i.value,inst:e,continue:!n.abort})}}),Rs=h("$ZodCheckNumberFormat",(e,n)=>{q.init(e,n),n.format=n.format||"float64";let i=n.format?.includes("int"),o=i?"int":"number",[t,r]=Wn[n.format];e._zod.check=a=>{let s=a.value;if(i){if(!Number.isInteger(s)){a.issues.push({expected:o,format:n.format,code:"invalid_type",continue:!1,input:s,inst:e});return}if(!Number.isSafeInteger(s)){s>0?a.issues.push({input:s,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:o,inclusive:!0,continue:!n.abort}):a.issues.push({input:s,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:o,inclusive:!0,continue:!n.abort});return}}sr&&a.issues.push({origin:"number",input:s,code:"too_big",maximum:r,inclusive:!0,inst:e,continue:!n.abort})}}),Ms=h("$ZodCheckBigIntFormat",(e,n)=>{q.init(e,n);let[i,o]=Kn[n.format];e._zod.check=t=>{let r=t.value;ro&&t.issues.push({origin:"bigint",input:r,code:"too_big",maximum:o,inclusive:!0,inst:e,continue:!n.abort})}}),Zs=h("$ZodCheckMaxSize",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Ls),e._zod.check=o=>{let t=o.value;t.size<=n.maximum||o.issues.push({origin:Xn(t),code:"too_big",maximum:n.maximum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Fs=h("$ZodCheckMinSize",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Ls),e._zod.check=o=>{let t=o.value;t.size>=n.minimum||o.issues.push({origin:Xn(t),code:"too_small",minimum:n.minimum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Vs=h("$ZodCheckSizeEquals",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=Ls),e._zod.check=o=>{let t=o.value,r=t.size;if(r===n.size)return;let a=r>n.size;o.issues.push({origin:Xn(t),...a?{code:"too_big",maximum:n.size}:{code:"too_small",minimum:n.size},inclusive:!0,exact:!0,input:o.value,inst:e,continue:!n.abort})}}),Gs=h("$ZodCheckMaxLength",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=js),e._zod.check=o=>{let t=o.value,r=t.length;if((typeof t=="string"&&r>n.maximum?xt(t):r)<=n.maximum)return;let s=qn(t);o.issues.push({origin:s,code:"too_big",maximum:n.maximum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Bs=h("$ZodCheckMinLength",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=js),e._zod.check=o=>{let t=o.value,r=t.length;if((typeof t=="string"&&r>=n.minimum&&r=n.minimum)return;let s=qn(t);o.issues.push({origin:s,code:"too_small",minimum:n.minimum,inclusive:!0,input:t,inst:e,continue:!n.abort})}}),Hs=h("$ZodCheckLengthEquals",(e,n)=>{var i;q.init(e,n),(i=e._zod.def).when??(i.when=js),e._zod.check=o=>{let t=o.value,r=t.length,a=typeof t=="string"&&r>=n.length&&r<=n.length*2?xt(t):r;if(a===n.length)return;let s=qn(t),u=a>n.length;o.issues.push({origin:s,...u?{code:"too_big",maximum:n.length}:{code:"too_small",minimum:n.length},inclusive:!0,exact:!0,input:o.value,inst:e,continue:!n.abort})}}),nn=h("$ZodCheckStringFormat",(e,n)=>{var i,o;q.init(e,n),n.pattern?(i=e._zod).check??(i.check=t=>{n.pattern.lastIndex=0,!n.pattern.test(t.value)&&t.issues.push({origin:"string",code:"invalid_format",format:n.format,input:t.value,...n.pattern?{pattern:n.pattern.toString()}:{},inst:e,continue:!n.abort})}):(o=e._zod).check??(o.check=()=>{})}),Ws=h("$ZodCheckRegex",(e,n)=>{nn.init(e,n),e._zod.check=i=>{n.pattern.lastIndex=0,!n.pattern.test(i.value)&&i.issues.push({origin:"string",code:"invalid_format",format:"regex",input:i.value,pattern:n.pattern.toString(),inst:e,continue:!n.abort})}}),Ks=h("$ZodCheckLowerCase",(e,n)=>{n.pattern??(n.pattern=zs),nn.init(e,n)}),Js=h("$ZodCheckUpperCase",(e,n)=>{n.pattern??(n.pattern=Ds),nn.init(e,n)}),Xs=h("$ZodCheckIncludes",(e,n)=>{q.init(e,n);let i=Pe(n.includes),o=new RegExp(typeof n.position=="number"?`^.{${n.position},}${i}`:i);n.pattern=o,e._zod.check=t=>{t.value.includes(n.includes,n.position)||t.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:n.includes,input:t.value,inst:e,continue:!n.abort})}}),qs=h("$ZodCheckStartsWith",(e,n)=>{q.init(e,n);let i=new RegExp(`^${Pe(n.prefix)}.*`);n.pattern??(n.pattern=i),e._zod.check=o=>{o.value.startsWith(n.prefix)||o.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:n.prefix,input:o.value,inst:e,continue:!n.abort})}}),Ys=h("$ZodCheckEndsWith",(e,n)=>{q.init(e,n);let i=new RegExp(`.*${Pe(n.suffix)}$`);n.pattern??(n.pattern=i),e._zod.check=o=>{o.value.endsWith(n.suffix)||o.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:n.suffix,input:o.value,inst:e,continue:!n.abort})}});function $i(e,n,i){e.issues.length&&n.issues.push(...be(i,e.issues))}var Qs=h("$ZodCheckProperty",(e,n)=>{q.init(e,n),e._zod.check=i=>{let o=n.schema._zod.run({value:i.value[n.property],issues:[]},{});if(o instanceof Promise)return o.then(t=>$i(t,i,n.property));$i(o,i,n.property)}}),eu=h("$ZodCheckProperties",(e,n)=>{q.init(e,n),Yn(e,Symbol.iterator,function*(){yield e});let i;e._zod.check=o=>{if(o.value==null){o.issues.push({expected:"object",code:"invalid_type",input:o.value,inst:e});return}i??(i=Reflect.ownKeys(n.shape).map(a=>[a,n.shape[a]]));let t=o.value,r;for(let[a,s]of i){let u=s._zod.run({value:t[a],issues:[]},{});u instanceof Promise?(r??(r=[]),r.push(u.then(c=>$i(c,o,a)))):$i(u,o,a)}if(r)return Promise.all(r).then(()=>{})}}),tu=h("$ZodCheckMimeType",(e,n)=>{q.init(e,n);let i=new Set(n.mime);e._zod.check=o=>{i.has(o.value.type)||o.issues.push({code:"invalid_value",values:n.mime,input:o.value.type,inst:e,continue:!n.abort})}}),nu=h("$ZodCheckOverwrite",(e,n)=>{q.init(e,n),e._zod.check=i=>{i.value=n.tx(i.value)}});var It=class{constructor(n=[],i={}){this.content=[],this.indent=0,this.args=n,this.closed=i}indented(n){this.indent+=1;try{n(this)}finally{this.indent-=1}}write(n){if(typeof n=="function"){n(this,{execution:"sync"}),n(this,{execution:"async"});return}let o=n.split(` `).filter(a=>a),t=Math.min(...o.map(a=>a.length-a.trimStart().length)),r=o.map(a=>a.slice(t)).map(a=>" ".repeat(this.indent*2)+a);for(let a of r)this.content.push(a)}compile(){let n=Function,i=this?.content??[""];return new n(...Object.keys(this.closed),`return function (${this.args.join(", ")}) { ${i.join(` `)} -};`)(...Object.values(this.closed))}};var Ys={major:4,minor:6,patch:7};var U=h("$ZodType",(e,n)=>{var i;e??(e={}),e._zod.def=n,e._zod.bag=e._zod.bag||{},e._zod.version=Ys;let o=e._zod.def.checks,t=e._zod.traits.has("$ZodCheck")?[e,...o??[]]:o?.length?[...o]:[];for(let r of t)for(let a of r._zod.onattach)a(e);if(t.length===0)(i=e._zod).deferred??(i.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let r=(s,c,u)=>{if(s.memo)return s;let d=pe(s),p;for(let m of c){if(m._zod.def.when){if(Ra(s)||!m._zod.def.when(s))continue}else if(d)continue;let f=s.issues.length,g=m._zod.check(s);if(g instanceof Promise&&u?.async===!1)throw new $e;if(p||g instanceof Promise)p=(p??Promise.resolve()).then(async()=>{await g,s.issues.length!==f&&(Jn(s.issues,f,e),d||(d=pe(s,f)))});else{if(s.issues.length===f)continue;Jn(s.issues,f,e),d||(d=pe(s,f))}}return p?p.then(()=>s):s},a=(s,c,u)=>{if(pe(s))return s.aborted=!0,s;let d=r(c,t,u);if(d instanceof Promise){if(u.async===!1)throw new $e;return d.then(p=>e._zod.parse(p,u))}return e._zod.parse(d,u)};e._zod.run=(s,c)=>{if(c.skipChecks)return e._zod.parse(s,c);if(c.direction==="backward"){let d=e._zod.parse({value:s.value,issues:[]},{...c,skipChecks:!0});return d instanceof Promise?d.then(p=>a(p,s,c)):a(d,s,c)}let u=e._zod.parse(s,c);if(u instanceof Promise){if(c.async===!1)throw new $e;return u.then(d=>r(d,t,c))}return r(u,t,c)}}},{get"~standard"(){return Yn(this,"~standard",Pi(this))},set"~standard"(e){it(this,"~standard",e)}}),$m=(e,n)=>e.issues.length?{issues:e.issues.map(i=>_e(i,n,ee()))}:{value:e.value};async function $_(e,n){let i={async:!0};return $m(await e._zod.run({value:n,issues:[]},i),i)}function Pi(e){return{validate:n=>{let i={async:!1};try{let o=e._zod.run({value:n,issues:[]},i);if(!(o instanceof Promise))return $m(o,i)}catch{}return $_(e,n)},vendor:"zod",version:1}}var St=h("$ZodString",(e,n)=>{U.init(e,n),e._zod.pattern=n.pattern??ks,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=String(i.value)}catch{}return typeof i.value=="string"||i.issues.push({expected:"string",code:"invalid_type",input:i.value,inst:e}),i}}),W=h("$ZodStringFormat",(e,n)=>{nn.init(e,n),St.init(e,n)}),ec=h("$ZodGUID",(e,n)=>{n.pattern??(n.pattern=os),W.init(e,n)}),tc=h("$ZodUUID",(e,n)=>{if(n.version){let o={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[n.version];if(o===void 0)throw new Error(`Invalid UUID version: "${n.version}"`);n.pattern??(n.pattern=wt(o))}else n.pattern??(n.pattern=wt());W.init(e,n)}),nc=h("$ZodEmail",(e,n)=>{n.pattern??(n.pattern=as),W.init(e,n)}),rc=1,sr=2;function ic(e){try{return typeof URL<"u"&&typeof URL.canParse=="function"?URL.canParse(e):(new URL(e),!0)}catch{return!1}}function Ti(e,n){return!("normalize"in n)&&!("hostname"in n)&&!("protocol"in n)?ic(e)||sr:km(e,n)}function km(e,n){if(!n.normalize&&n.protocol?.source===rr.source&&!/^https?:\/\//i.test(e))return rc;try{if(typeof URL<"u"){let i=URL;if(typeof i.parse=="function")return i.parse(e)??sr}return new URL(e)}catch{return sr}}var k_=/[\t\n\r]/g;function cr(e){return e.replace(k_,"")}function Ei(e,n){return n.lastIndex=0,n.test(e.hostname)}function Ai(e,n){return n.lastIndex=0,n.test(e.protocol.endsWith(":")?e.protocol.slice(0,-1):e.protocol)}var oc=h("$ZodURL",(e,n)=>{W.init(e,n),e._zod.check=i=>{try{let o=i.value.trim(),t=Ti(o,n);if(t===rc){i.issues.push({code:"invalid_format",format:"url",note:"Invalid URL format",input:i.value,inst:e,continue:!n.abort});return}if(t===sr){i.issues.push({code:"invalid_format",format:"url",input:i.value,inst:e,continue:!n.abort});return}if(t===!0){i.value=cr(o);return}n.hostname&&!Ei(t,n.hostname)&&i.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:n.hostname.source,input:i.value,inst:e,continue:!n.abort}),n.protocol&&!Ai(t,n.protocol)&&i.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:n.protocol.source,input:i.value,inst:e,continue:!n.abort}),i.value=n.normalize?t.href:cr(o);return}catch{i.issues.push({code:"invalid_format",format:"url",input:i.value,inst:e,continue:!n.abort})}}}),ac=h("$ZodEmoji",(e,n)=>{n.pattern??(n.pattern=ss()),W.init(e,n)}),sc=h("$ZodNanoID",(e,n)=>{if(n.length!==void 0&&(!Number.isInteger(n.length)||n.length<1))throw new Error(`Invalid nanoid length: ${n.length}`);n.pattern??(n.pattern=n.length===void 0?ns:rs(n.length)),W.init(e,n)}),cc=h("$ZodCUID",(e,n)=>{n.pattern??(n.pattern=qa),W.init(e,n)}),uc=h("$ZodCUID2",(e,n)=>{n.pattern??(n.pattern=Ya),W.init(e,n)}),lc=h("$ZodULID",(e,n)=>{n.pattern??(n.pattern=Qa),W.init(e,n)}),dc=h("$ZodXID",(e,n)=>{n.pattern??(n.pattern=es),W.init(e,n)}),pc=h("$ZodKSUID",(e,n)=>{n.pattern??(n.pattern=ts),W.init(e,n)}),mc=h("$ZodISODateTime",(e,n)=>{n.pattern??(n.pattern=$s(n)),W.init(e,n)}),fc=h("$ZodISODate",(e,n)=>{n.pattern??(n.pattern=bs),W.init(e,n)}),gc=h("$ZodISOTime",(e,n)=>{n.pattern??(n.pattern=_s(n)),W.init(e,n)}),hc=h("$ZodISODuration",(e,n)=>{n.pattern??(n.pattern=is),W.init(e,n)}),vc=h("$ZodIPv4",(e,n)=>{n.pattern??(n.pattern=cs),W.init(e,n)}),x_=/^[0-9a-fA-F:.]+$/;function ur(e){return x_.test(e)?ic(`http://[${e}]`):!1}var yc=h("$ZodIPv6",(e,n)=>{n.pattern??(n.pattern=us),W.init(e,n),e._zod.check=i=>{ur(i.value)||i.issues.push({code:"invalid_format",format:"ipv6",input:i.value,inst:e,continue:!n.abort})}}),bc=h("$ZodMAC",(e,n)=>{n.pattern??(n.pattern=ls(n.delimiter)),W.init(e,n)}),_c=h("$ZodCIDRv4",(e,n)=>{n.pattern??(n.pattern=ds),W.init(e,n)});function Oi(e){let n=e.split("/");if(n.length!==2)return!1;let[i,o]=n;if(!o)return!1;let t=Number(o);return`${t}`!==o||t<0||t>128?!1:ur(i)}var $c=h("$ZodCIDRv6",(e,n)=>{n.pattern??(n.pattern=ps),W.init(e,n),e._zod.check=i=>{Oi(i.value)||i.issues.push({code:"invalid_format",format:"cidrv6",input:i.value,inst:e,continue:!n.abort})}});function lr(e){if(e==="")return!0;if(/\s/.test(e)||e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}var Ni=/^[0-9a-zA-Z+/]*={0,2}$/,kc=h("$ZodBase64",(e,n)=>{n.pattern??(n.pattern=Ni),W.init(e,n),e._zod.check=i=>{lr(i.value)||i.issues.push({code:"invalid_format",format:"base64",input:i.value,inst:e,continue:!n.abort})}}),dr=/^[A-Za-z0-9_-]*$/;function zi(e){if(!dr.test(e))return!1;let n=e.replace(/[-_]/g,o=>o==="-"?"+":"/"),i=n.padEnd(Math.ceil(n.length/4)*4,"=");return lr(i)}var xc=h("$ZodBase64URL",(e,n)=>{n.pattern??(n.pattern=dr),W.init(e,n),e._zod.check=i=>{zi(i.value)||i.issues.push({code:"invalid_format",format:"base64url",input:i.value,inst:e,continue:!n.abort})}}),wc=h("$ZodE164",(e,n)=>{n.pattern??(n.pattern=vs),W.init(e,n)}),w_=/[- ]/g;function I_(e){let n=e.length,i=1,o=0;for(;n;){let t=e.charCodeAt(--n)-48;i^=1,o+=i?[0,2,4,6,8,1,3,5,7,9][t]:t}return o%10===0}function Di(e){return vi.test(e)?I_(e.replace(w_,"")):!1}var Ic=h("$ZodCreditCard",(e,n)=>{n.pattern??(n.pattern=vi),W.init(e,n),e._zod.check=i=>{Di(i.value)||i.issues.push({code:"invalid_format",format:"credit_card",input:i.value,inst:e,continue:!n.abort})}});function S_(e){let n=0,i=e.length;for(let o=4;o=65?n*100+(t-55):n*10+(t-48))%97}for(let o=0;o<4;o++){let t=e.charCodeAt(o);n=(t>=65?n*100+(t-55):n*10+(t-48))%97}return n===1}function Ci(e){return yi.test(e)?S_(e):!1}var Sc=h("$ZodIBAN",(e,n)=>{n.pattern??(n.pattern=yi),W.init(e,n),e._zod.check=i=>{Ci(i.value)||i.issues.push({code:"invalid_format",format:"iban",input:i.value,inst:e,continue:!n.abort})}});function ji(e,n=null){try{let i=e.split(".");if(i.length!==3)return!1;let[o]=i;if(!o)return!1;let t=JSON.parse(atob(o));return!("typ"in t&&t?.typ!=="JWT"||!t.alg||n&&(!("alg"in t)||t.alg!==n))}catch{return!1}}var Pc=h("$ZodJWT",(e,n)=>{W.init(e,n),e._zod.check=i=>{ji(i.value,n.alg)||i.issues.push({code:"invalid_format",format:"jwt",input:i.value,inst:e,continue:!n.abort})}}),Tc=h("$ZodCustomStringFormat",(e,n)=>{W.init(e,n),e._zod.check=i=>{n.fn(i.value)||i.issues.push({code:"invalid_format",format:n.format,input:i.value,inst:e,continue:!n.abort})}}),Li=h("$ZodNumber",(e,n)=>{U.init(e,n),e._zod.pattern=qe,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=Number(i.value)}catch{}let t=i.value;if(typeof t=="number"&&!Number.isNaN(t)&&Number.isFinite(t))return i;let r=typeof t=="number"?Number.isNaN(t)?"NaN":Number.isFinite(t)?void 0:String(t):void 0;return i.issues.push({expected:"number",code:"invalid_type",input:t,inst:e,...r?{received:r}:{}}),i}}),Ec=h("$ZodNumberFormat",(e,n)=>{Ds.init(e,n),Li.init(e,n)}),pr=h("$ZodBoolean",(e,n)=>{U.init(e,n),e._zod.pattern=Is,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=!!i.value}catch{}let t=i.value;return typeof t=="boolean"||i.issues.push({expected:"boolean",code:"invalid_type",input:t,inst:e}),i}}),Ui=h("$ZodBigInt",(e,n)=>{U.init(e,n),e._zod.pattern=ws,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=BigInt(i.value)}catch{}return typeof i.value=="bigint"||i.issues.push({expected:"bigint",code:"invalid_type",input:i.value,inst:e}),i}}),Ac=h("$ZodBigIntFormat",(e,n)=>{Cs.init(e,n),Ui.init(e,n)}),Oc=h("$ZodSymbol",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value;return typeof t=="symbol"||i.issues.push({expected:"symbol",code:"invalid_type",input:t,inst:e}),i}}),Nc=h("$ZodUndefined",(e,n)=>{U.init(e,n),e._zod.pattern=Ps,e._zod.values=new Set([void 0]),e._zod.parse=(i,o)=>{let t=i.value;return typeof t>"u"||i.issues.push({expected:"undefined",code:"invalid_type",input:t,inst:e}),i}}),zc=h("$ZodNull",(e,n)=>{U.init(e,n),e._zod.pattern=Ss,e._zod.values=new Set([null]),e._zod.parse=(i,o)=>{let t=i.value;return t===null||i.issues.push({expected:"null",code:"invalid_type",input:t,inst:e}),i}}),Dc=h("$ZodAny",(e,n)=>{U.init(e,n),e._zod.parse=i=>i}),Cc=h("$ZodUnknown",(e,n)=>{U.init(e,n),e._zod.parse=i=>i}),jc=h("$ZodNever",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>(i.issues.push({expected:"never",code:"invalid_type",input:i.value,inst:e}),i)}),Lc=h("$ZodVoid",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value;return typeof t>"u"||i.issues.push({expected:"void",code:"invalid_type",input:t,inst:e}),i}}),Uc=h("$ZodDate",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=new Date(i.value)}catch{}let t=i.value,r=t instanceof Date;return r&&!Number.isNaN(t.getTime())||i.issues.push({expected:"date",code:"invalid_type",input:t,...r?{received:"Invalid Date"}:{},inst:e}),i}});function Rc(e,n){let i=Xt(e._zod.def.checks);if(!i.length)return;let o=Math.min(...i.map(r=>r[0])),t=e._zod.parse;e._zod.parse=(r,a)=>{let s=n(r.value);if(s>o&&!a?.skipChecks&&a?.direction!=="backward"){for(let[c,u]of Xt(e._zod.def.checks))if(s>c&&u._zod.def.when(r)){let d=r.issues.length;return u._zod.check(r),r.issues[d].continue=!1,Jn(r.issues,d,e),r}}return t(r,a)}}var P_=e=>Array.isArray(e)?e.length:-1,T_=e=>e instanceof Map?e.size:-1,E_=e=>e instanceof Set?e.size:-1;function am(e,n,i){e.issues.length&&n.issues.push(...be(i,e.issues)),n.value[i]=e.value}var Mc=h("$ZodArray",(e,n)=>{U.init(e,n);let i=oe.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!Array.isArray(r))return o.issues.push({expected:"array",code:"invalid_type",input:r,inst:e}),o;o.value=i?i.alloc(e,o,Array(r.length),t):Array(r.length);let a=[],s=t?.abortEarly;for(let c=0;cam(p,o,c)));else if(am(d,o,c),s&&d.issues.length!==0&&pe(d))break}return a.length?Promise.all(a).then(()=>o):o},Rc(e,P_)});function Si(e,n,i,o,t,r){let a=i in o,s=r==="optional";if(!(!a&&s&&t==="optional")){if(e.issues.length){if(t!==void 0&&s&&!a)return;n.issues.push(...be(i,e.issues))}if(!a&&t===void 0){e.issues.length||n.issues.push({code:"invalid_type",expected:"nonoptional",input:void 0,path:[i]});return}e.value===void 0?(a||t==="defaulted"&&!s)&&(n.value[i]=void 0):n.value[i]=e.value}}var A_=[];function xm(e){let n=Object.keys(e.shape),i=Object.getOwnPropertySymbols(e.shape),o=i.length?i:A_,t=o.length?[...n,...o]:n;for(let a of t)if(!e.shape?.[a]?._zod?.traits?.has("$ZodType"))throw new Error(`Invalid element at key "${String(a)}": expected a Zod schema`);let r=Ua(e.shape);return{...e,allKeys:t,symbolKeys:o,keySet:new Set(n),numKeys:n.length,optionalKeys:new Set(r)}}function wm(e,n,i,o,t,r,a){let s=[],c=t.keySet,u=t.catchall._zod,d=u.def.type,p=u.optin,m=u.optout,f=0;for(let g in n){if(a&&i.issues.length!==f){if(pe(i,f))break;f=i.issues.length}if(c.has(g))continue;if(g==="__proto__"){d==="never"&&s.push(g);continue}if(d==="never"){s.push(g);continue}let y=u.run({value:n[g],issues:[]},o);y instanceof Promise?e.push(y.then(x=>Si(x,i,g,n,p,m))):Si(y,i,g,n,p,m)}return s.length&&i.issues.push({code:"unrecognized_keys",keys:s,input:n,inst:r,continue:!0}),e.length?Promise.all(e).then(()=>i):i}var Im=h("$ZodObject",(e,n)=>{U.init(e,n);let i=Object.getOwnPropertyDescriptor(n,"shape"),o=i?.get?i.get.raw:n.shape??{};if(o){let u=()=>{let d={...o};return Object.defineProperty(n,"shape",{value:d}),u.raw=d,d};u.raw=o,Object.defineProperty(n,"shape",{get:u})}let t=Wt(()=>xm(n));Z(e,"propValues",u=>{let d=u.def.shape,p={};for(let m in d){let f=d[m]._zod;if(f.values){Object.prototype.hasOwnProperty.call(p,m)||se(p,m,new Set);for(let g of f.values)p[m].add(g);f.optin!==void 0&&p[m].add(void 0)}}return p});let r=kt,a=n.catchall,s,c=oe.memoizer;c?.attach(e),e._zod.parse=(u,d)=>{s??(s=t.value);let p=u.value;if(!r(p))return u.issues.push({expected:"object",code:"invalid_type",input:p,inst:e}),u;u.value=c?c.alloc(e,u,{},d):{};let m=[],f=s.shape,g=d?.abortEarly,y=u.issues.length;for(let x of s.allKeys){if(g&&u.issues.length!==y){if(pe(u,y))break;y=u.issues.length}if(x==="__proto__")continue;let I=f[x],b=I._zod.optin,T=I._zod.optout,A=I._zod.run({value:p[x],issues:[]},d);A instanceof Promise?m.push(A.then(D=>Si(D,u,x,p,b,T))):Si(A,u,x,p,b,T)}return a?wm(m,p,u,d,t.value,e,g===!0):m.length?Promise.all(m).then(()=>u):u}}),Zc=h("$ZodObjectJIT",(e,n)=>{Im.init(e,n);let i=e._zod.parse,o=Wt(()=>xm(n)),t=oe.memoizer,r=f=>{let g=o.value,y=g.symbolKeys,x=new It(["payload","ctx"],{shape:f,inst:e,memo:t,syms:y}),I=D=>`shape[${D}]._zod.run({ value: input[${D}], issues: [] }, ctx)`,b=(D,w)=>` +};`)(...Object.values(this.closed))}};var ru={major:4,minor:6,patch:7};var U=h("$ZodType",(e,n)=>{var i;e??(e={}),e._zod.def=n,e._zod.bag=e._zod.bag||{},e._zod.version=ru;let o=e._zod.def.checks,t=e._zod.traits.has("$ZodCheck")?[e,...o??[]]:o?.length?[...o]:[];for(let r of t)for(let a of r._zod.onattach)a(e);if(t.length===0)(i=e._zod).deferred??(i.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let r=(s,u,c)=>{if(s.memo)return s;let d=me(s),p;for(let m of u){if(m._zod.def.when){if(Ga(s)||!m._zod.def.when(s))continue}else if(d)continue;let f=s.issues.length,g=m._zod.check(s);if(g instanceof Promise&&c?.async===!1)throw new $e;if(p||g instanceof Promise)p=(p??Promise.resolve()).then(async()=>{await g,s.issues.length!==f&&(Jn(s.issues,f,e),d||(d=me(s,f)))});else{if(s.issues.length===f)continue;Jn(s.issues,f,e),d||(d=me(s,f))}}return p?p.then(()=>s):s},a=(s,u,c)=>{if(me(s))return s.aborted=!0,s;let d=r(u,t,c);if(d instanceof Promise){if(c.async===!1)throw new $e;return d.then(p=>e._zod.parse(p,c))}return e._zod.parse(d,c)};e._zod.run=(s,u)=>{if(u.skipChecks)return e._zod.parse(s,u);if(u.direction==="backward"){let d=e._zod.parse({value:s.value,issues:[]},{...u,skipChecks:!0});return d instanceof Promise?d.then(p=>a(p,s,u)):a(d,s,u)}let c=e._zod.parse(s,u);if(c instanceof Promise){if(u.async===!1)throw new $e;return c.then(d=>r(d,t,u))}return r(c,t,u)}}},{get"~standard"(){return Yn(this,"~standard",Ti(this))},set"~standard"(e){it(this,"~standard",e)}}),Em=(e,n)=>e.issues.length?{issues:e.issues.map(i=>_e(i,n,te()))}:{value:e.value};async function M_(e,n){let i={async:!0};return Em(await e._zod.run({value:n,issues:[]},i),i)}function Ti(e){return{validate:n=>{let i={async:!1};try{let o=e._zod.run({value:n,issues:[]},i);if(!(o instanceof Promise))return Em(o,i)}catch{}return M_(e,n)},vendor:"zod",version:1}}var St=h("$ZodString",(e,n)=>{U.init(e,n),e._zod.pattern=n.pattern??Ps,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=String(i.value)}catch{}return typeof i.value=="string"||i.issues.push({expected:"string",code:"invalid_type",input:i.value,inst:e}),i}}),K=h("$ZodStringFormat",(e,n)=>{nn.init(e,n),St.init(e,n)}),ou=h("$ZodGUID",(e,n)=>{n.pattern??(n.pattern=ls),K.init(e,n)}),au=h("$ZodUUID",(e,n)=>{if(n.version){let o={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[n.version];if(o===void 0)throw new Error(`Invalid UUID version: "${n.version}"`);n.pattern??(n.pattern=wt(o))}else n.pattern??(n.pattern=wt());K.init(e,n)}),su=h("$ZodEmail",(e,n)=>{n.pattern??(n.pattern=ds),K.init(e,n)}),uu=1,sr=2;function cu(e){try{return typeof URL<"u"&&typeof URL.canParse=="function"?URL.canParse(e):(new URL(e),!0)}catch{return!1}}function Ei(e,n){return!("normalize"in n)&&!("hostname"in n)&&!("protocol"in n)?cu(e)||sr:Am(e,n)}function Am(e,n){if(!n.normalize&&n.protocol?.source===rr.source&&!/^https?:\/\//i.test(e))return uu;try{if(typeof URL<"u"){let i=URL;if(typeof i.parse=="function")return i.parse(e)??sr}return new URL(e)}catch{return sr}}var Z_=/[\t\n\r]/g;function ur(e){return e.replace(Z_,"")}function Ai(e,n){return n.lastIndex=0,n.test(e.hostname)}function Oi(e,n){return n.lastIndex=0,n.test(e.protocol.endsWith(":")?e.protocol.slice(0,-1):e.protocol)}var lu=h("$ZodURL",(e,n)=>{K.init(e,n),e._zod.check=i=>{try{let o=i.value.trim(),t=Ei(o,n);if(t===uu){i.issues.push({code:"invalid_format",format:"url",note:"Invalid URL format",input:i.value,inst:e,continue:!n.abort});return}if(t===sr){i.issues.push({code:"invalid_format",format:"url",input:i.value,inst:e,continue:!n.abort});return}if(t===!0){i.value=ur(o);return}n.hostname&&!Ai(t,n.hostname)&&i.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:n.hostname.source,input:i.value,inst:e,continue:!n.abort}),n.protocol&&!Oi(t,n.protocol)&&i.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:n.protocol.source,input:i.value,inst:e,continue:!n.abort}),i.value=n.normalize?t.href:ur(o);return}catch{i.issues.push({code:"invalid_format",format:"url",input:i.value,inst:e,continue:!n.abort})}}}),du=h("$ZodEmoji",(e,n)=>{n.pattern??(n.pattern=ps()),K.init(e,n)}),pu=h("$ZodNanoID",(e,n)=>{if(n.length!==void 0&&(!Number.isInteger(n.length)||n.length<1))throw new Error(`Invalid nanoid length: ${n.length}`);n.pattern??(n.pattern=n.length===void 0?ss:us(n.length)),K.init(e,n)}),mu=h("$ZodCUID",(e,n)=>{n.pattern??(n.pattern=ns),K.init(e,n)}),fu=h("$ZodCUID2",(e,n)=>{n.pattern??(n.pattern=rs),K.init(e,n)}),gu=h("$ZodULID",(e,n)=>{n.pattern??(n.pattern=is),K.init(e,n)}),hu=h("$ZodXID",(e,n)=>{n.pattern??(n.pattern=os),K.init(e,n)}),vu=h("$ZodKSUID",(e,n)=>{n.pattern??(n.pattern=as),K.init(e,n)}),yu=h("$ZodISODateTime",(e,n)=>{n.pattern??(n.pattern=Ss(n)),K.init(e,n)}),bu=h("$ZodISODate",(e,n)=>{n.pattern??(n.pattern=ws),K.init(e,n)}),_u=h("$ZodISOTime",(e,n)=>{n.pattern??(n.pattern=Is(n)),K.init(e,n)}),$u=h("$ZodISODuration",(e,n)=>{n.pattern??(n.pattern=cs),K.init(e,n)}),ku=h("$ZodIPv4",(e,n)=>{n.pattern??(n.pattern=ms),K.init(e,n)}),F_=/^[0-9a-fA-F:.]+$/;function cr(e){return F_.test(e)?cu(`http://[${e}]`):!1}var xu=h("$ZodIPv6",(e,n)=>{n.pattern??(n.pattern=fs),K.init(e,n),e._zod.check=i=>{cr(i.value)||i.issues.push({code:"invalid_format",format:"ipv6",input:i.value,inst:e,continue:!n.abort})}}),wu=h("$ZodMAC",(e,n)=>{n.pattern??(n.pattern=gs(n.delimiter)),K.init(e,n)}),Iu=h("$ZodCIDRv4",(e,n)=>{n.pattern??(n.pattern=hs),K.init(e,n)});function Ni(e){let n=e.split("/");if(n.length!==2)return!1;let[i,o]=n;if(!o)return!1;let t=Number(o);return`${t}`!==o||t<0||t>128?!1:cr(i)}var Su=h("$ZodCIDRv6",(e,n)=>{n.pattern??(n.pattern=vs),K.init(e,n),e._zod.check=i=>{Ni(i.value)||i.issues.push({code:"invalid_format",format:"cidrv6",input:i.value,inst:e,continue:!n.abort})}});function lr(e){if(e==="")return!0;if(/\s/.test(e)||e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}var zi=/^[0-9a-zA-Z+/]*={0,2}$/,Pu=h("$ZodBase64",(e,n)=>{n.pattern??(n.pattern=zi),K.init(e,n),e._zod.check=i=>{lr(i.value)||i.issues.push({code:"invalid_format",format:"base64",input:i.value,inst:e,continue:!n.abort})}}),dr=/^[A-Za-z0-9_-]*$/;function Di(e){if(!dr.test(e))return!1;let n=e.replace(/[-_]/g,o=>o==="-"?"+":"/"),i=n.padEnd(Math.ceil(n.length/4)*4,"=");return lr(i)}var Tu=h("$ZodBase64URL",(e,n)=>{n.pattern??(n.pattern=dr),K.init(e,n),e._zod.check=i=>{Di(i.value)||i.issues.push({code:"invalid_format",format:"base64url",input:i.value,inst:e,continue:!n.abort})}}),Eu=h("$ZodE164",(e,n)=>{n.pattern??(n.pattern=ks),K.init(e,n)}),V_=/[- ]/g;function G_(e){let n=e.length,i=1,o=0;for(;n;){let t=e.charCodeAt(--n)-48;i^=1,o+=i?[0,2,4,6,8,1,3,5,7,9][t]:t}return o%10===0}function Ci(e){return yi.test(e)?G_(e.replace(V_,"")):!1}var Au=h("$ZodCreditCard",(e,n)=>{n.pattern??(n.pattern=yi),K.init(e,n),e._zod.check=i=>{Ci(i.value)||i.issues.push({code:"invalid_format",format:"credit_card",input:i.value,inst:e,continue:!n.abort})}});function B_(e){let n=0,i=e.length;for(let o=4;o=65?n*100+(t-55):n*10+(t-48))%97}for(let o=0;o<4;o++){let t=e.charCodeAt(o);n=(t>=65?n*100+(t-55):n*10+(t-48))%97}return n===1}function Li(e){return bi.test(e)?B_(e):!1}var Ou=h("$ZodIBAN",(e,n)=>{n.pattern??(n.pattern=bi),K.init(e,n),e._zod.check=i=>{Li(i.value)||i.issues.push({code:"invalid_format",format:"iban",input:i.value,inst:e,continue:!n.abort})}});function ji(e,n=null){try{let i=e.split(".");if(i.length!==3)return!1;let[o]=i;if(!o)return!1;let t=JSON.parse(atob(o));return!("typ"in t&&t?.typ!=="JWT"||!t.alg||n&&(!("alg"in t)||t.alg!==n))}catch{return!1}}var Nu=h("$ZodJWT",(e,n)=>{K.init(e,n),e._zod.check=i=>{ji(i.value,n.alg)||i.issues.push({code:"invalid_format",format:"jwt",input:i.value,inst:e,continue:!n.abort})}}),zu=h("$ZodCustomStringFormat",(e,n)=>{K.init(e,n),e._zod.check=i=>{n.fn(i.value)||i.issues.push({code:"invalid_format",format:n.format,input:i.value,inst:e,continue:!n.abort})}}),Ui=h("$ZodNumber",(e,n)=>{U.init(e,n),e._zod.pattern=qe,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=Number(i.value)}catch{}let t=i.value;if(typeof t=="number"&&!Number.isNaN(t)&&Number.isFinite(t))return i;let r=typeof t=="number"?Number.isNaN(t)?"NaN":Number.isFinite(t)?void 0:String(t):void 0;return i.issues.push({expected:"number",code:"invalid_type",input:t,inst:e,...r?{received:r}:{}}),i}}),Du=h("$ZodNumberFormat",(e,n)=>{Rs.init(e,n),Ui.init(e,n)}),pr=h("$ZodBoolean",(e,n)=>{U.init(e,n),e._zod.pattern=As,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=!!i.value}catch{}let t=i.value;return typeof t=="boolean"||i.issues.push({expected:"boolean",code:"invalid_type",input:t,inst:e}),i}}),Ri=h("$ZodBigInt",(e,n)=>{U.init(e,n),e._zod.pattern=Es,e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=BigInt(i.value)}catch{}return typeof i.value=="bigint"||i.issues.push({expected:"bigint",code:"invalid_type",input:i.value,inst:e}),i}}),Cu=h("$ZodBigIntFormat",(e,n)=>{Ms.init(e,n),Ri.init(e,n)}),Lu=h("$ZodSymbol",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value;return typeof t=="symbol"||i.issues.push({expected:"symbol",code:"invalid_type",input:t,inst:e}),i}}),ju=h("$ZodUndefined",(e,n)=>{U.init(e,n),e._zod.pattern=Ns,e._zod.values=new Set([void 0]),e._zod.parse=(i,o)=>{let t=i.value;return typeof t>"u"||i.issues.push({expected:"undefined",code:"invalid_type",input:t,inst:e}),i}}),Uu=h("$ZodNull",(e,n)=>{U.init(e,n),e._zod.pattern=Os,e._zod.values=new Set([null]),e._zod.parse=(i,o)=>{let t=i.value;return t===null||i.issues.push({expected:"null",code:"invalid_type",input:t,inst:e}),i}}),Ru=h("$ZodAny",(e,n)=>{U.init(e,n),e._zod.parse=i=>i}),Mu=h("$ZodUnknown",(e,n)=>{U.init(e,n),e._zod.parse=i=>i}),Zu=h("$ZodNever",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>(i.issues.push({expected:"never",code:"invalid_type",input:i.value,inst:e}),i)}),Fu=h("$ZodVoid",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value;return typeof t>"u"||i.issues.push({expected:"void",code:"invalid_type",input:t,inst:e}),i}}),Vu=h("$ZodDate",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{if(n.coerce)try{i.value=new Date(i.value)}catch{}let t=i.value,r=t instanceof Date;return r&&!Number.isNaN(t.getTime())||i.issues.push({expected:"date",code:"invalid_type",input:t,...r?{received:"Invalid Date"}:{},inst:e}),i}});function Gu(e,n){let i=Xt(e._zod.def.checks);if(!i.length)return;let o=Math.min(...i.map(r=>r[0])),t=e._zod.parse;e._zod.parse=(r,a)=>{let s=n(r.value);if(s>o&&!a?.skipChecks&&a?.direction!=="backward"){for(let[u,c]of Xt(e._zod.def.checks))if(s>u&&c._zod.def.when(r)){let d=r.issues.length;return c._zod.check(r),r.issues[d].continue=!1,Jn(r.issues,d,e),r}}return t(r,a)}}var H_=e=>Array.isArray(e)?e.length:-1,W_=e=>e instanceof Map?e.size:-1,K_=e=>e instanceof Set?e.size:-1;function fm(e,n,i){e.issues.length&&n.issues.push(...be(i,e.issues)),n.value[i]=e.value}var Bu=h("$ZodArray",(e,n)=>{U.init(e,n);let i=ae.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!Array.isArray(r))return o.issues.push({expected:"array",code:"invalid_type",input:r,inst:e}),o;o.value=i?i.alloc(e,o,Array(r.length),t):Array(r.length);let a=[],s=t?.abortEarly;for(let u=0;ufm(p,o,u)));else if(fm(d,o,u),s&&d.issues.length!==0&&me(d))break}return a.length?Promise.all(a).then(()=>o):o},Gu(e,H_)});function Pi(e,n,i,o,t,r){let a=i in o,s=r==="optional";if(!(!a&&s&&t==="optional")){if(e.issues.length){if(t!==void 0&&s&&!a)return;n.issues.push(...be(i,e.issues))}if(!a&&t===void 0){e.issues.length||n.issues.push({code:"invalid_type",expected:"nonoptional",input:void 0,path:[i]});return}e.value===void 0?(a||t==="defaulted"&&!s)&&(n.value[i]=void 0):n.value[i]=e.value}}var J_=[];function Om(e){let n=Object.keys(e.shape),i=Object.getOwnPropertySymbols(e.shape),o=i.length?i:J_,t=o.length?[...n,...o]:n;for(let a of t)if(!e.shape?.[a]?._zod?.traits?.has("$ZodType"))throw new Error(`Invalid element at key "${String(a)}": expected a Zod schema`);let r=Va(e.shape);return{...e,allKeys:t,symbolKeys:o,keySet:new Set(n),numKeys:n.length,optionalKeys:new Set(r)}}function Nm(e,n,i,o,t,r,a){let s=[],u=t.keySet,c=t.catchall._zod,d=c.def.type,p=c.optin,m=c.optout,f=0;for(let g in n){if(a&&i.issues.length!==f){if(me(i,f))break;f=i.issues.length}if(u.has(g))continue;if(g==="__proto__"){d==="never"&&s.push(g);continue}if(d==="never"){s.push(g);continue}let y=c.run({value:n[g],issues:[]},o);y instanceof Promise?e.push(y.then(x=>Pi(x,i,g,n,p,m))):Pi(y,i,g,n,p,m)}return s.length&&i.issues.push({code:"unrecognized_keys",keys:s,input:n,inst:r,continue:!0}),e.length?Promise.all(e).then(()=>i):i}var zm=h("$ZodObject",(e,n)=>{U.init(e,n);let i=Object.getOwnPropertyDescriptor(n,"shape"),o=i?.get?i.get.raw:n.shape??{};if(o){let c=()=>{let d={...o};return Object.defineProperty(n,"shape",{value:d}),c.raw=d,d};c.raw=o,Object.defineProperty(n,"shape",{get:c})}let t=Kt(()=>Om(n));Z(e,"propValues",c=>{let d=c.def.shape,p={};for(let m in d){let f=d[m]._zod;if(f.values){Object.prototype.hasOwnProperty.call(p,m)||se(p,m,new Set);for(let g of f.values)p[m].add(g);f.optin!==void 0&&p[m].add(void 0)}}return p});let r=kt,a=n.catchall,s,u=ae.memoizer;u?.attach(e),e._zod.parse=(c,d)=>{s??(s=t.value);let p=c.value;if(!r(p))return c.issues.push({expected:"object",code:"invalid_type",input:p,inst:e}),c;c.value=u?u.alloc(e,c,{},d):{};let m=[],f=s.shape,g=d?.abortEarly,y=c.issues.length;for(let x of s.allKeys){if(g&&c.issues.length!==y){if(me(c,y))break;y=c.issues.length}if(x==="__proto__")continue;let I=f[x],b=I._zod.optin,T=I._zod.optout,A=I._zod.run({value:p[x],issues:[]},d);A instanceof Promise?m.push(A.then(D=>Pi(D,c,x,p,b,T))):Pi(A,c,x,p,b,T)}return a?Nm(m,p,c,d,t.value,e,g===!0):m.length?Promise.all(m).then(()=>c):c}}),Hu=h("$ZodObjectJIT",(e,n)=>{zm.init(e,n);let i=e._zod.parse,o=Kt(()=>Om(n)),t=ae.memoizer,r=f=>{let g=o.value,y=g.symbolKeys,x=new It(["payload","ctx"],{shape:f,inst:e,memo:t,syms:y}),I=D=>`shape[${D}]._zod.run({ value: input[${D}], issues: [] }, ctx)`,b=(D,w)=>` let ${D}_ab = false; for (let i = 0; i < ${D}.issues.length; i++) { const iss = ${D}.issues[i]; @@ -16,7 +16,7 @@ ${i.join(` if (${D}_ab && ctx && ctx.abortEarly) { payload.value = newResult; return payload; - }`;x.write("const input = payload.value;");let T=Object.create(null),A=0;for(let D of g.allKeys)T[D]=`key_${A++}`;x.write(t?"const newResult = memo.alloc(inst, payload, {}, ctx);":"const newResult = {};");for(let D of g.allKeys){if(D==="__proto__")continue;let w=T[D],O=typeof D=="symbol"?`syms[${y.indexOf(D)}]`:de(D),E=`${O} in input`,B=f[D],C=B?._zod?.optin,ne=C!==void 0,ye=B?._zod?.optout==="optional";if(x.write(`const ${w} = ${I(O)};`),ne&&ye){let Oe=C==="optional"?`${w}_present`:`${w}.value !== undefined || ${w}_present`;x.write(` + }`;x.write("const input = payload.value;");let T=Object.create(null),A=0;for(let D of g.allKeys)T[D]=`key_${A++}`;x.write(t?"const newResult = memo.alloc(inst, payload, {}, ctx);":"const newResult = {};");for(let D of g.allKeys){if(D==="__proto__")continue;let w=T[D],O=typeof D=="symbol"?`syms[${y.indexOf(D)}]`:pe(D),E=`${O} in input`,B=f[D],C=B?._zod?.optin,re=C!==void 0,ye=B?._zod?.optout==="optional";if(x.write(`const ${w} = ${I(O)};`),re&&ye){let Oe=C==="optional"?`${w}_present`:`${w}.value !== undefined || ${w}_present`;x.write(` const ${w}_present = ${E}; if (!${w}.issues.length || ${w}_present) { if (${w}.issues.length) {${b(w,O)} @@ -27,7 +27,7 @@ ${i.join(` } } - `)}else ne?(x.write(` + `)}else re?(x.write(` if (${w}.issues.length) {${b(w,O)} } `),C==="defaulted"?x.write(`newResult[${O}] = ${w}.value;`):x.write(` @@ -55,13 +55,13 @@ ${i.join(` newResult[${O}] = ${w}.value; } - `)}return x.write("payload.value = newResult;"),x.write("return payload;"),x.compile()},a,s=kt,c=!oe.jitless,d=c&&ja.value,p=n.catchall,m;e._zod.parse=(f,g)=>{m??(m=o.value);let y=f.value;return s(y)?c&&d&&g?.async===!1&&g.jitless!==!0?(a||(a=r(n.shape)),f=a(f,g),p?wm([],y,f,g,m,e,g?.abortEarly===!0):f):i(f,g):(f.issues.push({expected:"object",code:"invalid_type",input:y,inst:e}),f)}});function sm(e,n,i,o){for(let r of e)if(r.issues.length===0)return n.value=r.value,n;let t=e.filter(r=>!pe(r));return t.length===1?(n.value=t[0].value,t[0]):(n.issues.push({code:"invalid_union",input:n.value,inst:i,errors:e.map(r=>r.issues.map(a=>_e(a,o,ee())))}),n)}var mr=h("$ZodUnion",(e,n)=>{U.init(e,n),Z(e,"optin",o=>o.def.options.some(t=>t._zod.optin==="defaulted")?"defaulted":o.def.options.some(t=>t._zod.optin!==void 0)?"optional":void 0),Z(e,"optout",o=>o.def.options.some(t=>t._zod.optout==="optional")?"optional":void 0),Z(e,"values",o=>{if(o.def.options.every(t=>t._zod.values))return new Set(o.def.options.flatMap(t=>Array.from(t._zod.values)))}),Z(e,"pattern",o=>{if(o.def.options.every(t=>t._zod.pattern)){let t=o.def.options.map(r=>r._zod.pattern);return new RegExp(`^(${t.map(r=>Xe(r.source)).join("|")})$`)}});let i=n.options.length===1?n.options[0]._zod.run:null;e._zod.parse=(o,t)=>{if(i)return i(o,t);let r=!1,a=[];for(let s of n.options){let c=s._zod.run({value:o.value,issues:[]},t);if(c instanceof Promise)a.push(c),r=!0;else{if(c.issues.length===0)return c;a.push(c)}}return r?Promise.all(a).then(s=>sm(s,o,e,t)):sm(a,o,e,t)}});function cm(e,n,i,o){let t=[];for(let r=0;rr.issues.map(a=>_e(a,o,ee())))}):n.issues.push({code:"invalid_union",input:n.value,inst:i,errors:[],inclusive:!1,matches:t}),n)}var Vc=h("$ZodXor",(e,n)=>{mr.init(e,n),n.inclusive=!1;let i=n.options.length===1?n.options[0]._zod.run:null;e._zod.parse=(o,t)=>{if(i)return i(o,t);let r=!1,a=[];for(let s of n.options){let c=s._zod.run({value:o.value,issues:[]},t);c instanceof Promise?(a.push(c),r=!0):a.push(c)}return r?Promise.all(a).then(s=>cm(s,o,e,t)):cm(a,o,e,t)}});function Gc(e,n){let i=e._zod,o=i.bag.optionsMap;o||(o=Sm(i.def),i.bag.optionsMap=o);let t=o.get(n);if(t===null)throw new Error(`Ambiguous discriminator value "${String(n)}"`);return t}function Sm(e){let n=new Map;for(let i of e.options){let o=i._zod.propValues?.[e.discriminator];if(!o||o.size===0)throw new Error(`Invalid discriminated union option at index "${e.options.indexOf(i)}"`);for(let t of o)if(n.has(t)){if(t!==void 0)throw new Error(`Duplicate discriminator value "${String(t)}"`);n.set(t,null)}else n.set(t,i)}return n}var Fc=h("$ZodDiscriminatedUnion",(e,n)=>{n.inclusive=!1,mr.init(e,n);let i=e._zod.parse;Z(e,"propValues",t=>{let r={},a=0;for(let s of t.def.options){let c=s._zod.propValues;if(!c||Object.keys(c).length===0)throw new Error(`Invalid discriminated union option at index "${t.def.options.indexOf(s)}"`);c[t.def.discriminator]?.has(void 0)&&a++;for(let[u,d]of Object.entries(c)){Object.prototype.hasOwnProperty.call(r,u)||se(r,u,new Set);for(let p of d)r[u].add(p)}}return!t.def.unionFallback&&a>1&&r[t.def.discriminator]?.delete(void 0),r}),n.options.forEach((t,r)=>{let a=Jt(t._zod.def);if(a&&!Object.prototype.hasOwnProperty.call(a,n.discriminator))throw new Error(`Invalid discriminated union option at index "${r}"`)});let o=Wt(()=>Sm(n));e._zod.parse=(t,r)=>{let a=t.value;if(!kt(a))return t.issues.push({code:"invalid_type",expected:"object",input:a,inst:e}),t;let s=a?.[n.discriminator],c=o.value.get(s);return c&&(s!==void 0||r.direction!=="backward")?c._zod.run(t,r):n.unionFallback||r.direction==="backward"?i(t,r):(t.issues.push({code:"invalid_union",errors:[],note:"No matching discriminator",discriminator:n.discriminator,options:Array.from(o.value.keys()).filter(u=>o.value.get(u)!==null),input:a,path:[n.discriminator],inst:e}),t)}}),Bc=h("$ZodIntersection",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value,r=n.left._zod.run({value:t,issues:[]},o),a=n.right._zod.run({value:t,issues:[]},o);return r instanceof Promise||a instanceof Promise?Promise.all([r,a]).then(([c,u])=>um(i,c,u)):um(i,r,a)}});function rn(e,n){if(e===n)return{valid:!0,data:e};if(e instanceof Date&&n instanceof Date&&+e==+n)return{valid:!0,data:e};if(Se(e)&&Se(n)){let i=Object.keys(n),o=Object.keys(e).filter(r=>i.indexOf(r)!==-1),t={...e,...n};Object.prototype.hasOwnProperty.call(t,"__proto__")&&delete t.__proto__;for(let r of o){if(r==="__proto__")continue;let a=rn(e[r],n[r]);if(!a.valid)return{valid:!1,mergeErrorPath:[r,...a.mergeErrorPath]};t[r]=a.data}return{valid:!0,data:t}}if(Array.isArray(e)&&Array.isArray(n)){if(e.length!==n.length)return{valid:!1,mergeErrorPath:[]};let i=[];for(let o=0;o{let p;if(u.code==="unrecognized_keys"&&!u.path?.length)t??(t=u),p=u.keys;else if(u.code==="invalid_key"&&u.origin==="record"&&u.path?.length===1){let m=String(u.path[0]);r.has(m)||r.set(m,u),p=[m]}else return!1;for(let m of p)o.has(m)||o.set(m,{}),o.get(m)[d]=!0;return!0};for(let u of n.issues)a(u,"l")||e.issues.push(u);for(let u of i.issues)a(u,"r")||e.issues.push(u);let s=[...o].filter(([,u])=>u.l&&u.r).map(([u])=>u);if(s.length){let u=t?s.filter(d=>t.keys.includes(d)):[];u.length&&e.issues.push({...t,keys:u});for(let d of s)!u.includes(d)&&r.has(d)&&e.issues.push(r.get(d))}let c=rn(n.value,i.value);if(!c.valid){if(pe(e))return e;throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(c.mergeErrorPath)}`)}return e.value=c.data,e}var Ri=h("$ZodTuple",(e,n)=>{U.init(e,n);let i=n.items,o=oe.memoizer;o?.attach(e),e._zod.parse=(t,r)=>{let a=t.value;if(!Array.isArray(a))return t.issues.push({input:a,inst:e,expected:"tuple",code:"invalid_type"}),t;t.value=o?o.alloc(e,t,[],r):[];let s=[],c=lm(i,"optin"),u=lm(i,"optout");if(!n.rest){if(a.lengthi.length&&t.issues.push({code:"too_big",maximum:i.length,inclusive:!0,input:a,inst:e,origin:"array"})}let d=new Array(i.length),p=n.rest?r?.abortEarly:void 0,m=!1;for(let f=0;f{d[f]=y})):(d[f]=g,p&&!m&&g.issues.length&&(m=pe(g)))}if(n.rest&&!m){let f=i.length-1,g=a.slice(i.length),y=t.issues.length;for(let x of g){if(p&&t.issues.length!==y){if(pe(t,y))break;y=t.issues.length}f++;let I=n.rest._zod.run({value:x,issues:[]},r);I instanceof Promise?s.push(I.then(b=>dm(b,t,f))):dm(I,t,f)}}return s.length?Promise.all(s).then(()=>pm(d,t,i,a,u)):pm(d,t,i,a,u)}});function lm(e,n){for(let i=e.length-1;i>=0;i--)if(!(n==="optin"?e[i]._zod.optin!==void 0:e[i]._zod.optout==="optional"))return i+1;return 0}function dm(e,n,i){e.issues.length&&n.issues.push(...be(i,e.issues)),n.value[i]=e.value}function pm(e,n,i,o,t){for(let r=0;r=t&&i[r]._zod.optin==="optional"){n.value.length=r;break}if(a.issues.length){if(!s&&r>=t){n.value.length=r;break}n.issues.push(...be(r,a.issues))}n.value[r]=a.value}for(let r=n.value.length-1;r>=o.length&&(i[r]._zod.optout==="optional"&&n.value[r]===void 0);r--)n.value.length=r;return n}var Hc=h("$ZodRecord",(e,n)=>{U.init(e,n);let i=oe.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!Se(r))return o.issues.push({expected:"record",code:"invalid_type",input:r,inst:e}),o;let a=[],s=n.keyType._zod.values;if(s&&!n.partial){o.value=i?i.alloc(e,o,{},t):{};let c=new Set;for(let d of s)if(typeof d=="string"||typeof d=="number"||typeof d=="symbol"){if(c.add(typeof d=="number"?d.toString():d),d==="__proto__")continue;let p=n.keyType._zod.run({value:d,issues:[]},t);if(p instanceof Promise)throw new Error("Async schemas not supported in object keys currently");if(p.issues.length){o.issues.push({code:"invalid_key",origin:"record",issues:p.issues.map(g=>_e(g,t,ee())),input:d,path:[d],inst:e});continue}let m=p.value;if(m==="__proto__")continue;let f=n.valueType._zod.run({value:r[d],issues:[]},t);f instanceof Promise?a.push(f.then(g=>{g.issues.length&&o.issues.push(...be(d,g.issues)),o.value[m]=g.value})):(f.issues.length&&o.issues.push(...be(d,f.issues)),o.value[m]=f.value)}let u;for(let d in r)if(!c.has(d))if(n.mode==="loose"){if(d==="__proto__")continue;o.value[d]=r[d]}else u=u??[],u.push(d);u&&u.length>0&&o.issues.push({code:"unrecognized_keys",input:r,inst:e,keys:u,continue:!0})}else{o.value=i?i.alloc(e,o,{},t):{};let c;for(let u of Reflect.ownKeys(r)){if(u==="__proto__"||!Object.prototype.propertyIsEnumerable.call(r,u))continue;let d=n.keyType._zod.run({value:u,issues:[]},t);if(d instanceof Promise)throw new Error("Async schemas not supported in object keys currently");if(typeof u=="string"&&qe.test(u)&&d.issues.length){let g=n.keyType._zod.run({value:Number(u),issues:[]},t);if(g instanceof Promise)throw new Error("Async schemas not supported in object keys currently");g.issues.length===0&&(d=g)}if(d.issues.length){n.mode==="loose"?o.value[u]=r[u]:s?(c=c??[],c.push(u)):o.issues.push({code:"invalid_key",origin:"record",issues:d.issues.map(g=>_e(g,t,ee())),input:u,path:[u],inst:e});continue}let m=d.value;if(m==="__proto__")continue;let f=n.valueType._zod.run({value:r[u],issues:[]},t);f instanceof Promise?a.push(f.then(g=>{g.issues.length&&o.issues.push(...be(u,g.issues)),o.value[m]=g.value})):(f.issues.length&&o.issues.push(...be(u,f.issues)),o.value[m]=f.value)}c&&c.length>0&&o.issues.push({code:"unrecognized_keys",input:r,inst:e,keys:c,continue:!0})}return a.length?Promise.all(a).then(()=>o):o}}),Kc=h("$ZodMap",(e,n)=>{U.init(e,n);let i=oe.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!(r instanceof Map))return o.issues.push({expected:"map",code:"invalid_type",input:r,inst:e}),o;let a=[];o.value=i?i.alloc(e,o,new Map,t):new Map;let s=t?.abortEarly,c=o.issues.length;for(let[u,d]of r){if(s&&o.issues.length!==c){if(pe(o,c))break;c=o.issues.length}let p=n.keyType._zod.run({value:u,issues:[]},t),m=n.valueType._zod.run({value:d,issues:[]},t);p instanceof Promise||m instanceof Promise?a.push(Promise.all([p,m]).then(([f,g])=>{mm(f,g,o,u,r,e,t)})):mm(p,m,o,u,r,e,t)}return a.length?Promise.all(a).then(()=>o):o},Rc(e,T_)});function mm(e,n,i,o,t,r,a){e.issues.length&&(Hn.has(typeof o)?i.issues.push(...be(o,e.issues)):i.issues.push({code:"invalid_key",origin:"map",input:t,inst:r,issues:e.issues.map(s=>_e(s,a,ee()))})),n.issues.length&&(Hn.has(typeof o)?i.issues.push(...be(o,n.issues)):i.issues.push({origin:"map",code:"invalid_element",input:t,inst:r,key:o,issues:n.issues.map(s=>_e(s,a,ee()))})),i.value.set(e.value,n.value)}var Wc=h("$ZodSet",(e,n)=>{U.init(e,n);let i=oe.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!(r instanceof Set))return o.issues.push({input:r,inst:e,expected:"set",code:"invalid_type"}),o;let a=[];o.value=i?i.alloc(e,o,new Set,t):new Set;let s=t?.abortEarly,c=o.issues.length;for(let u of r){if(s&&o.issues.length!==c){if(pe(o,c))break;c=o.issues.length}let d=n.valueType._zod.run({value:u,issues:[]},t);d instanceof Promise?a.push(d.then(p=>fm(p,o))):fm(d,o)}return a.length?Promise.all(a).then(()=>o):o},Rc(e,E_)});function fm(e,n){e.issues.length&&n.issues.push(...e.issues),n.value.add(e.value)}var Jc=h("$ZodEnum",(e,n)=>{U.init(e,n);let i=Ht(n.entries),o=new Set(i);e._zod.values=o,Z(e,"pattern",t=>{let r=Ht(t.def.entries).filter(a=>Hn.has(typeof a));return new RegExp(r.length?`^(${r.map(a=>Pe(a.toString())).join("|")})$`:"^[^\\s\\S]$")}),e._zod.parse=(t,r)=>{let a=t.value;return o.has(a)||t.issues.push({code:"invalid_value",values:i,input:a,inst:e}),t}}),Xc=h("$ZodLiteral",(e,n)=>{U.init(e,n);let i=new Set(n.values);e._zod.values=i,Z(e,"pattern",o=>{let t=o.def.values;return new RegExp(t.length?`^(${t.map(r=>typeof r=="string"?Pe(r):r?Pe(r.toString()):String(r)).join("|")})$`:"^[^\\s\\S]$")}),e._zod.parse=(o,t)=>{let r=o.value;return i.has(r)||o.issues.push({code:"invalid_value",values:n.values,input:r,inst:e}),o}}),qc=h("$ZodFile",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value;return t instanceof File||i.issues.push({expected:"file",code:"invalid_type",input:t,inst:e}),i}}),Yc=h("$ZodTransform",(e,n)=>{U.init(e,n),e._zod.optin="optional",oe.memoizer?.guard(e),e._zod.parse=(i,o)=>{if(o.direction==="backward")throw new ot(e.constructor.name);let t=n.transform(i.value,i);if(o.async)return(t instanceof Promise?t:Promise.resolve(t)).then(a=>(i.value=a,i));if(t instanceof Promise)throw new $e;return i.value=t,i}});function gm(e,n){return e.value=n.issues.length?void 0:n.value,e}var Mi=h("$ZodOptional",(e,n)=>{U.init(e,n),Z(e,"optin",i=>i.def.innerType._zod.optin==="defaulted"?"defaulted":"optional"),e._zod.optout="optional",Z(e,"values",i=>{let o=i.def.innerType._zod.values;return o?new Set([...o,void 0]):void 0}),Z(e,"pattern",i=>{let o=i.def.innerType._zod.pattern;return o?new RegExp(`^(${Xe(o.source)})?$`):void 0}),e._zod.parse=(i,o)=>{if(i.value===void 0){if(n.innerType._zod.optin!=="defaulted")return i;let t=n.innerType._zod.run({value:i.value,issues:[]},o);return t instanceof Promise?t.then(r=>gm(i,r)):gm(i,t)}return n.innerType._zod.run(i,o)}}),Qc=h("$ZodExactOptional",(e,n)=>{Mi.init(e,n),Z(e,"values",i=>i.def.innerType._zod.values),Z(e,"pattern",i=>i.def.innerType._zod.pattern),e._zod.parse=(i,o)=>n.innerType._zod.run(i,o)}),eu=h("$ZodNullable",(e,n)=>{U.init(e,n),Z(e,"optin",i=>i.def.innerType._zod.optin),Z(e,"optout",i=>i.def.innerType._zod.optout),Z(e,"pattern",i=>{let o=i.def.innerType._zod.pattern;return o?new RegExp(`^(${Xe(o.source)}|null)$`):void 0}),Z(e,"values",i=>i.def.innerType._zod.values?new Set([...i.def.innerType._zod.values,null]):void 0),e._zod.parse=(i,o)=>i.value===null?i:n.innerType._zod.run(i,o)}),tu=h("$ZodDefault",(e,n)=>{U.init(e,n),e._zod.optin="defaulted",Z(e,"values",i=>i.def.innerType._zod.values),e._zod.parse=(i,o)=>{if(o.direction==="backward")return n.innerType._zod.run(i,o);if(i.value===void 0)return i.value=n.defaultValue,i;let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(r=>hm(r,n)):hm(t,n)}});function hm(e,n){return e.value===void 0&&(e.value=n.defaultValue),e}var nu=h("$ZodPrefault",(e,n)=>{U.init(e,n),e._zod.optin="defaulted",Z(e,"values",i=>i.def.innerType._zod.values),e._zod.parse=(i,o)=>(o.direction==="backward"||i.value===void 0&&(i.value=n.defaultValue),n.innerType._zod.run(i,o))}),ru=h("$ZodNonOptional",(e,n)=>{U.init(e,n),Z(e,"values",i=>{let o=i.def.innerType._zod.values;return o?new Set([...o].filter(t=>t!==void 0)):void 0}),e._zod.parse=(i,o)=>{let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(r=>vm(r,e)):vm(t,e)}});function vm(e,n){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:n}),e}var iu=h("$ZodSuccess",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{if(o.direction==="backward")throw new ot("ZodSuccess");let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(r=>(i.value=r.issues.length===0,i)):(i.value=t.issues.length===0,i)}});function ym(e,n,i,o){return n.issues.length?(e.value=i.catchValue({...n,value:e.value,error:{issues:n.issues.map(t=>_e(t,o,ee()))},input:e.value}),e):(e.value=n.value,n.memo&&(e.memo=!0),e)}var ou=h("$ZodCatch",(e,n)=>{U.init(e,n),Z(e,"optin",i=>i.def.innerType._zod.optin==="defaulted"?"defaulted":"optional"),Z(e,"optout",i=>i.def.innerType._zod.optout),Z(e,"values",i=>i.def.innerType._zod.values),e._zod.parse=(i,o)=>{if(o.direction==="backward")return n.innerType._zod.run(i,o);let t=n.innerType._zod.run({value:i.value,issues:[]},o);return t instanceof Promise?t.then(r=>ym(i,r,n,o)):ym(i,t,n,o)}}),au=h("$ZodNaN",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>((typeof i.value!="number"||!Number.isNaN(i.value))&&i.issues.push({input:i.value,inst:e,expected:"nan",code:"invalid_type"}),i)}),Zi=h("$ZodPipe",(e,n)=>{U.init(e,n),Z(e,"values",i=>i.def.in._zod.values),Z(e,"optin",i=>i.def.in._zod.optin),Z(e,"optout",i=>i.def.out._zod.optout),Z(e,"propValues",i=>i.def.in._zod.propValues),e._zod.parse=(i,o)=>{if(o.direction==="backward"){let r=n.out._zod.run(i,o);return r instanceof Promise?r.then(a=>xi(a,n.in,o)):xi(r,n.in,o)}let t=n.in._zod.run(i,o);return t instanceof Promise?t.then(r=>xi(r,n.out,o)):xi(t,n.out,o)}});function xi(e,n,i){return e.issues.some(o=>o.code!=="unrecognized_keys")?(e.aborted=!0,e):n._zod.run({value:e.value,issues:e.issues},i)}var fr=h("$ZodCodec",(e,n)=>{U.init(e,n),Z(e,"values",i=>i.def.in._zod.values),Z(e,"optin",i=>i.def.in._zod.optin),Z(e,"optout",i=>i.def.out._zod.optout),Z(e,"propValues",i=>i.def.in._zod.propValues),e._zod.parse=(i,o)=>{if((o.direction||"forward")==="forward"){let r=n.in._zod.run(i,o);return r instanceof Promise?r.then(a=>wi(a,n,o)):wi(r,n,o)}else{let r=n.out._zod.run(i,o);return r instanceof Promise?r.then(a=>wi(a,n,o)):wi(r,n,o)}}});function wi(e,n,i){if(e.issues.length)return e.aborted=!0,e;if((i.direction||"forward")==="forward"){let t=n.transform(e.value,e);return t instanceof Promise?t.then(r=>Ii(e,r,n.out,i)):Ii(e,t,n.out,i)}else{let t=n.reverseTransform(e.value,e);return t instanceof Promise?t.then(r=>Ii(e,r,n.in,i)):Ii(e,t,n.in,i)}}function Ii(e,n,i,o){return e.issues.length?(e.aborted=!0,e):i._zod.run({value:n,issues:e.issues},o)}var su=h("$ZodPreprocess",(e,n)=>{Zi.init(e,n)}),cu=h("$ZodReadonly",(e,n)=>{U.init(e,n),Z(e,"propValues",i=>i.def.innerType._zod.propValues),Z(e,"values",i=>i.def.innerType._zod.values),Z(e,"optin",i=>i.def.innerType?._zod?.optin),Z(e,"optout",i=>i.def.innerType?._zod?.optout),e._zod.parse=(i,o)=>{if(o.direction==="backward")return n.innerType._zod.run(i,o);let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(bm):bm(t)}});function bm(e){return e.memo||(e.value=Object.freeze(e.value)),e}function O_(e){let n=e._zod.def,i=n.pattern,o=!!n.format?.includes("int"),t,r;for(let s of n.checks??[]){let c=s._zod.def;c.pattern&&(i=c.pattern),o||(o=!!c.format?.includes("int"));let u=c.minimum??c.length,d=c.maximum??c.length;u!==void 0&&(t===void 0||u>t)&&(t=u),d!==void 0&&(r===void 0||dr)return"(?!)";if(t!==void 0||r!==void 0)return xs({minimum:t,maximum:r}).source;let a=e._zod.pattern;return(o&&a===qe?ir:a)?.source}function Qs(e){let n=e._zod.def,i=e._zod.pattern?.source,o=n.innerType??e._zod.innerType;if(o){let t=o._zod.pattern?.source,r=Qs(o);return i&&t&&r&&r!==t?i.replace(Xe(t),()=>Xe(r)):i}if(n.options){let t=n.options.map(Qs);if(t.every(Boolean))return`^(${t.map(r=>Xe(r)).join("|")})$`}return O_(e)}var uu=h("$ZodTemplateLiteral",(e,n)=>{U.init(e,n);let i=[];for(let o of n.parts)if(typeof o=="object"&&o!==null){let t=Qs(o);if(!t)throw new Error(`Invalid template literal part, no pattern found: ${[...o._zod.traits].shift()}`);i.push(Xe(t))}else if(o===null||La.has(typeof o))i.push(Pe(`${o}`));else throw new Error(`Invalid template literal part: ${o}`);e._zod.pattern=new RegExp(`^${i.join("")}$`),e._zod.parse=(o,t)=>typeof o.value!="string"?(o.issues.push({input:o.value,inst:e,expected:"string",code:"invalid_type"}),o):(e._zod.pattern.lastIndex=0,e._zod.pattern.test(o.value)||o.issues.push({input:o.value,inst:e,code:"invalid_format",format:n.format??"template_literal",pattern:e._zod.pattern.source}),o)}),lu=h("$ZodFunction",(e,n)=>(U.init(e,n),Object.defineProperty(e,"_def",{value:n}),e._zod.def=n,e.implement=i=>{if(typeof i!="function")throw new Error("implement() must be called with a function");return Object.defineProperty(function(...o){let t=e._def.input?si(e._def.input,o):o,r=Reflect.apply(i,this,t);return e._def.output?si(e._def.output,r):r},"_zod",{value:e._zod,enumerable:!1})},e.implementAsync=i=>{if(typeof i!="function")throw new Error("implementAsync() must be called with a function");return Object.defineProperty(async function(...o){let t=e._def.input?await ci(e._def.input,o):o,r=await Reflect.apply(i,this,t);return e._def.output?await ci(e._def.output,r):r},"_zod",{value:e._zod,enumerable:!1})},e._zod.parse=(i,o)=>typeof i.value!="function"?(i.issues.push({code:"invalid_type",expected:"function",input:i.value,inst:e}),i):(e._def.output&&e._def.output._zod.def.type==="promise"?i.value=e.implementAsync(i.value):i.value=e.implement(i.value),i),e.input=(...i)=>{let o=e.constructor;return Array.isArray(i[0])?new o({type:"function",input:new Ri({type:"tuple",items:i[0],rest:i[1]}),output:e._def.output}):new o({type:"function",input:i[0],output:e._def.output})},e.output=i=>{let o=e.constructor;return new o({type:"function",input:e._def.input,output:i})},e)),du=h("$ZodPromise",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>Promise.resolve(i.value).then(t=>n.innerType._zod.run({value:t,issues:[]},o))}),gr=h("$ZodLazy",(e,n)=>{U.init(e,n),Da(e._zod,"innerType",()=>{let i=n;return i._cachedInner||(i._cachedInner=n.getter()),i._cachedInner}),Z(e,"pattern",i=>i.innerType?._zod?.pattern),Z(e,"propValues",i=>i.innerType?._zod?.propValues),Z(e,"optin",i=>i.innerType?._zod?.optin??void 0),Z(e,"optout",i=>i.innerType?._zod?.optout??void 0),e._zod.parse=(i,o)=>e._zod.innerType._zod.run(i,o)}),pu=h("$ZodCustom",(e,n)=>{q.init(e,n),U.init(e,n),e._zod.parse=(i,o)=>i,e._zod.check=i=>{let o=i.value,t=n.fn(o);if(t instanceof Promise)return t.then(r=>_m(r,i,o,e));_m(t,i,o,e)}});function _m(e,n,i,o){if(!e){let t={code:"custom",input:i,inst:o,path:[...o._zod.def.path??[]],continue:!o._zod.def.abort};o._zod.def.params&&(t.params=o._zod.def.params),n.issues.push(qt(t))}}var Bi=class extends Error{constructor(){super("Cannot parse a reference cycle that closes through a transform"),this.name="ZodCyclicError"}},fu="~memo",Tm=[];function Em(e){return e!==null&&typeof e=="object"}function mu(e){return e.map(n=>n.path?{...n,path:n.path.slice()}:{...n})}var Am=new WeakMap,on=0,Fi=1,hr=2;function vr(e,n,i){let o=Am.get(e);if(o!==void 0)return o?hr:on;if(n.has(e))return hr;n.add(e);let t=on,r=d=>{if(t!==hr&&d?._zod){let p=vr(d,n,i);p>t&&(t=p)}},a=(d,p)=>{let m=on;for(let f of Reflect.ownKeys(d)){let g=Object.getOwnPropertyDescriptor(d,f);if(p&&!g.enumerable)continue;let y=g.get?Fi:g.value?._zod?vr(g.value,n,i):on;y>m&&(m=y)}return m},s=d=>{d>t&&(t=d)},c=e._zod.def,u=c.type;switch(u){case"object":{let d=Jt(c);s(d?a(d,!0):Fi),r(c.catchall);break}case"array":r(c.element);break;case"tuple":for(let d of c.items)r(d);r(c.rest);break;case"record":case"map":r(c.keyType),r(c.valueType);break;case"set":r(c.valueType);break;case"union":for(let d of c.options)r(d);break;case"intersection":r(c.left),r(c.right);break;case"optional":case"nullable":case"default":case"prefault":case"catch":case"readonly":case"nonoptional":case"promise":case"success":r(c.innerType);break;case"pipe":r(c.in),r(c.out);break;case"function":r(c.input),r(c.output);break;case"lazy":{let d=c._cachedInner??(i?e._zod.innerType:void 0);s(d?vr(d,n,!1):Fi);break}case"template_literal":case"string":case"number":case"int":case"boolean":case"bigint":case"symbol":case"undefined":case"null":case"void":case"never":case"any":case"unknown":case"date":case"nan":case"enum":case"literal":case"file":case"transform":case"custom":break;default:for(let d in c){let p=Object.getOwnPropertyDescriptor(c,d);if(!p||p.get)continue;let m=p.value;if(!(!m||typeof m!="object")){if(m._zod)r(m);else if(Array.isArray(m))for(let f of m)r(f)}}}return n.delete(e),N_(e,t)}function N_(e,n){return n!==Fi&&Am.set(e,n===hr),n}function Hi(e){return vr(e,new Set,!0)!==on}function z_(e,n){let i=e.buckets.get(n);return i||(i=new WeakMap,e.buckets.set(n,i)),i}var Vi,Gi=[],D_={alloc(e,n,i){let o=Vi;if(!o)return i;Vi=void 0;let t={value:i,issues:null};return o.set(n.value,t),Gi.push(t),i},guard(e){var n;(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred.push(()=>{let i=e._zod.parse,o=(t,r)=>{if(r.direction!=="backward"&&Ki(r,t.value))throw new Bi;return i(t,r)};e._zod.parse=o,e._zod.run===i&&(e._zod.run=o)})},attach(e){var n;let i,o=!1,t,r;(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred.push(()=>{let a=e._zod.parse,s=(c,u)=>{if(i===void 0){let I=vr(e,new Set,!1);if(I===on)return e._zod.parse=a,e._zod.run===s&&(e._zod.run=a),a(c,u);I===hr||o?i=!0:o=!0}let d=c.value;if(!Em(d))return a(c,u);let p=u[fu];p||(p={buckets:new WeakMap,backEdges:void 0},u[fu]=p);let m;t===u?m=r:(m=z_(p,e),t=u,r=m);let f=m.get(d);if(f)return c.value=f.value,f.issues?f.issues.length&&c.issues.push(...mu(f.issues)):(c.memo=!0,p.backEdges??(p.backEdges=new WeakSet),p.backEdges.add(f.value)),c;Vi=m;let g=Gi.length,y=a(c,u);Vi=void 0;let x=Gi.length>g?Gi.pop():void 0;return y instanceof Promise?y.then(I=>(x&&(x.issues=I.issues.length?mu(I.issues):Tm),I)):(x&&(x.issues=y.issues.length?mu(y.issues):Tm),y)};e._zod.parse=s,e._zod.run===a&&(e._zod.run=s)})}};function yr(){return D_}function Ki(e,n){let i=e[fu]?.backEdges;return i!==void 0&&Em(n)&&i.has(n)}var _r={};We(_r,{ar:()=>Om,az:()=>Nm,be:()=>Dm,bg:()=>Cm,bn:()=>jm,ca:()=>Lm,ckb:()=>Um,cs:()=>Rm,da:()=>Mm,de:()=>Zm,el:()=>Vm,en:()=>Wi,eo:()=>Gm,es:()=>Fm,fa:()=>Bm,fi:()=>Hm,fr:()=>Km,frCA:()=>Wm,gu:()=>Jm,he:()=>Xm,hi:()=>qm,hr:()=>Ym,hu:()=>Qm,hy:()=>tf,id:()=>nf,is:()=>rf,it:()=>of,ja:()=>af,ka:()=>sf,kh:()=>cf,km:()=>Ji,kn:()=>uf,ko:()=>lf,lt:()=>pf,mk:()=>mf,ms:()=>ff,ne:()=>gf,nl:()=>hf,nn:()=>vf,no:()=>yf,ota:()=>bf,pl:()=>$f,ps:()=>_f,pt:()=>kf,ptBR:()=>xf,ro:()=>wf,ru:()=>Sf,sk:()=>Pf,sl:()=>Tf,sv:()=>Ef,ta:()=>Af,tg:()=>Of,th:()=>Nf,tk:()=>zf,tr:()=>Df,ua:()=>Cf,uk:()=>Xi,ur:()=>jf,uz:()=>Lf,vi:()=>Uf,yo:()=>Zf,zhCN:()=>Rf,zhTW:()=>Mf});var C_=()=>{let e={string:{unit:"حرف",verb:"أن يحوي"},file:{unit:"بايت",verb:"أن يحوي"},array:{unit:"عنصر",verb:"أن يحوي"},set:{unit:"عنصر",verb:"أن يحوي"},map:{unit:"عنصر",verb:"أن يحوي"}};function n(t){return e[t]??null}let i={regex:"مدخل",email:"بريد إلكتروني",url:"رابط",emoji:"إيموجي",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"تاريخ ووقت بمعيار ISO",date:"تاريخ بمعيار ISO",time:"وقت بمعيار ISO",duration:"مدة بمعيار ISO",ipv4:"عنوان IPv4",ipv6:"عنوان IPv6",mac:"عنوان MAC",cidrv4:"مدى عناوين بصيغة IPv4",cidrv6:"مدى عناوين بصيغة IPv6",base64:"نَص بترميز base64-encoded",base64url:"نَص بترميز base64url-encoded",json_string:"نَص على هيئة JSON",e164:"رقم هاتف بمعيار E.164",credit_card:"رقم بطاقة الائتمان",currency_code:"رمز العملة",iban:"IBAN",jwt:"JWT",template_literal:"مدخل"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`مدخلات غير مقبولة: يفترض إدخال instanceof ${t.expected}، ولكن تم إدخال ${s}`:`مدخلات غير مقبولة: يفترض إدخال ${r}، ولكن تم إدخال ${s}`}case"invalid_value":return t.values.length===1?`مدخلات غير مقبولة: يفترض إدخال ${$(t.values[0])}`:`اختيار غير مقبول: يتوقع انتقاء أحد هذه الخيارات: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?` أكبر من اللازم: يفترض أن تكون ${t.origin??"القيمة"} ${r} ${t.maximum.toString()} ${a.unit??"عنصر"}`:`أكبر من اللازم: يفترض أن تكون ${t.origin??"القيمة"} ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`أصغر من اللازم: يفترض لـ ${t.origin} أن يكون ${r} ${t.minimum.toString()} ${a.unit}`:`أصغر من اللازم: يفترض لـ ${t.origin} أن يكون ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`نَص غير مقبول: يجب أن يبدأ بـ "${t.prefix}"`:r.format==="ends_with"?`نَص غير مقبول: يجب أن ينتهي بـ "${r.suffix}"`:r.format==="includes"?`نَص غير مقبول: يجب أن يتضمَّن "${r.includes}"`:r.format==="regex"?`نَص غير مقبول: يجب أن يطابق النمط ${r.pattern}`:`${i[r.format]??t.format} غير مقبول`}case"not_multiple_of":return`رقم غير مقبول: يجب أن يكون من مضاعفات ${t.divisor}`;case"unrecognized_keys":return`معرف${t.keys.length>1?"ات":""} غريب${t.keys.length>1?"ة":""}: ${v(t.keys,"، ")}`;case"invalid_key":return`معرف غير مقبول في ${t.origin}`;case"invalid_union":return"مدخل غير مقبول";case"invalid_element":return`مدخل غير مقبول في ${t.origin}`;default:return"مدخل غير مقبول"}}};function Om(){return{localeError:C_()}}var j_=()=>{let e={string:{unit:"simvol",verb:"olmalıdır"},file:{unit:"bayt",verb:"olmalıdır"},array:{unit:"element",verb:"olmalıdır"},set:{unit:"element",verb:"olmalıdır"},map:{unit:"element",verb:"olmalıdır"}};function n(t){return e[t]??null}let i={regex:"input",email:"email address",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datetime",date:"ISO date",time:"ISO time",duration:"ISO duration",ipv4:"IPv4 address",ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded string",base64url:"base64url-encoded string",json_string:"JSON string",e164:"E.164 number",credit_card:"kredit kartı nömrəsi",currency_code:"valyuta kodu",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Yanlış dəyər: gözlənilən instanceof ${t.expected}, daxil olan ${s}`:`Yanlış dəyər: gözlənilən ${r}, daxil olan ${s}`}case"invalid_value":return t.values.length===1?`Yanlış dəyər: gözlənilən ${$(t.values[0])}`:`Yanlış seçim: aşağıdakılardan biri olmalıdır: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Çox böyük: gözlənilən ${t.origin??"dəyər"} ${r}${t.maximum.toString()} ${a.unit??"element"}`:`Çox böyük: gözlənilən ${t.origin??"dəyər"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Çox kiçik: gözlənilən ${t.origin} ${r}${t.minimum.toString()} ${a.unit}`:`Çox kiçik: gözlənilən ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Yanlış mətn: "${r.prefix}" ilə başlamalıdır`:r.format==="ends_with"?`Yanlış mətn: "${r.suffix}" ilə bitməlidir`:r.format==="includes"?`Yanlış mətn: "${r.includes}" daxil olmalıdır`:r.format==="regex"?`Yanlış mətn: ${r.pattern} şablonuna uyğun olmalıdır`:`Yanlış ${i[r.format]??t.format}`}case"not_multiple_of":return`Yanlış ədəd: ${t.divisor} ilə bölünə bilən olmalıdır`;case"unrecognized_keys":return`Tanınmayan açar${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} daxilində yanlış açar`;case"invalid_union":return"Yanlış dəyər";case"invalid_element":return`${t.origin} daxilində yanlış dəyər`;default:return"Yanlış dəyər"}}};function Nm(){return{localeError:j_()}}function zm(e,n,i,o){let t=Math.abs(e),r=t%10,a=t%100;return a>=11&&a<=19?o:r===1?n:r>=2&&r<=4?i:o}var L_=()=>{let e={string:{unit:{one:"сімвал",few:"сімвалы",many:"сімвалаў"},verb:"мець"},array:{unit:{one:"элемент",few:"элементы",many:"элементаў"},verb:"мець"},set:{unit:{one:"элемент",few:"элементы",many:"элементаў"},verb:"мець"},map:{unit:{one:"элемент",few:"элементы",many:"элементаў"},verb:"мець"},file:{unit:{one:"байт",few:"байты",many:"байтаў"},verb:"мець"}};function n(t){return e[t]??null}let i={regex:"увод",email:"email адрас",url:"URL",emoji:"эмодзі",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO дата і час",date:"ISO дата",time:"ISO час",duration:"ISO працягласць",ipv4:"IPv4 адрас",ipv6:"IPv6 адрас",mac:"MAC адрас",cidrv4:"IPv4 дыяпазон",cidrv6:"IPv6 дыяпазон",base64:"радок у фармаце base64",base64url:"радок у фармаце base64url",json_string:"JSON радок",e164:"нумар E.164",credit_card:"нумар крэдытнай карты",currency_code:"код валюты",iban:"IBAN",jwt:"JWT",template_literal:"увод"},o={nan:"NaN",number:"лік",array:"масіў"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Няправільны ўвод: чакаўся instanceof ${t.expected}, атрымана ${s}`:`Няправільны ўвод: чакаўся ${r}, атрымана ${s}`}case"invalid_value":return t.values.length===1?`Няправільны ўвод: чакалася ${$(t.values[0])}`:`Няправільны варыянт: чакаўся адзін з ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);if(a){let s=Number(t.maximum),c=zm(s,a.unit.one,a.unit.few,a.unit.many);return`Занадта вялікі: чакалася, што ${t.origin??"значэнне"} павінна ${a.verb} ${r}${t.maximum.toString()} ${c}`}return`Занадта вялікі: чакалася, што ${t.origin??"значэнне"} павінна быць ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);if(a){let s=Number(t.minimum),c=zm(s,a.unit.one,a.unit.few,a.unit.many);return`Занадта малы: чакалася, што ${t.origin} павінна ${a.verb} ${r}${t.minimum.toString()} ${c}`}return`Занадта малы: чакалася, што ${t.origin} павінна быць ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Няправільны радок: павінен пачынацца з "${r.prefix}"`:r.format==="ends_with"?`Няправільны радок: павінен заканчвацца на "${r.suffix}"`:r.format==="includes"?`Няправільны радок: павінен змяшчаць "${r.includes}"`:r.format==="regex"?`Няправільны радок: павінен адпавядаць шаблону ${r.pattern}`:`Няправільны ${i[r.format]??t.format}`}case"not_multiple_of":return`Няправільны лік: павінен быць кратным ${t.divisor}`;case"unrecognized_keys":return`Нераспазнаны ${t.keys.length>1?"ключы":"ключ"}: ${v(t.keys,", ")}`;case"invalid_key":return`Няправільны ключ у ${t.origin}`;case"invalid_union":return"Няправільны ўвод";case"invalid_element":return`Няправільнае значэнне ў ${t.origin}`;default:return"Няправільны ўвод"}}};function Dm(){return{localeError:L_()}}var U_=()=>{let e={string:{unit:"символа",verb:"да съдържа"},file:{unit:"байта",verb:"да съдържа"},array:{unit:"елемента",verb:"да съдържа"},set:{unit:"елемента",verb:"да съдържа"},map:{unit:"елемента",verb:"да съдържа"}};function n(t){return e[t]??null}let i={regex:"вход",email:"имейл адрес",url:"URL",emoji:"емоджи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO време",date:"ISO дата",time:"ISO време",duration:"ISO продължителност",ipv4:"IPv4 адрес",ipv6:"IPv6 адрес",mac:"MAC адрес",cidrv4:"IPv4 диапазон",cidrv6:"IPv6 диапазон",base64:"base64-кодиран низ",base64url:"base64url-кодиран низ",json_string:"JSON низ",e164:"E.164 номер",credit_card:"номер на кредитна карта",currency_code:"код на валута",iban:"IBAN",jwt:"JWT",template_literal:"вход"},o={nan:"NaN",number:"число",array:"масив"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Невалиден вход: очакван instanceof ${t.expected}, получен ${s}`:`Невалиден вход: очакван ${r}, получен ${s}`}case"invalid_value":return t.values.length===1?`Невалиден вход: очакван ${$(t.values[0])}`:`Невалидна опция: очаквано едно от ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Твърде голямо: очаква се ${t.origin??"стойност"} да съдържа ${r}${t.maximum.toString()} ${a.unit??"елемента"}`:`Твърде голямо: очаква се ${t.origin??"стойност"} да бъде ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Твърде малко: очаква се ${t.origin} да съдържа ${r}${t.minimum.toString()} ${a.unit}`:`Твърде малко: очаква се ${t.origin} да бъде ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;if(r.format==="starts_with")return`Невалиден низ: трябва да започва с "${r.prefix}"`;if(r.format==="ends_with")return`Невалиден низ: трябва да завършва с "${r.suffix}"`;if(r.format==="includes")return`Невалиден низ: трябва да включва "${r.includes}"`;if(r.format==="regex")return`Невалиден низ: трябва да съвпада с ${r.pattern}`;let a="Невалиден";return r.format==="emoji"&&(a="Невалидно"),r.format==="datetime"&&(a="Невалидно"),r.format==="date"&&(a="Невалидна"),r.format==="time"&&(a="Невалидно"),r.format==="duration"&&(a="Невалидна"),`${a} ${i[r.format]??t.format}`}case"not_multiple_of":return`Невалидно число: трябва да бъде кратно на ${t.divisor}`;case"unrecognized_keys":return`Неразпознат${t.keys.length>1?"и":""} ключ${t.keys.length>1?"ове":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Невалиден ключ в ${t.origin}`;case"invalid_union":return"Невалиден вход";case"invalid_element":return`Невалидна стойност в ${t.origin}`;default:return"Невалиден вход"}}};function Cm(){return{localeError:U_()}}var R_=()=>{let e={string:{unit:"অক্ষর",verb:"থাকতে হবে"},file:{unit:"বাইট",verb:"থাকতে হবে"},array:{unit:"আইটেম",verb:"থাকতে হবে"},set:{unit:"আইটেম",verb:"থাকতে হবে"},map:{unit:"এন্ট্রি",verb:"থাকতে হবে"}};function n(t){return e[t]??null}let i={regex:"ইনপুট",email:"ইমেইল ঠিকানা",url:"URL",emoji:"ইমোজি",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO তারিখ ও সময়",date:"ISO তারিখ",time:"ISO সময়",duration:"ISO সময়কাল",ipv4:"IPv4 ঠিকানা",ipv6:"IPv6 ঠিকানা",mac:"MAC ঠিকানা",cidrv4:"IPv4 রেঞ্জ",cidrv6:"IPv6 রেঞ্জ",base64:"base64-এনকোডেড স্ট্রিং",base64url:"base64url-এনকোডেড স্ট্রিং",json_string:"JSON স্ট্রিং",e164:"E.164 নম্বর",credit_card:"ক্রেডিট কার্ড নম্বর",currency_code:"মুদ্রা কোড",iban:"IBAN",jwt:"JWT",template_literal:"ইনপুট"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`অবৈধ ইনপুট: প্রত্যাশিত ${r}, প্রাপ্ত ${s}`}case"invalid_value":return t.values.length===1?`অবৈধ ইনপুট: প্রত্যাশিত ${$(t.values[0])}`:`অবৈধ অপশন: ${v(t.values," | ")} এর মধ্যে একটি প্রত্যাশিত`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`অনেক বড়: ${t.origin??"মান"} ${r}${t.maximum.toString()} ${a.unit??"এলিমেন্ট"} হতে হবে`:`অনেক বড়: ${t.origin??"মান"} ${r}${t.maximum.toString()} হতে হবে`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`অনেক ছোট: ${t.origin} ${r}${t.minimum.toString()} ${a.unit} হতে হবে`:`অনেক ছোট: ${t.origin} ${r}${t.minimum.toString()} হতে হবে`}case"invalid_format":{let r=t;return r.format==="starts_with"?`অবৈধ স্ট্রিং: "${r.prefix}" দিয়ে শুরু হতে হবে`:r.format==="ends_with"?`অবৈধ স্ট্রিং: "${r.suffix}" দিয়ে শেষ হতে হবে`:r.format==="includes"?`অবৈধ স্ট্রিং: "${r.includes}" অন্তর্ভুক্ত থাকতে হবে`:r.format==="regex"?`অবৈধ স্ট্রিং: ${r.pattern} প্যাটার্ন মিলতে হবে`:`অবৈধ ${i[r.format]??t.format}`}case"not_multiple_of":return`অবৈধ নম্বর: ${t.divisor} এর গুণিতক হতে হবে`;case"unrecognized_keys":return`অচেনা কী${t.keys.length>1?"গুলো":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} এ অবৈধ কী`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`অবৈধ ডিসক্রিমিনেটর মান। প্রত্যাশিত ${t.options.map(a=>`'${a}'`).join(" | ")}`:"অবৈধ ইনপুট";case"invalid_element":return`${t.origin} এ অবৈধ মান`;default:return"অবৈধ ইনপুট"}}};function jm(){return{localeError:R_()}}var M_=()=>{let e={string:{unit:"caràcters",verb:"contenir"},file:{unit:"bytes",verb:"contenir"},array:{unit:"elements",verb:"contenir"},set:{unit:"elements",verb:"contenir"},map:{unit:"elements",verb:"contenir"}};function n(t){return e[t]??null}let i={regex:"entrada",email:"adreça electrònica",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data i hora ISO",date:"data ISO",time:"hora ISO",duration:"durada ISO",ipv4:"adreça IPv4",ipv6:"adreça IPv6",mac:"adreça MAC",cidrv4:"rang IPv4",cidrv6:"rang IPv6",base64:"cadena codificada en base64",base64url:"cadena codificada en base64url",json_string:"cadena JSON",e164:"número E.164",credit_card:"número de targeta de crèdit",currency_code:"codi de moneda",iban:"IBAN",jwt:"JWT",template_literal:"entrada"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Tipus invàlid: s'esperava instanceof ${t.expected}, s'ha rebut ${s}`:`Tipus invàlid: s'esperava ${r}, s'ha rebut ${s}`}case"invalid_value":return t.values.length===1?`Valor invàlid: s'esperava ${$(t.values[0])}`:`Opció invàlida: s'esperava una de ${v(t.values," o ")}`;case"too_big":{let r=t.inclusive?"com a màxim":"menys de",a=n(t.origin);return a?`Massa gran: s'esperava que ${t.origin??"el valor"} contingués ${r} ${t.maximum.toString()} ${a.unit??"elements"}`:`Massa gran: s'esperava que ${t.origin??"el valor"} fos ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?"com a mínim":"més de",a=n(t.origin);return a?`Massa petit: s'esperava que ${t.origin} contingués ${r} ${t.minimum.toString()} ${a.unit}`:`Massa petit: s'esperava que ${t.origin} fos ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Format invàlid: ha de començar amb "${r.prefix}"`:r.format==="ends_with"?`Format invàlid: ha d'acabar amb "${r.suffix}"`:r.format==="includes"?`Format invàlid: ha d'incloure "${r.includes}"`:r.format==="regex"?`Format invàlid: ha de coincidir amb el patró ${r.pattern}`:`Format invàlid per a ${i[r.format]??t.format}`}case"not_multiple_of":return`Número invàlid: ha de ser múltiple de ${t.divisor}`;case"unrecognized_keys":return`Clau${t.keys.length>1?"s":""} no reconeguda${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Clau invàlida a ${t.origin}`;case"invalid_union":return"Entrada invàlida";case"invalid_element":return`Element invàlid a ${t.origin}`;default:return"Entrada invàlida"}}};function Lm(){return{localeError:M_()}}var Z_=()=>{let e={string:{unit:"پیت",verb:"بێت"},file:{unit:"بایت",verb:"بێت"},array:{unit:"دانە",verb:"بێت"},set:{unit:"دانە",verb:"بێت"},map:{unit:"دانە",verb:"بێت"}};function n(t){return e[t]??null}let i={regex:"regex",email:"ئیمەیڵ",url:"بەستەر (URL)",emoji:"ئیمۆجی",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ڕێکەوت و کات",date:"ڕێکەوت",time:"کات",duration:"ماوە",ipv4:"ناونیشانی IPv4",ipv6:"ناونیشانی IPv6",mac:"ناونیشانی MAC",cidrv4:"مەودای IPv4",cidrv6:"مەودای IPv6",base64:"دەقی base64",base64url:"دەقی base64url",json_string:"دەقی JSON",e164:"ژمارەی E.164",credit_card:"ژمارەی کارتی کرێدیت",currency_code:"کۆدی دراو",iban:"IBAN",jwt:"JWT",template_literal:"تێکردە"},o={nan:"NaN",string:"نووسین",number:"ژمارە",boolean:"boolean",array:"array",object:"object",date:"ڕێکەوت",integer:"ژمارە",float:"ژمارە",null:"null",undefined:"undefined",function:"function",symbol:"symbol",unknown:"unknown",promise:"promise",void:"void",never:"never",map:"map",set:"set"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a,c=["ا","و","ۆ","وو","ە","ی","ێ"].some(d=>s.endsWith(d))?"یە":"ە",u=/^[a-zA-Z]+$/.test(s);return a==="null"||a==="undefined"?"داواکراوە":`چاوەڕوانکراوە ${r} بێت، بەڵام ${s}${u?"":c}`}case"invalid_value":return t.values.length===1?`بەهاکە نادرووستە: چاوەڕوانکراوە ${$(t.values[0])} بێت`:`هەڵبژاردەی نادروست: چاوەڕوانکراوە یەکێک بێت لە ${v(t.values,"|")}`;case"too_big":{let r=n(t.origin);return r?`پێویستە بە لایەنی زۆرەوە ${t.maximum.toString()} ${r.unit} ${r.verb}`:`پێویستە بە لایەنی زۆرەوە ${t.maximum.toString()} بێت`}case"too_small":{let r=n(t.origin);return r?`پێویستە بە لایەنی کەمەوە ${t.minimum.toString()} ${r.unit} ${r.verb}`:`پێویستە بە لایەنی کەمەوە ${t.minimum.toString()} بێت`}case"invalid_format":{let r=t;return r.format==="starts_with"?`دەقی نادروست: پێویستە دەستپێبکات بە "${r.prefix}"`:r.format==="ends_with"?`دەقی نادروست: پێویستە کۆتاییبێت بە "${r.suffix}"`:r.format==="includes"?`دەقی نادروست: پێویستە "${r.includes}" لەخۆبگرێت`:r.format==="regex"?`دەقی نادروست: پێویستە لەگەڵ پاتێرنی ${r.pattern} بگونجێت`:`بەهای ${i[r.format]??t.format} نادروستە`}case"not_multiple_of":return`ژمارەی نادروست: دەبێت چەند هێندە بێت بۆ ${t.divisor}`;case"unrecognized_keys":return`کلیلی نەناسراو: ${v(t.keys,", ")}`;case"invalid_key":return`کلیلی نادروست لە ${t.origin}`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`بەهای نەناسراو هەیە. بەهای چاوەڕوانکراو: ${t.options.map(a=>`'${a}'`).join(" | ")}`:"یەکگرتنی نادروست";case"invalid_element":return`${t.origin} بەهاکە نادروستە`;default:return"تێکردەی نادروست"}}};function Um(){return{localeError:Z_()}}var V_=()=>{let e={string:{unit:"znaků",verb:"mít"},file:{unit:"bajtů",verb:"mít"},array:{unit:"prvků",verb:"mít"},set:{unit:"prvků",verb:"mít"},map:{unit:"prvků",verb:"mít"}};function n(t){return e[t]??null}let i={regex:"regulární výraz",email:"e-mailová adresa",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"datum a čas ve formátu ISO",date:"datum ve formátu ISO",time:"čas ve formátu ISO",duration:"doba trvání ISO",ipv4:"IPv4 adresa",ipv6:"IPv6 adresa",mac:"MAC adresa",cidrv4:"rozsah IPv4",cidrv6:"rozsah IPv6",base64:"řetězec zakódovaný ve formátu base64",base64url:"řetězec zakódovaný ve formátu base64url",json_string:"řetězec ve formátu JSON",e164:"číslo E.164",credit_card:"číslo kreditní karty",currency_code:"kód měny",iban:"IBAN",jwt:"JWT",template_literal:"vstup"},o={nan:"NaN",number:"číslo",string:"řetězec",function:"funkce",array:"pole"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neplatný vstup: očekáváno instanceof ${t.expected}, obdrženo ${s}`:`Neplatný vstup: očekáváno ${r}, obdrženo ${s}`}case"invalid_value":return t.values.length===1?`Neplatný vstup: očekáváno ${$(t.values[0])}`:`Neplatná možnost: očekávána jedna z hodnot ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Hodnota je příliš velká: ${t.origin??"hodnota"} musí mít ${r}${t.maximum.toString()} ${a.unit??"prvků"}`:`Hodnota je příliš velká: ${t.origin??"hodnota"} musí být ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Hodnota je příliš malá: ${t.origin??"hodnota"} musí mít ${r}${t.minimum.toString()} ${a.unit??"prvků"}`:`Hodnota je příliš malá: ${t.origin??"hodnota"} musí být ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neplatný řetězec: musí začínat na "${r.prefix}"`:r.format==="ends_with"?`Neplatný řetězec: musí končit na "${r.suffix}"`:r.format==="includes"?`Neplatný řetězec: musí obsahovat "${r.includes}"`:r.format==="regex"?`Neplatný řetězec: musí odpovídat vzoru ${r.pattern}`:`Neplatný formát ${i[r.format]??t.format}`}case"not_multiple_of":return`Neplatné číslo: musí být násobkem ${t.divisor}`;case"unrecognized_keys":return`Neznámé klíče: ${v(t.keys,", ")}`;case"invalid_key":return`Neplatný klíč v ${t.origin}`;case"invalid_union":return"Neplatný vstup";case"invalid_element":return`Neplatná hodnota v ${t.origin}`;default:return"Neplatný vstup"}}};function Rm(){return{localeError:V_()}}var G_=()=>{let e={string:{unit:"tegn",verb:"havde"},file:{unit:"bytes",verb:"havde"},array:{unit:"elementer",verb:"indeholdt"},set:{unit:"elementer",verb:"indeholdt"},map:{unit:"elementer",verb:"indeholdt"}};function n(t){return e[t]??null}let i={regex:"input",email:"e-mailadresse",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dato- og klokkeslæt",date:"ISO-dato",time:"ISO-klokkeslæt",duration:"ISO-varighed",ipv4:"IPv4-adresse",ipv6:"IPv6-adresse",mac:"MAC-adresse",cidrv4:"IPv4-spektrum",cidrv6:"IPv6-spektrum",base64:"base64-kodet streng",base64url:"base64url-kodet streng",json_string:"JSON-streng",e164:"E.164-nummer",credit_card:"kreditkortnummer",currency_code:"valutakode",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",string:"streng",number:"tal",boolean:"boolean",array:"liste",object:"objekt",set:"sæt",file:"fil"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ugyldigt input: forventede instanceof ${t.expected}, fik ${s}`:`Ugyldigt input: forventede ${r}, fik ${s}`}case"invalid_value":return t.values.length===1?`Ugyldig værdi: forventede ${$(t.values[0])}`:`Ugyldigt valg: forventede en af følgende ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=o[t.origin]??t.origin;return a?`For stor: forventede ${s??"value"} ${a.verb} ${r} ${t.maximum.toString()} ${a.unit??"elementer"}`:`For stor: forventede ${s??"value"} havde ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=o[t.origin]??t.origin;return a?`For lille: forventede ${s} ${a.verb} ${r} ${t.minimum.toString()} ${a.unit}`:`For lille: forventede ${s} havde ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ugyldig streng: skal starte med "${r.prefix}"`:r.format==="ends_with"?`Ugyldig streng: skal ende med "${r.suffix}"`:r.format==="includes"?`Ugyldig streng: skal indeholde "${r.includes}"`:r.format==="regex"?`Ugyldig streng: skal matche mønsteret ${r.pattern}`:`Ugyldig ${i[r.format]??t.format}`}case"not_multiple_of":return`Ugyldigt tal: skal være deleligt med ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Ukendte nøgler":"Ukendt nøgle"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ugyldig nøgle i ${t.origin}`;case"invalid_union":return"Ugyldigt input: matcher ingen af de tilladte typer";case"invalid_element":return`Ugyldig værdi i ${t.origin}`;default:return"Ugyldigt input"}}};function Mm(){return{localeError:G_()}}var F_=()=>{let e={string:{unit:"Zeichen",verb:"zu haben"},file:{unit:"Bytes",verb:"zu haben"},array:{unit:"Elemente",verb:"zu haben"},set:{unit:"Elemente",verb:"zu haben"},map:{unit:"Elemente",verb:"zu haben"}};function n(t){return e[t]??null}let i={regex:"Eingabe",email:"E-Mail-Adresse",url:"URL",emoji:"Emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-Datum und -Uhrzeit",date:"ISO-Datum",time:"ISO-Uhrzeit",duration:"ISO-Dauer",ipv4:"IPv4-Adresse",ipv6:"IPv6-Adresse",mac:"MAC-Adresse",cidrv4:"IPv4-Bereich",cidrv6:"IPv6-Bereich",base64:"Base64-codierter String",base64url:"Base64-URL-codierter String",json_string:"JSON-String",e164:"E.164-Nummer",credit_card:"Kreditkartennummer",currency_code:"Währungscode",iban:"IBAN",jwt:"JWT",template_literal:"Eingabe"},o={nan:"NaN",number:"Zahl",array:"Array"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ungültige Eingabe: erwartet instanceof ${t.expected}, erhalten ${s}`:`Ungültige Eingabe: erwartet ${r}, erhalten ${s}`}case"invalid_value":return t.values.length===1?`Ungültige Eingabe: erwartet ${$(t.values[0])}`:`Ungültige Option: erwartet eine von ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Zu groß: erwartet, dass ${t.origin??"Wert"} ${r}${t.maximum.toString()} ${a.unit??"Elemente"} hat`:`Zu groß: erwartet, dass ${t.origin??"Wert"} ${r}${t.maximum.toString()} ist`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Zu klein: erwartet, dass ${t.origin} ${r}${t.minimum.toString()} ${a.unit} hat`:`Zu klein: erwartet, dass ${t.origin} ${r}${t.minimum.toString()} ist`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ungültiger String: muss mit "${r.prefix}" beginnen`:r.format==="ends_with"?`Ungültiger String: muss mit "${r.suffix}" enden`:r.format==="includes"?`Ungültiger String: muss "${r.includes}" enthalten`:r.format==="regex"?`Ungültiger String: muss dem Muster ${r.pattern} entsprechen`:`Ungültig: ${i[r.format]??t.format}`}case"not_multiple_of":return`Ungültige Zahl: muss ein Vielfaches von ${t.divisor} sein`;case"unrecognized_keys":return`${t.keys.length>1?"Unbekannte Schlüssel":"Unbekannter Schlüssel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ungültiger Schlüssel in ${t.origin}`;case"invalid_union":return"Ungültige Eingabe";case"invalid_element":return`Ungültiger Wert in ${t.origin}`;default:return"Ungültige Eingabe"}}};function Zm(){return{localeError:F_()}}var B_=()=>{let e={string:{unit:"χαρακτήρες",verb:"να έχει"},file:{unit:"bytes",verb:"να έχει"},array:{unit:"στοιχεία",verb:"να έχει"},set:{unit:"στοιχεία",verb:"να έχει"},map:{unit:"καταχωρήσεις",verb:"να έχει"}};function n(t){return e[t]??null}let i={regex:"είσοδος",email:"διεύθυνση email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO ημερομηνία και ώρα",date:"ISO ημερομηνία",time:"ISO ώρα",duration:"ISO διάρκεια",ipv4:"διεύθυνση IPv4",ipv6:"διεύθυνση IPv6",mac:"διεύθυνση MAC",cidrv4:"εύρος IPv4",cidrv6:"εύρος IPv6",base64:"συμβολοσειρά κωδικοποιημένη σε base64",base64url:"συμβολοσειρά κωδικοποιημένη σε base64url",json_string:"συμβολοσειρά JSON",e164:"αριθμός E.164",credit_card:"αριθμός πιστωτικής κάρτας",currency_code:"κωδικός νομίσματος",iban:"IBAN",jwt:"JWT",template_literal:"είσοδος"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return typeof t.expected=="string"&&/^[A-Z]/.test(t.expected)?`Μη έγκυρη είσοδος: αναμενόταν instanceof ${t.expected}, λήφθηκε ${s}`:`Μη έγκυρη είσοδος: αναμενόταν ${r}, λήφθηκε ${s}`}case"invalid_value":return t.values.length===1?`Μη έγκυρη είσοδος: αναμενόταν ${$(t.values[0])}`:`Μη έγκυρη επιλογή: αναμενόταν ένα από ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Πολύ μεγάλο: αναμενόταν ${t.origin??"τιμή"} να έχει ${r}${t.maximum.toString()} ${a.unit??"στοιχεία"}`:`Πολύ μεγάλο: αναμενόταν ${t.origin??"τιμή"} να είναι ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Πολύ μικρό: αναμενόταν ${t.origin} να έχει ${r}${t.minimum.toString()} ${a.unit}`:`Πολύ μικρό: αναμενόταν ${t.origin} να είναι ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Μη έγκυρη συμβολοσειρά: πρέπει να ξεκινά με "${r.prefix}"`:r.format==="ends_with"?`Μη έγκυρη συμβολοσειρά: πρέπει να τελειώνει με "${r.suffix}"`:r.format==="includes"?`Μη έγκυρη συμβολοσειρά: πρέπει να περιέχει "${r.includes}"`:r.format==="regex"?`Μη έγκυρη συμβολοσειρά: πρέπει να ταιριάζει με το μοτίβο ${r.pattern}`:`Μη έγκυρο: ${i[r.format]??t.format}`}case"not_multiple_of":return`Μη έγκυρος αριθμός: πρέπει να είναι πολλαπλάσιο του ${t.divisor}`;case"unrecognized_keys":return`Άγνωστ${t.keys.length>1?"α":"ο"} κλειδ${t.keys.length>1?"ιά":"ί"}: ${v(t.keys,", ")}`;case"invalid_key":return`Μη έγκυρο κλειδί στο ${t.origin}`;case"invalid_union":return"Μη έγκυρη είσοδος";case"invalid_element":return`Μη έγκυρη τιμή στο ${t.origin}`;default:return"Μη έγκυρη είσοδος"}}};function Vm(){return{localeError:B_()}}var H_=()=>{let e={string:{unit:"characters",verb:"to have"},file:{unit:"bytes",verb:"to have"},array:{unit:"items",verb:"to have"},set:{unit:"items",verb:"to have"},map:{unit:"entries",verb:"to have"}};function n(r){return e[r]??null}let i={regex:"input",email:"email address",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datetime",date:"ISO date",time:"ISO time",duration:"ISO duration",ipv4:"IPv4 address",ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded string",base64url:"base64url-encoded string",json_string:"JSON string",e164:"E.164 number",currency_code:"currency code",credit_card:"credit card number",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN"};function t(r,a){return r==="number"&&typeof a=="number"&&!Number.isFinite(a)?String(a):o[r]??r}return r=>{switch(r.code){case"invalid_type":{let a=t(r.expected),s=k(r.input),c=t(s,r.input);return`Invalid input: expected ${a}, received ${c}`}case"invalid_value":return r.values.length===1?`Invalid input: expected ${$(r.values[0])}`:`Invalid option: expected one of ${v(r.values,"|")}`;case"too_big":{let a=r.exact?"exactly ":r.inclusive?"<=":"<",s=n(r.origin);return s?`Too big: expected ${r.origin??"value"} to have ${a}${r.maximum.toString()} ${s.unit??"elements"}`:`Too big: expected ${r.origin??"value"} to be ${a}${r.maximum.toString()}`}case"too_small":{let a=r.exact?"exactly ":r.inclusive?">=":">",s=n(r.origin);return s?`Too small: expected ${r.origin} to have ${a}${r.minimum.toString()} ${s.unit}`:`Too small: expected ${r.origin} to be ${a}${r.minimum.toString()}`}case"invalid_format":{let a=r;return a.format==="starts_with"?`Invalid string: must start with "${a.prefix}"`:a.format==="ends_with"?`Invalid string: must end with "${a.suffix}"`:a.format==="includes"?`Invalid string: must include "${a.includes}"`:a.format==="regex"?`Invalid string: must match pattern ${a.pattern}`:`Invalid ${i[a.format]??r.format}`}case"not_multiple_of":return`Invalid number: must be a multiple of ${r.divisor}`;case"unrecognized_keys":return`Unrecognized key${r.keys.length>1?"s":""}: ${v(r.keys,", ")}`;case"invalid_key":return`Invalid key in ${r.origin}`;case"invalid_union":return r.options&&Array.isArray(r.options)&&r.options.length>0?`Invalid discriminator value. Expected ${r.options.map(s=>`'${s}'`).join(" | ")}`:r.inclusive===!1?"Invalid input: more than one option matched":"Invalid input";case"invalid_element":return`Invalid value in ${r.origin}`;default:return"Invalid input"}}};function Wi(){return{localeError:H_()}}var K_=()=>{let e={string:{unit:"karaktrojn",verb:"havi"},file:{unit:"bajtojn",verb:"havi"},array:{unit:"elementojn",verb:"havi"},set:{unit:"elementojn",verb:"havi"},map:{unit:"elementojn",verb:"havi"}};function n(t){return e[t]??null}let i={regex:"enigo",email:"retadreso",url:"URL",emoji:"emoĝio",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-datotempo",date:"ISO-dato",time:"ISO-tempo",duration:"ISO-daŭro",ipv4:"IPv4-adreso",ipv6:"IPv6-adreso",mac:"MAC-adreso",cidrv4:"IPv4-rango",cidrv6:"IPv6-rango",base64:"64-ume kodita karaktraro",base64url:"URL-64-ume kodita karaktraro",json_string:"JSON-karaktraro",e164:"E.164-nombro",credit_card:"kreditkarta numero",currency_code:"valuta kodo",iban:"IBAN",jwt:"JWT",template_literal:"enigo"},o={nan:"NaN",number:"nombro",array:"tabelo",null:"senvalora"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Nevalida enigo: atendiĝis instanceof ${t.expected}, riceviĝis ${s}`:`Nevalida enigo: atendiĝis ${r}, riceviĝis ${s}`}case"invalid_value":return t.values.length===1?`Nevalida enigo: atendiĝis ${$(t.values[0])}`:`Nevalida opcio: atendiĝis unu el ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Tro granda: atendiĝis ke ${t.origin??"valoro"} havu ${r}${t.maximum.toString()} ${a.unit??"elementojn"}`:`Tro granda: atendiĝis ke ${t.origin??"valoro"} havu ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Tro malgranda: atendiĝis ke ${t.origin} havu ${r}${t.minimum.toString()} ${a.unit}`:`Tro malgranda: atendiĝis ke ${t.origin} estu ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Nevalida karaktraro: devas komenciĝi per "${r.prefix}"`:r.format==="ends_with"?`Nevalida karaktraro: devas finiĝi per "${r.suffix}"`:r.format==="includes"?`Nevalida karaktraro: devas inkluzivi "${r.includes}"`:r.format==="regex"?`Nevalida karaktraro: devas kongrui kun la modelo ${r.pattern}`:`Nevalida ${i[r.format]??t.format}`}case"not_multiple_of":return`Nevalida nombro: devas esti oblo de ${t.divisor}`;case"unrecognized_keys":return`Nekonata${t.keys.length>1?"j":""} ŝlosilo${t.keys.length>1?"j":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Nevalida ŝlosilo en ${t.origin}`;case"invalid_union":return"Nevalida enigo";case"invalid_element":return`Nevalida valoro en ${t.origin}`;default:return"Nevalida enigo"}}};function Gm(){return{localeError:K_()}}var W_=()=>{let e={string:{unit:"caracteres",verb:"tener"},file:{unit:"bytes",verb:"tener"},array:{unit:"elementos",verb:"tener"},set:{unit:"elementos",verb:"tener"}};function n(t){return e[t]??null}let i={regex:"entrada",email:"dirección de correo electrónico",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"fecha y hora ISO",date:"fecha ISO",time:"hora ISO",duration:"duración ISO",ipv4:"dirección IPv4",ipv6:"dirección IPv6",mac:"dirección MAC",cidrv4:"rango IPv4",cidrv6:"rango IPv6",base64:"cadena codificada en base64",base64url:"URL codificada en base64",json_string:"cadena JSON",e164:"número E.164",credit_card:"número de tarjeta de crédito",currency_code:"código de moneda",iban:"IBAN",jwt:"JWT",template_literal:"entrada"},o={nan:"NaN",string:"texto",number:"número",boolean:"booleano",array:"arreglo",object:"objeto",set:"conjunto",file:"archivo",date:"fecha",bigint:"número grande",symbol:"símbolo",undefined:"indefinido",null:"nulo",function:"función",map:"mapa",record:"registro",tuple:"tupla",enum:"enumeración",union:"unión",literal:"literal",promise:"promesa",void:"vacío",never:"nunca",unknown:"desconocido",any:"cualquiera"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Entrada inválida: se esperaba instanceof ${t.expected}, recibido ${s}`:`Entrada inválida: se esperaba ${r}, recibido ${s}`}case"invalid_value":return t.values.length===1?`Entrada inválida: se esperaba ${$(t.values[0])}`:`Opción inválida: se esperaba una de ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Demasiado grande: se esperaba que ${s??"valor"} tuviera ${r}${t.maximum.toString()} ${a.unit??"elementos"}`:`Demasiado grande: se esperaba que ${s??"valor"} fuera ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Demasiado pequeño: se esperaba que ${s} tuviera ${r}${t.minimum.toString()} ${a.unit}`:`Demasiado pequeño: se esperaba que ${s} fuera ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Cadena inválida: debe comenzar con "${r.prefix}"`:r.format==="ends_with"?`Cadena inválida: debe terminar en "${r.suffix}"`:r.format==="includes"?`Cadena inválida: debe incluir "${r.includes}"`:r.format==="regex"?`Cadena inválida: debe coincidir con el patrón ${r.pattern}`:`Inválido ${i[r.format]??t.format}`}case"not_multiple_of":return`Número inválido: debe ser múltiplo de ${t.divisor}`;case"unrecognized_keys":return`Llave${t.keys.length>1?"s":""} desconocida${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Llave inválida en ${o[t.origin]??t.origin}`;case"invalid_union":return"Entrada inválida";case"invalid_element":return`Valor inválido en ${o[t.origin]??t.origin}`;default:return"Entrada inválida"}}};function Fm(){return{localeError:W_()}}var J_=()=>{let e={string:{unit:"کاراکتر",verb:"داشته باشد"},file:{unit:"بایت",verb:"داشته باشد"},array:{unit:"آیتم",verb:"داشته باشد"},set:{unit:"آیتم",verb:"داشته باشد"},map:{unit:"آیتم",verb:"داشته باشد"}};function n(t){return e[t]??null}let i={regex:"ورودی",email:"آدرس ایمیل",url:"URL",emoji:"ایموجی",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"تاریخ و زمان ایزو",date:"تاریخ ایزو",time:"زمان ایزو",duration:"مدت زمان ایزو",ipv4:"IPv4 آدرس",ipv6:"IPv6 آدرس",mac:"MAC آدرس",cidrv4:"IPv4 دامنه",cidrv6:"IPv6 دامنه",base64:"base64-encoded رشته",base64url:"base64url-encoded رشته",json_string:"JSON رشته",e164:"E.164 عدد",credit_card:"شماره کارت اعتباری",currency_code:"کد ارز",iban:"IBAN",jwt:"JWT",template_literal:"ورودی"},o={nan:"NaN",number:"عدد",array:"آرایه"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ورودی نامعتبر: می‌بایست instanceof ${t.expected} می‌بود، ${s} دریافت شد`:`ورودی نامعتبر: می‌بایست ${r} می‌بود، ${s} دریافت شد`}case"invalid_value":return t.values.length===1?`ورودی نامعتبر: می‌بایست ${$(t.values[0])} می‌بود`:`گزینه نامعتبر: می‌بایست یکی از ${v(t.values,"|")} می‌بود`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`خیلی بزرگ: ${t.origin??"مقدار"} باید ${r}${t.maximum.toString()} ${a.unit??"عنصر"} باشد`:`خیلی بزرگ: ${t.origin??"مقدار"} باید ${r}${t.maximum.toString()} باشد`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`خیلی کوچک: ${t.origin} باید ${r}${t.minimum.toString()} ${a.unit} باشد`:`خیلی کوچک: ${t.origin} باید ${r}${t.minimum.toString()} باشد`}case"invalid_format":{let r=t;return r.format==="starts_with"?`رشته نامعتبر: باید با "${r.prefix}" شروع شود`:r.format==="ends_with"?`رشته نامعتبر: باید با "${r.suffix}" تمام شود`:r.format==="includes"?`رشته نامعتبر: باید شامل "${r.includes}" باشد`:r.format==="regex"?`رشته نامعتبر: باید با الگوی ${r.pattern} مطابقت داشته باشد`:`${i[r.format]??t.format} نامعتبر`}case"not_multiple_of":return`عدد نامعتبر: باید مضرب ${t.divisor} باشد`;case"unrecognized_keys":return`کلید${t.keys.length>1?"های":""} ناشناس: ${v(t.keys,", ")}`;case"invalid_key":return`کلید ناشناس در ${t.origin}`;case"invalid_union":return"ورودی نامعتبر";case"invalid_element":return`مقدار نامعتبر در ${t.origin}`;default:return"ورودی نامعتبر"}}};function Bm(){return{localeError:J_()}}var X_=()=>{let e={string:{unit:"merkkiä",subject:"merkkijonon"},file:{unit:"tavua",subject:"tiedoston"},array:{unit:"alkiota",subject:"listan"},set:{unit:"alkiota",subject:"joukon"},map:{unit:"alkiota",subject:"kuvauksen"},number:{unit:"",subject:"luvun"},bigint:{unit:"",subject:"suuren kokonaisluvun"},int:{unit:"",subject:"kokonaisluvun"},date:{unit:"",subject:"päivämäärän"}};function n(t){return e[t]??null}let i={regex:"säännöllinen lauseke",email:"sähköpostiosoite",url:"URL-osoite",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-aikaleima",date:"ISO-päivämäärä",time:"ISO-aika",duration:"ISO-kesto",ipv4:"IPv4-osoite",ipv6:"IPv6-osoite",mac:"MAC-osoite",cidrv4:"IPv4-alue",cidrv6:"IPv6-alue",base64:"base64-koodattu merkkijono",base64url:"base64url-koodattu merkkijono",json_string:"JSON-merkkijono",e164:"E.164-luku",credit_card:"luottokortin numero",currency_code:"valuuttakoodi",iban:"IBAN",jwt:"JWT",template_literal:"templaattimerkkijono"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Virheellinen tyyppi: odotettiin instanceof ${t.expected}, oli ${s}`:`Virheellinen tyyppi: odotettiin ${r}, oli ${s}`}case"invalid_value":return t.values.length===1?`Virheellinen syöte: täytyy olla ${$(t.values[0])}`:`Virheellinen valinta: täytyy olla yksi seuraavista: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Liian suuri: ${a.subject} täytyy olla ${r}${t.maximum.toString()} ${a.unit}`.trim():`Liian suuri: arvon täytyy olla ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Liian pieni: ${a.subject} täytyy olla ${r}${t.minimum.toString()} ${a.unit}`.trim():`Liian pieni: arvon täytyy olla ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Virheellinen syöte: täytyy alkaa "${r.prefix}"`:r.format==="ends_with"?`Virheellinen syöte: täytyy loppua "${r.suffix}"`:r.format==="includes"?`Virheellinen syöte: täytyy sisältää "${r.includes}"`:r.format==="regex"?`Virheellinen syöte: täytyy vastata säännöllistä lauseketta ${r.pattern}`:`Virheellinen ${i[r.format]??t.format}`}case"not_multiple_of":return`Virheellinen luku: täytyy olla luvun ${t.divisor} monikerta`;case"unrecognized_keys":return`${t.keys.length>1?"Tuntemattomat avaimet":"Tuntematon avain"}: ${v(t.keys,", ")}`;case"invalid_key":return"Virheellinen avain tietueessa";case"invalid_union":return"Virheellinen unioni";case"invalid_element":return"Virheellinen arvo joukossa";default:return"Virheellinen syöte"}}};function Hm(){return{localeError:X_()}}var q_=()=>{let e={string:{unit:"caractères",verb:"avoir"},file:{unit:"octets",verb:"avoir"},array:{unit:"éléments",verb:"avoir"},set:{unit:"éléments",verb:"avoir"}};function n(t){return e[t]??null}let i={regex:"expression régulière",email:"adresse e-mail",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"date et heure ISO",date:"date ISO",time:"heure ISO",duration:"durée ISO",ipv4:"adresse IPv4",ipv6:"adresse IPv6",mac:"adresse MAC",cidrv4:"plage IPv4",cidrv6:"plage IPv6",base64:"chaîne de caractères encodée en base64",base64url:"chaîne de caractères encodée en base64url",json_string:"chaîne de caractères JSON",e164:"numéro au format E.164",credit_card:"numéro de carte de crédit",currency_code:"code de devise",iban:"IBAN",jwt:"JWT",template_literal:"entrée"},o={string:"chaîne de caractères",number:"nombre",int:"entier",boolean:"booléen",bigint:"grand entier",symbol:"symbole",undefined:"indéfini",null:"null",never:"jamais",void:"vide",date:"date",array:"tableau",object:"objet",tuple:"tuple",record:"record",map:"map",set:"ensemble",file:"fichier",nonoptional:"non optionnel",nan:"NaN",function:"fonction"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Entrée invalide : instance de ${t.expected} attendu, ${s} reçu`:`Entrée invalide : ${r} attendu, ${s} reçu`}case"invalid_value":return t.values.length===1?`Entrée invalide : ${$(t.values[0])} attendu`:`Option invalide : une valeur parmi ${v(t.values,"|")} attendue`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Trop grand : ${o[t.origin]??"valeur"} doit ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"élément(s)"}`:`Trop grand : ${o[t.origin]??"valeur"} doit être ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Trop petit : ${o[t.origin]??"valeur"} doit ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Trop petit : ${o[t.origin]??"valeur"} doit être ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Chaîne de caractères invalide : doit commencer par "${r.prefix}"`:r.format==="ends_with"?`Chaîne de caractères invalide : doit se terminer par "${r.suffix}"`:r.format==="includes"?`Chaîne de caractères invalide : doit inclure "${r.includes}"`:r.format==="regex"?`Chaîne de caractères invalide : doit correspondre au motif ${r.pattern}`:`${i[r.format]??t.format} invalide`}case"not_multiple_of":return`Nombre invalide : doit être un multiple de ${t.divisor}`;case"unrecognized_keys":return`Clé${t.keys.length>1?"s":""} non reconnue${t.keys.length>1?"s":""} : ${v(t.keys,", ")}`;case"invalid_key":return`Clé invalide dans ${t.origin}`;case"invalid_union":return"Entrée invalide";case"invalid_element":return`Valeur invalide dans ${t.origin}`;default:return"Entrée invalide"}}};function Km(){return{localeError:q_()}}var Y_=()=>{let e={string:{unit:"caractères",verb:"avoir"},file:{unit:"octets",verb:"avoir"},array:{unit:"éléments",verb:"avoir"},set:{unit:"éléments",verb:"avoir"},map:{unit:"éléments",verb:"avoir"}};function n(t){return e[t]??null}let i={regex:"entrée",email:"adresse courriel",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"date-heure ISO",date:"date ISO",time:"heure ISO",duration:"durée ISO",ipv4:"adresse IPv4",ipv6:"adresse IPv6",mac:"adresse MAC",cidrv4:"plage IPv4",cidrv6:"plage IPv6",base64:"chaîne encodée en base64",base64url:"chaîne encodée en base64url",json_string:"chaîne JSON",e164:"numéro E.164",credit_card:"numéro de carte de crédit",currency_code:"code de devise",iban:"IBAN",jwt:"JWT",template_literal:"entrée"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Entrée invalide : attendu instanceof ${t.expected}, reçu ${s}`:`Entrée invalide : attendu ${r}, reçu ${s}`}case"invalid_value":return t.values.length===1?`Entrée invalide : attendu ${$(t.values[0])}`:`Option invalide : attendu l'une des valeurs suivantes ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"≤":"<",a=n(t.origin);return a?`Trop grand : attendu que ${t.origin??"la valeur"} ait ${r}${t.maximum.toString()} ${a.unit}`:`Trop grand : attendu que ${t.origin??"la valeur"} soit ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?"≥":">",a=n(t.origin);return a?`Trop petit : attendu que ${t.origin} ait ${r}${t.minimum.toString()} ${a.unit}`:`Trop petit : attendu que ${t.origin} soit ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Chaîne invalide : doit commencer par "${r.prefix}"`:r.format==="ends_with"?`Chaîne invalide : doit se terminer par "${r.suffix}"`:r.format==="includes"?`Chaîne invalide : doit inclure "${r.includes}"`:r.format==="regex"?`Chaîne invalide : doit correspondre au motif ${r.pattern}`:`${i[r.format]??t.format} invalide`}case"not_multiple_of":return`Nombre invalide : doit être un multiple de ${t.divisor}`;case"unrecognized_keys":return`Clé${t.keys.length>1?"s":""} non reconnue${t.keys.length>1?"s":""} : ${v(t.keys,", ")}`;case"invalid_key":return`Clé invalide dans ${t.origin}`;case"invalid_union":return"Entrée invalide";case"invalid_element":return`Valeur invalide dans ${t.origin}`;default:return"Entrée invalide"}}};function Wm(){return{localeError:Y_()}}var Q_=()=>{let e={string:{unit:"અક્ષર",verb:"હોવા જોઈએ"},file:{unit:"બાયટ",verb:"હોવા જોઈએ"},array:{unit:"આઇટમ",verb:"હોવા જોઈએ"},set:{unit:"આઇટમ",verb:"હોવા જોઈએ"},map:{unit:"એન્ટ્રી",verb:"હોવા જોઈએ"}};function n(t){return e[t]??null}let i={regex:"ઇનપુટ",email:"ઈમેઇલ એડ્રેસ",url:"URL",emoji:"ઇમોજી",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO તારીખ અને સમય",date:"ISO તારીખ",time:"ISO સમય",duration:"ISO અવધિ",ipv4:"IPv4 એડ્રેસ",ipv6:"IPv6 એડ્રેસ",mac:"MAC એડ્રેસ",cidrv4:"IPv4 શ્રેણી",cidrv6:"IPv6 શ્રેણી",base64:"base64-એન્કોડેડ સ્ટ્રિંગ",base64url:"base64url-એન્કોડેડ સ્ટ્રિંગ",json_string:"JSON સ્ટ્રિંગ",e164:"E.164 નંબર",credit_card:"ક્રેડિટ કાર્ડ નંબર",currency_code:"ચલણ કોડ",iban:"IBAN",jwt:"JWT",template_literal:"ઇનપુટ"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`અમાન્ય ઇનપુટ: અપેક્ષિત ${r}, પ્રાપ્ત ${s}`}case"invalid_value":return t.values.length===1?`અમાન્ય ઇનપુટ: અપેક્ષિત ${$(t.values[0])}`:`અમાન્ય વિકલ્પ: ${v(t.values," | ")} માધ્યમથી એક અપેક્ષિત`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ખૂબ મોટું: ${t.origin??"મૂલ્ય"} ${r}${t.maximum.toString()} ${a.unit??"એલિમેન્ટ"} હોવા જોઈએ`:`ખૂબ મોટું: ${t.origin??"મૂલ્ય"} ${r}${t.maximum.toString()} હોવું જોઈએ`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ખૂબ નાનું: ${t.origin} ${r}${t.minimum.toString()} ${a.unit} હોવા જોઈએ`:`ખૂબ નાનું: ${t.origin} ${r}${t.minimum.toString()} હોવું જોઈએ`}case"invalid_format":{let r=t;return r.format==="starts_with"?`અમાન્ય સ્ટ્રિંગ: "${r.prefix}" થી શરૂ થવું જોઈએ`:r.format==="ends_with"?`અમાન્ય સ્ટ્રિંગ: "${r.suffix}" પર સમાપ્ત થવું જોઈએ`:r.format==="includes"?`અમાન્ય સ્ટ્રિંગ: "${r.includes}" શામેલ હોવું જોઈએ`:r.format==="regex"?`અમાન્ય સ્ટ્રિંગ: પેટર્ન ${r.pattern} સાથે મેળ ખાવું જોઈએ`:`અમાન્ય ${i[r.format]??t.format}`}case"not_multiple_of":return`અમાન્ય નંબર: ${t.divisor} નો ગુણાંક હોવો જોઈએ`;case"unrecognized_keys":return`ઓળખી શકાતા નહીં તે કી${t.keys.length>1?"ઓ":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} માં અમાન્ય કી`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`અમાન્ય ડિસ્ક્રિમિનેટર મૂલ્ય. અપેક્ષિત ${t.options.map(a=>`'${a}'`).join(" | ")}`:"અમાન્ય ઇનપુટ";case"invalid_element":return`${t.origin} માં અમાન્ય મૂલ્ય`;default:return"અમાન્ય ઇનપુટ"}}};function Jm(){return{localeError:Q_()}}var e$=()=>{let e={string:{label:"מחרוזת",gender:"f"},number:{label:"מספר",gender:"m"},boolean:{label:"ערך בוליאני",gender:"m"},bigint:{label:"BigInt",gender:"m"},date:{label:"תאריך",gender:"m"},array:{label:"מערך",gender:"m"},object:{label:"אובייקט",gender:"m"},null:{label:"ערך ריק (null)",gender:"m"},undefined:{label:"ערך לא מוגדר (undefined)",gender:"m"},symbol:{label:"סימבול (Symbol)",gender:"m"},function:{label:"פונקציה",gender:"f"},map:{label:"מפה (Map)",gender:"f"},set:{label:"קבוצה (Set)",gender:"f"},file:{label:"קובץ",gender:"m"},promise:{label:"Promise",gender:"m"},NaN:{label:"NaN",gender:"m"},unknown:{label:"ערך לא ידוע",gender:"m"},value:{label:"ערך",gender:"m"}},n={string:{unit:"תווים",shortLabel:"קצר",longLabel:"ארוך"},file:{unit:"בייטים",shortLabel:"קטן",longLabel:"גדול"},array:{unit:"פריטים",shortLabel:"קטן",longLabel:"גדול"},set:{unit:"פריטים",shortLabel:"קטן",longLabel:"גדול"},number:{unit:"",shortLabel:"קטן",longLabel:"גדול"}},i=u=>u?e[u]:void 0,o=u=>{let d=i(u);return d?d.label:u??e.unknown.label},t=u=>`ה${o(u)}`,r=u=>(i(u)?.gender??"m")==="f"?"צריכה להיות":"צריך להיות",a=u=>u?n[u]??null:null,s={regex:{label:"קלט",gender:"m"},email:{label:"כתובת אימייל",gender:"f"},url:{label:"כתובת רשת",gender:"f"},emoji:{label:"אימוג'י",gender:"m"},uuid:{label:"UUID",gender:"m"},uuidv4:{label:"UUIDv4",gender:"m"},uuidv6:{label:"UUIDv6",gender:"m"},nanoid:{label:"nanoid",gender:"m"},guid:{label:"GUID",gender:"m"},cuid:{label:"cuid",gender:"m"},cuid2:{label:"cuid2",gender:"m"},ulid:{label:"ULID",gender:"m"},xid:{label:"XID",gender:"m"},ksuid:{label:"KSUID",gender:"m"},datetime:{label:"תאריך וזמן ISO",gender:"m"},date:{label:"תאריך ISO",gender:"m"},time:{label:"זמן ISO",gender:"m"},duration:{label:"משך זמן ISO",gender:"m"},ipv4:{label:"כתובת IPv4",gender:"f"},ipv6:{label:"כתובת IPv6",gender:"f"},mac:{label:"כתובת MAC",gender:"f"},cidrv4:{label:"טווח IPv4",gender:"m"},cidrv6:{label:"טווח IPv6",gender:"m"},base64:{label:"מחרוזת בבסיס 64",gender:"f"},base64url:{label:"מחרוזת בבסיס 64 לכתובות רשת",gender:"f"},json_string:{label:"מחרוזת JSON",gender:"f"},e164:{label:"מספר E.164",gender:"m"},credit_card:{label:"מספר כרטיס אשראי",gender:"m"},currency_code:{label:"קוד מטבע",gender:"m"},iban:{label:"IBAN",gender:"m"},jwt:{label:"JWT",gender:"m"},template_literal:{label:"קלט",gender:"m"},ends_with:{label:"קלט",gender:"m"},includes:{label:"קלט",gender:"m"},lowercase:{label:"קלט",gender:"m"},starts_with:{label:"קלט",gender:"m"},uppercase:{label:"קלט",gender:"m"}},c={nan:"NaN"};return u=>{switch(u.code){case"invalid_type":{let d=u.expected,p=c[d??""]??o(d),m=k(u.input),f=c[m]??e[m]?.label??m;return/^[A-Z]/.test(u.expected)?`קלט לא תקין: צריך להיות instanceof ${u.expected}, התקבל ${f}`:`קלט לא תקין: צריך להיות ${p}, התקבל ${f}`}case"invalid_value":{if(u.values.length===1)return`ערך לא תקין: הערך חייב להיות ${$(u.values[0])}`;let d=u.values.map(f=>$(f));if(u.values.length===2)return`ערך לא תקין: האפשרויות המתאימות הן ${d[0]} או ${d[1]}`;let p=d[d.length-1];return`ערך לא תקין: האפשרויות המתאימות הן ${d.slice(0,-1).join(", ")} או ${p}`}case"too_big":{let d=a(u.origin),p=t(u.origin??"value");if(u.origin==="string")return`${d?.longLabel??"ארוך"} מדי: ${p} צריכה להכיל ${u.maximum.toString()} ${d?.unit??""} ${u.inclusive?"או פחות":"לכל היותר"}`.trim();if(u.origin==="number"){let g=u.inclusive?`קטן או שווה ל-${u.maximum}`:`קטן מ-${u.maximum}`;return`גדול מדי: ${p} צריך להיות ${g}`}if(u.origin==="array"||u.origin==="set"){let g=u.origin==="set"?"צריכה":"צריך",y=u.inclusive?`${u.maximum} ${d?.unit??""} או פחות`:`פחות מ-${u.maximum} ${d?.unit??""}`;return`גדול מדי: ${p} ${g} להכיל ${y}`.trim()}let m=u.inclusive?"<=":"<",f=r(u.origin??"value");return d?.unit?`${d.longLabel} מדי: ${p} ${f} ${m}${u.maximum.toString()} ${d.unit}`:`${d?.longLabel??"גדול"} מדי: ${p} ${f} ${m}${u.maximum.toString()}`}case"too_small":{let d=a(u.origin),p=t(u.origin??"value");if(u.origin==="string")return`${d?.shortLabel??"קצר"} מדי: ${p} צריכה להכיל ${u.minimum.toString()} ${d?.unit??""} ${u.inclusive?"או יותר":"לפחות"}`.trim();if(u.origin==="number"){let g=u.inclusive?`גדול או שווה ל-${u.minimum}`:`גדול מ-${u.minimum}`;return`קטן מדי: ${p} צריך להיות ${g}`}if(u.origin==="array"||u.origin==="set"){let g=u.origin==="set"?"צריכה":"צריך";if(u.minimum===1&&u.inclusive){let x=(u.origin==="set","לפחות פריט אחד");return`קטן מדי: ${p} ${g} להכיל ${x}`}let y=u.inclusive?`${u.minimum} ${d?.unit??""} או יותר`:`יותר מ-${u.minimum} ${d?.unit??""}`;return`קטן מדי: ${p} ${g} להכיל ${y}`.trim()}let m=u.inclusive?">=":">",f=r(u.origin??"value");return d?.unit?`${d.shortLabel} מדי: ${p} ${f} ${m}${u.minimum.toString()} ${d.unit}`:`${d?.shortLabel??"קטן"} מדי: ${p} ${f} ${m}${u.minimum.toString()}`}case"invalid_format":{let d=u;if(d.format==="starts_with")return`המחרוזת חייבת להתחיל ב "${d.prefix}"`;if(d.format==="ends_with")return`המחרוזת חייבת להסתיים ב "${d.suffix}"`;if(d.format==="includes")return`המחרוזת חייבת לכלול "${d.includes}"`;if(d.format==="regex")return`המחרוזת חייבת להתאים לתבנית ${d.pattern}`;let p=s[d.format],m=p?.label??d.format,g=(p?.gender??"m")==="f"?"תקינה":"תקין";return`${m} לא ${g}`}case"not_multiple_of":return`מספר לא תקין: חייב להיות מכפלה של ${u.divisor}`;case"unrecognized_keys":return`מפתח${u.keys.length>1?"ות":""} לא מזוה${u.keys.length>1?"ים":"ה"}: ${v(u.keys,", ")}`;case"invalid_key":return"שדה לא תקין באובייקט";case"invalid_union":return"קלט לא תקין";case"invalid_element":return`ערך לא תקין ב${t(u.origin??"array")}`;default:return"קלט לא תקין"}}};function Xm(){return{localeError:e$()}}var t$=()=>{let e={string:{unit:"अक्षर",verb:"रखने के लिए"},file:{unit:"बाइट्स",verb:"रखने के लिए"},array:{unit:"तत्व",verb:"रखने के लिए"},set:{unit:"तत्व",verb:"रखने के लिए"},map:{unit:"प्रविष्टियाँ",verb:"रखने के लिए"}};function n(t){return e[t]??null}let i={regex:"इनपुट",email:"ईमेल पता",url:"URL",emoji:"इमोजी",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO तिथि और समय",date:"ISO तिथि",time:"ISO समय",duration:"ISO अवधि",ipv4:"IPv4 पता",ipv6:"IPv6 पता",mac:"MAC पता",cidrv4:"IPv4 श्रेणी",cidrv6:"IPv6 श्रेणी",base64:"Base64-एन्कोडेड स्ट्रिंग",base64url:"Base64URL-एन्कोडेड स्ट्रिंग",json_string:"JSON स्ट्रिंग",e164:"E.164 संख्या",credit_card:"क्रेडिट कार्ड संख्या",currency_code:"मुद्रा कोड",iban:"IBAN",jwt:"JWT",template_literal:"इनपुट"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`अमान्य इनपुट: अपेक्षित ${r}, प्राप्त ${s}`}case"invalid_value":return t.values.length===1?`अमान्य इनपुट: अपेक्षित ${$(t.values[0])}`:`अमान्य विकल्प: अपेक्षित मानों में से एक ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`बहुत बड़ा: अपेक्षित था कि ${t.origin??"मान"} में ${r}${t.maximum} ${a.unit} हों`:`बहुत बड़ा: अपेक्षित था कि ${t.origin??"मान"} ${r}${t.maximum} हो`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`बहुत छोटा: अपेक्षित था कि ${t.origin} में ${r}${t.minimum} ${a.unit} हों`:`बहुत छोटा: अपेक्षित था कि ${t.origin} ${r}${t.minimum} हो`}case"invalid_format":{let r=t;return r.format==="starts_with"?`अमान्य स्ट्रिंग: "${r.prefix}" से शुरू होना चाहिए`:r.format==="ends_with"?`अमान्य स्ट्रिंग: "${r.suffix}" पर समाप्त होना चाहिए`:r.format==="includes"?`अमान्य स्ट्रिंग: इसमें "${r.includes}" शामिल होना चाहिए`:r.format==="regex"?`अमान्य स्ट्रिंग: पैटर्न ${r.pattern} से मेल खाना चाहिए`:`अमान्य ${i[r.format]??t.format}`}case"not_multiple_of":return`अमान्य संख्या: यह ${t.divisor} का गुणज होना चाहिए`;case"unrecognized_keys":return`अपरिचित कुंजी${t.keys.length>1?"याँ":""}: ${v(t.keys,", ")}`;case"invalid_key":return`अमान्य कुंजी: ${t.origin} में`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`अमान्य डिस्क्रिमिनेटर मान: अपेक्षित ${t.options.map(a=>`'${a}'`).join(" | ")}`:"अमान्य इनपुट";case"invalid_element":return`अमान्य मान: ${t.origin} में`;default:return"अमान्य इनपुट"}}};function qm(){return{localeError:t$()}}var n$=()=>{let e={string:{unit:"znakova",verb:"imati"},file:{unit:"bajtova",verb:"imati"},array:{unit:"stavki",verb:"imati"},set:{unit:"stavki",verb:"imati"}};function n(t){return e[t]??null}let i={regex:"unos",email:"email adresa",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datum i vrijeme",date:"ISO datum",time:"ISO vrijeme",duration:"ISO trajanje",ipv4:"IPv4 adresa",ipv6:"IPv6 adresa",mac:"MAC adresa",cidrv4:"IPv4 raspon",cidrv6:"IPv6 raspon",base64:"base64 kodirani tekst",base64url:"base64url kodirani tekst",json_string:"JSON tekst",e164:"E.164 broj",credit_card:"broj kreditne kartice",currency_code:"kod valute",iban:"IBAN",jwt:"JWT",template_literal:"unos"},o={nan:"NaN",string:"tekst",number:"broj",boolean:"boolean",array:"niz",object:"objekt",set:"skup",file:"datoteka",date:"datum",bigint:"bigint",symbol:"simbol",undefined:"undefined",null:"null",function:"funkcija",map:"mapa"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neispravan unos: očekuje se instanceof ${t.expected}, a primljeno je ${s}`:`Neispravan unos: očekuje se ${r}, a primljeno je ${s}`}case"invalid_value":return t.values.length===1?`Neispravna vrijednost: očekivano ${$(t.values[0])}`:`Neispravna opcija: očekivano jedno od ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Preveliko: očekivano da ${s??"vrijednost"} ima ${r}${t.maximum.toString()} ${a.unit??"elemenata"}`:`Preveliko: očekivano da ${s??"vrijednost"} bude ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Premalo: očekivano da ${s} ima ${r}${t.minimum.toString()} ${a.unit}`:`Premalo: očekivano da ${s} bude ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neispravan tekst: mora započinjati s "${r.prefix}"`:r.format==="ends_with"?`Neispravan tekst: mora završavati s "${r.suffix}"`:r.format==="includes"?`Neispravan tekst: mora sadržavati "${r.includes}"`:r.format==="regex"?`Neispravan tekst: mora odgovarati uzorku ${r.pattern}`:`Neispravna ${i[r.format]??t.format}`}case"not_multiple_of":return`Neispravan broj: mora biti višekratnik od ${t.divisor}`;case"unrecognized_keys":return`Neprepoznat${t.keys.length>1?"i ključevi":" ključ"}: ${v(t.keys,", ")}`;case"invalid_key":return`Neispravan ključ u ${o[t.origin]??t.origin}`;case"invalid_union":return"Neispravan unos";case"invalid_element":return`Neispravna vrijednost u ${o[t.origin]??t.origin}`;default:return"Neispravan unos"}}};function Ym(){return{localeError:n$()}}var r$=()=>{let e={string:{unit:"karakter",verb:"legyen"},file:{unit:"byte",verb:"legyen"},array:{unit:"elem",verb:"legyen"},set:{unit:"elem",verb:"legyen"},map:{unit:"elem",verb:"legyen"}};function n(t){return e[t]??null}let i={regex:"bemenet",email:"email cím",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO időbélyeg",date:"ISO dátum",time:"ISO idő",duration:"ISO időintervallum",ipv4:"IPv4 cím",ipv6:"IPv6 cím",mac:"MAC cím",cidrv4:"IPv4 tartomány",cidrv6:"IPv6 tartomány",base64:"base64-kódolt string",base64url:"base64url-kódolt string",json_string:"JSON string",e164:"E.164 szám",credit_card:"hitelkártyaszám",currency_code:"pénznemkód",iban:"IBAN",jwt:"JWT",template_literal:"bemenet"},o={nan:"NaN",number:"szám",array:"tömb"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Érvénytelen bemenet: a várt érték instanceof ${t.expected}, a kapott érték ${s}`:`Érvénytelen bemenet: a várt érték ${r}, a kapott érték ${s}`}case"invalid_value":return t.values.length===1?`Érvénytelen bemenet: a várt érték ${$(t.values[0])}`:`Érvénytelen opció: valamelyik érték várt ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Túl nagy: ${t.origin??"érték"} mérete túl nagy ${r}${t.maximum.toString()} ${a.unit??"elem"}`:`Túl nagy: a bemeneti érték ${t.origin??"érték"} túl nagy: ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Túl kicsi: a bemeneti érték ${t.origin} mérete túl kicsi ${r}${t.minimum.toString()} ${a.unit}`:`Túl kicsi: a bemeneti érték ${t.origin} túl kicsi ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Érvénytelen string: "${r.prefix}" értékkel kell kezdődnie`:r.format==="ends_with"?`Érvénytelen string: "${r.suffix}" értékkel kell végződnie`:r.format==="includes"?`Érvénytelen string: "${r.includes}" értéket kell tartalmaznia`:r.format==="regex"?`Érvénytelen string: ${r.pattern} mintának kell megfelelnie`:`Érvénytelen ${i[r.format]??t.format}`}case"not_multiple_of":return`Érvénytelen szám: ${t.divisor} többszörösének kell lennie`;case"unrecognized_keys":return`Ismeretlen kulcs${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Érvénytelen kulcs ${t.origin}`;case"invalid_union":return"Érvénytelen bemenet";case"invalid_element":return`Érvénytelen érték: ${t.origin}`;default:return"Érvénytelen bemenet"}}};function Qm(){return{localeError:r$()}}function ef(e,n,i){return Math.abs(e)===1?n:i}function an(e){if(!e)return"";let n=["ա","ե","ը","ի","ո","ու","օ"],i=e[e.length-1];return e+(n.includes(i)?"ն":"ը")}var i$=()=>{let e={string:{unit:{one:"նշան",many:"նշաններ"},verb:"ունենալ"},file:{unit:{one:"բայթ",many:"բայթեր"},verb:"ունենալ"},array:{unit:{one:"տարր",many:"տարրեր"},verb:"ունենալ"},set:{unit:{one:"տարր",many:"տարրեր"},verb:"ունենալ"},map:{unit:{one:"տարր",many:"տարրեր"},verb:"ունենալ"}};function n(t){return e[t]??null}let i={regex:"մուտք",email:"էլ. հասցե",url:"URL",emoji:"էմոջի",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO ամսաթիվ և ժամ",date:"ISO ամսաթիվ",time:"ISO ժամ",duration:"ISO տևողություն",ipv4:"IPv4 հասցե",ipv6:"IPv6 հասցե",mac:"MAC հասցե",cidrv4:"IPv4 միջակայք",cidrv6:"IPv6 միջակայք",base64:"base64 ձևաչափով տող",base64url:"base64url ձևաչափով տող",json_string:"JSON տող",e164:"E.164 համար",credit_card:"կրեդիտ քարտի համար",currency_code:"արժույթի կոդ",iban:"IBAN",jwt:"JWT",template_literal:"մուտք"},o={nan:"NaN",number:"թիվ",array:"զանգված"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Սխալ մուտքագրում․ սպասվում էր instanceof ${t.expected}, ստացվել է ${s}`:`Սխալ մուտքագրում․ սպասվում էր ${r}, ստացվել է ${s}`}case"invalid_value":return t.values.length===1?`Սխալ մուտքագրում․ սպասվում էր ${$(t.values[1])}`:`Սխալ տարբերակ․ սպասվում էր հետևյալներից մեկը՝ ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);if(a){let s=Number(t.maximum),c=ef(s,a.unit.one,a.unit.many);return`Չափազանց մեծ արժեք․ սպասվում է, որ ${an(t.origin??"արժեք")} կունենա ${r}${t.maximum.toString()} ${c}`}return`Չափազանց մեծ արժեք․ սպասվում է, որ ${an(t.origin??"արժեք")} լինի ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);if(a){let s=Number(t.minimum),c=ef(s,a.unit.one,a.unit.many);return`Չափազանց փոքր արժեք․ սպասվում է, որ ${an(t.origin)} կունենա ${r}${t.minimum.toString()} ${c}`}return`Չափազանց փոքր արժեք․ սպասվում է, որ ${an(t.origin)} լինի ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Սխալ տող․ պետք է սկսվի "${r.prefix}"-ով`:r.format==="ends_with"?`Սխալ տող․ պետք է ավարտվի "${r.suffix}"-ով`:r.format==="includes"?`Սխալ տող․ պետք է պարունակի "${r.includes}"`:r.format==="regex"?`Սխալ տող․ պետք է համապատասխանի ${r.pattern} ձևաչափին`:`Սխալ ${i[r.format]??t.format}`}case"not_multiple_of":return`Սխալ թիվ․ պետք է բազմապատիկ լինի ${t.divisor}-ի`;case"unrecognized_keys":return`Չճանաչված բանալի${t.keys.length>1?"ներ":""}. ${v(t.keys,", ")}`;case"invalid_key":return`Սխալ բանալի ${an(t.origin)}-ում`;case"invalid_union":return"Սխալ մուտքագրում";case"invalid_element":return`Սխալ արժեք ${an(t.origin)}-ում`;default:return"Սխալ մուտքագրում"}}};function tf(){return{localeError:i$()}}var o$=()=>{let e={string:{unit:"karakter",verb:"memiliki"},file:{unit:"byte",verb:"memiliki"},array:{unit:"item",verb:"memiliki"},set:{unit:"item",verb:"memiliki"},map:{unit:"item",verb:"memiliki"}};function n(t){return e[t]??null}let i={regex:"input",email:"alamat email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"tanggal dan waktu format ISO",date:"tanggal format ISO",time:"jam format ISO",duration:"durasi format ISO",ipv4:"alamat IPv4",ipv6:"alamat IPv6",mac:"alamat MAC",cidrv4:"rentang alamat IPv4",cidrv6:"rentang alamat IPv6",base64:"string dengan enkode base64",base64url:"string dengan enkode base64url",json_string:"string JSON",e164:"angka E.164",credit_card:"nomor kartu kredit",currency_code:"kode mata uang",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Input tidak valid: diharapkan instanceof ${t.expected}, diterima ${s}`:`Input tidak valid: diharapkan ${r}, diterima ${s}`}case"invalid_value":return t.values.length===1?`Input tidak valid: diharapkan ${$(t.values[0])}`:`Pilihan tidak valid: diharapkan salah satu dari ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Terlalu besar: diharapkan ${t.origin??"value"} memiliki ${r}${t.maximum.toString()} ${a.unit??"elemen"}`:`Terlalu besar: diharapkan ${t.origin??"value"} menjadi ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Terlalu kecil: diharapkan ${t.origin} memiliki ${r}${t.minimum.toString()} ${a.unit}`:`Terlalu kecil: diharapkan ${t.origin} menjadi ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`String tidak valid: harus dimulai dengan "${r.prefix}"`:r.format==="ends_with"?`String tidak valid: harus berakhir dengan "${r.suffix}"`:r.format==="includes"?`String tidak valid: harus menyertakan "${r.includes}"`:r.format==="regex"?`String tidak valid: harus sesuai pola ${r.pattern}`:`${i[r.format]??t.format} tidak valid`}case"not_multiple_of":return`Angka tidak valid: harus kelipatan dari ${t.divisor}`;case"unrecognized_keys":return`Kunci tidak dikenali ${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Kunci tidak valid di ${t.origin}`;case"invalid_union":return"Input tidak valid";case"invalid_element":return`Nilai tidak valid di ${t.origin}`;default:return"Input tidak valid"}}};function nf(){return{localeError:o$()}}var a$=()=>{let e={string:{unit:"stafi",verb:"að hafa"},file:{unit:"bæti",verb:"að hafa"},array:{unit:"hluti",verb:"að hafa"},set:{unit:"hluti",verb:"að hafa"},map:{unit:"hluti",verb:"að hafa"}};function n(t){return e[t]??null}let i={regex:"gildi",email:"netfang",url:"vefslóð",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dagsetning og tími",date:"ISO dagsetning",time:"ISO tími",duration:"ISO tímalengd",ipv4:"IPv4 address",ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded strengur",base64url:"base64url-encoded strengur",json_string:"JSON strengur",e164:"E.164 tölugildi",credit_card:"kreditkortanúmer",currency_code:"gjaldmiðilskóði",iban:"IBAN",jwt:"JWT",template_literal:"gildi"},o={nan:"NaN",number:"númer",array:"fylki"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Rangt gildi: Þú slóst inn ${s} þar sem á að vera instanceof ${t.expected}`:`Rangt gildi: Þú slóst inn ${s} þar sem á að vera ${r}`}case"invalid_value":return t.values.length===1?`Rangt gildi: gert ráð fyrir ${$(t.values[0])}`:`Ógilt val: má vera eitt af eftirfarandi ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Of stórt: gert er ráð fyrir að ${t.origin??"gildi"} hafi ${r}${t.maximum.toString()} ${a.unit??"hluti"}`:`Of stórt: gert er ráð fyrir að ${t.origin??"gildi"} sé ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Of lítið: gert er ráð fyrir að ${t.origin} hafi ${r}${t.minimum.toString()} ${a.unit}`:`Of lítið: gert er ráð fyrir að ${t.origin} sé ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ógildur strengur: verður að byrja á "${r.prefix}"`:r.format==="ends_with"?`Ógildur strengur: verður að enda á "${r.suffix}"`:r.format==="includes"?`Ógildur strengur: verður að innihalda "${r.includes}"`:r.format==="regex"?`Ógildur strengur: verður að fylgja mynstri ${r.pattern}`:`Rangt ${i[r.format]??t.format}`}case"not_multiple_of":return`Röng tala: verður að vera margfeldi af ${t.divisor}`;case"unrecognized_keys":return`Óþekkt ${t.keys.length>1?"ir lyklar":"ur lykill"}: ${v(t.keys,", ")}`;case"invalid_key":return`Rangur lykill í ${t.origin}`;case"invalid_union":return"Rangt gildi";case"invalid_element":return`Rangt gildi í ${t.origin}`;default:return"Rangt gildi"}}};function rf(){return{localeError:a$()}}var s$=()=>{let e={string:{unit:"caratteri",verb:"avere"},file:{unit:"byte",verb:"avere"},array:{unit:"elementi",verb:"avere"},set:{unit:"elementi",verb:"avere"},map:{unit:"elementi",verb:"avere"}};function n(t){return e[t]??null}let i={regex:"input",email:"indirizzo email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data e ora ISO",date:"data ISO",time:"ora ISO",duration:"durata ISO",ipv4:"indirizzo IPv4",ipv6:"indirizzo IPv6",mac:"indirizzo MAC",cidrv4:"intervallo IPv4",cidrv6:"intervallo IPv6",base64:"stringa codificata in base64",base64url:"URL codificata in base64",json_string:"stringa JSON",e164:"numero E.164",credit_card:"numero di carta di credito",currency_code:"codice valuta",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"numero",array:"vettore"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Input non valido: atteso instanceof ${t.expected}, ricevuto ${s}`:`Input non valido: atteso ${r}, ricevuto ${s}`}case"invalid_value":return t.values.length===1?`Input non valido: atteso ${$(t.values[0])}`:`Opzione non valida: atteso uno tra ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Troppo grande: ${t.origin??"valore"} deve avere ${r}${t.maximum.toString()} ${a.unit??"elementi"}`:`Troppo grande: ${t.origin??"valore"} deve essere ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Troppo piccolo: ${t.origin} deve avere ${r}${t.minimum.toString()} ${a.unit}`:`Troppo piccolo: ${t.origin} deve essere ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Stringa non valida: deve iniziare con "${r.prefix}"`:r.format==="ends_with"?`Stringa non valida: deve terminare con "${r.suffix}"`:r.format==="includes"?`Stringa non valida: deve includere "${r.includes}"`:r.format==="regex"?`Stringa non valida: deve corrispondere al pattern ${r.pattern}`:`Input non valido: ${i[r.format]??t.format}`}case"not_multiple_of":return`Numero non valido: deve essere un multiplo di ${t.divisor}`;case"unrecognized_keys":return`Chiav${t.keys.length>1?"i":"e"} non riconosciut${t.keys.length>1?"e":"a"}: ${v(t.keys,", ")}`;case"invalid_key":return`Chiave non valida in ${t.origin}`;case"invalid_union":return"Input non valido";case"invalid_element":return`Valore non valido in ${t.origin}`;default:return"Input non valido"}}};function of(){return{localeError:s$()}}var c$=()=>{let e={string:{unit:"文字",verb:"である"},file:{unit:"バイト",verb:"である"},array:{unit:"要素",verb:"である"},set:{unit:"要素",verb:"である"},map:{unit:"要素",verb:"である"}};function n(t){return e[t]??null}let i={regex:"入力値",email:"メールアドレス",url:"URL",emoji:"絵文字",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO日時",date:"ISO日付",time:"ISO時刻",duration:"ISO期間",ipv4:"IPv4アドレス",ipv6:"IPv6アドレス",mac:"MACアドレス",cidrv4:"IPv4範囲",cidrv6:"IPv6範囲",base64:"base64エンコード文字列",base64url:"base64urlエンコード文字列",json_string:"JSON文字列",e164:"E.164番号",credit_card:"クレジットカード番号",currency_code:"通貨コード",iban:"IBAN",jwt:"JWT",template_literal:"入力値"},o={nan:"NaN",number:"数値",array:"配列"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`無効な入力: instanceof ${t.expected}が期待されましたが、${s}が入力されました`:`無効な入力: ${r}が期待されましたが、${s}が入力されました`}case"invalid_value":return t.values.length===1?`無効な入力: ${$(t.values[0])}が期待されました`:`無効な選択: ${v(t.values,"、")}のいずれかである必要があります`;case"too_big":{let r=t.inclusive?"以下である":"より小さい",a=n(t.origin);return a?`大きすぎる値: ${t.origin??"値"}は${t.maximum.toString()}${a.unit??"要素"}${r}必要があります`:`大きすぎる値: ${t.origin??"値"}は${t.maximum.toString()}${r}必要があります`}case"too_small":{let r=t.inclusive?"以上である":"より大きい",a=n(t.origin);return a?`小さすぎる値: ${t.origin}は${t.minimum.toString()}${a.unit}${r}必要があります`:`小さすぎる値: ${t.origin}は${t.minimum.toString()}${r}必要があります`}case"invalid_format":{let r=t;return r.format==="starts_with"?`無効な文字列: "${r.prefix}"で始まる必要があります`:r.format==="ends_with"?`無効な文字列: "${r.suffix}"で終わる必要があります`:r.format==="includes"?`無効な文字列: "${r.includes}"を含む必要があります`:r.format==="regex"?`無効な文字列: パターン${r.pattern}に一致する必要があります`:`無効な${i[r.format]??t.format}`}case"not_multiple_of":return`無効な数値: ${t.divisor}の倍数である必要があります`;case"unrecognized_keys":return`認識されていないキー${t.keys.length>1?"群":""}: ${v(t.keys,"、")}`;case"invalid_key":return`${t.origin}内の無効なキー`;case"invalid_union":return"無効な入力";case"invalid_element":return`${t.origin}内の無効な値`;default:return"無効な入力"}}};function af(){return{localeError:c$()}}var u$=()=>{let e={string:{unit:"სიმბოლო",verb:"უნდა შეიცავდეს"},file:{unit:"ბაიტი",verb:"უნდა შეიცავდეს"},array:{unit:"ელემენტი",verb:"უნდა შეიცავდეს"},set:{unit:"ელემენტი",verb:"უნდა შეიცავდეს"},map:{unit:"ელემენტი",verb:"უნდა შეიცავდეს"}};function n(t){return e[t]??null}let i={regex:"შეყვანა",email:"ელ-ფოსტის მისამართი",url:"URL",emoji:"ემოჯი",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"თარიღი-დრო",date:"თარიღი",time:"დრო",duration:"ხანგრძლივობა",ipv4:"IPv4 მისამართი",ipv6:"IPv6 მისამართი",mac:"MAC მისამართი",cidrv4:"IPv4 დიაპაზონი",cidrv6:"IPv6 დიაპაზონი",base64:"base64-კოდირებული ველი",base64url:"base64url-კოდირებული ველი",json_string:"JSON ველი",e164:"E.164 ნომერი",credit_card:"საკრედიტო ბარათის ნომერი",currency_code:"ვალუტის კოდი",iban:"IBAN",jwt:"JWT",template_literal:"შეყვანა"},o={nan:"NaN",number:"რიცხვი",string:"ველი",boolean:"ბულეანი",function:"ფუნქცია",array:"მასივი"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`არასწორი შეყვანა: მოსალოდნელი instanceof ${t.expected}, მიღებული ${s}`:`არასწორი შეყვანა: მოსალოდნელი ${r}, მიღებული ${s}`}case"invalid_value":return t.values.length===1?`არასწორი შეყვანა: მოსალოდნელი ${$(t.values[0])}`:`არასწორი ვარიანტი: მოსალოდნელია ერთ-ერთი ${v(t.values,"|")}-დან`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ზედმეტად დიდი: მოსალოდნელი ${t.origin??"მნიშვნელობა"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit}`:`ზედმეტად დიდი: მოსალოდნელი ${t.origin??"მნიშვნელობა"} იყოს ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ზედმეტად პატარა: მოსალოდნელი ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`ზედმეტად პატარა: მოსალოდნელი ${t.origin} იყოს ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`არასწორი ველი: უნდა იწყებოდეს "${r.prefix}"-ით`:r.format==="ends_with"?`არასწორი ველი: უნდა მთავრდებოდეს "${r.suffix}"-ით`:r.format==="includes"?`არასწორი ველი: უნდა შეიცავდეს "${r.includes}"-ს`:r.format==="regex"?`არასწორი ველი: უნდა შეესაბამებოდეს შაბლონს ${r.pattern}`:`არასწორი ${i[r.format]??t.format}`}case"not_multiple_of":return`არასწორი რიცხვი: უნდა იყოს ${t.divisor}-ის ჯერადი`;case"unrecognized_keys":return`უცნობი გასაღებ${t.keys.length>1?"ები":"ი"}: ${v(t.keys,", ")}`;case"invalid_key":return`არასწორი გასაღები ${t.origin}-ში`;case"invalid_union":return"არასწორი შეყვანა";case"invalid_element":return`არასწორი მნიშვნელობა ${t.origin}-ში`;default:return"არასწორი შეყვანა"}}};function sf(){return{localeError:u$()}}var l$=()=>{let e={string:{unit:"តួអក្សរ",verb:"គួរមាន"},file:{unit:"បៃ",verb:"គួរមាន"},array:{unit:"ធាតុ",verb:"គួរមាន"},set:{unit:"ធាតុ",verb:"គួរមាន"},map:{unit:"ធាតុ",verb:"គួរមាន"}};function n(t){return e[t]??null}let i={regex:"ទិន្នន័យបញ្ចូល",email:"អាសយដ្ឋានអ៊ីមែល",url:"URL",emoji:"សញ្ញាអារម្មណ៍",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"កាលបរិច្ឆេទ និងម៉ោង ISO",date:"កាលបរិច្ឆេទ ISO",time:"ម៉ោង ISO",duration:"រយៈពេល ISO",ipv4:"អាសយដ្ឋាន IPv4",ipv6:"អាសយដ្ឋាន IPv6",mac:"អាសយដ្ឋាន MAC",cidrv4:"ដែនអាសយដ្ឋាន IPv4",cidrv6:"ដែនអាសយដ្ឋាន IPv6",base64:"ខ្សែអក្សរអ៊ិកូដ base64",base64url:"ខ្សែអក្សរអ៊ិកូដ base64url",json_string:"ខ្សែអក្សរ JSON",e164:"លេខ E.164",credit_card:"លេខប័ណ្ណឥណទាន",currency_code:"កូដរូបិយប័ណ្ណ",iban:"IBAN",jwt:"JWT",template_literal:"ទិន្នន័យបញ្ចូល"},o={nan:"NaN",number:"លេខ",array:"អារេ (Array)",null:"គ្មានតម្លៃ (null)"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ instanceof ${t.expected} ប៉ុន្តែទទួលបាន ${s}`:`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ ${r} ប៉ុន្តែទទួលបាន ${s}`}case"invalid_value":return t.values.length===1?`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ ${$(t.values[0])}`:`ជម្រើសមិនត្រឹមត្រូវ៖ ត្រូវជាមួយក្នុងចំណោម ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ធំពេក៖ ត្រូវការ ${t.origin??"តម្លៃ"} ${r} ${t.maximum.toString()} ${a.unit??"ធាតុ"}`:`ធំពេក៖ ត្រូវការ ${t.origin??"តម្លៃ"} ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`តូចពេក៖ ត្រូវការ ${t.origin} ${r} ${t.minimum.toString()} ${a.unit}`:`តូចពេក៖ ត្រូវការ ${t.origin} ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវចាប់ផ្តើមដោយ "${r.prefix}"`:r.format==="ends_with"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវបញ្ចប់ដោយ "${r.suffix}"`:r.format==="includes"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវមាន "${r.includes}"`:r.format==="regex"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវតែផ្គូផ្គងនឹងទម្រង់ដែលបានកំណត់ ${r.pattern}`:`មិនត្រឹមត្រូវ៖ ${i[r.format]??t.format}`}case"not_multiple_of":return`លេខមិនត្រឹមត្រូវ៖ ត្រូវតែជាពហុគុណនៃ ${t.divisor}`;case"unrecognized_keys":return`រកឃើញសោមិនស្គាល់៖ ${v(t.keys,", ")}`;case"invalid_key":return`សោមិនត្រឹមត្រូវនៅក្នុង ${t.origin}`;case"invalid_union":return"ទិន្នន័យមិនត្រឹមត្រូវ";case"invalid_element":return`ទិន្នន័យមិនត្រឹមត្រូវនៅក្នុង ${t.origin}`;default:return"ទិន្នន័យមិនត្រឹមត្រូវ"}}};function Ji(){return{localeError:l$()}}function cf(){return Ji()}var d$=()=>{let e={string:{unit:"ಅಕ್ಷರಗಳು",verb:"ಹೊಂದಲು"},file:{unit:"ಬೈಟ್‌ಗಳು",verb:"ಹೊಂದಲು"},array:{unit:"ವಸ್ತುಗಳು",verb:"ಹೊಂದಲು"},set:{unit:"ವಸ್ತುಗಳು",verb:"ಹೊಂದಲು"},map:{unit:"entries",verb:"ಹೊಂದಲು"}};function n(t){return e[t]??null}let i={regex:"ಇನ್ಪುಟ್",email:"email ವಿಳಾಸ",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO ದಿನಾಂಕದ ಸಮಯ",date:"ISO ದಿನಾಂಕ",time:"ISO ಸಮಯ",duration:"ISO ಅವಧಿ",ipv4:"IPv4 ವಿಳಾಸ",ipv6:"IPv6 ವಿಳಾಸ",mac:"MAC ವಿಳಾಸ",cidrv4:"IPv4 ವ್ಯಾಪ್ತಿಯ",cidrv6:"IPv6 ವ್ಯಾಪ್ತಿಯ",base64:"base64-encodedಸ್ಟ್ರಿಂಗ್",base64url:"base64url-encodedಸ್ಟ್ರಿಂಗ್",json_string:"JSONಸ್ಟ್ರಿಂಗ್",e164:"E.164 ಸಂಖ್ಯೆ",credit_card:"ಕ್ರೆಡಿಟ್ ಕಾರ್ಡ್ ಸಂಖ್ಯೆ",currency_code:"ಕರೆನ್ಸಿ ಕೋಡ್",iban:"IBAN",jwt:"JWT",template_literal:"ಇನ್ಪುಟ್"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${r}, ಸ್ವೀಕರಿಸಿದನು ${s}`}case"invalid_value":return t.values.length===1?`ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${$(t.values[0])}`:`ಅಮಾನ್ಯ ಆಯ್ಕೆ: ಇವುಗಳಲ್ಲಿ ಒಂದನ್ನು ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ತುಂಬಾ ದೊಡ್ಡದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin??"value"} ಹೊಂದಲು ${r}${t.maximum.toString()} ${a.unit??"ಅಂಶಗಳು"}`:`ತುಂಬಾ ದೊಡ್ಡದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin??"value"} ಎಂದು ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ತುಂಬಾ ಚಿಕ್ಕದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin} ಹೊಂದಲು ${r}${t.minimum.toString()} ${a.unit}`:`ತುಂಬಾ ಚಿಕ್ಕದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin} ಎಂದು ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`ಅಮಾನ್ಯವಾದ ಸ್ಟ್ರಿಂಗ್: ಇದರೊಂದಿಗೆ ಪ್ರಾರಂಭಿಸಬೇಕು "${r.prefix}"`:r.format==="ends_with"?`ಅಮಾನ್ಯವಾದ ಸ್ಟ್ರಿಂಗ್: ಇದರೊಂದಿಗೆ ಕೊನೆಗೊಳ್ಳಬೇಕು "${r.suffix}"`:r.format==="includes"?`ಅಮಾನ್ಯ ಸ್ಟ್ರಿಂಗ್: ಒಳಗೊಂಡಿರಬೇಕು "${r.includes}"`:r.format==="regex"?`ಅಮಾನ್ಯವಾದ ಸ್ಟ್ರಿಂಗ್: ಮಾದರಿಗೆ ಹೊಂದಿಕೆಯಾಗಬೇಕು ${r.pattern}`:`ಅಮಾನ್ಯ ${i[r.format]??t.format}`}case"not_multiple_of":return`ಅಮಾನ್ಯ ಸಂಖ್ಯೆ: ಬಹುಸಂಖ್ಯೆಯಾಗಿರಬೇಕು ${t.divisor}`;case"unrecognized_keys":return`ಗುರುತಿಸಲಾಗದ ಕೀ ${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`ಅಮಾನ್ಯವಾದ ಕೀ ಇನ್ ${t.origin}`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`ಅಮಾನ್ಯ ತಾರತಮ್ಯ ಮೌಲ್ಯ. ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.options.map(a=>`'${a}'`).join(" | ")}`:"ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್";case"invalid_element":return`ರಲ್ಲಿ ಅಮಾನ್ಯ ಮೌಲ್ಯ ${t.origin}`;default:return"ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್"}}};function uf(){return{localeError:d$()}}var p$=()=>{let e={string:{unit:"문자",verb:"to have"},file:{unit:"바이트",verb:"to have"},array:{unit:"개",verb:"to have"},set:{unit:"개",verb:"to have"},map:{unit:"개",verb:"to have"}};function n(t){return e[t]??null}let i={regex:"입력",email:"이메일 주소",url:"URL",emoji:"이모지",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO 날짜시간",date:"ISO 날짜",time:"ISO 시간",duration:"ISO 기간",ipv4:"IPv4 주소",ipv6:"IPv6 주소",mac:"MAC 주소",cidrv4:"IPv4 범위",cidrv6:"IPv6 범위",base64:"base64 인코딩 문자열",base64url:"base64url 인코딩 문자열",json_string:"JSON 문자열",e164:"E.164 번호",credit_card:"신용카드 번호",currency_code:"통화 코드",iban:"IBAN",jwt:"JWT",template_literal:"입력"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`잘못된 입력: 예상 타입은 instanceof ${t.expected}, 받은 타입은 ${s}입니다`:`잘못된 입력: 예상 타입은 ${r}, 받은 타입은 ${s}입니다`}case"invalid_value":return t.values.length===1?`잘못된 입력: 값은 ${$(t.values[0])} 이어야 합니다`:`잘못된 옵션: ${v(t.values,"또는 ")} 중 하나여야 합니다`;case"too_big":{let r=t.inclusive?"이하":"미만",a=r==="미만"?"이어야 합니다":"여야 합니다",s=n(t.origin),c=s?.unit??"요소";return s?`${t.origin??"값"}이 너무 큽니다: ${t.maximum.toString()}${c} ${r}${a}`:`${t.origin??"값"}이 너무 큽니다: ${t.maximum.toString()} ${r}${a}`}case"too_small":{let r=t.inclusive?"이상":"초과",a=r==="이상"?"이어야 합니다":"여야 합니다",s=n(t.origin),c=s?.unit??"요소";return s?`${t.origin??"값"}이 너무 작습니다: ${t.minimum.toString()}${c} ${r}${a}`:`${t.origin??"값"}이 너무 작습니다: ${t.minimum.toString()} ${r}${a}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`잘못된 문자열: "${r.prefix}"(으)로 시작해야 합니다`:r.format==="ends_with"?`잘못된 문자열: "${r.suffix}"(으)로 끝나야 합니다`:r.format==="includes"?`잘못된 문자열: "${r.includes}"을(를) 포함해야 합니다`:r.format==="regex"?`잘못된 문자열: 정규식 ${r.pattern} 패턴과 일치해야 합니다`:`잘못된 ${i[r.format]??t.format}`}case"not_multiple_of":return`잘못된 숫자: ${t.divisor}의 배수여야 합니다`;case"unrecognized_keys":return`인식할 수 없는 키: ${v(t.keys,", ")}`;case"invalid_key":return`잘못된 키: ${t.origin}`;case"invalid_union":return"잘못된 입력";case"invalid_element":return`잘못된 값: ${t.origin}`;default:return"잘못된 입력"}}};function lf(){return{localeError:p$()}}var br=e=>e.charAt(0).toUpperCase()+e.slice(1);function df(e){let n=Math.abs(e),i=n%10,o=n%100;return o>=11&&o<=19||i===0?"many":i===1?"one":"few"}var m$=()=>{let e={string:{unit:{one:"simbolis",few:"simboliai",many:"simbolių"},verb:{smaller:{inclusive:"turi būti ne ilgesnė kaip",notInclusive:"turi būti trumpesnė kaip"},bigger:{inclusive:"turi būti ne trumpesnė kaip",notInclusive:"turi būti ilgesnė kaip"}}},file:{unit:{one:"baitas",few:"baitai",many:"baitų"},verb:{smaller:{inclusive:"turi būti ne didesnis kaip",notInclusive:"turi būti mažesnis kaip"},bigger:{inclusive:"turi būti ne mažesnis kaip",notInclusive:"turi būti didesnis kaip"}}},array:{unit:{one:"elementą",few:"elementus",many:"elementų"},verb:{smaller:{inclusive:"turi turėti ne daugiau kaip",notInclusive:"turi turėti mažiau kaip"},bigger:{inclusive:"turi turėti ne mažiau kaip",notInclusive:"turi turėti daugiau kaip"}}},set:{unit:{one:"elementą",few:"elementus",many:"elementų"},verb:{smaller:{inclusive:"turi turėti ne daugiau kaip",notInclusive:"turi turėti mažiau kaip"},bigger:{inclusive:"turi turėti ne mažiau kaip",notInclusive:"turi turėti daugiau kaip"}}}};function n(t,r,a,s){let c=e[t]??null;return c===null?c:{unit:c.unit[r],verb:c.verb[s][a?"inclusive":"notInclusive"]}}let i={regex:"įvestis",email:"el. pašto adresas",url:"URL",emoji:"jaustukas",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO data ir laikas",date:"ISO data",time:"ISO laikas",duration:"ISO trukmė",ipv4:"IPv4 adresas",ipv6:"IPv6 adresas",mac:"MAC adresas",cidrv4:"IPv4 tinklo prefiksas (CIDR)",cidrv6:"IPv6 tinklo prefiksas (CIDR)",base64:"base64 užkoduota eilutė",base64url:"base64url užkoduota eilutė",json_string:"JSON eilutė",e164:"E.164 numeris",credit_card:"kredito kortelės numeris",currency_code:"valiutos kodas",iban:"IBAN",jwt:"JWT",template_literal:"įvestis"},o={nan:"NaN",number:"skaičius",bigint:"sveikasis skaičius",string:"eilutė",boolean:"loginė reikšmė",undefined:"neapibrėžta reikšmė",function:"funkcija",symbol:"simbolis",array:"masyvas",object:"objektas",null:"nulinė reikšmė"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Gautas tipas ${s}, o tikėtasi - instanceof ${t.expected}`:`Gautas tipas ${s}, o tikėtasi - ${r}`}case"invalid_value":return t.values.length===1?`Privalo būti ${$(t.values[0])}`:`Privalo būti vienas iš ${v(t.values,"|")} pasirinkimų`;case"too_big":{let r=o[t.origin]??t.origin,a=n(t.origin,df(Number(t.maximum)),t.inclusive??!1,"smaller");if(a?.verb)return`${br(r??t.origin??"reikšmė")} ${a.verb} ${t.maximum.toString()} ${a.unit??"elementų"}`;let s=t.inclusive?"ne didesnis kaip":"mažesnis kaip";return`${br(r??t.origin??"reikšmė")} turi būti ${s} ${t.maximum.toString()} ${a?.unit}`}case"too_small":{let r=o[t.origin]??t.origin,a=n(t.origin,df(Number(t.minimum)),t.inclusive??!1,"bigger");if(a?.verb)return`${br(r??t.origin??"reikšmė")} ${a.verb} ${t.minimum.toString()} ${a.unit??"elementų"}`;let s=t.inclusive?"ne mažesnis kaip":"didesnis kaip";return`${br(r??t.origin??"reikšmė")} turi būti ${s} ${t.minimum.toString()} ${a?.unit}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Eilutė privalo prasidėti "${r.prefix}"`:r.format==="ends_with"?`Eilutė privalo pasibaigti "${r.suffix}"`:r.format==="includes"?`Eilutė privalo įtraukti "${r.includes}"`:r.format==="regex"?`Eilutė privalo atitikti ${r.pattern}`:`Neteisingas ${i[r.format]??t.format}`}case"not_multiple_of":return`Skaičius privalo būti ${t.divisor} kartotinis.`;case"unrecognized_keys":return`Neatpažint${t.keys.length>1?"i":"as"} rakt${t.keys.length>1?"ai":"as"}: ${v(t.keys,", ")}`;case"invalid_key":return"Rastas klaidingas raktas";case"invalid_union":return"Klaidinga įvestis";case"invalid_element":{let r=o[t.origin]??t.origin;return`${br(r??t.origin??"reikšmė")} turi klaidingą įvestį`}default:return"Klaidinga įvestis"}}};function pf(){return{localeError:m$()}}var f$=()=>{let e={string:{unit:"знаци",verb:"да имаат"},file:{unit:"бајти",verb:"да имаат"},array:{unit:"ставки",verb:"да имаат"},set:{unit:"ставки",verb:"да имаат"},map:{unit:"ставки",verb:"да имаат"}};function n(t){return e[t]??null}let i={regex:"внес",email:"адреса на е-пошта",url:"URL",emoji:"емоџи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO датум и време",date:"ISO датум",time:"ISO време",duration:"ISO времетраење",ipv4:"IPv4 адреса",ipv6:"IPv6 адреса",mac:"MAC адреса",cidrv4:"IPv4 опсег",cidrv6:"IPv6 опсег",base64:"base64-енкодирана низа",base64url:"base64url-енкодирана низа",json_string:"JSON низа",e164:"E.164 број",credit_card:"број на кредитна картичка",currency_code:"код на валута",iban:"IBAN",jwt:"JWT",template_literal:"внес"},o={nan:"NaN",number:"број",array:"низа"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Грешен внес: се очекува instanceof ${t.expected}, примено ${s}`:`Грешен внес: се очекува ${r}, примено ${s}`}case"invalid_value":return t.values.length===1?`Invalid input: expected ${$(t.values[0])}`:`Грешана опција: се очекува една ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Премногу голем: се очекува ${t.origin??"вредноста"} да има ${r}${t.maximum.toString()} ${a.unit??"елементи"}`:`Премногу голем: се очекува ${t.origin??"вредноста"} да биде ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Премногу мал: се очекува ${t.origin} да има ${r}${t.minimum.toString()} ${a.unit}`:`Премногу мал: се очекува ${t.origin} да биде ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Неважечка низа: мора да започнува со "${r.prefix}"`:r.format==="ends_with"?`Неважечка низа: мора да завршува со "${r.suffix}"`:r.format==="includes"?`Неважечка низа: мора да вклучува "${r.includes}"`:r.format==="regex"?`Неважечка низа: мора да одгоара на патернот ${r.pattern}`:`Invalid ${i[r.format]??t.format}`}case"not_multiple_of":return`Грешен број: мора да биде делив со ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Непрепознаени клучеви":"Непрепознаен клуч"}: ${v(t.keys,", ")}`;case"invalid_key":return`Грешен клуч во ${t.origin}`;case"invalid_union":return"Грешен внес";case"invalid_element":return`Грешна вредност во ${t.origin}`;default:return"Грешен внес"}}};function mf(){return{localeError:f$()}}var g$=()=>{let e={string:{unit:"aksara",verb:"mempunyai"},file:{unit:"bait",verb:"mempunyai"},array:{unit:"elemen",verb:"mempunyai"},set:{unit:"elemen",verb:"mempunyai"},map:{unit:"elemen",verb:"mempunyai"}};function n(t){return e[t]??null}let i={regex:"input",email:"alamat e-mel",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"tarikh masa ISO",date:"tarikh ISO",time:"masa ISO",duration:"tempoh ISO",ipv4:"alamat IPv4",ipv6:"alamat IPv6",mac:"alamat MAC",cidrv4:"julat IPv4",cidrv6:"julat IPv6",base64:"string dikodkan base64",base64url:"string dikodkan base64url",json_string:"string JSON",e164:"nombor E.164",credit_card:"nombor kad kredit",currency_code:"kod mata wang",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"nombor"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Input tidak sah: dijangka instanceof ${t.expected}, diterima ${s}`:`Input tidak sah: dijangka ${r}, diterima ${s}`}case"invalid_value":return t.values.length===1?`Input tidak sah: dijangka ${$(t.values[0])}`:`Pilihan tidak sah: dijangka salah satu daripada ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Terlalu besar: dijangka ${t.origin??"nilai"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"elemen"}`:`Terlalu besar: dijangka ${t.origin??"nilai"} adalah ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Terlalu kecil: dijangka ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Terlalu kecil: dijangka ${t.origin} adalah ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`String tidak sah: mesti bermula dengan "${r.prefix}"`:r.format==="ends_with"?`String tidak sah: mesti berakhir dengan "${r.suffix}"`:r.format==="includes"?`String tidak sah: mesti mengandungi "${r.includes}"`:r.format==="regex"?`String tidak sah: mesti sepadan dengan corak ${r.pattern}`:`${i[r.format]??t.format} tidak sah`}case"not_multiple_of":return`Nombor tidak sah: perlu gandaan ${t.divisor}`;case"unrecognized_keys":return`Kunci tidak dikenali: ${v(t.keys,", ")}`;case"invalid_key":return`Kunci tidak sah dalam ${t.origin}`;case"invalid_union":return"Input tidak sah";case"invalid_element":return`Nilai tidak sah dalam ${t.origin}`;default:return"Input tidak sah"}}};function ff(){return{localeError:g$()}}var h$=()=>{let e={string:{unit:"अक्षर",verb:"हुनुपर्छ"},file:{unit:"बाइट",verb:"हुनुपर्छ"},array:{unit:"तत्व",verb:"हुनुपर्छ"},set:{unit:"तत्व",verb:"हुनुपर्छ"},map:{unit:"प्रविष्टि",verb:"हुनुपर्छ"}};function n(t){return e[t]??null}let i={regex:"इनपुट",email:"इमेल ठेगाना",url:"URL",emoji:"इमोजी",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO मिति र समय",date:"ISO मिति",time:"ISO समय",duration:"ISO अवधि",ipv4:"IPv4 ठेगाना",ipv6:"IPv6 ठेगाना",mac:"MAC ठेगाना",cidrv4:"IPv4 दायरा",cidrv6:"IPv6 दायरा",base64:"base64-इन्कोड गरिएको स्ट्रिङ",base64url:"base64url-इन्कोड गरिएको स्ट्रिङ",json_string:"JSON स्ट्रिङ",e164:"E.164 नम्बर",credit_card:"क्रेडिट कार्ड नम्बर",currency_code:"मुद्रा कोड",iban:"IBAN",jwt:"JWT",template_literal:"इनपुट"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`अमान्य इनपुट: अपेक्षित ${r}, प्राप्त ${s}`}case"invalid_value":return t.values.length===1?`अमान्य इनपुट: अपेक्षित ${$(t.values[0])}`:`अमान्य विकल्प: अपेक्षित मानहरू मध्ये एक ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`धेरै ठूलो: ${t.origin??"मान"} मा ${r}${t.maximum.toString()} ${a.unit} ${a.verb}`:`धेरै ठूलो: ${t.origin??"मान"} ${r}${t.maximum.toString()} हुनुपर्छ`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`धेरै सानो: ${t.origin} मा ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`धेरै सानो: ${t.origin} ${r}${t.minimum.toString()} हुनुपर्छ`}case"invalid_format":{let r=t;return r.format==="starts_with"?`अमान्य स्ट्रिङ: "${r.prefix}" बाट सुरु हुनुपर्छ`:r.format==="ends_with"?`अमान्य स्ट्रिङ: "${r.suffix}" मा समाप्त हुनुपर्छ`:r.format==="includes"?`अमान्य स्ट्रिङ: "${r.includes}" समावेश हुनुपर्छ`:r.format==="regex"?`अमान्य स्ट्रिङ: ढाँचा ${r.pattern} सँग मेल खानुपर्छ`:`अमान्य ${i[r.format]??t.format}`}case"not_multiple_of":return`अमान्य संख्या: ${t.divisor} को गुणज हुनुपर्छ`;case"unrecognized_keys":return`अपरिचित कुञ्जी${t.keys.length>1?"हरू":""}: ${v(t.keys,", ")}`;case"invalid_key":return`अमान्य कुञ्जी: ${t.origin} मा`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`अमान्य डिस्क्रिमिनेटर मान: अपेक्षित ${t.options.map(a=>`'${a}'`).join(" | ")}`:"अमान्य इनपुट";case"invalid_element":return`अमान्य मान: ${t.origin} मा`;default:return"अमान्य इनपुट"}}};function gf(){return{localeError:h$()}}var v$=()=>{let e={string:{unit:"tekens",verb:"heeft"},file:{unit:"bytes",verb:"heeft"},array:{unit:"elementen",verb:"heeft"},set:{unit:"elementen",verb:"heeft"},map:{unit:"elementen",verb:"heeft"}};function n(t){return e[t]??null}let i={regex:"invoer",email:"emailadres",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datum en tijd",date:"ISO datum",time:"ISO tijd",duration:"ISO duur",ipv4:"IPv4-adres",ipv6:"IPv6-adres",mac:"MAC-adres",cidrv4:"IPv4-bereik",cidrv6:"IPv6-bereik",base64:"base64-gecodeerde tekst",base64url:"base64 URL-gecodeerde tekst",json_string:"JSON string",e164:"E.164-nummer",credit_card:"creditcardnummer",currency_code:"valutacode",iban:"IBAN",jwt:"JWT",template_literal:"invoer"},o={nan:"NaN",number:"getal"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ongeldige invoer: verwacht instanceof ${t.expected}, ontving ${s}`:`Ongeldige invoer: verwacht ${r}, ontving ${s}`}case"invalid_value":return t.values.length===1?`Ongeldige invoer: verwacht ${$(t.values[0])}`:`Ongeldige optie: verwacht één van ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=t.origin==="date"?"laat":t.origin==="string"?"lang":"groot";return a?`Te ${s}: verwacht dat ${t.origin??"waarde"} ${r}${t.maximum.toString()} ${a.unit??"elementen"} ${a.verb}`:`Te ${s}: verwacht dat ${t.origin??"waarde"} ${r}${t.maximum.toString()} is`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=t.origin==="date"?"vroeg":t.origin==="string"?"kort":"klein";return a?`Te ${s}: verwacht dat ${t.origin} ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`Te ${s}: verwacht dat ${t.origin} ${r}${t.minimum.toString()} is`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ongeldige tekst: moet met "${r.prefix}" beginnen`:r.format==="ends_with"?`Ongeldige tekst: moet op "${r.suffix}" eindigen`:r.format==="includes"?`Ongeldige tekst: moet "${r.includes}" bevatten`:r.format==="regex"?`Ongeldige tekst: moet overeenkomen met patroon ${r.pattern}`:`Ongeldig: ${i[r.format]??t.format}`}case"not_multiple_of":return`Ongeldig getal: moet een veelvoud van ${t.divisor} zijn`;case"unrecognized_keys":return`Onbekende key${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Ongeldige key in ${t.origin}`;case"invalid_union":return"Ongeldige invoer";case"invalid_element":return`Ongeldige waarde in ${t.origin}`;default:return"Ongeldige invoer"}}};function hf(){return{localeError:v$()}}var y$=()=>{let e={string:{unit:"teikn",verb:"å ha"},file:{unit:"bytes",verb:"å ha"},array:{unit:"element",verb:"å innehalde"},set:{unit:"element",verb:"å innehalde"},map:{unit:"element",verb:"å innehalde"}};function n(t){return e[t]??null}let i={regex:"input",email:"e-postadresse",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dato- og klokkeslett",date:"ISO-dato",time:"ISO-klokkeslett",duration:"ISO-varigheit",ipv4:"IPv4-adresse",ipv6:"IPv6-adresse",mac:"MAC-adresse",cidrv4:"IPv4-spekter",cidrv6:"IPv6-spekter",base64:"base64-enkoda streng",base64url:"base64url-enkoda streng",json_string:"JSON-streng",e164:"E.164-nummer",credit_card:"kredittkortnummer",currency_code:"valutakode",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"tal",array:"liste"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ugyldig input: forventa instanceof ${t.expected}, fekk ${s}`:`Ugyldig input: forventa ${r}, fekk ${s}`}case"invalid_value":return t.values.length===1?`Ugyldig verdi: forventa ${$(t.values[0])}`:`Ugyldig val: forventa eitt av ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`For stor(t): forventa ${t.origin??"value"} til å ha ${r}${t.maximum.toString()} ${a.unit??"element"}`:`For stor(t): forventa ${t.origin??"value"} til å ha ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`For lite(n): forventa ${t.origin} til å ha ${r}${t.minimum.toString()} ${a.unit}`:`For lite(n): forventa ${t.origin} til å ha ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ugyldig streng: må starte med "${r.prefix}"`:r.format==="ends_with"?`Ugyldig streng: må slutte med "${r.suffix}"`:r.format==="includes"?`Ugyldig streng: må innehalde "${r.includes}"`:r.format==="regex"?`Ugyldig streng: må matche mønsteret ${r.pattern}`:`Ugyldig ${i[r.format]??t.format}`}case"not_multiple_of":return`Ugyldig tal: må vere eit multiplum av ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Ukjende nøklar":"Ukjend nøkkel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ugyldig nøkkel i ${t.origin}`;case"invalid_union":return"Ugyldig input";case"invalid_element":return`Ugyldig verdi i ${t.origin}`;default:return"Ugyldig input"}}};function vf(){return{localeError:y$()}}var b$=()=>{let e={string:{unit:"tegn",verb:"å ha"},file:{unit:"bytes",verb:"å ha"},array:{unit:"elementer",verb:"å inneholde"},set:{unit:"elementer",verb:"å inneholde"},map:{unit:"elementer",verb:"å inneholde"}};function n(t){return e[t]??null}let i={regex:"input",email:"e-postadresse",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dato- og klokkeslett",date:"ISO-dato",time:"ISO-klokkeslett",duration:"ISO-varighet",ipv4:"IPv4-adresse",ipv6:"IPv6-adresse",mac:"MAC-adresse",cidrv4:"IPv4-spekter",cidrv6:"IPv6-spekter",base64:"base64-enkodet streng",base64url:"base64url-enkodet streng",json_string:"JSON-streng",e164:"E.164-nummer",credit_card:"kredittkortnummer",currency_code:"valutakode",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"tall",array:"liste"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ugyldig input: forventet instanceof ${t.expected}, fikk ${s}`:`Ugyldig input: forventet ${r}, fikk ${s}`}case"invalid_value":return t.values.length===1?`Ugyldig verdi: forventet ${$(t.values[0])}`:`Ugyldig valg: forventet en av ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`For stor(t): forventet ${t.origin??"value"} til å ha ${r}${t.maximum.toString()} ${a.unit??"elementer"}`:`For stor(t): forventet ${t.origin??"value"} til å ha ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`For lite(n): forventet ${t.origin} til å ha ${r}${t.minimum.toString()} ${a.unit}`:`For lite(n): forventet ${t.origin} til å ha ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ugyldig streng: må starte med "${r.prefix}"`:r.format==="ends_with"?`Ugyldig streng: må ende med "${r.suffix}"`:r.format==="includes"?`Ugyldig streng: må inneholde "${r.includes}"`:r.format==="regex"?`Ugyldig streng: må matche mønsteret ${r.pattern}`:`Ugyldig ${i[r.format]??t.format}`}case"not_multiple_of":return`Ugyldig tall: må være et multiplum av ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Ukjente nøkler":"Ukjent nøkkel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ugyldig nøkkel i ${t.origin}`;case"invalid_union":return"Ugyldig input";case"invalid_element":return`Ugyldig verdi i ${t.origin}`;default:return"Ugyldig input"}}};function yf(){return{localeError:b$()}}var _$=()=>{let e={string:{unit:"harf",verb:"olmalıdır"},file:{unit:"bayt",verb:"olmalıdır"},array:{unit:"unsur",verb:"olmalıdır"},set:{unit:"unsur",verb:"olmalıdır"},map:{unit:"unsur",verb:"olmalıdır"}};function n(t){return e[t]??null}let i={regex:"giren",email:"epostagâh",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO hengâmı",date:"ISO tarihi",time:"ISO zamanı",duration:"ISO müddeti",ipv4:"IPv4 nişânı",ipv6:"IPv6 nişânı",mac:"MAC nişânı",cidrv4:"IPv4 menzili",cidrv6:"IPv6 menzili",base64:"base64-şifreli metin",base64url:"base64url-şifreli metin",json_string:"JSON metin",e164:"E.164 sayısı",credit_card:"i'tibâr kartı numarası",currency_code:"para birimi kodu",iban:"IBAN",jwt:"JWT",template_literal:"giren"},o={nan:"NaN",number:"numara",array:"saf",null:"gayb"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Fâsit giren: umulan instanceof ${t.expected}, alınan ${s}`:`Fâsit giren: umulan ${r}, alınan ${s}`}case"invalid_value":return t.values.length===1?`Fâsit giren: umulan ${$(t.values[0])}`:`Fâsit tercih: mûteberler ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Fazla büyük: ${t.origin??"value"}, ${r}${t.maximum.toString()} ${a.unit??"elements"} sahip olmalıydı.`:`Fazla büyük: ${t.origin??"value"}, ${r}${t.maximum.toString()} olmalıydı.`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Fazla küçük: ${t.origin}, ${r}${t.minimum.toString()} ${a.unit} sahip olmalıydı.`:`Fazla küçük: ${t.origin}, ${r}${t.minimum.toString()} olmalıydı.`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Fâsit metin: "${r.prefix}" ile başlamalı.`:r.format==="ends_with"?`Fâsit metin: "${r.suffix}" ile bitmeli.`:r.format==="includes"?`Fâsit metin: "${r.includes}" ihtivâ etmeli.`:r.format==="regex"?`Fâsit metin: ${r.pattern} nakşına uymalı.`:`Fâsit ${i[r.format]??t.format}`}case"not_multiple_of":return`Fâsit sayı: ${t.divisor} katı olmalıydı.`;case"unrecognized_keys":return`Tanınmayan anahtar ${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} için tanınmayan anahtar var.`;case"invalid_union":return"Giren tanınamadı.";case"invalid_element":return`${t.origin} için tanınmayan kıymet var.`;default:return"Kıymet tanınamadı."}}};function bf(){return{localeError:_$()}}var $$=()=>{let e={string:{unit:"توکي",verb:"ولري"},file:{unit:"بایټس",verb:"ولري"},array:{unit:"توکي",verb:"ولري"},set:{unit:"توکي",verb:"ولري"},map:{unit:"توکي",verb:"ولري"}};function n(t){return e[t]??null}let i={regex:"ورودي",email:"بریښنالیک",url:"یو آر ال",emoji:"ایموجي",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"نیټه او وخت",date:"نېټه",time:"وخت",duration:"موده",ipv4:"د IPv4 پته",ipv6:"د IPv6 پته",mac:"د MAC پته",cidrv4:"د IPv4 ساحه",cidrv6:"د IPv6 ساحه",base64:"base64-encoded متن",base64url:"base64url-encoded متن",json_string:"JSON متن",e164:"د E.164 شمېره",credit_card:"د کریډیټ کارت شمیره",currency_code:"د اسعارو کوډ",iban:"IBAN",jwt:"JWT",template_literal:"ورودي"},o={nan:"NaN",number:"عدد",array:"ارې"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ناسم ورودي: باید instanceof ${t.expected} وای, مګر ${s} ترلاسه شو`:`ناسم ورودي: باید ${r} وای, مګر ${s} ترلاسه شو`}case"invalid_value":return t.values.length===1?`ناسم ورودي: باید ${$(t.values[0])} وای`:`ناسم انتخاب: باید یو له ${v(t.values,"|")} څخه وای`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ډیر لوی: ${t.origin??"ارزښت"} باید ${r}${t.maximum.toString()} ${a.unit??"عنصرونه"} ولري`:`ډیر لوی: ${t.origin??"ارزښت"} باید ${r}${t.maximum.toString()} وي`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ډیر کوچنی: ${t.origin} باید ${r}${t.minimum.toString()} ${a.unit} ولري`:`ډیر کوچنی: ${t.origin} باید ${r}${t.minimum.toString()} وي`}case"invalid_format":{let r=t;return r.format==="starts_with"?`ناسم متن: باید د "${r.prefix}" سره پیل شي`:r.format==="ends_with"?`ناسم متن: باید د "${r.suffix}" سره پای ته ورسيږي`:r.format==="includes"?`ناسم متن: باید "${r.includes}" ولري`:r.format==="regex"?`ناسم متن: باید د ${r.pattern} سره مطابقت ولري`:`${i[r.format]??t.format} ناسم دی`}case"not_multiple_of":return`ناسم عدد: باید د ${t.divisor} مضرب وي`;case"unrecognized_keys":return`ناسم ${t.keys.length>1?"کلیډونه":"کلیډ"}: ${v(t.keys,", ")}`;case"invalid_key":return`ناسم کلیډ په ${t.origin} کې`;case"invalid_union":return"ناسمه ورودي";case"invalid_element":return`ناسم عنصر په ${t.origin} کې`;default:return"ناسمه ورودي"}}};function _f(){return{localeError:$$()}}var k$=()=>{let e={string:{unit:"znaków",verb:"mieć"},file:{unit:"bajtów",verb:"mieć"},array:{unit:"elementów",verb:"mieć"},set:{unit:"elementów",verb:"mieć"},map:{unit:"elementów",verb:"mieć"}};function n(t){return e[t]??null}let i={regex:"wyrażenie",email:"adres email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data i godzina w formacie ISO",date:"data w formacie ISO",time:"godzina w formacie ISO",duration:"czas trwania ISO",ipv4:"adres IPv4",ipv6:"adres IPv6",mac:"adres MAC",cidrv4:"zakres IPv4",cidrv6:"zakres IPv6",base64:"ciąg znaków zakodowany w formacie base64",base64url:"ciąg znaków zakodowany w formacie base64url",json_string:"ciąg znaków w formacie JSON",e164:"liczba E.164",credit_card:"numer karty kredytowej",currency_code:"kod waluty",iban:"IBAN",jwt:"JWT",template_literal:"wejście"},o={nan:"NaN",number:"liczba",array:"tablica"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Nieprawidłowe dane wejściowe: oczekiwano instanceof ${t.expected}, otrzymano ${s}`:`Nieprawidłowe dane wejściowe: oczekiwano ${r}, otrzymano ${s}`}case"invalid_value":return t.values.length===1?`Nieprawidłowe dane wejściowe: oczekiwano ${$(t.values[0])}`:`Nieprawidłowa opcja: oczekiwano jednej z wartości ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Za duża wartość: oczekiwano, że ${t.origin??"wartość"} będzie mieć ${r}${t.maximum.toString()} ${a.unit??"elementów"}`:`Zbyt duż(y/a/e): oczekiwano, że ${t.origin??"wartość"} będzie wynosić ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Za mała wartość: oczekiwano, że ${t.origin??"wartość"} będzie mieć ${r}${t.minimum.toString()} ${a.unit??"elementów"}`:`Zbyt mał(y/a/e): oczekiwano, że ${t.origin??"wartość"} będzie wynosić ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Nieprawidłowy ciąg znaków: musi zaczynać się od "${r.prefix}"`:r.format==="ends_with"?`Nieprawidłowy ciąg znaków: musi kończyć się na "${r.suffix}"`:r.format==="includes"?`Nieprawidłowy ciąg znaków: musi zawierać "${r.includes}"`:r.format==="regex"?`Nieprawidłowy ciąg znaków: musi odpowiadać wzorcowi ${r.pattern}`:`Nieprawidłow(y/a/e) ${i[r.format]??t.format}`}case"not_multiple_of":return`Nieprawidłowa liczba: musi być wielokrotnością ${t.divisor}`;case"unrecognized_keys":return`Nierozpoznane klucze${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Nieprawidłowy klucz w ${t.origin}`;case"invalid_union":return"Nieprawidłowe dane wejściowe";case"invalid_element":return`Nieprawidłowa wartość w ${t.origin}`;default:return"Nieprawidłowe dane wejściowe"}}};function $f(){return{localeError:k$()}}var x$=()=>{let e={string:{unit:"caracteres"},file:{unit:"bytes"},array:{unit:"elementos"},set:{unit:"elementos"},map:{unit:"entradas"}};function n(a){return e[a]??null}let i={regex:"a entrada",email:"o endereço de e-mail",url:"o URL",emoji:"o emoji",uuid:"o UUID",uuidv4:"o UUIDv4",uuidv6:"o UUIDv6",nanoid:"o nanoid",guid:"o GUID",cuid:"o cuid",cuid2:"o cuid2",ulid:"o ULID",xid:"o XID",ksuid:"o KSUID",datetime:"a data e hora ISO",date:"a data ISO",time:"a hora ISO",duration:"a duração ISO",ipv4:"o endereço IPv4",ipv6:"o endereço IPv6",mac:"o endereço MAC",cidrv4:"o intervalo de endereços IPv4",cidrv6:"o intervalo de endereços IPv6",base64:"o texto codificado em base64",base64url:"o texto codificado em base64url",json_string:"o texto JSON",e164:"o número E.164",credit_card:"o número de cartão de crédito",currency_code:"o código de moeda",iban:"o IBAN",jwt:"o JWT",template_literal:"a entrada"},o={masculine:{definite:"o",indefinite:"um"},feminine:{definite:"a",indefinite:"uma"}},t={string:{name:"texto",articles:o.masculine},number:{name:"número",articles:o.masculine},int:{name:"número inteiro",articles:o.masculine},boolean:{name:"valor booleano",articles:o.masculine},bigint:{name:"número bigint",articles:o.masculine},symbol:{name:"símbolo",articles:o.masculine},undefined:{name:'valor "undefined"',articles:o.masculine},null:{name:'valor "nulo"',articles:o.masculine},never:{name:'valor "never"',articles:o.masculine},void:{name:'valor "void"',articles:o.masculine},date:{name:"data",articles:o.feminine},array:{name:"vetor",articles:o.masculine},object:{name:"objeto",articles:o.masculine},tuple:{name:"tuplo",articles:o.masculine},record:{name:"registo",articles:o.masculine},map:{name:"mapa",articles:o.masculine},set:{name:"conjunto",articles:o.masculine},file:{name:"ficheiro",articles:o.masculine},nonoptional:{name:"valor não opcional",articles:o.masculine},nan:{name:'valor "NaN"',articles:o.masculine},function:{name:"função",articles:o.feminine}};function r(a,s){let c=t[a]??{name:`valor "${a}"`,articles:o.masculine};return`${c.articles[s]} ${c.name}`}return a=>{switch(a.code){case"invalid_type":{let s=r(a.expected,"indefinite"),c=k(a.input),u=r(c,"indefinite");return`Entrada inválida: esperava ${s}, recebeu ${u}`}case"invalid_value":return a.values.length===1?`Entrada inválida: esperava ${$(a.values[0])}`:`Opção inválida: esperava uma das seguintes opções: ${v(a.values,"|")}`;case"too_big":{let s=a.inclusive?"<=":"<",c=n(a.origin);return c?`Demasiado grande: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.maximum.toString()} ${c.unit??"elementos"}`:`Demasiado grande: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.maximum.toString()}`}case"too_small":{let s=a.inclusive?">=":">",c=n(a.origin);return c?`Demasiado pequeno: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.minimum.toString()} ${c.unit??"elementos"}`:`Demasiado pequeno: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.minimum.toString()}`}case"invalid_format":{let s=a;return s.format==="starts_with"?`Texto inválido: deve começar por "${s.prefix}"`:s.format==="ends_with"?`Texto inválido: deve terminar em "${s.suffix}"`:s.format==="includes"?`Texto inválido: deve incluir "${s.includes}"`:s.format==="regex"?`Texto inválido: deve corresponder ao padrão ${s.pattern}`:`Formato d${i[s.format]??a.format} inválido`}case"not_multiple_of":return`Número inválido: deve ser múltiplo de ${a.divisor}`;case"unrecognized_keys":{let s=a.keys.length>1?"s":"";return`Chave${s} inválida${s}: ${v(a.keys,", ")}`}case"invalid_key":return`Entrada inválida n${r(a.origin,"definite")}`;case"invalid_union":return a.options&&Array.isArray(a.options)&&a.options.length>0?`Valor de discriminação inválido. Esperava ${a.options.map(c=>`'${c}'`).join(" | ")}`:"Entrada inválida";case"invalid_element":return`Entrada inválida n${r(a.origin,"definite")}`;default:return"Entrada inválida"}}};function kf(){return{localeError:x$()}}var w$=()=>{let e={string:{unit:"caracteres"},file:{unit:"bytes"},array:{unit:"elementos"},set:{unit:"elementos"},map:{unit:"entradas"}};function n(a){return e[a]??null}let i={regex:"a entrada",email:"o endereço de e-mail",url:"o URL",emoji:"o emoji",uuid:"o UUID",uuidv4:"o UUIDv4",uuidv6:"o UUIDv6",nanoid:"o nanoid",guid:"o GUID",cuid:"o cuid",cuid2:"o cuid2",ulid:"o ULID",xid:"o XID",ksuid:"o KSUID",datetime:"a data e hora ISO",date:"a data ISO",time:"a hora ISO",duration:"a duração ISO",ipv4:"o endereço IPv4",ipv6:"o endereço IPv6",mac:"o endereço MAC",cidrv4:"a faixa de endereços IPv4",cidrv6:"a faixa de endereços IPv6",base64:"o texto codificado em base64",base64url:"o texto codificado em base64url",json_string:"o texto JSON",e164:"o número E.164",credit_card:"o número de cartão de crédito",currency_code:"o código de moeda",iban:"o IBAN",jwt:"o JWT",template_literal:"a entrada"},o={masculine:{definite:"o",indefinite:"um"},feminine:{definite:"a",indefinite:"uma"}},t={string:{name:"texto",articles:o.masculine},number:{name:"número",articles:o.masculine},int:{name:"número inteiro",articles:o.masculine},boolean:{name:"valor booleano",articles:o.masculine},bigint:{name:"número bigint",articles:o.masculine},symbol:{name:"símbolo",articles:o.masculine},undefined:{name:'valor "undefined"',articles:o.masculine},null:{name:'valor "nulo"',articles:o.masculine},never:{name:'valor "never"',articles:o.masculine},void:{name:'valor "void"',articles:o.masculine},date:{name:"data",articles:o.feminine},array:{name:"vetor",articles:o.masculine},object:{name:"objeto",articles:o.masculine},tuple:{name:"tupla",articles:o.feminine},record:{name:"registro",articles:o.masculine},map:{name:"mapa",articles:o.masculine},set:{name:"conjunto",articles:o.masculine},file:{name:"arquivo",articles:o.masculine},nonoptional:{name:"valor não opcional",articles:o.masculine},nan:{name:'valor "NaN"',articles:o.masculine},function:{name:"função",articles:o.feminine}};function r(a,s){let c=t[a]??{name:`valor "${a}"`,articles:o.masculine};return`${c.articles[s]} ${c.name}`}return a=>{switch(a.code){case"invalid_type":{let s=r(a.expected,"indefinite"),c=k(a.input),u=r(c,"indefinite");return`Entrada inválida: esperava ${s}, recebeu ${u}`}case"invalid_value":return a.values.length===1?`Entrada inválida: esperava ${$(a.values[0])}`:`Opção inválida: esperava uma das seguintes opções: ${v(a.values,"|")}`;case"too_big":{let s=a.inclusive?"<=":"<",c=n(a.origin);return c?`Grande demais: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.maximum.toString()} ${c.unit??"elementos"}`:`Grande demais: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.maximum.toString()}`}case"too_small":{let s=a.inclusive?">=":">",c=n(a.origin);return c?`Pequeno demais: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.minimum.toString()} ${c.unit??"elementos"}`:`Pequeno demais: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.minimum.toString()}`}case"invalid_format":{let s=a;return s.format==="starts_with"?`Texto inválido: deve começar com "${s.prefix}"`:s.format==="ends_with"?`Texto inválido: deve terminar com "${s.suffix}"`:s.format==="includes"?`Texto inválido: deve incluir "${s.includes}"`:s.format==="regex"?`Texto inválido: deve corresponder ao padrão ${s.pattern}`:`Formato d${i[s.format]??a.format} inválido`}case"not_multiple_of":return`Número inválido: deve ser múltiplo de ${a.divisor}`;case"unrecognized_keys":{let s=a.keys.length>1?"s":"";return`Chave${s} inválida${s}: ${v(a.keys,", ")}`}case"invalid_key":return`Entrada inválida n${r(a.origin,"definite")}`;case"invalid_union":return a.options&&Array.isArray(a.options)&&a.options.length>0?`Valor de discriminação inválido. Esperava ${a.options.map(c=>`'${c}'`).join(" | ")}`:"Entrada inválida";case"invalid_element":return`Entrada inválida n${r(a.origin,"definite")}`;default:return"Entrada inválida"}}};function xf(){return{localeError:w$()}}var I$=()=>{let e={string:{unit:"caractere",verb:"să aibă"},file:{unit:"octeți",verb:"să aibă"},array:{unit:"elemente",verb:"să aibă"},set:{unit:"elemente",verb:"să aibă"},map:{unit:"intrări",verb:"să aibă"}};function n(t){return e[t]??null}let i={regex:"intrare",email:"adresă de email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"dată și oră ISO",date:"dată ISO",time:"oră ISO",duration:"durată ISO",ipv4:"adresă IPv4",ipv6:"adresă IPv6",mac:"adresă MAC",cidrv4:"interval IPv4",cidrv6:"interval IPv6",base64:"șir codat base64",base64url:"șir codat base64url",json_string:"șir JSON",e164:"număr E.164",credit_card:"număr de card de credit",currency_code:"cod valutar",iban:"IBAN",jwt:"JWT",template_literal:"intrare"},o={nan:"NaN",string:"șir",number:"număr",boolean:"boolean",function:"funcție",array:"matrice",object:"obiect",undefined:"nedefinit",symbol:"simbol",bigint:"număr mare",void:"void",never:"never",map:"hartă",set:"set"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`Intrare invalidă: așteptat ${r}, primit ${s}`}case"invalid_value":return t.values.length===1?`Intrare invalidă: așteptat ${$(t.values[0])}`:`Opțiune invalidă: așteptat una dintre ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Prea mare: așteptat ca ${t.origin??"valoarea"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"elemente"}`:`Prea mare: așteptat ca ${t.origin??"valoarea"} să fie ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Prea mic: așteptat ca ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Prea mic: așteptat ca ${t.origin} să fie ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Șir invalid: trebuie să înceapă cu "${r.prefix}"`:r.format==="ends_with"?`Șir invalid: trebuie să se termine cu "${r.suffix}"`:r.format==="includes"?`Șir invalid: trebuie să includă "${r.includes}"`:r.format==="regex"?`Șir invalid: trebuie să se potrivească cu modelul ${r.pattern}`:`Format invalid: ${i[r.format]??t.format}`}case"not_multiple_of":return`Număr invalid: trebuie să fie multiplu de ${t.divisor}`;case"unrecognized_keys":return`Chei nerecunoscute: ${v(t.keys,", ")}`;case"invalid_key":return`Cheie invalidă în ${t.origin}`;case"invalid_union":return"Intrare invalidă";case"invalid_element":return`Valoare invalidă în ${t.origin}`;default:return"Intrare invalidă"}}};function wf(){return{localeError:I$()}}function If(e,n,i,o){let t=Math.abs(e),r=t%10,a=t%100;return a>=11&&a<=19?o:r===1?n:r>=2&&r<=4?i:o}var S$=()=>{let e={string:{unit:{one:"символ",few:"символа",many:"символов"},verb:"иметь"},file:{unit:{one:"байт",few:"байта",many:"байт"},verb:"иметь"},array:{unit:{one:"элемент",few:"элемента",many:"элементов"},verb:"иметь"},set:{unit:{one:"элемент",few:"элемента",many:"элементов"},verb:"иметь"},map:{unit:{one:"элемент",few:"элемента",many:"элементов"},verb:"иметь"}};function n(t){return e[t]??null}let i={regex:"ввод",email:"email адрес",url:"URL",emoji:"эмодзи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO дата и время",date:"ISO дата",time:"ISO время",duration:"ISO длительность",ipv4:"IPv4 адрес",ipv6:"IPv6 адрес",mac:"MAC адрес",cidrv4:"IPv4 диапазон",cidrv6:"IPv6 диапазон",base64:"строка в формате base64",base64url:"строка в формате base64url",json_string:"JSON строка",e164:"номер E.164",credit_card:"номер кредитной карты",currency_code:"код валюты",iban:"IBAN",jwt:"JWT",template_literal:"ввод"},o={nan:"NaN",number:"число",array:"массив"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Неверный ввод: ожидалось instanceof ${t.expected}, получено ${s}`:`Неверный ввод: ожидалось ${r}, получено ${s}`}case"invalid_value":return t.values.length===1?`Неверный ввод: ожидалось ${$(t.values[0])}`:`Неверный вариант: ожидалось одно из ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);if(a){let s=Number(t.maximum),c=If(s,a.unit.one,a.unit.few,a.unit.many);return`Слишком большое значение: ожидалось, что ${t.origin??"значение"} будет иметь ${r}${t.maximum.toString()} ${c}`}return`Слишком большое значение: ожидалось, что ${t.origin??"значение"} будет ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);if(a){let s=Number(t.minimum),c=If(s,a.unit.one,a.unit.few,a.unit.many);return`Слишком маленькое значение: ожидалось, что ${t.origin} будет иметь ${r}${t.minimum.toString()} ${c}`}return`Слишком маленькое значение: ожидалось, что ${t.origin} будет ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Неверная строка: должна начинаться с "${r.prefix}"`:r.format==="ends_with"?`Неверная строка: должна заканчиваться на "${r.suffix}"`:r.format==="includes"?`Неверная строка: должна содержать "${r.includes}"`:r.format==="regex"?`Неверная строка: должна соответствовать шаблону ${r.pattern}`:`Неверный ${i[r.format]??t.format}`}case"not_multiple_of":return`Неверное число: должно быть кратным ${t.divisor}`;case"unrecognized_keys":return`Нераспознанн${t.keys.length>1?"ые":"ый"} ключ${t.keys.length>1?"и":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Неверный ключ в ${t.origin}`;case"invalid_union":return"Неверные входные данные";case"invalid_element":return`Неверное значение в ${t.origin}`;default:return"Неверные входные данные"}}};function Sf(){return{localeError:S$()}}var P$=()=>{let e={string:{unit:"znakov",verb:"mať"},file:{unit:"bajtov",verb:"mať"},array:{unit:"prvkov",verb:"mať"},set:{unit:"prvkov",verb:"mať"},map:{unit:"položiek",verb:"mať"}};function n(t){return e[t]??null}let i={regex:"regulárny výraz",email:"e-mailová adresa",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"dátum a čas vo formáte ISO",date:"dátum vo formáte ISO",time:"čas vo formáte ISO",duration:"doba trvania ISO",ipv4:"IPv4 adresa",ipv6:"IPv6 adresa",mac:"MAC adresa",cidrv4:"rozsah IPv4",cidrv6:"rozsah IPv6",base64:"reťazec zakódovaný vo formáte base64",base64url:"reťazec zakódovaný vo formáte base64url",json_string:"reťazec vo formáte JSON",e164:"číslo E.164",credit_card:"číslo kreditnej karty",currency_code:"kód meny",iban:"IBAN",jwt:"JWT",template_literal:"vstup"},o={nan:"NaN",number:"číslo",string:"reťazec",function:"funkcia",array:"pole"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neplatný vstup: očakávané instanceof ${t.expected}, obdržané ${s}`:`Neplatný vstup: očakávané ${r}, obdržané ${s}`}case"invalid_value":return t.values.length===1?`Neplatný vstup: očakávané ${$(t.values[0])}`:`Neplatný vstup: očakávaná jedna z hodnôt ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Hodnota je príliš veľká: ${t.origin??"hodnota"} musí mať ${r}${t.maximum.toString()} ${a.unit??"prvkov"}`:`Hodnota je príliš veľká: ${t.origin??"hodnota"} musí byť ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Hodnota je príliš malá: ${t.origin??"hodnota"} musí mať ${r}${t.minimum.toString()} ${a.unit??"prvkov"}`:`Hodnota je príliš malá: ${t.origin??"hodnota"} musí byť ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neplatný reťazec: musí začínať na "${r.prefix}"`:r.format==="ends_with"?`Neplatný reťazec: musí končiť na "${r.suffix}"`:r.format==="includes"?`Neplatný reťazec: musí obsahovať "${r.includes}"`:r.format==="regex"?`Neplatný reťazec: musí zodpovedať vzoru ${r.pattern}`:`Neplatný formát ${i[r.format]??t.format}`}case"not_multiple_of":return`Neplatné číslo: musí byť násobkom ${t.divisor}`;case"unrecognized_keys":return`Neznáme klúče: ${v(t.keys,", ")}`;case"invalid_key":return`Neplatný klúč v ${t.origin}`;case"invalid_union":return"Neplatný vstup";case"invalid_element":return`Neplatná hodnota v ${t.origin}`;default:return"Neplatný vstup"}}};function Pf(){return{localeError:P$()}}var T$=()=>{let e={string:{unit:"znakov",verb:"imeti"},file:{unit:"bajtov",verb:"imeti"},array:{unit:"elementov",verb:"imeti"},set:{unit:"elementov",verb:"imeti"},map:{unit:"elementov",verb:"imeti"}};function n(t){return e[t]??null}let i={regex:"vnos",email:"e-poštni naslov",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datum in čas",date:"ISO datum",time:"ISO čas",duration:"ISO trajanje",ipv4:"IPv4 naslov",ipv6:"IPv6 naslov",mac:"MAC naslov",cidrv4:"obseg IPv4",cidrv6:"obseg IPv6",base64:"base64 kodiran niz",base64url:"base64url kodiran niz",json_string:"JSON niz",e164:"E.164 številka",credit_card:"številka kreditne kartice",currency_code:"koda valute",iban:"IBAN",jwt:"JWT",template_literal:"vnos"},o={nan:"NaN",number:"število",array:"tabela"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neveljaven vnos: pričakovano instanceof ${t.expected}, prejeto ${s}`:`Neveljaven vnos: pričakovano ${r}, prejeto ${s}`}case"invalid_value":return t.values.length===1?`Neveljaven vnos: pričakovano ${$(t.values[0])}`:`Neveljavna možnost: pričakovano eno izmed ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Preveliko: pričakovano, da bo ${t.origin??"vrednost"} imelo ${r}${t.maximum.toString()} ${a.unit??"elementov"}`:`Preveliko: pričakovano, da bo ${t.origin??"vrednost"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Premajhno: pričakovano, da bo ${t.origin} imelo ${r}${t.minimum.toString()} ${a.unit}`:`Premajhno: pričakovano, da bo ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neveljaven niz: mora se začeti z "${r.prefix}"`:r.format==="ends_with"?`Neveljaven niz: mora se končati z "${r.suffix}"`:r.format==="includes"?`Neveljaven niz: mora vsebovati "${r.includes}"`:r.format==="regex"?`Neveljaven niz: mora ustrezati vzorcu ${r.pattern}`:`Neveljaven ${i[r.format]??t.format}`}case"not_multiple_of":return`Neveljavno število: mora biti večkratnik ${t.divisor}`;case"unrecognized_keys":return`Neprepoznan${t.keys.length>1?"i ključi":" ključ"}: ${v(t.keys,", ")}`;case"invalid_key":return`Neveljaven ključ v ${t.origin}`;case"invalid_union":return"Neveljaven vnos";case"invalid_element":return`Neveljavna vrednost v ${t.origin}`;default:return"Neveljaven vnos"}}};function Tf(){return{localeError:T$()}}var E$=()=>{let e={string:{unit:"tecken",verb:"att ha"},file:{unit:"bytes",verb:"att ha"},array:{unit:"objekt",verb:"att innehålla"},set:{unit:"objekt",verb:"att innehålla"},map:{unit:"objekt",verb:"att innehålla"}};function n(t){return e[t]??null}let i={regex:"reguljärt uttryck",email:"e-postadress",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-datum och tid",date:"ISO-datum",time:"ISO-tid",duration:"ISO-varaktighet",ipv4:"IPv4-adress",ipv6:"IPv6-adress",mac:"MAC-adress",cidrv4:"IPv4-spektrum",cidrv6:"IPv6-spektrum",base64:"base64-kodad sträng",base64url:"base64url-kodad sträng",json_string:"JSON-sträng",e164:"E.164-nummer",credit_card:"kreditkortsnummer",currency_code:"valutakod",iban:"IBAN",jwt:"JWT",template_literal:"mall-literal"},o={nan:"NaN",number:"antal",array:"lista"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ogiltig inmatning: förväntat instanceof ${t.expected}, fick ${s}`:`Ogiltig inmatning: förväntat ${r}, fick ${s}`}case"invalid_value":return t.values.length===1?`Ogiltig inmatning: förväntat ${$(t.values[0])}`:`Ogiltigt val: förväntade en av ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`För stor(t): förväntade ${t.origin??"värdet"} att ha ${r}${t.maximum.toString()} ${a.unit??"element"}`:`För stor(t): förväntat ${t.origin??"värdet"} att ha ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`För lite(t): förväntade ${t.origin??"värdet"} att ha ${r}${t.minimum.toString()} ${a.unit}`:`För lite(t): förväntade ${t.origin??"värdet"} att ha ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ogiltig sträng: måste börja med "${r.prefix}"`:r.format==="ends_with"?`Ogiltig sträng: måste sluta med "${r.suffix}"`:r.format==="includes"?`Ogiltig sträng: måste innehålla "${r.includes}"`:r.format==="regex"?`Ogiltig sträng: måste matcha mönstret "${r.pattern}"`:`Ogiltig(t) ${i[r.format]??t.format}`}case"not_multiple_of":return`Ogiltigt tal: måste vara en multipel av ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Okända nycklar":"Okänd nyckel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ogiltig nyckel i ${t.origin??"värdet"}`;case"invalid_union":return"Ogiltig input";case"invalid_element":return`Ogiltigt värde i ${t.origin??"värdet"}`;default:return"Ogiltig input"}}};function Ef(){return{localeError:E$()}}var A$=()=>{let e={string:{unit:"எழுத்துக்கள்",verb:"கொண்டிருக்க வேண்டும்"},file:{unit:"பைட்டுகள்",verb:"கொண்டிருக்க வேண்டும்"},array:{unit:"உறுப்புகள்",verb:"கொண்டிருக்க வேண்டும்"},set:{unit:"உறுப்புகள்",verb:"கொண்டிருக்க வேண்டும்"},map:{unit:"உறுப்புகள்",verb:"கொண்டிருக்க வேண்டும்"}};function n(t){return e[t]??null}let i={regex:"உள்ளீடு",email:"மின்னஞ்சல் முகவரி",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO தேதி நேரம்",date:"ISO தேதி",time:"ISO நேரம்",duration:"ISO கால அளவு",ipv4:"IPv4 முகவரி",ipv6:"IPv6 முகவரி",mac:"MAC முகவரி",cidrv4:"IPv4 வரம்பு",cidrv6:"IPv6 வரம்பு",base64:"base64-encoded சரம்",base64url:"base64url-encoded சரம்",json_string:"JSON சரம்",e164:"E.164 எண்",credit_card:"கடன் அட்டை எண்",currency_code:"நாணயக் குறியீடு",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"எண்",array:"அணி",null:"வெறுமை"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது instanceof ${t.expected}, பெறப்பட்டது ${s}`:`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது ${r}, பெறப்பட்டது ${s}`}case"invalid_value":return t.values.length===1?`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது ${$(t.values[0])}`:`தவறான விருப்பம்: எதிர்பார்க்கப்பட்டது ${v(t.values,"|")} இல் ஒன்று`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`மிக பெரியது: எதிர்பார்க்கப்பட்டது ${t.origin??"மதிப்பு"} ${r}${t.maximum.toString()} ${a.unit??"உறுப்புகள்"} ஆக இருக்க வேண்டும்`:`மிக பெரியது: எதிர்பார்க்கப்பட்டது ${t.origin??"மதிப்பு"} ${r}${t.maximum.toString()} ஆக இருக்க வேண்டும்`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`மிகச் சிறியது: எதிர்பார்க்கப்பட்டது ${t.origin} ${r}${t.minimum.toString()} ${a.unit} ஆக இருக்க வேண்டும்`:`மிகச் சிறியது: எதிர்பார்க்கப்பட்டது ${t.origin} ${r}${t.minimum.toString()} ஆக இருக்க வேண்டும்`}case"invalid_format":{let r=t;return r.format==="starts_with"?`தவறான சரம்: "${r.prefix}" இல் தொடங்க வேண்டும்`:r.format==="ends_with"?`தவறான சரம்: "${r.suffix}" இல் முடிவடைய வேண்டும்`:r.format==="includes"?`தவறான சரம்: "${r.includes}" ஐ உள்ளடக்க வேண்டும்`:r.format==="regex"?`தவறான சரம்: ${r.pattern} முறைபாட்டுடன் பொருந்த வேண்டும்`:`தவறான ${i[r.format]??t.format}`}case"not_multiple_of":return`தவறான எண்: ${t.divisor} இன் பலமாக இருக்க வேண்டும்`;case"unrecognized_keys":return`அடையாளம் தெரியாத விசை${t.keys.length>1?"கள்":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} இல் தவறான விசை`;case"invalid_union":return"தவறான உள்ளீடு";case"invalid_element":return`${t.origin} இல் தவறான மதிப்பு`;default:return"தவறான உள்ளீடு"}}};function Af(){return{localeError:A$()}}var O$=()=>{let e={string:{unit:"аломат",verb:"дошта бошад"},file:{unit:"байт",verb:"дошта бошад"},array:{unit:"унсур",verb:"дошта бошад"},set:{unit:"унсур",verb:"дошта бошад"},map:{unit:"сабт",verb:"дошта бошад"}};function n(t){return e[t]??null}let i={regex:"вуруд",email:"суроғаи email",url:"URL",emoji:"эмоҷи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"санаву вақти ISO",date:"санаи ISO",time:"вақти ISO",duration:"давомнокии ISO",ipv4:"суроғаи IPv4",ipv6:"суроғаи IPv6",mac:"суроғаи MAC",cidrv4:"маҳдудаи IPv4",cidrv6:"маҳдудаи IPv6",base64:"сатри дар формати base64",base64url:"сатри дар формати base64url",json_string:"сатри JSON",e164:"рақами E.164",credit_card:"рақами корти кредитӣ",currency_code:"рамзи асъор",iban:"IBAN",jwt:"JWT",template_literal:"вуруд"},o={nan:"NaN",number:"рақам",string:"сатр",array:"массив",object:"объект",date:"сана"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`Вуруди нодуруст: ${r} интизор мерафт, ${s} гирифта шуд`}case"invalid_value":return t.values.length===1?`Вуруди нодуруст: ${$(t.values[0])} интизор мерафт`:`Интихоби нодуруст: яке аз ${v(t.values,"|")} интизор мерафт`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Хеле калон: ${t.origin??"қимат"} бояд ${r}${t.maximum.toString()} ${a.unit} ${a.verb}`:`Хеле калон: ${t.origin??"қимат"} бояд ${r}${t.maximum.toString()} бошад`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Хеле хурд: ${t.origin} бояд ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`Хеле хурд: ${t.origin} бояд ${r}${t.minimum.toString()} бошад`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Сатри нодуруст: бояд бо "${r.prefix}" оғоз шавад`:r.format==="ends_with"?`Сатри нодуруст: бояд бо "${r.suffix}" анҷом ёбад`:r.format==="includes"?`Сатри нодуруст: бояд "${r.includes}"-ро дар бар гирад`:r.format==="regex"?`Сатри нодуруст: бояд ба намунаи ${r.pattern} мувофиқат кунад`:`${i[r.format]??t.format}-и нодуруст`}case"not_multiple_of":return`Рақами нодуруст: бояд ба ${t.divisor} бе бақия тақсим шавад`;case"unrecognized_keys":return`Калид${t.keys.length>1?"ҳои":"и"} номаълум: ${v(t.keys,", ")}`;case"invalid_key":return`Калиди нодуруст дар ${t.origin}`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`Қимати нодурусти дискриминатор: ${t.options.map(a=>`'${a}'`).join(" | ")} интизор мерафт`:"Вуруди нодуруст";case"invalid_element":return`Қимати нодуруст дар ${t.origin}`;default:return"Вуруди нодуруст"}}};function Of(){return{localeError:O$()}}var N$=()=>{let e={string:{unit:"ตัวอักษร",verb:"ควรมี"},file:{unit:"ไบต์",verb:"ควรมี"},array:{unit:"รายการ",verb:"ควรมี"},set:{unit:"รายการ",verb:"ควรมี"},map:{unit:"รายการ",verb:"ควรมี"}};function n(t){return e[t]??null}let i={regex:"ข้อมูลที่ป้อน",email:"ที่อยู่อีเมล",url:"URL",emoji:"อิโมจิ",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"วันที่เวลาแบบ ISO",date:"วันที่แบบ ISO",time:"เวลาแบบ ISO",duration:"ช่วงเวลาแบบ ISO",ipv4:"ที่อยู่ IPv4",ipv6:"ที่อยู่ IPv6",mac:"ที่อยู่ MAC",cidrv4:"ช่วง IP แบบ IPv4",cidrv6:"ช่วง IP แบบ IPv6",base64:"ข้อความแบบ Base64",base64url:"ข้อความแบบ Base64 สำหรับ URL",json_string:"ข้อความแบบ JSON",e164:"เบอร์โทรศัพท์ระหว่างประเทศ (E.164)",credit_card:"หมายเลขบัตรเครดิต",currency_code:"รหัสสกุลเงิน",iban:"IBAN",jwt:"โทเคน JWT",template_literal:"ข้อมูลที่ป้อน"},o={nan:"NaN",number:"ตัวเลข",array:"อาร์เรย์ (Array)",null:"ไม่มีค่า (null)"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น instanceof ${t.expected} แต่ได้รับ ${s}`:`ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น ${r} แต่ได้รับ ${s}`}case"invalid_value":return t.values.length===1?`ค่าไม่ถูกต้อง: ควรเป็น ${$(t.values[0])}`:`ตัวเลือกไม่ถูกต้อง: ควรเป็นหนึ่งใน ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"ไม่เกิน":"น้อยกว่า",a=n(t.origin);return a?`เกินกำหนด: ${t.origin??"ค่า"} ควรมี${r} ${t.maximum.toString()} ${a.unit??"รายการ"}`:`เกินกำหนด: ${t.origin??"ค่า"} ควรมี${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?"อย่างน้อย":"มากกว่า",a=n(t.origin);return a?`น้อยกว่ากำหนด: ${t.origin} ควรมี${r} ${t.minimum.toString()} ${a.unit}`:`น้อยกว่ากำหนด: ${t.origin} ควรมี${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`รูปแบบไม่ถูกต้อง: ข้อความต้องขึ้นต้นด้วย "${r.prefix}"`:r.format==="ends_with"?`รูปแบบไม่ถูกต้อง: ข้อความต้องลงท้ายด้วย "${r.suffix}"`:r.format==="includes"?`รูปแบบไม่ถูกต้อง: ข้อความต้องมี "${r.includes}" อยู่ในข้อความ`:r.format==="regex"?`รูปแบบไม่ถูกต้อง: ต้องตรงกับรูปแบบที่กำหนด ${r.pattern}`:`รูปแบบไม่ถูกต้อง: ${i[r.format]??t.format}`}case"not_multiple_of":return`ตัวเลขไม่ถูกต้อง: ต้องเป็นจำนวนที่หารด้วย ${t.divisor} ได้ลงตัว`;case"unrecognized_keys":return`พบคีย์ที่ไม่รู้จัก: ${v(t.keys,", ")}`;case"invalid_key":return`คีย์ไม่ถูกต้องใน ${t.origin}`;case"invalid_union":return"ข้อมูลไม่ถูกต้อง: ไม่ตรงกับรูปแบบยูเนียนที่กำหนดไว้";case"invalid_element":return`ข้อมูลไม่ถูกต้องใน ${t.origin}`;default:return"ข้อมูลไม่ถูกต้อง"}}};function Nf(){return{localeError:N$()}}var z$=()=>{let e={string:{unit:"simwol",verb:"bolmaly"},file:{unit:"baýt",verb:"bolmaly"},array:{unit:"elementler",verb:"bolmaly"},set:{unit:"elementler",verb:"bolmaly"},map:{unit:"elementler",verb:"bolmaly"}};function n(t){return e[t]??null}let i={regex:"giriş",email:"e-poçta salgysy",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO sene we wagt",date:"ISO sene",time:"ISO wagt",duration:"ISO wagt aralygy",ipv4:"IPv4 salgysy",ipv6:"IPv6 salgysy",mac:"MAC salgysy",cidrv4:"IPv4 aralygy",cidrv6:"IPv6 aralygy",base64:"base64 bilen şifrlenen setir",base64url:"base64url bilen şifrlenen setir",json_string:"JSON setiri",e164:"E.164 nomeri",credit_card:"kredit kartynyň nomeri",currency_code:"walýuta kody",iban:"IBAN",jwt:"JWT",template_literal:"şablon"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`Nädogry baha: garaşylan ${r} ýerine ${s} alyndy`}case"invalid_value":return t.values.length===1?`Nädogry baha: ${$(t.values[0])} bolmaly`:`Nädogry saýlaw: aşakdakylardan biri bolmaly: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Has uly: garaşylýan ${t.origin??"baha"} ${r} ${t.maximum.toString()} ${a.unit??"element"}`:`Has uly: garaşylýan ${t.origin??"baha"} ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Has kiçi: garaşylýan ${t.origin} ${r} ${t.minimum.toString()} ${a.unit}`:`Has kiçi: garaşylýan ${t.origin} ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Nädogry setir: "${r.prefix}" bilen başlamaly`:r.format==="ends_with"?`Nädogry setir: "${r.suffix}" bilen gutarmaly`:r.format==="includes"?`Nädogry setir: "${r.includes}" saklamaly`:r.format==="regex"?`Nädogry setir: ${r.pattern} nusga laýyk bolmaly`:`Nädogry ${i[r.format]??t.format}`}case"not_multiple_of":return`Nädogry san: ${t.divisor} bilen galyndysyz bölünmeli`;case"unrecognized_keys":return`Tanalmaýan açar${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} içinde nädogry açar`;case"invalid_union":return"Nädogry baha";case"invalid_element":return`${t.origin} içinde nädogry baha`;default:return"Nädogry baha"}}};function zf(){return{localeError:z$()}}var D$=()=>{let e={string:{unit:"karakter",verb:"olmalı"},file:{unit:"bayt",verb:"olmalı"},array:{unit:"öğe",verb:"olmalı"},set:{unit:"öğe",verb:"olmalı"},map:{unit:"öğe",verb:"olmalı"}};function n(t){return e[t]??null}let i={regex:"girdi",email:"e-posta adresi",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO tarih ve saat",date:"ISO tarih",time:"ISO saat",duration:"ISO süre",ipv4:"IPv4 adresi",ipv6:"IPv6 adresi",mac:"MAC adresi",cidrv4:"IPv4 aralığı",cidrv6:"IPv6 aralığı",base64:"base64 ile şifrelenmiş metin",base64url:"base64url ile şifrelenmiş metin",json_string:"JSON dizesi",e164:"E.164 sayısı",credit_card:"kredi kartı numarası",currency_code:"para birimi kodu",iban:"IBAN",jwt:"JWT",template_literal:"Şablon dizesi"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Geçersiz değer: beklenen instanceof ${t.expected}, alınan ${s}`:`Geçersiz değer: beklenen ${r}, alınan ${s}`}case"invalid_value":return t.values.length===1?`Geçersiz değer: beklenen ${$(t.values[0])}`:`Geçersiz seçenek: aşağıdakilerden biri olmalı: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Çok büyük: beklenen ${t.origin??"değer"} ${r}${t.maximum.toString()} ${a.unit??"öğe"}`:`Çok büyük: beklenen ${t.origin??"değer"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Çok küçük: beklenen ${t.origin} ${r}${t.minimum.toString()} ${a.unit}`:`Çok küçük: beklenen ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Geçersiz metin: "${r.prefix}" ile başlamalı`:r.format==="ends_with"?`Geçersiz metin: "${r.suffix}" ile bitmeli`:r.format==="includes"?`Geçersiz metin: "${r.includes}" içermeli`:r.format==="regex"?`Geçersiz metin: ${r.pattern} desenine uymalı`:`Geçersiz ${i[r.format]??t.format}`}case"not_multiple_of":return`Geçersiz sayı: ${t.divisor} ile tam bölünebilmeli`;case"unrecognized_keys":return`Tanınmayan anahtar${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} içinde geçersiz anahtar`;case"invalid_union":return"Geçersiz değer";case"invalid_element":return`${t.origin} içinde geçersiz değer`;default:return"Geçersiz değer"}}};function Df(){return{localeError:D$()}}var C$=()=>{let e={string:{unit:"символів",verb:"матиме"},file:{unit:"байтів",verb:"матиме"},array:{unit:"елементів",verb:"матиме"},set:{unit:"елементів",verb:"матиме"},map:{unit:"елементів",verb:"матиме"}};function n(t){return e[t]??null}let i={regex:"вхідні дані",email:"адреса електронної пошти",url:"URL",emoji:"емодзі",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"дата та час ISO",date:"дата ISO",time:"час ISO",duration:"тривалість ISO",ipv4:"адреса IPv4",ipv6:"адреса IPv6",mac:"адреса MAC",cidrv4:"діапазон IPv4",cidrv6:"діапазон IPv6",base64:"рядок у кодуванні base64",base64url:"рядок у кодуванні base64url",json_string:"рядок JSON",e164:"номер E.164",credit_card:"номер кредитної картки",currency_code:"код валюти",iban:"IBAN",jwt:"JWT",template_literal:"вхідні дані"},o={nan:"NaN",number:"число",array:"масив"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Неправильні вхідні дані: очікується instanceof ${t.expected}, отримано ${s}`:`Неправильні вхідні дані: очікується ${r}, отримано ${s}`}case"invalid_value":return t.values.length===1?`Неправильні вхідні дані: очікується ${$(t.values[0])}`:`Неправильна опція: очікується одне з ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Занадто велике: очікується, що ${t.origin??"значення"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"елементів"}`:`Занадто велике: очікується, що ${t.origin??"значення"} буде ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Занадто мале: очікується, що ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Занадто мале: очікується, що ${t.origin} буде ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Неправильний рядок: повинен починатися з "${r.prefix}"`:r.format==="ends_with"?`Неправильний рядок: повинен закінчуватися на "${r.suffix}"`:r.format==="includes"?`Неправильний рядок: повинен містити "${r.includes}"`:r.format==="regex"?`Неправильний рядок: повинен відповідати шаблону ${r.pattern}`:`Неправильний ${i[r.format]??t.format}`}case"not_multiple_of":return`Неправильне число: повинно бути кратним ${t.divisor}`;case"unrecognized_keys":return`Нерозпізнаний ключ${t.keys.length>1?"і":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Неправильний ключ у ${t.origin}`;case"invalid_union":return"Неправильні вхідні дані";case"invalid_element":return`Неправильне значення у ${t.origin}`;default:return"Неправильні вхідні дані"}}};function Xi(){return{localeError:C$()}}function Cf(){return Xi()}var j$=()=>{let e={string:{unit:"حروف",verb:"ہونا"},file:{unit:"بائٹس",verb:"ہونا"},array:{unit:"آئٹمز",verb:"ہونا"},set:{unit:"آئٹمز",verb:"ہونا"},map:{unit:"آئٹمز",verb:"ہونا"}};function n(t){return e[t]??null}let i={regex:"ان پٹ",email:"ای میل ایڈریس",url:"یو آر ایل",emoji:"ایموجی",uuid:"یو یو آئی ڈی",uuidv4:"یو یو آئی ڈی وی 4",uuidv6:"یو یو آئی ڈی وی 6",nanoid:"نینو آئی ڈی",guid:"جی یو آئی ڈی",cuid:"سی یو آئی ڈی",cuid2:"سی یو آئی ڈی 2",ulid:"یو ایل آئی ڈی",xid:"ایکس آئی ڈی",ksuid:"کے ایس یو آئی ڈی",datetime:"آئی ایس او ڈیٹ ٹائم",date:"آئی ایس او تاریخ",time:"آئی ایس او وقت",duration:"آئی ایس او مدت",ipv4:"آئی پی وی 4 ایڈریس",ipv6:"آئی پی وی 6 ایڈریس",mac:"ایم اے سی ایڈریس",cidrv4:"آئی پی وی 4 رینج",cidrv6:"آئی پی وی 6 رینج",base64:"بیس 64 ان کوڈڈ سٹرنگ",base64url:"بیس 64 یو آر ایل ان کوڈڈ سٹرنگ",json_string:"جے ایس او این سٹرنگ",e164:"ای 164 نمبر",credit_card:"کریڈٹ کارڈ نمبر",currency_code:"کرنسی کوڈ",iban:"IBAN",jwt:"جے ڈبلیو ٹی",template_literal:"ان پٹ"},o={nan:"NaN",number:"نمبر",array:"آرے",null:"نل"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`غلط ان پٹ: instanceof ${t.expected} متوقع تھا، ${s} موصول ہوا`:`غلط ان پٹ: ${r} متوقع تھا، ${s} موصول ہوا`}case"invalid_value":return t.values.length===1?`غلط ان پٹ: ${$(t.values[0])} متوقع تھا`:`غلط آپشن: ${v(t.values,"|")} میں سے ایک متوقع تھا`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`بہت بڑا: ${t.origin??"ویلیو"} کے ${r}${t.maximum.toString()} ${a.unit??"عناصر"} ہونے متوقع تھے`:`بہت بڑا: ${t.origin??"ویلیو"} کا ${r}${t.maximum.toString()} ہونا متوقع تھا`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`بہت چھوٹا: ${t.origin} کے ${r}${t.minimum.toString()} ${a.unit} ہونے متوقع تھے`:`بہت چھوٹا: ${t.origin} کا ${r}${t.minimum.toString()} ہونا متوقع تھا`}case"invalid_format":{let r=t;return r.format==="starts_with"?`غلط سٹرنگ: "${r.prefix}" سے شروع ہونا چاہیے`:r.format==="ends_with"?`غلط سٹرنگ: "${r.suffix}" پر ختم ہونا چاہیے`:r.format==="includes"?`غلط سٹرنگ: "${r.includes}" شامل ہونا چاہیے`:r.format==="regex"?`غلط سٹرنگ: پیٹرن ${r.pattern} سے میچ ہونا چاہیے`:`غلط ${i[r.format]??t.format}`}case"not_multiple_of":return`غلط نمبر: ${t.divisor} کا مضاعف ہونا چاہیے`;case"unrecognized_keys":return`غیر تسلیم شدہ کی${t.keys.length>1?"ز":""}: ${v(t.keys,"، ")}`;case"invalid_key":return`${t.origin} میں غلط کی`;case"invalid_union":return"غلط ان پٹ";case"invalid_element":return`${t.origin} میں غلط ویلیو`;default:return"غلط ان پٹ"}}};function jf(){return{localeError:j$()}}var L$=()=>{let e={string:{unit:"belgi",verb:"bo‘lishi kerak"},file:{unit:"bayt",verb:"bo‘lishi kerak"},array:{unit:"element",verb:"bo‘lishi kerak"},set:{unit:"element",verb:"bo‘lishi kerak"},map:{unit:"yozuv",verb:"bo‘lishi kerak"}};function n(t){return e[t]??null}let i={regex:"kirish",email:"elektron pochta manzili",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO sana va vaqti",date:"ISO sana",time:"ISO vaqt",duration:"ISO davomiylik",ipv4:"IPv4 manzil",ipv6:"IPv6 manzil",mac:"MAC manzil",cidrv4:"IPv4 diapazon",cidrv6:"IPv6 diapazon",base64:"base64 kodlangan satr",base64url:"base64url kodlangan satr",json_string:"JSON satr",e164:"E.164 raqam",credit_card:"kredit karta raqami",currency_code:"valyuta kodi",iban:"IBAN",jwt:"JWT",template_literal:"kirish"},o={nan:"NaN",number:"raqam",array:"massiv"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Noto‘g‘ri kirish: kutilgan instanceof ${t.expected}, qabul qilingan ${s}`:`Noto‘g‘ri kirish: kutilgan ${r}, qabul qilingan ${s}`}case"invalid_value":return t.values.length===1?`Noto‘g‘ri kirish: kutilgan ${$(t.values[0])}`:`Noto‘g‘ri variant: quyidagilardan biri kutilgan ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Juda katta: kutilgan ${t.origin??"qiymat"} ${r}${t.maximum.toString()} ${a.unit} ${a.verb}`:`Juda katta: kutilgan ${t.origin??"qiymat"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Juda kichik: kutilgan ${t.origin} ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`Juda kichik: kutilgan ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Noto‘g‘ri satr: "${r.prefix}" bilan boshlanishi kerak`:r.format==="ends_with"?`Noto‘g‘ri satr: "${r.suffix}" bilan tugashi kerak`:r.format==="includes"?`Noto‘g‘ri satr: "${r.includes}" ni o‘z ichiga olishi kerak`:r.format==="regex"?`Noto‘g‘ri satr: ${r.pattern} shabloniga mos kelishi kerak`:`Noto‘g‘ri ${i[r.format]??t.format}`}case"not_multiple_of":return`Noto‘g‘ri raqam: ${t.divisor} ning karralisi bo‘lishi kerak`;case"unrecognized_keys":return`Noma’lum kalit${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} dagi kalit noto‘g‘ri`;case"invalid_union":return"Noto‘g‘ri kirish";case"invalid_element":return`${t.origin} da noto‘g‘ri qiymat`;default:return"Noto‘g‘ri kirish"}}};function Lf(){return{localeError:L$()}}var U$=()=>{let e={string:{unit:"ký tự",verb:"có"},file:{unit:"byte",verb:"có"},array:{unit:"phần tử",verb:"có"},set:{unit:"phần tử",verb:"có"},map:{unit:"phần tử",verb:"có"}};function n(t){return e[t]??null}let i={regex:"đầu vào",email:"địa chỉ email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ngày giờ ISO",date:"ngày ISO",time:"giờ ISO",duration:"khoảng thời gian ISO",ipv4:"địa chỉ IPv4",ipv6:"địa chỉ IPv6",mac:"địa chỉ MAC",cidrv4:"dải IPv4",cidrv6:"dải IPv6",base64:"chuỗi mã hóa base64",base64url:"chuỗi mã hóa base64url",json_string:"chuỗi JSON",e164:"số E.164",credit_card:"số thẻ tín dụng",currency_code:"mã tiền tệ",iban:"IBAN",jwt:"JWT",template_literal:"đầu vào"},o={nan:"NaN",number:"số",array:"mảng"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Đầu vào không hợp lệ: mong đợi instanceof ${t.expected}, nhận được ${s}`:`Đầu vào không hợp lệ: mong đợi ${r}, nhận được ${s}`}case"invalid_value":return t.values.length===1?`Đầu vào không hợp lệ: mong đợi ${$(t.values[0])}`:`Tùy chọn không hợp lệ: mong đợi một trong các giá trị ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Quá lớn: mong đợi ${t.origin??"giá trị"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"phần tử"}`:`Quá lớn: mong đợi ${t.origin??"giá trị"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Quá nhỏ: mong đợi ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Quá nhỏ: mong đợi ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Chuỗi không hợp lệ: phải bắt đầu bằng "${r.prefix}"`:r.format==="ends_with"?`Chuỗi không hợp lệ: phải kết thúc bằng "${r.suffix}"`:r.format==="includes"?`Chuỗi không hợp lệ: phải bao gồm "${r.includes}"`:r.format==="regex"?`Chuỗi không hợp lệ: phải khớp với mẫu ${r.pattern}`:`${i[r.format]??t.format} không hợp lệ`}case"not_multiple_of":return`Số không hợp lệ: phải là bội số của ${t.divisor}`;case"unrecognized_keys":return`Khóa không được nhận dạng: ${v(t.keys,", ")}`;case"invalid_key":return`Khóa không hợp lệ trong ${t.origin}`;case"invalid_union":return"Đầu vào không hợp lệ";case"invalid_element":return`Giá trị không hợp lệ trong ${t.origin}`;default:return"Đầu vào không hợp lệ"}}};function Uf(){return{localeError:U$()}}var R$=()=>{let e={string:{unit:"字符",verb:"包含"},file:{unit:"字节",verb:"包含"},array:{unit:"项",verb:"包含"},set:{unit:"项",verb:"包含"},map:{unit:"项",verb:"包含"}};function n(t){return e[t]??null}let i={regex:"输入",email:"电子邮件",url:"URL",emoji:"表情符号",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO日期时间",date:"ISO日期",time:"ISO时间",duration:"ISO时长",ipv4:"IPv4地址",ipv6:"IPv6地址",mac:"MAC地址",cidrv4:"IPv4网段",cidrv6:"IPv6网段",base64:"base64编码字符串",base64url:"base64url编码字符串",json_string:"JSON字符串",e164:"E.164号码",credit_card:"信用卡号",currency_code:"货币代码",iban:"IBAN",jwt:"JWT",template_literal:"输入"},o={nan:"NaN",number:"数字",array:"数组",null:"空值(null)"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`无效输入:期望 instanceof ${t.expected},实际接收 ${s}`:`无效输入:期望 ${r},实际接收 ${s}`}case"invalid_value":return t.values.length===1?`无效输入:期望 ${$(t.values[0])}`:`无效选项:期望以下之一 ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`数值过大:期望 ${t.origin??"值"} ${r}${t.maximum.toString()} ${a.unit??"个元素"}`:`数值过大:期望 ${t.origin??"值"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`数值过小:期望 ${t.origin} ${r}${t.minimum.toString()} ${a.unit}`:`数值过小:期望 ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`无效字符串:必须以 "${r.prefix}" 开头`:r.format==="ends_with"?`无效字符串:必须以 "${r.suffix}" 结尾`:r.format==="includes"?`无效字符串:必须包含 "${r.includes}"`:r.format==="regex"?`无效字符串:必须满足正则表达式 ${r.pattern}`:`无效${i[r.format]??t.format}`}case"not_multiple_of":return`无效数字:必须是 ${t.divisor} 的倍数`;case"unrecognized_keys":return`出现未知的键(key): ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} 中的键(key)无效`;case"invalid_union":return"无效输入";case"invalid_element":return`${t.origin} 中包含无效值(value)`;default:return"无效输入"}}};function Rf(){return{localeError:R$()}}var M$=()=>{let e={string:{unit:"字元",verb:"擁有"},file:{unit:"位元組",verb:"擁有"},array:{unit:"項目",verb:"擁有"},set:{unit:"項目",verb:"擁有"},map:{unit:"項目",verb:"擁有"}};function n(t){return e[t]??null}let i={regex:"輸入",email:"郵件地址",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO 日期時間",date:"ISO 日期",time:"ISO 時間",duration:"ISO 期間",ipv4:"IPv4 位址",ipv6:"IPv6 位址",mac:"MAC 位址",cidrv4:"IPv4 範圍",cidrv6:"IPv6 範圍",base64:"base64 編碼字串",base64url:"base64url 編碼字串",json_string:"JSON 字串",e164:"E.164 數值",credit_card:"信用卡號",currency_code:"貨幣代碼",iban:"IBAN",jwt:"JWT",template_literal:"輸入"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`無效的輸入值:預期為 instanceof ${t.expected},但收到 ${s}`:`無效的輸入值:預期為 ${r},但收到 ${s}`}case"invalid_value":return t.values.length===1?`無效的輸入值:預期為 ${$(t.values[0])}`:`無效的選項:預期為以下其中之一 ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`數值過大:預期 ${t.origin??"值"} 應為 ${r}${t.maximum.toString()} ${a.unit??"個元素"}`:`數值過大:預期 ${t.origin??"值"} 應為 ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`數值過小:預期 ${t.origin} 應為 ${r}${t.minimum.toString()} ${a.unit}`:`數值過小:預期 ${t.origin} 應為 ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`無效的字串:必須以 "${r.prefix}" 開頭`:r.format==="ends_with"?`無效的字串:必須以 "${r.suffix}" 結尾`:r.format==="includes"?`無效的字串:必須包含 "${r.includes}"`:r.format==="regex"?`無效的字串:必須符合格式 ${r.pattern}`:`無效的 ${i[r.format]??t.format}`}case"not_multiple_of":return`無效的數字:必須為 ${t.divisor} 的倍數`;case"unrecognized_keys":return`無法識別的鍵值${t.keys.length>1?"們":""}:${v(t.keys,"、")}`;case"invalid_key":return`${t.origin} 中有無效的鍵值`;case"invalid_union":return"無效的輸入值";case"invalid_element":return`${t.origin} 中有無效的值`;default:return"無效的輸入值"}}};function Mf(){return{localeError:M$()}}var Z$=()=>{let e={string:{unit:"àmi",verb:"ní"},file:{unit:"bytes",verb:"ní"},array:{unit:"nkan",verb:"ní"},set:{unit:"nkan",verb:"ní"},map:{unit:"nkan",verb:"ní"}};function n(t){return e[t]??null}let i={regex:"ẹ̀rọ ìbáwọlé",email:"àdírẹ́sì ìmẹ́lì",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"àkókò ISO",date:"ọjọ́ ISO",time:"àkókò ISO",duration:"àkókò tó pé ISO",ipv4:"àdírẹ́sì IPv4",ipv6:"àdírẹ́sì IPv6",mac:"àdírẹ́sì MAC",cidrv4:"àgbègbè IPv4",cidrv6:"àgbègbè IPv6",base64:"ọ̀rọ̀ tí a kọ́ ní base64",base64url:"ọ̀rọ̀ base64url",json_string:"ọ̀rọ̀ JSON",e164:"nọ́mbà E.164",credit_card:"nọmba kaadi gbese",currency_code:"koodu owó",iban:"IBAN",jwt:"JWT",template_literal:"ẹ̀rọ ìbáwọlé"},o={nan:"NaN",number:"nọ́mbà",array:"akopọ"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ìbáwọlé aṣìṣe: a ní láti fi instanceof ${t.expected}, àmọ̀ a rí ${s}`:`Ìbáwọlé aṣìṣe: a ní láti fi ${r}, àmọ̀ a rí ${s}`}case"invalid_value":return t.values.length===1?`Ìbáwọlé aṣìṣe: a ní láti fi ${$(t.values[0])}`:`Àṣàyàn aṣìṣe: yan ọ̀kan lára ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Tó pọ̀ jù: a ní láti jẹ́ pé ${t.origin??"iye"} ${a.verb} ${r}${t.maximum} ${a.unit}`:`Tó pọ̀ jù: a ní láti jẹ́ ${r}${t.maximum}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Kéré ju: a ní láti jẹ́ pé ${t.origin} ${a.verb} ${r}${t.minimum} ${a.unit}`:`Kéré ju: a ní láti jẹ́ ${r}${t.minimum}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ bẹ̀rẹ̀ pẹ̀lú "${r.prefix}"`:r.format==="ends_with"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ parí pẹ̀lú "${r.suffix}"`:r.format==="includes"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ ní "${r.includes}"`:r.format==="regex"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ bá àpẹẹrẹ mu ${r.pattern}`:`Aṣìṣe: ${i[r.format]??t.format}`}case"not_multiple_of":return`Nọ́mbà aṣìṣe: gbọ́dọ̀ jẹ́ èyà pípín ti ${t.divisor}`;case"unrecognized_keys":return`Bọtìnì àìmọ̀: ${v(t.keys,", ")}`;case"invalid_key":return`Bọtìnì aṣìṣe nínú ${t.origin}`;case"invalid_union":return"Ìbáwọlé aṣìṣe";case"invalid_element":return`Iye aṣìṣe nínú ${t.origin}`;default:return"Ìbáwọlé aṣìṣe"}}};function Zf(){return{localeError:Z$()}}var Vf,gu=Symbol("ZodOutput"),hu=Symbol("ZodInput"),qi=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(n,...i){let o=i[0];return this._map.set(n,o),o&&typeof o=="object"&&"id"in o&&this._idmap.set(o.id,n),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(n){let i=this._map.get(n);return i&&typeof i=="object"&&"id"in i&&this._idmap.delete(i.id),this._map.delete(n),this}get(n){let i=n._zod.parent;if(i){let o={...this.get(i)??{}};delete o.id;let t={...o,...this._map.get(n)};return Object.keys(t).length?t:void 0}return this._map.get(n)}has(n){return this._map.has(n)}};function Yi(){return new qi}(Vf=globalThis).__zod_globalRegistry??(Vf.__zod_globalRegistry=Yi());var ce=globalThis.__zod_globalRegistry;var me=Symbol.for("zod.compile.invalid"),Gf=Symbol.for("zod.compile.fallback"),De=class extends Error{constructor(n="z.compile does not support async refinements, transforms, or checks"){super(n),this.name="ZodCompileAsyncError"}},V=class extends Error{constructor(n,i=!0){super(`z.compile does not support ${n}; this schema must use the runtime parser`),this.name="ZodCompileUnsupportedError",this.islandable=i}};function G$(e,n){try{return eo(e,{assertOnly:!0})}catch{return n}}function _u(e,n){try{let i=eo(e),o=Qi(e,i);return o._zod.bag.validator=G$(e,i),o}catch(i){if(n?.strict)throw i;return e}}function Qi(e,n){if(Hi(e))throw new V("a schema whose subtree contains a reference cycle");let i=K(e),o=e._zod.run,t=o.__originalRun??o,r=(a,s)=>{if(s?.async||s?.direction==="backward"||s?.skipChecks||s?.[Gf]||s&&Ki(s,a.value))return t(a,s);let c=n(a.value);return c!==me?(a.value=c,a):(s&&(s[Gf]=!0),t(a,s))};return r.__originalRun=t,i._zod.bag.fallbackRun=t,i._zod.bag.validator=n,i._zod.run=r,o.__originalRun||F$(i,e,n),i}function F$(e,n,i){let o=e,t=n;if(typeof t.safeParse=="function"){let r=t.safeParse;o.safeParse=(a,s)=>{let c=i(a);return c!==me?{success:!0,data:c}:r(a,s)}}if(typeof t.parse=="function"){let r=t.parse;o.parse=(a,s)=>{let c=i(a);return c!==me?c:r(a,s)}}}function eo(e,n){let i=!0;try{i=Hi(e)}catch{}if(i)throw new V("a schema whose subtree contains a reference cycle");let o={constants:new Map,constantCounter:0,varCounter:0,definite:!0},t=new It(["input"]),r=Q(t,o,e,"input",!n?.assertOnly);t.write(r===null?"return true;":`return ${r};`);let a=["INVALID",...o.constants.keys()],s=[me,...o.constants.values()],c=t.content.join(` -`),u=n?.debug?a.length>0?`// Constants: ${a.join(", ")} -${c}`:c:"",d=Function,p=`return (input) => { -${c} -}`,m;try{m=new d(...a,p)(...s)}catch(f){throw new V(`this schema (generated code failed to evaluate: ${f.message})`)}return n?.debug&&(m.code=u),m.definite=o.definite,m}function L(e,n){for(let[o,t]of e.constants)if(t===n)return o;let i=`c${e.constantCounter++}`;return e.constants.set(i,n),i}function Tt(e,n){return e.definite=!1,L(e,n)}function z(e){return`v${e.varCounter++}`}function B$(e,n){let i=e._zod.run({value:n,issues:[]},{});if(i&&typeof i.then=="function")return me;let o=i;return o.issues.length===0?o.value:me}function ue(e,n,i,o,t=!0){let r=e.content.length,a=n.constants.size,s=n.constantCounter,c=n.varCounter;try{return Q(e,n,i,o,t)}catch(u){if(!(u instanceof V)||!u.islandable)throw u;if(e.content.length=r,n.constants.size>a){let d=Array.from(n.constants.keys()).slice(a);for(let p of d)n.constants.delete(p)}return n.constantCounter=s,n.varCounter=c,H$(e,n,i,o)}}function H$(e,n,i,o){n.definite=!1;let t=L(n,i),r=L(n,B$),a=z(n);return e.write(`const ${a} = ${r}(${t}, ${o});`),e.write(`if (${a} === INVALID) return INVALID;`),a}var K$=new Set(["max_size","min_size","size_equals","max_length","min_length","length_equals"]);function W$(e,n,i,o){let t=i._zod.def.checks;if(!t||t.length===0)return o;let r=o;for(let a of t){let s=a._zod.def;if(s.when&&!K$.has(s.check))throw new V('check with a custom "when" condition');switch(s.check){case"greater_than":J$(e,n,s,r);break;case"less_than":X$(e,n,s,r);break;case"multiple_of":q$(e,n,s,r);break;case"number_format":Jf(e,s,r);break;case"min_length":{let c=at(s.minimum,"min_length"),u=vu(e,n,r,`${r}.length >= ${c} && ${r}.length < ${s.minimum*2}`);e.write(`if (${u} < ${c}) return INVALID;`);break}case"max_length":{let c=at(s.maximum,"max_length"),u=vu(e,n,r,`${r}.length > ${c}`);e.write(`if (${u} > ${c}) return INVALID;`);break}case"length_equals":{let c=at(s.length,"length_equals"),u=vu(e,n,r,`${r}.length >= ${c} && ${r}.length <= ${s.length*2}`);e.write(`if (${u} !== ${c}) return INVALID;`);break}case"min_size":e.write(`if (${r}.size < ${at(s.minimum,"min_size")}) return INVALID;`);break;case"max_size":e.write(`if (${r}.size > ${at(s.maximum,"max_size")}) return INVALID;`);break;case"size_equals":e.write(`if (${r}.size !== ${at(s.size,"size_equals")}) return INVALID;`);break;case"string_format":r=Xf(e,n,s,r);break;case"custom":r=rk(e,n,a,r);break;case"bigint_format":Y$(e,s,r);break;case"mime_type":Q$(e,n,s,r);break;case"property":tk(e,n,s,r);break;case"properties":ek(e,n,s,r);break;case"overwrite":{let c=z(n);nk(e,n,a,r,c),r=c;break}default:throw new V(`check type ${s.check}`)}}return r}function vu(e,n,i,o){let t=L(n,xt),r=z(n);return e.write(`const ${r} = typeof ${i} === "string" && ${o} ? ${t}(${i}) : ${i}.length;`),r}function at(e,n){if(typeof e!="number"||!Number.isFinite(e))throw new V(`${n} bound of type ${typeof e}`);return`${e}`}function Wf(e,n){if(typeof n=="bigint")return`${n}n`;if(typeof n=="number"){if(Number.isNaN(n))throw new V("comparison check with NaN bound");return`${n}`}if(n instanceof Date){if(Number.isNaN(n.getTime()))throw new V("comparison check with Invalid Date bound");return L(e,n)}throw new V(`comparison check bound of type ${typeof n}`)}function J$(e,n,i,o){let t=i.inclusive?"<":"<=";e.write(`if (${o} ${t} ${Wf(n,i.value)}) return INVALID;`)}function X$(e,n,i,o){let t=i.inclusive?">":">=";e.write(`if (${o} ${t} ${Wf(n,i.value)}) return INVALID;`)}function q$(e,n,i,o){if(typeof i.value=="bigint"){if(i.value===BigInt(0))throw new V("multiple_of check with a zero divisor");e.write(`if (${o} % ${i.value}n !== 0n) return INVALID;`)}else{let t=L(n,Fn);e.write(`if (${t}(${o}, ${at(i.value,"multiple_of")}) !== 0) return INVALID;`)}}function Jf(e,n,i){let o=n.format;switch(o){case"safeint":e.write(`if (!Number.isSafeInteger(${i})) return INVALID;`);break;case"int32":e.write(`if (!Number.isInteger(${i}) || ${i} < -2147483648 || ${i} > 2147483647) return INVALID;`);break;case"uint32":e.write(`if (!Number.isInteger(${i}) || ${i} < 0 || ${i} > 4294967295) return INVALID;`);break;case"float32":e.write(`if (!Number.isFinite(${i}) || ${i} < -3.4028234663852886e38 || ${i} > 3.4028234663852886e38) return INVALID;`);break;case"float64":e.write(`if (!Number.isFinite(${i})) return INVALID;`);break;default:throw new V(`number format ${o}`)}}function Y$(e,n,i){let o=n.format;if(o)switch(o){case"int64":e.write(`if (${i} < -9223372036854775808n || ${i} > 9223372036854775807n) return INVALID;`);break;case"uint64":e.write(`if (${i} < 0n || ${i} > 18446744073709551615n) return INVALID;`);break;default:throw new V(`bigint format ${o}`)}}function Q$(e,n,i,o){let t=i.mime;if(t&&t.length>0){let r=L(n,new Set(t));e.write(`if (!${r}.has(${o}.type)) return INVALID;`)}}function ek(e,n,i,o){if(i.when)throw new V('check with a custom "when" condition');e.write(`if (${o} == null) return INVALID;`);let t=i.shape;for(let r of Reflect.ownKeys(t)){let a=typeof r=="symbol"?L(n,r):de(r),s=z(n);e.write(`const ${s} = ${o}[${a}];`),ue(e,n,t[r],s,!1)}}function tk(e,n,i,o){let t=`${o}[${JSON.stringify(i.property)}]`;Q(e,n,i.schema,t)}function nk(e,n,i,o,t){let r=i._zod.def.tx;if(!r)throw new V("overwrite check without a transform function");if(Et(r))throw new De("z.compile: async overwrite transforms are not supported");let a=L(n,r);e.write(`const ${t} = ${a}(${o});`)}function yu(){throw new $e}function $u(e){this.issues.push(e)}function rk(e,n,i,o){let t=i._zod.def;if(t.fn){if(Et(t.fn))throw new De("z.compile: async .refine() predicates are not supported");let r=Tt(n,t.fn),a=L(n,yu),s=z(n);return e.write(`const ${s} = ${r}(${o});`),e.write(`if (${s} instanceof Promise) ${a}();`),e.write(`if (!${s}) return INVALID;`),o}if(i._zod.check){if(Et(i._zod.check))throw new De("z.compile: async .superRefine() / check functions are not supported");let r=i._zod.check,s=Tt(n,u=>{let d={value:u,issues:[],addIssue:$u};return r(d)instanceof Promise&&yu(),d.issues.length===0?d.value:me}),c=z(n);return e.write(`const ${c} = ${s}(${o});`),e.write(`if (${c} === INVALID) return INVALID;`),c}throw new V("custom check without a predicate or check function")}var ik=new Set(["cidrv4","cuid","cuid2","date","datetime","duration","e164","email","emoji","ends_with","guid","includes","ipv4","ksuid","lowercase","mac","nanoid","regex","starts_with","time","ulid","uppercase","uuid","xid"]);function Xf(e,n,i,o,t=!0){let r=i.format;if(r==="base64"){let u=L(n,lr);return e.write(`if (!${u}(${o})) return INVALID;`),o}if(r==="base64url"){let u=L(n,zi);return e.write(`if (!${u}(${o})) return INVALID;`),o}if(r==="jwt"){let u=L(n,ji),d=L(n,i.alg??null);return e.write(`if (!${u}(${o}, ${d})) return INVALID;`),o}if(r==="ipv6"){let u=L(n,ur);return e.write(`if (!${u}(${o})) return INVALID;`),o}if(r==="cidrv6"){let u=L(n,Oi);return e.write(`if (!${u}(${o})) return INVALID;`),o}if(r==="credit_card"){let u=L(n,Di);return e.write(`if (!${u}(${o})) return INVALID;`),o}if(r==="iban"){let u=L(n,Ci);return e.write(`if (!${u}(${o})) return INVALID;`),o}let a=i;if(r==="url"||r==="httpurl"||a.normalize||a.hostname!==void 0||a.protocol!==void 0){let u=L(n,Ti),d=L(n,i),p=z(n),m=z(n);if(e.write(`const ${p} = ${o}.trim();`),e.write(`const ${m} = ${u}(${p}, ${d});`),e.write(`if (typeof ${m} === "number") return INVALID;`),a.hostname!==void 0){let y=L(n,Ei);e.write(`if (!${y}(${m}, ${d}.hostname)) return INVALID;`)}if(a.protocol!==void 0){let y=L(n,Ai);e.write(`if (!${y}(${m}, ${d}.protocol)) return INVALID;`)}if(!t)return null;let f=z(n),g=a.normalize?`${m}.href`:`${L(n,cr)}(${p})`;return e.write(`const ${f} = ${g};`),f}let s=i.fn;if(s){if(Et(s))throw new V(`async string format ${r}`);let u=L(n,s);return e.write(`if (!${u}(${o})) return INVALID;`),o}if(ik.has(r)&&i.pattern){let u=L(n,i.pattern);return e.write(`${u}.lastIndex = 0;`),e.write(`if (!${u}.test(${o})) return INVALID;`),o}let c=i.format;switch(c){case"regex":throw new V("regex format without a pattern");case"lowercase":e.write(`if (${o} !== ${o}.toLowerCase()) return INVALID;`);break;case"uppercase":e.write(`if (${o} !== ${o}.toUpperCase()) return INVALID;`);break;case"includes":e.write(`if (!${o}.includes(${de(i.includes)})) return INVALID;`);break;case"starts_with":{let u=i.prefix;e.write(`if (${o}.slice(0, ${u.length}) !== ${de(u)}) return INVALID;`);break}case"ends_with":{let u=i.suffix;e.write(`if (${o}.slice(-${u.length}) !== ${de(u)}) return INVALID;`);break}default:throw new V(`string format ${c}`)}return o}function Q(e,n,i,o,t=!0){let r=i._zod.def,a=r.type;if(r.coerce)throw new V(`coercion (z.coerce.${a}())`);let s=t||!!r.checks?.length,c;switch(a){case"string":c=ok(e,n,i,o,s);break;case"number":c=ak(e,i,o);break;case"boolean":c=sk(e,o);break;case"bigint":c=ck(e,i,o);break;case"symbol":c=uk(e,o);break;case"undefined":c=lk(e,o);break;case"null":c=dk(e,o);break;case"any":case"unknown":c=o;break;case"never":e.write("return INVALID;"),c=o;break;case"void":c=pk(e,o);break;case"nan":c=mk(e,o);break;case"date":c=fk(e,o);break;case"object":c=gk(e,n,i,o,s);break;case"optional":c=hk(e,n,i,o,s);break;case"nullable":c=bk(e,n,i,o,s);break;case"array":c=_k(e,n,i,o,s);break;case"literal":c=$k(e,n,i,o);break;case"enum":c=kk(e,n,i,o);break;case"readonly":{let u=Bf(e,n,i,o),d=z(n);e.write(`const ${d} = Object.freeze(${u});`),c=d;break}case"success":Bf(e,n,i,o),c="true";break;case"default":case"prefault":c=xk(e,n,i,o);break;case"nonoptional":c=wk(e,n,i,o);break;case"tuple":c=Ik(e,n,i,o);break;case"union":c=Sk(e,n,i,o);break;case"intersection":c=Ek(e,n,i,o);break;case"record":c=Ak(e,n,i,o);break;case"map":c=Nk(e,n,i,o);break;case"set":c=zk(e,n,i,o);break;case"file":c=Dk(e,o);break;case"template_literal":c=Ck(e,n,i,o);break;case"lazy":c=jk(e,n,i,o);break;case"pipe":c=Lk(e,n,i,o);break;case"custom":c=Uk(e,n,i,o);break;case"transform":c=Zk(e,n,i,o);break;case"catch":c=Mk(e,n,i,o);break;default:throw new V(`schema type ${a}`)}return c===null?null:W$(e,n,i,c)}function ok(e,n,i,o,t=!0){e.write(`if (typeof ${o} !== "string") return INVALID;`);let r=i._zod.def;return r.format===void 0?o:Xf(e,n,r,o,t)}function ak(e,n,i){e.write(`if (typeof ${i} !== "number" || !Number.isFinite(${i})) return INVALID;`);let o=n._zod.def;return o.check==="number_format"&&o.format&&Jf(e,{format:o.format},i),i}function sk(e,n){return e.write(`if (typeof ${n} !== "boolean") return INVALID;`),n}function ck(e,n,i){e.write(`if (typeof ${i} !== "bigint") return INVALID;`);let o=n._zod.def;if(o.format)switch(o.format){case"int64":e.write(`if (${i} < -9223372036854775808n || ${i} > 9223372036854775807n) return INVALID;`);break;case"uint64":e.write(`if (${i} < 0n || ${i} > 18446744073709551615n) return INVALID;`);break}return i}function uk(e,n){return e.write(`if (typeof ${n} !== "symbol") return INVALID;`),n}function lk(e,n){return e.write(`if (${n} !== undefined) return INVALID;`),n}function dk(e,n){return e.write(`if (${n} !== null) return INVALID;`),n}function pk(e,n){return e.write(`if (${n} !== undefined) return INVALID;`),n}function mk(e,n){return e.write(`if (typeof ${n} !== "number" || !Number.isNaN(${n})) return INVALID;`),n}function fk(e,n){return e.write(`if (!(${n} instanceof Date) || Number.isNaN(${n}.getTime())) return INVALID;`),n}function gk(e,n,i,o,t=!0){let r=i._zod.def;e.write(`if (typeof ${o} !== "object" || ${o} === null || Array.isArray(${o})) return INVALID;`);let a=r.shape,s=Object.keys(a),c=Object.getOwnPropertySymbols(a),u=c.length?[...s,...c]:s,d=b=>typeof b=="symbol"?L(n,b):de(b),p=b=>typeof b=="symbol"?`[${d(b)}]`:de(b),m=a;if(s.includes("__proto__"))throw new V('object shape key "__proto__"');let f=new Map;for(let b of u){let T=m[b],A=d(b),D=z(n);if(e.write(`const ${D} = ${o}[${A}];`),T._zod.optin!==void 0){let w=z(n);e.write(`let ${w} = (() => {`),e.indented(O=>{let E=ue(O,n,T,D);O.write(`return ${E};`)}),e.write("})();"),T._zod.optout==="optional"?(e.write(`if (${w} === INVALID) {`),e.indented(O=>{O.write(`if (${A} in ${o}) return INVALID;`),O.write(`${w} = undefined;`)}),e.write("}")):e.write(`if (${w} === INVALID) return INVALID;`),f.set(b,w)}else{yk(T)&&e.write(`if (!(${A} in ${o})) return INVALID;`);let w=ue(e,n,T,D,t);w!==null&&f.set(b,w)}}let g=r.catchall,y="none";if(g){let b=g._zod.def.type;if(b==="never"){let T=s.map(A=>`k !== ${de(A)}`).join(" && ")||"true";e.write(`for (const k in ${o}) {`),e.indented(A=>{A.write(`if (${T}) return INVALID;`)}),e.write("}")}else(b==="unknown"||b==="any")&&!g._zod.def.checks?.length?y="passthrough":y="schema"}let x=z(n),I=u.some(b=>Ff(m[b])||bu(m[b]));if(!t){if(y==="schema"){let b=s.length>0?L(n,new Set(s)):null;e.write(`for (const k in ${o}) {`),e.indented(T=>{T.write('if (k === "__proto__") continue;'),b&&T.write(`if (${b}.has(k)) continue;`);let A=z(n);T.write(`const ${A} = ${o}[k];`),ue(T,n,g,A,!1)}),e.write("}")}return null}if(I){e.write(`const ${x} = {};`);for(let b of u){let T=d(b),A=f.get(b);bu(m[b])?e.write(`if (${T} in ${o}) ${x}[${T}] = ${A};`):Ff(m[b])?e.write(`if (${A} !== undefined || ${T} in ${o}) ${x}[${T}] = ${A};`):e.write(`${x}[${T}] = ${A};`)}}else{let b=u.map(T=>`${p(T)}: ${f.get(T)}`).join(", ");e.write(`const ${x} = { ${b} };`)}if(y!=="none"){let b=s.length>0?L(n,new Set(s)):null;e.write(`for (const k in ${o}) {`),e.indented(T=>{if(T.write('if (k === "__proto__") continue;'),b&&T.write(`if (${b}.has(k)) continue;`),y==="passthrough")T.write(`${x}[k] = ${o}[k];`);else{let A=z(n);T.write(`const ${A} = ${o}[k];`);let D=ue(T,n,g,A);T.write(`${x}[k] = ${D};`)}}),e.write("}")}return x}function hk(e,n,i,o,t=!0){let r=i._zod.def;if(vk(i))return Q(e,n,r.innerType,o,t);if(r.innerType._zod.optin==="defaulted"){let s=z(n),c=z(n);return e.write(`let ${s};`),e.write(`if (${o} === undefined) {`),e.indented(u=>{u.write(`const ${c} = (() => {`),u.indented(d=>{let p=Q(d,n,r.innerType,o);d.write(`return ${p};`)}),u.write("})();"),u.write(`if (${c} !== INVALID) ${s} = ${c};`)}),e.write("} else {"),e.indented(u=>{let d=Q(u,n,r.innerType,o);u.write(`${s} = ${d};`)}),e.write("}"),s}let a=t?z(n):null;return a&&e.write(`let ${a};`),e.write(`if (${o} !== undefined) {`),e.indented(s=>{let c=Q(s,n,r.innerType,o,t);a&&c!==null&&s.write(`${a} = ${c};`)}),e.write("}"),a}function vk(e){return e._zod.traits?.has("$ZodExactOptional")===!0}function yk(e){return e._zod.optin===void 0&&Pt(e)}function Pt(e){if(e._zod.def.coerce)return!0;let n=e._zod.def;switch(n.type){case"any":case"unknown":case"undefined":case"void":case"default":case"prefault":case"transform":case"custom":case"lazy":return!0;case"string":case"number":case"boolean":case"bigint":case"symbol":case"null":case"never":case"nan":case"date":case"object":case"array":case"tuple":case"record":case"map":case"set":case"file":case"template_literal":return!1;case"nonoptional":return n.innerType?Pt(n.innerType):!1;case"literal":return!!n.values?.includes(void 0);case"enum":return!!e._zod.values?.has(void 0);case"optional":case"nullable":case"readonly":case"success":return n.innerType?Pt(n.innerType):!0;case"catch":return!0;case"union":return n.options?n.options.some(Pt):!0;case"intersection":return!n.left||!n.right?!0:Pt(n.left)&&Pt(n.right);case"pipe":return n.in?Pt(n.in):!0;default:return!0}}function bu(e){return e._zod.optin==="optional"&&e._zod.optout==="optional"}function Ff(e){return(e._zod.optin!=="defaulted"||e._zod.optout==="optional")&&sn(e)}function sn(e){let n=e._zod.def;switch(n.type){case"string":case"number":case"boolean":case"bigint":case"symbol":case"null":case"nan":case"date":case"object":case"array":case"tuple":case"record":case"map":case"set":case"file":case"template_literal":case"never":case"success":return!1;case"literal":return!!n.values?.includes(void 0);case"enum":return!!e._zod.values?.has(void 0);case"optional":return!0;case"nullable":case"readonly":case"nonoptional":return n.innerType?sn(n.innerType):!0;case"union":return n.options?n.options.some(sn):!0;case"intersection":return!n.left||!n.right||sn(n.left)||sn(n.right);case"pipe":return n.out?sn(n.out):!0;default:return!0}}function bk(e,n,i,o,t=!0){let r=i._zod.def,a=t?z(n):null;return a&&e.write(`let ${a} = null;`),e.write(`if (${o} !== null) {`),e.indented(s=>{let c=Q(s,n,r.innerType,o,t);a&&c!==null&&s.write(`${a} = ${c};`)}),e.write("}"),a}function ku(e,n,i){for(let[o,t]of Xt(n._zod.def.checks))e.write(`if (${i} > ${at(o,t._zod.def.check)}) return INVALID;`)}function _k(e,n,i,o,t=!0){let r=i._zod.def;e.write(`if (!Array.isArray(${o})) return INVALID;`),ku(e,i,`${o}.length`);let a=t?z(n):null,s=z(n),c=z(n);return a&&e.write(`const ${a} = new Array(${o}.length);`),e.write(`for (let ${s} = 0; ${s} < ${o}.length; ${s}++) {`),e.indented(u=>{u.write(`const ${c} = ${o}[${s}];`);let d=ue(u,n,r.element,c,t);a&&d!==null&&u.write(`${a}[${s}] = ${d};`)}),e.write("}"),a}function $k(e,n,i,o){let r=i._zod.def.values;if(r.length!==1){let s=L(n,new Set(r));return e.write(`if (!${s}.has(${o})) return INVALID;`),o}let a=r[0];if(typeof a=="number"&&Number.isNaN(a)){let s=L(n,new Set(r));return e.write(`if (!${s}.has(${o})) return INVALID;`),o}if(typeof a=="string")e.write(`if (${o} !== ${de(a)}) return INVALID;`);else if(typeof a=="number"||typeof a=="boolean")e.write(`if (${o} !== ${a}) return INVALID;`);else if(a===null)e.write(`if (${o} !== null) return INVALID;`);else if(a===void 0)e.write(`if (${o} !== undefined) return INVALID;`);else if(typeof a=="bigint")e.write(`if (${o} !== ${a}n) return INVALID;`);else throw new V(`literal type ${typeof a}`);return o}function kk(e,n,i,o){let t=i._zod.values;if(!t)throw new V("enum schema without enumerated values");let r=L(n,t);return e.write(`if (!${r}.has(${o})) return INVALID;`),o}function Bf(e,n,i,o){let t=i._zod.def;return Q(e,n,t.innerType,o)}function xk(e,n,i,o){let t=i._zod.def,a=Object.getOwnPropertyDescriptor(i._zod.def,"defaultValue")?()=>i._zod.def.defaultValue:void 0;if(i._zod.def.type==="prefault"){if(!a)return Q(e,n,t.innerType,o);let c=L(n,a),u=z(n);return e.write(`let ${u} = ${o};`),e.write(`if (${o} === undefined) ${u} = ${c}();`),Q(e,n,t.innerType,u)}let s=z(n);if(a){let c=L(n,a),u=L(n,Bn);e.write(`let ${s};`),e.write(`if (${o} === undefined) {`),e.indented(d=>{d.write(`${s} = ${u}(${c}());`)}),e.write("} else {"),e.indented(d=>{let p=Q(d,n,t.innerType,o);d.write(`${s} = ${p} === undefined ? ${u}(${c}()) : ${p};`)}),e.write("}")}else e.write(`let ${s};`),e.write(`if (${o} !== undefined) {`),e.indented(c=>{let u=Q(c,n,t.innerType,o);c.write(`${s} = ${u};`)}),e.write("}");return s}function wk(e,n,i,o){let t=i._zod.def,r=Q(e,n,t.innerType,o),a=z(n);return e.write(`const ${a} = ${r};`),e.write(`if (${a} === undefined) return INVALID;`),a}function Ik(e,n,i,o){let t=i._zod.def,r=t.items,a=t.rest;e.write(`if (!Array.isArray(${o})) return INVALID;`);let s=Hf(r,"optin"),c=Hf(r,"optout");a?e.write(`if (${o}.length < ${s}) return INVALID;`):e.write(`if (${o}.length < ${s} || ${o}.length > ${r.length}) return INVALID;`);let u=z(n);e.write(`const ${u} = [];`);for(let d=0;d=c)e.write(`if (${u}.length === ${d}) {`),e.indented(m=>{m.write(`if (${d} < ${o}.length) {`),m.indented(f=>{let g=z(n);f.write(`const ${g} = ${o}[${d}];`);let y=ue(f,n,p,g);f.write(`${u}[${d}] = ${y};`)}),m.write("} else {"),m.indented(f=>{if(bu(p)){f.write(`${u}.length = ${d};`);return}let g=z(n),y=z(n);f.write(`const ${g} = undefined;`),f.write(`const ${y} = (() => {`),f.indented(x=>{let I=ue(x,n,p,g);x.write(`return ${I};`)}),f.write("})();"),f.write(`if (${y} === INVALID || ${y} === undefined) ${u}.length = ${d};`),f.write(`else ${u}[${d}] = ${y};`)}),m.write("}")}),e.write("}");else{let m=z(n);e.write(`const ${m} = ${o}[${d}];`);let f=ue(e,n,p,m);e.write(`${u}[${d}] = ${f};`)}}if(a){let d=z(n),p=z(n);e.write(`for (let ${d} = ${r.length}; ${d} < ${o}.length; ${d}++) {`),e.indented(m=>{m.write(`const ${p} = ${o}[${d}];`);let f=ue(m,n,a,p);m.write(`${u}[${d}] = ${f};`)}),e.write("}")}return u}function Hf(e,n){for(let i=e.length-1;i>=0;i--)if(!(n==="optin"?e[i]._zod.optin!==void 0:e[i]._zod.optout==="optional"))return i+1;return 0}function Sk(e,n,i,o){let t=i._zod.def,r=t.options;if(t.discriminator)return Pk(e,n,t,o);if(t.inclusive===!1)throw new V("exclusive unions (z.xor)");if(r.length===0)return e.write("return INVALID;"),o;if(r.length===1)return Q(e,n,r[0],o);if(r.every(c=>c._zod.def.type==="literal"&&!c._zod.def.checks?.length)){let c=new Set(r.flatMap(d=>d._zod.def.values)),u=L(n,c);return e.write(`if (!${u}.has(${o})) return INVALID;`),o}let s=z(n);e.write(`let ${s};`);for(let c=0;c {`):e.write(`if (${s} === INVALID) ${s} = (() => {`),e.indented(d=>{let p=Q(d,n,u,o);d.write(`return ${p};`)}),e.write("})();")}return e.write(`if (${s} === INVALID) return INVALID;`),s}function Pk(e,n,i,o){if(i.unionFallback)throw new V("discriminated union with unionFallback");if(i.options.length===0)return e.write("return INVALID;"),o;let t=z(n),r=z(n);e.write(`const ${t} = ${o}?.[${de(i.discriminator)}];`),e.write(`let ${r};`);let a=!0,s=new Set;for(let c of i.options){let u=c._zod.propValues?.[i.discriminator];if(!u||u.size===0)throw new V("discriminated union option without static discriminator values");for(let m of u){if(s.has(m))throw new V(`duplicate discriminator value ${String(m)}`);s.add(m)}let d=Array.from(u,m=>Tk(n,t,m)),p=a?"if":"else if";e.write(`${p} (${d.join(" || ")}) {`),e.indented(m=>{let f=Q(m,n,c,o);m.write(`${r} = ${f};`)}),e.write("}"),a=!1}return e.write("else { return INVALID; }"),r}function Tk(e,n,i){if(typeof i=="string")return`${n} === ${de(i)}`;if(typeof i=="number")return Number.isNaN(i)?`Number.isNaN(${n})`:`${n} === ${i}`;if(typeof i=="boolean")return`${n} === ${i}`;if(i===null)return`${n} === null`;if(i===void 0)return`${n} === undefined`;if(typeof i=="bigint")return`${n} === ${i}n`;if(typeof i=="symbol"){let o=L(e,i);return`${n} === ${o}`}throw new V(`literal discriminator value ${String(i)}`)}function Ek(e,n,i,o){let t=i._zod.def;n.definite=!1;let r=ue(e,n,t.left,o),a=ue(e,n,t.right,o),s=L(n,rn),c=z(n);return e.write(`const ${c} = ${s}(${r}, ${a});`),e.write(`if (!${c}.valid) return INVALID;`),`${c}.data`}function Ak(e,n,i,o){let t=i._zod.def,r=L(n,Se);e.write(`if (!${r}(${o})) return INVALID;`);let a=z(n),s=z(n),c=z(n);e.write(`const ${a} = {};`);let u=t,d=u.partial?void 0:t.keyType._zod.values;if(d){let f=[];for(let y of d){if(!(typeof y=="string"||typeof y=="number"||typeof y=="symbol"))throw new V(`record key value ${String(y)}`);let x=typeof y=="number"?y.toString():y;if(x==="__proto__")throw new V('record key "__proto__"');f.push(x);let I=L(n,y),b=Q(e,n,t.keyType,I),T=z(n);e.write(`const ${T} = ${o}[${Ok(n,x)}];`);let A=ue(e,n,t.valueType,T);e.write(`${a}[${b}] = ${A};`)}let g=L(n,new Set(f));return e.write(`for (const ${s} in ${o}) {`),e.indented(y=>{y.write(`if (${g}.has(${s})) continue;`),u.mode==="loose"?y.write(`if (${s} !== "__proto__") ${a}[${s}] = ${o}[${s}];`):y.write("return INVALID;")}),e.write("}"),a}let p=t.keyType._zod.def;if(!(p.type==="string"&&p.format===void 0&&!p.coerce&&(p.checks?.length??0)===0)){let f=t.mode==="loose",g=eo(t.keyType);g.definite===!1&&(n.definite=!1);let y=L(n,g),x=L(n,qe),I=z(n);return Kf(e,n,o,s,b=>{b.write(`let ${I} = ${y}(${s});`),b.write(`if (${I} === INVALID && typeof ${s} === "string" && ${x}.test(${s})) ${I} = ${y}(Number(${s}));`),f?b.write(`if (${I} === INVALID) { ${a}[${s}] = ${o}[${s}]; continue; }`):b.write(`if (${I} === INVALID) return INVALID;`),b.write(`if (${I} === "__proto__") continue;`);let T=z(n);b.write(`const ${T} = ${o}[${s}];`);let A=ue(b,n,t.valueType,T);b.write(`${a}[${I}] = ${A};`)}),a}return Kf(e,n,o,s,f=>{f.write(`const ${c} = ${o}[${s}];`);let g=ue(f,n,t.valueType,c);f.write(`${a}[${s}] = ${g};`)},"return INVALID;"),a}function Kf(e,n,i,o,t,r){let a=L(n,Object.prototype.propertyIsEnumerable),s=z(n),c=z(n),u=z(n);e.write(`const ${s} = Object.getOwnPropertySymbols(${i});`),e.write(`const ${c} = Object.getOwnPropertyNames(${i});`),e.write(`for (let ${u} = 0; ${u} < ${c}.length; ${u}++) {`),e.indented(d=>{d.write(`const ${o} = ${c}[${u}];`),d.write(`if (${o} === "__proto__" || !${a}.call(${i}, ${o})) continue;`),t(d)}),e.write("}"),e.write(`for (let ${u} = 0; ${u} < ${s}.length; ${u}++) {`),e.indented(d=>{d.write(`const ${o} = ${s}[${u}];`),d.write(`if (!${a}.call(${i}, ${o})) continue;`),r?d.write(r):t(d)}),e.write("}")}function Ok(e,n){return typeof n=="string"?de(n):L(e,n)}function Nk(e,n,i,o){let t=i._zod.def;e.write(`if (!(${o} instanceof Map)) return INVALID;`),ku(e,i,`${o}.size`);let r=z(n),a=z(n),s=z(n);return e.write(`const ${r} = new Map();`),e.write(`for (const [${a}, ${s}] of ${o}) {`),e.indented(c=>{let u=Q(c,n,t.keyType,a),d=Q(c,n,t.valueType,s);c.write(`${r}.set(${u}, ${d});`)}),e.write("}"),r}function zk(e,n,i,o){let t=i._zod.def;e.write(`if (!(${o} instanceof Set)) return INVALID;`),ku(e,i,`${o}.size`);let r=z(n),a=z(n);return e.write(`const ${r} = new Set();`),e.write(`for (const ${a} of ${o}) {`),e.indented(s=>{let c=Q(s,n,t.valueType,a);s.write(`${r}.add(${c});`)}),e.write("}"),r}function Dk(e,n){return e.write(`if (!(${n} instanceof File)) return INVALID;`),n}function Ck(e,n,i,o){e.write(`if (typeof ${o} !== "string") return INVALID;`);let t=i._zod.pattern;if(t){let r=L(n,t);e.write(`${r}.lastIndex = 0;`),e.write(`if (!${r}.test(${o})) return INVALID;`)}return o}function jk(e,n,i,o){let t=i._zod.def,r=Tt(n,t.getter),a=L(n,{parser:null});e.write(`if (!${a}.parser) {`),e.indented(c=>{c.write(`const inner = ${r}();`),c.write(`${a}.parser = function(input) {`),c.indented(u=>{u.write("const result = inner._zod.run({ value: input, issues: [] }, {});"),u.write("return result.issues.length === 0 ? result.value : INVALID;")}),c.write("};")}),e.write("}");let s=z(n);return e.write(`const ${s} = ${a}.parser(${o});`),e.write(`if (${s} === INVALID) return INVALID;`),s}function Lk(e,n,i,o){let t=i._zod.def,r=Q(e,n,t.in,o);if(t.transform){if(Et(t.transform))throw new De("z.compile: async transforms in pipes are not supported");let a=t.transform,c=Tt(n,d=>{let p={value:d,issues:[],addIssue:$u},m=a(d,p);return m instanceof Promise?me:p.issues.length===0?m:me}),u=z(n);return e.write(`const ${u} = ${c}(${r});`),e.write(`if (${u} === INVALID) return INVALID;`),Q(e,n,t.out,u)}else return Q(e,n,t.out,r)}function Et(e){return typeof e=="function"&&(e.constructor.name==="AsyncFunction"||e[Symbol.toStringTag]==="AsyncFunction")}function Uk(e,n,i,o){let t=i._zod.def;if(t.fn){if(Et(t.fn))throw new De("z.compile: async custom predicates are not supported");let r=Tt(n,t.fn),a=L(n,yu),s=z(n);e.write(`const ${s} = ${r}(${o});`),e.write(`if (${s} instanceof Promise) ${a}();`),e.write(`if (!${s}) return INVALID;`)}else throw new V("custom schema without a predicate function");return o}function Rk(e,n,i){let o=e._zod.run({value:i,issues:[]},{});if(o&&typeof o.then=="function")return me;let t=o;return t.issues.length===0?t.value:n()}function Mk(e,n,i,o){let t=i._zod.def;if(!t.catchValue[oi])throw new V("catch with a callback (only a constant catch value compiles)",!1);let r=z(n);e.write(`let ${r} = (() => {`),e.indented(u=>{let d=ue(u,n,t.innerType,o);u.write(`return ${d};`)}),e.write("})();");let a=L(n,t.innerType),s=Tt(n,t.catchValue),c=L(n,Rk);return e.write(`if (${r} === INVALID) {`),e.indented(u=>{u.write(`${r} = ${c}(${a}, ${s}, ${o});`),u.write(`if (${r} === INVALID) return INVALID;`)}),e.write("}"),r}function Zk(e,n,i,o){let t=i._zod.def;if(t.transform){if(Et(t.transform))throw new De("z.compile: async transforms are not supported");let r=t.transform,s=Tt(n,u=>{let d={value:u,issues:[],addIssue:$u},p=r(u,d);return p instanceof Promise?me:d.issues.length===0?p:me}),c=z(n);return e.write(`const ${c} = ${s}(${o});`),e.write(`if (${c} === INVALID) return INVALID;`),c}return o}function to(e){return e.checks&&(e.checks=[...e.checks]),e}function xu(e,n){return new e(to({type:"string",...S(n)}))}function wu(e,n){return new e(to({type:"string",coerce:!0,...S(n)}))}function no(e,n){return new e({type:"string",format:"email",check:"string_format",abort:!1,...S(n)})}function ro(e,n){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...S(n)})}function io(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,...S(n)})}function oo(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...S(n)})}function ao(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...S(n)})}function so(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...S(n)})}function $r(e,n){return new e({type:"string",format:"url",check:"string_format",abort:!1,...S(n)})}function co(e,n){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,...S(n)})}function uo(e,n){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,...S(n)})}function lo(e,n){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,...S(n)})}function po(e,n){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,...S(n)})}function mo(e,n){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,...S(n)})}function fo(e,n){return new e({type:"string",format:"xid",check:"string_format",abort:!1,...S(n)})}function go(e,n){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,...S(n)})}function ho(e,n){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,...S(n)})}function vo(e,n){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,...S(n)})}function Iu(e,n){return new e({type:"string",format:"mac",check:"string_format",abort:!1,...S(n)})}function yo(e,n){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,...S(n)})}function bo(e,n){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,...S(n)})}function _o(e,n){return new e({type:"string",format:"base64",check:"string_format",abort:!1,...S(n)})}function $o(e,n){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,...S(n)})}function ko(e,n){return new e({type:"string",format:"e164",check:"string_format",abort:!1,...S(n)})}function Su(e,n){return new e({type:"string",format:"credit_card",check:"string_format",abort:!1,...S(n)})}function Pu(e,n){return new e({type:"string",format:"iban",check:"string_format",abort:!1,...S(n)})}function xo(e,n){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,...S(n)})}var Tu={Any:null,Minute:-1,Second:0,Millisecond:3,Microsecond:6};function kr(e,n){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...S(n)})}function xr(e,n){return new e({type:"string",format:"date",check:"string_format",...S(n)})}function wr(e,n){return new e({type:"string",format:"time",check:"string_format",precision:null,...S(n)})}function Ir(e,n){return new e({type:"string",format:"duration",check:"string_format",...S(n)})}function Eu(e,n){return new e(to({type:"number",checks:[],...S(n)}))}function Au(e,n){return new e(to({type:"number",coerce:!0,checks:[],...S(n)}))}function Ou(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",...S(n)})}function Nu(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"float32",...S(n)})}function zu(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"float64",...S(n)})}function Du(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"int32",...S(n)})}function Cu(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"uint32",...S(n)})}function ju(e,n){return new e({type:"boolean",...S(n)})}function Lu(e,n){return new e({type:"boolean",coerce:!0,...S(n)})}function Uu(e,n){return new e({type:"bigint",...S(n)})}function Ru(e,n){return new e({type:"bigint",coerce:!0,...S(n)})}function Mu(e,n){return new e({type:"bigint",check:"bigint_format",abort:!1,format:"int64",...S(n)})}function Zu(e,n){return new e({type:"bigint",check:"bigint_format",abort:!1,format:"uint64",...S(n)})}function Vu(e,n){return new e({type:"symbol",...S(n)})}function Gu(e,n){return new e({type:"undefined",...S(n)})}function Fu(e,n){return new e({type:"null",...S(n)})}function Bu(e){return new e({type:"any"})}function Hu(e){return new e({type:"unknown"})}function Ku(e,n){return new e({type:"never",...S(n)})}function Wu(e,n){return new e({type:"void",...S(n)})}function Ju(e,n){return new e({type:"date",...S(n)})}function Xu(e,n){return new e({type:"date",coerce:!0,...S(n)})}function qu(e,n){return new e({type:"nan",...S(n)})}function Ze(e,n){return new $i({check:"less_than",...S(n),value:e,inclusive:!1})}function we(e,n){return new $i({check:"less_than",...S(n),value:e,inclusive:!0})}function Ve(e,n){return new ki({check:"greater_than",...S(n),value:e,inclusive:!1})}function Ie(e,n){return new ki({check:"greater_than",...S(n),value:e,inclusive:!0})}function wo(e){return Ve(0,e)}function Io(e){return Ze(0,e)}function So(e){return we(0,e)}function Po(e){return Ie(0,e)}function st(e,n){return new zs({check:"multiple_of",...S(n),value:e})}function ct(e,n){return new js({check:"max_size",...S(n),maximum:e})}function Ge(e,n){return new Ls({check:"min_size",...S(n),minimum:e})}function At(e,n){return new Us({check:"size_equals",...S(n),size:e})}function Ot(e,n){return new Rs({check:"max_length",...S(n),maximum:e})}function Ye(e,n){return new Ms({check:"min_length",...S(n),minimum:e})}function Nt(e,n){return new Zs({check:"length_equals",...S(n),length:e})}function cn(e,n){return new Vs({check:"string_format",format:"regex",...S(n),pattern:e})}function un(e){return new Gs({check:"string_format",format:"lowercase",...S(e)})}function ln(e){return new Fs({check:"string_format",format:"uppercase",...S(e)})}function dn(e,n){return new Bs({check:"string_format",format:"includes",...S(n),includes:e})}function pn(e,n){return new Hs({check:"string_format",format:"starts_with",...S(n),prefix:e})}function mn(e,n){return new Ks({check:"string_format",format:"ends_with",...S(n),suffix:e})}function To(e,n,i){return new Ws({check:"property",property:e,schema:n,...S(i)})}function fn(e,n){return new Js({check:"properties",shape:e,...S(n)})}function gn(e,n){return new Xs({check:"mime_type",mime:e,...S(n)})}function Ce(e){return new qs({check:"overwrite",tx:e})}function hn(e){return Ce(n=>n.normalize(e))}function vn(){return Ce(e=>e.trim())}function yn(){return Ce(e=>e.toLowerCase())}function bn(){return Ce(e=>e.toUpperCase())}function _n(){return Ce(e=>Ca(e))}function Yu(e,n,i){return new e({type:"array",element:n,...S(i)})}function Vk(e,n,i){return new e({type:"union",options:n,...S(i)})}function Gk(e,n,i){return new e({type:"union",options:n,inclusive:!1,...S(i)})}function Fk(e,n,i,o){return new e({type:"union",options:i,discriminator:n,...S(o)})}function Bk(e,n,i){return new e({type:"intersection",left:n,right:i})}function Hk(e,n,i,o){let t=i instanceof U,r=t?o:i,a=t?i:null;return new e({type:"tuple",items:n,rest:a,...S(r)})}function Kk(e,n,i,o){return new e({type:"record",keyType:n,valueType:i,...S(o)})}function Wk(e,n,i,o){return new e({type:"map",keyType:n,valueType:i,...S(o)})}function Jk(e,n,i){return new e({type:"set",valueType:n,...S(i)})}function Xk(e,n,i){let o=Array.isArray(n)?Object.fromEntries(n.map(t=>[t,t])):n;return new e({type:"enum",entries:o,...S(i)})}function qk(e,n,i){return new e({type:"enum",entries:n,...S(i)})}function Yk(e,n,i){return new e({type:"literal",values:Array.isArray(n)?n:[n],...S(i)})}function Qu(e,n){return new e({type:"file",...S(n)})}function Qk(e,n){return new e({type:"transform",transform:n})}function ex(e,n){return new e({type:"optional",innerType:n})}function tx(e,n){return new e({type:"nullable",innerType:n})}function nx(e,n,i){return new e({type:"default",innerType:n,get defaultValue(){return typeof i=="function"?i():Bn(i)}})}function rx(e,n,i){return new e({type:"nonoptional",innerType:n,...S(i)})}function ix(e,n){return new e({type:"success",innerType:n})}function ox(e,n,i){return new e({type:"catch",innerType:n,catchValue:typeof i=="function"?i:Za(i)})}function ax(e,n,i){return new e({type:"pipe",in:n,out:i})}function sx(e,n){return new e({type:"readonly",innerType:n})}function cx(e,n,i){return new e({type:"template_literal",parts:n,...S(i)})}function ux(e,n){return new e({type:"lazy",getter:n})}function lx(e,n){return new e({type:"promise",innerType:n})}function el(e,n,i){let o=S(i);return o.abort??(o.abort=!0),new e({type:"custom",check:"custom",fn:n,...o})}function tl(e,n,i){return new e({type:"custom",check:"custom",fn:n,...S(i)})}function nl(e,n){let i=qf(o=>(o.addIssue=t=>{if(typeof t=="string")o.issues.push(qt(t,o.value,i._zod.def));else{let r=t;r.fatal&&(r.continue=!1),r.code??(r.code="custom"),"input"in r||(r.input=o.value),r.inst??(r.inst=i),r.continue??(r.continue=!i._zod.def.abort),o.issues.push(qt(r))}},e(o.value,o)),n);return i}function qf(e,n){let i=new q({check:"custom",...S(n)});return i._zod.check=e,i}function rl(e){let n=new q({check:"describe"});return n._zod.onattach=[i=>{let o=ce.get(i)??{};ce.add(i,{...o,description:e})}],n._zod.check=()=>{},n}function il(e){let n=new q({check:"meta"});return n._zod.onattach=[i=>{let o=ce.get(i)??{};ce.add(i,{...o,...e})}],n._zod.check=()=>{},n}function ol(e,n){let i=S(n),o=i.truthy??["true","1","yes","on","y","enabled"],t=i.falsy??["false","0","no","off","n","disabled"];i.case!=="sensitive"&&(o=o.map(f=>typeof f=="string"?f.toLowerCase():f),t=t.map(f=>typeof f=="string"?f.toLowerCase():f));let r=new Set(o),a=new Set(t),s=e.Codec??fr,c=e.Boolean??pr,u=e.String??St,d=new u({type:"string",error:i.error}),p=new c({type:"boolean",error:i.error}),m=new s({type:"pipe",in:d,out:p,transform:((f,g)=>{let y=f;return i.case!=="sensitive"&&(y=y.toLowerCase()),r.has(y)?!0:a.has(y)?!1:(g.issues.push({code:"invalid_value",expected:"stringbool",values:[...r,...a],input:g.value,inst:m,continue:!1}),{})}),reverseTransform:((f,g)=>f===!0?o[0]||"true":t[0]||"false"),error:i.error});return m._zod.bag.truthy=o,m._zod.bag.falsy=t,m._zod.bag.case=i.case??"insensitive",m}function zt(e,n,i,o={}){let t=S(o),r={check:"string_format",type:"string",format:n,fn:typeof i=="function"?i:s=>i.test(s),...t};return i instanceof RegExp&&(r.pattern=i),new e(r)}function Sr(e,...n){for(let i of n)for(let o of Reflect.ownKeys(i))Object.prototype.propertyIsEnumerable.call(i,o)&&se(e,o,i[o]);return e}function ut(e){let n=e?.target??"draft-2020-12";return n==="draft-4"&&(n="draft-04"),n==="draft-7"&&(n="draft-07"),{processors:e.processors??{},metadataRegistry:e?.metadata??ce,target:n,unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}),io:e?.io??"output",counter:0,seen:new Map,sharedDefsExtractedFor:void 0,sharedEmitDoneFor:void 0,cycles:e?.cycles??"ref",reused:e?.reused??"inline",intersections:[],deferred:[],external:e?.external??void 0}}function re(e,n,i,o,t){let r=typeof n.unrepresentable=="function"?n.unrepresentable({zodSchema:e,path:o.path,message:t}):n.unrepresentable;if(r==="any")return!1;if(r===void 0||r==="throw")throw new Error(t);return Object.assign(i,r),!0}function J(e,n,i={path:[],schemaPath:[]}){var o;let t=e._zod.def,r=n.seen.get(e);if(r)return r.count++,i.schemaPath.includes(e)&&(r.cycle=i.path),r.schema;let a={schema:{},count:1,cycle:void 0,path:i.path};n.seen.set(e,a),n.sharedDefsExtractedFor=void 0,n.sharedEmitDoneFor=void 0;let s=e._zod.toJSONSchema?.();if(s)a.schema=s;else{let d={...i,schemaPath:[...i.schemaPath,e],path:i.path};if(e._zod.processJSONSchema)e._zod.processJSONSchema(n,a.schema,d);else{let m=a.schema,f=n.processors[t.type];if(!f)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${t.type}`);f(e,n,m,d)}let p=e._zod.parent;p&&(a.ref||(a.ref=p),J(p,n,d),n.seen.get(p).isParent=!0)}let c=n.metadataRegistry.get(e);return c&&Sr(a.schema,c),n.io==="input"&&fe(e)&&(delete a.schema.examples,delete a.schema.default),n.io==="input"&&"_prefault"in a.schema&&((o=a.schema).default??(o.default=a.schema._prefault)),delete a.schema._prefault,n.seen.get(e).schema}function Yf(e){return e.replace(/~/g,"~0").replace(/\//g,"~1")}function lt(e,n){let i=e.seen.get(n);if(!i)throw new Error("Unprocessed schema. This is a bug in Zod.");if(e.external&&e.sharedDefsExtractedFor===e.external)return;let o=new Map;for(let a of e.seen.entries()){let s=e.metadataRegistry.get(a[0])?.id;if(s){let c=o.get(s);if(c&&c!==a[0])throw new Error(`Duplicate schema id "${s}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);o.set(s,a[0])}}let t=a=>{let s=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){let p=e.external.registry.get(a[0])?.id,m=e.external.uri??(g=>g);if(p)return{ref:m(p)};let f=a[1].defId??a[1].schema.id??`schema${e.counter++}`;return a[1].defId=f,{defId:f,ref:`${m("__shared")}#/${s}/${Yf(f)}`}}let c="#",u=`${c}/${s}/`;if(a[1]===i&&!a[1].schema.id)return{ref:c};let d=a[1].schema.id??`__schema${e.counter++}`;return{defId:d,ref:u+Yf(d)}},r=a=>{if(a[1].schema.$ref)return;let s=a[1],{ref:c,defId:u}=t(a);s.def={...s.schema},u&&(s.defId=u);let d=s.schema;for(let p in d)delete d[p];d.$ref=c};if(e.cycles==="throw")for(let a of e.seen.entries()){let s=a[1];if(s.cycle)throw new Error(`Cycle detected: #/${s.cycle?.join("/")}/ + `)}return x.write("payload.value = newResult;"),x.write("return payload;"),x.compile()},a,s=kt,u=!ae.jitless,d=u&&Za.value,p=n.catchall,m;e._zod.parse=(f,g)=>{m??(m=o.value);let y=f.value;return s(y)?u&&d&&g?.async===!1&&g.jitless!==!0?(a||(a=r(n.shape)),f=a(f,g),p?Nm([],y,f,g,m,e,g?.abortEarly===!0):f):i(f,g):(f.issues.push({expected:"object",code:"invalid_type",input:y,inst:e}),f)}});function gm(e,n,i,o){for(let r of e)if(r.issues.length===0)return n.value=r.value,n;let t=e.filter(r=>!me(r));return t.length===1?(n.value=t[0].value,t[0]):(n.issues.push({code:"invalid_union",input:n.value,inst:i,errors:e.map(r=>r.issues.map(a=>_e(a,o,te())))}),n)}var mr=h("$ZodUnion",(e,n)=>{U.init(e,n),Z(e,"optin",o=>o.def.options.some(t=>t._zod.optin==="defaulted")?"defaulted":o.def.options.some(t=>t._zod.optin!==void 0)?"optional":void 0),Z(e,"optout",o=>o.def.options.some(t=>t._zod.optout==="optional")?"optional":void 0),Z(e,"values",o=>{if(o.def.options.every(t=>t._zod.values))return new Set(o.def.options.flatMap(t=>Array.from(t._zod.values)))}),Z(e,"pattern",o=>{if(o.def.options.every(t=>t._zod.pattern)){let t=o.def.options.map(r=>r._zod.pattern);return new RegExp(`^(${t.map(r=>Xe(r.source)).join("|")})$`)}});let i=n.options.length===1?n.options[0]._zod.run:null;e._zod.parse=(o,t)=>{if(i)return i(o,t);let r=!1,a=[];for(let s of n.options){let u=s._zod.run({value:o.value,issues:[]},t);if(u instanceof Promise)a.push(u),r=!0;else{if(u.issues.length===0)return u;a.push(u)}}return r?Promise.all(a).then(s=>gm(s,o,e,t)):gm(a,o,e,t)}});function hm(e,n,i,o){let t=[];for(let r=0;rr.issues.map(a=>_e(a,o,te())))}):n.issues.push({code:"invalid_union",input:n.value,inst:i,errors:[],inclusive:!1,matches:t}),n)}var Wu=h("$ZodXor",(e,n)=>{mr.init(e,n),n.inclusive=!1;let i=n.options.length===1?n.options[0]._zod.run:null;e._zod.parse=(o,t)=>{if(i)return i(o,t);let r=!1,a=[];for(let s of n.options){let u=s._zod.run({value:o.value,issues:[]},t);u instanceof Promise?(a.push(u),r=!0):a.push(u)}return r?Promise.all(a).then(s=>hm(s,o,e,t)):hm(a,o,e,t)}});function Ku(e,n){let i=e._zod,o=i.bag.optionsMap;o||(o=Dm(i.def),i.bag.optionsMap=o);let t=o.get(n);if(t===null)throw new Error(`Ambiguous discriminator value "${String(n)}"`);return t}function Dm(e){let n=new Map;for(let i of e.options){let o=i._zod.propValues?.[e.discriminator];if(!o||o.size===0)throw new Error(`Invalid discriminated union option at index "${e.options.indexOf(i)}"`);for(let t of o)if(n.has(t)){if(t!==void 0)throw new Error(`Duplicate discriminator value "${String(t)}"`);n.set(t,null)}else n.set(t,i)}return n}var Ju=h("$ZodDiscriminatedUnion",(e,n)=>{n.inclusive=!1,mr.init(e,n);let i=e._zod.parse;Z(e,"propValues",t=>{let r={},a=0;for(let s of t.def.options){let u=s._zod.propValues;if(!u||Object.keys(u).length===0)throw new Error(`Invalid discriminated union option at index "${t.def.options.indexOf(s)}"`);u[t.def.discriminator]?.has(void 0)&&a++;for(let[c,d]of Object.entries(u)){Object.prototype.hasOwnProperty.call(r,c)||se(r,c,new Set);for(let p of d)r[c].add(p)}}return!t.def.unionFallback&&a>1&&r[t.def.discriminator]?.delete(void 0),r}),n.options.forEach((t,r)=>{let a=Jt(t._zod.def);if(a&&!Object.prototype.hasOwnProperty.call(a,n.discriminator))throw new Error(`Invalid discriminated union option at index "${r}"`)});let o=Kt(()=>Dm(n));e._zod.parse=(t,r)=>{let a=t.value;if(!kt(a))return t.issues.push({code:"invalid_type",expected:"object",input:a,inst:e}),t;let s=a?.[n.discriminator],u=o.value.get(s);return u&&(s!==void 0||r.direction!=="backward")?u._zod.run(t,r):n.unionFallback||r.direction==="backward"?i(t,r):(t.issues.push({code:"invalid_union",errors:[],note:"No matching discriminator",discriminator:n.discriminator,options:Array.from(o.value.keys()).filter(c=>o.value.get(c)!==null),input:a,path:[n.discriminator],inst:e}),t)}}),Xu=h("$ZodIntersection",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value,r=n.left._zod.run({value:t,issues:[]},o),a=n.right._zod.run({value:t,issues:[]},o);return r instanceof Promise||a instanceof Promise?Promise.all([r,a]).then(([u,c])=>vm(i,u,c)):vm(i,r,a)}});function rn(e,n){if(e===n)return{valid:!0,data:e};if(e instanceof Date&&n instanceof Date&&+e==+n)return{valid:!0,data:e};if(Se(e)&&Se(n)){let i=Object.keys(n),o=Object.keys(e).filter(r=>i.indexOf(r)!==-1),t={...e,...n};Object.prototype.hasOwnProperty.call(t,"__proto__")&&delete t.__proto__;for(let r of o){if(r==="__proto__")continue;let a=rn(e[r],n[r]);if(!a.valid)return{valid:!1,mergeErrorPath:[r,...a.mergeErrorPath]};t[r]=a.data}return{valid:!0,data:t}}if(Array.isArray(e)&&Array.isArray(n)){if(e.length!==n.length)return{valid:!1,mergeErrorPath:[]};let i=[];for(let o=0;o{let p;if(c.code==="unrecognized_keys"&&!c.path?.length)t??(t=c),p=c.keys;else if(c.code==="invalid_key"&&c.origin==="record"&&c.path?.length===1){let m=String(c.path[0]);r.has(m)||r.set(m,c),p=[m]}else return!1;for(let m of p)o.has(m)||o.set(m,{}),o.get(m)[d]=!0;return!0};for(let c of n.issues)a(c,"l")||e.issues.push(c);for(let c of i.issues)a(c,"r")||e.issues.push(c);let s=[...o].filter(([,c])=>c.l&&c.r).map(([c])=>c);if(s.length){let c=t?s.filter(d=>t.keys.includes(d)):[];c.length&&e.issues.push({...t,keys:c});for(let d of s)!c.includes(d)&&r.has(d)&&e.issues.push(r.get(d))}let u=rn(n.value,i.value);if(!u.valid){if(me(e))return e;throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(u.mergeErrorPath)}`)}return e.value=u.data,e}var Mi=h("$ZodTuple",(e,n)=>{U.init(e,n);let i=n.items,o=ae.memoizer;o?.attach(e),e._zod.parse=(t,r)=>{let a=t.value;if(!Array.isArray(a))return t.issues.push({input:a,inst:e,expected:"tuple",code:"invalid_type"}),t;t.value=o?o.alloc(e,t,[],r):[];let s=[],u=ym(i,"optin"),c=ym(i,"optout");if(!n.rest){if(a.lengthi.length&&t.issues.push({code:"too_big",maximum:i.length,inclusive:!0,input:a,inst:e,origin:"array"})}let d=new Array(i.length),p=n.rest?r?.abortEarly:void 0,m=!1;for(let f=0;f{d[f]=y})):(d[f]=g,p&&!m&&g.issues.length&&(m=me(g)))}if(n.rest&&!m){let f=i.length-1,g=a.slice(i.length),y=t.issues.length;for(let x of g){if(p&&t.issues.length!==y){if(me(t,y))break;y=t.issues.length}f++;let I=n.rest._zod.run({value:x,issues:[]},r);I instanceof Promise?s.push(I.then(b=>bm(b,t,f))):bm(I,t,f)}}return s.length?Promise.all(s).then(()=>_m(d,t,i,a,c)):_m(d,t,i,a,c)}});function ym(e,n){for(let i=e.length-1;i>=0;i--)if(!(n==="optin"?e[i]._zod.optin!==void 0:e[i]._zod.optout==="optional"))return i+1;return 0}function bm(e,n,i){e.issues.length&&n.issues.push(...be(i,e.issues)),n.value[i]=e.value}function _m(e,n,i,o,t){for(let r=0;r=t&&i[r]._zod.optin==="optional"){n.value.length=r;break}if(a.issues.length){if(!s&&r>=t){n.value.length=r;break}n.issues.push(...be(r,a.issues))}n.value[r]=a.value}for(let r=n.value.length-1;r>=o.length&&(i[r]._zod.optout==="optional"&&n.value[r]===void 0);r--)n.value.length=r;return n}var qu=h("$ZodRecord",(e,n)=>{U.init(e,n);let i=ae.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!Se(r))return o.issues.push({expected:"record",code:"invalid_type",input:r,inst:e}),o;let a=[],s=n.keyType._zod.values;if(s&&!n.partial){o.value=i?i.alloc(e,o,{},t):{};let u=new Set;for(let d of s)if(typeof d=="string"||typeof d=="number"||typeof d=="symbol"){if(u.add(typeof d=="number"?d.toString():d),d==="__proto__")continue;let p=n.keyType._zod.run({value:d,issues:[]},t);if(p instanceof Promise)throw new Error("Async schemas not supported in object keys currently");if(p.issues.length){o.issues.push({code:"invalid_key",origin:"record",issues:p.issues.map(g=>_e(g,t,te())),input:d,path:[d],inst:e});continue}let m=p.value;if(m==="__proto__")continue;let f=n.valueType._zod.run({value:r[d],issues:[]},t);f instanceof Promise?a.push(f.then(g=>{g.issues.length&&o.issues.push(...be(d,g.issues)),o.value[m]=g.value})):(f.issues.length&&o.issues.push(...be(d,f.issues)),o.value[m]=f.value)}let c;for(let d in r)if(!u.has(d))if(n.mode==="loose"){if(d==="__proto__")continue;o.value[d]=r[d]}else c=c??[],c.push(d);c&&c.length>0&&o.issues.push({code:"unrecognized_keys",input:r,inst:e,keys:c,continue:!0})}else{o.value=i?i.alloc(e,o,{},t):{};let u;for(let c of Reflect.ownKeys(r)){if(c==="__proto__"||!Object.prototype.propertyIsEnumerable.call(r,c))continue;let d=n.keyType._zod.run({value:c,issues:[]},t);if(d instanceof Promise)throw new Error("Async schemas not supported in object keys currently");if(typeof c=="string"&&qe.test(c)&&d.issues.length){let g=n.keyType._zod.run({value:Number(c),issues:[]},t);if(g instanceof Promise)throw new Error("Async schemas not supported in object keys currently");g.issues.length===0&&(d=g)}if(d.issues.length){n.mode==="loose"?o.value[c]=r[c]:s?(u=u??[],u.push(c)):o.issues.push({code:"invalid_key",origin:"record",issues:d.issues.map(g=>_e(g,t,te())),input:c,path:[c],inst:e});continue}let m=d.value;if(m==="__proto__")continue;let f=n.valueType._zod.run({value:r[c],issues:[]},t);f instanceof Promise?a.push(f.then(g=>{g.issues.length&&o.issues.push(...be(c,g.issues)),o.value[m]=g.value})):(f.issues.length&&o.issues.push(...be(c,f.issues)),o.value[m]=f.value)}u&&u.length>0&&o.issues.push({code:"unrecognized_keys",input:r,inst:e,keys:u,continue:!0})}return a.length?Promise.all(a).then(()=>o):o}}),Yu=h("$ZodMap",(e,n)=>{U.init(e,n);let i=ae.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!(r instanceof Map))return o.issues.push({expected:"map",code:"invalid_type",input:r,inst:e}),o;let a=[];o.value=i?i.alloc(e,o,new Map,t):new Map;let s=t?.abortEarly,u=o.issues.length;for(let[c,d]of r){if(s&&o.issues.length!==u){if(me(o,u))break;u=o.issues.length}let p=n.keyType._zod.run({value:c,issues:[]},t),m=n.valueType._zod.run({value:d,issues:[]},t);p instanceof Promise||m instanceof Promise?a.push(Promise.all([p,m]).then(([f,g])=>{$m(f,g,o,c,r,e,t)})):$m(p,m,o,c,r,e,t)}return a.length?Promise.all(a).then(()=>o):o},Gu(e,W_)});function $m(e,n,i,o,t,r,a){e.issues.length&&(Hn.has(typeof o)?i.issues.push(...be(o,e.issues)):i.issues.push({code:"invalid_key",origin:"map",input:t,inst:r,issues:e.issues.map(s=>_e(s,a,te()))})),n.issues.length&&(Hn.has(typeof o)?i.issues.push(...be(o,n.issues)):i.issues.push({origin:"map",code:"invalid_element",input:t,inst:r,key:o,issues:n.issues.map(s=>_e(s,a,te()))})),i.value.set(e.value,n.value)}var Qu=h("$ZodSet",(e,n)=>{U.init(e,n);let i=ae.memoizer;i?.attach(e),e._zod.parse=(o,t)=>{let r=o.value;if(!(r instanceof Set))return o.issues.push({input:r,inst:e,expected:"set",code:"invalid_type"}),o;let a=[];o.value=i?i.alloc(e,o,new Set,t):new Set;let s=t?.abortEarly,u=o.issues.length;for(let c of r){if(s&&o.issues.length!==u){if(me(o,u))break;u=o.issues.length}let d=n.valueType._zod.run({value:c,issues:[]},t);d instanceof Promise?a.push(d.then(p=>km(p,o))):km(d,o)}return a.length?Promise.all(a).then(()=>o):o},Gu(e,K_)});function km(e,n){e.issues.length&&n.issues.push(...e.issues),n.value.add(e.value)}var ec=h("$ZodEnum",(e,n)=>{U.init(e,n);let i=Ht(n.entries),o=new Set(i);e._zod.values=o,Z(e,"pattern",t=>{let r=Ht(t.def.entries).filter(a=>Hn.has(typeof a));return new RegExp(r.length?`^(${r.map(a=>Pe(a.toString())).join("|")})$`:"^[^\\s\\S]$")}),e._zod.parse=(t,r)=>{let a=t.value;return o.has(a)||t.issues.push({code:"invalid_value",values:i,input:a,inst:e}),t}}),tc=h("$ZodLiteral",(e,n)=>{U.init(e,n);let i=new Set(n.values);e._zod.values=i,Z(e,"pattern",o=>{let t=o.def.values;return new RegExp(t.length?`^(${t.map(r=>typeof r=="string"?Pe(r):r?Pe(r.toString()):String(r)).join("|")})$`:"^[^\\s\\S]$")}),e._zod.parse=(o,t)=>{let r=o.value;return i.has(r)||o.issues.push({code:"invalid_value",values:n.values,input:r,inst:e}),o}}),nc=h("$ZodFile",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{let t=i.value;return t instanceof File||i.issues.push({expected:"file",code:"invalid_type",input:t,inst:e}),i}}),rc=h("$ZodTransform",(e,n)=>{U.init(e,n),e._zod.optin="optional",ae.memoizer?.guard(e),e._zod.parse=(i,o)=>{if(o.direction==="backward")throw new ot(e.constructor.name);let t=n.transform(i.value,i);if(o.async)return(t instanceof Promise?t:Promise.resolve(t)).then(a=>(i.value=a,i));if(t instanceof Promise)throw new $e;return i.value=t,i}});function xm(e,n){return e.value=n.issues.length?void 0:n.value,e}var Zi=h("$ZodOptional",(e,n)=>{U.init(e,n),Z(e,"optin",i=>i.def.innerType._zod.optin==="defaulted"?"defaulted":"optional"),e._zod.optout="optional",Z(e,"values",i=>{let o=i.def.innerType._zod.values;return o?new Set([...o,void 0]):void 0}),Z(e,"pattern",i=>{let o=i.def.innerType._zod.pattern;return o?new RegExp(`^(${Xe(o.source)})?$`):void 0}),e._zod.parse=(i,o)=>{if(i.value===void 0){if(n.innerType._zod.optin!=="defaulted")return i;let t=n.innerType._zod.run({value:i.value,issues:[]},o);return t instanceof Promise?t.then(r=>xm(i,r)):xm(i,t)}return n.innerType._zod.run(i,o)}}),ic=h("$ZodExactOptional",(e,n)=>{Zi.init(e,n),Z(e,"values",i=>i.def.innerType._zod.values),Z(e,"pattern",i=>i.def.innerType._zod.pattern),e._zod.parse=(i,o)=>n.innerType._zod.run(i,o)}),oc=h("$ZodNullable",(e,n)=>{U.init(e,n),Z(e,"optin",i=>i.def.innerType._zod.optin),Z(e,"optout",i=>i.def.innerType._zod.optout),Z(e,"pattern",i=>{let o=i.def.innerType._zod.pattern;return o?new RegExp(`^(${Xe(o.source)}|null)$`):void 0}),Z(e,"values",i=>i.def.innerType._zod.values?new Set([...i.def.innerType._zod.values,null]):void 0),e._zod.parse=(i,o)=>i.value===null?i:n.innerType._zod.run(i,o)}),ac=h("$ZodDefault",(e,n)=>{U.init(e,n),e._zod.optin="defaulted",Z(e,"values",i=>i.def.innerType._zod.values),e._zod.parse=(i,o)=>{if(o.direction==="backward")return n.innerType._zod.run(i,o);if(i.value===void 0)return i.value=n.defaultValue,i;let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(r=>wm(r,n)):wm(t,n)}});function wm(e,n){return e.value===void 0&&(e.value=n.defaultValue),e}var sc=h("$ZodPrefault",(e,n)=>{U.init(e,n),e._zod.optin="defaulted",Z(e,"values",i=>i.def.innerType._zod.values),e._zod.parse=(i,o)=>(o.direction==="backward"||i.value===void 0&&(i.value=n.defaultValue),n.innerType._zod.run(i,o))}),uc=h("$ZodNonOptional",(e,n)=>{U.init(e,n),Z(e,"values",i=>{let o=i.def.innerType._zod.values;return o?new Set([...o].filter(t=>t!==void 0)):void 0}),e._zod.parse=(i,o)=>{let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(r=>Im(r,e)):Im(t,e)}});function Im(e,n){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:n}),e}var cc=h("$ZodSuccess",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>{if(o.direction==="backward")throw new ot("ZodSuccess");let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(r=>(i.value=r.issues.length===0,i)):(i.value=t.issues.length===0,i)}});function Sm(e,n,i,o){return n.issues.length?(e.value=i.catchValue({...n,value:e.value,error:{issues:n.issues.map(t=>_e(t,o,te()))},input:e.value}),e):(e.value=n.value,n.memo&&(e.memo=!0),e)}var lc=h("$ZodCatch",(e,n)=>{U.init(e,n),Z(e,"optin",i=>i.def.innerType._zod.optin==="defaulted"?"defaulted":"optional"),Z(e,"optout",i=>i.def.innerType._zod.optout),Z(e,"values",i=>i.def.innerType._zod.values),e._zod.parse=(i,o)=>{if(o.direction==="backward")return n.innerType._zod.run(i,o);let t=n.innerType._zod.run({value:i.value,issues:[]},o);return t instanceof Promise?t.then(r=>Sm(i,r,n,o)):Sm(i,t,n,o)}}),dc=h("$ZodNaN",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>((typeof i.value!="number"||!Number.isNaN(i.value))&&i.issues.push({input:i.value,inst:e,expected:"nan",code:"invalid_type"}),i)}),Fi=h("$ZodPipe",(e,n)=>{U.init(e,n),Z(e,"values",i=>i.def.in._zod.values),Z(e,"optin",i=>i.def.in._zod.optin),Z(e,"optout",i=>i.def.out._zod.optout),Z(e,"propValues",i=>i.def.in._zod.propValues),e._zod.parse=(i,o)=>{if(o.direction==="backward"){let r=n.out._zod.run(i,o);return r instanceof Promise?r.then(a=>wi(a,n.in,o)):wi(r,n.in,o)}let t=n.in._zod.run(i,o);return t instanceof Promise?t.then(r=>wi(r,n.out,o)):wi(t,n.out,o)}});function wi(e,n,i){return e.issues.some(o=>o.code!=="unrecognized_keys")?(e.aborted=!0,e):n._zod.run({value:e.value,issues:e.issues},i)}var fr=h("$ZodCodec",(e,n)=>{U.init(e,n),Z(e,"values",i=>i.def.in._zod.values),Z(e,"optin",i=>i.def.in._zod.optin),Z(e,"optout",i=>i.def.out._zod.optout),Z(e,"propValues",i=>i.def.in._zod.propValues),e._zod.parse=(i,o)=>{if((o.direction||"forward")==="forward"){let r=n.in._zod.run(i,o);return r instanceof Promise?r.then(a=>Ii(a,n,o)):Ii(r,n,o)}else{let r=n.out._zod.run(i,o);return r instanceof Promise?r.then(a=>Ii(a,n,o)):Ii(r,n,o)}}});function Ii(e,n,i){if(e.issues.length)return e.aborted=!0,e;if((i.direction||"forward")==="forward"){let t=n.transform(e.value,e);return t instanceof Promise?t.then(r=>Si(e,r,n.out,i)):Si(e,t,n.out,i)}else{let t=n.reverseTransform(e.value,e);return t instanceof Promise?t.then(r=>Si(e,r,n.in,i)):Si(e,t,n.in,i)}}function Si(e,n,i,o){return e.issues.length?(e.aborted=!0,e):i._zod.run({value:n,issues:e.issues},o)}var pc=h("$ZodPreprocess",(e,n)=>{Fi.init(e,n)}),mc=h("$ZodReadonly",(e,n)=>{U.init(e,n),Z(e,"propValues",i=>i.def.innerType._zod.propValues),Z(e,"values",i=>i.def.innerType._zod.values),Z(e,"optin",i=>i.def.innerType?._zod?.optin),Z(e,"optout",i=>i.def.innerType?._zod?.optout),e._zod.parse=(i,o)=>{if(o.direction==="backward")return n.innerType._zod.run(i,o);let t=n.innerType._zod.run(i,o);return t instanceof Promise?t.then(Pm):Pm(t)}});function Pm(e){return e.memo||(e.value=Object.freeze(e.value)),e}function X_(e){let n=e._zod.def,i=n.pattern,o=!!n.format?.includes("int"),t,r;for(let s of n.checks??[]){let u=s._zod.def;u.pattern&&(i=u.pattern),o||(o=!!u.format?.includes("int"));let c=u.minimum??u.length,d=u.maximum??u.length;c!==void 0&&(t===void 0||c>t)&&(t=c),d!==void 0&&(r===void 0||dr)return"(?!)";if(t!==void 0||r!==void 0)return Ts({minimum:t,maximum:r}).source;let a=e._zod.pattern;return(o&&a===qe?ir:a)?.source}function iu(e){let n=e._zod.def,i=e._zod.pattern?.source,o=n.innerType??e._zod.innerType;if(o){let t=o._zod.pattern?.source,r=iu(o);return i&&t&&r&&r!==t?i.replace(Xe(t),()=>Xe(r)):i}if(n.options){let t=n.options.map(iu);if(t.every(Boolean))return`^(${t.map(r=>Xe(r)).join("|")})$`}return X_(e)}var fc=h("$ZodTemplateLiteral",(e,n)=>{U.init(e,n);let i=[];for(let o of n.parts)if(typeof o=="object"&&o!==null){let t=iu(o);if(!t)throw new Error(`Invalid template literal part, no pattern found: ${[...o._zod.traits].shift()}`);i.push(Xe(t))}else if(o===null||Fa.has(typeof o))i.push(Pe(`${o}`));else throw new Error(`Invalid template literal part: ${o}`);e._zod.pattern=new RegExp(`^${i.join("")}$`),e._zod.parse=(o,t)=>typeof o.value!="string"?(o.issues.push({input:o.value,inst:e,expected:"string",code:"invalid_type"}),o):(e._zod.pattern.lastIndex=0,e._zod.pattern.test(o.value)||o.issues.push({input:o.value,inst:e,code:"invalid_format",format:n.format??"template_literal",pattern:e._zod.pattern.source}),o)}),gc=h("$ZodFunction",(e,n)=>(U.init(e,n),Object.defineProperty(e,"_def",{value:n}),e._zod.def=n,e.implement=i=>{if(typeof i!="function")throw new Error("implement() must be called with a function");return Object.defineProperty(function(...o){let t=e._def.input?ui(e._def.input,o):o,r=Reflect.apply(i,this,t);return e._def.output?ui(e._def.output,r):r},"_zod",{value:e._zod,enumerable:!1})},e.implementAsync=i=>{if(typeof i!="function")throw new Error("implementAsync() must be called with a function");return Object.defineProperty(async function(...o){let t=e._def.input?await ci(e._def.input,o):o,r=await Reflect.apply(i,this,t);return e._def.output?await ci(e._def.output,r):r},"_zod",{value:e._zod,enumerable:!1})},e._zod.parse=(i,o)=>typeof i.value!="function"?(i.issues.push({code:"invalid_type",expected:"function",input:i.value,inst:e}),i):(e._def.output&&e._def.output._zod.def.type==="promise"?i.value=e.implementAsync(i.value):i.value=e.implement(i.value),i),e.input=(...i)=>{let o=e.constructor;return Array.isArray(i[0])?new o({type:"function",input:new Mi({type:"tuple",items:i[0],rest:i[1]}),output:e._def.output}):new o({type:"function",input:i[0],output:e._def.output})},e.output=i=>{let o=e.constructor;return new o({type:"function",input:e._def.input,output:i})},e)),hc=h("$ZodPromise",(e,n)=>{U.init(e,n),e._zod.parse=(i,o)=>Promise.resolve(i.value).then(t=>n.innerType._zod.run({value:t,issues:[]},o))}),gr=h("$ZodLazy",(e,n)=>{U.init(e,n),Ra(e._zod,"innerType",()=>{let i=n;return i._cachedInner||(i._cachedInner=n.getter()),i._cachedInner}),Z(e,"pattern",i=>i.innerType?._zod?.pattern),Z(e,"propValues",i=>i.innerType?._zod?.propValues),Z(e,"optin",i=>i.innerType?._zod?.optin??void 0),Z(e,"optout",i=>i.innerType?._zod?.optout??void 0),e._zod.parse=(i,o)=>e._zod.innerType._zod.run(i,o)}),vc=h("$ZodCustom",(e,n)=>{q.init(e,n),U.init(e,n),e._zod.parse=(i,o)=>i,e._zod.check=i=>{let o=i.value,t=n.fn(o);if(t instanceof Promise)return t.then(r=>Tm(r,i,o,e));Tm(t,i,o,e)}});function Tm(e,n,i,o){if(!e){let t={code:"custom",input:i,inst:o,path:[...o._zod.def.path??[]],continue:!o._zod.def.abort};o._zod.def.params&&(t.params=o._zod.def.params),n.issues.push(qt(t))}}var Hi=class extends Error{constructor(){super("Cannot parse a reference cycle that closes through a transform"),this.name="ZodCyclicError"}},bc="~memo",Lm=[];function jm(e){return e!==null&&typeof e=="object"}function yc(e){return e.map(n=>n.path?{...n,path:n.path.slice()}:{...n})}var Um=new WeakMap,on=0,Bi=1,hr=2;function vr(e,n,i){let o=Um.get(e);if(o!==void 0)return o?hr:on;if(n.has(e))return hr;n.add(e);let t=on,r=d=>{if(t!==hr&&d?._zod){let p=vr(d,n,i);p>t&&(t=p)}},a=(d,p)=>{let m=on;for(let f of Reflect.ownKeys(d)){let g=Object.getOwnPropertyDescriptor(d,f);if(p&&!g.enumerable)continue;let y=g.get?Bi:g.value?._zod?vr(g.value,n,i):on;y>m&&(m=y)}return m},s=d=>{d>t&&(t=d)},u=e._zod.def,c=u.type;switch(c){case"object":{let d=Jt(u);s(d?a(d,!0):Bi),r(u.catchall);break}case"array":r(u.element);break;case"tuple":for(let d of u.items)r(d);r(u.rest);break;case"record":case"map":r(u.keyType),r(u.valueType);break;case"set":r(u.valueType);break;case"union":for(let d of u.options)r(d);break;case"intersection":r(u.left),r(u.right);break;case"optional":case"nullable":case"default":case"prefault":case"catch":case"readonly":case"nonoptional":case"promise":case"success":r(u.innerType);break;case"pipe":r(u.in),r(u.out);break;case"function":r(u.input),r(u.output);break;case"lazy":{let d=u._cachedInner??(i?e._zod.innerType:void 0);s(d?vr(d,n,!1):Bi);break}case"template_literal":case"string":case"number":case"int":case"boolean":case"bigint":case"symbol":case"undefined":case"null":case"void":case"never":case"any":case"unknown":case"date":case"nan":case"enum":case"literal":case"file":case"transform":case"custom":break;default:for(let d in u){let p=Object.getOwnPropertyDescriptor(u,d);if(!p||p.get)continue;let m=p.value;if(!(!m||typeof m!="object")){if(m._zod)r(m);else if(Array.isArray(m))for(let f of m)r(f)}}}return n.delete(e),q_(e,t)}function q_(e,n){return n!==Bi&&Um.set(e,n===hr),n}function Wi(e){return vr(e,new Set,!0)!==on}function Y_(e,n){let i=e.buckets.get(n);return i||(i=new WeakMap,e.buckets.set(n,i)),i}var Vi,Gi=[],Q_={alloc(e,n,i){let o=Vi;if(!o)return i;Vi=void 0;let t={value:i,issues:null};return o.set(n.value,t),Gi.push(t),i},guard(e){var n;(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred.push(()=>{let i=e._zod.parse,o=(t,r)=>{if(r.direction!=="backward"&&Ki(r,t.value))throw new Hi;return i(t,r)};e._zod.parse=o,e._zod.run===i&&(e._zod.run=o)})},attach(e){var n;let i,o=!1,t,r;(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred.push(()=>{let a=e._zod.parse,s=(u,c)=>{if(i===void 0){let I=vr(e,new Set,!1);if(I===on)return e._zod.parse=a,e._zod.run===s&&(e._zod.run=a),a(u,c);I===hr||o?i=!0:o=!0}let d=u.value;if(!jm(d))return a(u,c);let p=c[bc];p||(p={buckets:new WeakMap,backEdges:void 0},c[bc]=p);let m;t===c?m=r:(m=Y_(p,e),t=c,r=m);let f=m.get(d);if(f)return u.value=f.value,f.issues?f.issues.length&&u.issues.push(...yc(f.issues)):(u.memo=!0,p.backEdges??(p.backEdges=new WeakSet),p.backEdges.add(f.value)),u;Vi=m;let g=Gi.length,y=a(u,c);Vi=void 0;let x=Gi.length>g?Gi.pop():void 0;return y instanceof Promise?y.then(I=>(x&&(x.issues=I.issues.length?yc(I.issues):Lm),I)):(x&&(x.issues=y.issues.length?yc(y.issues):Lm),y)};e._zod.parse=s,e._zod.run===a&&(e._zod.run=s)})}};function yr(){return Q_}function Ki(e,n){let i=e[bc]?.backEdges;return i!==void 0&&jm(n)&&i.has(n)}var _r={};Ke(_r,{ar:()=>Rm,az:()=>Mm,be:()=>Fm,bg:()=>Vm,bn:()=>Gm,ca:()=>Bm,ckb:()=>Hm,cs:()=>Wm,da:()=>Km,de:()=>Jm,el:()=>Xm,en:()=>Ji,eo:()=>qm,es:()=>Ym,fa:()=>Qm,fi:()=>ef,fr:()=>tf,frCA:()=>nf,gu:()=>rf,he:()=>of,hi:()=>af,hr:()=>sf,hu:()=>uf,hy:()=>lf,id:()=>df,is:()=>pf,it:()=>mf,ja:()=>ff,ka:()=>gf,kh:()=>hf,km:()=>Xi,kn:()=>vf,ko:()=>yf,lt:()=>_f,mk:()=>$f,ms:()=>kf,ne:()=>xf,nl:()=>wf,nn:()=>If,no:()=>Sf,ota:()=>Pf,pl:()=>Ef,ps:()=>Tf,pt:()=>Af,ptBR:()=>Of,ro:()=>Nf,ru:()=>Df,sk:()=>Cf,sl:()=>Lf,sv:()=>jf,ta:()=>Uf,tg:()=>Rf,th:()=>Mf,tk:()=>Zf,tr:()=>Ff,ua:()=>Vf,uk:()=>qi,ur:()=>Gf,uz:()=>Bf,vi:()=>Hf,yo:()=>Jf,zhCN:()=>Wf,zhTW:()=>Kf});var e$=()=>{let e={string:{unit:"حرف",verb:"أن يحوي"},file:{unit:"بايت",verb:"أن يحوي"},array:{unit:"عنصر",verb:"أن يحوي"},set:{unit:"عنصر",verb:"أن يحوي"},map:{unit:"عنصر",verb:"أن يحوي"}};function n(t){return e[t]??null}let i={regex:"مدخل",email:"بريد إلكتروني",url:"رابط",emoji:"إيموجي",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"تاريخ ووقت بمعيار ISO",date:"تاريخ بمعيار ISO",time:"وقت بمعيار ISO",duration:"مدة بمعيار ISO",ipv4:"عنوان IPv4",ipv6:"عنوان IPv6",mac:"عنوان MAC",cidrv4:"مدى عناوين بصيغة IPv4",cidrv6:"مدى عناوين بصيغة IPv6",base64:"نَص بترميز base64-encoded",base64url:"نَص بترميز base64url-encoded",json_string:"نَص على هيئة JSON",e164:"رقم هاتف بمعيار E.164",credit_card:"رقم بطاقة الائتمان",currency_code:"رمز العملة",iban:"IBAN",jwt:"JWT",template_literal:"مدخل"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`مدخلات غير مقبولة: يفترض إدخال instanceof ${t.expected}، ولكن تم إدخال ${s}`:`مدخلات غير مقبولة: يفترض إدخال ${r}، ولكن تم إدخال ${s}`}case"invalid_value":return t.values.length===1?`مدخلات غير مقبولة: يفترض إدخال ${$(t.values[0])}`:`اختيار غير مقبول: يتوقع انتقاء أحد هذه الخيارات: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?` أكبر من اللازم: يفترض أن تكون ${t.origin??"القيمة"} ${r} ${t.maximum.toString()} ${a.unit??"عنصر"}`:`أكبر من اللازم: يفترض أن تكون ${t.origin??"القيمة"} ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`أصغر من اللازم: يفترض لـ ${t.origin} أن يكون ${r} ${t.minimum.toString()} ${a.unit}`:`أصغر من اللازم: يفترض لـ ${t.origin} أن يكون ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`نَص غير مقبول: يجب أن يبدأ بـ "${t.prefix}"`:r.format==="ends_with"?`نَص غير مقبول: يجب أن ينتهي بـ "${r.suffix}"`:r.format==="includes"?`نَص غير مقبول: يجب أن يتضمَّن "${r.includes}"`:r.format==="regex"?`نَص غير مقبول: يجب أن يطابق النمط ${r.pattern}`:`${i[r.format]??t.format} غير مقبول`}case"not_multiple_of":return`رقم غير مقبول: يجب أن يكون من مضاعفات ${t.divisor}`;case"unrecognized_keys":return`معرف${t.keys.length>1?"ات":""} غريب${t.keys.length>1?"ة":""}: ${v(t.keys,"، ")}`;case"invalid_key":return`معرف غير مقبول في ${t.origin}`;case"invalid_union":return"مدخل غير مقبول";case"invalid_element":return`مدخل غير مقبول في ${t.origin}`;default:return"مدخل غير مقبول"}}};function Rm(){return{localeError:e$()}}var t$=()=>{let e={string:{unit:"simvol",verb:"olmalıdır"},file:{unit:"bayt",verb:"olmalıdır"},array:{unit:"element",verb:"olmalıdır"},set:{unit:"element",verb:"olmalıdır"},map:{unit:"element",verb:"olmalıdır"}};function n(t){return e[t]??null}let i={regex:"input",email:"email address",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datetime",date:"ISO date",time:"ISO time",duration:"ISO duration",ipv4:"IPv4 address",ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded string",base64url:"base64url-encoded string",json_string:"JSON string",e164:"E.164 number",credit_card:"kredit kartı nömrəsi",currency_code:"valyuta kodu",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Yanlış dəyər: gözlənilən instanceof ${t.expected}, daxil olan ${s}`:`Yanlış dəyər: gözlənilən ${r}, daxil olan ${s}`}case"invalid_value":return t.values.length===1?`Yanlış dəyər: gözlənilən ${$(t.values[0])}`:`Yanlış seçim: aşağıdakılardan biri olmalıdır: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Çox böyük: gözlənilən ${t.origin??"dəyər"} ${r}${t.maximum.toString()} ${a.unit??"element"}`:`Çox böyük: gözlənilən ${t.origin??"dəyər"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Çox kiçik: gözlənilən ${t.origin} ${r}${t.minimum.toString()} ${a.unit}`:`Çox kiçik: gözlənilən ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Yanlış mətn: "${r.prefix}" ilə başlamalıdır`:r.format==="ends_with"?`Yanlış mətn: "${r.suffix}" ilə bitməlidir`:r.format==="includes"?`Yanlış mətn: "${r.includes}" daxil olmalıdır`:r.format==="regex"?`Yanlış mətn: ${r.pattern} şablonuna uyğun olmalıdır`:`Yanlış ${i[r.format]??t.format}`}case"not_multiple_of":return`Yanlış ədəd: ${t.divisor} ilə bölünə bilən olmalıdır`;case"unrecognized_keys":return`Tanınmayan açar${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} daxilində yanlış açar`;case"invalid_union":return"Yanlış dəyər";case"invalid_element":return`${t.origin} daxilində yanlış dəyər`;default:return"Yanlış dəyər"}}};function Mm(){return{localeError:t$()}}function Zm(e,n,i,o){let t=Math.abs(e),r=t%10,a=t%100;return a>=11&&a<=19?o:r===1?n:r>=2&&r<=4?i:o}var n$=()=>{let e={string:{unit:{one:"сімвал",few:"сімвалы",many:"сімвалаў"},verb:"мець"},array:{unit:{one:"элемент",few:"элементы",many:"элементаў"},verb:"мець"},set:{unit:{one:"элемент",few:"элементы",many:"элементаў"},verb:"мець"},map:{unit:{one:"элемент",few:"элементы",many:"элементаў"},verb:"мець"},file:{unit:{one:"байт",few:"байты",many:"байтаў"},verb:"мець"}};function n(t){return e[t]??null}let i={regex:"увод",email:"email адрас",url:"URL",emoji:"эмодзі",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO дата і час",date:"ISO дата",time:"ISO час",duration:"ISO працягласць",ipv4:"IPv4 адрас",ipv6:"IPv6 адрас",mac:"MAC адрас",cidrv4:"IPv4 дыяпазон",cidrv6:"IPv6 дыяпазон",base64:"радок у фармаце base64",base64url:"радок у фармаце base64url",json_string:"JSON радок",e164:"нумар E.164",credit_card:"нумар крэдытнай карты",currency_code:"код валюты",iban:"IBAN",jwt:"JWT",template_literal:"увод"},o={nan:"NaN",number:"лік",array:"масіў"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Няправільны ўвод: чакаўся instanceof ${t.expected}, атрымана ${s}`:`Няправільны ўвод: чакаўся ${r}, атрымана ${s}`}case"invalid_value":return t.values.length===1?`Няправільны ўвод: чакалася ${$(t.values[0])}`:`Няправільны варыянт: чакаўся адзін з ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);if(a){let s=Number(t.maximum),u=Zm(s,a.unit.one,a.unit.few,a.unit.many);return`Занадта вялікі: чакалася, што ${t.origin??"значэнне"} павінна ${a.verb} ${r}${t.maximum.toString()} ${u}`}return`Занадта вялікі: чакалася, што ${t.origin??"значэнне"} павінна быць ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);if(a){let s=Number(t.minimum),u=Zm(s,a.unit.one,a.unit.few,a.unit.many);return`Занадта малы: чакалася, што ${t.origin} павінна ${a.verb} ${r}${t.minimum.toString()} ${u}`}return`Занадта малы: чакалася, што ${t.origin} павінна быць ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Няправільны радок: павінен пачынацца з "${r.prefix}"`:r.format==="ends_with"?`Няправільны радок: павінен заканчвацца на "${r.suffix}"`:r.format==="includes"?`Няправільны радок: павінен змяшчаць "${r.includes}"`:r.format==="regex"?`Няправільны радок: павінен адпавядаць шаблону ${r.pattern}`:`Няправільны ${i[r.format]??t.format}`}case"not_multiple_of":return`Няправільны лік: павінен быць кратным ${t.divisor}`;case"unrecognized_keys":return`Нераспазнаны ${t.keys.length>1?"ключы":"ключ"}: ${v(t.keys,", ")}`;case"invalid_key":return`Няправільны ключ у ${t.origin}`;case"invalid_union":return"Няправільны ўвод";case"invalid_element":return`Няправільнае значэнне ў ${t.origin}`;default:return"Няправільны ўвод"}}};function Fm(){return{localeError:n$()}}var r$=()=>{let e={string:{unit:"символа",verb:"да съдържа"},file:{unit:"байта",verb:"да съдържа"},array:{unit:"елемента",verb:"да съдържа"},set:{unit:"елемента",verb:"да съдържа"},map:{unit:"елемента",verb:"да съдържа"}};function n(t){return e[t]??null}let i={regex:"вход",email:"имейл адрес",url:"URL",emoji:"емоджи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO време",date:"ISO дата",time:"ISO време",duration:"ISO продължителност",ipv4:"IPv4 адрес",ipv6:"IPv6 адрес",mac:"MAC адрес",cidrv4:"IPv4 диапазон",cidrv6:"IPv6 диапазон",base64:"base64-кодиран низ",base64url:"base64url-кодиран низ",json_string:"JSON низ",e164:"E.164 номер",credit_card:"номер на кредитна карта",currency_code:"код на валута",iban:"IBAN",jwt:"JWT",template_literal:"вход"},o={nan:"NaN",number:"число",array:"масив"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Невалиден вход: очакван instanceof ${t.expected}, получен ${s}`:`Невалиден вход: очакван ${r}, получен ${s}`}case"invalid_value":return t.values.length===1?`Невалиден вход: очакван ${$(t.values[0])}`:`Невалидна опция: очаквано едно от ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Твърде голямо: очаква се ${t.origin??"стойност"} да съдържа ${r}${t.maximum.toString()} ${a.unit??"елемента"}`:`Твърде голямо: очаква се ${t.origin??"стойност"} да бъде ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Твърде малко: очаква се ${t.origin} да съдържа ${r}${t.minimum.toString()} ${a.unit}`:`Твърде малко: очаква се ${t.origin} да бъде ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;if(r.format==="starts_with")return`Невалиден низ: трябва да започва с "${r.prefix}"`;if(r.format==="ends_with")return`Невалиден низ: трябва да завършва с "${r.suffix}"`;if(r.format==="includes")return`Невалиден низ: трябва да включва "${r.includes}"`;if(r.format==="regex")return`Невалиден низ: трябва да съвпада с ${r.pattern}`;let a="Невалиден";return r.format==="emoji"&&(a="Невалидно"),r.format==="datetime"&&(a="Невалидно"),r.format==="date"&&(a="Невалидна"),r.format==="time"&&(a="Невалидно"),r.format==="duration"&&(a="Невалидна"),`${a} ${i[r.format]??t.format}`}case"not_multiple_of":return`Невалидно число: трябва да бъде кратно на ${t.divisor}`;case"unrecognized_keys":return`Неразпознат${t.keys.length>1?"и":""} ключ${t.keys.length>1?"ове":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Невалиден ключ в ${t.origin}`;case"invalid_union":return"Невалиден вход";case"invalid_element":return`Невалидна стойност в ${t.origin}`;default:return"Невалиден вход"}}};function Vm(){return{localeError:r$()}}var i$=()=>{let e={string:{unit:"অক্ষর",verb:"থাকতে হবে"},file:{unit:"বাইট",verb:"থাকতে হবে"},array:{unit:"আইটেম",verb:"থাকতে হবে"},set:{unit:"আইটেম",verb:"থাকতে হবে"},map:{unit:"এন্ট্রি",verb:"থাকতে হবে"}};function n(t){return e[t]??null}let i={regex:"ইনপুট",email:"ইমেইল ঠিকানা",url:"URL",emoji:"ইমোজি",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO তারিখ ও সময়",date:"ISO তারিখ",time:"ISO সময়",duration:"ISO সময়কাল",ipv4:"IPv4 ঠিকানা",ipv6:"IPv6 ঠিকানা",mac:"MAC ঠিকানা",cidrv4:"IPv4 রেঞ্জ",cidrv6:"IPv6 রেঞ্জ",base64:"base64-এনকোডেড স্ট্রিং",base64url:"base64url-এনকোডেড স্ট্রিং",json_string:"JSON স্ট্রিং",e164:"E.164 নম্বর",credit_card:"ক্রেডিট কার্ড নম্বর",currency_code:"মুদ্রা কোড",iban:"IBAN",jwt:"JWT",template_literal:"ইনপুট"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`অবৈধ ইনপুট: প্রত্যাশিত ${r}, প্রাপ্ত ${s}`}case"invalid_value":return t.values.length===1?`অবৈধ ইনপুট: প্রত্যাশিত ${$(t.values[0])}`:`অবৈধ অপশন: ${v(t.values," | ")} এর মধ্যে একটি প্রত্যাশিত`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`অনেক বড়: ${t.origin??"মান"} ${r}${t.maximum.toString()} ${a.unit??"এলিমেন্ট"} হতে হবে`:`অনেক বড়: ${t.origin??"মান"} ${r}${t.maximum.toString()} হতে হবে`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`অনেক ছোট: ${t.origin} ${r}${t.minimum.toString()} ${a.unit} হতে হবে`:`অনেক ছোট: ${t.origin} ${r}${t.minimum.toString()} হতে হবে`}case"invalid_format":{let r=t;return r.format==="starts_with"?`অবৈধ স্ট্রিং: "${r.prefix}" দিয়ে শুরু হতে হবে`:r.format==="ends_with"?`অবৈধ স্ট্রিং: "${r.suffix}" দিয়ে শেষ হতে হবে`:r.format==="includes"?`অবৈধ স্ট্রিং: "${r.includes}" অন্তর্ভুক্ত থাকতে হবে`:r.format==="regex"?`অবৈধ স্ট্রিং: ${r.pattern} প্যাটার্ন মিলতে হবে`:`অবৈধ ${i[r.format]??t.format}`}case"not_multiple_of":return`অবৈধ নম্বর: ${t.divisor} এর গুণিতক হতে হবে`;case"unrecognized_keys":return`অচেনা কী${t.keys.length>1?"গুলো":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} এ অবৈধ কী`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`অবৈধ ডিসক্রিমিনেটর মান। প্রত্যাশিত ${t.options.map(a=>`'${a}'`).join(" | ")}`:"অবৈধ ইনপুট";case"invalid_element":return`${t.origin} এ অবৈধ মান`;default:return"অবৈধ ইনপুট"}}};function Gm(){return{localeError:i$()}}var o$=()=>{let e={string:{unit:"caràcters",verb:"contenir"},file:{unit:"bytes",verb:"contenir"},array:{unit:"elements",verb:"contenir"},set:{unit:"elements",verb:"contenir"},map:{unit:"elements",verb:"contenir"}};function n(t){return e[t]??null}let i={regex:"entrada",email:"adreça electrònica",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data i hora ISO",date:"data ISO",time:"hora ISO",duration:"durada ISO",ipv4:"adreça IPv4",ipv6:"adreça IPv6",mac:"adreça MAC",cidrv4:"rang IPv4",cidrv6:"rang IPv6",base64:"cadena codificada en base64",base64url:"cadena codificada en base64url",json_string:"cadena JSON",e164:"número E.164",credit_card:"número de targeta de crèdit",currency_code:"codi de moneda",iban:"IBAN",jwt:"JWT",template_literal:"entrada"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Tipus invàlid: s'esperava instanceof ${t.expected}, s'ha rebut ${s}`:`Tipus invàlid: s'esperava ${r}, s'ha rebut ${s}`}case"invalid_value":return t.values.length===1?`Valor invàlid: s'esperava ${$(t.values[0])}`:`Opció invàlida: s'esperava una de ${v(t.values," o ")}`;case"too_big":{let r=t.inclusive?"com a màxim":"menys de",a=n(t.origin);return a?`Massa gran: s'esperava que ${t.origin??"el valor"} contingués ${r} ${t.maximum.toString()} ${a.unit??"elements"}`:`Massa gran: s'esperava que ${t.origin??"el valor"} fos ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?"com a mínim":"més de",a=n(t.origin);return a?`Massa petit: s'esperava que ${t.origin} contingués ${r} ${t.minimum.toString()} ${a.unit}`:`Massa petit: s'esperava que ${t.origin} fos ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Format invàlid: ha de començar amb "${r.prefix}"`:r.format==="ends_with"?`Format invàlid: ha d'acabar amb "${r.suffix}"`:r.format==="includes"?`Format invàlid: ha d'incloure "${r.includes}"`:r.format==="regex"?`Format invàlid: ha de coincidir amb el patró ${r.pattern}`:`Format invàlid per a ${i[r.format]??t.format}`}case"not_multiple_of":return`Número invàlid: ha de ser múltiple de ${t.divisor}`;case"unrecognized_keys":return`Clau${t.keys.length>1?"s":""} no reconeguda${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Clau invàlida a ${t.origin}`;case"invalid_union":return"Entrada invàlida";case"invalid_element":return`Element invàlid a ${t.origin}`;default:return"Entrada invàlida"}}};function Bm(){return{localeError:o$()}}var a$=()=>{let e={string:{unit:"پیت",verb:"بێت"},file:{unit:"بایت",verb:"بێت"},array:{unit:"دانە",verb:"بێت"},set:{unit:"دانە",verb:"بێت"},map:{unit:"دانە",verb:"بێت"}};function n(t){return e[t]??null}let i={regex:"regex",email:"ئیمەیڵ",url:"بەستەر (URL)",emoji:"ئیمۆجی",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ڕێکەوت و کات",date:"ڕێکەوت",time:"کات",duration:"ماوە",ipv4:"ناونیشانی IPv4",ipv6:"ناونیشانی IPv6",mac:"ناونیشانی MAC",cidrv4:"مەودای IPv4",cidrv6:"مەودای IPv6",base64:"دەقی base64",base64url:"دەقی base64url",json_string:"دەقی JSON",e164:"ژمارەی E.164",credit_card:"ژمارەی کارتی کرێدیت",currency_code:"کۆدی دراو",iban:"IBAN",jwt:"JWT",template_literal:"تێکردە"},o={nan:"NaN",string:"نووسین",number:"ژمارە",boolean:"boolean",array:"array",object:"object",date:"ڕێکەوت",integer:"ژمارە",float:"ژمارە",null:"null",undefined:"undefined",function:"function",symbol:"symbol",unknown:"unknown",promise:"promise",void:"void",never:"never",map:"map",set:"set"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a,u=["ا","و","ۆ","وو","ە","ی","ێ"].some(d=>s.endsWith(d))?"یە":"ە",c=/^[a-zA-Z]+$/.test(s);return a==="null"||a==="undefined"?"داواکراوە":`چاوەڕوانکراوە ${r} بێت، بەڵام ${s}${c?"":u}`}case"invalid_value":return t.values.length===1?`بەهاکە نادرووستە: چاوەڕوانکراوە ${$(t.values[0])} بێت`:`هەڵبژاردەی نادروست: چاوەڕوانکراوە یەکێک بێت لە ${v(t.values,"|")}`;case"too_big":{let r=n(t.origin);return r?`پێویستە بە لایەنی زۆرەوە ${t.maximum.toString()} ${r.unit} ${r.verb}`:`پێویستە بە لایەنی زۆرەوە ${t.maximum.toString()} بێت`}case"too_small":{let r=n(t.origin);return r?`پێویستە بە لایەنی کەمەوە ${t.minimum.toString()} ${r.unit} ${r.verb}`:`پێویستە بە لایەنی کەمەوە ${t.minimum.toString()} بێت`}case"invalid_format":{let r=t;return r.format==="starts_with"?`دەقی نادروست: پێویستە دەستپێبکات بە "${r.prefix}"`:r.format==="ends_with"?`دەقی نادروست: پێویستە کۆتاییبێت بە "${r.suffix}"`:r.format==="includes"?`دەقی نادروست: پێویستە "${r.includes}" لەخۆبگرێت`:r.format==="regex"?`دەقی نادروست: پێویستە لەگەڵ پاتێرنی ${r.pattern} بگونجێت`:`بەهای ${i[r.format]??t.format} نادروستە`}case"not_multiple_of":return`ژمارەی نادروست: دەبێت چەند هێندە بێت بۆ ${t.divisor}`;case"unrecognized_keys":return`کلیلی نەناسراو: ${v(t.keys,", ")}`;case"invalid_key":return`کلیلی نادروست لە ${t.origin}`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`بەهای نەناسراو هەیە. بەهای چاوەڕوانکراو: ${t.options.map(a=>`'${a}'`).join(" | ")}`:"یەکگرتنی نادروست";case"invalid_element":return`${t.origin} بەهاکە نادروستە`;default:return"تێکردەی نادروست"}}};function Hm(){return{localeError:a$()}}var s$=()=>{let e={string:{unit:"znaků",verb:"mít"},file:{unit:"bajtů",verb:"mít"},array:{unit:"prvků",verb:"mít"},set:{unit:"prvků",verb:"mít"},map:{unit:"prvků",verb:"mít"}};function n(t){return e[t]??null}let i={regex:"regulární výraz",email:"e-mailová adresa",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"datum a čas ve formátu ISO",date:"datum ve formátu ISO",time:"čas ve formátu ISO",duration:"doba trvání ISO",ipv4:"IPv4 adresa",ipv6:"IPv6 adresa",mac:"MAC adresa",cidrv4:"rozsah IPv4",cidrv6:"rozsah IPv6",base64:"řetězec zakódovaný ve formátu base64",base64url:"řetězec zakódovaný ve formátu base64url",json_string:"řetězec ve formátu JSON",e164:"číslo E.164",credit_card:"číslo kreditní karty",currency_code:"kód měny",iban:"IBAN",jwt:"JWT",template_literal:"vstup"},o={nan:"NaN",number:"číslo",string:"řetězec",function:"funkce",array:"pole"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neplatný vstup: očekáváno instanceof ${t.expected}, obdrženo ${s}`:`Neplatný vstup: očekáváno ${r}, obdrženo ${s}`}case"invalid_value":return t.values.length===1?`Neplatný vstup: očekáváno ${$(t.values[0])}`:`Neplatná možnost: očekávána jedna z hodnot ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Hodnota je příliš velká: ${t.origin??"hodnota"} musí mít ${r}${t.maximum.toString()} ${a.unit??"prvků"}`:`Hodnota je příliš velká: ${t.origin??"hodnota"} musí být ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Hodnota je příliš malá: ${t.origin??"hodnota"} musí mít ${r}${t.minimum.toString()} ${a.unit??"prvků"}`:`Hodnota je příliš malá: ${t.origin??"hodnota"} musí být ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neplatný řetězec: musí začínat na "${r.prefix}"`:r.format==="ends_with"?`Neplatný řetězec: musí končit na "${r.suffix}"`:r.format==="includes"?`Neplatný řetězec: musí obsahovat "${r.includes}"`:r.format==="regex"?`Neplatný řetězec: musí odpovídat vzoru ${r.pattern}`:`Neplatný formát ${i[r.format]??t.format}`}case"not_multiple_of":return`Neplatné číslo: musí být násobkem ${t.divisor}`;case"unrecognized_keys":return`Neznámé klíče: ${v(t.keys,", ")}`;case"invalid_key":return`Neplatný klíč v ${t.origin}`;case"invalid_union":return"Neplatný vstup";case"invalid_element":return`Neplatná hodnota v ${t.origin}`;default:return"Neplatný vstup"}}};function Wm(){return{localeError:s$()}}var u$=()=>{let e={string:{unit:"tegn",verb:"havde"},file:{unit:"bytes",verb:"havde"},array:{unit:"elementer",verb:"indeholdt"},set:{unit:"elementer",verb:"indeholdt"},map:{unit:"elementer",verb:"indeholdt"}};function n(t){return e[t]??null}let i={regex:"input",email:"e-mailadresse",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dato- og klokkeslæt",date:"ISO-dato",time:"ISO-klokkeslæt",duration:"ISO-varighed",ipv4:"IPv4-adresse",ipv6:"IPv6-adresse",mac:"MAC-adresse",cidrv4:"IPv4-spektrum",cidrv6:"IPv6-spektrum",base64:"base64-kodet streng",base64url:"base64url-kodet streng",json_string:"JSON-streng",e164:"E.164-nummer",credit_card:"kreditkortnummer",currency_code:"valutakode",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",string:"streng",number:"tal",boolean:"boolean",array:"liste",object:"objekt",set:"sæt",file:"fil"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ugyldigt input: forventede instanceof ${t.expected}, fik ${s}`:`Ugyldigt input: forventede ${r}, fik ${s}`}case"invalid_value":return t.values.length===1?`Ugyldig værdi: forventede ${$(t.values[0])}`:`Ugyldigt valg: forventede en af følgende ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=o[t.origin]??t.origin;return a?`For stor: forventede ${s??"value"} ${a.verb} ${r} ${t.maximum.toString()} ${a.unit??"elementer"}`:`For stor: forventede ${s??"value"} havde ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=o[t.origin]??t.origin;return a?`For lille: forventede ${s} ${a.verb} ${r} ${t.minimum.toString()} ${a.unit}`:`For lille: forventede ${s} havde ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ugyldig streng: skal starte med "${r.prefix}"`:r.format==="ends_with"?`Ugyldig streng: skal ende med "${r.suffix}"`:r.format==="includes"?`Ugyldig streng: skal indeholde "${r.includes}"`:r.format==="regex"?`Ugyldig streng: skal matche mønsteret ${r.pattern}`:`Ugyldig ${i[r.format]??t.format}`}case"not_multiple_of":return`Ugyldigt tal: skal være deleligt med ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Ukendte nøgler":"Ukendt nøgle"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ugyldig nøgle i ${t.origin}`;case"invalid_union":return"Ugyldigt input: matcher ingen af de tilladte typer";case"invalid_element":return`Ugyldig værdi i ${t.origin}`;default:return"Ugyldigt input"}}};function Km(){return{localeError:u$()}}var c$=()=>{let e={string:{unit:"Zeichen",verb:"zu haben"},file:{unit:"Bytes",verb:"zu haben"},array:{unit:"Elemente",verb:"zu haben"},set:{unit:"Elemente",verb:"zu haben"},map:{unit:"Elemente",verb:"zu haben"}};function n(t){return e[t]??null}let i={regex:"Eingabe",email:"E-Mail-Adresse",url:"URL",emoji:"Emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-Datum und -Uhrzeit",date:"ISO-Datum",time:"ISO-Uhrzeit",duration:"ISO-Dauer",ipv4:"IPv4-Adresse",ipv6:"IPv6-Adresse",mac:"MAC-Adresse",cidrv4:"IPv4-Bereich",cidrv6:"IPv6-Bereich",base64:"Base64-codierter String",base64url:"Base64-URL-codierter String",json_string:"JSON-String",e164:"E.164-Nummer",credit_card:"Kreditkartennummer",currency_code:"Währungscode",iban:"IBAN",jwt:"JWT",template_literal:"Eingabe"},o={nan:"NaN",number:"Zahl",array:"Array"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ungültige Eingabe: erwartet instanceof ${t.expected}, erhalten ${s}`:`Ungültige Eingabe: erwartet ${r}, erhalten ${s}`}case"invalid_value":return t.values.length===1?`Ungültige Eingabe: erwartet ${$(t.values[0])}`:`Ungültige Option: erwartet eine von ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Zu groß: erwartet, dass ${t.origin??"Wert"} ${r}${t.maximum.toString()} ${a.unit??"Elemente"} hat`:`Zu groß: erwartet, dass ${t.origin??"Wert"} ${r}${t.maximum.toString()} ist`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Zu klein: erwartet, dass ${t.origin} ${r}${t.minimum.toString()} ${a.unit} hat`:`Zu klein: erwartet, dass ${t.origin} ${r}${t.minimum.toString()} ist`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ungültiger String: muss mit "${r.prefix}" beginnen`:r.format==="ends_with"?`Ungültiger String: muss mit "${r.suffix}" enden`:r.format==="includes"?`Ungültiger String: muss "${r.includes}" enthalten`:r.format==="regex"?`Ungültiger String: muss dem Muster ${r.pattern} entsprechen`:`Ungültig: ${i[r.format]??t.format}`}case"not_multiple_of":return`Ungültige Zahl: muss ein Vielfaches von ${t.divisor} sein`;case"unrecognized_keys":return`${t.keys.length>1?"Unbekannte Schlüssel":"Unbekannter Schlüssel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ungültiger Schlüssel in ${t.origin}`;case"invalid_union":return"Ungültige Eingabe";case"invalid_element":return`Ungültiger Wert in ${t.origin}`;default:return"Ungültige Eingabe"}}};function Jm(){return{localeError:c$()}}var l$=()=>{let e={string:{unit:"χαρακτήρες",verb:"να έχει"},file:{unit:"bytes",verb:"να έχει"},array:{unit:"στοιχεία",verb:"να έχει"},set:{unit:"στοιχεία",verb:"να έχει"},map:{unit:"καταχωρήσεις",verb:"να έχει"}};function n(t){return e[t]??null}let i={regex:"είσοδος",email:"διεύθυνση email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO ημερομηνία και ώρα",date:"ISO ημερομηνία",time:"ISO ώρα",duration:"ISO διάρκεια",ipv4:"διεύθυνση IPv4",ipv6:"διεύθυνση IPv6",mac:"διεύθυνση MAC",cidrv4:"εύρος IPv4",cidrv6:"εύρος IPv6",base64:"συμβολοσειρά κωδικοποιημένη σε base64",base64url:"συμβολοσειρά κωδικοποιημένη σε base64url",json_string:"συμβολοσειρά JSON",e164:"αριθμός E.164",credit_card:"αριθμός πιστωτικής κάρτας",currency_code:"κωδικός νομίσματος",iban:"IBAN",jwt:"JWT",template_literal:"είσοδος"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return typeof t.expected=="string"&&/^[A-Z]/.test(t.expected)?`Μη έγκυρη είσοδος: αναμενόταν instanceof ${t.expected}, λήφθηκε ${s}`:`Μη έγκυρη είσοδος: αναμενόταν ${r}, λήφθηκε ${s}`}case"invalid_value":return t.values.length===1?`Μη έγκυρη είσοδος: αναμενόταν ${$(t.values[0])}`:`Μη έγκυρη επιλογή: αναμενόταν ένα από ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Πολύ μεγάλο: αναμενόταν ${t.origin??"τιμή"} να έχει ${r}${t.maximum.toString()} ${a.unit??"στοιχεία"}`:`Πολύ μεγάλο: αναμενόταν ${t.origin??"τιμή"} να είναι ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Πολύ μικρό: αναμενόταν ${t.origin} να έχει ${r}${t.minimum.toString()} ${a.unit}`:`Πολύ μικρό: αναμενόταν ${t.origin} να είναι ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Μη έγκυρη συμβολοσειρά: πρέπει να ξεκινά με "${r.prefix}"`:r.format==="ends_with"?`Μη έγκυρη συμβολοσειρά: πρέπει να τελειώνει με "${r.suffix}"`:r.format==="includes"?`Μη έγκυρη συμβολοσειρά: πρέπει να περιέχει "${r.includes}"`:r.format==="regex"?`Μη έγκυρη συμβολοσειρά: πρέπει να ταιριάζει με το μοτίβο ${r.pattern}`:`Μη έγκυρο: ${i[r.format]??t.format}`}case"not_multiple_of":return`Μη έγκυρος αριθμός: πρέπει να είναι πολλαπλάσιο του ${t.divisor}`;case"unrecognized_keys":return`Άγνωστ${t.keys.length>1?"α":"ο"} κλειδ${t.keys.length>1?"ιά":"ί"}: ${v(t.keys,", ")}`;case"invalid_key":return`Μη έγκυρο κλειδί στο ${t.origin}`;case"invalid_union":return"Μη έγκυρη είσοδος";case"invalid_element":return`Μη έγκυρη τιμή στο ${t.origin}`;default:return"Μη έγκυρη είσοδος"}}};function Xm(){return{localeError:l$()}}var d$=()=>{let e={string:{unit:"characters",verb:"to have"},file:{unit:"bytes",verb:"to have"},array:{unit:"items",verb:"to have"},set:{unit:"items",verb:"to have"},map:{unit:"entries",verb:"to have"}};function n(r){return e[r]??null}let i={regex:"input",email:"email address",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datetime",date:"ISO date",time:"ISO time",duration:"ISO duration",ipv4:"IPv4 address",ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded string",base64url:"base64url-encoded string",json_string:"JSON string",e164:"E.164 number",currency_code:"currency code",credit_card:"credit card number",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN"};function t(r,a){return r==="number"&&typeof a=="number"&&!Number.isFinite(a)?String(a):o[r]??r}return r=>{switch(r.code){case"invalid_type":{let a=t(r.expected),s=k(r.input),u=t(s,r.input);return`Invalid input: expected ${a}, received ${u}`}case"invalid_value":return r.values.length===1?`Invalid input: expected ${$(r.values[0])}`:`Invalid option: expected one of ${v(r.values,"|")}`;case"too_big":{let a=r.exact?"exactly ":r.inclusive?"<=":"<",s=n(r.origin);return s?`Too big: expected ${r.origin??"value"} to have ${a}${r.maximum.toString()} ${s.unit??"elements"}`:`Too big: expected ${r.origin??"value"} to be ${a}${r.maximum.toString()}`}case"too_small":{let a=r.exact?"exactly ":r.inclusive?">=":">",s=n(r.origin);return s?`Too small: expected ${r.origin} to have ${a}${r.minimum.toString()} ${s.unit}`:`Too small: expected ${r.origin} to be ${a}${r.minimum.toString()}`}case"invalid_format":{let a=r;return a.format==="starts_with"?`Invalid string: must start with "${a.prefix}"`:a.format==="ends_with"?`Invalid string: must end with "${a.suffix}"`:a.format==="includes"?`Invalid string: must include "${a.includes}"`:a.format==="regex"?`Invalid string: must match pattern ${a.pattern}`:`Invalid ${i[a.format]??r.format}`}case"not_multiple_of":return`Invalid number: must be a multiple of ${r.divisor}`;case"unrecognized_keys":return`Unrecognized key${r.keys.length>1?"s":""}: ${v(r.keys,", ")}`;case"invalid_key":return`Invalid key in ${r.origin}`;case"invalid_union":return r.options&&Array.isArray(r.options)&&r.options.length>0?`Invalid discriminator value. Expected ${r.options.map(s=>`'${s}'`).join(" | ")}`:r.inclusive===!1?"Invalid input: more than one option matched":"Invalid input";case"invalid_element":return`Invalid value in ${r.origin}`;default:return"Invalid input"}}};function Ji(){return{localeError:d$()}}var p$=()=>{let e={string:{unit:"karaktrojn",verb:"havi"},file:{unit:"bajtojn",verb:"havi"},array:{unit:"elementojn",verb:"havi"},set:{unit:"elementojn",verb:"havi"},map:{unit:"elementojn",verb:"havi"}};function n(t){return e[t]??null}let i={regex:"enigo",email:"retadreso",url:"URL",emoji:"emoĝio",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-datotempo",date:"ISO-dato",time:"ISO-tempo",duration:"ISO-daŭro",ipv4:"IPv4-adreso",ipv6:"IPv6-adreso",mac:"MAC-adreso",cidrv4:"IPv4-rango",cidrv6:"IPv6-rango",base64:"64-ume kodita karaktraro",base64url:"URL-64-ume kodita karaktraro",json_string:"JSON-karaktraro",e164:"E.164-nombro",credit_card:"kreditkarta numero",currency_code:"valuta kodo",iban:"IBAN",jwt:"JWT",template_literal:"enigo"},o={nan:"NaN",number:"nombro",array:"tabelo",null:"senvalora"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Nevalida enigo: atendiĝis instanceof ${t.expected}, riceviĝis ${s}`:`Nevalida enigo: atendiĝis ${r}, riceviĝis ${s}`}case"invalid_value":return t.values.length===1?`Nevalida enigo: atendiĝis ${$(t.values[0])}`:`Nevalida opcio: atendiĝis unu el ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Tro granda: atendiĝis ke ${t.origin??"valoro"} havu ${r}${t.maximum.toString()} ${a.unit??"elementojn"}`:`Tro granda: atendiĝis ke ${t.origin??"valoro"} havu ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Tro malgranda: atendiĝis ke ${t.origin} havu ${r}${t.minimum.toString()} ${a.unit}`:`Tro malgranda: atendiĝis ke ${t.origin} estu ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Nevalida karaktraro: devas komenciĝi per "${r.prefix}"`:r.format==="ends_with"?`Nevalida karaktraro: devas finiĝi per "${r.suffix}"`:r.format==="includes"?`Nevalida karaktraro: devas inkluzivi "${r.includes}"`:r.format==="regex"?`Nevalida karaktraro: devas kongrui kun la modelo ${r.pattern}`:`Nevalida ${i[r.format]??t.format}`}case"not_multiple_of":return`Nevalida nombro: devas esti oblo de ${t.divisor}`;case"unrecognized_keys":return`Nekonata${t.keys.length>1?"j":""} ŝlosilo${t.keys.length>1?"j":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Nevalida ŝlosilo en ${t.origin}`;case"invalid_union":return"Nevalida enigo";case"invalid_element":return`Nevalida valoro en ${t.origin}`;default:return"Nevalida enigo"}}};function qm(){return{localeError:p$()}}var m$=()=>{let e={string:{unit:"caracteres",verb:"tener"},file:{unit:"bytes",verb:"tener"},array:{unit:"elementos",verb:"tener"},set:{unit:"elementos",verb:"tener"}};function n(t){return e[t]??null}let i={regex:"entrada",email:"dirección de correo electrónico",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"fecha y hora ISO",date:"fecha ISO",time:"hora ISO",duration:"duración ISO",ipv4:"dirección IPv4",ipv6:"dirección IPv6",mac:"dirección MAC",cidrv4:"rango IPv4",cidrv6:"rango IPv6",base64:"cadena codificada en base64",base64url:"URL codificada en base64",json_string:"cadena JSON",e164:"número E.164",credit_card:"número de tarjeta de crédito",currency_code:"código de moneda",iban:"IBAN",jwt:"JWT",template_literal:"entrada"},o={nan:"NaN",string:"texto",number:"número",boolean:"booleano",array:"arreglo",object:"objeto",set:"conjunto",file:"archivo",date:"fecha",bigint:"número grande",symbol:"símbolo",undefined:"indefinido",null:"nulo",function:"función",map:"mapa",record:"registro",tuple:"tupla",enum:"enumeración",union:"unión",literal:"literal",promise:"promesa",void:"vacío",never:"nunca",unknown:"desconocido",any:"cualquiera"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Entrada inválida: se esperaba instanceof ${t.expected}, recibido ${s}`:`Entrada inválida: se esperaba ${r}, recibido ${s}`}case"invalid_value":return t.values.length===1?`Entrada inválida: se esperaba ${$(t.values[0])}`:`Opción inválida: se esperaba una de ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Demasiado grande: se esperaba que ${s??"valor"} tuviera ${r}${t.maximum.toString()} ${a.unit??"elementos"}`:`Demasiado grande: se esperaba que ${s??"valor"} fuera ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Demasiado pequeño: se esperaba que ${s} tuviera ${r}${t.minimum.toString()} ${a.unit}`:`Demasiado pequeño: se esperaba que ${s} fuera ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Cadena inválida: debe comenzar con "${r.prefix}"`:r.format==="ends_with"?`Cadena inválida: debe terminar en "${r.suffix}"`:r.format==="includes"?`Cadena inválida: debe incluir "${r.includes}"`:r.format==="regex"?`Cadena inválida: debe coincidir con el patrón ${r.pattern}`:`Inválido ${i[r.format]??t.format}`}case"not_multiple_of":return`Número inválido: debe ser múltiplo de ${t.divisor}`;case"unrecognized_keys":return`Llave${t.keys.length>1?"s":""} desconocida${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Llave inválida en ${o[t.origin]??t.origin}`;case"invalid_union":return"Entrada inválida";case"invalid_element":return`Valor inválido en ${o[t.origin]??t.origin}`;default:return"Entrada inválida"}}};function Ym(){return{localeError:m$()}}var f$=()=>{let e={string:{unit:"کاراکتر",verb:"داشته باشد"},file:{unit:"بایت",verb:"داشته باشد"},array:{unit:"آیتم",verb:"داشته باشد"},set:{unit:"آیتم",verb:"داشته باشد"},map:{unit:"آیتم",verb:"داشته باشد"}};function n(t){return e[t]??null}let i={regex:"ورودی",email:"آدرس ایمیل",url:"URL",emoji:"ایموجی",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"تاریخ و زمان ایزو",date:"تاریخ ایزو",time:"زمان ایزو",duration:"مدت زمان ایزو",ipv4:"IPv4 آدرس",ipv6:"IPv6 آدرس",mac:"MAC آدرس",cidrv4:"IPv4 دامنه",cidrv6:"IPv6 دامنه",base64:"base64-encoded رشته",base64url:"base64url-encoded رشته",json_string:"JSON رشته",e164:"E.164 عدد",credit_card:"شماره کارت اعتباری",currency_code:"کد ارز",iban:"IBAN",jwt:"JWT",template_literal:"ورودی"},o={nan:"NaN",number:"عدد",array:"آرایه"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ورودی نامعتبر: می‌بایست instanceof ${t.expected} می‌بود، ${s} دریافت شد`:`ورودی نامعتبر: می‌بایست ${r} می‌بود، ${s} دریافت شد`}case"invalid_value":return t.values.length===1?`ورودی نامعتبر: می‌بایست ${$(t.values[0])} می‌بود`:`گزینه نامعتبر: می‌بایست یکی از ${v(t.values,"|")} می‌بود`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`خیلی بزرگ: ${t.origin??"مقدار"} باید ${r}${t.maximum.toString()} ${a.unit??"عنصر"} باشد`:`خیلی بزرگ: ${t.origin??"مقدار"} باید ${r}${t.maximum.toString()} باشد`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`خیلی کوچک: ${t.origin} باید ${r}${t.minimum.toString()} ${a.unit} باشد`:`خیلی کوچک: ${t.origin} باید ${r}${t.minimum.toString()} باشد`}case"invalid_format":{let r=t;return r.format==="starts_with"?`رشته نامعتبر: باید با "${r.prefix}" شروع شود`:r.format==="ends_with"?`رشته نامعتبر: باید با "${r.suffix}" تمام شود`:r.format==="includes"?`رشته نامعتبر: باید شامل "${r.includes}" باشد`:r.format==="regex"?`رشته نامعتبر: باید با الگوی ${r.pattern} مطابقت داشته باشد`:`${i[r.format]??t.format} نامعتبر`}case"not_multiple_of":return`عدد نامعتبر: باید مضرب ${t.divisor} باشد`;case"unrecognized_keys":return`کلید${t.keys.length>1?"های":""} ناشناس: ${v(t.keys,", ")}`;case"invalid_key":return`کلید ناشناس در ${t.origin}`;case"invalid_union":return"ورودی نامعتبر";case"invalid_element":return`مقدار نامعتبر در ${t.origin}`;default:return"ورودی نامعتبر"}}};function Qm(){return{localeError:f$()}}var g$=()=>{let e={string:{unit:"merkkiä",subject:"merkkijonon"},file:{unit:"tavua",subject:"tiedoston"},array:{unit:"alkiota",subject:"listan"},set:{unit:"alkiota",subject:"joukon"},map:{unit:"alkiota",subject:"kuvauksen"},number:{unit:"",subject:"luvun"},bigint:{unit:"",subject:"suuren kokonaisluvun"},int:{unit:"",subject:"kokonaisluvun"},date:{unit:"",subject:"päivämäärän"}};function n(t){return e[t]??null}let i={regex:"säännöllinen lauseke",email:"sähköpostiosoite",url:"URL-osoite",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-aikaleima",date:"ISO-päivämäärä",time:"ISO-aika",duration:"ISO-kesto",ipv4:"IPv4-osoite",ipv6:"IPv6-osoite",mac:"MAC-osoite",cidrv4:"IPv4-alue",cidrv6:"IPv6-alue",base64:"base64-koodattu merkkijono",base64url:"base64url-koodattu merkkijono",json_string:"JSON-merkkijono",e164:"E.164-luku",credit_card:"luottokortin numero",currency_code:"valuuttakoodi",iban:"IBAN",jwt:"JWT",template_literal:"templaattimerkkijono"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Virheellinen tyyppi: odotettiin instanceof ${t.expected}, oli ${s}`:`Virheellinen tyyppi: odotettiin ${r}, oli ${s}`}case"invalid_value":return t.values.length===1?`Virheellinen syöte: täytyy olla ${$(t.values[0])}`:`Virheellinen valinta: täytyy olla yksi seuraavista: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Liian suuri: ${a.subject} täytyy olla ${r}${t.maximum.toString()} ${a.unit}`.trim():`Liian suuri: arvon täytyy olla ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Liian pieni: ${a.subject} täytyy olla ${r}${t.minimum.toString()} ${a.unit}`.trim():`Liian pieni: arvon täytyy olla ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Virheellinen syöte: täytyy alkaa "${r.prefix}"`:r.format==="ends_with"?`Virheellinen syöte: täytyy loppua "${r.suffix}"`:r.format==="includes"?`Virheellinen syöte: täytyy sisältää "${r.includes}"`:r.format==="regex"?`Virheellinen syöte: täytyy vastata säännöllistä lauseketta ${r.pattern}`:`Virheellinen ${i[r.format]??t.format}`}case"not_multiple_of":return`Virheellinen luku: täytyy olla luvun ${t.divisor} monikerta`;case"unrecognized_keys":return`${t.keys.length>1?"Tuntemattomat avaimet":"Tuntematon avain"}: ${v(t.keys,", ")}`;case"invalid_key":return"Virheellinen avain tietueessa";case"invalid_union":return"Virheellinen unioni";case"invalid_element":return"Virheellinen arvo joukossa";default:return"Virheellinen syöte"}}};function ef(){return{localeError:g$()}}var h$=()=>{let e={string:{unit:"caractères",verb:"avoir"},file:{unit:"octets",verb:"avoir"},array:{unit:"éléments",verb:"avoir"},set:{unit:"éléments",verb:"avoir"}};function n(t){return e[t]??null}let i={regex:"expression régulière",email:"adresse e-mail",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"date et heure ISO",date:"date ISO",time:"heure ISO",duration:"durée ISO",ipv4:"adresse IPv4",ipv6:"adresse IPv6",mac:"adresse MAC",cidrv4:"plage IPv4",cidrv6:"plage IPv6",base64:"chaîne de caractères encodée en base64",base64url:"chaîne de caractères encodée en base64url",json_string:"chaîne de caractères JSON",e164:"numéro au format E.164",credit_card:"numéro de carte de crédit",currency_code:"code de devise",iban:"IBAN",jwt:"JWT",template_literal:"entrée"},o={string:"chaîne de caractères",number:"nombre",int:"entier",boolean:"booléen",bigint:"grand entier",symbol:"symbole",undefined:"indéfini",null:"null",never:"jamais",void:"vide",date:"date",array:"tableau",object:"objet",tuple:"tuple",record:"record",map:"map",set:"ensemble",file:"fichier",nonoptional:"non optionnel",nan:"NaN",function:"fonction"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Entrée invalide : instance de ${t.expected} attendu, ${s} reçu`:`Entrée invalide : ${r} attendu, ${s} reçu`}case"invalid_value":return t.values.length===1?`Entrée invalide : ${$(t.values[0])} attendu`:`Option invalide : une valeur parmi ${v(t.values,"|")} attendue`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Trop grand : ${o[t.origin]??"valeur"} doit ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"élément(s)"}`:`Trop grand : ${o[t.origin]??"valeur"} doit être ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Trop petit : ${o[t.origin]??"valeur"} doit ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Trop petit : ${o[t.origin]??"valeur"} doit être ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Chaîne de caractères invalide : doit commencer par "${r.prefix}"`:r.format==="ends_with"?`Chaîne de caractères invalide : doit se terminer par "${r.suffix}"`:r.format==="includes"?`Chaîne de caractères invalide : doit inclure "${r.includes}"`:r.format==="regex"?`Chaîne de caractères invalide : doit correspondre au motif ${r.pattern}`:`${i[r.format]??t.format} invalide`}case"not_multiple_of":return`Nombre invalide : doit être un multiple de ${t.divisor}`;case"unrecognized_keys":return`Clé${t.keys.length>1?"s":""} non reconnue${t.keys.length>1?"s":""} : ${v(t.keys,", ")}`;case"invalid_key":return`Clé invalide dans ${t.origin}`;case"invalid_union":return"Entrée invalide";case"invalid_element":return`Valeur invalide dans ${t.origin}`;default:return"Entrée invalide"}}};function tf(){return{localeError:h$()}}var v$=()=>{let e={string:{unit:"caractères",verb:"avoir"},file:{unit:"octets",verb:"avoir"},array:{unit:"éléments",verb:"avoir"},set:{unit:"éléments",verb:"avoir"},map:{unit:"éléments",verb:"avoir"}};function n(t){return e[t]??null}let i={regex:"entrée",email:"adresse courriel",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"date-heure ISO",date:"date ISO",time:"heure ISO",duration:"durée ISO",ipv4:"adresse IPv4",ipv6:"adresse IPv6",mac:"adresse MAC",cidrv4:"plage IPv4",cidrv6:"plage IPv6",base64:"chaîne encodée en base64",base64url:"chaîne encodée en base64url",json_string:"chaîne JSON",e164:"numéro E.164",credit_card:"numéro de carte de crédit",currency_code:"code de devise",iban:"IBAN",jwt:"JWT",template_literal:"entrée"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Entrée invalide : attendu instanceof ${t.expected}, reçu ${s}`:`Entrée invalide : attendu ${r}, reçu ${s}`}case"invalid_value":return t.values.length===1?`Entrée invalide : attendu ${$(t.values[0])}`:`Option invalide : attendu l'une des valeurs suivantes ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"≤":"<",a=n(t.origin);return a?`Trop grand : attendu que ${t.origin??"la valeur"} ait ${r}${t.maximum.toString()} ${a.unit}`:`Trop grand : attendu que ${t.origin??"la valeur"} soit ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?"≥":">",a=n(t.origin);return a?`Trop petit : attendu que ${t.origin} ait ${r}${t.minimum.toString()} ${a.unit}`:`Trop petit : attendu que ${t.origin} soit ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Chaîne invalide : doit commencer par "${r.prefix}"`:r.format==="ends_with"?`Chaîne invalide : doit se terminer par "${r.suffix}"`:r.format==="includes"?`Chaîne invalide : doit inclure "${r.includes}"`:r.format==="regex"?`Chaîne invalide : doit correspondre au motif ${r.pattern}`:`${i[r.format]??t.format} invalide`}case"not_multiple_of":return`Nombre invalide : doit être un multiple de ${t.divisor}`;case"unrecognized_keys":return`Clé${t.keys.length>1?"s":""} non reconnue${t.keys.length>1?"s":""} : ${v(t.keys,", ")}`;case"invalid_key":return`Clé invalide dans ${t.origin}`;case"invalid_union":return"Entrée invalide";case"invalid_element":return`Valeur invalide dans ${t.origin}`;default:return"Entrée invalide"}}};function nf(){return{localeError:v$()}}var y$=()=>{let e={string:{unit:"અક્ષર",verb:"હોવા જોઈએ"},file:{unit:"બાયટ",verb:"હોવા જોઈએ"},array:{unit:"આઇટમ",verb:"હોવા જોઈએ"},set:{unit:"આઇટમ",verb:"હોવા જોઈએ"},map:{unit:"એન્ટ્રી",verb:"હોવા જોઈએ"}};function n(t){return e[t]??null}let i={regex:"ઇનપુટ",email:"ઈમેઇલ એડ્રેસ",url:"URL",emoji:"ઇમોજી",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO તારીખ અને સમય",date:"ISO તારીખ",time:"ISO સમય",duration:"ISO અવધિ",ipv4:"IPv4 એડ્રેસ",ipv6:"IPv6 એડ્રેસ",mac:"MAC એડ્રેસ",cidrv4:"IPv4 શ્રેણી",cidrv6:"IPv6 શ્રેણી",base64:"base64-એન્કોડેડ સ્ટ્રિંગ",base64url:"base64url-એન્કોડેડ સ્ટ્રિંગ",json_string:"JSON સ્ટ્રિંગ",e164:"E.164 નંબર",credit_card:"ક્રેડિટ કાર્ડ નંબર",currency_code:"ચલણ કોડ",iban:"IBAN",jwt:"JWT",template_literal:"ઇનપુટ"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`અમાન્ય ઇનપુટ: અપેક્ષિત ${r}, પ્રાપ્ત ${s}`}case"invalid_value":return t.values.length===1?`અમાન્ય ઇનપુટ: અપેક્ષિત ${$(t.values[0])}`:`અમાન્ય વિકલ્પ: ${v(t.values," | ")} માધ્યમથી એક અપેક્ષિત`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ખૂબ મોટું: ${t.origin??"મૂલ્ય"} ${r}${t.maximum.toString()} ${a.unit??"એલિમેન્ટ"} હોવા જોઈએ`:`ખૂબ મોટું: ${t.origin??"મૂલ્ય"} ${r}${t.maximum.toString()} હોવું જોઈએ`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ખૂબ નાનું: ${t.origin} ${r}${t.minimum.toString()} ${a.unit} હોવા જોઈએ`:`ખૂબ નાનું: ${t.origin} ${r}${t.minimum.toString()} હોવું જોઈએ`}case"invalid_format":{let r=t;return r.format==="starts_with"?`અમાન્ય સ્ટ્રિંગ: "${r.prefix}" થી શરૂ થવું જોઈએ`:r.format==="ends_with"?`અમાન્ય સ્ટ્રિંગ: "${r.suffix}" પર સમાપ્ત થવું જોઈએ`:r.format==="includes"?`અમાન્ય સ્ટ્રિંગ: "${r.includes}" શામેલ હોવું જોઈએ`:r.format==="regex"?`અમાન્ય સ્ટ્રિંગ: પેટર્ન ${r.pattern} સાથે મેળ ખાવું જોઈએ`:`અમાન્ય ${i[r.format]??t.format}`}case"not_multiple_of":return`અમાન્ય નંબર: ${t.divisor} નો ગુણાંક હોવો જોઈએ`;case"unrecognized_keys":return`ઓળખી શકાતા નહીં તે કી${t.keys.length>1?"ઓ":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} માં અમાન્ય કી`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`અમાન્ય ડિસ્ક્રિમિનેટર મૂલ્ય. અપેક્ષિત ${t.options.map(a=>`'${a}'`).join(" | ")}`:"અમાન્ય ઇનપુટ";case"invalid_element":return`${t.origin} માં અમાન્ય મૂલ્ય`;default:return"અમાન્ય ઇનપુટ"}}};function rf(){return{localeError:y$()}}var b$=()=>{let e={string:{label:"מחרוזת",gender:"f"},number:{label:"מספר",gender:"m"},boolean:{label:"ערך בוליאני",gender:"m"},bigint:{label:"BigInt",gender:"m"},date:{label:"תאריך",gender:"m"},array:{label:"מערך",gender:"m"},object:{label:"אובייקט",gender:"m"},null:{label:"ערך ריק (null)",gender:"m"},undefined:{label:"ערך לא מוגדר (undefined)",gender:"m"},symbol:{label:"סימבול (Symbol)",gender:"m"},function:{label:"פונקציה",gender:"f"},map:{label:"מפה (Map)",gender:"f"},set:{label:"קבוצה (Set)",gender:"f"},file:{label:"קובץ",gender:"m"},promise:{label:"Promise",gender:"m"},NaN:{label:"NaN",gender:"m"},unknown:{label:"ערך לא ידוע",gender:"m"},value:{label:"ערך",gender:"m"}},n={string:{unit:"תווים",shortLabel:"קצר",longLabel:"ארוך"},file:{unit:"בייטים",shortLabel:"קטן",longLabel:"גדול"},array:{unit:"פריטים",shortLabel:"קטן",longLabel:"גדול"},set:{unit:"פריטים",shortLabel:"קטן",longLabel:"גדול"},number:{unit:"",shortLabel:"קטן",longLabel:"גדול"}},i=c=>c?e[c]:void 0,o=c=>{let d=i(c);return d?d.label:c??e.unknown.label},t=c=>`ה${o(c)}`,r=c=>(i(c)?.gender??"m")==="f"?"צריכה להיות":"צריך להיות",a=c=>c?n[c]??null:null,s={regex:{label:"קלט",gender:"m"},email:{label:"כתובת אימייל",gender:"f"},url:{label:"כתובת רשת",gender:"f"},emoji:{label:"אימוג'י",gender:"m"},uuid:{label:"UUID",gender:"m"},uuidv4:{label:"UUIDv4",gender:"m"},uuidv6:{label:"UUIDv6",gender:"m"},nanoid:{label:"nanoid",gender:"m"},guid:{label:"GUID",gender:"m"},cuid:{label:"cuid",gender:"m"},cuid2:{label:"cuid2",gender:"m"},ulid:{label:"ULID",gender:"m"},xid:{label:"XID",gender:"m"},ksuid:{label:"KSUID",gender:"m"},datetime:{label:"תאריך וזמן ISO",gender:"m"},date:{label:"תאריך ISO",gender:"m"},time:{label:"זמן ISO",gender:"m"},duration:{label:"משך זמן ISO",gender:"m"},ipv4:{label:"כתובת IPv4",gender:"f"},ipv6:{label:"כתובת IPv6",gender:"f"},mac:{label:"כתובת MAC",gender:"f"},cidrv4:{label:"טווח IPv4",gender:"m"},cidrv6:{label:"טווח IPv6",gender:"m"},base64:{label:"מחרוזת בבסיס 64",gender:"f"},base64url:{label:"מחרוזת בבסיס 64 לכתובות רשת",gender:"f"},json_string:{label:"מחרוזת JSON",gender:"f"},e164:{label:"מספר E.164",gender:"m"},credit_card:{label:"מספר כרטיס אשראי",gender:"m"},currency_code:{label:"קוד מטבע",gender:"m"},iban:{label:"IBAN",gender:"m"},jwt:{label:"JWT",gender:"m"},template_literal:{label:"קלט",gender:"m"},ends_with:{label:"קלט",gender:"m"},includes:{label:"קלט",gender:"m"},lowercase:{label:"קלט",gender:"m"},starts_with:{label:"קלט",gender:"m"},uppercase:{label:"קלט",gender:"m"}},u={nan:"NaN"};return c=>{switch(c.code){case"invalid_type":{let d=c.expected,p=u[d??""]??o(d),m=k(c.input),f=u[m]??e[m]?.label??m;return/^[A-Z]/.test(c.expected)?`קלט לא תקין: צריך להיות instanceof ${c.expected}, התקבל ${f}`:`קלט לא תקין: צריך להיות ${p}, התקבל ${f}`}case"invalid_value":{if(c.values.length===1)return`ערך לא תקין: הערך חייב להיות ${$(c.values[0])}`;let d=c.values.map(f=>$(f));if(c.values.length===2)return`ערך לא תקין: האפשרויות המתאימות הן ${d[0]} או ${d[1]}`;let p=d[d.length-1];return`ערך לא תקין: האפשרויות המתאימות הן ${d.slice(0,-1).join(", ")} או ${p}`}case"too_big":{let d=a(c.origin),p=t(c.origin??"value");if(c.origin==="string")return`${d?.longLabel??"ארוך"} מדי: ${p} צריכה להכיל ${c.maximum.toString()} ${d?.unit??""} ${c.inclusive?"או פחות":"לכל היותר"}`.trim();if(c.origin==="number"){let g=c.inclusive?`קטן או שווה ל-${c.maximum}`:`קטן מ-${c.maximum}`;return`גדול מדי: ${p} צריך להיות ${g}`}if(c.origin==="array"||c.origin==="set"){let g=c.origin==="set"?"צריכה":"צריך",y=c.inclusive?`${c.maximum} ${d?.unit??""} או פחות`:`פחות מ-${c.maximum} ${d?.unit??""}`;return`גדול מדי: ${p} ${g} להכיל ${y}`.trim()}let m=c.inclusive?"<=":"<",f=r(c.origin??"value");return d?.unit?`${d.longLabel} מדי: ${p} ${f} ${m}${c.maximum.toString()} ${d.unit}`:`${d?.longLabel??"גדול"} מדי: ${p} ${f} ${m}${c.maximum.toString()}`}case"too_small":{let d=a(c.origin),p=t(c.origin??"value");if(c.origin==="string")return`${d?.shortLabel??"קצר"} מדי: ${p} צריכה להכיל ${c.minimum.toString()} ${d?.unit??""} ${c.inclusive?"או יותר":"לפחות"}`.trim();if(c.origin==="number"){let g=c.inclusive?`גדול או שווה ל-${c.minimum}`:`גדול מ-${c.minimum}`;return`קטן מדי: ${p} צריך להיות ${g}`}if(c.origin==="array"||c.origin==="set"){let g=c.origin==="set"?"צריכה":"צריך";if(c.minimum===1&&c.inclusive){let x=(c.origin==="set","לפחות פריט אחד");return`קטן מדי: ${p} ${g} להכיל ${x}`}let y=c.inclusive?`${c.minimum} ${d?.unit??""} או יותר`:`יותר מ-${c.minimum} ${d?.unit??""}`;return`קטן מדי: ${p} ${g} להכיל ${y}`.trim()}let m=c.inclusive?">=":">",f=r(c.origin??"value");return d?.unit?`${d.shortLabel} מדי: ${p} ${f} ${m}${c.minimum.toString()} ${d.unit}`:`${d?.shortLabel??"קטן"} מדי: ${p} ${f} ${m}${c.minimum.toString()}`}case"invalid_format":{let d=c;if(d.format==="starts_with")return`המחרוזת חייבת להתחיל ב "${d.prefix}"`;if(d.format==="ends_with")return`המחרוזת חייבת להסתיים ב "${d.suffix}"`;if(d.format==="includes")return`המחרוזת חייבת לכלול "${d.includes}"`;if(d.format==="regex")return`המחרוזת חייבת להתאים לתבנית ${d.pattern}`;let p=s[d.format],m=p?.label??d.format,g=(p?.gender??"m")==="f"?"תקינה":"תקין";return`${m} לא ${g}`}case"not_multiple_of":return`מספר לא תקין: חייב להיות מכפלה של ${c.divisor}`;case"unrecognized_keys":return`מפתח${c.keys.length>1?"ות":""} לא מזוה${c.keys.length>1?"ים":"ה"}: ${v(c.keys,", ")}`;case"invalid_key":return"שדה לא תקין באובייקט";case"invalid_union":return"קלט לא תקין";case"invalid_element":return`ערך לא תקין ב${t(c.origin??"array")}`;default:return"קלט לא תקין"}}};function of(){return{localeError:b$()}}var _$=()=>{let e={string:{unit:"अक्षर",verb:"रखने के लिए"},file:{unit:"बाइट्स",verb:"रखने के लिए"},array:{unit:"तत्व",verb:"रखने के लिए"},set:{unit:"तत्व",verb:"रखने के लिए"},map:{unit:"प्रविष्टियाँ",verb:"रखने के लिए"}};function n(t){return e[t]??null}let i={regex:"इनपुट",email:"ईमेल पता",url:"URL",emoji:"इमोजी",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO तिथि और समय",date:"ISO तिथि",time:"ISO समय",duration:"ISO अवधि",ipv4:"IPv4 पता",ipv6:"IPv6 पता",mac:"MAC पता",cidrv4:"IPv4 श्रेणी",cidrv6:"IPv6 श्रेणी",base64:"Base64-एन्कोडेड स्ट्रिंग",base64url:"Base64URL-एन्कोडेड स्ट्रिंग",json_string:"JSON स्ट्रिंग",e164:"E.164 संख्या",credit_card:"क्रेडिट कार्ड संख्या",currency_code:"मुद्रा कोड",iban:"IBAN",jwt:"JWT",template_literal:"इनपुट"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`अमान्य इनपुट: अपेक्षित ${r}, प्राप्त ${s}`}case"invalid_value":return t.values.length===1?`अमान्य इनपुट: अपेक्षित ${$(t.values[0])}`:`अमान्य विकल्प: अपेक्षित मानों में से एक ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`बहुत बड़ा: अपेक्षित था कि ${t.origin??"मान"} में ${r}${t.maximum} ${a.unit} हों`:`बहुत बड़ा: अपेक्षित था कि ${t.origin??"मान"} ${r}${t.maximum} हो`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`बहुत छोटा: अपेक्षित था कि ${t.origin} में ${r}${t.minimum} ${a.unit} हों`:`बहुत छोटा: अपेक्षित था कि ${t.origin} ${r}${t.minimum} हो`}case"invalid_format":{let r=t;return r.format==="starts_with"?`अमान्य स्ट्रिंग: "${r.prefix}" से शुरू होना चाहिए`:r.format==="ends_with"?`अमान्य स्ट्रिंग: "${r.suffix}" पर समाप्त होना चाहिए`:r.format==="includes"?`अमान्य स्ट्रिंग: इसमें "${r.includes}" शामिल होना चाहिए`:r.format==="regex"?`अमान्य स्ट्रिंग: पैटर्न ${r.pattern} से मेल खाना चाहिए`:`अमान्य ${i[r.format]??t.format}`}case"not_multiple_of":return`अमान्य संख्या: यह ${t.divisor} का गुणज होना चाहिए`;case"unrecognized_keys":return`अपरिचित कुंजी${t.keys.length>1?"याँ":""}: ${v(t.keys,", ")}`;case"invalid_key":return`अमान्य कुंजी: ${t.origin} में`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`अमान्य डिस्क्रिमिनेटर मान: अपेक्षित ${t.options.map(a=>`'${a}'`).join(" | ")}`:"अमान्य इनपुट";case"invalid_element":return`अमान्य मान: ${t.origin} में`;default:return"अमान्य इनपुट"}}};function af(){return{localeError:_$()}}var $$=()=>{let e={string:{unit:"znakova",verb:"imati"},file:{unit:"bajtova",verb:"imati"},array:{unit:"stavki",verb:"imati"},set:{unit:"stavki",verb:"imati"}};function n(t){return e[t]??null}let i={regex:"unos",email:"email adresa",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datum i vrijeme",date:"ISO datum",time:"ISO vrijeme",duration:"ISO trajanje",ipv4:"IPv4 adresa",ipv6:"IPv6 adresa",mac:"MAC adresa",cidrv4:"IPv4 raspon",cidrv6:"IPv6 raspon",base64:"base64 kodirani tekst",base64url:"base64url kodirani tekst",json_string:"JSON tekst",e164:"E.164 broj",credit_card:"broj kreditne kartice",currency_code:"kod valute",iban:"IBAN",jwt:"JWT",template_literal:"unos"},o={nan:"NaN",string:"tekst",number:"broj",boolean:"boolean",array:"niz",object:"objekt",set:"skup",file:"datoteka",date:"datum",bigint:"bigint",symbol:"simbol",undefined:"undefined",null:"null",function:"funkcija",map:"mapa"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neispravan unos: očekuje se instanceof ${t.expected}, a primljeno je ${s}`:`Neispravan unos: očekuje se ${r}, a primljeno je ${s}`}case"invalid_value":return t.values.length===1?`Neispravna vrijednost: očekivano ${$(t.values[0])}`:`Neispravna opcija: očekivano jedno od ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Preveliko: očekivano da ${s??"vrijednost"} ima ${r}${t.maximum.toString()} ${a.unit??"elemenata"}`:`Preveliko: očekivano da ${s??"vrijednost"} bude ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=o[t.origin]??t.origin;return a?`Premalo: očekivano da ${s} ima ${r}${t.minimum.toString()} ${a.unit}`:`Premalo: očekivano da ${s} bude ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neispravan tekst: mora započinjati s "${r.prefix}"`:r.format==="ends_with"?`Neispravan tekst: mora završavati s "${r.suffix}"`:r.format==="includes"?`Neispravan tekst: mora sadržavati "${r.includes}"`:r.format==="regex"?`Neispravan tekst: mora odgovarati uzorku ${r.pattern}`:`Neispravna ${i[r.format]??t.format}`}case"not_multiple_of":return`Neispravan broj: mora biti višekratnik od ${t.divisor}`;case"unrecognized_keys":return`Neprepoznat${t.keys.length>1?"i ključevi":" ključ"}: ${v(t.keys,", ")}`;case"invalid_key":return`Neispravan ključ u ${o[t.origin]??t.origin}`;case"invalid_union":return"Neispravan unos";case"invalid_element":return`Neispravna vrijednost u ${o[t.origin]??t.origin}`;default:return"Neispravan unos"}}};function sf(){return{localeError:$$()}}var k$=()=>{let e={string:{unit:"karakter",verb:"legyen"},file:{unit:"byte",verb:"legyen"},array:{unit:"elem",verb:"legyen"},set:{unit:"elem",verb:"legyen"},map:{unit:"elem",verb:"legyen"}};function n(t){return e[t]??null}let i={regex:"bemenet",email:"email cím",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO időbélyeg",date:"ISO dátum",time:"ISO idő",duration:"ISO időintervallum",ipv4:"IPv4 cím",ipv6:"IPv6 cím",mac:"MAC cím",cidrv4:"IPv4 tartomány",cidrv6:"IPv6 tartomány",base64:"base64-kódolt string",base64url:"base64url-kódolt string",json_string:"JSON string",e164:"E.164 szám",credit_card:"hitelkártyaszám",currency_code:"pénznemkód",iban:"IBAN",jwt:"JWT",template_literal:"bemenet"},o={nan:"NaN",number:"szám",array:"tömb"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Érvénytelen bemenet: a várt érték instanceof ${t.expected}, a kapott érték ${s}`:`Érvénytelen bemenet: a várt érték ${r}, a kapott érték ${s}`}case"invalid_value":return t.values.length===1?`Érvénytelen bemenet: a várt érték ${$(t.values[0])}`:`Érvénytelen opció: valamelyik érték várt ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Túl nagy: ${t.origin??"érték"} mérete túl nagy ${r}${t.maximum.toString()} ${a.unit??"elem"}`:`Túl nagy: a bemeneti érték ${t.origin??"érték"} túl nagy: ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Túl kicsi: a bemeneti érték ${t.origin} mérete túl kicsi ${r}${t.minimum.toString()} ${a.unit}`:`Túl kicsi: a bemeneti érték ${t.origin} túl kicsi ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Érvénytelen string: "${r.prefix}" értékkel kell kezdődnie`:r.format==="ends_with"?`Érvénytelen string: "${r.suffix}" értékkel kell végződnie`:r.format==="includes"?`Érvénytelen string: "${r.includes}" értéket kell tartalmaznia`:r.format==="regex"?`Érvénytelen string: ${r.pattern} mintának kell megfelelnie`:`Érvénytelen ${i[r.format]??t.format}`}case"not_multiple_of":return`Érvénytelen szám: ${t.divisor} többszörösének kell lennie`;case"unrecognized_keys":return`Ismeretlen kulcs${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Érvénytelen kulcs ${t.origin}`;case"invalid_union":return"Érvénytelen bemenet";case"invalid_element":return`Érvénytelen érték: ${t.origin}`;default:return"Érvénytelen bemenet"}}};function uf(){return{localeError:k$()}}function cf(e,n,i){return Math.abs(e)===1?n:i}function an(e){if(!e)return"";let n=["ա","ե","ը","ի","ո","ու","օ"],i=e[e.length-1];return e+(n.includes(i)?"ն":"ը")}var x$=()=>{let e={string:{unit:{one:"նշան",many:"նշաններ"},verb:"ունենալ"},file:{unit:{one:"բայթ",many:"բայթեր"},verb:"ունենալ"},array:{unit:{one:"տարր",many:"տարրեր"},verb:"ունենալ"},set:{unit:{one:"տարր",many:"տարրեր"},verb:"ունենալ"},map:{unit:{one:"տարր",many:"տարրեր"},verb:"ունենալ"}};function n(t){return e[t]??null}let i={regex:"մուտք",email:"էլ. հասցե",url:"URL",emoji:"էմոջի",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO ամսաթիվ և ժամ",date:"ISO ամսաթիվ",time:"ISO ժամ",duration:"ISO տևողություն",ipv4:"IPv4 հասցե",ipv6:"IPv6 հասցե",mac:"MAC հասցե",cidrv4:"IPv4 միջակայք",cidrv6:"IPv6 միջակայք",base64:"base64 ձևաչափով տող",base64url:"base64url ձևաչափով տող",json_string:"JSON տող",e164:"E.164 համար",credit_card:"կրեդիտ քարտի համար",currency_code:"արժույթի կոդ",iban:"IBAN",jwt:"JWT",template_literal:"մուտք"},o={nan:"NaN",number:"թիվ",array:"զանգված"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Սխալ մուտքագրում․ սպասվում էր instanceof ${t.expected}, ստացվել է ${s}`:`Սխալ մուտքագրում․ սպասվում էր ${r}, ստացվել է ${s}`}case"invalid_value":return t.values.length===1?`Սխալ մուտքագրում․ սպասվում էր ${$(t.values[1])}`:`Սխալ տարբերակ․ սպասվում էր հետևյալներից մեկը՝ ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);if(a){let s=Number(t.maximum),u=cf(s,a.unit.one,a.unit.many);return`Չափազանց մեծ արժեք․ սպասվում է, որ ${an(t.origin??"արժեք")} կունենա ${r}${t.maximum.toString()} ${u}`}return`Չափազանց մեծ արժեք․ սպասվում է, որ ${an(t.origin??"արժեք")} լինի ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);if(a){let s=Number(t.minimum),u=cf(s,a.unit.one,a.unit.many);return`Չափազանց փոքր արժեք․ սպասվում է, որ ${an(t.origin)} կունենա ${r}${t.minimum.toString()} ${u}`}return`Չափազանց փոքր արժեք․ սպասվում է, որ ${an(t.origin)} լինի ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Սխալ տող․ պետք է սկսվի "${r.prefix}"-ով`:r.format==="ends_with"?`Սխալ տող․ պետք է ավարտվի "${r.suffix}"-ով`:r.format==="includes"?`Սխալ տող․ պետք է պարունակի "${r.includes}"`:r.format==="regex"?`Սխալ տող․ պետք է համապատասխանի ${r.pattern} ձևաչափին`:`Սխալ ${i[r.format]??t.format}`}case"not_multiple_of":return`Սխալ թիվ․ պետք է բազմապատիկ լինի ${t.divisor}-ի`;case"unrecognized_keys":return`Չճանաչված բանալի${t.keys.length>1?"ներ":""}. ${v(t.keys,", ")}`;case"invalid_key":return`Սխալ բանալի ${an(t.origin)}-ում`;case"invalid_union":return"Սխալ մուտքագրում";case"invalid_element":return`Սխալ արժեք ${an(t.origin)}-ում`;default:return"Սխալ մուտքագրում"}}};function lf(){return{localeError:x$()}}var w$=()=>{let e={string:{unit:"karakter",verb:"memiliki"},file:{unit:"byte",verb:"memiliki"},array:{unit:"item",verb:"memiliki"},set:{unit:"item",verb:"memiliki"},map:{unit:"item",verb:"memiliki"}};function n(t){return e[t]??null}let i={regex:"input",email:"alamat email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"tanggal dan waktu format ISO",date:"tanggal format ISO",time:"jam format ISO",duration:"durasi format ISO",ipv4:"alamat IPv4",ipv6:"alamat IPv6",mac:"alamat MAC",cidrv4:"rentang alamat IPv4",cidrv6:"rentang alamat IPv6",base64:"string dengan enkode base64",base64url:"string dengan enkode base64url",json_string:"string JSON",e164:"angka E.164",credit_card:"nomor kartu kredit",currency_code:"kode mata uang",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Input tidak valid: diharapkan instanceof ${t.expected}, diterima ${s}`:`Input tidak valid: diharapkan ${r}, diterima ${s}`}case"invalid_value":return t.values.length===1?`Input tidak valid: diharapkan ${$(t.values[0])}`:`Pilihan tidak valid: diharapkan salah satu dari ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Terlalu besar: diharapkan ${t.origin??"value"} memiliki ${r}${t.maximum.toString()} ${a.unit??"elemen"}`:`Terlalu besar: diharapkan ${t.origin??"value"} menjadi ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Terlalu kecil: diharapkan ${t.origin} memiliki ${r}${t.minimum.toString()} ${a.unit}`:`Terlalu kecil: diharapkan ${t.origin} menjadi ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`String tidak valid: harus dimulai dengan "${r.prefix}"`:r.format==="ends_with"?`String tidak valid: harus berakhir dengan "${r.suffix}"`:r.format==="includes"?`String tidak valid: harus menyertakan "${r.includes}"`:r.format==="regex"?`String tidak valid: harus sesuai pola ${r.pattern}`:`${i[r.format]??t.format} tidak valid`}case"not_multiple_of":return`Angka tidak valid: harus kelipatan dari ${t.divisor}`;case"unrecognized_keys":return`Kunci tidak dikenali ${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Kunci tidak valid di ${t.origin}`;case"invalid_union":return"Input tidak valid";case"invalid_element":return`Nilai tidak valid di ${t.origin}`;default:return"Input tidak valid"}}};function df(){return{localeError:w$()}}var I$=()=>{let e={string:{unit:"stafi",verb:"að hafa"},file:{unit:"bæti",verb:"að hafa"},array:{unit:"hluti",verb:"að hafa"},set:{unit:"hluti",verb:"að hafa"},map:{unit:"hluti",verb:"að hafa"}};function n(t){return e[t]??null}let i={regex:"gildi",email:"netfang",url:"vefslóð",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dagsetning og tími",date:"ISO dagsetning",time:"ISO tími",duration:"ISO tímalengd",ipv4:"IPv4 address",ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded strengur",base64url:"base64url-encoded strengur",json_string:"JSON strengur",e164:"E.164 tölugildi",credit_card:"kreditkortanúmer",currency_code:"gjaldmiðilskóði",iban:"IBAN",jwt:"JWT",template_literal:"gildi"},o={nan:"NaN",number:"númer",array:"fylki"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Rangt gildi: Þú slóst inn ${s} þar sem á að vera instanceof ${t.expected}`:`Rangt gildi: Þú slóst inn ${s} þar sem á að vera ${r}`}case"invalid_value":return t.values.length===1?`Rangt gildi: gert ráð fyrir ${$(t.values[0])}`:`Ógilt val: má vera eitt af eftirfarandi ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Of stórt: gert er ráð fyrir að ${t.origin??"gildi"} hafi ${r}${t.maximum.toString()} ${a.unit??"hluti"}`:`Of stórt: gert er ráð fyrir að ${t.origin??"gildi"} sé ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Of lítið: gert er ráð fyrir að ${t.origin} hafi ${r}${t.minimum.toString()} ${a.unit}`:`Of lítið: gert er ráð fyrir að ${t.origin} sé ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ógildur strengur: verður að byrja á "${r.prefix}"`:r.format==="ends_with"?`Ógildur strengur: verður að enda á "${r.suffix}"`:r.format==="includes"?`Ógildur strengur: verður að innihalda "${r.includes}"`:r.format==="regex"?`Ógildur strengur: verður að fylgja mynstri ${r.pattern}`:`Rangt ${i[r.format]??t.format}`}case"not_multiple_of":return`Röng tala: verður að vera margfeldi af ${t.divisor}`;case"unrecognized_keys":return`Óþekkt ${t.keys.length>1?"ir lyklar":"ur lykill"}: ${v(t.keys,", ")}`;case"invalid_key":return`Rangur lykill í ${t.origin}`;case"invalid_union":return"Rangt gildi";case"invalid_element":return`Rangt gildi í ${t.origin}`;default:return"Rangt gildi"}}};function pf(){return{localeError:I$()}}var S$=()=>{let e={string:{unit:"caratteri",verb:"avere"},file:{unit:"byte",verb:"avere"},array:{unit:"elementi",verb:"avere"},set:{unit:"elementi",verb:"avere"},map:{unit:"elementi",verb:"avere"}};function n(t){return e[t]??null}let i={regex:"input",email:"indirizzo email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data e ora ISO",date:"data ISO",time:"ora ISO",duration:"durata ISO",ipv4:"indirizzo IPv4",ipv6:"indirizzo IPv6",mac:"indirizzo MAC",cidrv4:"intervallo IPv4",cidrv6:"intervallo IPv6",base64:"stringa codificata in base64",base64url:"URL codificata in base64",json_string:"stringa JSON",e164:"numero E.164",credit_card:"numero di carta di credito",currency_code:"codice valuta",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"numero",array:"vettore"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Input non valido: atteso instanceof ${t.expected}, ricevuto ${s}`:`Input non valido: atteso ${r}, ricevuto ${s}`}case"invalid_value":return t.values.length===1?`Input non valido: atteso ${$(t.values[0])}`:`Opzione non valida: atteso uno tra ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Troppo grande: ${t.origin??"valore"} deve avere ${r}${t.maximum.toString()} ${a.unit??"elementi"}`:`Troppo grande: ${t.origin??"valore"} deve essere ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Troppo piccolo: ${t.origin} deve avere ${r}${t.minimum.toString()} ${a.unit}`:`Troppo piccolo: ${t.origin} deve essere ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Stringa non valida: deve iniziare con "${r.prefix}"`:r.format==="ends_with"?`Stringa non valida: deve terminare con "${r.suffix}"`:r.format==="includes"?`Stringa non valida: deve includere "${r.includes}"`:r.format==="regex"?`Stringa non valida: deve corrispondere al pattern ${r.pattern}`:`Input non valido: ${i[r.format]??t.format}`}case"not_multiple_of":return`Numero non valido: deve essere un multiplo di ${t.divisor}`;case"unrecognized_keys":return`Chiav${t.keys.length>1?"i":"e"} non riconosciut${t.keys.length>1?"e":"a"}: ${v(t.keys,", ")}`;case"invalid_key":return`Chiave non valida in ${t.origin}`;case"invalid_union":return"Input non valido";case"invalid_element":return`Valore non valido in ${t.origin}`;default:return"Input non valido"}}};function mf(){return{localeError:S$()}}var P$=()=>{let e={string:{unit:"文字",verb:"である"},file:{unit:"バイト",verb:"である"},array:{unit:"要素",verb:"である"},set:{unit:"要素",verb:"である"},map:{unit:"要素",verb:"である"}};function n(t){return e[t]??null}let i={regex:"入力値",email:"メールアドレス",url:"URL",emoji:"絵文字",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO日時",date:"ISO日付",time:"ISO時刻",duration:"ISO期間",ipv4:"IPv4アドレス",ipv6:"IPv6アドレス",mac:"MACアドレス",cidrv4:"IPv4範囲",cidrv6:"IPv6範囲",base64:"base64エンコード文字列",base64url:"base64urlエンコード文字列",json_string:"JSON文字列",e164:"E.164番号",credit_card:"クレジットカード番号",currency_code:"通貨コード",iban:"IBAN",jwt:"JWT",template_literal:"入力値"},o={nan:"NaN",number:"数値",array:"配列"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`無効な入力: instanceof ${t.expected}が期待されましたが、${s}が入力されました`:`無効な入力: ${r}が期待されましたが、${s}が入力されました`}case"invalid_value":return t.values.length===1?`無効な入力: ${$(t.values[0])}が期待されました`:`無効な選択: ${v(t.values,"、")}のいずれかである必要があります`;case"too_big":{let r=t.inclusive?"以下である":"より小さい",a=n(t.origin);return a?`大きすぎる値: ${t.origin??"値"}は${t.maximum.toString()}${a.unit??"要素"}${r}必要があります`:`大きすぎる値: ${t.origin??"値"}は${t.maximum.toString()}${r}必要があります`}case"too_small":{let r=t.inclusive?"以上である":"より大きい",a=n(t.origin);return a?`小さすぎる値: ${t.origin}は${t.minimum.toString()}${a.unit}${r}必要があります`:`小さすぎる値: ${t.origin}は${t.minimum.toString()}${r}必要があります`}case"invalid_format":{let r=t;return r.format==="starts_with"?`無効な文字列: "${r.prefix}"で始まる必要があります`:r.format==="ends_with"?`無効な文字列: "${r.suffix}"で終わる必要があります`:r.format==="includes"?`無効な文字列: "${r.includes}"を含む必要があります`:r.format==="regex"?`無効な文字列: パターン${r.pattern}に一致する必要があります`:`無効な${i[r.format]??t.format}`}case"not_multiple_of":return`無効な数値: ${t.divisor}の倍数である必要があります`;case"unrecognized_keys":return`認識されていないキー${t.keys.length>1?"群":""}: ${v(t.keys,"、")}`;case"invalid_key":return`${t.origin}内の無効なキー`;case"invalid_union":return"無効な入力";case"invalid_element":return`${t.origin}内の無効な値`;default:return"無効な入力"}}};function ff(){return{localeError:P$()}}var T$=()=>{let e={string:{unit:"სიმბოლო",verb:"უნდა შეიცავდეს"},file:{unit:"ბაიტი",verb:"უნდა შეიცავდეს"},array:{unit:"ელემენტი",verb:"უნდა შეიცავდეს"},set:{unit:"ელემენტი",verb:"უნდა შეიცავდეს"},map:{unit:"ელემენტი",verb:"უნდა შეიცავდეს"}};function n(t){return e[t]??null}let i={regex:"შეყვანა",email:"ელ-ფოსტის მისამართი",url:"URL",emoji:"ემოჯი",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"თარიღი-დრო",date:"თარიღი",time:"დრო",duration:"ხანგრძლივობა",ipv4:"IPv4 მისამართი",ipv6:"IPv6 მისამართი",mac:"MAC მისამართი",cidrv4:"IPv4 დიაპაზონი",cidrv6:"IPv6 დიაპაზონი",base64:"base64-კოდირებული ველი",base64url:"base64url-კოდირებული ველი",json_string:"JSON ველი",e164:"E.164 ნომერი",credit_card:"საკრედიტო ბარათის ნომერი",currency_code:"ვალუტის კოდი",iban:"IBAN",jwt:"JWT",template_literal:"შეყვანა"},o={nan:"NaN",number:"რიცხვი",string:"ველი",boolean:"ბულეანი",function:"ფუნქცია",array:"მასივი"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`არასწორი შეყვანა: მოსალოდნელი instanceof ${t.expected}, მიღებული ${s}`:`არასწორი შეყვანა: მოსალოდნელი ${r}, მიღებული ${s}`}case"invalid_value":return t.values.length===1?`არასწორი შეყვანა: მოსალოდნელი ${$(t.values[0])}`:`არასწორი ვარიანტი: მოსალოდნელია ერთ-ერთი ${v(t.values,"|")}-დან`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ზედმეტად დიდი: მოსალოდნელი ${t.origin??"მნიშვნელობა"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit}`:`ზედმეტად დიდი: მოსალოდნელი ${t.origin??"მნიშვნელობა"} იყოს ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ზედმეტად პატარა: მოსალოდნელი ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`ზედმეტად პატარა: მოსალოდნელი ${t.origin} იყოს ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`არასწორი ველი: უნდა იწყებოდეს "${r.prefix}"-ით`:r.format==="ends_with"?`არასწორი ველი: უნდა მთავრდებოდეს "${r.suffix}"-ით`:r.format==="includes"?`არასწორი ველი: უნდა შეიცავდეს "${r.includes}"-ს`:r.format==="regex"?`არასწორი ველი: უნდა შეესაბამებოდეს შაბლონს ${r.pattern}`:`არასწორი ${i[r.format]??t.format}`}case"not_multiple_of":return`არასწორი რიცხვი: უნდა იყოს ${t.divisor}-ის ჯერადი`;case"unrecognized_keys":return`უცნობი გასაღებ${t.keys.length>1?"ები":"ი"}: ${v(t.keys,", ")}`;case"invalid_key":return`არასწორი გასაღები ${t.origin}-ში`;case"invalid_union":return"არასწორი შეყვანა";case"invalid_element":return`არასწორი მნიშვნელობა ${t.origin}-ში`;default:return"არასწორი შეყვანა"}}};function gf(){return{localeError:T$()}}var E$=()=>{let e={string:{unit:"តួអក្សរ",verb:"គួរមាន"},file:{unit:"បៃ",verb:"គួរមាន"},array:{unit:"ធាតុ",verb:"គួរមាន"},set:{unit:"ធាតុ",verb:"គួរមាន"},map:{unit:"ធាតុ",verb:"គួរមាន"}};function n(t){return e[t]??null}let i={regex:"ទិន្នន័យបញ្ចូល",email:"អាសយដ្ឋានអ៊ីមែល",url:"URL",emoji:"សញ្ញាអារម្មណ៍",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"កាលបរិច្ឆេទ និងម៉ោង ISO",date:"កាលបរិច្ឆេទ ISO",time:"ម៉ោង ISO",duration:"រយៈពេល ISO",ipv4:"អាសយដ្ឋាន IPv4",ipv6:"អាសយដ្ឋាន IPv6",mac:"អាសយដ្ឋាន MAC",cidrv4:"ដែនអាសយដ្ឋាន IPv4",cidrv6:"ដែនអាសយដ្ឋាន IPv6",base64:"ខ្សែអក្សរអ៊ិកូដ base64",base64url:"ខ្សែអក្សរអ៊ិកូដ base64url",json_string:"ខ្សែអក្សរ JSON",e164:"លេខ E.164",credit_card:"លេខប័ណ្ណឥណទាន",currency_code:"កូដរូបិយប័ណ្ណ",iban:"IBAN",jwt:"JWT",template_literal:"ទិន្នន័យបញ្ចូល"},o={nan:"NaN",number:"លេខ",array:"អារេ (Array)",null:"គ្មានតម្លៃ (null)"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ instanceof ${t.expected} ប៉ុន្តែទទួលបាន ${s}`:`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ ${r} ប៉ុន្តែទទួលបាន ${s}`}case"invalid_value":return t.values.length===1?`ទិន្នន័យបញ្ចូលមិនត្រឹមត្រូវ៖ ត្រូវការ ${$(t.values[0])}`:`ជម្រើសមិនត្រឹមត្រូវ៖ ត្រូវជាមួយក្នុងចំណោម ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ធំពេក៖ ត្រូវការ ${t.origin??"តម្លៃ"} ${r} ${t.maximum.toString()} ${a.unit??"ធាតុ"}`:`ធំពេក៖ ត្រូវការ ${t.origin??"តម្លៃ"} ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`តូចពេក៖ ត្រូវការ ${t.origin} ${r} ${t.minimum.toString()} ${a.unit}`:`តូចពេក៖ ត្រូវការ ${t.origin} ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវចាប់ផ្តើមដោយ "${r.prefix}"`:r.format==="ends_with"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវបញ្ចប់ដោយ "${r.suffix}"`:r.format==="includes"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវមាន "${r.includes}"`:r.format==="regex"?`ខ្សែអក្សរមិនត្រឹមត្រូវ៖ ត្រូវតែផ្គូផ្គងនឹងទម្រង់ដែលបានកំណត់ ${r.pattern}`:`មិនត្រឹមត្រូវ៖ ${i[r.format]??t.format}`}case"not_multiple_of":return`លេខមិនត្រឹមត្រូវ៖ ត្រូវតែជាពហុគុណនៃ ${t.divisor}`;case"unrecognized_keys":return`រកឃើញសោមិនស្គាល់៖ ${v(t.keys,", ")}`;case"invalid_key":return`សោមិនត្រឹមត្រូវនៅក្នុង ${t.origin}`;case"invalid_union":return"ទិន្នន័យមិនត្រឹមត្រូវ";case"invalid_element":return`ទិន្នន័យមិនត្រឹមត្រូវនៅក្នុង ${t.origin}`;default:return"ទិន្នន័យមិនត្រឹមត្រូវ"}}};function Xi(){return{localeError:E$()}}function hf(){return Xi()}var A$=()=>{let e={string:{unit:"ಅಕ್ಷರಗಳು",verb:"ಹೊಂದಲು"},file:{unit:"ಬೈಟ್‌ಗಳು",verb:"ಹೊಂದಲು"},array:{unit:"ವಸ್ತುಗಳು",verb:"ಹೊಂದಲು"},set:{unit:"ವಸ್ತುಗಳು",verb:"ಹೊಂದಲು"},map:{unit:"entries",verb:"ಹೊಂದಲು"}};function n(t){return e[t]??null}let i={regex:"ಇನ್ಪುಟ್",email:"email ವಿಳಾಸ",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO ದಿನಾಂಕದ ಸಮಯ",date:"ISO ದಿನಾಂಕ",time:"ISO ಸಮಯ",duration:"ISO ಅವಧಿ",ipv4:"IPv4 ವಿಳಾಸ",ipv6:"IPv6 ವಿಳಾಸ",mac:"MAC ವಿಳಾಸ",cidrv4:"IPv4 ವ್ಯಾಪ್ತಿಯ",cidrv6:"IPv6 ವ್ಯಾಪ್ತಿಯ",base64:"base64-encodedಸ್ಟ್ರಿಂಗ್",base64url:"base64url-encodedಸ್ಟ್ರಿಂಗ್",json_string:"JSONಸ್ಟ್ರಿಂಗ್",e164:"E.164 ಸಂಖ್ಯೆ",credit_card:"ಕ್ರೆಡಿಟ್ ಕಾರ್ಡ್ ಸಂಖ್ಯೆ",currency_code:"ಕರೆನ್ಸಿ ಕೋಡ್",iban:"IBAN",jwt:"JWT",template_literal:"ಇನ್ಪುಟ್"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${r}, ಸ್ವೀಕರಿಸಿದನು ${s}`}case"invalid_value":return t.values.length===1?`ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${$(t.values[0])}`:`ಅಮಾನ್ಯ ಆಯ್ಕೆ: ಇವುಗಳಲ್ಲಿ ಒಂದನ್ನು ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ತುಂಬಾ ದೊಡ್ಡದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin??"value"} ಹೊಂದಲು ${r}${t.maximum.toString()} ${a.unit??"ಅಂಶಗಳು"}`:`ತುಂಬಾ ದೊಡ್ಡದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin??"value"} ಎಂದು ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ತುಂಬಾ ಚಿಕ್ಕದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin} ಹೊಂದಲು ${r}${t.minimum.toString()} ${a.unit}`:`ತುಂಬಾ ಚಿಕ್ಕದು: ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.origin} ಎಂದು ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`ಅಮಾನ್ಯವಾದ ಸ್ಟ್ರಿಂಗ್: ಇದರೊಂದಿಗೆ ಪ್ರಾರಂಭಿಸಬೇಕು "${r.prefix}"`:r.format==="ends_with"?`ಅಮಾನ್ಯವಾದ ಸ್ಟ್ರಿಂಗ್: ಇದರೊಂದಿಗೆ ಕೊನೆಗೊಳ್ಳಬೇಕು "${r.suffix}"`:r.format==="includes"?`ಅಮಾನ್ಯ ಸ್ಟ್ರಿಂಗ್: ಒಳಗೊಂಡಿರಬೇಕು "${r.includes}"`:r.format==="regex"?`ಅಮಾನ್ಯವಾದ ಸ್ಟ್ರಿಂಗ್: ಮಾದರಿಗೆ ಹೊಂದಿಕೆಯಾಗಬೇಕು ${r.pattern}`:`ಅಮಾನ್ಯ ${i[r.format]??t.format}`}case"not_multiple_of":return`ಅಮಾನ್ಯ ಸಂಖ್ಯೆ: ಬಹುಸಂಖ್ಯೆಯಾಗಿರಬೇಕು ${t.divisor}`;case"unrecognized_keys":return`ಗುರುತಿಸಲಾಗದ ಕೀ ${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`ಅಮಾನ್ಯವಾದ ಕೀ ಇನ್ ${t.origin}`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`ಅಮಾನ್ಯ ತಾರತಮ್ಯ ಮೌಲ್ಯ. ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ${t.options.map(a=>`'${a}'`).join(" | ")}`:"ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್";case"invalid_element":return`ರಲ್ಲಿ ಅಮಾನ್ಯ ಮೌಲ್ಯ ${t.origin}`;default:return"ಅಮಾನ್ಯ ಇನ್‌ಪುಟ್"}}};function vf(){return{localeError:A$()}}var O$=()=>{let e={string:{unit:"문자",verb:"to have"},file:{unit:"바이트",verb:"to have"},array:{unit:"개",verb:"to have"},set:{unit:"개",verb:"to have"},map:{unit:"개",verb:"to have"}};function n(t){return e[t]??null}let i={regex:"입력",email:"이메일 주소",url:"URL",emoji:"이모지",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO 날짜시간",date:"ISO 날짜",time:"ISO 시간",duration:"ISO 기간",ipv4:"IPv4 주소",ipv6:"IPv6 주소",mac:"MAC 주소",cidrv4:"IPv4 범위",cidrv6:"IPv6 범위",base64:"base64 인코딩 문자열",base64url:"base64url 인코딩 문자열",json_string:"JSON 문자열",e164:"E.164 번호",credit_card:"신용카드 번호",currency_code:"통화 코드",iban:"IBAN",jwt:"JWT",template_literal:"입력"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`잘못된 입력: 예상 타입은 instanceof ${t.expected}, 받은 타입은 ${s}입니다`:`잘못된 입력: 예상 타입은 ${r}, 받은 타입은 ${s}입니다`}case"invalid_value":return t.values.length===1?`잘못된 입력: 값은 ${$(t.values[0])} 이어야 합니다`:`잘못된 옵션: ${v(t.values,"또는 ")} 중 하나여야 합니다`;case"too_big":{let r=t.inclusive?"이하":"미만",a=r==="미만"?"이어야 합니다":"여야 합니다",s=n(t.origin),u=s?.unit??"요소";return s?`${t.origin??"값"}이 너무 큽니다: ${t.maximum.toString()}${u} ${r}${a}`:`${t.origin??"값"}이 너무 큽니다: ${t.maximum.toString()} ${r}${a}`}case"too_small":{let r=t.inclusive?"이상":"초과",a=r==="이상"?"이어야 합니다":"여야 합니다",s=n(t.origin),u=s?.unit??"요소";return s?`${t.origin??"값"}이 너무 작습니다: ${t.minimum.toString()}${u} ${r}${a}`:`${t.origin??"값"}이 너무 작습니다: ${t.minimum.toString()} ${r}${a}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`잘못된 문자열: "${r.prefix}"(으)로 시작해야 합니다`:r.format==="ends_with"?`잘못된 문자열: "${r.suffix}"(으)로 끝나야 합니다`:r.format==="includes"?`잘못된 문자열: "${r.includes}"을(를) 포함해야 합니다`:r.format==="regex"?`잘못된 문자열: 정규식 ${r.pattern} 패턴과 일치해야 합니다`:`잘못된 ${i[r.format]??t.format}`}case"not_multiple_of":return`잘못된 숫자: ${t.divisor}의 배수여야 합니다`;case"unrecognized_keys":return`인식할 수 없는 키: ${v(t.keys,", ")}`;case"invalid_key":return`잘못된 키: ${t.origin}`;case"invalid_union":return"잘못된 입력";case"invalid_element":return`잘못된 값: ${t.origin}`;default:return"잘못된 입력"}}};function yf(){return{localeError:O$()}}var br=e=>e.charAt(0).toUpperCase()+e.slice(1);function bf(e){let n=Math.abs(e),i=n%10,o=n%100;return o>=11&&o<=19||i===0?"many":i===1?"one":"few"}var N$=()=>{let e={string:{unit:{one:"simbolis",few:"simboliai",many:"simbolių"},verb:{smaller:{inclusive:"turi būti ne ilgesnė kaip",notInclusive:"turi būti trumpesnė kaip"},bigger:{inclusive:"turi būti ne trumpesnė kaip",notInclusive:"turi būti ilgesnė kaip"}}},file:{unit:{one:"baitas",few:"baitai",many:"baitų"},verb:{smaller:{inclusive:"turi būti ne didesnis kaip",notInclusive:"turi būti mažesnis kaip"},bigger:{inclusive:"turi būti ne mažesnis kaip",notInclusive:"turi būti didesnis kaip"}}},array:{unit:{one:"elementą",few:"elementus",many:"elementų"},verb:{smaller:{inclusive:"turi turėti ne daugiau kaip",notInclusive:"turi turėti mažiau kaip"},bigger:{inclusive:"turi turėti ne mažiau kaip",notInclusive:"turi turėti daugiau kaip"}}},set:{unit:{one:"elementą",few:"elementus",many:"elementų"},verb:{smaller:{inclusive:"turi turėti ne daugiau kaip",notInclusive:"turi turėti mažiau kaip"},bigger:{inclusive:"turi turėti ne mažiau kaip",notInclusive:"turi turėti daugiau kaip"}}}};function n(t,r,a,s){let u=e[t]??null;return u===null?u:{unit:u.unit[r],verb:u.verb[s][a?"inclusive":"notInclusive"]}}let i={regex:"įvestis",email:"el. pašto adresas",url:"URL",emoji:"jaustukas",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO data ir laikas",date:"ISO data",time:"ISO laikas",duration:"ISO trukmė",ipv4:"IPv4 adresas",ipv6:"IPv6 adresas",mac:"MAC adresas",cidrv4:"IPv4 tinklo prefiksas (CIDR)",cidrv6:"IPv6 tinklo prefiksas (CIDR)",base64:"base64 užkoduota eilutė",base64url:"base64url užkoduota eilutė",json_string:"JSON eilutė",e164:"E.164 numeris",credit_card:"kredito kortelės numeris",currency_code:"valiutos kodas",iban:"IBAN",jwt:"JWT",template_literal:"įvestis"},o={nan:"NaN",number:"skaičius",bigint:"sveikasis skaičius",string:"eilutė",boolean:"loginė reikšmė",undefined:"neapibrėžta reikšmė",function:"funkcija",symbol:"simbolis",array:"masyvas",object:"objektas",null:"nulinė reikšmė"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Gautas tipas ${s}, o tikėtasi - instanceof ${t.expected}`:`Gautas tipas ${s}, o tikėtasi - ${r}`}case"invalid_value":return t.values.length===1?`Privalo būti ${$(t.values[0])}`:`Privalo būti vienas iš ${v(t.values,"|")} pasirinkimų`;case"too_big":{let r=o[t.origin]??t.origin,a=n(t.origin,bf(Number(t.maximum)),t.inclusive??!1,"smaller");if(a?.verb)return`${br(r??t.origin??"reikšmė")} ${a.verb} ${t.maximum.toString()} ${a.unit??"elementų"}`;let s=t.inclusive?"ne didesnis kaip":"mažesnis kaip";return`${br(r??t.origin??"reikšmė")} turi būti ${s} ${t.maximum.toString()} ${a?.unit}`}case"too_small":{let r=o[t.origin]??t.origin,a=n(t.origin,bf(Number(t.minimum)),t.inclusive??!1,"bigger");if(a?.verb)return`${br(r??t.origin??"reikšmė")} ${a.verb} ${t.minimum.toString()} ${a.unit??"elementų"}`;let s=t.inclusive?"ne mažesnis kaip":"didesnis kaip";return`${br(r??t.origin??"reikšmė")} turi būti ${s} ${t.minimum.toString()} ${a?.unit}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Eilutė privalo prasidėti "${r.prefix}"`:r.format==="ends_with"?`Eilutė privalo pasibaigti "${r.suffix}"`:r.format==="includes"?`Eilutė privalo įtraukti "${r.includes}"`:r.format==="regex"?`Eilutė privalo atitikti ${r.pattern}`:`Neteisingas ${i[r.format]??t.format}`}case"not_multiple_of":return`Skaičius privalo būti ${t.divisor} kartotinis.`;case"unrecognized_keys":return`Neatpažint${t.keys.length>1?"i":"as"} rakt${t.keys.length>1?"ai":"as"}: ${v(t.keys,", ")}`;case"invalid_key":return"Rastas klaidingas raktas";case"invalid_union":return"Klaidinga įvestis";case"invalid_element":{let r=o[t.origin]??t.origin;return`${br(r??t.origin??"reikšmė")} turi klaidingą įvestį`}default:return"Klaidinga įvestis"}}};function _f(){return{localeError:N$()}}var z$=()=>{let e={string:{unit:"знаци",verb:"да имаат"},file:{unit:"бајти",verb:"да имаат"},array:{unit:"ставки",verb:"да имаат"},set:{unit:"ставки",verb:"да имаат"},map:{unit:"ставки",verb:"да имаат"}};function n(t){return e[t]??null}let i={regex:"внес",email:"адреса на е-пошта",url:"URL",emoji:"емоџи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO датум и време",date:"ISO датум",time:"ISO време",duration:"ISO времетраење",ipv4:"IPv4 адреса",ipv6:"IPv6 адреса",mac:"MAC адреса",cidrv4:"IPv4 опсег",cidrv6:"IPv6 опсег",base64:"base64-енкодирана низа",base64url:"base64url-енкодирана низа",json_string:"JSON низа",e164:"E.164 број",credit_card:"број на кредитна картичка",currency_code:"код на валута",iban:"IBAN",jwt:"JWT",template_literal:"внес"},o={nan:"NaN",number:"број",array:"низа"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Грешен внес: се очекува instanceof ${t.expected}, примено ${s}`:`Грешен внес: се очекува ${r}, примено ${s}`}case"invalid_value":return t.values.length===1?`Invalid input: expected ${$(t.values[0])}`:`Грешана опција: се очекува една ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Премногу голем: се очекува ${t.origin??"вредноста"} да има ${r}${t.maximum.toString()} ${a.unit??"елементи"}`:`Премногу голем: се очекува ${t.origin??"вредноста"} да биде ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Премногу мал: се очекува ${t.origin} да има ${r}${t.minimum.toString()} ${a.unit}`:`Премногу мал: се очекува ${t.origin} да биде ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Неважечка низа: мора да започнува со "${r.prefix}"`:r.format==="ends_with"?`Неважечка низа: мора да завршува со "${r.suffix}"`:r.format==="includes"?`Неважечка низа: мора да вклучува "${r.includes}"`:r.format==="regex"?`Неважечка низа: мора да одгоара на патернот ${r.pattern}`:`Invalid ${i[r.format]??t.format}`}case"not_multiple_of":return`Грешен број: мора да биде делив со ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Непрепознаени клучеви":"Непрепознаен клуч"}: ${v(t.keys,", ")}`;case"invalid_key":return`Грешен клуч во ${t.origin}`;case"invalid_union":return"Грешен внес";case"invalid_element":return`Грешна вредност во ${t.origin}`;default:return"Грешен внес"}}};function $f(){return{localeError:z$()}}var D$=()=>{let e={string:{unit:"aksara",verb:"mempunyai"},file:{unit:"bait",verb:"mempunyai"},array:{unit:"elemen",verb:"mempunyai"},set:{unit:"elemen",verb:"mempunyai"},map:{unit:"elemen",verb:"mempunyai"}};function n(t){return e[t]??null}let i={regex:"input",email:"alamat e-mel",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"tarikh masa ISO",date:"tarikh ISO",time:"masa ISO",duration:"tempoh ISO",ipv4:"alamat IPv4",ipv6:"alamat IPv6",mac:"alamat MAC",cidrv4:"julat IPv4",cidrv6:"julat IPv6",base64:"string dikodkan base64",base64url:"string dikodkan base64url",json_string:"string JSON",e164:"nombor E.164",credit_card:"nombor kad kredit",currency_code:"kod mata wang",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"nombor"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Input tidak sah: dijangka instanceof ${t.expected}, diterima ${s}`:`Input tidak sah: dijangka ${r}, diterima ${s}`}case"invalid_value":return t.values.length===1?`Input tidak sah: dijangka ${$(t.values[0])}`:`Pilihan tidak sah: dijangka salah satu daripada ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Terlalu besar: dijangka ${t.origin??"nilai"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"elemen"}`:`Terlalu besar: dijangka ${t.origin??"nilai"} adalah ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Terlalu kecil: dijangka ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Terlalu kecil: dijangka ${t.origin} adalah ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`String tidak sah: mesti bermula dengan "${r.prefix}"`:r.format==="ends_with"?`String tidak sah: mesti berakhir dengan "${r.suffix}"`:r.format==="includes"?`String tidak sah: mesti mengandungi "${r.includes}"`:r.format==="regex"?`String tidak sah: mesti sepadan dengan corak ${r.pattern}`:`${i[r.format]??t.format} tidak sah`}case"not_multiple_of":return`Nombor tidak sah: perlu gandaan ${t.divisor}`;case"unrecognized_keys":return`Kunci tidak dikenali: ${v(t.keys,", ")}`;case"invalid_key":return`Kunci tidak sah dalam ${t.origin}`;case"invalid_union":return"Input tidak sah";case"invalid_element":return`Nilai tidak sah dalam ${t.origin}`;default:return"Input tidak sah"}}};function kf(){return{localeError:D$()}}var C$=()=>{let e={string:{unit:"अक्षर",verb:"हुनुपर्छ"},file:{unit:"बाइट",verb:"हुनुपर्छ"},array:{unit:"तत्व",verb:"हुनुपर्छ"},set:{unit:"तत्व",verb:"हुनुपर्छ"},map:{unit:"प्रविष्टि",verb:"हुनुपर्छ"}};function n(t){return e[t]??null}let i={regex:"इनपुट",email:"इमेल ठेगाना",url:"URL",emoji:"इमोजी",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO मिति र समय",date:"ISO मिति",time:"ISO समय",duration:"ISO अवधि",ipv4:"IPv4 ठेगाना",ipv6:"IPv6 ठेगाना",mac:"MAC ठेगाना",cidrv4:"IPv4 दायरा",cidrv6:"IPv6 दायरा",base64:"base64-इन्कोड गरिएको स्ट्रिङ",base64url:"base64url-इन्कोड गरिएको स्ट्रिङ",json_string:"JSON स्ट्रिङ",e164:"E.164 नम्बर",credit_card:"क्रेडिट कार्ड नम्बर",currency_code:"मुद्रा कोड",iban:"IBAN",jwt:"JWT",template_literal:"इनपुट"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`अमान्य इनपुट: अपेक्षित ${r}, प्राप्त ${s}`}case"invalid_value":return t.values.length===1?`अमान्य इनपुट: अपेक्षित ${$(t.values[0])}`:`अमान्य विकल्प: अपेक्षित मानहरू मध्ये एक ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`धेरै ठूलो: ${t.origin??"मान"} मा ${r}${t.maximum.toString()} ${a.unit} ${a.verb}`:`धेरै ठूलो: ${t.origin??"मान"} ${r}${t.maximum.toString()} हुनुपर्छ`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`धेरै सानो: ${t.origin} मा ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`धेरै सानो: ${t.origin} ${r}${t.minimum.toString()} हुनुपर्छ`}case"invalid_format":{let r=t;return r.format==="starts_with"?`अमान्य स्ट्रिङ: "${r.prefix}" बाट सुरु हुनुपर्छ`:r.format==="ends_with"?`अमान्य स्ट्रिङ: "${r.suffix}" मा समाप्त हुनुपर्छ`:r.format==="includes"?`अमान्य स्ट्रिङ: "${r.includes}" समावेश हुनुपर्छ`:r.format==="regex"?`अमान्य स्ट्रिङ: ढाँचा ${r.pattern} सँग मेल खानुपर्छ`:`अमान्य ${i[r.format]??t.format}`}case"not_multiple_of":return`अमान्य संख्या: ${t.divisor} को गुणज हुनुपर्छ`;case"unrecognized_keys":return`अपरिचित कुञ्जी${t.keys.length>1?"हरू":""}: ${v(t.keys,", ")}`;case"invalid_key":return`अमान्य कुञ्जी: ${t.origin} मा`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`अमान्य डिस्क्रिमिनेटर मान: अपेक्षित ${t.options.map(a=>`'${a}'`).join(" | ")}`:"अमान्य इनपुट";case"invalid_element":return`अमान्य मान: ${t.origin} मा`;default:return"अमान्य इनपुट"}}};function xf(){return{localeError:C$()}}var L$=()=>{let e={string:{unit:"tekens",verb:"heeft"},file:{unit:"bytes",verb:"heeft"},array:{unit:"elementen",verb:"heeft"},set:{unit:"elementen",verb:"heeft"},map:{unit:"elementen",verb:"heeft"}};function n(t){return e[t]??null}let i={regex:"invoer",email:"emailadres",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datum en tijd",date:"ISO datum",time:"ISO tijd",duration:"ISO duur",ipv4:"IPv4-adres",ipv6:"IPv6-adres",mac:"MAC-adres",cidrv4:"IPv4-bereik",cidrv6:"IPv6-bereik",base64:"base64-gecodeerde tekst",base64url:"base64 URL-gecodeerde tekst",json_string:"JSON string",e164:"E.164-nummer",credit_card:"creditcardnummer",currency_code:"valutacode",iban:"IBAN",jwt:"JWT",template_literal:"invoer"},o={nan:"NaN",number:"getal"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ongeldige invoer: verwacht instanceof ${t.expected}, ontving ${s}`:`Ongeldige invoer: verwacht ${r}, ontving ${s}`}case"invalid_value":return t.values.length===1?`Ongeldige invoer: verwacht ${$(t.values[0])}`:`Ongeldige optie: verwacht één van ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin),s=t.origin==="date"?"laat":t.origin==="string"?"lang":"groot";return a?`Te ${s}: verwacht dat ${t.origin??"waarde"} ${r}${t.maximum.toString()} ${a.unit??"elementen"} ${a.verb}`:`Te ${s}: verwacht dat ${t.origin??"waarde"} ${r}${t.maximum.toString()} is`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin),s=t.origin==="date"?"vroeg":t.origin==="string"?"kort":"klein";return a?`Te ${s}: verwacht dat ${t.origin} ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`Te ${s}: verwacht dat ${t.origin} ${r}${t.minimum.toString()} is`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ongeldige tekst: moet met "${r.prefix}" beginnen`:r.format==="ends_with"?`Ongeldige tekst: moet op "${r.suffix}" eindigen`:r.format==="includes"?`Ongeldige tekst: moet "${r.includes}" bevatten`:r.format==="regex"?`Ongeldige tekst: moet overeenkomen met patroon ${r.pattern}`:`Ongeldig: ${i[r.format]??t.format}`}case"not_multiple_of":return`Ongeldig getal: moet een veelvoud van ${t.divisor} zijn`;case"unrecognized_keys":return`Onbekende key${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Ongeldige key in ${t.origin}`;case"invalid_union":return"Ongeldige invoer";case"invalid_element":return`Ongeldige waarde in ${t.origin}`;default:return"Ongeldige invoer"}}};function wf(){return{localeError:L$()}}var j$=()=>{let e={string:{unit:"teikn",verb:"å ha"},file:{unit:"bytes",verb:"å ha"},array:{unit:"element",verb:"å innehalde"},set:{unit:"element",verb:"å innehalde"},map:{unit:"element",verb:"å innehalde"}};function n(t){return e[t]??null}let i={regex:"input",email:"e-postadresse",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dato- og klokkeslett",date:"ISO-dato",time:"ISO-klokkeslett",duration:"ISO-varigheit",ipv4:"IPv4-adresse",ipv6:"IPv6-adresse",mac:"MAC-adresse",cidrv4:"IPv4-spekter",cidrv6:"IPv6-spekter",base64:"base64-enkoda streng",base64url:"base64url-enkoda streng",json_string:"JSON-streng",e164:"E.164-nummer",credit_card:"kredittkortnummer",currency_code:"valutakode",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"tal",array:"liste"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ugyldig input: forventa instanceof ${t.expected}, fekk ${s}`:`Ugyldig input: forventa ${r}, fekk ${s}`}case"invalid_value":return t.values.length===1?`Ugyldig verdi: forventa ${$(t.values[0])}`:`Ugyldig val: forventa eitt av ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`For stor(t): forventa ${t.origin??"value"} til å ha ${r}${t.maximum.toString()} ${a.unit??"element"}`:`For stor(t): forventa ${t.origin??"value"} til å ha ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`For lite(n): forventa ${t.origin} til å ha ${r}${t.minimum.toString()} ${a.unit}`:`For lite(n): forventa ${t.origin} til å ha ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ugyldig streng: må starte med "${r.prefix}"`:r.format==="ends_with"?`Ugyldig streng: må slutte med "${r.suffix}"`:r.format==="includes"?`Ugyldig streng: må innehalde "${r.includes}"`:r.format==="regex"?`Ugyldig streng: må matche mønsteret ${r.pattern}`:`Ugyldig ${i[r.format]??t.format}`}case"not_multiple_of":return`Ugyldig tal: må vere eit multiplum av ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Ukjende nøklar":"Ukjend nøkkel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ugyldig nøkkel i ${t.origin}`;case"invalid_union":return"Ugyldig input";case"invalid_element":return`Ugyldig verdi i ${t.origin}`;default:return"Ugyldig input"}}};function If(){return{localeError:j$()}}var U$=()=>{let e={string:{unit:"tegn",verb:"å ha"},file:{unit:"bytes",verb:"å ha"},array:{unit:"elementer",verb:"å inneholde"},set:{unit:"elementer",verb:"å inneholde"},map:{unit:"elementer",verb:"å inneholde"}};function n(t){return e[t]??null}let i={regex:"input",email:"e-postadresse",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO dato- og klokkeslett",date:"ISO-dato",time:"ISO-klokkeslett",duration:"ISO-varighet",ipv4:"IPv4-adresse",ipv6:"IPv6-adresse",mac:"MAC-adresse",cidrv4:"IPv4-spekter",cidrv6:"IPv6-spekter",base64:"base64-enkodet streng",base64url:"base64url-enkodet streng",json_string:"JSON-streng",e164:"E.164-nummer",credit_card:"kredittkortnummer",currency_code:"valutakode",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"tall",array:"liste"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ugyldig input: forventet instanceof ${t.expected}, fikk ${s}`:`Ugyldig input: forventet ${r}, fikk ${s}`}case"invalid_value":return t.values.length===1?`Ugyldig verdi: forventet ${$(t.values[0])}`:`Ugyldig valg: forventet en av ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`For stor(t): forventet ${t.origin??"value"} til å ha ${r}${t.maximum.toString()} ${a.unit??"elementer"}`:`For stor(t): forventet ${t.origin??"value"} til å ha ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`For lite(n): forventet ${t.origin} til å ha ${r}${t.minimum.toString()} ${a.unit}`:`For lite(n): forventet ${t.origin} til å ha ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ugyldig streng: må starte med "${r.prefix}"`:r.format==="ends_with"?`Ugyldig streng: må ende med "${r.suffix}"`:r.format==="includes"?`Ugyldig streng: må inneholde "${r.includes}"`:r.format==="regex"?`Ugyldig streng: må matche mønsteret ${r.pattern}`:`Ugyldig ${i[r.format]??t.format}`}case"not_multiple_of":return`Ugyldig tall: må være et multiplum av ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Ukjente nøkler":"Ukjent nøkkel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ugyldig nøkkel i ${t.origin}`;case"invalid_union":return"Ugyldig input";case"invalid_element":return`Ugyldig verdi i ${t.origin}`;default:return"Ugyldig input"}}};function Sf(){return{localeError:U$()}}var R$=()=>{let e={string:{unit:"harf",verb:"olmalıdır"},file:{unit:"bayt",verb:"olmalıdır"},array:{unit:"unsur",verb:"olmalıdır"},set:{unit:"unsur",verb:"olmalıdır"},map:{unit:"unsur",verb:"olmalıdır"}};function n(t){return e[t]??null}let i={regex:"giren",email:"epostagâh",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO hengâmı",date:"ISO tarihi",time:"ISO zamanı",duration:"ISO müddeti",ipv4:"IPv4 nişânı",ipv6:"IPv6 nişânı",mac:"MAC nişânı",cidrv4:"IPv4 menzili",cidrv6:"IPv6 menzili",base64:"base64-şifreli metin",base64url:"base64url-şifreli metin",json_string:"JSON metin",e164:"E.164 sayısı",credit_card:"i'tibâr kartı numarası",currency_code:"para birimi kodu",iban:"IBAN",jwt:"JWT",template_literal:"giren"},o={nan:"NaN",number:"numara",array:"saf",null:"gayb"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Fâsit giren: umulan instanceof ${t.expected}, alınan ${s}`:`Fâsit giren: umulan ${r}, alınan ${s}`}case"invalid_value":return t.values.length===1?`Fâsit giren: umulan ${$(t.values[0])}`:`Fâsit tercih: mûteberler ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Fazla büyük: ${t.origin??"value"}, ${r}${t.maximum.toString()} ${a.unit??"elements"} sahip olmalıydı.`:`Fazla büyük: ${t.origin??"value"}, ${r}${t.maximum.toString()} olmalıydı.`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Fazla küçük: ${t.origin}, ${r}${t.minimum.toString()} ${a.unit} sahip olmalıydı.`:`Fazla küçük: ${t.origin}, ${r}${t.minimum.toString()} olmalıydı.`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Fâsit metin: "${r.prefix}" ile başlamalı.`:r.format==="ends_with"?`Fâsit metin: "${r.suffix}" ile bitmeli.`:r.format==="includes"?`Fâsit metin: "${r.includes}" ihtivâ etmeli.`:r.format==="regex"?`Fâsit metin: ${r.pattern} nakşına uymalı.`:`Fâsit ${i[r.format]??t.format}`}case"not_multiple_of":return`Fâsit sayı: ${t.divisor} katı olmalıydı.`;case"unrecognized_keys":return`Tanınmayan anahtar ${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} için tanınmayan anahtar var.`;case"invalid_union":return"Giren tanınamadı.";case"invalid_element":return`${t.origin} için tanınmayan kıymet var.`;default:return"Kıymet tanınamadı."}}};function Pf(){return{localeError:R$()}}var M$=()=>{let e={string:{unit:"توکي",verb:"ولري"},file:{unit:"بایټس",verb:"ولري"},array:{unit:"توکي",verb:"ولري"},set:{unit:"توکي",verb:"ولري"},map:{unit:"توکي",verb:"ولري"}};function n(t){return e[t]??null}let i={regex:"ورودي",email:"بریښنالیک",url:"یو آر ال",emoji:"ایموجي",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"نیټه او وخت",date:"نېټه",time:"وخت",duration:"موده",ipv4:"د IPv4 پته",ipv6:"د IPv6 پته",mac:"د MAC پته",cidrv4:"د IPv4 ساحه",cidrv6:"د IPv6 ساحه",base64:"base64-encoded متن",base64url:"base64url-encoded متن",json_string:"JSON متن",e164:"د E.164 شمېره",credit_card:"د کریډیټ کارت شمیره",currency_code:"د اسعارو کوډ",iban:"IBAN",jwt:"JWT",template_literal:"ورودي"},o={nan:"NaN",number:"عدد",array:"ارې"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ناسم ورودي: باید instanceof ${t.expected} وای, مګر ${s} ترلاسه شو`:`ناسم ورودي: باید ${r} وای, مګر ${s} ترلاسه شو`}case"invalid_value":return t.values.length===1?`ناسم ورودي: باید ${$(t.values[0])} وای`:`ناسم انتخاب: باید یو له ${v(t.values,"|")} څخه وای`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`ډیر لوی: ${t.origin??"ارزښت"} باید ${r}${t.maximum.toString()} ${a.unit??"عنصرونه"} ولري`:`ډیر لوی: ${t.origin??"ارزښت"} باید ${r}${t.maximum.toString()} وي`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`ډیر کوچنی: ${t.origin} باید ${r}${t.minimum.toString()} ${a.unit} ولري`:`ډیر کوچنی: ${t.origin} باید ${r}${t.minimum.toString()} وي`}case"invalid_format":{let r=t;return r.format==="starts_with"?`ناسم متن: باید د "${r.prefix}" سره پیل شي`:r.format==="ends_with"?`ناسم متن: باید د "${r.suffix}" سره پای ته ورسيږي`:r.format==="includes"?`ناسم متن: باید "${r.includes}" ولري`:r.format==="regex"?`ناسم متن: باید د ${r.pattern} سره مطابقت ولري`:`${i[r.format]??t.format} ناسم دی`}case"not_multiple_of":return`ناسم عدد: باید د ${t.divisor} مضرب وي`;case"unrecognized_keys":return`ناسم ${t.keys.length>1?"کلیډونه":"کلیډ"}: ${v(t.keys,", ")}`;case"invalid_key":return`ناسم کلیډ په ${t.origin} کې`;case"invalid_union":return"ناسمه ورودي";case"invalid_element":return`ناسم عنصر په ${t.origin} کې`;default:return"ناسمه ورودي"}}};function Tf(){return{localeError:M$()}}var Z$=()=>{let e={string:{unit:"znaków",verb:"mieć"},file:{unit:"bajtów",verb:"mieć"},array:{unit:"elementów",verb:"mieć"},set:{unit:"elementów",verb:"mieć"},map:{unit:"elementów",verb:"mieć"}};function n(t){return e[t]??null}let i={regex:"wyrażenie",email:"adres email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"data i godzina w formacie ISO",date:"data w formacie ISO",time:"godzina w formacie ISO",duration:"czas trwania ISO",ipv4:"adres IPv4",ipv6:"adres IPv6",mac:"adres MAC",cidrv4:"zakres IPv4",cidrv6:"zakres IPv6",base64:"ciąg znaków zakodowany w formacie base64",base64url:"ciąg znaków zakodowany w formacie base64url",json_string:"ciąg znaków w formacie JSON",e164:"liczba E.164",credit_card:"numer karty kredytowej",currency_code:"kod waluty",iban:"IBAN",jwt:"JWT",template_literal:"wejście"},o={nan:"NaN",number:"liczba",array:"tablica"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Nieprawidłowe dane wejściowe: oczekiwano instanceof ${t.expected}, otrzymano ${s}`:`Nieprawidłowe dane wejściowe: oczekiwano ${r}, otrzymano ${s}`}case"invalid_value":return t.values.length===1?`Nieprawidłowe dane wejściowe: oczekiwano ${$(t.values[0])}`:`Nieprawidłowa opcja: oczekiwano jednej z wartości ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Za duża wartość: oczekiwano, że ${t.origin??"wartość"} będzie mieć ${r}${t.maximum.toString()} ${a.unit??"elementów"}`:`Zbyt duż(y/a/e): oczekiwano, że ${t.origin??"wartość"} będzie wynosić ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Za mała wartość: oczekiwano, że ${t.origin??"wartość"} będzie mieć ${r}${t.minimum.toString()} ${a.unit??"elementów"}`:`Zbyt mał(y/a/e): oczekiwano, że ${t.origin??"wartość"} będzie wynosić ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Nieprawidłowy ciąg znaków: musi zaczynać się od "${r.prefix}"`:r.format==="ends_with"?`Nieprawidłowy ciąg znaków: musi kończyć się na "${r.suffix}"`:r.format==="includes"?`Nieprawidłowy ciąg znaków: musi zawierać "${r.includes}"`:r.format==="regex"?`Nieprawidłowy ciąg znaków: musi odpowiadać wzorcowi ${r.pattern}`:`Nieprawidłow(y/a/e) ${i[r.format]??t.format}`}case"not_multiple_of":return`Nieprawidłowa liczba: musi być wielokrotnością ${t.divisor}`;case"unrecognized_keys":return`Nierozpoznane klucze${t.keys.length>1?"s":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Nieprawidłowy klucz w ${t.origin}`;case"invalid_union":return"Nieprawidłowe dane wejściowe";case"invalid_element":return`Nieprawidłowa wartość w ${t.origin}`;default:return"Nieprawidłowe dane wejściowe"}}};function Ef(){return{localeError:Z$()}}var F$=()=>{let e={string:{unit:"caracteres"},file:{unit:"bytes"},array:{unit:"elementos"},set:{unit:"elementos"},map:{unit:"entradas"}};function n(a){return e[a]??null}let i={regex:"a entrada",email:"o endereço de e-mail",url:"o URL",emoji:"o emoji",uuid:"o UUID",uuidv4:"o UUIDv4",uuidv6:"o UUIDv6",nanoid:"o nanoid",guid:"o GUID",cuid:"o cuid",cuid2:"o cuid2",ulid:"o ULID",xid:"o XID",ksuid:"o KSUID",datetime:"a data e hora ISO",date:"a data ISO",time:"a hora ISO",duration:"a duração ISO",ipv4:"o endereço IPv4",ipv6:"o endereço IPv6",mac:"o endereço MAC",cidrv4:"o intervalo de endereços IPv4",cidrv6:"o intervalo de endereços IPv6",base64:"o texto codificado em base64",base64url:"o texto codificado em base64url",json_string:"o texto JSON",e164:"o número E.164",credit_card:"o número de cartão de crédito",currency_code:"o código de moeda",iban:"o IBAN",jwt:"o JWT",template_literal:"a entrada"},o={masculine:{definite:"o",indefinite:"um"},feminine:{definite:"a",indefinite:"uma"}},t={string:{name:"texto",articles:o.masculine},number:{name:"número",articles:o.masculine},int:{name:"número inteiro",articles:o.masculine},boolean:{name:"valor booleano",articles:o.masculine},bigint:{name:"número bigint",articles:o.masculine},symbol:{name:"símbolo",articles:o.masculine},undefined:{name:'valor "undefined"',articles:o.masculine},null:{name:'valor "nulo"',articles:o.masculine},never:{name:'valor "never"',articles:o.masculine},void:{name:'valor "void"',articles:o.masculine},date:{name:"data",articles:o.feminine},array:{name:"vetor",articles:o.masculine},object:{name:"objeto",articles:o.masculine},tuple:{name:"tuplo",articles:o.masculine},record:{name:"registo",articles:o.masculine},map:{name:"mapa",articles:o.masculine},set:{name:"conjunto",articles:o.masculine},file:{name:"ficheiro",articles:o.masculine},nonoptional:{name:"valor não opcional",articles:o.masculine},nan:{name:'valor "NaN"',articles:o.masculine},function:{name:"função",articles:o.feminine}};function r(a,s){let u=t[a]??{name:`valor "${a}"`,articles:o.masculine};return`${u.articles[s]} ${u.name}`}return a=>{switch(a.code){case"invalid_type":{let s=r(a.expected,"indefinite"),u=k(a.input),c=r(u,"indefinite");return`Entrada inválida: esperava ${s}, recebeu ${c}`}case"invalid_value":return a.values.length===1?`Entrada inválida: esperava ${$(a.values[0])}`:`Opção inválida: esperava uma das seguintes opções: ${v(a.values,"|")}`;case"too_big":{let s=a.inclusive?"<=":"<",u=n(a.origin);return u?`Demasiado grande: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.maximum.toString()} ${u.unit??"elementos"}`:`Demasiado grande: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.maximum.toString()}`}case"too_small":{let s=a.inclusive?">=":">",u=n(a.origin);return u?`Demasiado pequeno: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.minimum.toString()} ${u.unit??"elementos"}`:`Demasiado pequeno: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.minimum.toString()}`}case"invalid_format":{let s=a;return s.format==="starts_with"?`Texto inválido: deve começar por "${s.prefix}"`:s.format==="ends_with"?`Texto inválido: deve terminar em "${s.suffix}"`:s.format==="includes"?`Texto inválido: deve incluir "${s.includes}"`:s.format==="regex"?`Texto inválido: deve corresponder ao padrão ${s.pattern}`:`Formato d${i[s.format]??a.format} inválido`}case"not_multiple_of":return`Número inválido: deve ser múltiplo de ${a.divisor}`;case"unrecognized_keys":{let s=a.keys.length>1?"s":"";return`Chave${s} inválida${s}: ${v(a.keys,", ")}`}case"invalid_key":return`Entrada inválida n${r(a.origin,"definite")}`;case"invalid_union":return a.options&&Array.isArray(a.options)&&a.options.length>0?`Valor de discriminação inválido. Esperava ${a.options.map(u=>`'${u}'`).join(" | ")}`:"Entrada inválida";case"invalid_element":return`Entrada inválida n${r(a.origin,"definite")}`;default:return"Entrada inválida"}}};function Af(){return{localeError:F$()}}var V$=()=>{let e={string:{unit:"caracteres"},file:{unit:"bytes"},array:{unit:"elementos"},set:{unit:"elementos"},map:{unit:"entradas"}};function n(a){return e[a]??null}let i={regex:"a entrada",email:"o endereço de e-mail",url:"o URL",emoji:"o emoji",uuid:"o UUID",uuidv4:"o UUIDv4",uuidv6:"o UUIDv6",nanoid:"o nanoid",guid:"o GUID",cuid:"o cuid",cuid2:"o cuid2",ulid:"o ULID",xid:"o XID",ksuid:"o KSUID",datetime:"a data e hora ISO",date:"a data ISO",time:"a hora ISO",duration:"a duração ISO",ipv4:"o endereço IPv4",ipv6:"o endereço IPv6",mac:"o endereço MAC",cidrv4:"a faixa de endereços IPv4",cidrv6:"a faixa de endereços IPv6",base64:"o texto codificado em base64",base64url:"o texto codificado em base64url",json_string:"o texto JSON",e164:"o número E.164",credit_card:"o número de cartão de crédito",currency_code:"o código de moeda",iban:"o IBAN",jwt:"o JWT",template_literal:"a entrada"},o={masculine:{definite:"o",indefinite:"um"},feminine:{definite:"a",indefinite:"uma"}},t={string:{name:"texto",articles:o.masculine},number:{name:"número",articles:o.masculine},int:{name:"número inteiro",articles:o.masculine},boolean:{name:"valor booleano",articles:o.masculine},bigint:{name:"número bigint",articles:o.masculine},symbol:{name:"símbolo",articles:o.masculine},undefined:{name:'valor "undefined"',articles:o.masculine},null:{name:'valor "nulo"',articles:o.masculine},never:{name:'valor "never"',articles:o.masculine},void:{name:'valor "void"',articles:o.masculine},date:{name:"data",articles:o.feminine},array:{name:"vetor",articles:o.masculine},object:{name:"objeto",articles:o.masculine},tuple:{name:"tupla",articles:o.feminine},record:{name:"registro",articles:o.masculine},map:{name:"mapa",articles:o.masculine},set:{name:"conjunto",articles:o.masculine},file:{name:"arquivo",articles:o.masculine},nonoptional:{name:"valor não opcional",articles:o.masculine},nan:{name:'valor "NaN"',articles:o.masculine},function:{name:"função",articles:o.feminine}};function r(a,s){let u=t[a]??{name:`valor "${a}"`,articles:o.masculine};return`${u.articles[s]} ${u.name}`}return a=>{switch(a.code){case"invalid_type":{let s=r(a.expected,"indefinite"),u=k(a.input),c=r(u,"indefinite");return`Entrada inválida: esperava ${s}, recebeu ${c}`}case"invalid_value":return a.values.length===1?`Entrada inválida: esperava ${$(a.values[0])}`:`Opção inválida: esperava uma das seguintes opções: ${v(a.values,"|")}`;case"too_big":{let s=a.inclusive?"<=":"<",u=n(a.origin);return u?`Grande demais: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.maximum.toString()} ${u.unit??"elementos"}`:`Grande demais: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.maximum.toString()}`}case"too_small":{let s=a.inclusive?">=":">",u=n(a.origin);return u?`Pequeno demais: esperava que ${r(a.origin,"definite")} tivesse ${s} ${a.minimum.toString()} ${u.unit??"elementos"}`:`Pequeno demais: esperava que ${r(a.origin,"definite")} fosse ${s} ${a.minimum.toString()}`}case"invalid_format":{let s=a;return s.format==="starts_with"?`Texto inválido: deve começar com "${s.prefix}"`:s.format==="ends_with"?`Texto inválido: deve terminar com "${s.suffix}"`:s.format==="includes"?`Texto inválido: deve incluir "${s.includes}"`:s.format==="regex"?`Texto inválido: deve corresponder ao padrão ${s.pattern}`:`Formato d${i[s.format]??a.format} inválido`}case"not_multiple_of":return`Número inválido: deve ser múltiplo de ${a.divisor}`;case"unrecognized_keys":{let s=a.keys.length>1?"s":"";return`Chave${s} inválida${s}: ${v(a.keys,", ")}`}case"invalid_key":return`Entrada inválida n${r(a.origin,"definite")}`;case"invalid_union":return a.options&&Array.isArray(a.options)&&a.options.length>0?`Valor de discriminação inválido. Esperava ${a.options.map(u=>`'${u}'`).join(" | ")}`:"Entrada inválida";case"invalid_element":return`Entrada inválida n${r(a.origin,"definite")}`;default:return"Entrada inválida"}}};function Of(){return{localeError:V$()}}var G$=()=>{let e={string:{unit:"caractere",verb:"să aibă"},file:{unit:"octeți",verb:"să aibă"},array:{unit:"elemente",verb:"să aibă"},set:{unit:"elemente",verb:"să aibă"},map:{unit:"intrări",verb:"să aibă"}};function n(t){return e[t]??null}let i={regex:"intrare",email:"adresă de email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"dată și oră ISO",date:"dată ISO",time:"oră ISO",duration:"durată ISO",ipv4:"adresă IPv4",ipv6:"adresă IPv6",mac:"adresă MAC",cidrv4:"interval IPv4",cidrv6:"interval IPv6",base64:"șir codat base64",base64url:"șir codat base64url",json_string:"șir JSON",e164:"număr E.164",credit_card:"număr de card de credit",currency_code:"cod valutar",iban:"IBAN",jwt:"JWT",template_literal:"intrare"},o={nan:"NaN",string:"șir",number:"număr",boolean:"boolean",function:"funcție",array:"matrice",object:"obiect",undefined:"nedefinit",symbol:"simbol",bigint:"număr mare",void:"void",never:"never",map:"hartă",set:"set"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`Intrare invalidă: așteptat ${r}, primit ${s}`}case"invalid_value":return t.values.length===1?`Intrare invalidă: așteptat ${$(t.values[0])}`:`Opțiune invalidă: așteptat una dintre ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Prea mare: așteptat ca ${t.origin??"valoarea"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"elemente"}`:`Prea mare: așteptat ca ${t.origin??"valoarea"} să fie ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Prea mic: așteptat ca ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Prea mic: așteptat ca ${t.origin} să fie ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Șir invalid: trebuie să înceapă cu "${r.prefix}"`:r.format==="ends_with"?`Șir invalid: trebuie să se termine cu "${r.suffix}"`:r.format==="includes"?`Șir invalid: trebuie să includă "${r.includes}"`:r.format==="regex"?`Șir invalid: trebuie să se potrivească cu modelul ${r.pattern}`:`Format invalid: ${i[r.format]??t.format}`}case"not_multiple_of":return`Număr invalid: trebuie să fie multiplu de ${t.divisor}`;case"unrecognized_keys":return`Chei nerecunoscute: ${v(t.keys,", ")}`;case"invalid_key":return`Cheie invalidă în ${t.origin}`;case"invalid_union":return"Intrare invalidă";case"invalid_element":return`Valoare invalidă în ${t.origin}`;default:return"Intrare invalidă"}}};function Nf(){return{localeError:G$()}}function zf(e,n,i,o){let t=Math.abs(e),r=t%10,a=t%100;return a>=11&&a<=19?o:r===1?n:r>=2&&r<=4?i:o}var B$=()=>{let e={string:{unit:{one:"символ",few:"символа",many:"символов"},verb:"иметь"},file:{unit:{one:"байт",few:"байта",many:"байт"},verb:"иметь"},array:{unit:{one:"элемент",few:"элемента",many:"элементов"},verb:"иметь"},set:{unit:{one:"элемент",few:"элемента",many:"элементов"},verb:"иметь"},map:{unit:{one:"элемент",few:"элемента",many:"элементов"},verb:"иметь"}};function n(t){return e[t]??null}let i={regex:"ввод",email:"email адрес",url:"URL",emoji:"эмодзи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO дата и время",date:"ISO дата",time:"ISO время",duration:"ISO длительность",ipv4:"IPv4 адрес",ipv6:"IPv6 адрес",mac:"MAC адрес",cidrv4:"IPv4 диапазон",cidrv6:"IPv6 диапазон",base64:"строка в формате base64",base64url:"строка в формате base64url",json_string:"JSON строка",e164:"номер E.164",credit_card:"номер кредитной карты",currency_code:"код валюты",iban:"IBAN",jwt:"JWT",template_literal:"ввод"},o={nan:"NaN",number:"число",array:"массив"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Неверный ввод: ожидалось instanceof ${t.expected}, получено ${s}`:`Неверный ввод: ожидалось ${r}, получено ${s}`}case"invalid_value":return t.values.length===1?`Неверный ввод: ожидалось ${$(t.values[0])}`:`Неверный вариант: ожидалось одно из ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);if(a){let s=Number(t.maximum),u=zf(s,a.unit.one,a.unit.few,a.unit.many);return`Слишком большое значение: ожидалось, что ${t.origin??"значение"} будет иметь ${r}${t.maximum.toString()} ${u}`}return`Слишком большое значение: ожидалось, что ${t.origin??"значение"} будет ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);if(a){let s=Number(t.minimum),u=zf(s,a.unit.one,a.unit.few,a.unit.many);return`Слишком маленькое значение: ожидалось, что ${t.origin} будет иметь ${r}${t.minimum.toString()} ${u}`}return`Слишком маленькое значение: ожидалось, что ${t.origin} будет ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Неверная строка: должна начинаться с "${r.prefix}"`:r.format==="ends_with"?`Неверная строка: должна заканчиваться на "${r.suffix}"`:r.format==="includes"?`Неверная строка: должна содержать "${r.includes}"`:r.format==="regex"?`Неверная строка: должна соответствовать шаблону ${r.pattern}`:`Неверный ${i[r.format]??t.format}`}case"not_multiple_of":return`Неверное число: должно быть кратным ${t.divisor}`;case"unrecognized_keys":return`Нераспознанн${t.keys.length>1?"ые":"ый"} ключ${t.keys.length>1?"и":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Неверный ключ в ${t.origin}`;case"invalid_union":return"Неверные входные данные";case"invalid_element":return`Неверное значение в ${t.origin}`;default:return"Неверные входные данные"}}};function Df(){return{localeError:B$()}}var H$=()=>{let e={string:{unit:"znakov",verb:"mať"},file:{unit:"bajtov",verb:"mať"},array:{unit:"prvkov",verb:"mať"},set:{unit:"prvkov",verb:"mať"},map:{unit:"položiek",verb:"mať"}};function n(t){return e[t]??null}let i={regex:"regulárny výraz",email:"e-mailová adresa",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"dátum a čas vo formáte ISO",date:"dátum vo formáte ISO",time:"čas vo formáte ISO",duration:"doba trvania ISO",ipv4:"IPv4 adresa",ipv6:"IPv6 adresa",mac:"MAC adresa",cidrv4:"rozsah IPv4",cidrv6:"rozsah IPv6",base64:"reťazec zakódovaný vo formáte base64",base64url:"reťazec zakódovaný vo formáte base64url",json_string:"reťazec vo formáte JSON",e164:"číslo E.164",credit_card:"číslo kreditnej karty",currency_code:"kód meny",iban:"IBAN",jwt:"JWT",template_literal:"vstup"},o={nan:"NaN",number:"číslo",string:"reťazec",function:"funkcia",array:"pole"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neplatný vstup: očakávané instanceof ${t.expected}, obdržané ${s}`:`Neplatný vstup: očakávané ${r}, obdržané ${s}`}case"invalid_value":return t.values.length===1?`Neplatný vstup: očakávané ${$(t.values[0])}`:`Neplatný vstup: očakávaná jedna z hodnôt ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Hodnota je príliš veľká: ${t.origin??"hodnota"} musí mať ${r}${t.maximum.toString()} ${a.unit??"prvkov"}`:`Hodnota je príliš veľká: ${t.origin??"hodnota"} musí byť ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Hodnota je príliš malá: ${t.origin??"hodnota"} musí mať ${r}${t.minimum.toString()} ${a.unit??"prvkov"}`:`Hodnota je príliš malá: ${t.origin??"hodnota"} musí byť ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neplatný reťazec: musí začínať na "${r.prefix}"`:r.format==="ends_with"?`Neplatný reťazec: musí končiť na "${r.suffix}"`:r.format==="includes"?`Neplatný reťazec: musí obsahovať "${r.includes}"`:r.format==="regex"?`Neplatný reťazec: musí zodpovedať vzoru ${r.pattern}`:`Neplatný formát ${i[r.format]??t.format}`}case"not_multiple_of":return`Neplatné číslo: musí byť násobkom ${t.divisor}`;case"unrecognized_keys":return`Neznáme klúče: ${v(t.keys,", ")}`;case"invalid_key":return`Neplatný klúč v ${t.origin}`;case"invalid_union":return"Neplatný vstup";case"invalid_element":return`Neplatná hodnota v ${t.origin}`;default:return"Neplatný vstup"}}};function Cf(){return{localeError:H$()}}var W$=()=>{let e={string:{unit:"znakov",verb:"imeti"},file:{unit:"bajtov",verb:"imeti"},array:{unit:"elementov",verb:"imeti"},set:{unit:"elementov",verb:"imeti"},map:{unit:"elementov",verb:"imeti"}};function n(t){return e[t]??null}let i={regex:"vnos",email:"e-poštni naslov",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datum in čas",date:"ISO datum",time:"ISO čas",duration:"ISO trajanje",ipv4:"IPv4 naslov",ipv6:"IPv6 naslov",mac:"MAC naslov",cidrv4:"obseg IPv4",cidrv6:"obseg IPv6",base64:"base64 kodiran niz",base64url:"base64url kodiran niz",json_string:"JSON niz",e164:"E.164 številka",credit_card:"številka kreditne kartice",currency_code:"koda valute",iban:"IBAN",jwt:"JWT",template_literal:"vnos"},o={nan:"NaN",number:"število",array:"tabela"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Neveljaven vnos: pričakovano instanceof ${t.expected}, prejeto ${s}`:`Neveljaven vnos: pričakovano ${r}, prejeto ${s}`}case"invalid_value":return t.values.length===1?`Neveljaven vnos: pričakovano ${$(t.values[0])}`:`Neveljavna možnost: pričakovano eno izmed ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Preveliko: pričakovano, da bo ${t.origin??"vrednost"} imelo ${r}${t.maximum.toString()} ${a.unit??"elementov"}`:`Preveliko: pričakovano, da bo ${t.origin??"vrednost"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Premajhno: pričakovano, da bo ${t.origin} imelo ${r}${t.minimum.toString()} ${a.unit}`:`Premajhno: pričakovano, da bo ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Neveljaven niz: mora se začeti z "${r.prefix}"`:r.format==="ends_with"?`Neveljaven niz: mora se končati z "${r.suffix}"`:r.format==="includes"?`Neveljaven niz: mora vsebovati "${r.includes}"`:r.format==="regex"?`Neveljaven niz: mora ustrezati vzorcu ${r.pattern}`:`Neveljaven ${i[r.format]??t.format}`}case"not_multiple_of":return`Neveljavno število: mora biti večkratnik ${t.divisor}`;case"unrecognized_keys":return`Neprepoznan${t.keys.length>1?"i ključi":" ključ"}: ${v(t.keys,", ")}`;case"invalid_key":return`Neveljaven ključ v ${t.origin}`;case"invalid_union":return"Neveljaven vnos";case"invalid_element":return`Neveljavna vrednost v ${t.origin}`;default:return"Neveljaven vnos"}}};function Lf(){return{localeError:W$()}}var K$=()=>{let e={string:{unit:"tecken",verb:"att ha"},file:{unit:"bytes",verb:"att ha"},array:{unit:"objekt",verb:"att innehålla"},set:{unit:"objekt",verb:"att innehålla"},map:{unit:"objekt",verb:"att innehålla"}};function n(t){return e[t]??null}let i={regex:"reguljärt uttryck",email:"e-postadress",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO-datum och tid",date:"ISO-datum",time:"ISO-tid",duration:"ISO-varaktighet",ipv4:"IPv4-adress",ipv6:"IPv6-adress",mac:"MAC-adress",cidrv4:"IPv4-spektrum",cidrv6:"IPv6-spektrum",base64:"base64-kodad sträng",base64url:"base64url-kodad sträng",json_string:"JSON-sträng",e164:"E.164-nummer",credit_card:"kreditkortsnummer",currency_code:"valutakod",iban:"IBAN",jwt:"JWT",template_literal:"mall-literal"},o={nan:"NaN",number:"antal",array:"lista"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ogiltig inmatning: förväntat instanceof ${t.expected}, fick ${s}`:`Ogiltig inmatning: förväntat ${r}, fick ${s}`}case"invalid_value":return t.values.length===1?`Ogiltig inmatning: förväntat ${$(t.values[0])}`:`Ogiltigt val: förväntade en av ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`För stor(t): förväntade ${t.origin??"värdet"} att ha ${r}${t.maximum.toString()} ${a.unit??"element"}`:`För stor(t): förväntat ${t.origin??"värdet"} att ha ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`För lite(t): förväntade ${t.origin??"värdet"} att ha ${r}${t.minimum.toString()} ${a.unit}`:`För lite(t): förväntade ${t.origin??"värdet"} att ha ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ogiltig sträng: måste börja med "${r.prefix}"`:r.format==="ends_with"?`Ogiltig sträng: måste sluta med "${r.suffix}"`:r.format==="includes"?`Ogiltig sträng: måste innehålla "${r.includes}"`:r.format==="regex"?`Ogiltig sträng: måste matcha mönstret "${r.pattern}"`:`Ogiltig(t) ${i[r.format]??t.format}`}case"not_multiple_of":return`Ogiltigt tal: måste vara en multipel av ${t.divisor}`;case"unrecognized_keys":return`${t.keys.length>1?"Okända nycklar":"Okänd nyckel"}: ${v(t.keys,", ")}`;case"invalid_key":return`Ogiltig nyckel i ${t.origin??"värdet"}`;case"invalid_union":return"Ogiltig input";case"invalid_element":return`Ogiltigt värde i ${t.origin??"värdet"}`;default:return"Ogiltig input"}}};function jf(){return{localeError:K$()}}var J$=()=>{let e={string:{unit:"எழுத்துக்கள்",verb:"கொண்டிருக்க வேண்டும்"},file:{unit:"பைட்டுகள்",verb:"கொண்டிருக்க வேண்டும்"},array:{unit:"உறுப்புகள்",verb:"கொண்டிருக்க வேண்டும்"},set:{unit:"உறுப்புகள்",verb:"கொண்டிருக்க வேண்டும்"},map:{unit:"உறுப்புகள்",verb:"கொண்டிருக்க வேண்டும்"}};function n(t){return e[t]??null}let i={regex:"உள்ளீடு",email:"மின்னஞ்சல் முகவரி",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO தேதி நேரம்",date:"ISO தேதி",time:"ISO நேரம்",duration:"ISO கால அளவு",ipv4:"IPv4 முகவரி",ipv6:"IPv6 முகவரி",mac:"MAC முகவரி",cidrv4:"IPv4 வரம்பு",cidrv6:"IPv6 வரம்பு",base64:"base64-encoded சரம்",base64url:"base64url-encoded சரம்",json_string:"JSON சரம்",e164:"E.164 எண்",credit_card:"கடன் அட்டை எண்",currency_code:"நாணயக் குறியீடு",iban:"IBAN",jwt:"JWT",template_literal:"input"},o={nan:"NaN",number:"எண்",array:"அணி",null:"வெறுமை"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது instanceof ${t.expected}, பெறப்பட்டது ${s}`:`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது ${r}, பெறப்பட்டது ${s}`}case"invalid_value":return t.values.length===1?`தவறான உள்ளீடு: எதிர்பார்க்கப்பட்டது ${$(t.values[0])}`:`தவறான விருப்பம்: எதிர்பார்க்கப்பட்டது ${v(t.values,"|")} இல் ஒன்று`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`மிக பெரியது: எதிர்பார்க்கப்பட்டது ${t.origin??"மதிப்பு"} ${r}${t.maximum.toString()} ${a.unit??"உறுப்புகள்"} ஆக இருக்க வேண்டும்`:`மிக பெரியது: எதிர்பார்க்கப்பட்டது ${t.origin??"மதிப்பு"} ${r}${t.maximum.toString()} ஆக இருக்க வேண்டும்`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`மிகச் சிறியது: எதிர்பார்க்கப்பட்டது ${t.origin} ${r}${t.minimum.toString()} ${a.unit} ஆக இருக்க வேண்டும்`:`மிகச் சிறியது: எதிர்பார்க்கப்பட்டது ${t.origin} ${r}${t.minimum.toString()} ஆக இருக்க வேண்டும்`}case"invalid_format":{let r=t;return r.format==="starts_with"?`தவறான சரம்: "${r.prefix}" இல் தொடங்க வேண்டும்`:r.format==="ends_with"?`தவறான சரம்: "${r.suffix}" இல் முடிவடைய வேண்டும்`:r.format==="includes"?`தவறான சரம்: "${r.includes}" ஐ உள்ளடக்க வேண்டும்`:r.format==="regex"?`தவறான சரம்: ${r.pattern} முறைபாட்டுடன் பொருந்த வேண்டும்`:`தவறான ${i[r.format]??t.format}`}case"not_multiple_of":return`தவறான எண்: ${t.divisor} இன் பலமாக இருக்க வேண்டும்`;case"unrecognized_keys":return`அடையாளம் தெரியாத விசை${t.keys.length>1?"கள்":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} இல் தவறான விசை`;case"invalid_union":return"தவறான உள்ளீடு";case"invalid_element":return`${t.origin} இல் தவறான மதிப்பு`;default:return"தவறான உள்ளீடு"}}};function Uf(){return{localeError:J$()}}var X$=()=>{let e={string:{unit:"аломат",verb:"дошта бошад"},file:{unit:"байт",verb:"дошта бошад"},array:{unit:"унсур",verb:"дошта бошад"},set:{unit:"унсур",verb:"дошта бошад"},map:{unit:"сабт",verb:"дошта бошад"}};function n(t){return e[t]??null}let i={regex:"вуруд",email:"суроғаи email",url:"URL",emoji:"эмоҷи",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"санаву вақти ISO",date:"санаи ISO",time:"вақти ISO",duration:"давомнокии ISO",ipv4:"суроғаи IPv4",ipv6:"суроғаи IPv6",mac:"суроғаи MAC",cidrv4:"маҳдудаи IPv4",cidrv6:"маҳдудаи IPv6",base64:"сатри дар формати base64",base64url:"сатри дар формати base64url",json_string:"сатри JSON",e164:"рақами E.164",credit_card:"рақами корти кредитӣ",currency_code:"рамзи асъор",iban:"IBAN",jwt:"JWT",template_literal:"вуруд"},o={nan:"NaN",number:"рақам",string:"сатр",array:"массив",object:"объект",date:"сана"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`Вуруди нодуруст: ${r} интизор мерафт, ${s} гирифта шуд`}case"invalid_value":return t.values.length===1?`Вуруди нодуруст: ${$(t.values[0])} интизор мерафт`:`Интихоби нодуруст: яке аз ${v(t.values,"|")} интизор мерафт`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Хеле калон: ${t.origin??"қимат"} бояд ${r}${t.maximum.toString()} ${a.unit} ${a.verb}`:`Хеле калон: ${t.origin??"қимат"} бояд ${r}${t.maximum.toString()} бошад`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Хеле хурд: ${t.origin} бояд ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`Хеле хурд: ${t.origin} бояд ${r}${t.minimum.toString()} бошад`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Сатри нодуруст: бояд бо "${r.prefix}" оғоз шавад`:r.format==="ends_with"?`Сатри нодуруст: бояд бо "${r.suffix}" анҷом ёбад`:r.format==="includes"?`Сатри нодуруст: бояд "${r.includes}"-ро дар бар гирад`:r.format==="regex"?`Сатри нодуруст: бояд ба намунаи ${r.pattern} мувофиқат кунад`:`${i[r.format]??t.format}-и нодуруст`}case"not_multiple_of":return`Рақами нодуруст: бояд ба ${t.divisor} бе бақия тақсим шавад`;case"unrecognized_keys":return`Калид${t.keys.length>1?"ҳои":"и"} номаълум: ${v(t.keys,", ")}`;case"invalid_key":return`Калиди нодуруст дар ${t.origin}`;case"invalid_union":return t.options&&Array.isArray(t.options)&&t.options.length>0?`Қимати нодурусти дискриминатор: ${t.options.map(a=>`'${a}'`).join(" | ")} интизор мерафт`:"Вуруди нодуруст";case"invalid_element":return`Қимати нодуруст дар ${t.origin}`;default:return"Вуруди нодуруст"}}};function Rf(){return{localeError:X$()}}var q$=()=>{let e={string:{unit:"ตัวอักษร",verb:"ควรมี"},file:{unit:"ไบต์",verb:"ควรมี"},array:{unit:"รายการ",verb:"ควรมี"},set:{unit:"รายการ",verb:"ควรมี"},map:{unit:"รายการ",verb:"ควรมี"}};function n(t){return e[t]??null}let i={regex:"ข้อมูลที่ป้อน",email:"ที่อยู่อีเมล",url:"URL",emoji:"อิโมจิ",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"วันที่เวลาแบบ ISO",date:"วันที่แบบ ISO",time:"เวลาแบบ ISO",duration:"ช่วงเวลาแบบ ISO",ipv4:"ที่อยู่ IPv4",ipv6:"ที่อยู่ IPv6",mac:"ที่อยู่ MAC",cidrv4:"ช่วง IP แบบ IPv4",cidrv6:"ช่วง IP แบบ IPv6",base64:"ข้อความแบบ Base64",base64url:"ข้อความแบบ Base64 สำหรับ URL",json_string:"ข้อความแบบ JSON",e164:"เบอร์โทรศัพท์ระหว่างประเทศ (E.164)",credit_card:"หมายเลขบัตรเครดิต",currency_code:"รหัสสกุลเงิน",iban:"IBAN",jwt:"โทเคน JWT",template_literal:"ข้อมูลที่ป้อน"},o={nan:"NaN",number:"ตัวเลข",array:"อาร์เรย์ (Array)",null:"ไม่มีค่า (null)"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น instanceof ${t.expected} แต่ได้รับ ${s}`:`ประเภทข้อมูลไม่ถูกต้อง: ควรเป็น ${r} แต่ได้รับ ${s}`}case"invalid_value":return t.values.length===1?`ค่าไม่ถูกต้อง: ควรเป็น ${$(t.values[0])}`:`ตัวเลือกไม่ถูกต้อง: ควรเป็นหนึ่งใน ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"ไม่เกิน":"น้อยกว่า",a=n(t.origin);return a?`เกินกำหนด: ${t.origin??"ค่า"} ควรมี${r} ${t.maximum.toString()} ${a.unit??"รายการ"}`:`เกินกำหนด: ${t.origin??"ค่า"} ควรมี${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?"อย่างน้อย":"มากกว่า",a=n(t.origin);return a?`น้อยกว่ากำหนด: ${t.origin} ควรมี${r} ${t.minimum.toString()} ${a.unit}`:`น้อยกว่ากำหนด: ${t.origin} ควรมี${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`รูปแบบไม่ถูกต้อง: ข้อความต้องขึ้นต้นด้วย "${r.prefix}"`:r.format==="ends_with"?`รูปแบบไม่ถูกต้อง: ข้อความต้องลงท้ายด้วย "${r.suffix}"`:r.format==="includes"?`รูปแบบไม่ถูกต้อง: ข้อความต้องมี "${r.includes}" อยู่ในข้อความ`:r.format==="regex"?`รูปแบบไม่ถูกต้อง: ต้องตรงกับรูปแบบที่กำหนด ${r.pattern}`:`รูปแบบไม่ถูกต้อง: ${i[r.format]??t.format}`}case"not_multiple_of":return`ตัวเลขไม่ถูกต้อง: ต้องเป็นจำนวนที่หารด้วย ${t.divisor} ได้ลงตัว`;case"unrecognized_keys":return`พบคีย์ที่ไม่รู้จัก: ${v(t.keys,", ")}`;case"invalid_key":return`คีย์ไม่ถูกต้องใน ${t.origin}`;case"invalid_union":return"ข้อมูลไม่ถูกต้อง: ไม่ตรงกับรูปแบบยูเนียนที่กำหนดไว้";case"invalid_element":return`ข้อมูลไม่ถูกต้องใน ${t.origin}`;default:return"ข้อมูลไม่ถูกต้อง"}}};function Mf(){return{localeError:q$()}}var Y$=()=>{let e={string:{unit:"simwol",verb:"bolmaly"},file:{unit:"baýt",verb:"bolmaly"},array:{unit:"elementler",verb:"bolmaly"},set:{unit:"elementler",verb:"bolmaly"},map:{unit:"elementler",verb:"bolmaly"}};function n(t){return e[t]??null}let i={regex:"giriş",email:"e-poçta salgysy",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO sene we wagt",date:"ISO sene",time:"ISO wagt",duration:"ISO wagt aralygy",ipv4:"IPv4 salgysy",ipv6:"IPv6 salgysy",mac:"MAC salgysy",cidrv4:"IPv4 aralygy",cidrv6:"IPv6 aralygy",base64:"base64 bilen şifrlenen setir",base64url:"base64url bilen şifrlenen setir",json_string:"JSON setiri",e164:"E.164 nomeri",credit_card:"kredit kartynyň nomeri",currency_code:"walýuta kody",iban:"IBAN",jwt:"JWT",template_literal:"şablon"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return`Nädogry baha: garaşylan ${r} ýerine ${s} alyndy`}case"invalid_value":return t.values.length===1?`Nädogry baha: ${$(t.values[0])} bolmaly`:`Nädogry saýlaw: aşakdakylardan biri bolmaly: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Has uly: garaşylýan ${t.origin??"baha"} ${r} ${t.maximum.toString()} ${a.unit??"element"}`:`Has uly: garaşylýan ${t.origin??"baha"} ${r} ${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Has kiçi: garaşylýan ${t.origin} ${r} ${t.minimum.toString()} ${a.unit}`:`Has kiçi: garaşylýan ${t.origin} ${r} ${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Nädogry setir: "${r.prefix}" bilen başlamaly`:r.format==="ends_with"?`Nädogry setir: "${r.suffix}" bilen gutarmaly`:r.format==="includes"?`Nädogry setir: "${r.includes}" saklamaly`:r.format==="regex"?`Nädogry setir: ${r.pattern} nusga laýyk bolmaly`:`Nädogry ${i[r.format]??t.format}`}case"not_multiple_of":return`Nädogry san: ${t.divisor} bilen galyndysyz bölünmeli`;case"unrecognized_keys":return`Tanalmaýan açar${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} içinde nädogry açar`;case"invalid_union":return"Nädogry baha";case"invalid_element":return`${t.origin} içinde nädogry baha`;default:return"Nädogry baha"}}};function Zf(){return{localeError:Y$()}}var Q$=()=>{let e={string:{unit:"karakter",verb:"olmalı"},file:{unit:"bayt",verb:"olmalı"},array:{unit:"öğe",verb:"olmalı"},set:{unit:"öğe",verb:"olmalı"},map:{unit:"öğe",verb:"olmalı"}};function n(t){return e[t]??null}let i={regex:"girdi",email:"e-posta adresi",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO tarih ve saat",date:"ISO tarih",time:"ISO saat",duration:"ISO süre",ipv4:"IPv4 adresi",ipv6:"IPv6 adresi",mac:"MAC adresi",cidrv4:"IPv4 aralığı",cidrv6:"IPv6 aralığı",base64:"base64 ile şifrelenmiş metin",base64url:"base64url ile şifrelenmiş metin",json_string:"JSON dizesi",e164:"E.164 sayısı",credit_card:"kredi kartı numarası",currency_code:"para birimi kodu",iban:"IBAN",jwt:"JWT",template_literal:"Şablon dizesi"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Geçersiz değer: beklenen instanceof ${t.expected}, alınan ${s}`:`Geçersiz değer: beklenen ${r}, alınan ${s}`}case"invalid_value":return t.values.length===1?`Geçersiz değer: beklenen ${$(t.values[0])}`:`Geçersiz seçenek: aşağıdakilerden biri olmalı: ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Çok büyük: beklenen ${t.origin??"değer"} ${r}${t.maximum.toString()} ${a.unit??"öğe"}`:`Çok büyük: beklenen ${t.origin??"değer"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Çok küçük: beklenen ${t.origin} ${r}${t.minimum.toString()} ${a.unit}`:`Çok küçük: beklenen ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Geçersiz metin: "${r.prefix}" ile başlamalı`:r.format==="ends_with"?`Geçersiz metin: "${r.suffix}" ile bitmeli`:r.format==="includes"?`Geçersiz metin: "${r.includes}" içermeli`:r.format==="regex"?`Geçersiz metin: ${r.pattern} desenine uymalı`:`Geçersiz ${i[r.format]??t.format}`}case"not_multiple_of":return`Geçersiz sayı: ${t.divisor} ile tam bölünebilmeli`;case"unrecognized_keys":return`Tanınmayan anahtar${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} içinde geçersiz anahtar`;case"invalid_union":return"Geçersiz değer";case"invalid_element":return`${t.origin} içinde geçersiz değer`;default:return"Geçersiz değer"}}};function Ff(){return{localeError:Q$()}}var ek=()=>{let e={string:{unit:"символів",verb:"матиме"},file:{unit:"байтів",verb:"матиме"},array:{unit:"елементів",verb:"матиме"},set:{unit:"елементів",verb:"матиме"},map:{unit:"елементів",verb:"матиме"}};function n(t){return e[t]??null}let i={regex:"вхідні дані",email:"адреса електронної пошти",url:"URL",emoji:"емодзі",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"дата та час ISO",date:"дата ISO",time:"час ISO",duration:"тривалість ISO",ipv4:"адреса IPv4",ipv6:"адреса IPv6",mac:"адреса MAC",cidrv4:"діапазон IPv4",cidrv6:"діапазон IPv6",base64:"рядок у кодуванні base64",base64url:"рядок у кодуванні base64url",json_string:"рядок JSON",e164:"номер E.164",credit_card:"номер кредитної картки",currency_code:"код валюти",iban:"IBAN",jwt:"JWT",template_literal:"вхідні дані"},o={nan:"NaN",number:"число",array:"масив"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Неправильні вхідні дані: очікується instanceof ${t.expected}, отримано ${s}`:`Неправильні вхідні дані: очікується ${r}, отримано ${s}`}case"invalid_value":return t.values.length===1?`Неправильні вхідні дані: очікується ${$(t.values[0])}`:`Неправильна опція: очікується одне з ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Занадто велике: очікується, що ${t.origin??"значення"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"елементів"}`:`Занадто велике: очікується, що ${t.origin??"значення"} буде ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Занадто мале: очікується, що ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Занадто мале: очікується, що ${t.origin} буде ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Неправильний рядок: повинен починатися з "${r.prefix}"`:r.format==="ends_with"?`Неправильний рядок: повинен закінчуватися на "${r.suffix}"`:r.format==="includes"?`Неправильний рядок: повинен містити "${r.includes}"`:r.format==="regex"?`Неправильний рядок: повинен відповідати шаблону ${r.pattern}`:`Неправильний ${i[r.format]??t.format}`}case"not_multiple_of":return`Неправильне число: повинно бути кратним ${t.divisor}`;case"unrecognized_keys":return`Нерозпізнаний ключ${t.keys.length>1?"і":""}: ${v(t.keys,", ")}`;case"invalid_key":return`Неправильний ключ у ${t.origin}`;case"invalid_union":return"Неправильні вхідні дані";case"invalid_element":return`Неправильне значення у ${t.origin}`;default:return"Неправильні вхідні дані"}}};function qi(){return{localeError:ek()}}function Vf(){return qi()}var tk=()=>{let e={string:{unit:"حروف",verb:"ہونا"},file:{unit:"بائٹس",verb:"ہونا"},array:{unit:"آئٹمز",verb:"ہونا"},set:{unit:"آئٹمز",verb:"ہونا"},map:{unit:"آئٹمز",verb:"ہونا"}};function n(t){return e[t]??null}let i={regex:"ان پٹ",email:"ای میل ایڈریس",url:"یو آر ایل",emoji:"ایموجی",uuid:"یو یو آئی ڈی",uuidv4:"یو یو آئی ڈی وی 4",uuidv6:"یو یو آئی ڈی وی 6",nanoid:"نینو آئی ڈی",guid:"جی یو آئی ڈی",cuid:"سی یو آئی ڈی",cuid2:"سی یو آئی ڈی 2",ulid:"یو ایل آئی ڈی",xid:"ایکس آئی ڈی",ksuid:"کے ایس یو آئی ڈی",datetime:"آئی ایس او ڈیٹ ٹائم",date:"آئی ایس او تاریخ",time:"آئی ایس او وقت",duration:"آئی ایس او مدت",ipv4:"آئی پی وی 4 ایڈریس",ipv6:"آئی پی وی 6 ایڈریس",mac:"ایم اے سی ایڈریس",cidrv4:"آئی پی وی 4 رینج",cidrv6:"آئی پی وی 6 رینج",base64:"بیس 64 ان کوڈڈ سٹرنگ",base64url:"بیس 64 یو آر ایل ان کوڈڈ سٹرنگ",json_string:"جے ایس او این سٹرنگ",e164:"ای 164 نمبر",credit_card:"کریڈٹ کارڈ نمبر",currency_code:"کرنسی کوڈ",iban:"IBAN",jwt:"جے ڈبلیو ٹی",template_literal:"ان پٹ"},o={nan:"NaN",number:"نمبر",array:"آرے",null:"نل"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`غلط ان پٹ: instanceof ${t.expected} متوقع تھا، ${s} موصول ہوا`:`غلط ان پٹ: ${r} متوقع تھا، ${s} موصول ہوا`}case"invalid_value":return t.values.length===1?`غلط ان پٹ: ${$(t.values[0])} متوقع تھا`:`غلط آپشن: ${v(t.values,"|")} میں سے ایک متوقع تھا`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`بہت بڑا: ${t.origin??"ویلیو"} کے ${r}${t.maximum.toString()} ${a.unit??"عناصر"} ہونے متوقع تھے`:`بہت بڑا: ${t.origin??"ویلیو"} کا ${r}${t.maximum.toString()} ہونا متوقع تھا`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`بہت چھوٹا: ${t.origin} کے ${r}${t.minimum.toString()} ${a.unit} ہونے متوقع تھے`:`بہت چھوٹا: ${t.origin} کا ${r}${t.minimum.toString()} ہونا متوقع تھا`}case"invalid_format":{let r=t;return r.format==="starts_with"?`غلط سٹرنگ: "${r.prefix}" سے شروع ہونا چاہیے`:r.format==="ends_with"?`غلط سٹرنگ: "${r.suffix}" پر ختم ہونا چاہیے`:r.format==="includes"?`غلط سٹرنگ: "${r.includes}" شامل ہونا چاہیے`:r.format==="regex"?`غلط سٹرنگ: پیٹرن ${r.pattern} سے میچ ہونا چاہیے`:`غلط ${i[r.format]??t.format}`}case"not_multiple_of":return`غلط نمبر: ${t.divisor} کا مضاعف ہونا چاہیے`;case"unrecognized_keys":return`غیر تسلیم شدہ کی${t.keys.length>1?"ز":""}: ${v(t.keys,"، ")}`;case"invalid_key":return`${t.origin} میں غلط کی`;case"invalid_union":return"غلط ان پٹ";case"invalid_element":return`${t.origin} میں غلط ویلیو`;default:return"غلط ان پٹ"}}};function Gf(){return{localeError:tk()}}var nk=()=>{let e={string:{unit:"belgi",verb:"bo‘lishi kerak"},file:{unit:"bayt",verb:"bo‘lishi kerak"},array:{unit:"element",verb:"bo‘lishi kerak"},set:{unit:"element",verb:"bo‘lishi kerak"},map:{unit:"yozuv",verb:"bo‘lishi kerak"}};function n(t){return e[t]??null}let i={regex:"kirish",email:"elektron pochta manzili",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO sana va vaqti",date:"ISO sana",time:"ISO vaqt",duration:"ISO davomiylik",ipv4:"IPv4 manzil",ipv6:"IPv6 manzil",mac:"MAC manzil",cidrv4:"IPv4 diapazon",cidrv6:"IPv6 diapazon",base64:"base64 kodlangan satr",base64url:"base64url kodlangan satr",json_string:"JSON satr",e164:"E.164 raqam",credit_card:"kredit karta raqami",currency_code:"valyuta kodi",iban:"IBAN",jwt:"JWT",template_literal:"kirish"},o={nan:"NaN",number:"raqam",array:"massiv"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Noto‘g‘ri kirish: kutilgan instanceof ${t.expected}, qabul qilingan ${s}`:`Noto‘g‘ri kirish: kutilgan ${r}, qabul qilingan ${s}`}case"invalid_value":return t.values.length===1?`Noto‘g‘ri kirish: kutilgan ${$(t.values[0])}`:`Noto‘g‘ri variant: quyidagilardan biri kutilgan ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Juda katta: kutilgan ${t.origin??"qiymat"} ${r}${t.maximum.toString()} ${a.unit} ${a.verb}`:`Juda katta: kutilgan ${t.origin??"qiymat"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Juda kichik: kutilgan ${t.origin} ${r}${t.minimum.toString()} ${a.unit} ${a.verb}`:`Juda kichik: kutilgan ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Noto‘g‘ri satr: "${r.prefix}" bilan boshlanishi kerak`:r.format==="ends_with"?`Noto‘g‘ri satr: "${r.suffix}" bilan tugashi kerak`:r.format==="includes"?`Noto‘g‘ri satr: "${r.includes}" ni o‘z ichiga olishi kerak`:r.format==="regex"?`Noto‘g‘ri satr: ${r.pattern} shabloniga mos kelishi kerak`:`Noto‘g‘ri ${i[r.format]??t.format}`}case"not_multiple_of":return`Noto‘g‘ri raqam: ${t.divisor} ning karralisi bo‘lishi kerak`;case"unrecognized_keys":return`Noma’lum kalit${t.keys.length>1?"lar":""}: ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} dagi kalit noto‘g‘ri`;case"invalid_union":return"Noto‘g‘ri kirish";case"invalid_element":return`${t.origin} da noto‘g‘ri qiymat`;default:return"Noto‘g‘ri kirish"}}};function Bf(){return{localeError:nk()}}var rk=()=>{let e={string:{unit:"ký tự",verb:"có"},file:{unit:"byte",verb:"có"},array:{unit:"phần tử",verb:"có"},set:{unit:"phần tử",verb:"có"},map:{unit:"phần tử",verb:"có"}};function n(t){return e[t]??null}let i={regex:"đầu vào",email:"địa chỉ email",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ngày giờ ISO",date:"ngày ISO",time:"giờ ISO",duration:"khoảng thời gian ISO",ipv4:"địa chỉ IPv4",ipv6:"địa chỉ IPv6",mac:"địa chỉ MAC",cidrv4:"dải IPv4",cidrv6:"dải IPv6",base64:"chuỗi mã hóa base64",base64url:"chuỗi mã hóa base64url",json_string:"chuỗi JSON",e164:"số E.164",credit_card:"số thẻ tín dụng",currency_code:"mã tiền tệ",iban:"IBAN",jwt:"JWT",template_literal:"đầu vào"},o={nan:"NaN",number:"số",array:"mảng"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Đầu vào không hợp lệ: mong đợi instanceof ${t.expected}, nhận được ${s}`:`Đầu vào không hợp lệ: mong đợi ${r}, nhận được ${s}`}case"invalid_value":return t.values.length===1?`Đầu vào không hợp lệ: mong đợi ${$(t.values[0])}`:`Tùy chọn không hợp lệ: mong đợi một trong các giá trị ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Quá lớn: mong đợi ${t.origin??"giá trị"} ${a.verb} ${r}${t.maximum.toString()} ${a.unit??"phần tử"}`:`Quá lớn: mong đợi ${t.origin??"giá trị"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Quá nhỏ: mong đợi ${t.origin} ${a.verb} ${r}${t.minimum.toString()} ${a.unit}`:`Quá nhỏ: mong đợi ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Chuỗi không hợp lệ: phải bắt đầu bằng "${r.prefix}"`:r.format==="ends_with"?`Chuỗi không hợp lệ: phải kết thúc bằng "${r.suffix}"`:r.format==="includes"?`Chuỗi không hợp lệ: phải bao gồm "${r.includes}"`:r.format==="regex"?`Chuỗi không hợp lệ: phải khớp với mẫu ${r.pattern}`:`${i[r.format]??t.format} không hợp lệ`}case"not_multiple_of":return`Số không hợp lệ: phải là bội số của ${t.divisor}`;case"unrecognized_keys":return`Khóa không được nhận dạng: ${v(t.keys,", ")}`;case"invalid_key":return`Khóa không hợp lệ trong ${t.origin}`;case"invalid_union":return"Đầu vào không hợp lệ";case"invalid_element":return`Giá trị không hợp lệ trong ${t.origin}`;default:return"Đầu vào không hợp lệ"}}};function Hf(){return{localeError:rk()}}var ik=()=>{let e={string:{unit:"字符",verb:"包含"},file:{unit:"字节",verb:"包含"},array:{unit:"项",verb:"包含"},set:{unit:"项",verb:"包含"},map:{unit:"项",verb:"包含"}};function n(t){return e[t]??null}let i={regex:"输入",email:"电子邮件",url:"URL",emoji:"表情符号",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO日期时间",date:"ISO日期",time:"ISO时间",duration:"ISO时长",ipv4:"IPv4地址",ipv6:"IPv6地址",mac:"MAC地址",cidrv4:"IPv4网段",cidrv6:"IPv6网段",base64:"base64编码字符串",base64url:"base64url编码字符串",json_string:"JSON字符串",e164:"E.164号码",credit_card:"信用卡号",currency_code:"货币代码",iban:"IBAN",jwt:"JWT",template_literal:"输入"},o={nan:"NaN",number:"数字",array:"数组",null:"空值(null)"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`无效输入:期望 instanceof ${t.expected},实际接收 ${s}`:`无效输入:期望 ${r},实际接收 ${s}`}case"invalid_value":return t.values.length===1?`无效输入:期望 ${$(t.values[0])}`:`无效选项:期望以下之一 ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`数值过大:期望 ${t.origin??"值"} ${r}${t.maximum.toString()} ${a.unit??"个元素"}`:`数值过大:期望 ${t.origin??"值"} ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`数值过小:期望 ${t.origin} ${r}${t.minimum.toString()} ${a.unit}`:`数值过小:期望 ${t.origin} ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`无效字符串:必须以 "${r.prefix}" 开头`:r.format==="ends_with"?`无效字符串:必须以 "${r.suffix}" 结尾`:r.format==="includes"?`无效字符串:必须包含 "${r.includes}"`:r.format==="regex"?`无效字符串:必须满足正则表达式 ${r.pattern}`:`无效${i[r.format]??t.format}`}case"not_multiple_of":return`无效数字:必须是 ${t.divisor} 的倍数`;case"unrecognized_keys":return`出现未知的键(key): ${v(t.keys,", ")}`;case"invalid_key":return`${t.origin} 中的键(key)无效`;case"invalid_union":return"无效输入";case"invalid_element":return`${t.origin} 中包含无效值(value)`;default:return"无效输入"}}};function Wf(){return{localeError:ik()}}var ok=()=>{let e={string:{unit:"字元",verb:"擁有"},file:{unit:"位元組",verb:"擁有"},array:{unit:"項目",verb:"擁有"},set:{unit:"項目",verb:"擁有"},map:{unit:"項目",verb:"擁有"}};function n(t){return e[t]??null}let i={regex:"輸入",email:"郵件地址",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO 日期時間",date:"ISO 日期",time:"ISO 時間",duration:"ISO 期間",ipv4:"IPv4 位址",ipv6:"IPv6 位址",mac:"MAC 位址",cidrv4:"IPv4 範圍",cidrv6:"IPv6 範圍",base64:"base64 編碼字串",base64url:"base64url 編碼字串",json_string:"JSON 字串",e164:"E.164 數值",credit_card:"信用卡號",currency_code:"貨幣代碼",iban:"IBAN",jwt:"JWT",template_literal:"輸入"},o={nan:"NaN"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`無效的輸入值:預期為 instanceof ${t.expected},但收到 ${s}`:`無效的輸入值:預期為 ${r},但收到 ${s}`}case"invalid_value":return t.values.length===1?`無效的輸入值:預期為 ${$(t.values[0])}`:`無效的選項:預期為以下其中之一 ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`數值過大:預期 ${t.origin??"值"} 應為 ${r}${t.maximum.toString()} ${a.unit??"個元素"}`:`數值過大:預期 ${t.origin??"值"} 應為 ${r}${t.maximum.toString()}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`數值過小:預期 ${t.origin} 應為 ${r}${t.minimum.toString()} ${a.unit}`:`數值過小:預期 ${t.origin} 應為 ${r}${t.minimum.toString()}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`無效的字串:必須以 "${r.prefix}" 開頭`:r.format==="ends_with"?`無效的字串:必須以 "${r.suffix}" 結尾`:r.format==="includes"?`無效的字串:必須包含 "${r.includes}"`:r.format==="regex"?`無效的字串:必須符合格式 ${r.pattern}`:`無效的 ${i[r.format]??t.format}`}case"not_multiple_of":return`無效的數字:必須為 ${t.divisor} 的倍數`;case"unrecognized_keys":return`無法識別的鍵值${t.keys.length>1?"們":""}:${v(t.keys,"、")}`;case"invalid_key":return`${t.origin} 中有無效的鍵值`;case"invalid_union":return"無效的輸入值";case"invalid_element":return`${t.origin} 中有無效的值`;default:return"無效的輸入值"}}};function Kf(){return{localeError:ok()}}var ak=()=>{let e={string:{unit:"àmi",verb:"ní"},file:{unit:"bytes",verb:"ní"},array:{unit:"nkan",verb:"ní"},set:{unit:"nkan",verb:"ní"},map:{unit:"nkan",verb:"ní"}};function n(t){return e[t]??null}let i={regex:"ẹ̀rọ ìbáwọlé",email:"àdírẹ́sì ìmẹ́lì",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"àkókò ISO",date:"ọjọ́ ISO",time:"àkókò ISO",duration:"àkókò tó pé ISO",ipv4:"àdírẹ́sì IPv4",ipv6:"àdírẹ́sì IPv6",mac:"àdírẹ́sì MAC",cidrv4:"àgbègbè IPv4",cidrv6:"àgbègbè IPv6",base64:"ọ̀rọ̀ tí a kọ́ ní base64",base64url:"ọ̀rọ̀ base64url",json_string:"ọ̀rọ̀ JSON",e164:"nọ́mbà E.164",credit_card:"nọmba kaadi gbese",currency_code:"koodu owó",iban:"IBAN",jwt:"JWT",template_literal:"ẹ̀rọ ìbáwọlé"},o={nan:"NaN",number:"nọ́mbà",array:"akopọ"};return t=>{switch(t.code){case"invalid_type":{let r=o[t.expected]??t.expected,a=k(t.input),s=o[a]??a;return/^[A-Z]/.test(t.expected)?`Ìbáwọlé aṣìṣe: a ní láti fi instanceof ${t.expected}, àmọ̀ a rí ${s}`:`Ìbáwọlé aṣìṣe: a ní láti fi ${r}, àmọ̀ a rí ${s}`}case"invalid_value":return t.values.length===1?`Ìbáwọlé aṣìṣe: a ní láti fi ${$(t.values[0])}`:`Àṣàyàn aṣìṣe: yan ọ̀kan lára ${v(t.values,"|")}`;case"too_big":{let r=t.inclusive?"<=":"<",a=n(t.origin);return a?`Tó pọ̀ jù: a ní láti jẹ́ pé ${t.origin??"iye"} ${a.verb} ${r}${t.maximum} ${a.unit}`:`Tó pọ̀ jù: a ní láti jẹ́ ${r}${t.maximum}`}case"too_small":{let r=t.inclusive?">=":">",a=n(t.origin);return a?`Kéré ju: a ní láti jẹ́ pé ${t.origin} ${a.verb} ${r}${t.minimum} ${a.unit}`:`Kéré ju: a ní láti jẹ́ ${r}${t.minimum}`}case"invalid_format":{let r=t;return r.format==="starts_with"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ bẹ̀rẹ̀ pẹ̀lú "${r.prefix}"`:r.format==="ends_with"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ parí pẹ̀lú "${r.suffix}"`:r.format==="includes"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ ní "${r.includes}"`:r.format==="regex"?`Ọ̀rọ̀ aṣìṣe: gbọ́dọ̀ bá àpẹẹrẹ mu ${r.pattern}`:`Aṣìṣe: ${i[r.format]??t.format}`}case"not_multiple_of":return`Nọ́mbà aṣìṣe: gbọ́dọ̀ jẹ́ èyà pípín ti ${t.divisor}`;case"unrecognized_keys":return`Bọtìnì àìmọ̀: ${v(t.keys,", ")}`;case"invalid_key":return`Bọtìnì aṣìṣe nínú ${t.origin}`;case"invalid_union":return"Ìbáwọlé aṣìṣe";case"invalid_element":return`Iye aṣìṣe nínú ${t.origin}`;default:return"Ìbáwọlé aṣìṣe"}}};function Jf(){return{localeError:ak()}}var Xf,_c=Symbol("ZodOutput"),$c=Symbol("ZodInput"),Yi=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(n,...i){let o=i[0];return this._map.set(n,o),o&&typeof o=="object"&&"id"in o&&this._idmap.set(o.id,n),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(n){let i=this._map.get(n);return i&&typeof i=="object"&&"id"in i&&this._idmap.delete(i.id),this._map.delete(n),this}get(n){let i=n._zod.parent;if(i){let o={...this.get(i)??{}};delete o.id;let t={...o,...this._map.get(n)};return Object.keys(t).length?t:void 0}return this._map.get(n)}has(n){return this._map.has(n)}};function Qi(){return new Yi}(Xf=globalThis).__zod_globalRegistry??(Xf.__zod_globalRegistry=Qi());var ue=globalThis.__zod_globalRegistry;var fe=Symbol.for("zod.compile.invalid"),qf=Symbol.for("zod.compile.fallback"),De=class extends Error{constructor(n="z.compile does not support async refinements, transforms, or checks"){super(n),this.name="ZodCompileAsyncError"}},F=class extends Error{constructor(n,i=!0){super(`z.compile does not support ${n}; this schema must use the runtime parser`),this.name="ZodCompileUnsupportedError",this.islandable=i}};function uk(e,n){try{return to(e,{assertOnly:!0})}catch{return n}}function Ic(e,n){try{let i=to(e),o=eo(e,i);return o._zod.bag.validator=uk(e,i),o}catch(i){if(n?.strict)throw i;return e}}function eo(e,n){if(Wi(e))throw new F("a schema whose subtree contains a reference cycle");let i=W(e),o=e._zod.run,t=o.__originalRun??o,r=(a,s)=>{if(s?.async||s?.direction==="backward"||s?.skipChecks||s?.[qf]||s&&Ki(s,a.value))return t(a,s);let u=n(a.value);return u!==fe?(a.value=u,a):(s&&(s[qf]=!0),t(a,s))};return r.__originalRun=t,i._zod.bag.fallbackRun=t,i._zod.bag.validator=n,i._zod.run=r,o.__originalRun||ck(i,e,n),i}function ck(e,n,i){let o=e,t=n;if(typeof t.safeParse=="function"){let r=t.safeParse;o.safeParse=(a,s)=>{let u=i(a);return u!==fe?{success:!0,data:u}:r(a,s)}}if(typeof t.parse=="function"){let r=t.parse;o.parse=(a,s)=>{let u=i(a);return u!==fe?u:r(a,s)}}}function to(e,n){let i=!0;try{i=Wi(e)}catch{}if(i)throw new F("a schema whose subtree contains a reference cycle");let o={constants:new Map,constantCounter:0,varCounter:0,definite:!0},t=new It(["input"]),r=Q(t,o,e,"input",!n?.assertOnly);t.write(r===null?"return true;":`return ${r};`);let a=["INVALID",...o.constants.keys()],s=[fe,...o.constants.values()],u=t.content.join(` +`),c=n?.debug?a.length>0?`// Constants: ${a.join(", ")} +${u}`:u:"",d=Function,p=`return (input) => { +${u} +}`,m;try{m=new d(...a,p)(...s)}catch(f){throw new F(`this schema (generated code failed to evaluate: ${f.message})`)}return n?.debug&&(m.code=c),m.definite=o.definite,m}function j(e,n){for(let[o,t]of e.constants)if(t===n)return o;let i=`c${e.constantCounter++}`;return e.constants.set(i,n),i}function Tt(e,n){return e.definite=!1,j(e,n)}function z(e){return`v${e.varCounter++}`}function lk(e,n){let i=e._zod.run({value:n,issues:[]},{});if(i&&typeof i.then=="function")return fe;let o=i;return o.issues.length===0?o.value:fe}function le(e,n,i,o,t=!0){let r=e.content.length,a=n.constants.size,s=n.constantCounter,u=n.varCounter;try{return Q(e,n,i,o,t)}catch(c){if(!(c instanceof F)||!c.islandable)throw c;if(e.content.length=r,n.constants.size>a){let d=Array.from(n.constants.keys()).slice(a);for(let p of d)n.constants.delete(p)}return n.constantCounter=s,n.varCounter=u,dk(e,n,i,o)}}function dk(e,n,i,o){n.definite=!1;let t=j(n,i),r=j(n,lk),a=z(n);return e.write(`const ${a} = ${r}(${t}, ${o});`),e.write(`if (${a} === INVALID) return INVALID;`),a}var pk=new Set(["max_size","min_size","size_equals","max_length","min_length","length_equals"]);function mk(e,n,i,o){let t=i._zod.def.checks;if(!t||t.length===0)return o;let r=o;for(let a of t){let s=a._zod.def;if(s.when&&!pk.has(s.check))throw new F('check with a custom "when" condition');switch(s.check){case"greater_than":fk(e,n,s,r);break;case"less_than":gk(e,n,s,r);break;case"multiple_of":hk(e,n,s,r);break;case"number_format":rg(e,s,r);break;case"min_length":{let u=at(s.minimum,"min_length"),c=kc(e,n,r,`${r}.length >= ${u} && ${r}.length < ${s.minimum*2}`);e.write(`if (${c} < ${u}) return INVALID;`);break}case"max_length":{let u=at(s.maximum,"max_length"),c=kc(e,n,r,`${r}.length > ${u}`);e.write(`if (${c} > ${u}) return INVALID;`);break}case"length_equals":{let u=at(s.length,"length_equals"),c=kc(e,n,r,`${r}.length >= ${u} && ${r}.length <= ${s.length*2}`);e.write(`if (${c} !== ${u}) return INVALID;`);break}case"min_size":e.write(`if (${r}.size < ${at(s.minimum,"min_size")}) return INVALID;`);break;case"max_size":e.write(`if (${r}.size > ${at(s.maximum,"max_size")}) return INVALID;`);break;case"size_equals":e.write(`if (${r}.size !== ${at(s.size,"size_equals")}) return INVALID;`);break;case"string_format":r=ig(e,n,s,r);break;case"custom":r=kk(e,n,a,r);break;case"bigint_format":vk(e,s,r);break;case"mime_type":yk(e,n,s,r);break;case"property":_k(e,n,s,r);break;case"properties":bk(e,n,s,r);break;case"overwrite":{let u=z(n);$k(e,n,a,r,u),r=u;break}default:throw new F(`check type ${s.check}`)}}return r}function kc(e,n,i,o){let t=j(n,xt),r=z(n);return e.write(`const ${r} = typeof ${i} === "string" && ${o} ? ${t}(${i}) : ${i}.length;`),r}function at(e,n){if(typeof e!="number"||!Number.isFinite(e))throw new F(`${n} bound of type ${typeof e}`);return`${e}`}function ng(e,n){if(typeof n=="bigint")return`${n}n`;if(typeof n=="number"){if(Number.isNaN(n))throw new F("comparison check with NaN bound");return`${n}`}if(n instanceof Date){if(Number.isNaN(n.getTime()))throw new F("comparison check with Invalid Date bound");return j(e,n)}throw new F(`comparison check bound of type ${typeof n}`)}function fk(e,n,i,o){let t=i.inclusive?"<":"<=";e.write(`if (${o} ${t} ${ng(n,i.value)}) return INVALID;`)}function gk(e,n,i,o){let t=i.inclusive?">":">=";e.write(`if (${o} ${t} ${ng(n,i.value)}) return INVALID;`)}function hk(e,n,i,o){if(typeof i.value=="bigint"){if(i.value===BigInt(0))throw new F("multiple_of check with a zero divisor");e.write(`if (${o} % ${i.value}n !== 0n) return INVALID;`)}else{let t=j(n,Gn);e.write(`if (${t}(${o}, ${at(i.value,"multiple_of")}) !== 0) return INVALID;`)}}function rg(e,n,i){let o=n.format;switch(o){case"safeint":e.write(`if (!Number.isSafeInteger(${i})) return INVALID;`);break;case"int32":e.write(`if (!Number.isInteger(${i}) || ${i} < -2147483648 || ${i} > 2147483647) return INVALID;`);break;case"uint32":e.write(`if (!Number.isInteger(${i}) || ${i} < 0 || ${i} > 4294967295) return INVALID;`);break;case"float32":e.write(`if (!Number.isFinite(${i}) || ${i} < -3.4028234663852886e38 || ${i} > 3.4028234663852886e38) return INVALID;`);break;case"float64":e.write(`if (!Number.isFinite(${i})) return INVALID;`);break;default:throw new F(`number format ${o}`)}}function vk(e,n,i){let o=n.format;if(o)switch(o){case"int64":e.write(`if (${i} < -9223372036854775808n || ${i} > 9223372036854775807n) return INVALID;`);break;case"uint64":e.write(`if (${i} < 0n || ${i} > 18446744073709551615n) return INVALID;`);break;default:throw new F(`bigint format ${o}`)}}function yk(e,n,i,o){let t=i.mime;if(t&&t.length>0){let r=j(n,new Set(t));e.write(`if (!${r}.has(${o}.type)) return INVALID;`)}}function bk(e,n,i,o){if(i.when)throw new F('check with a custom "when" condition');e.write(`if (${o} == null) return INVALID;`);let t=i.shape;for(let r of Reflect.ownKeys(t)){let a=typeof r=="symbol"?j(n,r):pe(r),s=z(n);e.write(`const ${s} = ${o}[${a}];`),le(e,n,t[r],s,!1)}}function _k(e,n,i,o){let t=`${o}[${JSON.stringify(i.property)}]`;Q(e,n,i.schema,t)}function $k(e,n,i,o,t){let r=i._zod.def.tx;if(!r)throw new F("overwrite check without a transform function");if(Et(r))throw new De("z.compile: async overwrite transforms are not supported");let a=j(n,r);e.write(`const ${t} = ${a}(${o});`)}function xc(){throw new $e}function Sc(e){this.issues.push(e)}function kk(e,n,i,o){let t=i._zod.def;if(t.fn){if(Et(t.fn))throw new De("z.compile: async .refine() predicates are not supported");let r=Tt(n,t.fn),a=j(n,xc),s=z(n);return e.write(`const ${s} = ${r}(${o});`),e.write(`if (${s} instanceof Promise) ${a}();`),e.write(`if (!${s}) return INVALID;`),o}if(i._zod.check){if(Et(i._zod.check))throw new De("z.compile: async .superRefine() / check functions are not supported");let r=i._zod.check,s=Tt(n,c=>{let d={value:c,issues:[],addIssue:Sc};return r(d)instanceof Promise&&xc(),d.issues.length===0?d.value:fe}),u=z(n);return e.write(`const ${u} = ${s}(${o});`),e.write(`if (${u} === INVALID) return INVALID;`),u}throw new F("custom check without a predicate or check function")}var xk=new Set(["cidrv4","cuid","cuid2","date","datetime","duration","e164","email","emoji","ends_with","guid","includes","ipv4","ksuid","lowercase","mac","nanoid","regex","starts_with","time","ulid","uppercase","uuid","xid"]);function ig(e,n,i,o,t=!0){let r=i.format;if(r==="base64"){let c=j(n,lr);return e.write(`if (!${c}(${o})) return INVALID;`),o}if(r==="base64url"){let c=j(n,Di);return e.write(`if (!${c}(${o})) return INVALID;`),o}if(r==="jwt"){let c=j(n,ji),d=j(n,i.alg??null);return e.write(`if (!${c}(${o}, ${d})) return INVALID;`),o}if(r==="ipv6"){let c=j(n,cr);return e.write(`if (!${c}(${o})) return INVALID;`),o}if(r==="cidrv6"){let c=j(n,Ni);return e.write(`if (!${c}(${o})) return INVALID;`),o}if(r==="credit_card"){let c=j(n,Ci);return e.write(`if (!${c}(${o})) return INVALID;`),o}if(r==="iban"){let c=j(n,Li);return e.write(`if (!${c}(${o})) return INVALID;`),o}let a=i;if(r==="url"||r==="httpurl"||a.normalize||a.hostname!==void 0||a.protocol!==void 0){let c=j(n,Ei),d=j(n,i),p=z(n),m=z(n);if(e.write(`const ${p} = ${o}.trim();`),e.write(`const ${m} = ${c}(${p}, ${d});`),e.write(`if (typeof ${m} === "number") return INVALID;`),a.hostname!==void 0){let y=j(n,Ai);e.write(`if (!${y}(${m}, ${d}.hostname)) return INVALID;`)}if(a.protocol!==void 0){let y=j(n,Oi);e.write(`if (!${y}(${m}, ${d}.protocol)) return INVALID;`)}if(!t)return null;let f=z(n),g=a.normalize?`${m}.href`:`${j(n,ur)}(${p})`;return e.write(`const ${f} = ${g};`),f}let s=i.fn;if(s){if(Et(s))throw new F(`async string format ${r}`);let c=j(n,s);return e.write(`if (!${c}(${o})) return INVALID;`),o}if(xk.has(r)&&i.pattern){let c=j(n,i.pattern);return e.write(`${c}.lastIndex = 0;`),e.write(`if (!${c}.test(${o})) return INVALID;`),o}let u=i.format;switch(u){case"regex":throw new F("regex format without a pattern");case"lowercase":e.write(`if (${o} !== ${o}.toLowerCase()) return INVALID;`);break;case"uppercase":e.write(`if (${o} !== ${o}.toUpperCase()) return INVALID;`);break;case"includes":e.write(`if (!${o}.includes(${pe(i.includes)})) return INVALID;`);break;case"starts_with":{let c=i.prefix;e.write(`if (${o}.slice(0, ${c.length}) !== ${pe(c)}) return INVALID;`);break}case"ends_with":{let c=i.suffix;e.write(`if (${o}.slice(-${c.length}) !== ${pe(c)}) return INVALID;`);break}default:throw new F(`string format ${u}`)}return o}function Q(e,n,i,o,t=!0){let r=i._zod.def,a=r.type;if(r.coerce)throw new F(`coercion (z.coerce.${a}())`);let s=t||!!r.checks?.length,u;switch(a){case"string":u=wk(e,n,i,o,s);break;case"number":u=Ik(e,i,o);break;case"boolean":u=Sk(e,o);break;case"bigint":u=Pk(e,i,o);break;case"symbol":u=Tk(e,o);break;case"undefined":u=Ek(e,o);break;case"null":u=Ak(e,o);break;case"any":case"unknown":u=o;break;case"never":e.write("return INVALID;"),u=o;break;case"void":u=Ok(e,o);break;case"nan":u=Nk(e,o);break;case"date":u=zk(e,o);break;case"object":u=Dk(e,n,i,o,s);break;case"optional":u=Ck(e,n,i,o,s);break;case"nullable":u=Uk(e,n,i,o,s);break;case"array":u=Rk(e,n,i,o,s);break;case"literal":u=Mk(e,n,i,o);break;case"enum":u=Zk(e,n,i,o);break;case"readonly":{let c=Qf(e,n,i,o),d=z(n);e.write(`const ${d} = Object.freeze(${c});`),u=d;break}case"success":Qf(e,n,i,o),u="true";break;case"default":case"prefault":u=Fk(e,n,i,o);break;case"nonoptional":u=Vk(e,n,i,o);break;case"tuple":u=Gk(e,n,i,o);break;case"union":u=Bk(e,n,i,o);break;case"intersection":u=Kk(e,n,i,o);break;case"record":u=Jk(e,n,i,o);break;case"map":u=qk(e,n,i,o);break;case"set":u=Yk(e,n,i,o);break;case"file":u=Qk(e,o);break;case"template_literal":u=ex(e,n,i,o);break;case"lazy":u=tx(e,n,i,o);break;case"pipe":u=nx(e,n,i,o);break;case"custom":u=rx(e,n,i,o);break;case"transform":u=ax(e,n,i,o);break;case"catch":u=ox(e,n,i,o);break;default:throw new F(`schema type ${a}`)}return u===null?null:mk(e,n,i,u)}function wk(e,n,i,o,t=!0){e.write(`if (typeof ${o} !== "string") return INVALID;`);let r=i._zod.def;return r.format===void 0?o:ig(e,n,r,o,t)}function Ik(e,n,i){e.write(`if (typeof ${i} !== "number" || !Number.isFinite(${i})) return INVALID;`);let o=n._zod.def;return o.check==="number_format"&&o.format&&rg(e,{format:o.format},i),i}function Sk(e,n){return e.write(`if (typeof ${n} !== "boolean") return INVALID;`),n}function Pk(e,n,i){e.write(`if (typeof ${i} !== "bigint") return INVALID;`);let o=n._zod.def;if(o.format)switch(o.format){case"int64":e.write(`if (${i} < -9223372036854775808n || ${i} > 9223372036854775807n) return INVALID;`);break;case"uint64":e.write(`if (${i} < 0n || ${i} > 18446744073709551615n) return INVALID;`);break}return i}function Tk(e,n){return e.write(`if (typeof ${n} !== "symbol") return INVALID;`),n}function Ek(e,n){return e.write(`if (${n} !== undefined) return INVALID;`),n}function Ak(e,n){return e.write(`if (${n} !== null) return INVALID;`),n}function Ok(e,n){return e.write(`if (${n} !== undefined) return INVALID;`),n}function Nk(e,n){return e.write(`if (typeof ${n} !== "number" || !Number.isNaN(${n})) return INVALID;`),n}function zk(e,n){return e.write(`if (!(${n} instanceof Date) || Number.isNaN(${n}.getTime())) return INVALID;`),n}function Dk(e,n,i,o,t=!0){let r=i._zod.def;e.write(`if (typeof ${o} !== "object" || ${o} === null || Array.isArray(${o})) return INVALID;`);let a=r.shape,s=Object.keys(a),u=Object.getOwnPropertySymbols(a),c=u.length?[...s,...u]:s,d=b=>typeof b=="symbol"?j(n,b):pe(b),p=b=>typeof b=="symbol"?`[${d(b)}]`:pe(b),m=a;if(s.includes("__proto__"))throw new F('object shape key "__proto__"');let f=new Map;for(let b of c){let T=m[b],A=d(b),D=z(n);if(e.write(`const ${D} = ${o}[${A}];`),T._zod.optin!==void 0){let w=z(n);e.write(`let ${w} = (() => {`),e.indented(O=>{let E=le(O,n,T,D);O.write(`return ${E};`)}),e.write("})();"),T._zod.optout==="optional"?(e.write(`if (${w} === INVALID) {`),e.indented(O=>{O.write(`if (${A} in ${o}) return INVALID;`),O.write(`${w} = undefined;`)}),e.write("}")):e.write(`if (${w} === INVALID) return INVALID;`),f.set(b,w)}else{jk(T)&&e.write(`if (!(${A} in ${o})) return INVALID;`);let w=le(e,n,T,D,t);w!==null&&f.set(b,w)}}let g=r.catchall,y="none";if(g){let b=g._zod.def.type;if(b==="never"){let T=s.map(A=>`k !== ${pe(A)}`).join(" && ")||"true";e.write(`for (const k in ${o}) {`),e.indented(A=>{A.write(`if (${T}) return INVALID;`)}),e.write("}")}else(b==="unknown"||b==="any")&&!g._zod.def.checks?.length?y="passthrough":y="schema"}let x=z(n),I=c.some(b=>Yf(m[b])||wc(m[b]));if(!t){if(y==="schema"){let b=s.length>0?j(n,new Set(s)):null;e.write(`for (const k in ${o}) {`),e.indented(T=>{T.write('if (k === "__proto__") continue;'),b&&T.write(`if (${b}.has(k)) continue;`);let A=z(n);T.write(`const ${A} = ${o}[k];`),le(T,n,g,A,!1)}),e.write("}")}return null}if(I){e.write(`const ${x} = {};`);for(let b of c){let T=d(b),A=f.get(b);wc(m[b])?e.write(`if (${T} in ${o}) ${x}[${T}] = ${A};`):Yf(m[b])?e.write(`if (${A} !== undefined || ${T} in ${o}) ${x}[${T}] = ${A};`):e.write(`${x}[${T}] = ${A};`)}}else{let b=c.map(T=>`${p(T)}: ${f.get(T)}`).join(", ");e.write(`const ${x} = { ${b} };`)}if(y!=="none"){let b=s.length>0?j(n,new Set(s)):null;e.write(`for (const k in ${o}) {`),e.indented(T=>{if(T.write('if (k === "__proto__") continue;'),b&&T.write(`if (${b}.has(k)) continue;`),y==="passthrough")T.write(`${x}[k] = ${o}[k];`);else{let A=z(n);T.write(`const ${A} = ${o}[k];`);let D=le(T,n,g,A);T.write(`${x}[k] = ${D};`)}}),e.write("}")}return x}function Ck(e,n,i,o,t=!0){let r=i._zod.def;if(Lk(i))return Q(e,n,r.innerType,o,t);if(r.innerType._zod.optin==="defaulted"){let s=z(n),u=z(n);return e.write(`let ${s};`),e.write(`if (${o} === undefined) {`),e.indented(c=>{c.write(`const ${u} = (() => {`),c.indented(d=>{let p=Q(d,n,r.innerType,o);d.write(`return ${p};`)}),c.write("})();"),c.write(`if (${u} !== INVALID) ${s} = ${u};`)}),e.write("} else {"),e.indented(c=>{let d=Q(c,n,r.innerType,o);c.write(`${s} = ${d};`)}),e.write("}"),s}let a=t?z(n):null;return a&&e.write(`let ${a};`),e.write(`if (${o} !== undefined) {`),e.indented(s=>{let u=Q(s,n,r.innerType,o,t);a&&u!==null&&s.write(`${a} = ${u};`)}),e.write("}"),a}function Lk(e){return e._zod.traits?.has("$ZodExactOptional")===!0}function jk(e){return e._zod.optin===void 0&&Pt(e)}function Pt(e){if(e._zod.def.coerce)return!0;let n=e._zod.def;switch(n.type){case"any":case"unknown":case"undefined":case"void":case"default":case"prefault":case"transform":case"custom":case"lazy":return!0;case"string":case"number":case"boolean":case"bigint":case"symbol":case"null":case"never":case"nan":case"date":case"object":case"array":case"tuple":case"record":case"map":case"set":case"file":case"template_literal":return!1;case"nonoptional":return n.innerType?Pt(n.innerType):!1;case"literal":return!!n.values?.includes(void 0);case"enum":return!!e._zod.values?.has(void 0);case"optional":case"nullable":case"readonly":case"success":return n.innerType?Pt(n.innerType):!0;case"catch":return!0;case"union":return n.options?n.options.some(Pt):!0;case"intersection":return!n.left||!n.right?!0:Pt(n.left)&&Pt(n.right);case"pipe":return n.in?Pt(n.in):!0;default:return!0}}function wc(e){return e._zod.optin==="optional"&&e._zod.optout==="optional"}function Yf(e){return(e._zod.optin!=="defaulted"||e._zod.optout==="optional")&&sn(e)}function sn(e){let n=e._zod.def;switch(n.type){case"string":case"number":case"boolean":case"bigint":case"symbol":case"null":case"nan":case"date":case"object":case"array":case"tuple":case"record":case"map":case"set":case"file":case"template_literal":case"never":case"success":return!1;case"literal":return!!n.values?.includes(void 0);case"enum":return!!e._zod.values?.has(void 0);case"optional":return!0;case"nullable":case"readonly":case"nonoptional":return n.innerType?sn(n.innerType):!0;case"union":return n.options?n.options.some(sn):!0;case"intersection":return!n.left||!n.right||sn(n.left)||sn(n.right);case"pipe":return n.out?sn(n.out):!0;default:return!0}}function Uk(e,n,i,o,t=!0){let r=i._zod.def,a=t?z(n):null;return a&&e.write(`let ${a} = null;`),e.write(`if (${o} !== null) {`),e.indented(s=>{let u=Q(s,n,r.innerType,o,t);a&&u!==null&&s.write(`${a} = ${u};`)}),e.write("}"),a}function Pc(e,n,i){for(let[o,t]of Xt(n._zod.def.checks))e.write(`if (${i} > ${at(o,t._zod.def.check)}) return INVALID;`)}function Rk(e,n,i,o,t=!0){let r=i._zod.def;e.write(`if (!Array.isArray(${o})) return INVALID;`),Pc(e,i,`${o}.length`);let a=t?z(n):null,s=z(n),u=z(n);return a&&e.write(`const ${a} = new Array(${o}.length);`),e.write(`for (let ${s} = 0; ${s} < ${o}.length; ${s}++) {`),e.indented(c=>{c.write(`const ${u} = ${o}[${s}];`);let d=le(c,n,r.element,u,t);a&&d!==null&&c.write(`${a}[${s}] = ${d};`)}),e.write("}"),a}function Mk(e,n,i,o){let r=i._zod.def.values;if(r.length!==1){let s=j(n,new Set(r));return e.write(`if (!${s}.has(${o})) return INVALID;`),o}let a=r[0];if(typeof a=="number"&&Number.isNaN(a)){let s=j(n,new Set(r));return e.write(`if (!${s}.has(${o})) return INVALID;`),o}if(typeof a=="string")e.write(`if (${o} !== ${pe(a)}) return INVALID;`);else if(typeof a=="number"||typeof a=="boolean")e.write(`if (${o} !== ${a}) return INVALID;`);else if(a===null)e.write(`if (${o} !== null) return INVALID;`);else if(a===void 0)e.write(`if (${o} !== undefined) return INVALID;`);else if(typeof a=="bigint")e.write(`if (${o} !== ${a}n) return INVALID;`);else throw new F(`literal type ${typeof a}`);return o}function Zk(e,n,i,o){let t=i._zod.values;if(!t)throw new F("enum schema without enumerated values");let r=j(n,t);return e.write(`if (!${r}.has(${o})) return INVALID;`),o}function Qf(e,n,i,o){let t=i._zod.def;return Q(e,n,t.innerType,o)}function Fk(e,n,i,o){let t=i._zod.def,a=Object.getOwnPropertyDescriptor(i._zod.def,"defaultValue")?()=>i._zod.def.defaultValue:void 0;if(i._zod.def.type==="prefault"){if(!a)return Q(e,n,t.innerType,o);let u=j(n,a),c=z(n);return e.write(`let ${c} = ${o};`),e.write(`if (${o} === undefined) ${c} = ${u}();`),Q(e,n,t.innerType,c)}let s=z(n);if(a){let u=j(n,a),c=j(n,Bn);e.write(`let ${s};`),e.write(`if (${o} === undefined) {`),e.indented(d=>{d.write(`${s} = ${c}(${u}());`)}),e.write("} else {"),e.indented(d=>{let p=Q(d,n,t.innerType,o);d.write(`${s} = ${p} === undefined ? ${c}(${u}()) : ${p};`)}),e.write("}")}else e.write(`let ${s};`),e.write(`if (${o} !== undefined) {`),e.indented(u=>{let c=Q(u,n,t.innerType,o);u.write(`${s} = ${c};`)}),e.write("}");return s}function Vk(e,n,i,o){let t=i._zod.def,r=Q(e,n,t.innerType,o),a=z(n);return e.write(`const ${a} = ${r};`),e.write(`if (${a} === undefined) return INVALID;`),a}function Gk(e,n,i,o){let t=i._zod.def,r=t.items,a=t.rest;e.write(`if (!Array.isArray(${o})) return INVALID;`);let s=eg(r,"optin"),u=eg(r,"optout");a?e.write(`if (${o}.length < ${s}) return INVALID;`):e.write(`if (${o}.length < ${s} || ${o}.length > ${r.length}) return INVALID;`);let c=z(n);e.write(`const ${c} = [];`);for(let d=0;d=u)e.write(`if (${c}.length === ${d}) {`),e.indented(m=>{m.write(`if (${d} < ${o}.length) {`),m.indented(f=>{let g=z(n);f.write(`const ${g} = ${o}[${d}];`);let y=le(f,n,p,g);f.write(`${c}[${d}] = ${y};`)}),m.write("} else {"),m.indented(f=>{if(wc(p)){f.write(`${c}.length = ${d};`);return}let g=z(n),y=z(n);f.write(`const ${g} = undefined;`),f.write(`const ${y} = (() => {`),f.indented(x=>{let I=le(x,n,p,g);x.write(`return ${I};`)}),f.write("})();"),f.write(`if (${y} === INVALID || ${y} === undefined) ${c}.length = ${d};`),f.write(`else ${c}[${d}] = ${y};`)}),m.write("}")}),e.write("}");else{let m=z(n);e.write(`const ${m} = ${o}[${d}];`);let f=le(e,n,p,m);e.write(`${c}[${d}] = ${f};`)}}if(a){let d=z(n),p=z(n);e.write(`for (let ${d} = ${r.length}; ${d} < ${o}.length; ${d}++) {`),e.indented(m=>{m.write(`const ${p} = ${o}[${d}];`);let f=le(m,n,a,p);m.write(`${c}[${d}] = ${f};`)}),e.write("}")}return c}function eg(e,n){for(let i=e.length-1;i>=0;i--)if(!(n==="optin"?e[i]._zod.optin!==void 0:e[i]._zod.optout==="optional"))return i+1;return 0}function Bk(e,n,i,o){let t=i._zod.def,r=t.options;if(t.discriminator)return Hk(e,n,t,o);if(t.inclusive===!1)throw new F("exclusive unions (z.xor)");if(r.length===0)return e.write("return INVALID;"),o;if(r.length===1)return Q(e,n,r[0],o);if(r.every(u=>u._zod.def.type==="literal"&&!u._zod.def.checks?.length)){let u=new Set(r.flatMap(d=>d._zod.def.values)),c=j(n,u);return e.write(`if (!${c}.has(${o})) return INVALID;`),o}let s=z(n);e.write(`let ${s};`);for(let u=0;u {`):e.write(`if (${s} === INVALID) ${s} = (() => {`),e.indented(d=>{let p=Q(d,n,c,o);d.write(`return ${p};`)}),e.write("})();")}return e.write(`if (${s} === INVALID) return INVALID;`),s}function Hk(e,n,i,o){if(i.unionFallback)throw new F("discriminated union with unionFallback");if(i.options.length===0)return e.write("return INVALID;"),o;let t=z(n),r=z(n);e.write(`const ${t} = ${o}?.[${pe(i.discriminator)}];`),e.write(`let ${r};`);let a=!0,s=new Set;for(let u of i.options){let c=u._zod.propValues?.[i.discriminator];if(!c||c.size===0)throw new F("discriminated union option without static discriminator values");for(let m of c){if(s.has(m))throw new F(`duplicate discriminator value ${String(m)}`);s.add(m)}let d=Array.from(c,m=>Wk(n,t,m)),p=a?"if":"else if";e.write(`${p} (${d.join(" || ")}) {`),e.indented(m=>{let f=Q(m,n,u,o);m.write(`${r} = ${f};`)}),e.write("}"),a=!1}return e.write("else { return INVALID; }"),r}function Wk(e,n,i){if(typeof i=="string")return`${n} === ${pe(i)}`;if(typeof i=="number")return Number.isNaN(i)?`Number.isNaN(${n})`:`${n} === ${i}`;if(typeof i=="boolean")return`${n} === ${i}`;if(i===null)return`${n} === null`;if(i===void 0)return`${n} === undefined`;if(typeof i=="bigint")return`${n} === ${i}n`;if(typeof i=="symbol"){let o=j(e,i);return`${n} === ${o}`}throw new F(`literal discriminator value ${String(i)}`)}function Kk(e,n,i,o){let t=i._zod.def;n.definite=!1;let r=le(e,n,t.left,o),a=le(e,n,t.right,o),s=j(n,rn),u=z(n);return e.write(`const ${u} = ${s}(${r}, ${a});`),e.write(`if (!${u}.valid) return INVALID;`),`${u}.data`}function Jk(e,n,i,o){let t=i._zod.def,r=j(n,Se);e.write(`if (!${r}(${o})) return INVALID;`);let a=z(n),s=z(n),u=z(n);e.write(`const ${a} = {};`);let c=t,d=c.partial?void 0:t.keyType._zod.values;if(d){let f=[];for(let y of d){if(!(typeof y=="string"||typeof y=="number"||typeof y=="symbol"))throw new F(`record key value ${String(y)}`);let x=typeof y=="number"?y.toString():y;if(x==="__proto__")throw new F('record key "__proto__"');f.push(x);let I=j(n,y),b=Q(e,n,t.keyType,I),T=z(n);e.write(`const ${T} = ${o}[${Xk(n,x)}];`);let A=le(e,n,t.valueType,T);e.write(`${a}[${b}] = ${A};`)}let g=j(n,new Set(f));return e.write(`for (const ${s} in ${o}) {`),e.indented(y=>{y.write(`if (${g}.has(${s})) continue;`),c.mode==="loose"?y.write(`if (${s} !== "__proto__") ${a}[${s}] = ${o}[${s}];`):y.write("return INVALID;")}),e.write("}"),a}let p=t.keyType._zod.def;if(!(p.type==="string"&&p.format===void 0&&!p.coerce&&(p.checks?.length??0)===0)){let f=t.mode==="loose",g=to(t.keyType);g.definite===!1&&(n.definite=!1);let y=j(n,g),x=j(n,qe),I=z(n);return tg(e,n,o,s,b=>{b.write(`let ${I} = ${y}(${s});`),b.write(`if (${I} === INVALID && typeof ${s} === "string" && ${x}.test(${s})) ${I} = ${y}(Number(${s}));`),f?b.write(`if (${I} === INVALID) { ${a}[${s}] = ${o}[${s}]; continue; }`):b.write(`if (${I} === INVALID) return INVALID;`),b.write(`if (${I} === "__proto__") continue;`);let T=z(n);b.write(`const ${T} = ${o}[${s}];`);let A=le(b,n,t.valueType,T);b.write(`${a}[${I}] = ${A};`)}),a}return tg(e,n,o,s,f=>{f.write(`const ${u} = ${o}[${s}];`);let g=le(f,n,t.valueType,u);f.write(`${a}[${s}] = ${g};`)},"return INVALID;"),a}function tg(e,n,i,o,t,r){let a=j(n,Object.prototype.propertyIsEnumerable),s=z(n),u=z(n),c=z(n);e.write(`const ${s} = Object.getOwnPropertySymbols(${i});`),e.write(`const ${u} = Object.getOwnPropertyNames(${i});`),e.write(`for (let ${c} = 0; ${c} < ${u}.length; ${c}++) {`),e.indented(d=>{d.write(`const ${o} = ${u}[${c}];`),d.write(`if (${o} === "__proto__" || !${a}.call(${i}, ${o})) continue;`),t(d)}),e.write("}"),e.write(`for (let ${c} = 0; ${c} < ${s}.length; ${c}++) {`),e.indented(d=>{d.write(`const ${o} = ${s}[${c}];`),d.write(`if (!${a}.call(${i}, ${o})) continue;`),r?d.write(r):t(d)}),e.write("}")}function Xk(e,n){return typeof n=="string"?pe(n):j(e,n)}function qk(e,n,i,o){let t=i._zod.def;e.write(`if (!(${o} instanceof Map)) return INVALID;`),Pc(e,i,`${o}.size`);let r=z(n),a=z(n),s=z(n);return e.write(`const ${r} = new Map();`),e.write(`for (const [${a}, ${s}] of ${o}) {`),e.indented(u=>{let c=Q(u,n,t.keyType,a),d=Q(u,n,t.valueType,s);u.write(`${r}.set(${c}, ${d});`)}),e.write("}"),r}function Yk(e,n,i,o){let t=i._zod.def;e.write(`if (!(${o} instanceof Set)) return INVALID;`),Pc(e,i,`${o}.size`);let r=z(n),a=z(n);return e.write(`const ${r} = new Set();`),e.write(`for (const ${a} of ${o}) {`),e.indented(s=>{let u=Q(s,n,t.valueType,a);s.write(`${r}.add(${u});`)}),e.write("}"),r}function Qk(e,n){return e.write(`if (!(${n} instanceof File)) return INVALID;`),n}function ex(e,n,i,o){e.write(`if (typeof ${o} !== "string") return INVALID;`);let t=i._zod.pattern;if(t){let r=j(n,t);e.write(`${r}.lastIndex = 0;`),e.write(`if (!${r}.test(${o})) return INVALID;`)}return o}function tx(e,n,i,o){let t=i._zod.def,r=Tt(n,t.getter),a=j(n,{parser:null});e.write(`if (!${a}.parser) {`),e.indented(u=>{u.write(`const inner = ${r}();`),u.write(`${a}.parser = function(input) {`),u.indented(c=>{c.write("const result = inner._zod.run({ value: input, issues: [] }, {});"),c.write("return result.issues.length === 0 ? result.value : INVALID;")}),u.write("};")}),e.write("}");let s=z(n);return e.write(`const ${s} = ${a}.parser(${o});`),e.write(`if (${s} === INVALID) return INVALID;`),s}function nx(e,n,i,o){let t=i._zod.def,r=Q(e,n,t.in,o);if(t.transform){if(Et(t.transform))throw new De("z.compile: async transforms in pipes are not supported");let a=t.transform,u=Tt(n,d=>{let p={value:d,issues:[],addIssue:Sc},m=a(d,p);return m instanceof Promise?fe:p.issues.length===0?m:fe}),c=z(n);return e.write(`const ${c} = ${u}(${r});`),e.write(`if (${c} === INVALID) return INVALID;`),Q(e,n,t.out,c)}else return Q(e,n,t.out,r)}function Et(e){return typeof e=="function"&&(e.constructor.name==="AsyncFunction"||e[Symbol.toStringTag]==="AsyncFunction")}function rx(e,n,i,o){let t=i._zod.def;if(t.fn){if(Et(t.fn))throw new De("z.compile: async custom predicates are not supported");let r=Tt(n,t.fn),a=j(n,xc),s=z(n);e.write(`const ${s} = ${r}(${o});`),e.write(`if (${s} instanceof Promise) ${a}();`),e.write(`if (!${s}) return INVALID;`)}else throw new F("custom schema without a predicate function");return o}function ix(e,n,i){let o=e._zod.run({value:i,issues:[]},{});if(o&&typeof o.then=="function")return fe;let t=o;return t.issues.length===0?t.value:n()}function ox(e,n,i,o){let t=i._zod.def;if(!t.catchValue[ai])throw new F("catch with a callback (only a constant catch value compiles)",!1);let r=z(n);e.write(`let ${r} = (() => {`),e.indented(c=>{let d=le(c,n,t.innerType,o);c.write(`return ${d};`)}),e.write("})();");let a=j(n,t.innerType),s=Tt(n,t.catchValue),u=j(n,ix);return e.write(`if (${r} === INVALID) {`),e.indented(c=>{c.write(`${r} = ${u}(${a}, ${s}, ${o});`),c.write(`if (${r} === INVALID) return INVALID;`)}),e.write("}"),r}function ax(e,n,i,o){let t=i._zod.def;if(t.transform){if(Et(t.transform))throw new De("z.compile: async transforms are not supported");let r=t.transform,s=Tt(n,c=>{let d={value:c,issues:[],addIssue:Sc},p=r(c,d);return p instanceof Promise?fe:d.issues.length===0?p:fe}),u=z(n);return e.write(`const ${u} = ${s}(${o});`),e.write(`if (${u} === INVALID) return INVALID;`),u}return o}function no(e){return e.checks&&(e.checks=[...e.checks]),e}function Tc(e,n){return new e(no({type:"string",...S(n)}))}function Ec(e,n){return new e(no({type:"string",coerce:!0,...S(n)}))}function ro(e,n){return new e({type:"string",format:"email",check:"string_format",abort:!1,...S(n)})}function io(e,n){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...S(n)})}function oo(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,...S(n)})}function ao(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...S(n)})}function so(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...S(n)})}function uo(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...S(n)})}function $r(e,n){return new e({type:"string",format:"url",check:"string_format",abort:!1,...S(n)})}function co(e,n){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,...S(n)})}function lo(e,n){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,...S(n)})}function po(e,n){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,...S(n)})}function mo(e,n){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,...S(n)})}function fo(e,n){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,...S(n)})}function go(e,n){return new e({type:"string",format:"xid",check:"string_format",abort:!1,...S(n)})}function ho(e,n){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,...S(n)})}function vo(e,n){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,...S(n)})}function yo(e,n){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,...S(n)})}function Ac(e,n){return new e({type:"string",format:"mac",check:"string_format",abort:!1,...S(n)})}function bo(e,n){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,...S(n)})}function _o(e,n){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,...S(n)})}function $o(e,n){return new e({type:"string",format:"base64",check:"string_format",abort:!1,...S(n)})}function ko(e,n){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,...S(n)})}function xo(e,n){return new e({type:"string",format:"e164",check:"string_format",abort:!1,...S(n)})}function Oc(e,n){return new e({type:"string",format:"credit_card",check:"string_format",abort:!1,...S(n)})}function Nc(e,n){return new e({type:"string",format:"iban",check:"string_format",abort:!1,...S(n)})}function wo(e,n){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,...S(n)})}var zc={Any:null,Minute:-1,Second:0,Millisecond:3,Microsecond:6};function kr(e,n){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...S(n)})}function xr(e,n){return new e({type:"string",format:"date",check:"string_format",...S(n)})}function wr(e,n){return new e({type:"string",format:"time",check:"string_format",precision:null,...S(n)})}function Ir(e,n){return new e({type:"string",format:"duration",check:"string_format",...S(n)})}function Dc(e,n){return new e(no({type:"number",checks:[],...S(n)}))}function Cc(e,n){return new e(no({type:"number",coerce:!0,checks:[],...S(n)}))}function Lc(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",...S(n)})}function jc(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"float32",...S(n)})}function Uc(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"float64",...S(n)})}function Rc(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"int32",...S(n)})}function Mc(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"uint32",...S(n)})}function Zc(e,n){return new e({type:"boolean",...S(n)})}function Fc(e,n){return new e({type:"boolean",coerce:!0,...S(n)})}function Vc(e,n){return new e({type:"bigint",...S(n)})}function Gc(e,n){return new e({type:"bigint",coerce:!0,...S(n)})}function Bc(e,n){return new e({type:"bigint",check:"bigint_format",abort:!1,format:"int64",...S(n)})}function Hc(e,n){return new e({type:"bigint",check:"bigint_format",abort:!1,format:"uint64",...S(n)})}function Wc(e,n){return new e({type:"symbol",...S(n)})}function Kc(e,n){return new e({type:"undefined",...S(n)})}function Jc(e,n){return new e({type:"null",...S(n)})}function Xc(e){return new e({type:"any"})}function qc(e){return new e({type:"unknown"})}function Yc(e,n){return new e({type:"never",...S(n)})}function Qc(e,n){return new e({type:"void",...S(n)})}function el(e,n){return new e({type:"date",...S(n)})}function tl(e,n){return new e({type:"date",coerce:!0,...S(n)})}function nl(e,n){return new e({type:"nan",...S(n)})}function Ze(e,n){return new ki({check:"less_than",...S(n),value:e,inclusive:!1})}function we(e,n){return new ki({check:"less_than",...S(n),value:e,inclusive:!0})}function Fe(e,n){return new xi({check:"greater_than",...S(n),value:e,inclusive:!1})}function Ie(e,n){return new xi({check:"greater_than",...S(n),value:e,inclusive:!0})}function Io(e){return Fe(0,e)}function So(e){return Ze(0,e)}function Po(e){return we(0,e)}function To(e){return Ie(0,e)}function st(e,n){return new Us({check:"multiple_of",...S(n),value:e})}function ut(e,n){return new Zs({check:"max_size",...S(n),maximum:e})}function Ve(e,n){return new Fs({check:"min_size",...S(n),minimum:e})}function At(e,n){return new Vs({check:"size_equals",...S(n),size:e})}function Ot(e,n){return new Gs({check:"max_length",...S(n),maximum:e})}function Ye(e,n){return new Bs({check:"min_length",...S(n),minimum:e})}function Nt(e,n){return new Hs({check:"length_equals",...S(n),length:e})}function un(e,n){return new Ws({check:"string_format",format:"regex",...S(n),pattern:e})}function cn(e){return new Ks({check:"string_format",format:"lowercase",...S(e)})}function ln(e){return new Js({check:"string_format",format:"uppercase",...S(e)})}function dn(e,n){return new Xs({check:"string_format",format:"includes",...S(n),includes:e})}function pn(e,n){return new qs({check:"string_format",format:"starts_with",...S(n),prefix:e})}function mn(e,n){return new Ys({check:"string_format",format:"ends_with",...S(n),suffix:e})}function Eo(e,n,i){return new Qs({check:"property",property:e,schema:n,...S(i)})}function fn(e,n){return new eu({check:"properties",shape:e,...S(n)})}function gn(e,n){return new tu({check:"mime_type",mime:e,...S(n)})}function Ce(e){return new nu({check:"overwrite",tx:e})}function hn(e){return Ce(n=>n.normalize(e))}function vn(){return Ce(e=>e.trim())}function yn(){return Ce(e=>e.toLowerCase())}function bn(){return Ce(e=>e.toUpperCase())}function _n(){return Ce(e=>Ma(e))}function rl(e,n,i){return new e({type:"array",element:n,...S(i)})}function sx(e,n,i){return new e({type:"union",options:n,...S(i)})}function ux(e,n,i){return new e({type:"union",options:n,inclusive:!1,...S(i)})}function cx(e,n,i,o){return new e({type:"union",options:i,discriminator:n,...S(o)})}function lx(e,n,i){return new e({type:"intersection",left:n,right:i})}function dx(e,n,i,o){let t=i instanceof U,r=t?o:i,a=t?i:null;return new e({type:"tuple",items:n,rest:a,...S(r)})}function px(e,n,i,o){return new e({type:"record",keyType:n,valueType:i,...S(o)})}function mx(e,n,i,o){return new e({type:"map",keyType:n,valueType:i,...S(o)})}function fx(e,n,i){return new e({type:"set",valueType:n,...S(i)})}function gx(e,n,i){let o=Array.isArray(n)?Object.fromEntries(n.map(t=>[t,t])):n;return new e({type:"enum",entries:o,...S(i)})}function hx(e,n,i){return new e({type:"enum",entries:n,...S(i)})}function vx(e,n,i){return new e({type:"literal",values:Array.isArray(n)?n:[n],...S(i)})}function il(e,n){return new e({type:"file",...S(n)})}function yx(e,n){return new e({type:"transform",transform:n})}function bx(e,n){return new e({type:"optional",innerType:n})}function _x(e,n){return new e({type:"nullable",innerType:n})}function $x(e,n,i){return new e({type:"default",innerType:n,get defaultValue(){return typeof i=="function"?i():Bn(i)}})}function kx(e,n,i){return new e({type:"nonoptional",innerType:n,...S(i)})}function xx(e,n){return new e({type:"success",innerType:n})}function wx(e,n,i){return new e({type:"catch",innerType:n,catchValue:typeof i=="function"?i:Ha(i)})}function Ix(e,n,i){return new e({type:"pipe",in:n,out:i})}function Sx(e,n){return new e({type:"readonly",innerType:n})}function Px(e,n,i){return new e({type:"template_literal",parts:n,...S(i)})}function Tx(e,n){return new e({type:"lazy",getter:n})}function Ex(e,n){return new e({type:"promise",innerType:n})}function ol(e,n,i){let o=S(i);return o.abort??(o.abort=!0),new e({type:"custom",check:"custom",fn:n,...o})}function al(e,n,i){return new e({type:"custom",check:"custom",fn:n,...S(i)})}function sl(e,n){let i=og(o=>(o.addIssue=t=>{if(typeof t=="string")o.issues.push(qt(t,o.value,i._zod.def));else{let r=t;r.fatal&&(r.continue=!1),r.code??(r.code="custom"),"input"in r||(r.input=o.value),r.inst??(r.inst=i),r.continue??(r.continue=!i._zod.def.abort),o.issues.push(qt(r))}},e(o.value,o)),n);return i}function og(e,n){let i=new q({check:"custom",...S(n)});return i._zod.check=e,i}function ul(e){let n=new q({check:"describe"});return n._zod.onattach=[i=>{let o=ue.get(i)??{};ue.add(i,{...o,description:e})}],n._zod.check=()=>{},n}function cl(e){let n=new q({check:"meta"});return n._zod.onattach=[i=>{let o=ue.get(i)??{};ue.add(i,{...o,...e})}],n._zod.check=()=>{},n}function ll(e,n){let i=S(n),o=i.truthy??["true","1","yes","on","y","enabled"],t=i.falsy??["false","0","no","off","n","disabled"];i.case!=="sensitive"&&(o=o.map(f=>typeof f=="string"?f.toLowerCase():f),t=t.map(f=>typeof f=="string"?f.toLowerCase():f));let r=new Set(o),a=new Set(t),s=e.Codec??fr,u=e.Boolean??pr,c=e.String??St,d=new c({type:"string",error:i.error}),p=new u({type:"boolean",error:i.error}),m=new s({type:"pipe",in:d,out:p,transform:((f,g)=>{let y=f;return i.case!=="sensitive"&&(y=y.toLowerCase()),r.has(y)?!0:a.has(y)?!1:(g.issues.push({code:"invalid_value",expected:"stringbool",values:[...r,...a],input:g.value,inst:m,continue:!1}),{})}),reverseTransform:((f,g)=>f===!0?o[0]||"true":t[0]||"false"),error:i.error});return m._zod.bag.truthy=o,m._zod.bag.falsy=t,m._zod.bag.case=i.case??"insensitive",m}function zt(e,n,i,o={}){let t=S(o),r={check:"string_format",type:"string",format:n,fn:typeof i=="function"?i:s=>i.test(s),...t};return i instanceof RegExp&&(r.pattern=i),new e(r)}function Sr(e,...n){for(let i of n)for(let o of Reflect.ownKeys(i))Object.prototype.propertyIsEnumerable.call(i,o)&&se(e,o,i[o]);return e}function ct(e){let n=e?.target??"draft-2020-12";return n==="draft-4"&&(n="draft-04"),n==="draft-7"&&(n="draft-07"),{processors:e.processors??{},metadataRegistry:e?.metadata??ue,target:n,unrepresentable:e?.unrepresentable??"throw",override:e?.override??(()=>{}),io:e?.io??"output",counter:0,seen:new Map,sharedDefsExtractedFor:void 0,sharedEmitDoneFor:void 0,cycles:e?.cycles??"ref",reused:e?.reused??"inline",intersections:[],deferred:[],external:e?.external??void 0}}function ie(e,n,i,o,t){let r=typeof n.unrepresentable=="function"?n.unrepresentable({zodSchema:e,path:o.path,message:t}):n.unrepresentable;if(r==="any")return!1;if(r===void 0||r==="throw")throw new Error(t);return Object.assign(i,r),!0}function J(e,n,i={path:[],schemaPath:[]}){var o;let t=e._zod.def,r=n.seen.get(e);if(r)return r.count++,i.schemaPath.includes(e)&&(r.cycle=i.path),r.schema;let a={schema:{},count:1,cycle:void 0,path:i.path};n.seen.set(e,a),n.sharedDefsExtractedFor=void 0,n.sharedEmitDoneFor=void 0;let s=e._zod.toJSONSchema?.();if(s)a.schema=s;else{let d={...i,schemaPath:[...i.schemaPath,e],path:i.path};if(e._zod.processJSONSchema)e._zod.processJSONSchema(n,a.schema,d);else{let m=a.schema,f=n.processors[t.type];if(!f)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${t.type}`);f(e,n,m,d)}let p=e._zod.parent;p&&(a.ref||(a.ref=p),J(p,n,d),n.seen.get(p).isParent=!0)}let u=n.metadataRegistry.get(e);return u&&Sr(a.schema,u),n.io==="input"&&ge(e)&&(delete a.schema.examples,delete a.schema.default),n.io==="input"&&"_prefault"in a.schema&&((o=a.schema).default??(o.default=a.schema._prefault)),delete a.schema._prefault,n.seen.get(e).schema}function ag(e){return e.replace(/~/g,"~0").replace(/\//g,"~1")}function lt(e,n){let i=e.seen.get(n);if(!i)throw new Error("Unprocessed schema. This is a bug in Zod.");if(e.external&&e.sharedDefsExtractedFor===e.external)return;let o=new Map;for(let a of e.seen.entries()){let s=e.metadataRegistry.get(a[0])?.id;if(s){let u=o.get(s);if(u&&u!==a[0])throw new Error(`Duplicate schema id "${s}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);o.set(s,a[0])}}let t=a=>{let s=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){let p=e.external.registry.get(a[0])?.id,m=e.external.uri??(g=>g);if(p)return{ref:m(p)};let f=a[1].defId??a[1].schema.id??`schema${e.counter++}`;return a[1].defId=f,{defId:f,ref:`${m("__shared")}#/${s}/${ag(f)}`}}let u="#",c=`${u}/${s}/`;if(a[1]===i&&!a[1].schema.id)return{ref:u};let d=a[1].schema.id??`__schema${e.counter++}`;return{defId:d,ref:c+ag(d)}},r=a=>{if(a[1].schema.$ref)return;let s=a[1],{ref:u,defId:c}=t(a);s.def={...s.schema},c&&(s.defId=c);let d=s.schema;for(let p in d)delete d[p];d.$ref=u};if(e.cycles==="throw")for(let a of e.seen.entries()){let s=a[1];if(s.cycle)throw new Error(`Cycle detected: #/${s.cycle?.join("/")}/ -Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(let a of e.seen.entries()){let s=a[1];if(n===a[0]){r(a);continue}if(e.external){let u=e.external.registry.get(a[0])?.id;if(n!==a[0]&&u){r(a);continue}}if(e.metadataRegistry.get(a[0])?.id){r(a);continue}if(s.cycle){r(a);continue}s.count>1&&e.reused==="ref"&&r(a)}e.external&&(e.sharedDefsExtractedFor=e.external)}function tg(e){let n=e.anyOf;if(!Array.isArray(n)||n.length===0||e.type!==void 0)return;let i=[];for(let o of n){if(!o||typeof o!="object")return;tg(o);let t=Object.keys(o);if(t.length!==1||t[0]!=="type")return;let r=o.type;for(let a of Array.isArray(r)?r:[r]){if(typeof a!="string")return;i.includes(a)||i.push(a)}}delete e.anyOf,e.type=i.length===1?i[0]:i}var ng=new Set(["type","properties","required","additionalProperties"]),Qf=["oneOf","anyOf"];function eg(e){let n=e.additionalProperties;return n===void 0||n===!1||typeof n!="object"||n===null?null:Object.keys(n).length?n:null}function al(e){let n=[];for(let r of e){if(typeof r!="object"||r.type!=="object")return null;for(let a in r)if(!ng.has(a))return null;n.push(r)}let i={},o=new Set;for(let r of n){for(let a in r.properties){if(Object.prototype.hasOwnProperty.call(i,a))continue;let s=[];for(let u of n){let d=u.properties?.[a]??eg(u);d!=null&&(s.some(p=>JSON.stringify(p)===JSON.stringify(d))||s.push(d))}let c=s.length===1?s[0]:al(s)??{allOf:s};se(i,a,c)}for(let a of r.required??[])o.add(a)}let t={type:"object",properties:i};if(o.size&&(t.required=[...o]),n.every(r=>r.additionalProperties===!1))t.additionalProperties=!1;else{let r=[];for(let a of n){let s=eg(a);s&&!r.some(c=>JSON.stringify(c)===JSON.stringify(s))&&r.push(s)}r.length===1?t.additionalProperties=r[0]:r.length>1&&(t.additionalProperties={allOf:r})}return t}function dx(e){let n=e.allOf;if(!Array.isArray(n)||n.length<2)return;for(let t of ng)if(t in e)return;let i=n.filter(t=>Qf.some(r=>Array.isArray(t[r]))),o=null;if(!i.length)o=al(n);else{let t=i[0],r=Qf.find(c=>Array.isArray(t[c]));if(Object.keys(t).length!==1)return;let a=n.filter(c=>c!==t),s=t[r].map(c=>al([...a,c]));if(s.some(c=>!c))return;o={[r]:s}}o&&(delete e.allOf,Sr(e,o))}function dt(e,n){let i=e.seen.get(n);if(!i)throw new Error("Unprocessed schema. This is a bug in Zod.");let o=s=>{let c=e.seen.get(s);if(c.ref===null)return;let u=c.def??c.schema,d={...u},p=c.ref;if(c.ref=null,p){o(p);let f=e.seen.get(p),g=f.schema;if(g.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(u.allOf=u.allOf??[],u.allOf.push(g)):Sr(u,g),Sr(u,d),s._zod.parent===p)for(let x in u)x==="$ref"||x==="allOf"||x in d||delete u[x];if(g.$ref&&f.def)for(let x in u)x==="$ref"||x==="allOf"||x in f.def&&JSON.stringify(u[x])===JSON.stringify(f.def[x])&&delete u[x]}let m=s._zod.parent;if(m&&m!==p){o(m);let f=e.seen.get(m);if(f?.schema.$ref&&(u.$ref=f.schema.$ref,f.def))for(let g in u)g==="$ref"||g==="allOf"||g in f.def&&JSON.stringify(u[g])===JSON.stringify(f.def[g])&&delete u[g]}e.override({zodSchema:s,jsonSchema:u,path:c.path??[]})};if(!e.external||e.sharedEmitDoneFor!==e.external){for(let s of[...e.seen.entries()].reverse())o(s[0]);if(e.target!=="openapi-3.0")for(let s of e.seen.entries())tg(s[1].def??s[1].schema);for(let s of e.deferred)s();if(e.intersections.length){let s=new Map;for(let c of e.seen.values())for(let u of[c.schema,c.def]){let d=u?.allOf;if(!Array.isArray(d))continue;let p=s.get(d);p?p.push(u):s.set(d,[u])}for(let c of e.intersections)for(let u of s.get(c)??[])dx(u)}}let t={};if(e.target==="draft-2020-12"?t.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?t.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?t.$schema="http://json-schema.org/draft-04/schema#":e.target,e.external?.uri){let s=e.external.registry.get(n)?.id;if(!s)throw new Error("Schema is missing an `id` property");t.$id=e.external.uri(s)}Sr(t,i.defId?i.schema:i.def??i.schema);let r=e.metadataRegistry.get(n)?.id;r!==void 0&&t.id===r&&delete t.id;let a=e.external?.defs??{};if(!e.external||e.sharedEmitDoneFor!==e.external)for(let s of e.seen.entries()){let c=s[1];c.def&&c.defId&&(c.def.id===c.defId&&delete c.def.id,se(a,c.defId,c.def))}e.external&&(e.sharedEmitDoneFor=e.external),e.external||Object.keys(a).length>0&&(e.target==="draft-2020-12"?t.$defs=a:t.definitions=a);try{let s=JSON.parse(JSON.stringify(t));return Object.defineProperty(s,"~standard",{value:{...n["~standard"],jsonSchema:{input:$n(n,"input",e.processors),output:$n(n,"output",e.processors)}},enumerable:!1,writable:!1}),s}catch{throw new Error("Error converting schema to JSON.")}}function fe(e,n){let i=n??{seen:new Set};if(i.seen.has(e))return!1;i.seen.add(e);let o=e._zod.def;if(o.type==="transform")return!0;if(o.type==="array")return fe(o.element,i);if(o.type==="set")return fe(o.valueType,i);if(o.type==="lazy")return fe(o.getter(),i);if(o.type==="promise"||o.type==="optional"||o.type==="nonoptional"||o.type==="nullable"||o.type==="readonly"||o.type==="default"||o.type==="prefault"||o.type==="catch")return fe(o.innerType,i);if(o.type==="intersection")return fe(o.left,i)||fe(o.right,i);if(o.type==="record"||o.type==="map")return fe(o.keyType,i)||fe(o.valueType,i);if(o.type==="pipe")return e._zod.traits.has("$ZodCodec")?!0:fe(o.in,i)||fe(o.out,i);if(o.type==="object"){for(let t in o.shape)if(fe(o.shape[t],i))return!0;return!1}if(o.type==="union"){for(let t of o.options)if(fe(t,i))return!0;return!1}if(o.type==="tuple"){for(let t of o.items)if(fe(t,i))return!0;return!!(o.rest&&fe(o.rest,i))}return!1}var sl=(e,n={})=>i=>{let o=ut({...i,processors:n});return J(e,o),lt(o,e),dt(o,e)},$n=(e,n,i={})=>o=>{let{libraryOptions:t,target:r}=o??{},a=ut({...t??{},target:r,io:n,processors:i});return J(e,a),lt(a,e),dt(a,e)};var kn=(e,n,i)=>{(e[n]===void 0||i>e[n])&&(e[n]=i)},xn=(e,n,i)=>{(e[n]===void 0||i{kn(e,"minimum",n),xn(e,"maximum",n)},sg=(e,n)=>{e.multipleOf??(e.multipleOf=[]),e.multipleOf.includes(n)||e.multipleOf.push(n)},cg=(e,n)=>{e.patterns??(e.patterns=new Set),e.patterns.add(n)},ug=(e,n)=>{e.mime=e.mime?e.mime.filter(i=>n.includes(i)):[...n]},lg=(e,n)=>{e.format=n,n.includes("int")&&(e.isInt=!0)},ig=(e,n)=>kn(e,"minimum",n.minimum),og=(e,n)=>xn(e,"maximum",n.maximum),ag=e=>(n,i)=>{lg(n,i.format);let[o,t]=e[i.format];kn(n,"minimum",o),xn(n,"maximum",t)},px={greater_than:(e,n)=>kn(e,n.inclusive?"minimum":"exclusiveMinimum",n.value),less_than:(e,n)=>xn(e,n.inclusive?"maximum":"exclusiveMaximum",n.value),multiple_of:(e,n)=>sg(e,n.value),number_format:ag(Kn),bigint_format:ag(Wn),min_length:ig,max_length:og,length_equals:(e,n)=>rg(e,n.length),min_size:ig,max_size:og,size_equals:(e,n)=>rg(e,n.size),string_format:(e,n)=>{lg(e,n.format),n.pattern&&cg(e,n.pattern),(n.format==="base64"||n.format==="base64url")&&(e.contentEncoding=n.format),(n.local||n.precision===-1)&&(e.laxFormat=!0)},mime_type:(e,n)=>ug(e,n.mime)};function ie(e){let n={},i=e._zod.def,o=e._zod.traits.has("$ZodCheck")?[e,...i.checks??[]]:i.checks??[];for(let r of o)px[r._zod.def.check]?.(n,r._zod.def);let t=e._zod.bag;t.minimum!==void 0&&kn(n,"minimum",t.minimum),t.exclusiveMinimum!==void 0&&kn(n,"exclusiveMinimum",t.exclusiveMinimum),t.maximum!==void 0&&xn(n,"maximum",t.maximum),t.exclusiveMaximum!==void 0&&xn(n,"exclusiveMaximum",t.exclusiveMaximum),t.multipleOf!==void 0&&sg(n,t.multipleOf),t.format!==void 0&&(n.format??(n.format=t.format),t.format.includes("int")&&(n.isInt=!0)),t.mime&&ug(n,t.mime);for(let r of t.patterns??[])cg(n,r);return n}var mx={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},fx=new Map([[Ni,ms],[dr,fs]]),dg=e=>fx.get(e)??e,ll=(e,n,i,o)=>{let t=i;t.type="string";let{minimum:r,maximum:a,format:s,patterns:c,contentEncoding:u,laxFormat:d}=ie(e);if(typeof r=="number"&&(t.minLength=r),typeof a=="number"&&(t.maxLength=a),s&&(t.format=mx[s]??s,t.format===""&&delete t.format,(s==="time"||d)&&delete t.format),u&&(t.contentEncoding=u),c&&c.size>0){let p=[...c].map(dg);p.length===1?t.pattern=p[0].source:p.length>1&&(t.allOf=[...p.map(m=>({...n.target==="draft-07"||n.target==="draft-04"||n.target==="openapi-3.0"?{type:"string"}:{},pattern:m.source}))])}},dl=(e,n,i,o)=>{let t=i,{minimum:r,maximum:a,multipleOf:s,exclusiveMaximum:c,exclusiveMinimum:u,isInt:d}=ie(e);t.type=d?"integer":"number";let p=typeof u=="number"&&u>=(r??Number.NEGATIVE_INFINITY),m=typeof c=="number"&&c<=(a??Number.POSITIVE_INFINITY),f=n.target==="draft-04"||n.target==="openapi-3.0";if(p?f?(t.minimum=u,t.exclusiveMinimum=!0):t.exclusiveMinimum=u:typeof r=="number"&&(t.minimum=r),m?f?(t.maximum=c,t.exclusiveMaximum=!0):t.exclusiveMaximum=c:typeof a=="number"&&(t.maximum=a),s){let g=new Set;for(let I of s)Number.isFinite(I)&&I!==0?g.add(Math.abs(I)):re(e,n,t,o,`A multipleOf divisor of ${I} cannot be represented in JSON Schema`);let[y,...x]=g;y!==void 0&&(t.multipleOf=y),x.length&&(t.allOf=[...t.allOf??[],...x.map(I=>({multipleOf:I}))])}},pl=(e,n,i,o)=>{i.type="boolean"},ml=(e,n,i,o)=>{re(e,n,i,o,"BigInt cannot be represented in JSON Schema")},fl=(e,n,i,o)=>{re(e,n,i,o,"Symbols cannot be represented in JSON Schema")},gl=(e,n,i,o)=>{n.target==="openapi-3.0"?(i.type="string",i.nullable=!0,i.enum=[null]):i.type="null"},hl=(e,n,i,o)=>{re(e,n,i,o,"Undefined cannot be represented in JSON Schema")},vl=(e,n,i,o)=>{re(e,n,i,o,"Void cannot be represented in JSON Schema")},yl=(e,n,i,o)=>{i.not={}},bl=(e,n,i,o)=>{},_l=(e,n,i,o)=>{},$l=(e,n,i,o)=>{re(e,n,i,o,"Date cannot be represented in JSON Schema")},kl=(e,n,i,o)=>{let t=e._zod.def,r=Ht(t.entries);if(r.length===0){i.not={};return}r.every(a=>typeof a=="number")&&(i.type="number"),r.every(a=>typeof a=="string")&&(i.type="string"),i.enum=r},xl=(e,n,i,o)=>{let t=e._zod.def;if(t.values.length===0){i.not={};return}let r=[];for(let a of t.values)if(a===void 0){if(re(e,n,i,o,"Literal `undefined` cannot be represented in JSON Schema"))return}else if(typeof a=="bigint"){if(re(e,n,i,o,"BigInt literals cannot be represented in JSON Schema"))return;r.push(Number(a))}else r.push(a);if(r.length!==0)if(r.length===1){let a=r[0];i.type=a===null?"null":typeof a,n.target==="draft-04"||n.target==="openapi-3.0"?i.enum=[a]:i.const=a}else r.every(a=>typeof a=="number")&&(i.type="number"),r.every(a=>typeof a=="string")&&(i.type="string"),r.every(a=>typeof a=="boolean")&&(i.type="boolean"),r.every(a=>a===null)&&(i.type="null"),i.enum=r},wl=(e,n,i,o)=>{re(e,n,i,o,"NaN cannot be represented in JSON Schema")},Il=(e,n,i,o)=>{let t=i,r=e._zod.pattern;if(!r)throw new Error("Pattern not found in template literal");t.type="string",t.pattern=r.source},Sl=(e,n,i,o)=>{let t=i;t.type="string",t.format="binary",t.contentEncoding="binary";let{minimum:r,maximum:a,mime:s}=ie(e);r!==void 0&&(t.minLength=r),a!==void 0&&(t.maxLength=a),s&&(s.length===0?t.not={}:s.length===1?t.contentMediaType=s[0]:t.anyOf=s.map(c=>({contentMediaType:c})))},Pl=(e,n,i,o)=>{i.type="boolean"},Tl=(e,n,i,o)=>{re(e,n,i,o,"Custom types cannot be represented in JSON Schema")},El=(e,n,i,o)=>{re(e,n,i,o,"Function types cannot be represented in JSON Schema")},Al=(e,n,i,o)=>{re(e,n,i,o,"Transforms cannot be represented in JSON Schema")},Ol=(e,n,i,o)=>{re(e,n,i,o,"Map cannot be represented in JSON Schema")},Nl=(e,n,i,o)=>{re(e,n,i,o,"Set cannot be represented in JSON Schema")},zl=(e,n,i,o)=>{let t=i,r=e._zod.def,{minimum:a,maximum:s}=ie(e);typeof a=="number"&&(t.minItems=a),typeof s=="number"&&(t.maxItems=s),t.type="array",t.items=J(r.element,n,{...o,path:[...o.path,"items"]})};function Pr(e){let n=e._zod.def;return n.type==="pipe"&&n.in._zod.traits.has("$ZodTransform")?Pr(n.out):n.type==="catch"?Pr(n.innerType):e._zod.optin}var Dl=(e,n,i,o)=>{let t=i,r=e._zod.def,a=r.shape;if(Object.getOwnPropertySymbols(a).length&&re(e,n,t,o,"Symbol keys cannot be represented in JSON Schema"))return;t.type="object",t.properties={};for(let u in a)se(t.properties,u,J(a[u],n,{...o,path:[...o.path,"properties",u]}));let c=[];for(let u of Object.keys(a)){let d=r.shape[u];(n.io==="input"?Pr(d)===void 0:d._zod.optout===void 0)&&c.push(u)}c.length>0&&(t.required=c),r.catchall?._zod.def.type==="never"?t.additionalProperties=!1:r.catchall?r.catchall&&(t.additionalProperties=J(r.catchall,n,{...o,path:[...o.path,"additionalProperties"]})):n.io==="output"&&(t.additionalProperties=!1)},Ao=(e,n,i,o)=>{let t=e._zod.def,r=t.inclusive===!1,a=t.options.map((s,c)=>J(s,n,{...o,path:[...o.path,r?"oneOf":"anyOf",c]}));r?i.oneOf=a:i.anyOf=a},Cl=(e,n,i,o)=>{let t=e._zod.def,r=J(t.left,n,{...o,path:[...o.path,"allOf",0]}),a=J(t.right,n,{...o,path:[...o.path,"allOf",1]}),s=u=>"allOf"in u&&Object.keys(u).length===1,c=[...s(r)?r.allOf:[r],...s(a)?a.allOf:[a]];i.allOf=c,n.intersections.push(c)},jl=(e,n,i,o)=>{let t=i,r=e._zod.def;t.type="array";let a=n.target==="draft-2020-12"?"prefixItems":"items",s=n.target==="draft-2020-12"||n.target==="openapi-3.0"?"items":"additionalItems",c=r.items.map((y,x)=>J(y,n,{...o,path:[...o.path,a,x]})),u=r.rest?J(r.rest,n,{...o,path:[...o.path,s,...n.target==="openapi-3.0"?[r.items.length]:[]]}):null,d=r.items.length;for(;d>0;){let y=r.items[d-1];if(!(n.io==="input"?Pr(y)!==void 0:y._zod.optout==="optional"))break;d--}let p=r.items.length,m=!r.rest;n.target==="draft-2020-12"?(t.prefixItems=c,m?t.items=!1:u&&(t.items=u),d>0&&(t.minItems=d),m&&(t.maxItems=p)):n.target==="openapi-3.0"?(t.items={anyOf:c},u&&t.items.anyOf.push(u),d>0&&(t.minItems=d),m&&(t.maxItems=p)):(t.items=c,m?t.additionalItems=!1:u&&(t.additionalItems=u),d>0&&(t.minItems=d),m&&(t.maxItems=p));let{minimum:f,maximum:g}=ie(e);typeof f=="number"&&(t.minItems=f),typeof g=="number"&&(t.maxItems=g)};function cl(e,n,i){if(n.$ref){if(i.has(n))return n;i.add(n);let g=e.get(n)?.def;if(!g)return n;let y=cl(e,g,i);return y===g?n:y}for(let g of["anyOf","oneOf"]){let y=n[g];if(!Array.isArray(y))continue;let x=y.map(I=>cl(e,I,i));x.some((I,b)=>I!==y[b])&&(n={...n,[g]:x})}let o=Array.isArray(n.type)?n.type:[n.type],t=!o.includes("string")&&o.some(g=>g==="number"||g==="integer"),r=n.enum??(n.const!==void 0?[n.const]:void 0);if(!t&&!r?.some(g=>typeof g=="number"))return n;let{minimum:a,maximum:s,exclusiveMinimum:c,exclusiveMaximum:u,multipleOf:d,format:p,id:m,...f}=n;return f.enum?f.enum=f.enum.map(g=>typeof g=="number"?String(g):g):typeof f.const=="number"&&(f.const=String(f.const)),t&&(f.type="string",r||(f.pattern=(o.includes("number")?qe:ir).source)),f}var ul=new WeakMap;function gx(e){let n=new Map;for(let o of e.seen.values())o.def&&!n.has(o.schema)&&n.set(o.schema,o);let i=new Map;for(let o of ul.get(e)??[]){let t=e.seen.get(o),r=(t?.def??t?.schema)?.propertyNames;if(!r||r===!0||i.has(r))continue;let a=cl(n,r,new Set);a!==r&&i.set(r,a)}if(i.size)for(let o of e.seen.values())for(let t of[o.schema,o.def]){let r=t&&i.get(t.propertyNames);r&&(t.propertyNames=r)}}var Ll=(e,n,i,o)=>{let t=i,r=e._zod.def;t.type="object";let a=r.keyType,s=ie(a).patterns;if(r.mode==="loose"&&s&&s.size>0){let d=J(r.valueType,n,{...o,path:[...o.path,"patternProperties","*"]});t.patternProperties={};for(let p of s)se(t.patternProperties,dg(p).source,d)}else{if(n.target==="draft-07"||n.target==="draft-2020-12"){t.propertyNames=J(r.keyType,n,{...o,path:[...o.path,"propertyNames"]});let d=ul.get(n);d||(d=[],ul.set(n,d),n.deferred.push(()=>gx(n))),d.push(e)}t.additionalProperties=J(r.valueType,n,{...o,path:[...o.path,"additionalProperties"]})}let c=a._zod.values,u=n.io==="input"&&Pr(r.valueType)!==void 0;if(c&&!r.partial&&!u){let d=[...c].filter(p=>typeof p=="string"||typeof p=="number");d.length>0&&(t.required=d.map(String))}},Ul=(e,n,i,o)=>{let t=e._zod.def,r=J(t.innerType,n,o),a=n.seen.get(e);n.target==="openapi-3.0"?(a.ref=t.innerType,i.nullable=!0):i.anyOf=[r,{type:"null"}]},Rl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType},Ml=Symbol();function pg(e,n,i,o,t){let r=!1,a=JSON.stringify(e,(s,c)=>typeof c!="bigint"?c:(r=!0,null));return r?(re(n,i,o,t,"BigInt defaults cannot be represented in JSON Schema"),Ml):JSON.parse(a)}var Zl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType;let a=pg(t.defaultValue,e,n,i,o);a!==Ml&&(i.default=a)},Vl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);if(r.ref=t.innerType,n.io!=="input")return;let a=pg(t.defaultValue,e,n,i,o);a!==Ml&&(i._prefault=a)},Gl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType;let a;try{a=t.catchValue(void 0)}catch{re(e,n,i,o,"Dynamic catch values are not supported in JSON Schema");return}i.default=a},Fl=(e,n,i,o)=>{let t=e._zod.def,r=t.in._zod.traits.has("$ZodTransform"),a=n.io==="input"?r?t.out:t.in:t.out;J(a,n,o);let s=n.seen.get(e);s.ref=a},Bl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType,i.readOnly=!0},Hl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType},Oo=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType},Kl=(e,n,i,o)=>{let t=e._zod.innerType;J(t,n,o);let r=n.seen.get(e);r.ref=t},Eo={string:ll,number:dl,boolean:pl,bigint:ml,symbol:fl,null:gl,undefined:hl,void:vl,never:yl,any:bl,unknown:_l,date:$l,enum:kl,literal:xl,nan:wl,template_literal:Il,file:Sl,success:Pl,custom:Tl,function:El,transform:Al,map:Ol,set:Nl,array:zl,object:Dl,union:Ao,intersection:Cl,tuple:jl,record:Ll,nullable:Ul,nonoptional:Rl,default:Zl,prefault:Vl,catch:Gl,pipe:Fl,readonly:Bl,promise:Hl,optional:Oo,lazy:Kl};function No(e,n){if("_idmap"in e){let o=e,t=ut({...n,processors:Eo}),r={};for(let c of o._idmap.entries()){let[u,d]=c;J(d,t)}let a={},s={registry:o,uri:n?.uri,defs:r};t.external=s;for(let c of o._idmap.entries()){let[u,d]=c;lt(t,d),se(a,u,dt(t,d))}if(Object.keys(r).length>0){let c=t.target==="draft-2020-12"?"$defs":"definitions";a.__shared={[c]:r}}return{schemas:a}}let i=ut({...n,processors:Eo});return J(e,i),lt(i,e),dt(i,e)}var zo=class{get metadataRegistry(){return this.ctx.metadataRegistry}get target(){return this.ctx.target}get unrepresentable(){return this.ctx.unrepresentable}get override(){return this.ctx.override}get io(){return this.ctx.io}get counter(){return this.ctx.counter}set counter(n){this.ctx.counter=n}get seen(){return this.ctx.seen}constructor(n){let i=n?.target??"draft-2020-12";i==="draft-4"&&(i="draft-04"),i==="draft-7"&&(i="draft-07"),this.ctx=ut({processors:Eo,target:i,...n?.metadata&&{metadata:n.metadata},...n?.unrepresentable&&{unrepresentable:n.unrepresentable},...n?.override&&{override:n.override},...n?.io&&{io:n.io}})}process(n,i={path:[],schemaPath:[]}){return J(n,this.ctx,i)}emit(n,i){i&&(i.cycles&&(this.ctx.cycles=i.cycles),i.reused&&(this.ctx.reused=i.reused),i.external&&(this.ctx.external=i.external)),this.ctx.sharedDefsExtractedFor=void 0,this.ctx.sharedEmitDoneFor=void 0,lt(this.ctx,n);let o=dt(this.ctx,n),{"~standard":t,...r}=o;return r}};var mg={};var On={};We(On,{ZodAny:()=>gd,ZodArray:()=>bd,ZodBase64:()=>Yo,ZodBase64URL:()=>Qo,ZodBigInt:()=>En,ZodBigIntFormat:()=>na,ZodBoolean:()=>Tn,ZodCIDRv4:()=>Xo,ZodCIDRv6:()=>qo,ZodCUID:()=>Go,ZodCUID2:()=>Fo,ZodCatch:()=>Md,ZodCodec:()=>Lr,ZodCreditCard:()=>sd,ZodCustom:()=>Ur,ZodCustomStringFormat:()=>Lt,ZodDate:()=>Or,ZodDefault:()=>Dd,ZodDiscriminatedUnion:()=>$d,ZodE164:()=>ea,ZodEmail:()=>Ro,ZodEmoji:()=>Zo,ZodEnum:()=>In,ZodExactOptional:()=>aa,ZodFile:()=>Ad,ZodFunction:()=>Xd,ZodGUID:()=>Mo,ZodIBAN:()=>cd,ZodIPv4:()=>Wo,ZodIPv6:()=>Jo,ZodISODate:()=>mt,ZodISODateTime:()=>pt,ZodISODuration:()=>gt,ZodISOTime:()=>ft,ZodInstanceOf:()=>Qd,ZodIntersection:()=>kd,ZodJWT:()=>ta,ZodKSUID:()=>Ko,ZodLazy:()=>Kd,ZodLiteral:()=>Ed,ZodMAC:()=>ad,ZodMap:()=>Pd,ZodNaN:()=>Vd,ZodNanoID:()=>Vo,ZodNever:()=>vd,ZodNonOptional:()=>sa,ZodNull:()=>md,ZodNullable:()=>zd,ZodNumber:()=>Pn,ZodNumberFormat:()=>Ut,ZodObject:()=>zr,ZodOptional:()=>Cr,ZodPipe:()=>jr,ZodPrefault:()=>jd,ZodPreprocess:()=>Gd,ZodPromise:()=>Jd,ZodReadonly:()=>Fd,ZodRecord:()=>wn,ZodSet:()=>Td,ZodString:()=>Sn,ZodStringFormat:()=>X,ZodSuccess:()=>Rd,ZodSymbol:()=>dd,ZodTemplateLiteral:()=>Hd,ZodTransform:()=>Od,ZodTuple:()=>wd,ZodType:()=>M,ZodULID:()=>Bo,ZodURL:()=>Ar,ZodUUID:()=>Fe,ZodUndefined:()=>pd,ZodUnion:()=>Dr,ZodUnknown:()=>hd,ZodVoid:()=>yd,ZodXID:()=>Ho,ZodXor:()=>_d,_ZodString:()=>Uo,_default:()=>Cd,_function:()=>Ih,any:()=>nh,array:()=>Nr,base64:()=>jg,base64url:()=>Lg,bigint:()=>qg,boolean:()=>ld,catch:()=>Zd,check:()=>Sh,cidrv4:()=>Dg,cidrv6:()=>Cg,codec:()=>$h,creditCard:()=>Rg,cuid:()=>Sg,cuid2:()=>Pg,currencyCode:()=>Bg,custom:()=>Ph,date:()=>ih,describe:()=>Th,discriminatedUnion:()=>lh,e164:()=>Ug,email:()=>hg,emoji:()=>wg,enum:()=>ia,exactOptional:()=>Nd,file:()=>vh,float32:()=>Kg,float64:()=>Wg,function:()=>Ih,guid:()=>vg,hash:()=>Hg,hex:()=>Fg,hostname:()=>Gg,httpUrl:()=>xg,iban:()=>Mg,instanceof:()=>Ah,int:()=>jo,int32:()=>Jg,int64:()=>Yg,intersection:()=>xd,invertCodec:()=>kh,ipv4:()=>Og,ipv6:()=>zg,json:()=>Nh,jwt:()=>Zg,keyof:()=>oh,ksuid:()=>Ag,lazy:()=>Wd,literal:()=>hh,looseObject:()=>ch,looseRecord:()=>ph,mac:()=>Ng,map:()=>mh,meta:()=>Eh,nan:()=>_h,nanoid:()=>Ig,nativeEnum:()=>gh,never:()=>ra,nonoptional:()=>Ud,null:()=>fd,nullable:()=>Er,nullish:()=>yh,number:()=>ud,object:()=>ah,optional:()=>Ct,partialRecord:()=>dh,pipe:()=>Lo,prefault:()=>Ld,preprocess:()=>zh,promise:()=>wh,readonly:()=>Bd,record:()=>Sd,refine:()=>qd,set:()=>fh,strictObject:()=>sh,string:()=>Tr,stringFormat:()=>Vg,stringbool:()=>Oh,success:()=>bh,superRefine:()=>Yd,symbol:()=>eh,templateLiteral:()=>xh,transform:()=>oa,tuple:()=>Id,uint32:()=>Xg,uint64:()=>Qg,ulid:()=>Tg,undefined:()=>th,union:()=>An,unknown:()=>Dt,url:()=>kg,uuid:()=>yg,uuidv4:()=>bg,uuidv6:()=>_g,uuidv7:()=>$g,void:()=>rh,xid:()=>Eg,xor:()=>uh});var Do={};We(Do,{endsWith:()=>mn,gt:()=>Ve,gte:()=>Ie,includes:()=>dn,length:()=>Nt,lowercase:()=>un,lt:()=>Ze,lte:()=>we,maxLength:()=>Ot,maxSize:()=>ct,mime:()=>gn,minLength:()=>Ye,minSize:()=>Ge,multipleOf:()=>st,negative:()=>Io,nonnegative:()=>Po,nonpositive:()=>So,normalize:()=>hn,overwrite:()=>Ce,positive:()=>wo,properties:()=>fn,property:()=>To,regex:()=>cn,size:()=>At,slugify:()=>_n,startsWith:()=>pn,toLowerCase:()=>yn,toUpperCase:()=>bn,trim:()=>vn,uppercase:()=>ln});var fg=new WeakSet([Object.prototype,Error.prototype]);function Co(e,n,i){Object.defineProperty(e,n,{configurable:!0,enumerable:!1,get(){let o=i(this);return Object.defineProperty(this,n,{value:o,configurable:!0,writable:!0}),o},set(o){Object.defineProperty(this,n,{value:o,configurable:!0,writable:!0})}})}var gg=(e,n)=>{Ka.init(e,n),e.name="ZodError";let i=Object.getPrototypeOf(e);fg.has(i)||(fg.add(i),Co(i,"format",o=>t=>er(o,t)),Co(i,"flatten",o=>t=>Qn(o,t)),Co(i,"addIssue",o=>t=>{o.issues.push(t),o.message=JSON.stringify(o.issues,Kt,2)}),Co(i,"addIssues",o=>t=>{o.issues.push(...t),o.message=JSON.stringify(o.issues,Kt,2)}),Object.defineProperty(i,"isEmpty",{configurable:!0,enumerable:!1,get(){return this.issues.length===0}}))},vx=h("ZodError",gg),xe=h("ZodError",gg,void 0,{Parent:Error});var Wl=Yt(xe),Jl=Qt(xe),Xl=en(xe),ql=tn(xe),Yl=ui(xe),Ql=li(xe),ed=di(xe),td=pi(xe),nd=mi(xe),rd=fi(xe),id=gi(xe),od=hi(xe);function bx(){oe.localeError||ee(Wi())}function jt(){oe.memoizer||ee({memoizer:yr()})}var M=h("ZodType",(e,n)=>(bx(),U.init(e,n),e.def=n,e.type=n.type,e),{check(...e){let n=this.def;return this.clone(_.mergeDefs(n,{checks:[...n.checks??[],...e.map(i=>typeof i=="function"?{_zod:{check:i,def:{check:"custom"},onattach:[]}}:i)]}),{parent:!0})},with(...e){return this.check(...e)},clone(e,n){return K(this,e,n)},brand(){return this},register(e,n){return e.add(this,n),this},refine(e,n){return this.check(qd(e,n))},superRefine(e,n){return this.check(Yd(e,n))},overwrite(e){return this.check(Ce(e))},optional(){return Ct(this)},exactOptional(){return Nd(this)},nullable(){return Er(this)},nullish(){return Ct(Er(this))},nonoptional(e){return Ud(this,e)},array(){return Nr(this)},or(e){return An([this,e])},and(e){return xd(this,e)},transform(e){return Lo(this,oa(e))},default(e){return Cd(this,e)},prefault(e){return Ld(this,e)},catch(e){return Zd(this,e)},pipe(e){return Lo(this,e)},readonly(){return Bd(this)},describe(e){let n=this.clone();return ce.add(n,{description:e}),n},meta(...e){if(e.length===0)return ce.get(this);let n=this.clone();return ce.add(n,e[0]),n},isOptional(){return this.safeParse(void 0).success},isNullable(){return this.safeParse(null).success},apply(e,...n){return n.length===0?e(this):e(this,...n)},get"~standard"(){return _.hide(this,"~standard",{...Pi(this),jsonSchema:{input:$n(this,"input"),output:$n(this,"output")}})},set"~standard"(e){_.own(this,"~standard",e)},parse:function e(n,i){return Wl(this,n,i,{callee:e})},parseAsync:async function e(n,i){return await Jl(this,n,i,{callee:e})},safeParse(e,n){return Xl(this,e,n)},async safeParseAsync(e,n){return ql(this,e,n)},get spa(){return this?.safeParseAsync},set spa(e){_.own(this,"spa",e)},validate(e,n){return tr(this,e,n)},validateAsync(e,n){return nr(this,e,n)},encode:function e(n,i){return Yl(this,n,i,{callee:e})},decode:function e(n,i){return Ql(this,n,i,{callee:e})},encodeAsync:async function e(n,i){return await ed(this,n,i,{callee:e})},decodeAsync:async function e(n,i){return await td(this,n,i,{callee:e})},safeEncode(e,n){return nd(this,e,n)},safeDecode(e,n){return rd(this,e,n)},async safeEncodeAsync(e,n){return id(this,e,n)},async safeDecodeAsync(e,n){return od(this,e,n)},toJSONSchema(e){return sl(this,{})(e)},get description(){return ce.get(this)?.description},get _def(){return this._zod.def}}),Uo=h("_ZodString",(e,n)=>{St.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>ll(e,i,o,t)},_.derived({format:e=>ie(e).format??null,minLength:e=>ie(e).minimum??null,maxLength:e=>ie(e).maximum??null},{regex(...e){return this.check(cn(...e))},includes(...e){return this.check(dn(...e))},startsWith(...e){return this.check(pn(...e))},endsWith(...e){return this.check(mn(...e))},min(...e){return this.check(Ye(...e))},max(...e){return this.check(Ot(...e))},length(...e){return this.check(Nt(...e))},nonempty(...e){return this.check(Ye(1,...e))},lowercase(e){return this.check(un(e))},uppercase(e){return this.check(ln(e))},trim(){return this.check(vn())},normalize(...e){return this.check(hn(...e))},toLowerCase(){return this.check(yn())},toUpperCase(){return this.check(bn())},slugify(){return this.check(_n())}})),Sn=h("ZodString",(e,n)=>{St.init(e,n),Uo.init(e,n)},{email(e){return this.check(no(Ro,e))},url(e){return this.check($r(Ar,e))},jwt(e){return this.check(xo(ta,e))},emoji(e){return this.check(co(Zo,e))},guid(e){return this.check(ro(Mo,e))},uuid(e){return this.check(io(Fe,e))},uuidv4(e){return this.check(oo(Fe,e))},uuidv6(e){return this.check(ao(Fe,e))},uuidv7(e){return this.check(so(Fe,e))},nanoid(e){return this.check(uo(Vo,e))},cuid(e){return this.check(lo(Go,e))},cuid2(e){return this.check(po(Fo,e))},ulid(e){return this.check(mo(Bo,e))},base64(e){return this.check(_o(Yo,e))},base64url(e){return this.check($o(Qo,e))},xid(e){return this.check(fo(Ho,e))},ksuid(e){return this.check(go(Ko,e))},ipv4(e){return this.check(ho(Wo,e))},ipv6(e){return this.check(vo(Jo,e))},cidrv4(e){return this.check(yo(Xo,e))},cidrv6(e){return this.check(bo(qo,e))},e164(e){return this.check(ko(ea,e))},datetime(e){return this.check(kr(pt,e))},date(e){return this.check(xr(mt,e))},time(e){return this.check(wr(ft,e))},duration(e){return this.check(Ir(gt,e))}});function Tr(e){return xu(Sn,e)}var X=h("ZodStringFormat",(e,n)=>{W.init(e,n),Uo.init(e,n)}),pt=h("ZodISODateTime",(e,n)=>{mc.init(e,n),X.init(e,n)}),mt=h("ZodISODate",(e,n)=>{fc.init(e,n),X.init(e,n)}),ft=h("ZodISOTime",(e,n)=>{gc.init(e,n),X.init(e,n)}),gt=h("ZodISODuration",(e,n)=>{hc.init(e,n),X.init(e,n)}),Ro=h("ZodEmail",(e,n)=>{nc.init(e,n),X.init(e,n)});function hg(e){return no(Ro,e)}var Mo=h("ZodGUID",(e,n)=>{ec.init(e,n),X.init(e,n)});function vg(e){return ro(Mo,e)}var Fe=h("ZodUUID",(e,n)=>{tc.init(e,n),X.init(e,n)});function yg(e){return io(Fe,e)}function bg(e){return oo(Fe,e)}function _g(e){return ao(Fe,e)}function $g(e){return so(Fe,e)}var Ar=h("ZodURL",(e,n)=>{oc.init(e,n),X.init(e,n)});function kg(e){return $r(Ar,e)}function xg(e){return $r(Ar,{protocol:rr,hostname:hs,..._.normalizeParams(e)})}var Zo=h("ZodEmoji",(e,n)=>{ac.init(e,n),X.init(e,n)});function wg(e){return co(Zo,e)}var Vo=h("ZodNanoID",(e,n)=>{sc.init(e,n),X.init(e,n)});function Ig(e){return uo(Vo,e)}var Go=h("ZodCUID",(e,n)=>{cc.init(e,n),X.init(e,n)});function Sg(e){return lo(Go,e)}var Fo=h("ZodCUID2",(e,n)=>{uc.init(e,n),X.init(e,n)});function Pg(e){return po(Fo,e)}var Bo=h("ZodULID",(e,n)=>{lc.init(e,n),X.init(e,n)});function Tg(e){return mo(Bo,e)}var Ho=h("ZodXID",(e,n)=>{dc.init(e,n),X.init(e,n)});function Eg(e){return fo(Ho,e)}var Ko=h("ZodKSUID",(e,n)=>{pc.init(e,n),X.init(e,n)});function Ag(e){return go(Ko,e)}var Wo=h("ZodIPv4",(e,n)=>{vc.init(e,n),X.init(e,n)});function Og(e){return ho(Wo,e)}var ad=h("ZodMAC",(e,n)=>{bc.init(e,n),X.init(e,n)});function Ng(e){return Iu(ad,e)}var Jo=h("ZodIPv6",(e,n)=>{yc.init(e,n),X.init(e,n)});function zg(e){return vo(Jo,e)}var Xo=h("ZodCIDRv4",(e,n)=>{_c.init(e,n),X.init(e,n)});function Dg(e){return yo(Xo,e)}var qo=h("ZodCIDRv6",(e,n)=>{$c.init(e,n),X.init(e,n)});function Cg(e){return bo(qo,e)}var Yo=h("ZodBase64",(e,n)=>{kc.init(e,n),X.init(e,n)});function jg(e){return _o(Yo,e)}var Qo=h("ZodBase64URL",(e,n)=>{xc.init(e,n),X.init(e,n)});function Lg(e){return $o(Qo,e)}var ea=h("ZodE164",(e,n)=>{wc.init(e,n),X.init(e,n)});function Ug(e){return ko(ea,e)}var sd=h("ZodCreditCard",(e,n)=>{Ic.init(e,n),X.init(e,n)});function Rg(e){return Su(sd,e)}var cd=h("ZodIBAN",(e,n)=>{Sc.init(e,n),X.init(e,n)});function Mg(e){return Pu(cd,e)}var ta=h("ZodJWT",(e,n)=>{Pc.init(e,n),X.init(e,n)});function Zg(e){return xo(ta,e)}var Lt=h("ZodCustomStringFormat",(e,n)=>{Tc.init(e,n),X.init(e,n)});function Vg(e,n,i={}){return zt(Lt,e,n,i)}function Gg(e){return zt(Lt,"hostname",gs,e)}function Fg(e){return zt(Lt,"hex",As,e)}function Bg(e){return zt(Lt,"currency_code",ys,e)}function Hg(e,n){let i=n?.enc??"hex",o=`${e}_${i}`,t=Te[o];if(!t)throw new Error(`Unrecognized hash format: ${o}`);return zt(Lt,o,t,n)}var Pn=h("ZodNumber",(e,n)=>{Li.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>dl(e,i,o,t),e.isFinite=!0},_.derived({minValue:e=>{let{minimum:n,exclusiveMinimum:i}=ie(e);return Math.max(n??Number.NEGATIVE_INFINITY,i??Number.NEGATIVE_INFINITY)},maxValue:e=>{let{maximum:n,exclusiveMaximum:i}=ie(e);return Math.min(n??Number.POSITIVE_INFINITY,i??Number.POSITIVE_INFINITY)},isInt:e=>{let{isInt:n,multipleOf:i}=ie(e);return!!n||!!i?.some(Number.isSafeInteger)},format:e=>ie(e).format??null},{gt(e,n){return this.check(Ve(e,n))},gte(e,n){return this.check(Ie(e,n))},min(e,n){return this.check(Ie(e,n))},lt(e,n){return this.check(Ze(e,n))},lte(e,n){return this.check(we(e,n))},max(e,n){return this.check(we(e,n))},int(e){return this.check(jo(e))},safe(e){return this.check(jo(e))},positive(e){return this.check(Ve(0,e))},nonnegative(e){return this.check(Ie(0,e))},negative(e){return this.check(Ze(0,e))},nonpositive(e){return this.check(we(0,e))},multipleOf(e,n){return this.check(st(e,n))},step(e,n){return this.check(st(e,n))},finite(){return this}}));function ud(e){return Eu(Pn,e)}var Ut=h("ZodNumberFormat",(e,n)=>{Ec.init(e,n),Pn.init(e,n)});function jo(e){return Ou(Ut,e)}function Kg(e){return Nu(Ut,e)}function Wg(e){return zu(Ut,e)}function Jg(e){return Du(Ut,e)}function Xg(e){return Cu(Ut,e)}var Tn=h("ZodBoolean",(e,n)=>{pr.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>pl(e,i,o,t)});function ld(e){return ju(Tn,e)}var En=h("ZodBigInt",(e,n)=>{Ui.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>ml(e,i,o,t)},_.derived({minValue:e=>ie(e).minimum??null,maxValue:e=>ie(e).maximum??null,format:e=>ie(e).format??null},{gte(e,n){return this.check(Ie(e,n))},min(e,n){return this.check(Ie(e,n))},gt(e,n){return this.check(Ve(e,n))},lt(e,n){return this.check(Ze(e,n))},lte(e,n){return this.check(we(e,n))},max(e,n){return this.check(we(e,n))},positive(e){return this.check(Ve(BigInt(0),e))},negative(e){return this.check(Ze(BigInt(0),e))},nonpositive(e){return this.check(we(BigInt(0),e))},nonnegative(e){return this.check(Ie(BigInt(0),e))},multipleOf(e,n){return this.check(st(e,n))}}));function qg(e){return Uu(En,e)}var na=h("ZodBigIntFormat",(e,n)=>{Ac.init(e,n),En.init(e,n)});function Yg(e){return Mu(na,e)}function Qg(e){return Zu(na,e)}var dd=h("ZodSymbol",(e,n)=>{Oc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>fl(e,i,o,t)});function eh(e){return Vu(dd,e)}var pd=h("ZodUndefined",(e,n)=>{Nc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>hl(e,i,o,t)});function th(e){return Gu(pd,e)}var md=h("ZodNull",(e,n)=>{zc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>gl(e,i,o,t)});function fd(e){return Fu(md,e)}var gd=h("ZodAny",(e,n)=>{Dc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>bl(e,i,o,t)});function nh(){return Bu(gd)}var hd=h("ZodUnknown",(e,n)=>{Cc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>_l(e,i,o,t)});function Dt(){return Hu(hd)}var vd=h("ZodNever",(e,n)=>{jc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>yl(e,i,o,t)});function ra(e){return Ku(vd,e)}var yd=h("ZodVoid",(e,n)=>{Lc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>vl(e,i,o,t)});function rh(e){return Wu(yd,e)}var Or=h("ZodDate",(e,n)=>{Uc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>$l(e,i,o,t),e.min=(i,o)=>e.check(Ie(i,o)),e.max=(i,o)=>e.check(we(i,o))},_.derived({minDate:e=>{let{minimum:n}=ie(e);return n?new Date(n):null},maxDate:e=>{let{maximum:n}=ie(e);return n?new Date(n):null}},{}));function ih(e){return Ju(Or,e)}var bd=h("ZodArray",(e,n)=>{jt(),Mc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>zl(e,i,o,t),e.element=n.element},{min(e,n){return this.check(Ye(e,n))},nonempty(e){return this.check(Ye(1,e))},max(e,n){return this.check(Ot(e,n))},length(e,n){return this.check(Nt(e,n))},unwrap(){return this.element}});function Nr(e,n){return Yu(bd,e,n)}function oh(e){let n=e._zod.def.shape;return ia(Object.keys(n))}var zr=h("ZodObject",(e,n)=>{jt(),Zc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Dl(e,i,o,t),_.installLazyProp(e,"shape",i=>i._zod.def.shape,!1)},{keyof(){return ia(Object.keys(this._zod.def.shape))},catchall(e){return this.clone(_.mergeDefs(this._zod.def,{catchall:e}))},passthrough(){return this.clone(_.mergeDefs(this._zod.def,{catchall:Dt()}))},loose(){return this.clone(_.mergeDefs(this._zod.def,{catchall:Dt()}))},strict(){return this.clone(_.mergeDefs(this._zod.def,{catchall:ra()}))},strip(){return this.clone(_.mergeDefs(this._zod.def,{catchall:void 0}))},extend(e){return _.extend(this,e)},safeExtend(e){return _.safeExtend(this,e)},merge(e){return _.merge(this,e)},pick(e){return _.pick(this,e)},omit(e){return _.omit(this,e)},partial(...e){return _.partial(Cr,this,e[0])},exactPartial(...e){return _.partial(aa,this,e[0],"exactPartial")},required(...e){return _.required(sa,this,e[0])}});function ah(e,n){let i={type:"object",shape:e??{},..._.normalizeParams(n)};return new zr(i)}function sh(e,n){return new zr({type:"object",shape:e,catchall:ra(),..._.normalizeParams(n)})}function ch(e,n){return new zr({type:"object",shape:e,catchall:Dt(),..._.normalizeParams(n)})}var Dr=h("ZodUnion",(e,n)=>{mr.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ao(e,i,o,t),e.options=n.options});function An(e,n){return new Dr({type:"union",options:e,..._.normalizeParams(n)})}var _d=h("ZodXor",(e,n)=>{Dr.init(e,n),Vc.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ao(e,i,o,t),e.options=n.options});function uh(e,n){return new _d({type:"union",options:e,inclusive:!1,..._.normalizeParams(n)})}var $d=h("ZodDiscriminatedUnion",(e,n)=>{Dr.init(e,n),Fc.init(e,n)});function lh(e,n,i){return new $d({type:"union",options:n,discriminator:e,..._.normalizeParams(i)})}var kd=h("ZodIntersection",(e,n)=>{Bc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Cl(e,i,o,t)});function xd(e,n){return new kd({type:"intersection",left:e,right:n})}var wd=h("ZodTuple",(e,n)=>{jt(),Ri.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>jl(e,i,o,t)},{rest(e){return this.clone({...this._zod.def,rest:e})},partial(){let e=this._zod.def;if(e.checks?.length)throw new Error(".partial() cannot be used on tuple schemas containing refinements");return this.clone({...e,items:e.items.map(n=>new Cr({type:"optional",innerType:n}))})}});function Id(e,n,i){let o=n instanceof U,t=o?i:n,r=o?n:null;return new wd({type:"tuple",items:e,rest:r,..._.normalizeParams(t)})}var wn=h("ZodRecord",(e,n)=>{jt(),Hc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ll(e,i,o,t),e.keyType=n.keyType,e.valueType=n.valueType});function Sd(e,n,i){return!n||!n._zod?new wn({type:"record",keyType:Tr(),valueType:e,..._.normalizeParams(n)}):new wn({type:"record",keyType:e,valueType:n,..._.normalizeParams(i)})}function dh(e,n,i){return new wn({type:"record",keyType:e,valueType:n,..._.normalizeParams(i),partial:!0})}function ph(e,n,i){return new wn({type:"record",keyType:e,valueType:n,mode:"loose",..._.normalizeParams(i)})}var Pd=h("ZodMap",(e,n)=>{jt(),Kc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ol(e,i,o,t),e.keyType=n.keyType,e.valueType=n.valueType,e.min=(...i)=>e.check(Ge(...i)),e.nonempty=i=>e.check(Ge(1,i)),e.max=(...i)=>e.check(ct(...i)),e.size=(...i)=>e.check(At(...i))});function mh(e,n,i){return new Pd({type:"map",keyType:e,valueType:n,..._.normalizeParams(i)})}var Td=h("ZodSet",(e,n)=>{jt(),Wc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Nl(e,i,o,t),e.min=(...i)=>e.check(Ge(...i)),e.nonempty=i=>e.check(Ge(1,i)),e.max=(...i)=>e.check(ct(...i)),e.size=(...i)=>e.check(At(...i))});function fh(e,n){return new Td({type:"set",valueType:e,..._.normalizeParams(n)})}var In=h("ZodEnum",(e,n)=>{Jc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(o,t,r)=>kl(e,o,t,r),e.enum=n.entries,e.options=[...e._zod.values];let i=new Set(Object.keys(n.entries));e.extract=(o,t)=>{let r={};for(let a of o)if(i.has(a))r[a]=n.entries[a];else throw new Error(`Key ${a} not found in enum`);return new In({...n,checks:[],..._.normalizeParams(t),entries:r})},e.exclude=(o,t)=>{let r={...n.entries};for(let a of o)if(i.has(a))delete r[a];else throw new Error(`Key ${a} not found in enum`);return new In({...n,checks:[],..._.normalizeParams(t),entries:r})}});function ia(e,n){let i=Array.isArray(e)?Object.fromEntries(e.map(o=>[o,o])):e;return new In({type:"enum",entries:i,..._.normalizeParams(n)})}function gh(e,n){return new In({type:"enum",entries:e,..._.normalizeParams(n)})}var Ed=h("ZodLiteral",(e,n)=>{Xc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>xl(e,i,o,t),e.values=new Set(n.values),Object.defineProperty(e,"value",{get(){if(n.values.length>1)throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");return n.values[0]}})});function hh(e,n){return new Ed({type:"literal",values:Array.isArray(e)?e:[e],..._.normalizeParams(n)})}var Ad=h("ZodFile",(e,n)=>{qc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Sl(e,i,o,t),e.min=(i,o)=>e.check(Ge(i,o)),e.max=(i,o)=>e.check(ct(i,o)),e.mime=(i,o)=>e.check(gn(Array.isArray(i)?i:[i],o))});function vh(e){return Qu(Ad,e)}var Od=h("ZodTransform",(e,n)=>{jt(),Yc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Al(e,i,o,t),e._zod.parse=(i,o)=>{if(o.direction==="backward")throw new ot(e.constructor.name);i.addIssue=r=>{if(typeof r=="string")i.issues.push(_.issue(r,i.value,n));else{let a=r;a.fatal&&(a.continue=!1),a.code??(a.code="custom"),"input"in a||(a.input=i.value),a.inst??(a.inst=e),i.issues.push(_.issue(a))}};let t=n.transform(i.value,i);return t instanceof Promise?t.then(r=>(i.value=r,i)):(i.value=t,i)}});function oa(e){return new Od({type:"transform",transform:e})}var Cr=h("ZodOptional",(e,n)=>{Mi.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Oo(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Ct(e){return new Cr({type:"optional",innerType:e})}var aa=h("ZodExactOptional",(e,n)=>{Qc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Oo(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Nd(e){return new aa({type:"optional",innerType:e})}var zd=h("ZodNullable",(e,n)=>{eu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ul(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Er(e){return new zd({type:"nullable",innerType:e})}function yh(e){return Ct(Er(e))}var Dd=h("ZodDefault",(e,n)=>{tu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Zl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Cd(e,n){return new Dd({type:"default",innerType:e,get defaultValue(){return typeof n=="function"?n():_.shallowClone(n)}})}var jd=h("ZodPrefault",(e,n)=>{nu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Vl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Ld(e,n){return new jd({type:"prefault",innerType:e,get defaultValue(){return typeof n=="function"?n():_.shallowClone(n)}})}var sa=h("ZodNonOptional",(e,n)=>{ru.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Rl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Ud(e,n){return new sa({type:"nonoptional",innerType:e,..._.normalizeParams(n)})}var Rd=h("ZodSuccess",(e,n)=>{iu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Pl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function bh(e){return new Rd({type:"success",innerType:e})}var Md=h("ZodCatch",(e,n)=>{ou.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Gl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function Zd(e,n){return new Md({type:"catch",innerType:e,catchValue:typeof n=="function"?n:_.constantCatch(n)})}var Vd=h("ZodNaN",(e,n)=>{au.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>wl(e,i,o,t)});function _h(e){return qu(Vd,e)}var jr=h("ZodPipe",(e,n)=>{Zi.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Fl(e,i,o,t),e.in=n.in,e.out=n.out});function Lo(e,n){return new jr({type:"pipe",in:e,out:n})}var Lr=h("ZodCodec",(e,n)=>{jr.init(e,n),fr.init(e,n)});function $h(e,n,i){return new Lr({type:"pipe",in:e,out:n,transform:i.decode,reverseTransform:i.encode})}function kh(e){let n=e._zod.def;return new Lr({type:"pipe",in:n.out,out:n.in,transform:n.reverseTransform,reverseTransform:n.transform})}var Gd=h("ZodPreprocess",(e,n)=>{jr.init(e,n),su.init(e,n)}),Fd=h("ZodReadonly",(e,n)=>{cu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Bl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Bd(e){return new Fd({type:"readonly",innerType:e})}var Hd=h("ZodTemplateLiteral",(e,n)=>{uu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Il(e,i,o,t)});function xh(e,n){return new Hd({type:"template_literal",parts:e,..._.normalizeParams(n)})}var Kd=h("ZodLazy",(e,n)=>{gr.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Kl(e,i,o,t),e.unwrap=()=>e._zod.def.getter()});function Wd(e){return new Kd({type:"lazy",getter:e})}var Jd=h("ZodPromise",(e,n)=>{du.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Hl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function wh(e){return new Jd({type:"promise",innerType:e})}var Xd=h("ZodFunction",(e,n)=>{lu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>El(e,i,o,t)});function Ih(e){return new Xd({type:"function",input:Array.isArray(e?.input)?Id(e?.input):e?.input??Nr(Dt()),output:e?.output??Dt()})}var Ur=h("ZodCustom",(e,n)=>{pu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Tl(e,i,o,t)});function Sh(e){let n=new q({check:"custom"});return n._zod.check=e,n}function Ph(e,n){return el(Ur,e??(()=>!0),n)}function qd(e,n={}){return tl(Ur,e,n)}function Yd(e,n){return nl(e,n)}var Th=rl,Eh=il,Qd=h("ZodInstanceOf",(e,n)=>{Ur.init(e,n)},{properties(e,n){return this.check(fn(e,n))}});function Ah(e,n={}){let i=new Qd({type:"custom",check:"custom",fn:o=>o instanceof e,abort:!0,..._.normalizeParams(n)});return i._zod.bag.Class=e,i._zod.check=o=>{o.value instanceof e||o.issues.push({code:"invalid_type",expected:e.name,input:o.value,inst:i,path:[...i._zod.def.path??[]]})},i}var Oh=(...e)=>ol({Codec:Lr,Boolean:Tn,String:Sn},...e);function Nh(e){let n=Wd(()=>An([Tr(e),ud(),ld(),fd(),Nr(n),Sd(Tr(),n)]));return n}function zh(e,n){return new Gd({type:"pipe",in:oa(e),out:n})}var _x={invalid_type:"invalid_type",too_big:"too_big",too_small:"too_small",invalid_format:"invalid_format",not_multiple_of:"not_multiple_of",unrecognized_keys:"unrecognized_keys",invalid_union:"invalid_union",invalid_key:"invalid_key",invalid_element:"invalid_element",invalid_value:"invalid_value",custom:"custom"};function $x(e){ee({customError:e})}function kx(){return ee().customError}var ep;ep||(ep={});var Rr={};We(Rr,{ZodISODate:()=>mt,ZodISODateTime:()=>pt,ZodISODuration:()=>gt,ZodISOTime:()=>ft,date:()=>wx,datetime:()=>xx,duration:()=>Sx,time:()=>Ix});function xx(e){return kr(pt,e)}function wx(e){return xr(mt,e)}function Ix(e){return wr(ft,e)}function Sx(e){return Ir(gt,e)}var P={...On,...Do,iso:Rr},Px=new Set(["$schema","$ref","$defs","definitions","$id","id","$comment","$anchor","$vocabulary","$dynamicRef","$dynamicAnchor","type","enum","const","anyOf","oneOf","allOf","not","properties","required","additionalProperties","patternProperties","propertyNames","minProperties","maxProperties","items","prefixItems","additionalItems","minItems","maxItems","uniqueItems","contains","minContains","maxContains","minLength","maxLength","pattern","format","minimum","maximum","exclusiveMinimum","exclusiveMaximum","multipleOf","description","default","contentEncoding","contentMediaType","contentSchema","unevaluatedItems","unevaluatedProperties","if","then","else","dependentSchemas","dependentRequired","nullable","readOnly"]);function Tx(e,n){let i=e.$schema;return i==="https://json-schema.org/draft/2020-12/schema"?"draft-2020-12":i==="http://json-schema.org/draft-07/schema#"?"draft-7":i==="http://json-schema.org/draft-04/schema#"?"draft-4":n??"draft-2020-12"}function Dh(e,n){return e.map((i,o)=>oo).check(o=>{let t=o.value;if(typeof t!="object"||t===null||Array.isArray(t))return;let r=Object.getOwnPropertyNames(t);if(n.minProperties!==void 0&&r.length=${n.minProperties} properties`,input:t,inst:e,continue:!0}),n.maxProperties!==void 0&&r.length>n.maxProperties&&o.issues.push({origin:"object",code:"too_big",maximum:n.maxProperties,inclusive:!0,message:`Too big: expected object to have <=${n.maxProperties} properties`,input:t,inst:e,continue:!0}),n.keySchema)for(let a of r){let s=n.keySchema.safeParse(a);s.success||o.issues.push({code:"invalid_key",origin:"record",issues:s.error.issues,input:a,path:[a],continue:!0})}}).pipe(e)}function tp(e,n){if(e===null)return"z";let i=typeof e;if(i!=="object"){if(i==="number"&&Number.isNaN(e))return null;let o=String(e);return`${i[0]}${o.length}:${o}`}if(n.has(e))return null;n.add(e);try{if(Array.isArray(e)){let r=[];for(let a of e){let s=tp(a,n);if(s===null)return null;r.push(s)}return`a${r.length}:[${r.join(",")}]`}let o=Object.keys(e).sort(),t=[];for(let r of o){let a=tp(e[r],n);if(a===null)return null;t.push(`${r.length}:${r}=${a}`)}return`o${t.length}:{${t.join(",")}}`}finally{n.delete(e)}}var Nx=new Set(["items","prefixItems","additionalItems","additionalProperties","contains","propertyNames","not","if","then","else","allOf","anyOf","oneOf","unevaluatedItems","unevaluatedProperties","contentSchema"]),zx=new Set(["properties","patternProperties","dependentSchemas","dependencies","$defs","definitions"]);function Mr(e){return typeof e!="object"||e===null?!1:Array.isArray(e)?e.some(Mr):typeof e.$ref=="string"?!0:Object.entries(e).some(([n,i])=>Nx.has(n)?Mr(i):!zx.has(n)||typeof i!="object"||i===null?!1:Object.values(i).some(Mr))}function Ch(e){return e===1?"element":"elements"}function Dx(e,n){return P.transform(o=>o).check(o=>{let t=o.value;if(Array.isArray(t)){if(n.uniqueItems===!0){let r=new Map;for(let a=0;a=a)break;sn.maxContains&&o.issues.push({code:"custom",message:`Array must contain at most ${n.maxContains} matching ${Ch(n.maxContains)}`,input:t,continue:!0})}}}).pipe(e)}function jh(e,n){if(e!==!1)return e===void 0||e===!0?P.any():ge(e,n)}var Cx=/^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;function Lh(e,n){if(e.not!==void 0){if(typeof e.not=="object"&&Object.keys(e.not).length===0)return P.never();throw new Error("not is not supported in Zod (except { not: {} } for never)")}if(e.unevaluatedItems!==void 0)throw new Error("unevaluatedItems is not supported");if(e.unevaluatedProperties!==void 0)throw new Error("unevaluatedProperties is not supported");if(e.if!==void 0||e.then!==void 0||e.else!==void 0)throw new Error("Conditional schemas (if/then/else) are not supported");if(e.dependentSchemas!==void 0||e.dependentRequired!==void 0)throw new Error("dependentSchemas and dependentRequired are not supported");if(e.$ref){let t=e.$ref;if(n.refs.has(t))return n.refs.get(t);if(n.processing.has(t))return P.lazy(()=>{if(!n.refs.has(t))throw new Error(`Circular reference not resolved: ${t}`);return n.refs.get(t)});n.processing.add(t);let r=Ax(t,n),a=ge(r,n);return n.refs.set(t,a),n.processing.delete(t),a}if(e.enum!==void 0){let t=e.enum;if(n.version==="openapi-3.0"&&e.nullable===!0&&t.length===1&&t[0]===null)return P.null();if(t.length===0)return P.never();if(t.length===1)return P.literal(t[0]);if(t.every(a=>typeof a=="string"))return P.enum(t);let r=t.map(a=>P.literal(a));return r.length<2?r[0]:P.union([r[0],r[1],...r.slice(2)])}if(e.const!==void 0)return P.literal(e.const);let i=e.type;if(Array.isArray(i)){let t=i.map(r=>{let a={...e,type:r};return Lh(a,n)});return t.length===0?P.never():t.length===1?t[0]:P.union(t)}if(!i)return P.any();let o;switch(i){case"string":{let t=P.string();if(e.format){let r=e.format;r==="email"?t=t.check(P.email()):r==="uri"||r==="uri-reference"?t=t.check(P.url()):r==="uuid"||r==="guid"?t=t.check(P.uuid()):r==="date-time"?t=t.check(P.iso.datetime({offset:!0})):r==="date"?t=t.check(P.iso.date()):r==="time"?t=t.check(P.regex(Cx)):r==="duration"?t=t.check(P.iso.duration()):r==="hostname"?t=t.check(P.hostname()):r==="ipv4"?t=t.check(P.ipv4()):r==="ipv6"?t=t.check(P.ipv6()):r==="mac"?t=t.check(P.mac()):r==="cidr"?t=t.check(P.cidrv4()):r==="cidr-v6"?t=t.check(P.cidrv6()):r==="base64"?t=t.check(P.base64()):r==="base64url"?t=t.check(P.base64url()):r==="e164"?t=t.check(P.e164()):r==="credit_card"?t=t.check(P.creditCard()):r==="iban"?t=t.check(P.iban()):r==="jwt"?t=t.check(P.jwt()):r==="emoji"?t=t.check(P.emoji()):r==="nanoid"?t=t.check(P.nanoid()):r==="cuid"?t=t.check(P.cuid()):r==="cuid2"?t=t.check(P.cuid2()):r==="ulid"?t=t.check(P.ulid()):r==="xid"?t=t.check(P.xid()):r==="ksuid"&&(t=t.check(P.ksuid()))}typeof e.minLength=="number"&&(t=t.min(e.minLength)),typeof e.maxLength=="number"&&(t=t.max(e.maxLength)),e.pattern&&(t=t.regex(new RegExp(e.pattern))),o=t;break}case"number":case"integer":{let t=i==="integer"?P.number().int():P.number();typeof e.minimum=="number"&&e.exclusiveMinimum!==!0&&(t=t.min(e.minimum)),typeof e.maximum=="number"&&e.exclusiveMaximum!==!0&&(t=t.max(e.maximum)),typeof e.exclusiveMinimum=="number"?t=t.gt(e.exclusiveMinimum):e.exclusiveMinimum===!0&&typeof e.minimum=="number"&&(t=t.gt(e.minimum)),typeof e.exclusiveMaximum=="number"?t=t.lt(e.exclusiveMaximum):e.exclusiveMaximum===!0&&typeof e.maximum=="number"&&(t=t.lt(e.maximum)),typeof e.multipleOf=="number"&&(t=t.multipleOf(e.multipleOf)),o=t;break}case"boolean":{o=P.boolean();break}case"null":{o=P.null();break}case"object":{let t={},r=e.properties||{},a=new Set(e.required||[]),s=typeof e.additionalProperties=="object"?ge(e.additionalProperties,n):void 0;for(let[p,m]of Object.entries(r)){let f=ge(m,n);se(t,p,a.has(p)?f:f.optional())}if(e.patternProperties){let p=e.patternProperties,m=Object.keys(p),f=[];for(let y of m){let x=ge(p[y],n),I=P.string().regex(new RegExp(y));f.push(P.looseRecord(I,x))}let g=[];if(Object.keys(t).length>0&&g.push(P.object(t).passthrough()),g.push(...f),g.length===0)o=P.object({}).passthrough();else if(g.length===1)o=g[0];else{let y=P.intersection(g[0],g[1]);for(let x=2;xnew RegExp(b)),I=o;o=o.check(b=>{if(!Se(b.value))return;let T=[];for(let A of Object.keys(b.value))y.includes(A)||x.some(D=>D.test(A))||T.push(A);T.length&&b.issues.push({code:"unrecognized_keys",keys:T,input:b.value,inst:I})})}}else{let p=P.object(t);e.additionalProperties===!1?o=p.strict():s?o=p.catchall(s):o=p.passthrough()}let c=e.propertyNames!==void 0&&e.propertyNames!==!0,u=typeof e.minProperties=="number"?e.minProperties:void 0,d=typeof e.maxProperties=="number"?e.maxProperties:void 0;if(c||u!==void 0||d!==void 0){let p;if(c){let m=typeof e.propertyNames=="object"&&e.propertyNames.type===void 0?{type:"string",...e.propertyNames}:e.propertyNames;p=ge(m,n)}o=Ox(o,{keySchema:p,minProperties:u,maxProperties:d})}break}case"array":{let t=e.prefixItems,r=e.items;if(t&&Array.isArray(t)){let a=typeof e.minItems=="number"?e.minItems:0,s=t.map(p=>ge(p,n)),c=Dh(s,a),u=Array.isArray(r)?void 0:jh(r,n),d=P.tuple(c);o=u?d.rest(u):d,typeof e.minItems=="number"&&(o=o.check(P.minLength(e.minItems))),typeof e.maxItems=="number"&&(o=o.check(P.maxLength(e.maxItems)))}else if(Array.isArray(r)){let a=typeof e.minItems=="number"?e.minItems:0,s=r.map(p=>ge(p,n)),c=Dh(s,a),u=jh(e.additionalItems,n),d=P.tuple(c);o=u?d.rest(u):d,typeof e.minItems=="number"&&(o=o.check(P.minLength(e.minItems))),typeof e.maxItems=="number"&&(o=o.check(P.maxLength(e.maxItems)))}else if(r!==void 0){let a=ge(r,n),s=P.array(a);typeof e.minItems=="number"&&(s=s.min(e.minItems)),typeof e.maxItems=="number"&&(s=s.max(e.maxItems)),o=s}else o=P.array(P.any());(e.uniqueItems===!0||e.contains!==void 0)&&(o=Dx(o,{uniqueItems:e.uniqueItems===!0,containsSchema:e.contains!==void 0?ge(e.contains,n):void 0,minContains:typeof e.minContains=="number"?e.minContains:void 0,maxContains:typeof e.maxContains=="number"?e.maxContains:void 0}));break}default:throw new Error(`Unsupported type: ${i}`)}return o}function ge(e,n){if(typeof e=="boolean")return e?P.any():P.never();let i=Lh(e,n),o=e.type||e.enum!==void 0||e.const!==void 0;if(e.anyOf&&Array.isArray(e.anyOf)){let s=e.anyOf.map(u=>ge(u,n)),c=P.union(s);i=o?P.intersection(i,c):c}if(e.oneOf&&Array.isArray(e.oneOf)){let s=e.oneOf.map(u=>ge(u,n)),c=P.xor(s);i=o?P.intersection(i,c):c}if(e.allOf&&Array.isArray(e.allOf))if(e.allOf.length===0)i=o?i:P.any();else{let s=o?i:ge(e.allOf[0],n),c=o?0:1;for(let u=c;u0&&n.registry.add(i,t),e.description&&(i=i.describe(e.description)),i}function Uh(e,n){if(typeof e=="boolean")return e?P.any():P.never();let i;try{i=JSON.parse(JSON.stringify(e))}catch{throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas")}let o=Tx(i,n?.defaultTarget),t=i.$defs||i.definitions||{},r={version:o,defs:t,refs:new Map,processing:new Set,rootSchema:i,registry:n?.registry??ce};return ge(i,r)}var Rh=Symbol("z.visit/resolving");function Zr(e,n){let i=typeof n=="function"?n:(a,s)=>{let c=n[a._zod.def.type];return c?c(a,s):a},o=new Map;function t(a){let s=o.get(a);if(s===Rh)return new gr({type:"lazy",getter:()=>o.get(a)});if(s!==void 0)return s;o.set(a,Rh);let c=r(a),u=i(c,c!==a);return o.set(a,u),u}function r(a){let s=a._zod.def,c=s.type;switch(c){case"object":{let u=s.shape,d=Object.keys(u),p=!1,m={};for(let g of d){let y=t(u[g]);y!==u[g]&&(p=!0),m[g]=y}let f=s.catchall;return s.catchall&&(f=t(s.catchall),f!==s.catchall&&(p=!0)),p?K(a,{...s,shape:m,catchall:f}):a}case"array":{let u=t(s.element);return u===s.element?a:K(a,{...s,element:u})}case"tuple":{let u=s.items,d=!1,p=[];for(let f of u){let g=t(f);g!==f&&(d=!0),p.push(g)}let m=s.rest;return s.rest&&(m=t(s.rest),m!==s.rest&&(d=!0)),d?K(a,{...s,items:p,rest:m}):a}case"record":case"map":{let u=t(s.keyType),d=t(s.valueType);return u===s.keyType&&d===s.valueType?a:K(a,{...s,keyType:u,valueType:d})}case"set":{let u=t(s.valueType);return u===s.valueType?a:K(a,{...s,valueType:u})}case"union":{let u=s.options,d=!1,p=[];for(let m of u){let f=t(m);f!==m&&(d=!0),p.push(f)}return d?K(a,{...s,options:p}):a}case"intersection":{let u=t(s.left),d=t(s.right);return u===s.left&&d===s.right?a:K(a,{...s,left:u,right:d})}case"optional":case"nullable":case"default":case"prefault":case"catch":case"readonly":case"nonoptional":case"promise":case"success":{let u=t(s.innerType);return u===s.innerType?a:K(a,{...s,innerType:u})}case"pipe":{let u=t(s.in),d=t(s.out);return u===s.in&&d===s.out?a:K(a,{...s,in:u,out:d})}case"function":{let u=t(s.input),d=t(s.output);return u===s.input&&d===s.output?a:K(a,{...s,input:u,output:d})}case"lazy":{let u=s.getter,{_cachedInner:d,...p}=s;return K(a,{...p,getter:()=>t(u())})}case"template_literal":case"string":case"number":case"int":case"boolean":case"bigint":case"symbol":case"undefined":case"null":case"void":case"never":case"any":case"unknown":case"date":case"nan":case"enum":case"literal":case"file":case"transform":case"custom":return a;default:return a}}return t(e)}function Mh(e){return Zr(e,{object:n=>n.partial(),union:n=>{let i=n._zod.def;return i.discriminator===void 0?n:An(i.options)}})}function jx(e,n){if(!n?.length)return e;let i=e._zod.def;return K(e,ze(i,{checks:[...i.checks??[],...n]}),{parent:!0})}function Zh(e){return jx(e.out,e.checks)}function Lx(e){return e.in._zod.traits.has("$ZodTransform")?Zh(e):e.in}function Vh(e){return Zr(e,{pipe:n=>Lx(n._zod.def),default:(n,i)=>i?Ct(n._zod.def.innerType):n,catch:(n,i)=>i?n._zod.def.innerType:n})}function Gh(e){return Zr(e,{pipe:n=>Zh(n._zod.def),prefault:(n,i)=>i?n._zod.def.innerType:n})}var np={};We(np,{bigint:()=>Zx,boolean:()=>Mx,date:()=>Vx,number:()=>Rx,string:()=>Ux});function Ux(e){return wu(Sn,e)}function Rx(e){return Au(Pn,e)}function Mx(e){return Lu(Tn,e)}function Zx(e){return Ru(En,e)}function Vx(e){return Xu(Or,e)}var Gx=new Set(["__proto__","prototype","constructor"]),Rt=l.string().min(1).max(256).refine(e=>!Gx.has(e),"reserved storage key"),ca=l.json(),ua=256*1024,Fx=5*1024*1024,Fh=1024,Bh=l.strictObject({key:Rt}),Hh=l.object({value:ca}),Kh=l.strictObject({key:Rt,value:ca}),Wh=l.object({ok:l.literal(!0)}),Jh=l.strictObject({key:Rt}),Xh=l.object({ok:l.literal(!0)}),qh=l.strictObject({}).optional(),Yh=l.object({keys:l.array(l.string()).max(Fh)}),Qh=l.strictObject({key:Rt}),ev=l.object({value:l.string().nullable()}),tv=l.strictObject({key:Rt,value:l.string().max(64*1024)}),nv=l.object({ok:l.literal(!0)}),rv=l.strictObject({key:Rt}),iv=l.object({ok:l.literal(!0)}),ov=l.strictObject({}).optional(),av=l.object({status:l.enum(["os-keyring","unavailable"])}),sv=l.strictObject({}).optional(),cv=l.object({settings:l.record(l.string(),ca)}),uv=l.strictObject({key:Rt,value:ca}),lv=l.object({ok:l.literal(!0)});var Nn="x-orca-enum-since";var et="1.4.160",rp="1.4.245";var Vr="1.4.246";var dv=Vr,pv=Vr;var je="1.4.248";var Hx="Relative to the plugin root; `..` segments and Windows device names such as `CON` are refused.";function mv(e,n,i){return ae(`${e} ${Hx}`,n,i)}function ae(e,n,i){return i?{description:e,"x-orca-since":n,examples:[...i]}:{description:e,"x-orca-since":n}}var ht=["workspace.created","workspace.removed","agent.status.changed","workspace.changed"],vO=ht.length,Kx={"workspace.created":rp,"workspace.removed":rp,"agent.status.changed":et,"workspace.changed":pv},la=new Map([["worktree.created","workspace.created"],["worktree.removed","workspace.removed"]]);var yO=l.object({on:l.enum(ht).meta({...ae("Event delivered to the plugin worker.",et),[Nn]:Kx})}).strict().meta({id:"EventContribution",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."});var he=["worker","panel","editor"],le=["worker"],zn=["panel","editor"],F=e=>e;function fv(e,n){return n==="worker"?e.since:Math.max(e.since,e.framesSince??0)}var Wx=["success","failure","cancellation","superseded"],gv=[...Wx,"interruption","unconfirmed"];var Dn=["working","blocked","waiting","done"];var MO=new Set(Dn);var vt=l.string().regex(/^[A-Za-z0-9_.:-]{1,128}$/,"is 1-128 letters, digits, or . _ : -");var da=12e4,FO=1024*1024,BO=256*1024;var Qx=l.object({id:l.string().min(1).max(64),surface:l.enum(["panel","editor"]),contributionId:l.string().min(1).max(256)}),HO=l.object({name:vt,input:l.unknown(),panel:Qx}),KO=l.object({event:vt,payload:l.unknown(),panel:l.string().min(1).max(64).optional()}),hv=["storage","settings"],WO=l.object({area:l.enum(hv),key:l.string().min(1).max(256)}),JO=l.discriminatedUnion("kind",[l.object({kind:l.literal("message"),event:vt,payload:l.unknown()}),l.object({kind:l.literal("event"),event:l.enum(ht),payload:l.unknown()}),l.object({kind:l.literal("changed"),area:l.enum(hv),key:l.string()})]);var ew=l.strictObject({name:vt,input:l.json().optional(),timeoutMs:l.number().int().min(1).max(da).optional()}),tw=l.object({output:l.json()}),nw=50,rw=l.strictObject({}).optional(),iw=l.object({agents:l.array(l.object({terminalId:l.string().min(1).max(1024).nullable(),title:l.string().max(512).nullable(),agent:l.string().max(128).nullable(),state:l.enum(Dn),stateStartedAt:l.number().finite()})).max(nw)}),ow=l.strictObject({event:l.enum(ht)}),aw=l.object({ok:l.literal(!0)}),vv={"plugin.request":F({summary:"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.",since:2,scope:"plugin-private",capability:null,mutation:!1,surfaces:zn,params:ew,result:tw}),"agents.list":F({summary:"The agents in the focused workspace: their terminal, title, agent kind and status. `agent.status.changed` reports each change after.",since:2,scope:"active-workspace",capability:"agent:read",mutation:!1,surfaces:he,params:rw,result:iw}),"events.subscribe":F({summary:"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.",since:2,scope:"plugin-private",capability:null,mutation:!1,surfaces:zn,params:ow,result:aw})};var sw=/^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9\u00B9\u00B2\u00B3]|lpt[1-9\u00B9\u00B2\u00B3])(?:\..*)?$/i,cw=/[<>:"|?*]/,pa=String.raw`[^<>:"|?*\x00-\x1f\\/]*[^<>:"|?*\x00-\x1f\\/. ]`,ip=new RegExp(`^${pa}(?:[\\\\/]${pa})*$`),rN=new RegExp(`^${pa}(?:/${pa})*$`);function uw(e){return e.length===0||e==="."||e===".."?"empty and dot path segments are not allowed":e.endsWith(".")||e.endsWith(" ")?"path segments may not end with a dot or space":cw.test(e)||[...e].some(n=>n.charCodeAt(0)<=31)?"path segment contains a Windows-forbidden character or alternate-data-stream colon":sw.test(e)?"path segment is a Windows reserved device name":null}function lw(e){if(e.length===0||e.startsWith("/")||e.startsWith("\\"))return"must be a non-empty relative path";let n=e.split(/[\\/]/);for(let i of n){let o=uw(i);if(o)return o}return null}function op(e){return lw(e)===null}var yv=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,dw=new Set(["__proto__","prototype","constructor"]),ap=64;function bv(e){return dw.has(e)}var sp="must be a portable relative path inside the plugin directory",Cn=l.string().max(ap).regex(yv,"must be kebab-case (a-z, 0-9, dashes)").refine(e=>!bv(e),"must not be a reserved name"),_v=l.string().min(1).max(1024).regex(ip,sp).refine(e=>!ip.test(e)||op(e),sp),dN=l.string().min(1).max(1024).transform(e=>e.replace(/[\\/]+$/,"")).refine(op,sp),$v=l.string().min(1).max(256).regex(/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/,"must be a portable command id");var pw=/[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]/u,mw=new Set(["‌","‍"]);function fw(e){return mw.has(e)||!pw.test(e)}function kv(e){return[...e].filter(fw).join("")}var gw=/[\t\n\v\f\r\u0085\u2028\u2029]+/g;function hw(e){return kv(e.replace(gw," "))}function Gr(e){let n=e.overwrite(hw);return e.minLength?n.min(e.minLength,"must contain visible characters"):n}var j={maxNodes:5e3,maxDepth:64,maxTreeBytes:524288,maxTextLength:16384,maxLabelLength:1024,maxPropsPerNode:32,maxSelectOptions:500,maxEventArgs:8,maxCallbackIdLength:64,maxRadioOptions:50,maxTableColumns:32,maxTableRows:5e3,maxTreeItems:2e3,maxTreeItemDepth:16,maxKeyValueItems:200,maxDocumentLength:65536,maxEventArgItems:5e3},vN={maxBytes:640*1024,maxMessages:200,perMs:1e4,maxDepth:j.maxDepth*2+24,maxObjects:j.maxNodes*8+1024};var Be={render:"ui.render",event:"ui.event",error:"ui.error",ack:"ui.ack",ready:"ui.ready",hydrate:"ui.hydrate",visibility:"ui.visibility",persist:"ui.persist",retain:"ui.retain",snapshot:"ui.snapshot",restore:"ui.restore"};var vw={Input:{value:"value",change:"onChange"},Textarea:{value:"value",change:"onChange"},Switch:{value:"checked",change:"onChange"},Checkbox:{value:"checked",change:"onChange"},Select:{value:"value",change:"onChange"},RadioGroup:{value:"value",change:"onChange"},Slider:{value:"value",change:"onChange"},Tabs:{value:"value",change:"onChange"},Collapsible:{value:"open",change:"onChange"},Table:{value:"selected",change:"onSelectionChange"},Tree:{value:"selected",change:"onSelect"}},yw=new Map(Object.entries(vw));function cp(e){return yw.get(e)??null}var ma="$edit";var fa=/^[A-Za-z0-9_.:-]+$/,xv=l.object({$callback:l.string().min(1).max(j.maxCallbackIdLength).regex(fa)}).strict(),Mt=["neutral","accent","success","warning","danger"],G=l.string().max(j.maxLabelLength),H=G.min(1),Y=xv.optional(),Le=l.string().regex(/^[a-z0-9-]{1,64}$/),Ee=l.enum(["none","xs","sm","md","lg"]).optional(),Fr=l.enum(["start","center","end","stretch"]).optional(),tt=l.enum(Mt).optional(),ga=l.enum(["xs","sm","md"]).optional(),up=l.enum(["solid","outline","ghost"]).optional(),N=l.boolean().optional(),jn=l.string().max(j.maxTextLength),Br=l.enum(["sm","md","lg","none"]).optional(),Zt=l.string().min(1).max(j.maxLabelLength),ve={[ma]:l.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional()};function R(e,n){return{props:e,children:n}}var yt=["activity","bar-chart-3","bell","blocks","book","bot","bug","calendar","cloud","code","database","file-text","flag","folder","gauge","globe","hammer","layers","lightbulb","package","plug","puzzle","rocket","star","terminal","wrench","zap","add","remove","delete","edit","refresh","search","settings","check","close","info","warning","error","open-external","copy","download","upload","play","pause","stop","more","expand","collapse","next","filter","branch","file","agent","link"],bw=Object.fromEntries(yt.slice(yt.indexOf("add")).map(e=>[e,Vr])),EN=new Map(yt.map(e=>[e.replaceAll("-",""),e]));var _w=["html","components"],$w=/\.(?:m?js|tsx?|mts|jsx)$/i,AN=l.object({id:Cn.meta(ae("Panel id, unique among this plugin's panels. The reserved names `constructor` and `prototype` are refused.",et,["dashboard"])),title:Gr(l.string().min(1).max(256)).meta(ae("Title shown on the panel tab and its tooltip.",et,["Dashboard"])),icon:l.enum(yt).optional().meta({...ae("Icon shown in the right-sidebar activity bar. Defaults to `plug`.",et,["database"]),[Nn]:bw}),entry:_v.meta(mv(`What the panel runs: an HTML page for \`ui: "html"\`, a \`.js\`/\`.mjs\` module for \`ui: "components"\`. Either may instead be a \`.ts\`/\`.tsx\`/\`.mts\`/\`.jsx\` file, which Orca transpiles when it loads (needs \`engines.orca\` \`>=${dv}\`; an \`html\` panel then gets a generated page around it). It is trusted code: install panels you trust.`,et,["dist/panel.html"])),ui:l.enum(_w).optional().meta(ae("How the panel draws its UI. `html` (default) shows `entry` as a web page in an isolated frame; `components` runs `entry` as a hidden module and Orca draws the component tree it returns with its own controls.",Vr,["components"]))}).strict().refine(e=>e.ui!=="components"||$w.test(e.entry),{path:["entry"],message:"a components panel entry must be a .js, .mjs, .ts, .tsx, .mts or .jsx module"}).meta({id:"PanelContribution",description:"A sidebar panel. Requires the `sandboxed-ui` execution profile or higher."});var kw="1.4.248";var xw=64,ww=256,Ln=kw,wv={text:Gr(l.string().max(xw)).optional().meta(ae("Short label shown in the status bar.",Ln,["3 blocked"])),icon:l.enum(yt).optional().meta(ae("Icon shown before the text, from the panel icon set.",Ln,["bell"])),tone:l.enum(Mt).optional().meta(ae("Colour of the item. Defaults to `neutral`.",Ln,["warning"])),tooltip:Gr(l.string().max(ww)).optional().meta(ae("Shown on hover. Defaults to the plugin name.",Ln))},UN=l.object({id:Cn.meta(ae("Item id, unique among this plugin's status bar items; `statusBar.update` names it.",Ln,["blocked-count"])),...wv,command:$v.optional().meta(ae("Id of a command this plugin contributes in `contributes.commands`, run when the item is pressed.",Ln,["guard.showLog"]))}).strict().meta({id:"StatusBarItemContribution",description:"An item in the status bar. Requires the `declarative` profile; a `trusted-worker` can change it with `statusBar.update`."}),Iv=l.strictObject({...wv,visible:l.boolean().optional()});var Iw=l.strictObject({id:Cn,...Iv.shape}),Sw=l.object({ok:l.literal(!0)}),Sv={"statusBar.update":F({summary:"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.",since:2,scope:"plugin-private",capability:null,mutation:!1,surfaces:le,params:Iw,result:Sw})};function lp(e){return e==="linux"?"orca-ide":e==="win32"?"orca.cmd":"orca"}function Pw(e){return{...e,launchCmd:e.launchCmd??e.detectCmd,expectedProcess:e.expectedProcess??e.detectCmd}}var Tw={claude:{detectCmd:"claude",promptInjectionMode:"argv",pasteNeedsTypedRequest:!0,draftPromptFlag:"--prefill",preflightTrust:"claude",draftPasteReadySignal:"claude-composer-prompt",workerStartReadsComposerMarker:!0,ctrlEnterEncoding:"csi-u"},"claude-agent-teams":{detectCmd:"orca",detectCmdAliases:["orca-dev","orca-ide"],detectRequiredCommands:["claude"],detectUnsupportedRuntimes:["win32","wsl"],launchCmd:"orca claude-teams",launchCmdByPlatform:{linux:`${lp("linux")} claude-teams`,win32:`${lp("win32")} claude-teams`},expectedProcess:"claude",promptInjectionMode:"stdin-after-start",pasteNeedsTypedRequest:!0,preflightTrust:"claude",draftPasteReadySignal:"claude-composer-prompt",workerStartReadsComposerMarker:!0,ctrlEnterEncoding:"csi-u"},codebuddy:{detectCmd:"codebuddy",detectCmdAliases:["cbc"],promptInjectionMode:"argv"},openclaude:{detectCmd:"openclaude",promptInjectionMode:"argv",draftPromptFlag:"--prefill"},codex:{detectCmd:"codex",promptInjectionMode:"argv",windowsInputRecordPasteNewline:"alt-enter",preflightTrust:"codex",draftPasteReadySignal:"codex-composer-prompt",draftPasteReadyTimeoutMs:2e4,submitRetryDelayMs:1200},autohand:{detectCmd:"autohand",promptInjectionMode:"stdin-after-start"},ante:{detectCmd:"ante",promptInjectionMode:"stdin-after-start"},trae:{detectCmd:"traecli",promptInjectionMode:"argv",argvPromptSeparator:"--"},opencode:{detectCmd:"opencode",promptInjectionMode:"flag-prompt",draftPasteReadySignal:"render-cursor-after-bracketed-paste",submitPasteReadySignal:"opencode-agent-row",draftPasteReadyTimeoutMs:2e4,ctrlEnterEncoding:"csi-u",composerReadyCaptures:["opencode-1-18-32-timed-boot-slow","opencode-1-18-32-timed-boot-hidden-pane","opencode-1-18-32-timed-first-launch","opencode-cmd-2-0-21-timed-warm-server"]},opencode2:{detectCmd:"opencode2",launchCmd:"opencode2 --standalone",promptInjectionMode:"flag-prompt",draftPasteReadySignal:"render-cursor-after-bracketed-paste",submitPasteReadySignal:"opencode-agent-row",draftPasteReadyTimeoutMs:2e4,composerReadyCaptures:["opencode-2-0-18-timed-boot-hidden-pane","opencode-2-0-21-timed-cold-standalone","opencode-2-0-21-timed-cold-standalone-hidden-pane","opencode-2-0-21-timed-natural-load-enter-dropped","opencode-2-0-14-timed-cold-standalone"]},"mimo-code":{detectCmd:"mimo",promptInjectionMode:"flag-prompt",draftPasteReadySignal:"render-cursor-after-bracketed-paste"},pi:{detectCmd:"pi",promptInjectionMode:"argv",draftPromptEnvVar:"ORCA_PI_PREFILL",windowsShiftEnterEncoding:"csi-u"},omp:{detectCmd:"omp",promptInjectionMode:"argv",draftPromptEnvVar:"ORCA_OMP_PREFILL",windowsShiftEnterEncoding:"csi-u"},"prime-agent":{detectCmd:"prime-agent",promptInjectionMode:"argv",argvPromptSeparator:"--",windowsShiftEnterEncoding:"csi-u"},qoder:{detectCmd:"qodercli",detectCmdAliases:["qoder"],promptInjectionMode:"flag-prompt-interactive",preflightTrust:"qoder"},"qoder-cn":{detectCmd:"qoderclicn",detectCmdAliases:["qodercn"],promptInjectionMode:"flag-prompt-interactive",preflightTrust:"qoder-cn"},gemini:{detectCmd:"gemini",promptInjectionMode:"flag-prompt-interactive"},antigravity:{detectCmd:"agy",promptInjectionMode:"flag-prompt-interactive",preflightTrust:"antigravity",draftPasteReadySignal:"antigravity-composer-prompt",draftPasteReadyTimeoutMs:2e4,submitLineSettleMsPerLine:45},aider:{detectCmd:"aider",promptInjectionMode:"stdin-after-start"},goose:{detectCmd:"goose",promptInjectionMode:"stdin-after-start"},amp:{detectCmd:"amp",promptInjectionMode:"stdin-after-start"},kilo:{detectCmd:"kilo",promptInjectionMode:"stdin-after-start"},kiro:{detectCmd:"kiro-cli",launchCmd:"kiro-cli chat --tui",promptInjectionMode:"stdin-after-start"},crush:{detectCmd:"crush",promptInjectionMode:"stdin-after-start"},aug:{detectCmd:"auggie",promptInjectionMode:"stdin-after-start"},cline:{detectCmd:"cline",promptInjectionMode:"stdin-after-start"},freebuff:{detectCmd:"freebuff",promptInjectionMode:"stdin-after-start"},codebuff:{detectCmd:"codebuff",promptInjectionMode:"stdin-after-start"},"command-code":{detectCmd:"command-code",launchCmd:"command-code --trust",promptInjectionMode:"argv"},continue:{detectCmd:"cn",promptInjectionMode:"stdin-after-start"},cursor:{detectCmd:"cursor-agent",promptInjectionMode:"argv",preflightTrust:"cursor"},droid:{detectCmd:"droid",promptInjectionMode:"argv",windowsShiftEnterEncoding:"csi-u",ctrlEnterEncoding:"csi-u"},kimi:{preflightTrust:"kimi",detectCmd:"kimi",detectCmdAliases:["kimi-code"],promptInjectionMode:"stdin-after-start"},"mistral-vibe":{detectCmd:"vibe",detectCmdAliases:["mistral-vibe"],promptInjectionMode:"stdin-after-start"},"qwen-code":{detectCmd:"qwen",promptInjectionMode:"stdin-after-start",submitRetryDelayMs:1200},rovo:{detectCmd:"acli",launchCmd:"acli rovodev run",promptInjectionMode:"stdin-after-start"},hermes:{detectCmd:"hermes",launchCmd:"hermes --tui",promptInjectionMode:"hermes-query"},openclaw:{detectCmd:"openclaw",promptInjectionMode:"stdin-after-start"},copilot:{detectCmd:"copilot",promptInjectionMode:"flag-interactive",preflightTrust:"copilot",draftPasteReadySignal:"copilot-composer-prompt"},grok:{detectCmd:"grok",promptInjectionMode:"argv",argvPromptSeparator:"--",draftPasteReadySignal:"grok-composer-prompt",ctrlEnterEncoding:"csi-u"},muse:{detectCmd:"muse",launchCmd:"muse --trust-workspace",promptInjectionMode:"stdin-after-start"},dsh:{detectCmd:"dsh-tui",detectCmdAliases:["dst"],detectRequiredCommands:["dsh"],launchCmd:"dsh-tui .",expectedProcess:"dsh",promptInjectionMode:"stdin-after-start",draftPasteReadySignal:"dsh-composer-prompt",submitRetryDelayMs:1e3,submitRetryBudgetMs:3e4},zcode:{detectCmd:"zcode",expectedProcess:"zcode-cli",promptInjectionMode:"stdin-after-start",draftPasteReadySignal:"zcode-composer-prompt",composerReadyCaptures:["zcode-composer-ready"]},devin:{detectCmd:"devin",promptInjectionMode:"argv",argvPromptSeparator:"--"},jcode:{detectCmd:"jcode",launchCmd:"jcode",expectedProcess:"jcode",promptInjectionMode:"stdin-after-start"}},Ew=Object.fromEntries(Object.entries(Tw).map(([e,n])=>[e,Pw(n)]));function Pv(e){return typeof e=="string"&&Object.hasOwn(Ew,e)}var dp=["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"];var ez=new Set(dp);var Aw=500,pp=2e3,Hr=1024,Tv=4096,Un=l.string().min(1).max(Hr),mp=l.string().min(1).max(Hr),Ow=l.strictObject({}).optional(),Nw=l.object({workspaces:l.array(l.object({id:Un,kind:l.enum(["worktree","folder"]),name:l.string().max(512),project:l.string().max(512).nullable().optional(),path:l.string().max(Hr),branch:l.string().max(512).nullable(),hostId:l.string().max(Hr).nullable()})).max(Aw),truncated:l.boolean()}),zw=l.strictObject({workspaceId:Un}),Dw=l.object({opened:l.literal(!0)}),Cw=200,jw=[[0,31],[127,159],[173,173],[847,847],[1564,1564],[4447,4448],[6068,6069],[6155,6159],[8203,8207],[8232,8238],[8288,8303],[10240,10240],[12644,12644],[65024,65039],[65279,65279],[65440,65440],[65520,65531],[119155,119162],[917504,921599]],Lw=e=>{let n=e.codePointAt(0)??0;return jw.some(([i,o])=>n>=i&&n<=o)},Uw=l.strictObject({workspaceId:Un,name:l.string().max(Cw).refine(e=>e.trim().length>0,"name must not be blank").refine(e=>![...e].some(Lw),"name must be one line of plain text")}),Rw=l.object({name:l.string()}),Mw=l.strictObject({workspaceId:Un,title:l.string().min(1).max(512).optional(),command:l.string().min(1).max(Tv).optional()}),Zw=l.object({terminalId:mp}),Vw=l.strictObject({terminalId:mp,maxLines:l.number().int().min(1).max(pp).optional()}),Gw=l.object({lines:l.array(l.string()).max(pp),truncated:l.boolean()}),Fw=l.strictObject({workspaceId:Un,agent:l.string().min(1).max(64).refine(Pv,"unknown agent"),prompt:l.string().min(1).max(Tv).optional()}),Bw=l.object({terminalId:mp});function Hw(e){let n=e.split(/[\\/]/);return!/^[a-z]:/i.test(e)&&n[0]!==""&&n.every(i=>i!=="..")&&n.some(i=>i!==""&&i!==".")}var Kw=l.strictObject({workspaceId:Un,path:l.string().min(1).max(Hr).refine(Hw,"path must be relative and inside the workspace")}),Ww=l.object({opened:l.literal(!0)}),Jw=l.strictObject({action:l.enum(dp)}),Xw=l.object({ran:l.literal(!0)}),Ev={"workspace.list":F({summary:"Lists every workspace, Git worktrees and folders, on every host Orca reaches.",since:3,scope:"any-workspace",capability:"workspace:read",mutation:!1,surfaces:le,params:Ow,result:Nw}),"workspace.open":F({summary:"Shows a workspace in Orca, as clicking it in the sidebar does; a folder workspace needs the desktop app.",since:3,scope:"any-workspace",capability:"workspace:write",mutation:!0,surfaces:le,params:zw,result:Dw}),"workspace.rename":F({summary:"Renames a workspace in Orca's sidebar, as renaming it by hand does; the name stays until it is renamed again.",since:5,scope:"any-workspace",capability:"workspace:write",mutation:!0,surfaces:le,params:Uw,result:Rw}),"terminal.create":F({summary:"Opens a terminal in a workspace, optionally running a command, in the background.",since:3,scope:"any-workspace",capability:"terminal:send",mutation:!0,surfaces:le,params:Mw,result:Zw}),"terminal.read":F({summary:`Reads a terminal's newest output as plain text, at most ${pp} lines.`,since:3,scope:"explicit-terminal",capability:"terminal:read",mutation:!1,surfaces:le,params:Vw,result:Gw}),"agent.launch":F({summary:"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.",since:3,scope:"any-workspace",capability:"agent:launch",mutation:!0,surfaces:le,params:Fw,result:Bw}),"editor.open":F({summary:"Opens a file of a workspace in an editor tab of Orca's window; desktop app only. `workspace.open` shows the workspace.",since:3,scope:"any-workspace",capability:"workspace:write",mutation:!0,surfaces:le,params:Kw,result:Ww}),"commands.run":F({summary:"Runs one of Orca's built-in actions in its window, never a plugin's replacement of it; desktop app only.",since:3,scope:"desktop",capability:"workspace:write",mutation:!0,surfaces:le,params:Jw,result:Xw})};var fp=l.string().regex(/^[a-f0-9]{64}$/),Av=l.string().min(1).max(16*1024),qw=l.strictObject({expectedRevision:fp,app:Av,terminal:Av}),Ov={"appearance.read":F({summary:"Reads the current desktop appearance revision for a conditional update.",since:4,scope:"desktop",capability:"appearance:write",mutation:!1,surfaces:le,params:l.strictObject({}).optional(),result:l.strictObject({revision:fp})}),"appearance.apply":F({summary:"Publishes owned app/editor and terminal colors and selects them in the desktop. Returns publication, not renderer acknowledgement.",since:4,scope:"desktop",capability:"appearance:write",mutation:!0,surfaces:le,params:qw,result:l.strictObject({revision:fp,themeId:l.templateLiteral(["plugin:",l.string()]),mode:l.enum(["dark","light"]),delivery:l.literal("published")})})};var Nv={...Sv,...Ov,...Ev};var Yw=4096,Qw=50,Kr=512,nt=1024,eI=l.strictObject({}).optional(),gp=l.object({branch:l.string().max(Kr),displayName:l.string().max(Kr),terminals:l.array(l.object({id:l.string().min(1).max(nt),title:l.string().max(Kr).optional()}).strict()).max(Qw)}).strict().nullable(),tI=gp,nI=l.strictObject({terminalId:l.string().min(1).max(nt),text:l.string().min(1).max(Yw),enter:l.boolean().default(!1)}),rI=l.object({accepted:l.boolean()}),iI=l.strictObject({title:l.string().min(1).max(120),body:l.string().max(1e3).optional()}),oI=l.object({delivered:l.boolean()}),hp={openExternal:"shell.openExternal",saveAs:"files.saveAs"},aI=8192,sI=["http:","https:","mailto:"],zv=64*1024*1024,cI=l.strictObject({url:l.string().min(1).max(aI).refine(mI,"url must be an http:, https: or mailto: URL")}),uI=l.object({opened:l.boolean(),reason:l.enum(["cancelled","consent-unavailable"]).optional()}),lI=l.union([l.string().max(zv),l.custom(e=>e instanceof ArrayBuffer||e instanceof Uint8Array,"data must be a string or ArrayBuffer").refine(e=>e.byteLength<=zv,"data exceeds 64 MiB").meta({type:"ArrayBuffer"})]),dI=l.strictObject({suggestedName:l.string().min(1).max(255).optional(),mimeType:l.string().min(1).max(255).optional(),data:lI}),pI=l.object({status:l.enum(["saved","cancelled"])});function mI(e){try{return sI.includes(new URL(e).protocol)}catch{return!1}}var Vt={"workspace.readContext":F({summary:"Branch, name and terminal ids of the focused workspace, or null when none is focused.",since:0,scope:"active-workspace",capability:"workspace:read",mutation:!1,surfaces:he,params:eI,result:tI}),"terminal.sendText":F({summary:"Types text into one terminal named by id, optionally pressing Enter.",since:0,scope:"explicit-terminal",capability:"terminal:send",mutation:!0,surfaces:le,params:nI,result:rI}),"notifications.show":F({summary:"Shows a desktop notification.",since:0,scope:"desktop",capability:"notifications:show",mutation:!0,surfaces:he,params:iI,result:oI}),"storage.get":F({summary:"Reads a JSON value from the plugin's private store; null when absent.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!1,framesSince:2,surfaces:he,params:Bh,result:Hh}),"storage.set":F({summary:"Writes a JSON value to the plugin's private store.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!0,framesSince:2,surfaces:he,params:Kh,result:Wh}),"storage.delete":F({summary:"Removes a key from the plugin's private store.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!0,framesSince:2,surfaces:he,params:Jh,result:Xh}),"storage.keys":F({summary:"Lists the keys in the plugin's private store.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!1,framesSince:2,surfaces:he,params:qh,result:Yh}),"secrets.get":F({summary:"Reads a secret from the plugin's encrypted secrets file; null when absent.",since:0,scope:"plugin-private",capability:"secrets:own",mutation:!1,framesSince:2,surfaces:he,params:Qh,result:ev}),"secrets.set":F({summary:"Stores a secret in the plugin's encrypted secrets file.",since:0,scope:"plugin-private",capability:"secrets:own",mutation:!0,framesSince:2,surfaces:he,params:tv,result:nv}),"secrets.delete":F({summary:"Removes a secret from the plugin's encrypted secrets file.",since:0,scope:"plugin-private",capability:"secrets:own",mutation:!0,framesSince:2,surfaces:he,params:rv,result:iv}),"secrets.protection":F({summary:"How secrets are protected at rest: `os-keyring`, or `unavailable` (no OS key store, or Linux `basic_text`), where `secrets.set` fails.",since:1,scope:"plugin-private",capability:"secrets:own",mutation:!1,framesSince:2,surfaces:he,params:ov,result:av}),"settings.get":F({summary:"Reads every setting the plugin owns.",since:0,scope:"plugin-private",capability:"settings:own",mutation:!1,framesSince:2,surfaces:he,params:sv,result:cv}),"settings.set":F({summary:"Writes one setting the plugin owns.",since:0,scope:"plugin-private",capability:"settings:own",mutation:!0,framesSince:2,surfaces:he,params:uv,result:lv}),"shell.openExternal":F({summary:"Opens an http:, https: or mailto: URL in the default browser. Needs a click or key press in the frame; desktop app only. Orca asks the person before an http(s) host the plugin was not allowed; `opened` is false if they cancel.",since:1,scope:"desktop",capability:null,userActivation:!0,mutation:!1,surfaces:zn,params:cI,result:uI}),"files.saveAs":F({summary:"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.",since:1,scope:"desktop",capability:null,userActivation:!0,mutation:!0,surfaces:zn,params:dI,result:pI}),...vv,...Nv},Dv=Object.keys(Vt).filter(Cv);function Cv(e){return Object.hasOwn(Vt,e)}function vp(e){return Cv(e)?Vt[e]:null}var jv=Math.max(...Dv.map(e=>Vt[e].since));function Wr(e){return Dv.filter(n=>Vt[n].surfaces.includes(e))}function ha(e,n){return Wr(e).filter(i=>n||Vt[i].userActivation!==!0)}function va(e,n="panel"){return Math.max(0,...e.map(i=>fv(Vt[i],n)))}var Ue=1,$z=l.number().refine(e=>e===Ue,{error:e=>typeof e.input=="number"&&e.input>Ue?`targets host API ${e.input}, but this Orca supports only ${Ue}; update Orca`:`must be ${Ue}`}).meta({const:Ue});var fI=new Set(["rate_limited","unavailable","timeout","conflict"]);function He(e,n=e,i){return Object.assign(new Error(n),{code:e,retryable:fI.has(e),...i===void 0?{}:{retryAfterMs:i}})}var gI=new Set(["plugin.request","events.subscribe","storage.get","storage.set","storage.delete","storage.keys","settings.get","settings.set","secrets.get","secrets.set","secrets.delete","secrets.protection"]);function ya(e,n){return typeof e=="object"&&e!==null&&n in e?Object.getOwnPropertyDescriptor(e,n)?.value:void 0}function Lv(e){let n={storage:new Map(Object.entries(e.storage??{})),settings:new Map(Object.entries(e.settings??{})),secrets:new Map(Object.entries(e.secrets??{}))},i=new Set,o=(a,s,c)=>{if(c===void 0?n[a].delete(s):n[a].set(s,c),a!=="secrets")for(let u of i)u(a,s);return{ok:!0}},t=null;async function r(a){let s=String(ya(a,"name")),c=ya(a,"input");if(!e.requests||!Object.hasOwn(e.requests,s)){if(t)return{output:await t(s,c)??null};throw He("unknown_method",`no panel handler named ${s}`)}let u=e.requests[s];return{output:(typeof u=="function"?await u(c):u)??null}}return{handles:a=>gI.has(a),async answer(a,s){let c=String(ya(s,"key")),u=ya(s,"value");switch(a){case"plugin.request":return r(s);case"storage.get":return{value:n.storage.get(c)??null};case"storage.keys":return{keys:[...n.storage.keys()]};case"settings.get":return{settings:Object.fromEntries(n.settings)};case"secrets.get":return{value:n.secrets.get(c)??null};case"secrets.protection":return{status:"os-keyring"};case"storage.set":return o("storage",c,u);case"settings.set":return o("settings",c,u);case"secrets.set":return o("secrets",c,u);case"storage.delete":return o("storage",c,void 0);case"secrets.delete":return o("secrets",c,void 0);default:return{ok:!0}}},routeRequests(a){return t=a,()=>{t===a&&(t=null)}},onChange(a){return i.add(a),()=>i.delete(a)},snapshot:()=>({storage:Object.fromEntries(n.storage),settings:Object.fromEntries(n.settings),secrets:Object.fromEntries(n.secrets)})}}var ba=class extends Error{constructor(i,o){super(o);this.code=i}code};function hI(e){return typeof e=="function"}var Uv={ok:!0};function vI(e){return e.result.safeParse(Uv).success}var yp={"workspace.readContext":null,"notifications.show":{delivered:!0},"shell.openExternal":{opened:!0},"files.saveAs":{status:"saved"},"agents.list":{agents:[]}};function Rv(e,n){let i=new Set(e.capabilities??n),o=new Map(Object.entries(e.results??{})),t=Lv(e),r={panel:ha("panel",!0),worker:Wr("worker")},a=[],s=[],c=new Set,u=0,d=(g,y)=>{throw new ba(g,y)};async function p(g,y,x){let I=vp(g);if(!I)return d("unknown_method",`${g} does not exist in this Orca`);if(!I.surfaces.includes(x))return d("not_permitted",`${g} is not available to ${x}s`);if(I.userActivation===!0){if(u===0)return d("not_permitted",`${g} needs a click or key press in the frame`);u-=1}if(I.capability&&!i.has(I.capability))return d("capability_denied",`${g} needs the ${I.capability} capability`);let b=I.params.safeParse(y);return b.success?b.data:d("invalid_params",b.error.issues[0]?.message??"invalid params")}async function m(g,y,x){let I;try{I=await p(g,y,x)}catch(E){throw E instanceof ba?(s.push({method:g,code:E.code,message:E.message}),He(E.code,E.message)):E}let b=vp(g);if(!b)throw He("unknown_method",g);let T=!o.has(g)&&t.handles(g),A=vI(b);if(!o.has(g)&&!T&&!Object.hasOwn(yp,g)&&!A)throw He("unavailable",`the test configured no result for ${g}`);let D=o.has(g)?o.get(g):Object.hasOwn(yp,g)?yp[g]:Uv,w=T?await t.answer(g,I):hI(D)?await D(I):D,O=b.result.safeParse(w);if(!O.success)throw new TypeError(`mockHost: the result configured for ${g} does not match the host API: ${O.error.issues[0]?.message??"invalid result"}`);return O.data}let f=g=>({api:{major:Ue,minor:va(r[g])},call(y,x){a.push({method:y,params:x});let I=m(y,x,g);return c.add(I),I.catch(()=>{}).finally(()=>c.delete(I)),I},has:y=>r[g].some(x=>x===y)});return{calls:a,refusals:s,capabilities:[...i],callsOf:g=>a.filter(y=>y.method===g).map(y=>y.params),respond:(g,y)=>o.set(g,y),grantGesture:()=>{u=1},data:()=>t.snapshot(),onDataChange:g=>t.onChange(g),routeRequests:g=>t.routeRequests(g),async idle(){for(;c.size>0;)await Promise.allSettled(c)},bridge:f("panel"),worker:f("worker")}}var yI=l.object({label:H,labelHidden:N,value:jn.optional(),defaultValue:jn.optional(),placeholder:G.optional(),rows:l.number().int().min(2).max(20).optional(),maxLength:l.number().int().positive().max(j.maxTextLength).optional(),invalid:N,disabled:N,onChange:Y,onSubmit:Y,...ve}).strict(),bI=l.object({label:H,description:G.optional(),checked:N,defaultChecked:N,disabled:N,onChange:Y,...ve}).strict(),_I=l.object({label:H,labelHidden:N,value:G.optional(),defaultValue:G.optional(),options:l.array(l.object({value:H,label:H,description:G.optional(),disabled:N}).strict()).max(j.maxRadioOptions),orientation:l.enum(["vertical","horizontal"]).optional(),disabled:N,onChange:Y,...ve}).strict(),_a=l.number().refine(Number.isFinite,"must be finite"),$I=l.object({label:H,labelHidden:N,value:_a.optional(),defaultValue:_a.optional(),min:_a.optional(),max:_a.optional(),step:l.number().positive().refine(Number.isFinite).optional(),showValue:N,disabled:N,onChange:Y,...ve}).strict(),kI=l.object({label:H,description:G.optional(),error:G.optional(),required:N}).strict(),Mv={Textarea:R(yI,"none"),Checkbox:R(bI,"none"),RadioGroup:R(_I,"none"),Slider:R($I,"none"),Field:R(kI,"any")};var xI=l.object({label:H,value:G.optional(),defaultValue:G.optional(),onChange:Y,...ve}).strict(),wI=l.object({value:H,label:H,icon:Le.optional(),disabled:N}).strict(),II=l.object({title:H,description:G.optional(),open:N,defaultOpen:N,disabled:N,onChange:Y,...ve}).strict(),SI=l.object({label:H,gap:Ee}).strict(),PI=l.object({columns:l.number().int().min(1).max(6).optional(),minItemWidth:l.enum(["xs","sm","md","lg"]).optional(),gap:Ee}).strict(),Zv={Tabs:R(xI,"tabs"),Tab:R(wI,"any"),Collapsible:R(II,"any"),Toolbar:R(SI,"any"),Grid:R(PI,"any")};function Re(e){return typeof e=="object"&&e!==null}var Jr=["long","short","duration"],bp=l.number().refine(Number.isFinite,"must be a finite time in milliseconds"),Vv=l.object({type:l.literal("relative"),at:bp,format:l.enum(Jr).optional()}).strict(),TI=[["day",864e5],["hour",36e5],["minute",6e4],["second",1e3]];function EI(e){let n=Math.abs(e),[i,o]=TI.find(([,t])=>n>=t)??["second",1e3];return{value:Math.trunc(e/o),unit:i}}function Gv(e,n,i,o){let{value:t,unit:r}=EI(e-n);return i==="duration"?new Intl.NumberFormat(o,{style:"unit",unit:r,unitDisplay:"narrow"}).format(Math.abs(t)):new Intl.RelativeTimeFormat(o,{numeric:i==="long"?"auto":"always",style:i==="long"?"long":"narrow"}).format(t,r)}var _p=l.string().min(1).max(64),AI=l.union([l.string().max(j.maxLabelLength),l.number(),l.boolean(),l.null(),Vv]),Fv=e=>new Set(e.map(n=>n.key)).size===e.length,OI=l.object({label:H,columns:l.array(l.object({key:_p,label:H,align:l.enum(["start","center","end"]).optional(),width:l.enum(["auto","xs","sm","md","lg"]).optional(),minWidth:l.enum(["auto","xs","sm","md"]).optional(),hideBelow:l.enum(["sm","md"]).optional(),sortable:N}).strict()).min(1).max(j.maxTableColumns).refine(Fv,"column keys must be unique"),rows:l.array(l.object({key:Zt,cells:l.record(_p,AI.refine(e=>typeof e!="number"||Number.isFinite(e))).refine(e=>Object.keys(e).length<=j.maxTableColumns),disabled:N}).strict()).max(j.maxTableRows).refine(Fv,"row keys must be unique"),sort:l.object({column:_p,direction:l.enum(["asc","desc"])}).strict().optional(),selectionMode:l.enum(["none","single","multiple"]).optional(),selected:l.array(Zt).max(j.maxTableRows).optional(),defaultSelected:l.array(Zt).max(j.maxTableRows).optional(),hasMore:N,loading:N,emptyText:G.optional(),maxHeight:Br,onSortChange:Y,onSelectionChange:Y,onRowAction:Y,onLoadMore:Y,...ve}).strict(),NI=l.object({key:Zt,label:H,description:G.optional(),icon:Le.optional(),disabled:N,hasChildren:N,children:l.array(l.unknown()).optional()}).strict();function zI(e){let n=[],i=new Set,o=[{raw:e,out:n,depth:1}],t=0;for(let r=o.pop();r;r=o.pop()){if(r.depth>j.maxTreeItemDepth)return`items nest deeper than ${j.maxTreeItemDepth}`;for(let a of r.raw){if(++t>j.maxTreeItems)return`more than ${j.maxTreeItems} items`;let s=NI.safeParse(Re(a)?a:null);if(!s.success)return"each item needs a key and a label";let{children:c,...u}=s.data;if(i.has(u.key))return`duplicate item key ${JSON.stringify(u.key.slice(0,64))}`;i.add(u.key);let d=u;r.out.push(d),c&&(d.children=[],o.push({raw:c,out:d.children,depth:r.depth+1}))}}return n}var DI=l.array(l.unknown()).transform((e,n)=>{let i=zI(e);return typeof i=="string"?(n.addIssue({code:"custom",message:i}),l.NEVER):i}),CI=l.object({label:H,items:DI,selected:G.optional(),defaultSelected:G.optional(),expanded:l.array(Zt).max(j.maxTreeItems).optional(),defaultExpanded:l.array(Zt).max(j.maxTreeItems).optional(),maxHeight:Br,onSelect:Y,onToggle:Y,onAction:Y,...ve}).strict(),Bv={Table:R(OI,"none"),Tree:R(CI,"none")};var Hv=l.string().max(j.maxDocumentLength),jI=/\.(?:png|jpe?g|webp)$/i;function LI(e){return jI.test(e)&&!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(e)&&!/[\\\0?#]/.test(e)&&e.split("/").every(n=>n!==""&&n!=="."&&n!=="..")}var UI=l.object({content:Hv}).strict(),RI=l.object({code:Hv,language:l.string().regex(/^[A-Za-z0-9_+#.-]{1,64}$/).optional(),label:G.optional(),lineNumbers:N,wrap:N,maxHeight:Br}).strict(),MI=l.object({src:l.string().max(512).refine(LI,"must be an image file in the plugin"),alt:G,size:l.enum(["xs","sm","md","lg","full"]).optional(),fit:l.enum(["contain","cover"]).optional(),rounded:N}).strict(),ZI=l.object({label:G.optional(),items:l.array(l.object({label:H,value:l.union([G,l.number().refine(Number.isFinite),l.boolean(),l.null()]),tone:tt,code:N}).strict()).max(j.maxKeyValueItems)}).strict(),VI=l.object({tone:tt,title:G.optional(),icon:Le.optional()}).strict(),GI=l.object({at:bp,format:l.enum(Jr).optional(),tone:tt,muted:N}).strict(),FI=l.object({shape:l.enum(["text","block","circle"]).optional(),lines:l.number().int().min(1).max(10).optional(),label:G.optional()}).strict(),Kv={Markdown:R(UI,"none"),CodeView:R(RI,"none"),Image:R(MI,"none"),KeyValue:R(ZI,"none"),Callout:R(VI,"any"),Skeleton:R(FI,"none"),RelativeTime:R(GI,"none")};var BI=4096,Rn=l.union([l.number().min(0).max(BI),l.string().regex(/^(100|[1-9]?[0-9])%$/)]).optional(),HI=["none","muted","card",...Mt],KI=l.object({direction:l.enum(["row","column"]).optional(),gap:Ee,padding:Ee,paddingX:Ee,paddingY:Ee,margin:Ee,align:Fr,justify:l.enum(["start","center","end","between"]).optional(),wrap:N,grow:N,shrink:N,width:Rn,height:Rn,minWidth:Rn,minHeight:Rn,maxWidth:Rn,maxHeight:Rn,columns:l.number().int().min(1).max(6).optional(),scroll:l.enum(["x","y","both"]).optional(),border:l.union([l.boolean(),l.enum(Mt)]).optional(),radius:l.enum(["none","sm","md","lg","full"]).optional(),background:l.enum(HI).optional()}).strict(),Wv={Box:R(KI,"any")};var WI=yt,Jv=new Set(WI),JI=l.object({gap:Ee,align:Fr}).strict(),XI=l.object({gap:Ee,align:Fr,justify:l.enum(["start","center","end","between"]).optional(),wrap:N}).strict(),qI=l.object({tone:tt,muted:N,size:ga,weight:l.enum(["regular","medium","semibold"]).optional(),truncate:N}).strict(),YI=l.object({level:l.union([l.literal(1),l.literal(2),l.literal(3)]).optional()}).strict(),QI=l.object({variant:up,tone:tt,size:ga,icon:Le.optional(),tooltip:G.optional(),disabled:N,onPress:Y}).strict(),eS=l.object({icon:Le,label:H,tooltip:G.optional(),variant:up,tone:tt,size:ga,disabled:N,onPress:Y}).strict(),tS=l.object({tone:tt}).strict(),nS=l.object({title:G.optional(),description:G.optional()}).strict(),rS=l.object({label:G.optional()}).strict(),iS=l.object({title:G.optional(),description:G.optional(),icon:Le.optional(),current:N,disabled:N,onPress:Y}).strict(),oS=l.object({label:H,labelHidden:N,value:jn.optional(),defaultValue:jn.optional(),placeholder:G.optional(),type:l.enum(["text","search","password"]).optional(),maxLength:l.number().int().positive().max(j.maxTextLength).optional(),invalid:N,disabled:N,onChange:Y,onSubmit:Y,...ve}).strict(),aS=l.object({label:H,checked:N,defaultChecked:N,disabled:N,onChange:Y,...ve}).strict(),sS=l.object({label:H,labelHidden:N,value:G.optional(),defaultValue:G.optional(),placeholder:G.optional(),options:l.array(l.object({value:H,label:H,disabled:N}).strict()).max(j.maxSelectOptions),disabled:N,onChange:Y,...ve}).strict(),cS=l.object({orientation:l.enum(["horizontal","vertical"]).optional()}).strict(),uS=l.object({label:H,labelHidden:N,value:l.number().min(0).max(100).optional()}).strict(),lS=l.object({label:H,labelHidden:N}).strict(),dS=l.object({title:H,description:G.optional(),icon:Le.optional()}).strict(),pS=l.object({href:l.string().max(2048).regex(/^(https?:\/\/|mailto:)/i).optional(),onPress:Y}).strict(),mS=l.object({block:N}).strict(),Xr={Stack:R(JI,"any"),Row:R(XI,"any"),Text:R(qI,"inline"),Heading:R(YI,"inline"),Button:R(QI,"text"),IconButton:R(eS,"none"),Badge:R(tS,"text"),Card:R(nS,"any"),List:R(rS,"items"),ListItem:R(iS,"any"),Input:R(oS,"none"),Switch:R(aS,"none"),Select:R(sS,"none"),Separator:R(cS,"none"),Progress:R(uS,"none"),Spinner:R(lS,"none"),EmptyState:R(dS,"any"),Link:R(pS,"text"),Code:R(mS,"text"),...Mv,...Zv,...Bv,...Kv,...Wv};function $p(e){return typeof e=="string"&&Object.hasOwn(Xr,e)}var dD=Object.keys(Xr).filter($p),Xv=new Set(["Text","Link","Code","Badge","RelativeTime"]),qv=new Set(["Button","IconButton","Input","Switch","Select","Link","ListItem","Textarea","Checkbox","RadioGroup","Slider","Tabs","Collapsible","Table","Tree","Markdown"]),$a={items:"ListItem",tabs:"Tab"};var fS=new Set(["type","key","props","children"]),kp=32,gS=8,hS=/^[A-Z][A-Za-z0-9]{0,63}$/,vS=20,yS=new Map([[$a.items,"items"],[$a.tabs,"tabs"]]),bS={ListItem:"List",Tab:"Tabs"};function Yv(e){return e==="items"||e==="tabs"?$a[e]:void 0}var ka=class extends Error{};function te(e,n){throw new ka(`${e}: ${n}`)}function _S(e,n){return e===void 0?[]:(Array.isArray(e)||te(n,"children must be an array"),e)}function $S(e,n,i){let o=Xr[e].props.shape,t={};for(let[r,a]of Object.entries(n)){if(a===void 0)continue;if(!Object.hasOwn(o,r)){i(`${e}: ignored unknown prop "${r.slice(0,64)}"`);continue}let s=o[r].safeParse(a);if(!s.success){i(`${e}: ignored invalid ${r}`);continue}t[r]=s.data}for(let[r,a]of Object.entries(o))if(!Object.hasOwn(t,r)&&!a.safeParse(void 0).success)return i(`${e}: missing required ${r}; showing a placeholder`),null;return typeof t.icon=="string"&&!Jv.has(t.icon)&&i(`${e}: unknown icon "${t.icon}"; showing a fallback glyph`),t}function Qv(e){if(e===null)return{ok:!0,tree:null,nodes:0,warnings:[]};let n=new Set,i=u=>{n.sizej.maxTextLength&&te(I,"text is too long"),r+ ++a>j.maxNodes&&te(I,`more than ${j.maxNodes} nodes`),s+=gS+Ne.length,s>j.maxTreeBytes&&te(I,`tree exceeds ${j.maxTreeBytes} bytes`),x.push(Ne);continue}Re(d)||te(I,"expected a node, string or number");let b=d.type,T=$p(b);!T&&(typeof b!="string"||!hS.test(b))&&te(I,`invalid component name ${JSON.stringify(String(b)).slice(0,80)}`),(f==="none"||f==="text")&&te(I,`${m} accepts only text`),f==="inline"&&T&&!Xv.has(b)&&te(I,`${m} accepts only text, Text, Link, Code, Badge and RelativeTime`);let A=Yv(f),D=typeof b=="string"?yS.get(b):void 0;A!==void 0&&b!==A&&te(I,`${m} holds only ${A}`),D!==void 0&&D!==f&&te(I,`${b} must be a direct child of ${bS[b]??"its container"}`),g&&qv.has(b)&&te(I,`a ListItem with onPress cannot contain a ${b}`),++r+a>j.maxNodes&&te(I,`more than ${j.maxNodes} nodes`),p>j.maxDepth&&te(I,`deeper than ${j.maxDepth}`);for(let Ne of Object.keys(d))fS.has(Ne)||te(I,`unknown node field ${Ne.slice(0,64)}`);let w=d.key;w!==void 0&&((typeof w!="string"||w.length>j.maxLabelLength)&&te(I,"key must be a short string"),y.has(w)&&te(I,`duplicate key ${JSON.stringify(w.slice(0,64))} among siblings`),y.add(w));let O=w===void 0?{}:{key:w};if(!T){i(`unknown component ${b}; showing a placeholder`),x.push({type:"Unsupported",...O,props:{name:b},children:[]}),s+=kp+b.length;continue}let E=d.props??{};(!Re(E)||Object.keys(E).length>j.maxPropsPerNode)&&te(I,"props must be an object with few fields");let B=$S(b,E,i);if(!B){x.push({type:"Unsupported",...O,props:{name:b},children:[]}),s+=kp+b.length;continue}s+=kp+JSON.stringify(B).length,s>j.maxTreeBytes&&te(I,`tree exceeds ${j.maxTreeBytes} bytes`);let C=[],ne={type:b,...O,props:B,children:C};x.push(ne);let ye=_S(d.children,I);ye.length>j.maxNodes&&te(I,`more than ${j.maxNodes} children`);let Oe=new Set,ti=g||b==="ListItem"&&B.onPress!==void 0;for(let Ne=ye.length-1;Ne>=0;Ne--)t.push({raw:ye[Ne],depth:p+1,parentType:b,policy:Xr[b].children,inPressable:ti,siblingKeys:Oe,out:C,path:`${I}.children.${Ne}`})}}catch(u){if(u instanceof ka)return{ok:!1,error:u.message};throw u}let c=o[0];return typeof c=="object"?{ok:!0,tree:c,nodes:r,warnings:[...n]}:{ok:!1,error:"tree: expected a node"}}var Mn=l.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),Gt=l.string().min(1).max(64),xp=l.union([l.string().max(j.maxTextLength),l.number().refine(Number.isFinite),l.boolean(),l.null()]),ey=l.array(xp).max(j.maxEventArgItems),kS=l.union([xp,ey,l.record(l.string().max(64),l.union([xp,ey])).refine(e=>Object.keys(e).length<=16,"too many fields")]),xD=l.object({document:Gt,revision:Mn,callback:l.string().min(1).max(j.maxCallbackIdLength).regex(fa),args:l.array(kS).max(j.maxEventArgs),edit:Mn.optional()}).strict(),xS=l.object({document:Gt,revision:Mn,tree:l.unknown()}).strict(),ty=l.object({document:Gt.optional(),revision:Mn,message:l.string().max(j.maxLabelLength*4),stack:l.string().max(8192).optional()}).strict();function ny(e){let n=xS.safeParse(e);if(!n.success)return{ok:!1,document:null,revision:null,error:"malformed render"};let{document:i,revision:o}=n.data,t=Qv(n.data.tree);return t.ok?{...t,document:i,revision:o}:{ok:!1,document:i,revision:o,error:t.error}}var wD=l.object({document:Gt,revision:Mn,accepted:l.boolean(),retryAfterMs:l.number().int().nonnegative().max(6e4).optional()}).strict(),ID=l.object({document:Gt}).strict(),ry=/^[A-Za-z0-9_.:-]{1,128}$/,wS=["plugin","workspace"],wp=l.number().int().min(0).max(1e6),SD=l.object({version:wp,value:l.json()}).strict().refine(e=>JSON.stringify(e.value).length<=ua,"persisted value is too large"),iy=l.object({document:Gt,entries:l.array(l.object({scope:l.enum(wS),key:l.string().regex(ry),version:wp,value:l.json()}).strict()).min(1).max(64),hydration:Mn.optional()}).strict(),PD=l.object({document:Gt,entries:l.array(l.object({key:l.string().regex(ry),version:wp,value:l.json().optional()}).strict().refine(e=>JSON.stringify(e.value??null).length<=ua,"retained value is too large")).min(1).max(64)}).strict();import{installOrcaDeclarativeRuntime as US}from"./orca-runtime.mjs";var IS={Stack:"group",Row:"group",Card:"region",Text:"text",Heading:"heading",Button:"button",IconButton:"button",Badge:"status",List:"list",ListItem:"listitem",Input:"textbox",Switch:"switch",Select:"combobox",Separator:"separator",Progress:"progressbar",Spinner:"status",EmptyState:"group",Link:"link",Code:"code",Textarea:"textbox",Checkbox:"checkbox",RadioGroup:"radiogroup",Slider:"slider",Field:"group",Tabs:"tablist",Tab:"tab",Collapsible:"group",Toolbar:"toolbar",Grid:"group",Table:"grid",Tree:"tree",Markdown:"document",CodeView:"code",Image:"img",KeyValue:"list",Callout:"note",Skeleton:"status",RelativeTime:"time",Box:"group",Unsupported:"unsupported"};function ay(e,n){if(typeof e!="number")return"";let i=Jr.find(o=>o===n)??"short";return Gv(e,Date.now(),i,"en")}function SS(e){return typeof e=="object"&&e!==null&&"at"in e?ay(e.at,"format"in e?e.format:void 0):String(e??"")}var PS=["title","description","content","code","error","alt"];function TS(e){let n=[],i=[e.rows,e.items].flatMap(o=>Array.isArray(o)?o:[]);for(let o=i.shift();o!==void 0;o=i.shift()){if(typeof o!="object"||o===null)continue;let t={...o};typeof t.cells=="object"&&t.cells!==null&&n.push(...Object.values(t.cells).map(SS));for(let r of["label","value"])t[r]!==void 0&&t[r]!==null&&n.push(String(t[r]));Array.isArray(t.children)&&i.push(...t.children)}return n}function sy(e){if(typeof e=="string")return e;let n=e.props,i=PS.map(o=>n[o]).filter(o=>typeof o=="string");return e.type==="RelativeTime"?ay(n.at,n.format):e.type==="Tab"?typeof n.label=="string"?n.label:"":[...i,...TS(n),...e.children.map(sy)].join(" ")}var Ip=e=>e.replace(/\s+/g," ").trim();function ES(e){let n=e.props,i=n.label??n.title;return typeof i=="string"?i:void 0}function AS(e,n){let i=e.props,o=e.type==="ListItem"&&i.onPress!==void 0;return{type:e.type,key:e.key?.startsWith("#")?void 0:e.key,role:o?"button":IS[e.type]??"generic",label:ES(e),text:Ip(sy(e)),props:i,disabled:i.disabled===!0,path:n}}function xa(e){let n=[],i=(o,t)=>{n.push(AS(o,t)),o.children.forEach((r,a)=>{typeof r!="string"&&i(r,`${t}/${r.key??`#${a}`}`)})};return e&&i(e,e.key??"#0"),n}function oy(e,n){return e===void 0?!1:typeof n=="string"?Ip(e)===Ip(n):n.test(e)}function cy(e,n){return(n.type===void 0||e.type===n.type)&&(n.role===void 0||e.role===n.role)&&(n.text===void 0||oy(e.text,n.text))&&(n.label===void 0||oy(e.label,n.label))&&(n.testId===void 0||e.key===n.testId)}function uy(e){return e.filter(n=>!e.some(i=>i.path.startsWith(`${n.path}/`)))}function ly(e){return Object.entries(e).map(([n,i])=>`${n}: ${i instanceof RegExp?String(i):JSON.stringify(i)}`).join(", ")}function Sp(e){if(typeof e=="string")return e;let n={};for(let[i,o]of Object.entries(e.props)){if(i.startsWith("$"))continue;let t=typeof o=="object"&&o!==null&&"$callback"in o;n[i]=t?"[handler]":o}return{type:e.type,...e.key&&!e.key.startsWith("#")?{key:e.key}:{},...Object.keys(n).length>0?{props:n}:{},...e.children.length>0?{children:e.children.map(Sp)}:{}}}var OS={value:"defaultValue",checked:"defaultChecked",open:"defaultOpen",selected:"defaultSelected"};function Pp(e){return typeof e=="string"||typeof e=="boolean"||typeof e=="number"||Array.isArray(e)&&e.every(n=>typeof n=="string")}function NS(e){switch(e.type){case"Switch":case"Checkbox":case"Collapsible":return!1;case"Slider":return typeof e.props.min=="number"?e.props.min:0;case"Table":return[];default:return""}}function dy(){let e=new Map;function n(t){let r=cp(t.type);if(!r)throw new Error(`a ${t.type} has no value; expected a form control, Tabs or Table`);return r}function i(t){let r=e.get(t.path);if(r)return r;let{value:a}=n(t),s=t.props[a],c=t.props[OS[a]??""],d={value:Pp(s)?s:Pp(c)?c:NS(t),sent:0};return e.set(t.path,d),d}function o(t,r){let{value:a,change:s}=n(t);if(t.props[a]!==void 0&&t.props[s]===void 0)throw new Error(`the ${t.type} is read-only: it has a ${a} and no ${s}`);let c=i(t);return c.sent+=1,c.value=r,{value:r,id:c.sent}}return{observe(t){let r=new Map;for(let a of t){let s=cp(a.type);if(!s)continue;let c=e.get(a.path)??i(a),u=a.props[s.value],d=a.props[ma];Pp(u)&&(typeof d=="number"?d:0)>=c.sent&&(c.value=u),r.set(a.path,c)}e=r},valueOf:t=>i(t).value,edit:o,typeInto(t,r,a){if(t.type!=="Input"&&t.type!=="Textarea")throw new Error(`type: a ${t.type} takes no text; expected an Input or Textarea`);let s=a?"":String(i(t).value),c=[];a&&c.push(o(t,s));for(let u of r)s+=u,c.push(o(t,s));return c},toggle(t){if(!["Switch","Checkbox","Collapsible"].includes(t.type))throw new Error(`toggle: expected a Switch, Checkbox or Collapsible, not a ${t.type}`);return o(t,!i(t).value)}}}function py(e){let n=new Map,i=(s,c)=>{let u=n.get(s)??new Set;return u.add(c),n.set(s,u),{dispose:()=>u.delete(c)}},o=(s,c)=>{for(let u of n.get(s)??[])u(c)},t=s=>c=>typeof c=="object"&&c!==null&&s in c?Object.getOwnPropertyDescriptor(c,s)?.value:void 0,r=()=>{};return{facades:{plugin:{request:(s,c,u)=>{let d=typeof u=="object"&&u!==null&&"timeoutMs"in u?u.timeoutMs:void 0;return e("plugin.request",{name:s,...c===void 0?{}:{input:c},...typeof d=="number"?{timeoutMs:d}:{}}).then(t("output"))},on:(s,c)=>i(`message:${s}`,c)},storage:{get:s=>e("storage.get",{key:s}).then(c=>t("value")(c)??null),set:(s,c)=>e("storage.set",{key:s,value:c}).then(r),delete:s=>e("storage.delete",{key:s}).then(r),keys:async()=>{let s=t("keys")(await e("storage.keys"));return Array.isArray(s)?s.map(String):[]},onDidChange:s=>i("changed:storage",c=>s({key:String(c)}))},settings:{get:async s=>{let c=t("settings")(await e("settings.get"));return s===void 0?c:t(s)(c)},set:(s,c)=>e("settings.set",{key:s,value:c}).then(r),onDidChange:s=>i("changed:settings",c=>s({key:String(c)}))},secrets:{get:async s=>{let c=t("value")(await e("secrets.get",{key:s}));return typeof c=="string"?c:null},set:(s,c)=>e("secrets.set",{key:s,value:c}).then(r),delete:s=>e("secrets.delete",{key:s}).then(r)}},post:(s,c)=>o(`message:${s}`,c),changed:(s,c)=>o(`changed:${s}`,c)}}var my=Symbol.for("orca.testing.stored");function fy(e,n){return Object.freeze({[my]:!0,version:n,value:e})}function zS(e){return typeof e=="object"&&e!==null&&my in e}function gy(e,n){for(let i of["plugin","workspace"])for(let[o,t]of Object.entries(n?.[i]??{}))e[i].set(o,zS(t)?{version:t.version,value:t.value}:{version:1,value:t})}var DS={sm:360,md:480};function hy(e,n){let i=e.filter(o=>!o.hideBelow||n>=DS[o.hideBelow]);return i.length>0?i:e.slice(0,1)}var Tp=(e,n)=>e.path.startsWith(`${n.path}/`);function wa(e,n){return n.filter(i=>i.type==="Tab"&&Tp(i,e)&&!i.path.slice(e.path.length+1).includes("/"))}function Ep(e,n,i){let o=wa(e,n);return o.find(t=>t.props.value===i)??o.find(t=>!t.disabled)}function vy(e,n){let i=[];for(let o of e)if(!i.some(t=>Tp(o,t))&&(o.type==="Collapsible"&&n(o)!==!0&&i.push(o),o.type==="Tabs")){let t=Ep(o,e,n(o));i.push(...wa(o,e).filter(r=>r!==t))}return e.filter(o=>!i.some(t=>Tp(o,t)))}function yy(e){return typeof e=="object"&&e!==null&&"key"in e&&typeof e.key=="string"}function Ap(e){return Array.isArray(e.props.rows)?e.props.rows.filter(yy):[]}function Op(e){let n=[],i=Array.isArray(e.props.items)?[...e.props.items]:[];for(let o=i.shift();o!==void 0;o=i.shift())if(yy(o)){n.push(o);let t="children"in o?o.children:void 0;Array.isArray(t)&&i.push(...t)}return n}function Ae(e,n,i){if(e.type!==n)throw new Error(`${i}: expected a ${n}, not a ${e.type}`)}function Np(e,n,i){let o=Ap(e).find(t=>t.key===n);if(!o||o.disabled===!0)throw new Error(`${i}: the Table has no enabled row "${n}"`);return o}function Ia(e,n,i){let o=Op(e).find(t=>t.key===n);if(!o)throw new Error(`${i}: the Tree has no item "${n}"`);return o}function by(e){return e.hasChildren===!0||Array.isArray(e.children)&&e.children.length>0}function _y(e,n){let o=(Array.isArray(e.props.columns)?e.props.columns:[]).find(a=>typeof a=="object"&&a!==null&&"key"in a&&a.key===n);if(!o||typeof o!="object"||!("sortable"in o)||o.sortable!==!0)throw new Error(`sort: the Table has no sortable column "${n}"`);let t=e.props.sort,r=typeof t=="object"&&t!==null&&"column"in t&&t.column===n&&"direction"in t&&t.direction==="asc";return{column:n,direction:r?"desc":"asc"}}function CS(e,n){return e.type==="Tabs"?wa(e,n).map(o=>({value:o.props.value,disabled:o.disabled})):(Array.isArray(e.props.options)?e.props.options:[]).map(o=>typeof o=="object"&&o!==null&&"value"in o?{value:o.value,disabled:"disabled"in o&&o.disabled===!0}:{value:void 0,disabled:!0})}function $y({host:e,controls:n,resolve:i,elements:o,dispatch:t,settled:r}){let a=new Map,s=d=>{let p=a.get(d.path);if(!p){let m=d.props.expanded??d.props.defaultExpanded;p=new Set(Array.isArray(m)?m.filter(f=>typeof f=="string"):[]),a.set(d.path,p)}return p},c=async(d,p,m,f)=>{let g=n.edit(d,m);t(p,[f],g.id),await r()},u=async(d,p,m,f)=>{let g=i(d,f);if(Ae(g,"Tree",f),!by(Ia(g,p,f)))throw new Error(`${f}: the Tree item "${p}" has no children`);let y=s(g);y.has(p)!==m&&(m?y.add(p):y.delete(p),t(g.props.onToggle,[{key:p,expanded:m}]),await r())};return{async press(d){let p=i(d,"press"),{href:m,onPress:f}=p.props;if(p.type==="Link"&&typeof m=="string")e.grantGesture(),e.bridge.call("shell.openExternal",{url:m}).catch(()=>{});else{if(f===void 0)throw new Error(`press: the ${p.type} has no onPress`);e.grantGesture()}t(f,[]),await r()},async type(d,p,m={}){let f=i(d,"type");for(let g of n.typeInto(f,p,m.replace===!0))t(f.props.onChange,[g.value],g.id),await r();m.submit&&(t(f.props.onSubmit,[n.valueOf(f)]),await r())},async submit(d){let p=i(d,"submit");t(p.props.onSubmit,[n.valueOf(p)]),await r()},async toggle(d){let p=i(d,"toggle"),m=n.toggle(p);t(p.props.onChange,[m.value],m.id),await r()},async select(d,p){let m=i(d,"select");if(!["Select","RadioGroup","Tabs"].includes(m.type))throw new Error(`select: expected a Select, RadioGroup or Tabs, not a ${m.type}`);let f=CS(m,o()).find(g=>g.value===p);if(!f||f.disabled)throw new Error(`select: the ${m.type} has no enabled option "${p}"`);await c(m,m.props.onChange,p,p)},async slide(d,p){let m=i(d,"slide");Ae(m,"Slider","slide");let f=typeof m.props.min=="number"?m.props.min:0,g=typeof m.props.max=="number"?m.props.max:100;if(!Number.isFinite(p)||pg)throw new Error(`slide: ${p} is outside the Slider's ${f} to ${g}`);await c(m,m.props.onChange,p,p)},async sort(d,p){let m=i(d,"sort");if(Ae(m,"Table","sort"),m.props.onSortChange===void 0)throw new Error("sort: the Table has no onSortChange");t(m.props.onSortChange,[_y(m,p)]),await r()},async selectRows(d,p){let m=i(d,"selectRows");Ae(m,"Table","selectRows");let f=m.props.selectionMode??"none";if(f==="none"||f==="single"&&p.length>1)throw new Error(`selectRows: the Table's selectionMode is ${String(f)}`);p.forEach(g=>Np(m,g,"selectRows")),await c(m,m.props.onSelectionChange,[...p],{keys:[...p]})},async activateRow(d,p){let m=i(d,"activateRow");if(Ae(m,"Table","activateRow"),Np(m,p,"activateRow"),m.props.onRowAction===void 0)throw new Error("activateRow: the Table has no onRowAction");t(m.props.onRowAction,[{key:p}]),await r()},async loadMore(d){let p=i(d,"loadMore");if(Ae(p,"Table","loadMore"),p.props.hasMore!==!0||p.props.onLoadMore===void 0)throw new Error("loadMore: the Table has no more rows (hasMore and onLoadMore)");t(p.props.onLoadMore,[]),await r()},expand:(d,p)=>u(d,p,!0,"expand"),collapse:(d,p)=>u(d,p,!1,"collapse"),async selectItem(d,p){let m=i(d,"selectItem");if(Ae(m,"Tree","selectItem"),Ia(m,p,"selectItem").disabled===!0)throw new Error(`selectItem: the Tree item "${p}" is disabled`);await c(m,m.props.onSelect,p,{key:p})},async activateItem(d,p){let m=i(d,"activateItem");if(Ae(m,"Tree","activateItem"),Ia(m,p,"activateItem").disabled===!0||!m.props.onAction)throw new Error(`activateItem: the Tree item "${p}" cannot be activated`);t(m.props.onAction,[{key:p}]),await r()},expandedOf:d=>{let p=i(d,"expandedOf");return Ae(p,"Tree","expandedOf"),Op(p).map(m=>m.key).filter(m=>s(p).has(m))},columnsOf:(d,p={})=>{let m=i(d,"columnsOf");Ae(m,"Table","columnsOf");let g=(Array.isArray(m.props.columns)?m.props.columns:[]).flatMap(y=>{if(typeof y!="object"||y===null||!("key"in y))return[];let x="hideBelow"in y?y.hideBelow:void 0;return typeof y.key=="string"?[{key:y.key,hideBelow:x==="sm"||x==="md"?x:void 0}]:[]});return hy(g,p.width??Number.POSITIVE_INFINITY).map(y=>y.key)},rowsOf:d=>{let p=i(d,"rowsOf");return Ae(p,"Table","rowsOf"),Ap(p).map(m=>m.key)}}}var Me=e=>{let n=(e??"").replace(/\s+/g," ").trim();return JSON.stringify(n.length>60?`${n.slice(0,59)}…`:n)},jS=e=>e==="danger"||e==="warning",ky=e=>Array.isArray(e)?e.length:0;function LS(e,n){let{props:i}=e;switch(e.type){case"Heading":return`Heading ${Me(e.text)}`;case"Callout":return`Callout ${String(i.tone??"neutral")} ${Me(e.label??e.text)}`;case"EmptyState":return`EmptyState ${Me(e.label)}`;case"Table":return`Table ${Me(e.label)}: ${ky(i.rows)} row(s)${i.hasMore===!0?", more":""}`;case"Tree":return`Tree ${Me(e.label)}: ${ky(i.items)} top-level item(s)`;case"Tabs":return`Tabs ${Me(e.label)}: ${Me(n.get(e.path))} selected`;case"Skeleton":case"Spinner":return`${e.type} ${Me(e.label??"Loading")}`;case"Text":return i.tone==="danger"||i.tone==="warning"?`Text ${String(i.tone)} ${Me(e.text)}`:null;case"Box":{let o=[i.background,i.border].find(jS);return o?`Box ${o} ${Me(e.text)}`:null}default:return null}}function xy(e,n){let i=[];for(let o of e){if(o.type==="List"){let r=e.filter(a=>a.type==="ListItem"&&a.path.startsWith(`${o.path}/`)).length;i.push(`List ${Me(o.label)}: ${r} item(s)`);continue}let t=LS(o,n);t&&i.push(t)}return i.length>12?[...i.slice(0,12),`… ${i.length-12} more`]:i}function wy(e){return"path"in e}var RS=100,MS=()=>new Promise(e=>typeof setImmediate=="function"?setImmediate(e):setTimeout(e,0));async function Iy(e,n){let i=n.host,o=new Map,t=(w,O)=>{for(let E of o.get(w)??[])E(O)},r=[],a=[],s=[],c={plugin:new Map,workspace:new Map};gy(c,n.persisted);let u=n.visible??!0,d={document:"",revision:0,tree:null},p=dy(),m=py(i.bridge.call),f=i.onDataChange(m.changed);function g(w,O){if(w===Be.ready)t(Be.hydrate,{panel:{id:n.panelId??"panel"},visible:u,plugin:Object.fromEntries(c.plugin),workspace:Object.fromEntries(c.workspace)});else if(w===Be.render){let E=ny(O);if(!E.ok){a.push({kind:"refused",message:E.error}),E.document!==null&&E.revision!==null&&t(Be.ack,{document:E.document,revision:E.revision,accepted:!1});return}s.push(...E.warnings.filter(B=>!s.includes(B))),d={document:E.document,revision:E.revision,tree:E.tree},p.observe(xa(E.tree)),t(Be.ack,{document:E.document,revision:E.revision,accepted:!0})}else if(w===Be.error){let E=ty.safeParse(O);a.push(E.success?{kind:"plugin",message:E.data.message,...E.data.stack?{stack:E.data.stack}:{}}:{kind:"plugin",message:String(O)})}else if(w===Be.persist){let E=iy.safeParse(O);if(!E.success){a.push({kind:"refused",message:"persisted state Orca would not store"});return}for(let B of E.data.entries)c[B.scope].set(B.key,{version:B.version,value:B.value})}}let y=US({post:g,awaitHydration:!0,colorScheme:n.theme??"dark",schedule:w=>r.push(w),bridge:{api:i.bridge.api,locale:n.locale??"en",host:{call:i.bridge.call,has:i.bridge.has},...m.facades,on(w,O){let E=o.get(w)??new Set;return E.add(O),o.set(w,E),{dispose:()=>E.delete(O)}}}});async function x(){for(let w=0;wvy(xa(d.tree),w=>p.valueOf(w)),b=w=>uy(I().filter(O=>cy(O,w)));function T(w,O){let E=wy(w)?I().find(B=>B.path===w.path):b(w)[0];if(!E)throw new Error(wy(w)?`${O}: the element is no longer in the panel`:`${O}: nothing in the panel matches { ${ly(w)} }`);if(E.disabled)throw new Error(`${O}: the ${E.type} is disabled`);return E}function A(w,O,E){let B=typeof w=="object"&&w!==null&&"$callback"in w?w.$callback:null;typeof B=="string"&&t(Be.event,{document:d.document,revision:d.revision,callback:B,args:O,...E===void 0?{}:{edit:E}})}await Promise.resolve(typeof e=="function"?{default:e}:e).then(w=>y.boot(w)).catch(w=>a.push({kind:"plugin",message:w instanceof Error?w.message:String(w),...w instanceof Error&&w.stack?{stack:w.stack}:{}})),await x();let D=$y({host:i,controls:p,resolve:T,elements:I,dispatch:A,settled:x});return{host:i,errors:a,warnings:s,get refusals(){return i.refusals},find:w=>b(w)[0]??null,findAll:b,tree:()=>d.tree?Sp(d.tree):null,drawnTree:()=>d.tree,summary:()=>{let w=xa(d.tree),O=I(),E=new Map(O.filter(B=>B.type==="Tabs").map(B=>[B.path,Ep(B,w,p.valueOf(B))?.label??""]));return xy(O,E)},get persisted(){let w=O=>Object.fromEntries([...c[O]].map(([E,B])=>[E,B.value]));return{plugin:w("plugin"),workspace:w("workspace"),storage:i.data().storage}},settled:x,...D,valueOf:w=>p.valueOf(T(w,"valueOf")),async emit(w,O){t(w,O),await x()},async post(w,O){m.post(w,O),await x()},async setVisible(w){u=w,t(Be.visibility,{visible:w}),await x()},async rerender(){y.rerender(),await x()},async unmount(){y.dispose(),o.clear(),f()}}}var Sy={workspaceId:l.string().min(1).max(2048),kind:l.enum(["worktree","folder"])},ZS=l.object({...Sy,path:l.string().min(1).max(32*1024),branch:l.string().max(1024)}),VS=l.object({...Sy,path:l.string().min(1).max(32*1024)}),GS=l.object({workspaceId:l.string().min(1).max(2048).nullable(),paneKey:l.string().min(1).max(2048),terminalId:l.string().min(1).max(nt).optional(),title:l.string().max(Kr).optional(),state:l.enum(Dn),receivedAt:l.number().finite().positive(),mainAgent:l.object({state:l.enum(Dn),outcome:l.enum(gv).optional(),stateStartedAt:l.number().finite()}).optional()}),FS=l.object({context:gp}),zp={"workspace.created":ZS,"workspace.removed":VS,"agent.status.changed":GS,"workspace.changed":FS};var qr=["terminal.send","workspace.create.before","workspace.create.after","agent.launch.before","agent.prompt.before","agent.tool.before","agent.tool.after","editor.save.before"],uC=qr.length,BS={"terminal.send":je,"workspace.create.before":je,"workspace.create.after":je,"agent.launch.before":je,"agent.prompt.before":je,"agent.tool.before":je,"agent.tool.after":je,"editor.save.before":je};function Sa(e){return qr.some(n=>n===e)}var lC=l.object({event:l.enum(qr).meta({...ae("Hook the worker handles with `orca.hooks.on`.",je),[Nn]:BS}),onError:l.enum(["pass","deny"]).optional().meta(ae("What Orca does when this plugin's hook throws, times out, crashes or does not start: `pass` (the default) carries on as if it passed; `deny` refuses the action, naming the plugin, so a guard fails closed. Ignored for hooks that only observe.",je))}).strict().meta({id:"HookContribution",description:"A hook Orca runs through the worker before (or after) it acts. Requires `main` and the `trusted-worker` execution profile."});var mC=1024*1024,fC=new TextEncoder,gC=new Uint8Array(0);var kC=16*1024*1024,xC=16*1024*1024;var KS=256*1024;var EC=16*1024,Yr=16*1024*1024;var bt=2e3,Zn=l.strictObject({deny:l.string().min(1).max(500)}),WS=l.object({terminalId:l.string().min(1).max(nt),text:l.string().max(Yr),enter:l.boolean(),inputKind:l.enum(["driving","orchestration","launch"])}),JS=l.object({kind:l.enum(["worktree","folder"]),projectId:l.string().min(1).max(2048),name:l.string().min(1).max(1024),branch:l.string().min(1).max(1024).optional(),base:l.string().min(1).max(1024).optional()}),XS=l.object({workspaceId:l.string().min(1).max(2048),kind:l.enum(["worktree","folder"]),path:l.string().min(1).max(32*1024),branch:l.string().max(1024)}),qS=l.record(l.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(256).refine(e=>!e.toUpperCase().startsWith("ORCA_"),"ORCA_* variables are reserved"),l.string().max(32*1024)).refine(e=>Object.keys(e).length<=64,"at most 64 variables"),YS=l.enum(["worktree","folder"]),QS=l.object({agent:l.string().min(1).max(64),workspaceId:l.string().min(1).max(2048).nullable(),workspaceKind:YS.nullable(),terminalId:l.string().min(1).max(nt).optional(),command:l.string().min(1).max(Yr),env:qS}),eP=l.object({agent:l.string().min(1).max(64).nullable(),terminalId:l.string().min(1).max(nt),prompt:l.string().max(Yr),inputKind:l.enum(["driving","orchestration","launch"])}),Py={agent:l.string().min(1).max(64),workspaceId:l.string().min(1).max(2048).nullable(),terminalId:l.string().min(1).max(nt).optional(),tool:l.string().min(1).max(256),input:l.unknown(),callId:l.string().min(1).max(256).optional()},tP=l.object(Py),nP=l.object({...Py,output:l.unknown().optional()}),rP=l.object({workspaceId:l.string().min(1).max(2048).nullable(),path:l.string().min(1).max(32*1024),languageId:l.string().min(1).max(128).optional(),text:l.string().max(Yr)}),Qr={"terminal.send":{payload:WS,result:Zn,mutable:["text","enter"],deadlineMs:bt,summary:"Text Orca, the CLI or a plugin is about to send to a terminal. A person typing (on the desktop, a phone or a paired client) is never hooked, and prompts Orca submits to an agent take `agent.prompt.before` instead. Writes to one terminal stay in order while a hook decides. Rewrite `text` or `enter` with `next`, or return `{ deny }` to refuse the send."},"workspace.create.before":{payload:JS,result:Zn,mutable:["name","branch","base"],deadlineMs:bt,summary:"A worktree or folder workspace is about to be created. Change `name`, `branch` or `base` with `next`, or return `{ deny }` to refuse it."},"workspace.create.after":{payload:XS,result:null,mutable:[],deadlineMs:bt,summary:"A worktree or folder workspace was created. Observe only: Orca does not wait for it, and results are ignored."},"agent.launch.before":{payload:QS,result:Zn,mutable:["command","env"],deadlineMs:bt,summary:"Orca is about to start an agent CLI in a terminal, from any launcher (new tab, workspace create, CLI, automation, orchestration). Change `command` or add `env` with `next`, or return `{ deny }` to refuse the launch. Restores of saved sessions are not hooked."},"agent.prompt.before":{payload:eP,result:Zn,mutable:["prompt"],deadlineMs:bt,summary:"Orca is about to submit a prompt to an agent in a terminal: a launch prompt, an orchestration dispatch, an issue or task prompt, or `orca terminal send` to an agent. A prompt a person submits is never hooked, and writes to one terminal stay in order while it decides. Rewrite `prompt` with `next`, or return `{ deny }` to refuse it."},"agent.tool.before":{payload:tP,result:Zn,mutable:["input"],deadlineMs:bt,summary:"An agent in an Orca terminal is about to run a tool. Claude Code waits for the answer: return `{ deny }` to refuse the call (the agent sees the reason) or change `input` with `next`; the person is still asked as usual. Other agents only report the call, so their results are ignored. Agents on SSH or WSL hosts are not hooked yet."},"agent.tool.after":{payload:nP,result:null,mutable:[],deadlineMs:bt,summary:"An agent in an Orca terminal finished a tool call (successfully or not). Observe only: Orca does not wait for it."},"editor.save.before":{payload:rP,result:Zn,mutable:["text"],deadlineMs:bt,summary:"The person is about to save a text file from an Orca editor tab (an explicit save, not autosave). Change `text` with `next` (format on save) or return `{ deny }` to refuse the save, which shows your reason. Edits made while the hook ran stay unsaved."}};var Pa=class{constructor(n){this.event=n}event},LC=l.object({event:l.enum(qr),payload:l.unknown()}),Ty=l.discriminatedUnion("decision",[l.object({decision:l.literal("continue"),payload:l.unknown()}),l.object({decision:l.literal("result"),result:l.unknown()})]);async function Ey(e,n){let i=n;for(let o of e){let r=await o(i,a=>new Pa(a??i));if(r instanceof Pa)i=r.event;else if(r!=null)return{decision:"result",result:r}}return{decision:"continue",payload:i}}function iP(e){return e instanceof Error?e.message:String(e)}function Ay(e,n,i="timed out"){let o,t=new Promise((r,a)=>{o=setTimeout(()=>a(new Error(`${i} after ${n}ms`)),n)});return Promise.race([e,t]).finally(()=>clearTimeout(o))}function oP(e,n,i){let o=Qr[e];if(!Re(n)||!Re(i))return{ok:!1,reason:"next() was given something other than the event object"};let t={...n};for(let a of o.mutable)Object.hasOwn(i,a)&&(t[a]=i[a]);let r=o.payload.safeParse(t);return r.success?{ok:!0,payload:r.data}:{ok:!1,reason:r.error.issues[0]?.message??"invalid change"}}var Dp=class extends Error{};async function Oy(e){let{event:n}=e,i=Qr[n],o=e.deadlineMs??i.deadlineMs,t=e.payload;for(let r of e.participants){let a=e.now(),s=()=>Math.round(e.now()-a),c=i.result!==null&&e.failsClosed?.(r)===!0,u=(f,g=!1)=>c&&!g?(e.log(r,`hook ${n} denied after ${s()}ms (onError: deny): ${f}`),{kind:"deny",pluginKey:r,reason:`${r} failed and denies on error: ${f}`}):(e.log(r,`hook ${n} skipped after ${s()}ms: ${f}`),null),d;try{if(e.start){let f=e.startTimeoutMs??o;await Ay(e.start(r),f,"worker did not start")}d=await Ay(e.invoke(r,t,o),o)}catch(f){let g=u(iP(f),f instanceof Dp);if(g)return g;continue}let p=Ty.safeParse(d);if(!p.success){let f=u("the worker answered with something other than a hook decision");if(f)return f;continue}if(p.data.decision==="continue"){let f=oP(n,t,p.data.payload);f.ok?t=f.payload:e.log(r,`hook ${n}: change ignored: ${f.reason}`);continue}if(i.result===null)continue;let m=i.result.safeParse(p.data.result);if(!m.success){if(c){let f=u(`malformed result: ${m.error.issues[0]?.message??"invalid"}`);if(f)return f}e.log(r,`hook ${n}: result ignored: return next(event) or { deny: reason }`);continue}return{kind:"deny",pluginKey:r,reason:m.data.deny}}return{kind:"continue",payload:t}}function aP(e){return typeof e!="object"||e===null||!("workspaceId"in e)||"worktreeId"in e?e:{...e,worktreeId:e.workspaceId}}function ei(e,n){return typeof e=="object"&&e!==null&&n in e?Object.getOwnPropertyDescriptor(e,n)?.value:void 0}function Ta(e,n){return e.add(n),{dispose:()=>{e.delete(n)}}}var sP=new Set(Wr("worker"));function Ny(){return{commands:new Map,events:new Map,hooks:new Map,panelRequests:new Map,dataListeners:{storage:new Set,settings:new Set}}}function zy(e){let{tables:n,hostCall:i}=e,o=async(t,r)=>{await i(t,r)};return Object.freeze({api:Object.freeze({major:Ue,minor:jv}),commands:Object.freeze({register(t,r){return n.commands.set(t,r),{dispose:()=>{n.commands.delete(t)}}}}),events:Object.freeze({on(t,r){let a=la.get(t)??t,s=n.events.get(a)??new Set;n.events.set(a,s);let c=a!==t||t==="agent.status.changed",u=Ta(s,p=>r(c?aP(p):p));if(t!=="workspace.changed")return u;let d=!0;return i("workspace.readContext").then(p=>d&&r({context:p})).catch(()=>{}),{dispose:()=>{d=!1,u.dispose()}}}}),hooks:Object.freeze({on(t,r){if(!Sa(t))throw new TypeError(`orca.hooks.on: unknown hook ${String(t)}`);if(typeof r!="function")throw new TypeError("orca.hooks.on needs a handler function");let a=n.hooks.get(t)??new Set;return n.hooks.set(t,a),Ta(a,r)}}),host:Object.freeze({call:(t,r,a)=>i(t,r,a),has:t=>sP.has(t)}),panels:Object.freeze({handle(t,r){if(vt.parse(t),typeof r!="function")throw new TypeError("orca.panels.handle needs a handler function");return n.panelRequests.set(t,r),{dispose:()=>{n.panelRequests.get(t)===r&&n.panelRequests.delete(t)}}},post(t,r,a){vt.parse(t),e.postToPanels({event:t,payload:r,...a?.panel?{panel:a.panel}:{}})}}),storage:Object.freeze({get:async t=>ei(await i("storage.get",{key:t}),"value")??null,set:(t,r)=>o("storage.set",{key:t,value:r}),delete:t=>o("storage.delete",{key:t}),keys:async()=>{let t=ei(await i("storage.keys"),"keys");return Array.isArray(t)?t.filter(r=>typeof r=="string"):[]},onDidChange:t=>Ta(n.dataListeners.storage,t)}),settings:Object.freeze({get:async t=>{let r=ei(await i("settings.get"),"settings"),a=typeof r=="object"&&r!==null?{...r}:{};return t===void 0?a:ei(a,t)},set:(t,r)=>o("settings.set",{key:t,value:r}),onDidChange:t=>Ta(n.dataListeners.settings,t)}),secrets:Object.freeze({get:async t=>{let r=ei(await i("secrets.get",{key:t}),"value");return typeof r=="string"?r:null},set:(t,r)=>o("secrets.set",{key:t,value:r}),delete:t=>o("secrets.delete",{key:t})}),grantedCapabilities:Object.freeze([...e.grantedCapabilities]),log:t=>e.log(String(t))})}var Dy={id:"test-panel",surface:"panel",contributionId:"panel"};function cP(e){return Object.hasOwn(zp,e)}function uP(e){return e instanceof Error?e.message:String(e)}async function Cy(e,n){let{host:i}=n,o=new Set(n.events),t=n.hooks.map(b=>typeof b=="string"?{event:b}:b),r=new Set(t.map(b=>b.event)),a=new Set(t.filter(b=>b.onError==="deny").map(b=>b.event)),s=Ny(),c=[],u=[],d=!0,p=e.default;if(typeof p!="function")throw new TypeError(typeof e.activate=="function"?"mountWorker: the module exports activate by name, but Orca calls the default export: write `export default function activate(orca)`":"mountWorker: the module has no default-exported activate(orca) function");let m=e.deactivate;if(m!==void 0&&typeof m!="function")throw new TypeError("mountWorker: the module exports a deactivate that is not a function");let f=zy({tables:s,hostCall:(b,T)=>i.worker.call(b,T),postToPanels:b=>{u.push(b)},log:b=>{c.push(b)},grantedCapabilities:i.capabilities}),g=i.onDataChange((b,T)=>{for(let A of s.dataListeners[b])A({key:T})});await p(f);let y=n.commands?[...s.commands.keys()].find(b=>!n.commands?.includes(b)):void 0;if(y!==void 0)throw new Error(`registered undeclared command ${y}: list it in contributes.commands`);async function x(b,T,A){let D=s.panelRequests.get(b);if(!D)throw He("unknown_method",`no panel handler named ${b}`);let w={...Dy,contributionId:A??Dy.contributionId};return D(T,{panel:w,signal:new AbortController().signal})}let I=i.routeRequests((b,T)=>x(b,T));return{host:i,logs:c,posts:u,get refusals(){return i.refusals},async emit(b,T){if(!cP(b))throw new Error(`emit: "${b}" is not an event Orca sends to workers`);if(!o.has(b))throw new Error(`emit: "${b}" never arrives: it is not in contributes.events`);let A=zp[b].safeParse(T);if(!A.success)throw new TypeError(`emit: that ${b} payload is not one Orca sends: ${A.error.issues[0]?.message??""}`);let D=s.events.get(la.get(b)??b)??[];await Promise.all([...D].map(async w=>w(A.data))),await i.idle()},hooks:{async fire(b,T){if(!Sa(b))throw new Error(`hooks.fire: "${b}" is not a hook Orca runs`);if(!r.has(b))throw new Error(`hooks.fire: "${b}" never runs: it is not in contributes.hooks`);let A=Qr[b].payload.safeParse(T);if(!A.success)throw new TypeError(`hooks.fire: that ${b} event is not one Orca sends: ${A.error.issues[0]?.message??""}`);let D=await Oy({event:b,payload:A.data,participants:["worker"],invoke:(w,O)=>Ey(s.hooks.get(b)??[],O),log:(w,O)=>{c.push(O)},now:()=>performance.now(),failsClosed:()=>a.has(b)});return await i.idle(),D.kind==="deny"?{kind:"deny",reason:D.reason}:{kind:"continue",event:D.payload}}},async command(b,T){let A=s.commands.get(b);if(!A)throw new Error(`command: the worker registered no "${b}"`);let D=await A(T,{signal:new AbortController().signal});return await i.idle(),D},async request(b,T,A={}){let D=await x(b,T,A.panel);return await i.idle(),D},async deactivate(){if(d){d=!1,g(),I(),s.events.clear(),s.hooks.clear(),s.commands.clear(),s.panelRequests.clear();try{await(typeof m=="function"?m():void 0)}catch(b){throw new Error(`deactivate threw: ${uP(b)}`,{cause:b})}await i.idle()}}}}import{test as nL,describe as rL,beforeEach as iL,afterEach as oL}from"node:test";import{default as sL}from"node:assert/strict";import{runInThisContext as vP}from"node:vm";function jy(e){if(typeof e!="object"||e===null||!("on"in e)||!("document"in e))return!1;let n=e.document;return typeof e.on=="function"&&typeof n=="object"&&n!==null&&"session"in n&&typeof n.session=="function"&&"applyEdit"in n&&typeof n.applyEdit=="function"}var Ke=1;var jp=["invalid_request","invalid_params","unknown_method","capability_denied","consent_required","not_permitted","rate_limited","unavailable","action_failed","cancelled","timeout","conflict","too_large"],lP=new Set(["rate_limited","unavailable","timeout","conflict"]);function dP(e){return jp.find(n=>n===e)??"action_failed"}function _t(e,n,i){let o=dP(e);return{code:o,message:n.slice(0,8192),retryable:lP.has(o),...i===void 0?{}:{retryAfterMs:i}}}var Cp=l.number().int().nonnegative(),Ly=l.string().min(1).max(128),pP=l.object({code:l.string().max(64),message:l.string().max(8192),retryable:l.boolean(),retryAfterMs:l.number().int().nonnegative().optional()}),sj=l.discriminatedUnion("kind",[l.object({orca:l.literal(Ke),kind:l.literal("req"),id:Cp,method:Ly,params:l.unknown().optional()}),l.object({orca:l.literal(Ke),kind:l.literal("res"),id:Cp,result:l.unknown().optional(),error:pP.optional()}),l.object({orca:l.literal(Ke),kind:l.literal("evt"),event:Ly,payload:l.unknown().optional()}),l.object({orca:l.literal(Ke),kind:l.literal("cancel"),id:Cp})]);var Ft={ready:"ready",hello:"hello",theme:"theme",ping:"ping",pong:"pong",editorKeydown:"editor.keydown",editorDocument:"editor.document",editorSubscribed:"editor.subscribed",editorMerges:"editor.merges",push:"plugin.push"},Vn={applyEdit:"editor.applyEdit",flush:"editor.flush"},Uy=64*1024;var mP=l.string().min(32).max(128),pj=l.object({sessionToken:mP,action:l.string().min(1),params:l.unknown().optional()}).strict();var Lp=64*1024*1024,yj=16*1024*1024;var Up=1e3,fP=l.strictObject({baseVersion:l.number().int().nonnegative(),content:l.discriminatedUnion("kind",[l.strictObject({kind:l.literal("text"),text:l.string()}),l.strictObject({kind:l.literal("bytes"),bytes:l.instanceof(ArrayBuffer)})])});function Ry(e){let n=fP.safeParse(e);return n.success?n.data:null}var Rp=_t("conflict","The file changed outside this editor; edit the latest document."),My=_t("too_large","The edit is larger than Orca accepts; the file keeps its last accepted edit.");var Zy="__orcaPluginFrameTransport",Vy="__orcaDeclarativePost";var Gy=` +Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(let a of e.seen.entries()){let s=a[1];if(n===a[0]){r(a);continue}if(e.external){let c=e.external.registry.get(a[0])?.id;if(n!==a[0]&&c){r(a);continue}}if(e.metadataRegistry.get(a[0])?.id){r(a);continue}if(s.cycle){r(a);continue}s.count>1&&e.reused==="ref"&&r(a)}e.external&&(e.sharedDefsExtractedFor=e.external)}function cg(e){let n=e.anyOf;if(!Array.isArray(n)||n.length===0||e.type!==void 0)return;let i=[];for(let o of n){if(!o||typeof o!="object")return;cg(o);let t=Object.keys(o);if(t.length!==1||t[0]!=="type")return;let r=o.type;for(let a of Array.isArray(r)?r:[r]){if(typeof a!="string")return;i.includes(a)||i.push(a)}}delete e.anyOf,e.type=i.length===1?i[0]:i}var lg=new Set(["type","properties","required","additionalProperties"]),sg=["oneOf","anyOf"];function ug(e){let n=e.additionalProperties;return n===void 0||n===!1||typeof n!="object"||n===null?null:Object.keys(n).length?n:null}function dl(e){let n=[];for(let r of e){if(typeof r!="object"||r.type!=="object")return null;for(let a in r)if(!lg.has(a))return null;n.push(r)}let i={},o=new Set;for(let r of n){for(let a in r.properties){if(Object.prototype.hasOwnProperty.call(i,a))continue;let s=[];for(let c of n){let d=c.properties?.[a]??ug(c);d!=null&&(s.some(p=>JSON.stringify(p)===JSON.stringify(d))||s.push(d))}let u=s.length===1?s[0]:dl(s)??{allOf:s};se(i,a,u)}for(let a of r.required??[])o.add(a)}let t={type:"object",properties:i};if(o.size&&(t.required=[...o]),n.every(r=>r.additionalProperties===!1))t.additionalProperties=!1;else{let r=[];for(let a of n){let s=ug(a);s&&!r.some(u=>JSON.stringify(u)===JSON.stringify(s))&&r.push(s)}r.length===1?t.additionalProperties=r[0]:r.length>1&&(t.additionalProperties={allOf:r})}return t}function Ax(e){let n=e.allOf;if(!Array.isArray(n)||n.length<2)return;for(let t of lg)if(t in e)return;let i=n.filter(t=>sg.some(r=>Array.isArray(t[r]))),o=null;if(!i.length)o=dl(n);else{let t=i[0],r=sg.find(u=>Array.isArray(t[u]));if(Object.keys(t).length!==1)return;let a=n.filter(u=>u!==t),s=t[r].map(u=>dl([...a,u]));if(s.some(u=>!u))return;o={[r]:s}}o&&(delete e.allOf,Sr(e,o))}function dt(e,n){let i=e.seen.get(n);if(!i)throw new Error("Unprocessed schema. This is a bug in Zod.");let o=s=>{let u=e.seen.get(s);if(u.ref===null)return;let c=u.def??u.schema,d={...c},p=u.ref;if(u.ref=null,p){o(p);let f=e.seen.get(p),g=f.schema;if(g.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(c.allOf=c.allOf??[],c.allOf.push(g)):Sr(c,g),Sr(c,d),s._zod.parent===p)for(let x in c)x==="$ref"||x==="allOf"||x in d||delete c[x];if(g.$ref&&f.def)for(let x in c)x==="$ref"||x==="allOf"||x in f.def&&JSON.stringify(c[x])===JSON.stringify(f.def[x])&&delete c[x]}let m=s._zod.parent;if(m&&m!==p){o(m);let f=e.seen.get(m);if(f?.schema.$ref&&(c.$ref=f.schema.$ref,f.def))for(let g in c)g==="$ref"||g==="allOf"||g in f.def&&JSON.stringify(c[g])===JSON.stringify(f.def[g])&&delete c[g]}e.override({zodSchema:s,jsonSchema:c,path:u.path??[]})};if(!e.external||e.sharedEmitDoneFor!==e.external){for(let s of[...e.seen.entries()].reverse())o(s[0]);if(e.target!=="openapi-3.0")for(let s of e.seen.entries())cg(s[1].def??s[1].schema);for(let s of e.deferred)s();if(e.intersections.length){let s=new Map;for(let u of e.seen.values())for(let c of[u.schema,u.def]){let d=c?.allOf;if(!Array.isArray(d))continue;let p=s.get(d);p?p.push(c):s.set(d,[c])}for(let u of e.intersections)for(let c of s.get(u)??[])Ax(c)}}let t={};if(e.target==="draft-2020-12"?t.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?t.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?t.$schema="http://json-schema.org/draft-04/schema#":e.target,e.external?.uri){let s=e.external.registry.get(n)?.id;if(!s)throw new Error("Schema is missing an `id` property");t.$id=e.external.uri(s)}Sr(t,i.defId?i.schema:i.def??i.schema);let r=e.metadataRegistry.get(n)?.id;r!==void 0&&t.id===r&&delete t.id;let a=e.external?.defs??{};if(!e.external||e.sharedEmitDoneFor!==e.external)for(let s of e.seen.entries()){let u=s[1];u.def&&u.defId&&(u.def.id===u.defId&&delete u.def.id,se(a,u.defId,u.def))}e.external&&(e.sharedEmitDoneFor=e.external),e.external||Object.keys(a).length>0&&(e.target==="draft-2020-12"?t.$defs=a:t.definitions=a);try{let s=JSON.parse(JSON.stringify(t));return Object.defineProperty(s,"~standard",{value:{...n["~standard"],jsonSchema:{input:$n(n,"input",e.processors),output:$n(n,"output",e.processors)}},enumerable:!1,writable:!1}),s}catch{throw new Error("Error converting schema to JSON.")}}function ge(e,n){let i=n??{seen:new Set};if(i.seen.has(e))return!1;i.seen.add(e);let o=e._zod.def;if(o.type==="transform")return!0;if(o.type==="array")return ge(o.element,i);if(o.type==="set")return ge(o.valueType,i);if(o.type==="lazy")return ge(o.getter(),i);if(o.type==="promise"||o.type==="optional"||o.type==="nonoptional"||o.type==="nullable"||o.type==="readonly"||o.type==="default"||o.type==="prefault"||o.type==="catch")return ge(o.innerType,i);if(o.type==="intersection")return ge(o.left,i)||ge(o.right,i);if(o.type==="record"||o.type==="map")return ge(o.keyType,i)||ge(o.valueType,i);if(o.type==="pipe")return e._zod.traits.has("$ZodCodec")?!0:ge(o.in,i)||ge(o.out,i);if(o.type==="object"){for(let t in o.shape)if(ge(o.shape[t],i))return!0;return!1}if(o.type==="union"){for(let t of o.options)if(ge(t,i))return!0;return!1}if(o.type==="tuple"){for(let t of o.items)if(ge(t,i))return!0;return!!(o.rest&&ge(o.rest,i))}return!1}var pl=(e,n={})=>i=>{let o=ct({...i,processors:n});return J(e,o),lt(o,e),dt(o,e)},$n=(e,n,i={})=>o=>{let{libraryOptions:t,target:r}=o??{},a=ct({...t??{},target:r,io:n,processors:i});return J(e,a),lt(a,e),dt(a,e)};var kn=(e,n,i)=>{(e[n]===void 0||i>e[n])&&(e[n]=i)},xn=(e,n,i)=>{(e[n]===void 0||i{kn(e,"minimum",n),xn(e,"maximum",n)},gg=(e,n)=>{e.multipleOf??(e.multipleOf=[]),e.multipleOf.includes(n)||e.multipleOf.push(n)},hg=(e,n)=>{e.patterns??(e.patterns=new Set),e.patterns.add(n)},vg=(e,n)=>{e.mime=e.mime?e.mime.filter(i=>n.includes(i)):[...n]},yg=(e,n)=>{e.format=n,n.includes("int")&&(e.isInt=!0)},pg=(e,n)=>kn(e,"minimum",n.minimum),mg=(e,n)=>xn(e,"maximum",n.maximum),fg=e=>(n,i)=>{yg(n,i.format);let[o,t]=e[i.format];kn(n,"minimum",o),xn(n,"maximum",t)},Ox={greater_than:(e,n)=>kn(e,n.inclusive?"minimum":"exclusiveMinimum",n.value),less_than:(e,n)=>xn(e,n.inclusive?"maximum":"exclusiveMaximum",n.value),multiple_of:(e,n)=>gg(e,n.value),number_format:fg(Wn),bigint_format:fg(Kn),min_length:pg,max_length:mg,length_equals:(e,n)=>dg(e,n.length),min_size:pg,max_size:mg,size_equals:(e,n)=>dg(e,n.size),string_format:(e,n)=>{yg(e,n.format),n.pattern&&hg(e,n.pattern),(n.format==="base64"||n.format==="base64url")&&(e.contentEncoding=n.format),(n.local||n.precision===-1)&&(e.laxFormat=!0)},mime_type:(e,n)=>vg(e,n.mime)};function oe(e){let n={},i=e._zod.def,o=e._zod.traits.has("$ZodCheck")?[e,...i.checks??[]]:i.checks??[];for(let r of o)Ox[r._zod.def.check]?.(n,r._zod.def);let t=e._zod.bag;t.minimum!==void 0&&kn(n,"minimum",t.minimum),t.exclusiveMinimum!==void 0&&kn(n,"exclusiveMinimum",t.exclusiveMinimum),t.maximum!==void 0&&xn(n,"maximum",t.maximum),t.exclusiveMaximum!==void 0&&xn(n,"exclusiveMaximum",t.exclusiveMaximum),t.multipleOf!==void 0&&gg(n,t.multipleOf),t.format!==void 0&&(n.format??(n.format=t.format),t.format.includes("int")&&(n.isInt=!0)),t.mime&&vg(n,t.mime);for(let r of t.patterns??[])hg(n,r);return n}var Nx={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},zx=new Map([[zi,ys],[dr,bs]]),bg=e=>zx.get(e)??e,gl=(e,n,i,o)=>{let t=i;t.type="string";let{minimum:r,maximum:a,format:s,patterns:u,contentEncoding:c,laxFormat:d}=oe(e);if(typeof r=="number"&&(t.minLength=r),typeof a=="number"&&(t.maxLength=a),s&&(t.format=Nx[s]??s,t.format===""&&delete t.format,(s==="time"||d)&&delete t.format),c&&(t.contentEncoding=c),u&&u.size>0){let p=[...u].map(bg);p.length===1?t.pattern=p[0].source:p.length>1&&(t.allOf=[...p.map(m=>({...n.target==="draft-07"||n.target==="draft-04"||n.target==="openapi-3.0"?{type:"string"}:{},pattern:m.source}))])}},hl=(e,n,i,o)=>{let t=i,{minimum:r,maximum:a,multipleOf:s,exclusiveMaximum:u,exclusiveMinimum:c,isInt:d}=oe(e);t.type=d?"integer":"number";let p=typeof c=="number"&&c>=(r??Number.NEGATIVE_INFINITY),m=typeof u=="number"&&u<=(a??Number.POSITIVE_INFINITY),f=n.target==="draft-04"||n.target==="openapi-3.0";if(p?f?(t.minimum=c,t.exclusiveMinimum=!0):t.exclusiveMinimum=c:typeof r=="number"&&(t.minimum=r),m?f?(t.maximum=u,t.exclusiveMaximum=!0):t.exclusiveMaximum=u:typeof a=="number"&&(t.maximum=a),s){let g=new Set;for(let I of s)Number.isFinite(I)&&I!==0?g.add(Math.abs(I)):ie(e,n,t,o,`A multipleOf divisor of ${I} cannot be represented in JSON Schema`);let[y,...x]=g;y!==void 0&&(t.multipleOf=y),x.length&&(t.allOf=[...t.allOf??[],...x.map(I=>({multipleOf:I}))])}},vl=(e,n,i,o)=>{i.type="boolean"},yl=(e,n,i,o)=>{ie(e,n,i,o,"BigInt cannot be represented in JSON Schema")},bl=(e,n,i,o)=>{ie(e,n,i,o,"Symbols cannot be represented in JSON Schema")},_l=(e,n,i,o)=>{n.target==="openapi-3.0"?(i.type="string",i.nullable=!0,i.enum=[null]):i.type="null"},$l=(e,n,i,o)=>{ie(e,n,i,o,"Undefined cannot be represented in JSON Schema")},kl=(e,n,i,o)=>{ie(e,n,i,o,"Void cannot be represented in JSON Schema")},xl=(e,n,i,o)=>{i.not={}},wl=(e,n,i,o)=>{},Il=(e,n,i,o)=>{},Sl=(e,n,i,o)=>{ie(e,n,i,o,"Date cannot be represented in JSON Schema")},Pl=(e,n,i,o)=>{let t=e._zod.def,r=Ht(t.entries);if(r.length===0){i.not={};return}r.every(a=>typeof a=="number")&&(i.type="number"),r.every(a=>typeof a=="string")&&(i.type="string"),i.enum=r},Tl=(e,n,i,o)=>{let t=e._zod.def;if(t.values.length===0){i.not={};return}let r=[];for(let a of t.values)if(a===void 0){if(ie(e,n,i,o,"Literal `undefined` cannot be represented in JSON Schema"))return}else if(typeof a=="bigint"){if(ie(e,n,i,o,"BigInt literals cannot be represented in JSON Schema"))return;r.push(Number(a))}else r.push(a);if(r.length!==0)if(r.length===1){let a=r[0];i.type=a===null?"null":typeof a,n.target==="draft-04"||n.target==="openapi-3.0"?i.enum=[a]:i.const=a}else r.every(a=>typeof a=="number")&&(i.type="number"),r.every(a=>typeof a=="string")&&(i.type="string"),r.every(a=>typeof a=="boolean")&&(i.type="boolean"),r.every(a=>a===null)&&(i.type="null"),i.enum=r},El=(e,n,i,o)=>{ie(e,n,i,o,"NaN cannot be represented in JSON Schema")},Al=(e,n,i,o)=>{let t=i,r=e._zod.pattern;if(!r)throw new Error("Pattern not found in template literal");t.type="string",t.pattern=r.source},Ol=(e,n,i,o)=>{let t=i;t.type="string",t.format="binary",t.contentEncoding="binary";let{minimum:r,maximum:a,mime:s}=oe(e);r!==void 0&&(t.minLength=r),a!==void 0&&(t.maxLength=a),s&&(s.length===0?t.not={}:s.length===1?t.contentMediaType=s[0]:t.anyOf=s.map(u=>({contentMediaType:u})))},Nl=(e,n,i,o)=>{i.type="boolean"},zl=(e,n,i,o)=>{ie(e,n,i,o,"Custom types cannot be represented in JSON Schema")},Dl=(e,n,i,o)=>{ie(e,n,i,o,"Function types cannot be represented in JSON Schema")},Cl=(e,n,i,o)=>{ie(e,n,i,o,"Transforms cannot be represented in JSON Schema")},Ll=(e,n,i,o)=>{ie(e,n,i,o,"Map cannot be represented in JSON Schema")},jl=(e,n,i,o)=>{ie(e,n,i,o,"Set cannot be represented in JSON Schema")},Ul=(e,n,i,o)=>{let t=i,r=e._zod.def,{minimum:a,maximum:s}=oe(e);typeof a=="number"&&(t.minItems=a),typeof s=="number"&&(t.maxItems=s),t.type="array",t.items=J(r.element,n,{...o,path:[...o.path,"items"]})};function Pr(e){let n=e._zod.def;return n.type==="pipe"&&n.in._zod.traits.has("$ZodTransform")?Pr(n.out):n.type==="catch"?Pr(n.innerType):e._zod.optin}var Rl=(e,n,i,o)=>{let t=i,r=e._zod.def,a=r.shape;if(Object.getOwnPropertySymbols(a).length&&ie(e,n,t,o,"Symbol keys cannot be represented in JSON Schema"))return;t.type="object",t.properties={};for(let c in a)se(t.properties,c,J(a[c],n,{...o,path:[...o.path,"properties",c]}));let u=[];for(let c of Object.keys(a)){let d=r.shape[c];(n.io==="input"?Pr(d)===void 0:d._zod.optout===void 0)&&u.push(c)}u.length>0&&(t.required=u),r.catchall?._zod.def.type==="never"?t.additionalProperties=!1:r.catchall?r.catchall&&(t.additionalProperties=J(r.catchall,n,{...o,path:[...o.path,"additionalProperties"]})):n.io==="output"&&(t.additionalProperties=!1)},Oo=(e,n,i,o)=>{let t=e._zod.def,r=t.inclusive===!1,a=t.options.map((s,u)=>J(s,n,{...o,path:[...o.path,r?"oneOf":"anyOf",u]}));r?i.oneOf=a:i.anyOf=a},Ml=(e,n,i,o)=>{let t=e._zod.def,r=J(t.left,n,{...o,path:[...o.path,"allOf",0]}),a=J(t.right,n,{...o,path:[...o.path,"allOf",1]}),s=c=>"allOf"in c&&Object.keys(c).length===1,u=[...s(r)?r.allOf:[r],...s(a)?a.allOf:[a]];i.allOf=u,n.intersections.push(u)},Zl=(e,n,i,o)=>{let t=i,r=e._zod.def;t.type="array";let a=n.target==="draft-2020-12"?"prefixItems":"items",s=n.target==="draft-2020-12"||n.target==="openapi-3.0"?"items":"additionalItems",u=r.items.map((y,x)=>J(y,n,{...o,path:[...o.path,a,x]})),c=r.rest?J(r.rest,n,{...o,path:[...o.path,s,...n.target==="openapi-3.0"?[r.items.length]:[]]}):null,d=r.items.length;for(;d>0;){let y=r.items[d-1];if(!(n.io==="input"?Pr(y)!==void 0:y._zod.optout==="optional"))break;d--}let p=r.items.length,m=!r.rest;n.target==="draft-2020-12"?(t.prefixItems=u,m?t.items=!1:c&&(t.items=c),d>0&&(t.minItems=d),m&&(t.maxItems=p)):n.target==="openapi-3.0"?(t.items={anyOf:u},c&&t.items.anyOf.push(c),d>0&&(t.minItems=d),m&&(t.maxItems=p)):(t.items=u,m?t.additionalItems=!1:c&&(t.additionalItems=c),d>0&&(t.minItems=d),m&&(t.maxItems=p));let{minimum:f,maximum:g}=oe(e);typeof f=="number"&&(t.minItems=f),typeof g=="number"&&(t.maxItems=g)};function ml(e,n,i){if(n.$ref){if(i.has(n))return n;i.add(n);let g=e.get(n)?.def;if(!g)return n;let y=ml(e,g,i);return y===g?n:y}for(let g of["anyOf","oneOf"]){let y=n[g];if(!Array.isArray(y))continue;let x=y.map(I=>ml(e,I,i));x.some((I,b)=>I!==y[b])&&(n={...n,[g]:x})}let o=Array.isArray(n.type)?n.type:[n.type],t=!o.includes("string")&&o.some(g=>g==="number"||g==="integer"),r=n.enum??(n.const!==void 0?[n.const]:void 0);if(!t&&!r?.some(g=>typeof g=="number"))return n;let{minimum:a,maximum:s,exclusiveMinimum:u,exclusiveMaximum:c,multipleOf:d,format:p,id:m,...f}=n;return f.enum?f.enum=f.enum.map(g=>typeof g=="number"?String(g):g):typeof f.const=="number"&&(f.const=String(f.const)),t&&(f.type="string",r||(f.pattern=(o.includes("number")?qe:ir).source)),f}var fl=new WeakMap;function Dx(e){let n=new Map;for(let o of e.seen.values())o.def&&!n.has(o.schema)&&n.set(o.schema,o);let i=new Map;for(let o of fl.get(e)??[]){let t=e.seen.get(o),r=(t?.def??t?.schema)?.propertyNames;if(!r||r===!0||i.has(r))continue;let a=ml(n,r,new Set);a!==r&&i.set(r,a)}if(i.size)for(let o of e.seen.values())for(let t of[o.schema,o.def]){let r=t&&i.get(t.propertyNames);r&&(t.propertyNames=r)}}var Fl=(e,n,i,o)=>{let t=i,r=e._zod.def;t.type="object";let a=r.keyType,s=oe(a).patterns;if(r.mode==="loose"&&s&&s.size>0){let d=J(r.valueType,n,{...o,path:[...o.path,"patternProperties","*"]});t.patternProperties={};for(let p of s)se(t.patternProperties,bg(p).source,d)}else{if(n.target==="draft-07"||n.target==="draft-2020-12"){t.propertyNames=J(r.keyType,n,{...o,path:[...o.path,"propertyNames"]});let d=fl.get(n);d||(d=[],fl.set(n,d),n.deferred.push(()=>Dx(n))),d.push(e)}t.additionalProperties=J(r.valueType,n,{...o,path:[...o.path,"additionalProperties"]})}let u=a._zod.values,c=n.io==="input"&&Pr(r.valueType)!==void 0;if(u&&!r.partial&&!c){let d=[...u].filter(p=>typeof p=="string"||typeof p=="number");d.length>0&&(t.required=d.map(String))}},Vl=(e,n,i,o)=>{let t=e._zod.def,r=J(t.innerType,n,o),a=n.seen.get(e);n.target==="openapi-3.0"?(a.ref=t.innerType,i.nullable=!0):i.anyOf=[r,{type:"null"}]},Gl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType},Bl=Symbol();function _g(e,n,i,o,t){let r=!1,a=JSON.stringify(e,(s,u)=>typeof u!="bigint"?u:(r=!0,null));return r?(ie(n,i,o,t,"BigInt defaults cannot be represented in JSON Schema"),Bl):JSON.parse(a)}var Hl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType;let a=_g(t.defaultValue,e,n,i,o);a!==Bl&&(i.default=a)},Wl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);if(r.ref=t.innerType,n.io!=="input")return;let a=_g(t.defaultValue,e,n,i,o);a!==Bl&&(i._prefault=a)},Kl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType;let a;try{a=t.catchValue(void 0)}catch{ie(e,n,i,o,"Dynamic catch values are not supported in JSON Schema");return}i.default=a},Jl=(e,n,i,o)=>{let t=e._zod.def,r=t.in._zod.traits.has("$ZodTransform"),a=n.io==="input"?r?t.out:t.in:t.out;J(a,n,o);let s=n.seen.get(e);s.ref=a},Xl=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType,i.readOnly=!0},ql=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType},No=(e,n,i,o)=>{let t=e._zod.def;J(t.innerType,n,o);let r=n.seen.get(e);r.ref=t.innerType},Yl=(e,n,i,o)=>{let t=e._zod.innerType;J(t,n,o);let r=n.seen.get(e);r.ref=t},Ao={string:gl,number:hl,boolean:vl,bigint:yl,symbol:bl,null:_l,undefined:$l,void:kl,never:xl,any:wl,unknown:Il,date:Sl,enum:Pl,literal:Tl,nan:El,template_literal:Al,file:Ol,success:Nl,custom:zl,function:Dl,transform:Cl,map:Ll,set:jl,array:Ul,object:Rl,union:Oo,intersection:Ml,tuple:Zl,record:Fl,nullable:Vl,nonoptional:Gl,default:Hl,prefault:Wl,catch:Kl,pipe:Jl,readonly:Xl,promise:ql,optional:No,lazy:Yl};function zo(e,n){if("_idmap"in e){let o=e,t=ct({...n,processors:Ao}),r={};for(let u of o._idmap.entries()){let[c,d]=u;J(d,t)}let a={},s={registry:o,uri:n?.uri,defs:r};t.external=s;for(let u of o._idmap.entries()){let[c,d]=u;lt(t,d),se(a,c,dt(t,d))}if(Object.keys(r).length>0){let u=t.target==="draft-2020-12"?"$defs":"definitions";a.__shared={[u]:r}}return{schemas:a}}let i=ct({...n,processors:Ao});return J(e,i),lt(i,e),dt(i,e)}var Do=class{get metadataRegistry(){return this.ctx.metadataRegistry}get target(){return this.ctx.target}get unrepresentable(){return this.ctx.unrepresentable}get override(){return this.ctx.override}get io(){return this.ctx.io}get counter(){return this.ctx.counter}set counter(n){this.ctx.counter=n}get seen(){return this.ctx.seen}constructor(n){let i=n?.target??"draft-2020-12";i==="draft-4"&&(i="draft-04"),i==="draft-7"&&(i="draft-07"),this.ctx=ct({processors:Ao,target:i,...n?.metadata&&{metadata:n.metadata},...n?.unrepresentable&&{unrepresentable:n.unrepresentable},...n?.override&&{override:n.override},...n?.io&&{io:n.io}})}process(n,i={path:[],schemaPath:[]}){return J(n,this.ctx,i)}emit(n,i){i&&(i.cycles&&(this.ctx.cycles=i.cycles),i.reused&&(this.ctx.reused=i.reused),i.external&&(this.ctx.external=i.external)),this.ctx.sharedDefsExtractedFor=void 0,this.ctx.sharedEmitDoneFor=void 0,lt(this.ctx,n);let o=dt(this.ctx,n),{"~standard":t,...r}=o;return r}};var $g={};var On={};Ke(On,{ZodAny:()=>_d,ZodArray:()=>wd,ZodBase64:()=>Qo,ZodBase64URL:()=>ea,ZodBigInt:()=>En,ZodBigIntFormat:()=>ra,ZodBoolean:()=>Tn,ZodCIDRv4:()=>qo,ZodCIDRv6:()=>Yo,ZodCUID:()=>Go,ZodCUID2:()=>Bo,ZodCatch:()=>Bd,ZodCodec:()=>jr,ZodCreditCard:()=>pd,ZodCustom:()=>Ur,ZodCustomStringFormat:()=>jt,ZodDate:()=>Or,ZodDefault:()=>Rd,ZodDiscriminatedUnion:()=>Sd,ZodE164:()=>ta,ZodEmail:()=>Mo,ZodEmoji:()=>Fo,ZodEnum:()=>In,ZodExactOptional:()=>sa,ZodFile:()=>Cd,ZodFunction:()=>tp,ZodGUID:()=>Zo,ZodIBAN:()=>md,ZodIPv4:()=>Jo,ZodIPv6:()=>Xo,ZodISODate:()=>mt,ZodISODateTime:()=>pt,ZodISODuration:()=>gt,ZodISOTime:()=>ft,ZodInstanceOf:()=>ip,ZodIntersection:()=>Pd,ZodJWT:()=>na,ZodKSUID:()=>Ko,ZodLazy:()=>Yd,ZodLiteral:()=>Dd,ZodMAC:()=>dd,ZodMap:()=>Nd,ZodNaN:()=>Wd,ZodNanoID:()=>Vo,ZodNever:()=>kd,ZodNonOptional:()=>ua,ZodNull:()=>yd,ZodNullable:()=>Ud,ZodNumber:()=>Pn,ZodNumberFormat:()=>Ut,ZodObject:()=>zr,ZodOptional:()=>Cr,ZodPipe:()=>Lr,ZodPrefault:()=>Zd,ZodPreprocess:()=>Kd,ZodPromise:()=>ep,ZodReadonly:()=>Jd,ZodRecord:()=>wn,ZodSet:()=>zd,ZodString:()=>Sn,ZodStringFormat:()=>X,ZodSuccess:()=>Gd,ZodSymbol:()=>hd,ZodTemplateLiteral:()=>qd,ZodTransform:()=>Ld,ZodTuple:()=>Ed,ZodType:()=>M,ZodULID:()=>Ho,ZodURL:()=>Ar,ZodUUID:()=>Ge,ZodUndefined:()=>vd,ZodUnion:()=>Dr,ZodUnknown:()=>$d,ZodVoid:()=>xd,ZodXID:()=>Wo,ZodXor:()=>Id,_ZodString:()=>Ro,_default:()=>Md,_function:()=>zh,any:()=>lh,array:()=>Nr,base64:()=>Gg,base64url:()=>Bg,bigint:()=>oh,boolean:()=>gd,catch:()=>Hd,check:()=>Dh,cidrv4:()=>Fg,cidrv6:()=>Vg,codec:()=>Eh,creditCard:()=>Wg,cuid:()=>Dg,cuid2:()=>Cg,currencyCode:()=>Qg,custom:()=>Ch,date:()=>ph,describe:()=>Lh,discriminatedUnion:()=>yh,e164:()=>Hg,email:()=>wg,emoji:()=>Ng,enum:()=>oa,exactOptional:()=>jd,file:()=>Ih,float32:()=>th,float64:()=>nh,function:()=>zh,guid:()=>Ig,hash:()=>eh,hex:()=>Yg,hostname:()=>qg,httpUrl:()=>Og,iban:()=>Kg,instanceof:()=>Uh,int:()=>jo,int32:()=>rh,int64:()=>ah,intersection:()=>Td,invertCodec:()=>Ah,ipv4:()=>Rg,ipv6:()=>Zg,json:()=>Mh,jwt:()=>Jg,keyof:()=>mh,ksuid:()=>Ug,lazy:()=>Qd,literal:()=>wh,looseObject:()=>hh,looseRecord:()=>_h,mac:()=>Mg,map:()=>$h,meta:()=>jh,nan:()=>Th,nanoid:()=>zg,nativeEnum:()=>xh,never:()=>ia,nonoptional:()=>Vd,null:()=>bd,nullable:()=>Er,nullish:()=>Sh,number:()=>fd,object:()=>fh,optional:()=>Ct,partialRecord:()=>bh,pipe:()=>Uo,prefault:()=>Fd,preprocess:()=>Zh,promise:()=>Nh,readonly:()=>Xd,record:()=>Od,refine:()=>np,set:()=>kh,strictObject:()=>gh,string:()=>Tr,stringFormat:()=>Xg,stringbool:()=>Rh,success:()=>Ph,superRefine:()=>rp,symbol:()=>uh,templateLiteral:()=>Oh,transform:()=>aa,tuple:()=>Ad,uint32:()=>ih,uint64:()=>sh,ulid:()=>Lg,undefined:()=>ch,union:()=>An,unknown:()=>Dt,url:()=>Ag,uuid:()=>Sg,uuidv4:()=>Pg,uuidv6:()=>Tg,uuidv7:()=>Eg,void:()=>dh,xid:()=>jg,xor:()=>vh});var Co={};Ke(Co,{endsWith:()=>mn,gt:()=>Fe,gte:()=>Ie,includes:()=>dn,length:()=>Nt,lowercase:()=>cn,lt:()=>Ze,lte:()=>we,maxLength:()=>Ot,maxSize:()=>ut,mime:()=>gn,minLength:()=>Ye,minSize:()=>Ve,multipleOf:()=>st,negative:()=>So,nonnegative:()=>To,nonpositive:()=>Po,normalize:()=>hn,overwrite:()=>Ce,positive:()=>Io,properties:()=>fn,property:()=>Eo,regex:()=>un,size:()=>At,slugify:()=>_n,startsWith:()=>pn,toLowerCase:()=>yn,toUpperCase:()=>bn,trim:()=>vn,uppercase:()=>ln});var kg=new WeakSet([Object.prototype,Error.prototype]);function Lo(e,n,i){Object.defineProperty(e,n,{configurable:!0,enumerable:!1,get(){let o=i(this);return Object.defineProperty(this,n,{value:o,configurable:!0,writable:!0}),o},set(o){Object.defineProperty(this,n,{value:o,configurable:!0,writable:!0})}})}var xg=(e,n)=>{Ya.init(e,n),e.name="ZodError";let i=Object.getPrototypeOf(e);kg.has(i)||(kg.add(i),Lo(i,"format",o=>t=>er(o,t)),Lo(i,"flatten",o=>t=>Qn(o,t)),Lo(i,"addIssue",o=>t=>{o.issues.push(t),o.message=JSON.stringify(o.issues,Wt,2)}),Lo(i,"addIssues",o=>t=>{o.issues.push(...t),o.message=JSON.stringify(o.issues,Wt,2)}),Object.defineProperty(i,"isEmpty",{configurable:!0,enumerable:!1,get(){return this.issues.length===0}}))},Lx=h("ZodError",xg),xe=h("ZodError",xg,void 0,{Parent:Error});var Ql=Yt(xe),ed=Qt(xe),td=en(xe),nd=tn(xe),rd=li(xe),id=di(xe),od=pi(xe),ad=mi(xe),sd=fi(xe),ud=gi(xe),cd=hi(xe),ld=vi(xe);function Ux(){ae.localeError||te(Ji())}function Lt(){ae.memoizer||te({memoizer:yr()})}var M=h("ZodType",(e,n)=>(Ux(),U.init(e,n),e.def=n,e.type=n.type,e),{check(...e){let n=this.def;return this.clone(_.mergeDefs(n,{checks:[...n.checks??[],...e.map(i=>typeof i=="function"?{_zod:{check:i,def:{check:"custom"},onattach:[]}}:i)]}),{parent:!0})},with(...e){return this.check(...e)},clone(e,n){return W(this,e,n)},brand(){return this},register(e,n){return e.add(this,n),this},refine(e,n){return this.check(np(e,n))},superRefine(e,n){return this.check(rp(e,n))},overwrite(e){return this.check(Ce(e))},optional(){return Ct(this)},exactOptional(){return jd(this)},nullable(){return Er(this)},nullish(){return Ct(Er(this))},nonoptional(e){return Vd(this,e)},array(){return Nr(this)},or(e){return An([this,e])},and(e){return Td(this,e)},transform(e){return Uo(this,aa(e))},default(e){return Md(this,e)},prefault(e){return Fd(this,e)},catch(e){return Hd(this,e)},pipe(e){return Uo(this,e)},readonly(){return Xd(this)},describe(e){let n=this.clone();return ue.add(n,{description:e}),n},meta(...e){if(e.length===0)return ue.get(this);let n=this.clone();return ue.add(n,e[0]),n},isOptional(){return this.safeParse(void 0).success},isNullable(){return this.safeParse(null).success},apply(e,...n){return n.length===0?e(this):e(this,...n)},get"~standard"(){return _.hide(this,"~standard",{...Ti(this),jsonSchema:{input:$n(this,"input"),output:$n(this,"output")}})},set"~standard"(e){_.own(this,"~standard",e)},parse:function e(n,i){return Ql(this,n,i,{callee:e})},parseAsync:async function e(n,i){return await ed(this,n,i,{callee:e})},safeParse(e,n){return td(this,e,n)},async safeParseAsync(e,n){return nd(this,e,n)},get spa(){return this?.safeParseAsync},set spa(e){_.own(this,"spa",e)},validate(e,n){return tr(this,e,n)},validateAsync(e,n){return nr(this,e,n)},encode:function e(n,i){return rd(this,n,i,{callee:e})},decode:function e(n,i){return id(this,n,i,{callee:e})},encodeAsync:async function e(n,i){return await od(this,n,i,{callee:e})},decodeAsync:async function e(n,i){return await ad(this,n,i,{callee:e})},safeEncode(e,n){return sd(this,e,n)},safeDecode(e,n){return ud(this,e,n)},async safeEncodeAsync(e,n){return cd(this,e,n)},async safeDecodeAsync(e,n){return ld(this,e,n)},toJSONSchema(e){return pl(this,{})(e)},get description(){return ue.get(this)?.description},get _def(){return this._zod.def}}),Ro=h("_ZodString",(e,n)=>{St.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>gl(e,i,o,t)},_.derived({format:e=>oe(e).format??null,minLength:e=>oe(e).minimum??null,maxLength:e=>oe(e).maximum??null},{regex(...e){return this.check(un(...e))},includes(...e){return this.check(dn(...e))},startsWith(...e){return this.check(pn(...e))},endsWith(...e){return this.check(mn(...e))},min(...e){return this.check(Ye(...e))},max(...e){return this.check(Ot(...e))},length(...e){return this.check(Nt(...e))},nonempty(...e){return this.check(Ye(1,...e))},lowercase(e){return this.check(cn(e))},uppercase(e){return this.check(ln(e))},trim(){return this.check(vn())},normalize(...e){return this.check(hn(...e))},toLowerCase(){return this.check(yn())},toUpperCase(){return this.check(bn())},slugify(){return this.check(_n())}})),Sn=h("ZodString",(e,n)=>{St.init(e,n),Ro.init(e,n)},{email(e){return this.check(ro(Mo,e))},url(e){return this.check($r(Ar,e))},jwt(e){return this.check(wo(na,e))},emoji(e){return this.check(co(Fo,e))},guid(e){return this.check(io(Zo,e))},uuid(e){return this.check(oo(Ge,e))},uuidv4(e){return this.check(ao(Ge,e))},uuidv6(e){return this.check(so(Ge,e))},uuidv7(e){return this.check(uo(Ge,e))},nanoid(e){return this.check(lo(Vo,e))},cuid(e){return this.check(po(Go,e))},cuid2(e){return this.check(mo(Bo,e))},ulid(e){return this.check(fo(Ho,e))},base64(e){return this.check($o(Qo,e))},base64url(e){return this.check(ko(ea,e))},xid(e){return this.check(go(Wo,e))},ksuid(e){return this.check(ho(Ko,e))},ipv4(e){return this.check(vo(Jo,e))},ipv6(e){return this.check(yo(Xo,e))},cidrv4(e){return this.check(bo(qo,e))},cidrv6(e){return this.check(_o(Yo,e))},e164(e){return this.check(xo(ta,e))},datetime(e){return this.check(kr(pt,e))},date(e){return this.check(xr(mt,e))},time(e){return this.check(wr(ft,e))},duration(e){return this.check(Ir(gt,e))}});function Tr(e){return Tc(Sn,e)}var X=h("ZodStringFormat",(e,n)=>{K.init(e,n),Ro.init(e,n)}),pt=h("ZodISODateTime",(e,n)=>{yu.init(e,n),X.init(e,n)}),mt=h("ZodISODate",(e,n)=>{bu.init(e,n),X.init(e,n)}),ft=h("ZodISOTime",(e,n)=>{_u.init(e,n),X.init(e,n)}),gt=h("ZodISODuration",(e,n)=>{$u.init(e,n),X.init(e,n)}),Mo=h("ZodEmail",(e,n)=>{su.init(e,n),X.init(e,n)});function wg(e){return ro(Mo,e)}var Zo=h("ZodGUID",(e,n)=>{ou.init(e,n),X.init(e,n)});function Ig(e){return io(Zo,e)}var Ge=h("ZodUUID",(e,n)=>{au.init(e,n),X.init(e,n)});function Sg(e){return oo(Ge,e)}function Pg(e){return ao(Ge,e)}function Tg(e){return so(Ge,e)}function Eg(e){return uo(Ge,e)}var Ar=h("ZodURL",(e,n)=>{lu.init(e,n),X.init(e,n)});function Ag(e){return $r(Ar,e)}function Og(e){return $r(Ar,{protocol:rr,hostname:$s,..._.normalizeParams(e)})}var Fo=h("ZodEmoji",(e,n)=>{du.init(e,n),X.init(e,n)});function Ng(e){return co(Fo,e)}var Vo=h("ZodNanoID",(e,n)=>{pu.init(e,n),X.init(e,n)});function zg(e){return lo(Vo,e)}var Go=h("ZodCUID",(e,n)=>{mu.init(e,n),X.init(e,n)});function Dg(e){return po(Go,e)}var Bo=h("ZodCUID2",(e,n)=>{fu.init(e,n),X.init(e,n)});function Cg(e){return mo(Bo,e)}var Ho=h("ZodULID",(e,n)=>{gu.init(e,n),X.init(e,n)});function Lg(e){return fo(Ho,e)}var Wo=h("ZodXID",(e,n)=>{hu.init(e,n),X.init(e,n)});function jg(e){return go(Wo,e)}var Ko=h("ZodKSUID",(e,n)=>{vu.init(e,n),X.init(e,n)});function Ug(e){return ho(Ko,e)}var Jo=h("ZodIPv4",(e,n)=>{ku.init(e,n),X.init(e,n)});function Rg(e){return vo(Jo,e)}var dd=h("ZodMAC",(e,n)=>{wu.init(e,n),X.init(e,n)});function Mg(e){return Ac(dd,e)}var Xo=h("ZodIPv6",(e,n)=>{xu.init(e,n),X.init(e,n)});function Zg(e){return yo(Xo,e)}var qo=h("ZodCIDRv4",(e,n)=>{Iu.init(e,n),X.init(e,n)});function Fg(e){return bo(qo,e)}var Yo=h("ZodCIDRv6",(e,n)=>{Su.init(e,n),X.init(e,n)});function Vg(e){return _o(Yo,e)}var Qo=h("ZodBase64",(e,n)=>{Pu.init(e,n),X.init(e,n)});function Gg(e){return $o(Qo,e)}var ea=h("ZodBase64URL",(e,n)=>{Tu.init(e,n),X.init(e,n)});function Bg(e){return ko(ea,e)}var ta=h("ZodE164",(e,n)=>{Eu.init(e,n),X.init(e,n)});function Hg(e){return xo(ta,e)}var pd=h("ZodCreditCard",(e,n)=>{Au.init(e,n),X.init(e,n)});function Wg(e){return Oc(pd,e)}var md=h("ZodIBAN",(e,n)=>{Ou.init(e,n),X.init(e,n)});function Kg(e){return Nc(md,e)}var na=h("ZodJWT",(e,n)=>{Nu.init(e,n),X.init(e,n)});function Jg(e){return wo(na,e)}var jt=h("ZodCustomStringFormat",(e,n)=>{zu.init(e,n),X.init(e,n)});function Xg(e,n,i={}){return zt(jt,e,n,i)}function qg(e){return zt(jt,"hostname",_s,e)}function Yg(e){return zt(jt,"hex",Cs,e)}function Qg(e){return zt(jt,"currency_code",xs,e)}function eh(e,n){let i=n?.enc??"hex",o=`${e}_${i}`,t=Te[o];if(!t)throw new Error(`Unrecognized hash format: ${o}`);return zt(jt,o,t,n)}var Pn=h("ZodNumber",(e,n)=>{Ui.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>hl(e,i,o,t),e.isFinite=!0},_.derived({minValue:e=>{let{minimum:n,exclusiveMinimum:i}=oe(e);return Math.max(n??Number.NEGATIVE_INFINITY,i??Number.NEGATIVE_INFINITY)},maxValue:e=>{let{maximum:n,exclusiveMaximum:i}=oe(e);return Math.min(n??Number.POSITIVE_INFINITY,i??Number.POSITIVE_INFINITY)},isInt:e=>{let{isInt:n,multipleOf:i}=oe(e);return!!n||!!i?.some(Number.isSafeInteger)},format:e=>oe(e).format??null},{gt(e,n){return this.check(Fe(e,n))},gte(e,n){return this.check(Ie(e,n))},min(e,n){return this.check(Ie(e,n))},lt(e,n){return this.check(Ze(e,n))},lte(e,n){return this.check(we(e,n))},max(e,n){return this.check(we(e,n))},int(e){return this.check(jo(e))},safe(e){return this.check(jo(e))},positive(e){return this.check(Fe(0,e))},nonnegative(e){return this.check(Ie(0,e))},negative(e){return this.check(Ze(0,e))},nonpositive(e){return this.check(we(0,e))},multipleOf(e,n){return this.check(st(e,n))},step(e,n){return this.check(st(e,n))},finite(){return this}}));function fd(e){return Dc(Pn,e)}var Ut=h("ZodNumberFormat",(e,n)=>{Du.init(e,n),Pn.init(e,n)});function jo(e){return Lc(Ut,e)}function th(e){return jc(Ut,e)}function nh(e){return Uc(Ut,e)}function rh(e){return Rc(Ut,e)}function ih(e){return Mc(Ut,e)}var Tn=h("ZodBoolean",(e,n)=>{pr.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>vl(e,i,o,t)});function gd(e){return Zc(Tn,e)}var En=h("ZodBigInt",(e,n)=>{Ri.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>yl(e,i,o,t)},_.derived({minValue:e=>oe(e).minimum??null,maxValue:e=>oe(e).maximum??null,format:e=>oe(e).format??null},{gte(e,n){return this.check(Ie(e,n))},min(e,n){return this.check(Ie(e,n))},gt(e,n){return this.check(Fe(e,n))},lt(e,n){return this.check(Ze(e,n))},lte(e,n){return this.check(we(e,n))},max(e,n){return this.check(we(e,n))},positive(e){return this.check(Fe(BigInt(0),e))},negative(e){return this.check(Ze(BigInt(0),e))},nonpositive(e){return this.check(we(BigInt(0),e))},nonnegative(e){return this.check(Ie(BigInt(0),e))},multipleOf(e,n){return this.check(st(e,n))}}));function oh(e){return Vc(En,e)}var ra=h("ZodBigIntFormat",(e,n)=>{Cu.init(e,n),En.init(e,n)});function ah(e){return Bc(ra,e)}function sh(e){return Hc(ra,e)}var hd=h("ZodSymbol",(e,n)=>{Lu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>bl(e,i,o,t)});function uh(e){return Wc(hd,e)}var vd=h("ZodUndefined",(e,n)=>{ju.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>$l(e,i,o,t)});function ch(e){return Kc(vd,e)}var yd=h("ZodNull",(e,n)=>{Uu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>_l(e,i,o,t)});function bd(e){return Jc(yd,e)}var _d=h("ZodAny",(e,n)=>{Ru.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>wl(e,i,o,t)});function lh(){return Xc(_d)}var $d=h("ZodUnknown",(e,n)=>{Mu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Il(e,i,o,t)});function Dt(){return qc($d)}var kd=h("ZodNever",(e,n)=>{Zu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>xl(e,i,o,t)});function ia(e){return Yc(kd,e)}var xd=h("ZodVoid",(e,n)=>{Fu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>kl(e,i,o,t)});function dh(e){return Qc(xd,e)}var Or=h("ZodDate",(e,n)=>{Vu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Sl(e,i,o,t),e.min=(i,o)=>e.check(Ie(i,o)),e.max=(i,o)=>e.check(we(i,o))},_.derived({minDate:e=>{let{minimum:n}=oe(e);return n?new Date(n):null},maxDate:e=>{let{maximum:n}=oe(e);return n?new Date(n):null}},{}));function ph(e){return el(Or,e)}var wd=h("ZodArray",(e,n)=>{Lt(),Bu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ul(e,i,o,t),e.element=n.element},{min(e,n){return this.check(Ye(e,n))},nonempty(e){return this.check(Ye(1,e))},max(e,n){return this.check(Ot(e,n))},length(e,n){return this.check(Nt(e,n))},unwrap(){return this.element}});function Nr(e,n){return rl(wd,e,n)}function mh(e){let n=e._zod.def.shape;return oa(Object.keys(n))}var zr=h("ZodObject",(e,n)=>{Lt(),Hu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Rl(e,i,o,t),_.installLazyProp(e,"shape",i=>i._zod.def.shape,!1)},{keyof(){return oa(Object.keys(this._zod.def.shape))},catchall(e){return this.clone(_.mergeDefs(this._zod.def,{catchall:e}))},passthrough(){return this.clone(_.mergeDefs(this._zod.def,{catchall:Dt()}))},loose(){return this.clone(_.mergeDefs(this._zod.def,{catchall:Dt()}))},strict(){return this.clone(_.mergeDefs(this._zod.def,{catchall:ia()}))},strip(){return this.clone(_.mergeDefs(this._zod.def,{catchall:void 0}))},extend(e){return _.extend(this,e)},safeExtend(e){return _.safeExtend(this,e)},merge(e){return _.merge(this,e)},pick(e){return _.pick(this,e)},omit(e){return _.omit(this,e)},partial(...e){return _.partial(Cr,this,e[0])},exactPartial(...e){return _.partial(sa,this,e[0],"exactPartial")},required(...e){return _.required(ua,this,e[0])}});function fh(e,n){let i={type:"object",shape:e??{},..._.normalizeParams(n)};return new zr(i)}function gh(e,n){return new zr({type:"object",shape:e,catchall:ia(),..._.normalizeParams(n)})}function hh(e,n){return new zr({type:"object",shape:e,catchall:Dt(),..._.normalizeParams(n)})}var Dr=h("ZodUnion",(e,n)=>{mr.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Oo(e,i,o,t),e.options=n.options});function An(e,n){return new Dr({type:"union",options:e,..._.normalizeParams(n)})}var Id=h("ZodXor",(e,n)=>{Dr.init(e,n),Wu.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Oo(e,i,o,t),e.options=n.options});function vh(e,n){return new Id({type:"union",options:e,inclusive:!1,..._.normalizeParams(n)})}var Sd=h("ZodDiscriminatedUnion",(e,n)=>{Dr.init(e,n),Ju.init(e,n)});function yh(e,n,i){return new Sd({type:"union",options:n,discriminator:e,..._.normalizeParams(i)})}var Pd=h("ZodIntersection",(e,n)=>{Xu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ml(e,i,o,t)});function Td(e,n){return new Pd({type:"intersection",left:e,right:n})}var Ed=h("ZodTuple",(e,n)=>{Lt(),Mi.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Zl(e,i,o,t)},{rest(e){return this.clone({...this._zod.def,rest:e})},partial(){let e=this._zod.def;if(e.checks?.length)throw new Error(".partial() cannot be used on tuple schemas containing refinements");return this.clone({...e,items:e.items.map(n=>new Cr({type:"optional",innerType:n}))})}});function Ad(e,n,i){let o=n instanceof U,t=o?i:n,r=o?n:null;return new Ed({type:"tuple",items:e,rest:r,..._.normalizeParams(t)})}var wn=h("ZodRecord",(e,n)=>{Lt(),qu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Fl(e,i,o,t),e.keyType=n.keyType,e.valueType=n.valueType});function Od(e,n,i){return!n||!n._zod?new wn({type:"record",keyType:Tr(),valueType:e,..._.normalizeParams(n)}):new wn({type:"record",keyType:e,valueType:n,..._.normalizeParams(i)})}function bh(e,n,i){return new wn({type:"record",keyType:e,valueType:n,..._.normalizeParams(i),partial:!0})}function _h(e,n,i){return new wn({type:"record",keyType:e,valueType:n,mode:"loose",..._.normalizeParams(i)})}var Nd=h("ZodMap",(e,n)=>{Lt(),Yu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ll(e,i,o,t),e.keyType=n.keyType,e.valueType=n.valueType,e.min=(...i)=>e.check(Ve(...i)),e.nonempty=i=>e.check(Ve(1,i)),e.max=(...i)=>e.check(ut(...i)),e.size=(...i)=>e.check(At(...i))});function $h(e,n,i){return new Nd({type:"map",keyType:e,valueType:n,..._.normalizeParams(i)})}var zd=h("ZodSet",(e,n)=>{Lt(),Qu.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>jl(e,i,o,t),e.min=(...i)=>e.check(Ve(...i)),e.nonempty=i=>e.check(Ve(1,i)),e.max=(...i)=>e.check(ut(...i)),e.size=(...i)=>e.check(At(...i))});function kh(e,n){return new zd({type:"set",valueType:e,..._.normalizeParams(n)})}var In=h("ZodEnum",(e,n)=>{ec.init(e,n),M.init(e,n),e._zod.processJSONSchema=(o,t,r)=>Pl(e,o,t,r),e.enum=n.entries,e.options=[...e._zod.values];let i=new Set(Object.keys(n.entries));e.extract=(o,t)=>{let r={};for(let a of o)if(i.has(a))r[a]=n.entries[a];else throw new Error(`Key ${a} not found in enum`);return new In({...n,checks:[],..._.normalizeParams(t),entries:r})},e.exclude=(o,t)=>{let r={...n.entries};for(let a of o)if(i.has(a))delete r[a];else throw new Error(`Key ${a} not found in enum`);return new In({...n,checks:[],..._.normalizeParams(t),entries:r})}});function oa(e,n){let i=Array.isArray(e)?Object.fromEntries(e.map(o=>[o,o])):e;return new In({type:"enum",entries:i,..._.normalizeParams(n)})}function xh(e,n){return new In({type:"enum",entries:e,..._.normalizeParams(n)})}var Dd=h("ZodLiteral",(e,n)=>{tc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Tl(e,i,o,t),e.values=new Set(n.values),Object.defineProperty(e,"value",{get(){if(n.values.length>1)throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");return n.values[0]}})});function wh(e,n){return new Dd({type:"literal",values:Array.isArray(e)?e:[e],..._.normalizeParams(n)})}var Cd=h("ZodFile",(e,n)=>{nc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Ol(e,i,o,t),e.min=(i,o)=>e.check(Ve(i,o)),e.max=(i,o)=>e.check(ut(i,o)),e.mime=(i,o)=>e.check(gn(Array.isArray(i)?i:[i],o))});function Ih(e){return il(Cd,e)}var Ld=h("ZodTransform",(e,n)=>{Lt(),rc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Cl(e,i,o,t),e._zod.parse=(i,o)=>{if(o.direction==="backward")throw new ot(e.constructor.name);i.addIssue=r=>{if(typeof r=="string")i.issues.push(_.issue(r,i.value,n));else{let a=r;a.fatal&&(a.continue=!1),a.code??(a.code="custom"),"input"in a||(a.input=i.value),a.inst??(a.inst=e),i.issues.push(_.issue(a))}};let t=n.transform(i.value,i);return t instanceof Promise?t.then(r=>(i.value=r,i)):(i.value=t,i)}});function aa(e){return new Ld({type:"transform",transform:e})}var Cr=h("ZodOptional",(e,n)=>{Zi.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>No(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Ct(e){return new Cr({type:"optional",innerType:e})}var sa=h("ZodExactOptional",(e,n)=>{ic.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>No(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function jd(e){return new sa({type:"optional",innerType:e})}var Ud=h("ZodNullable",(e,n)=>{oc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Vl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Er(e){return new Ud({type:"nullable",innerType:e})}function Sh(e){return Ct(Er(e))}var Rd=h("ZodDefault",(e,n)=>{ac.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Hl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Md(e,n){return new Rd({type:"default",innerType:e,get defaultValue(){return typeof n=="function"?n():_.shallowClone(n)}})}var Zd=h("ZodPrefault",(e,n)=>{sc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Wl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Fd(e,n){return new Zd({type:"prefault",innerType:e,get defaultValue(){return typeof n=="function"?n():_.shallowClone(n)}})}var ua=h("ZodNonOptional",(e,n)=>{uc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Gl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Vd(e,n){return new ua({type:"nonoptional",innerType:e,..._.normalizeParams(n)})}var Gd=h("ZodSuccess",(e,n)=>{cc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Nl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Ph(e){return new Gd({type:"success",innerType:e})}var Bd=h("ZodCatch",(e,n)=>{lc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Kl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function Hd(e,n){return new Bd({type:"catch",innerType:e,catchValue:typeof n=="function"?n:_.constantCatch(n)})}var Wd=h("ZodNaN",(e,n)=>{dc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>El(e,i,o,t)});function Th(e){return nl(Wd,e)}var Lr=h("ZodPipe",(e,n)=>{Fi.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Jl(e,i,o,t),e.in=n.in,e.out=n.out});function Uo(e,n){return new Lr({type:"pipe",in:e,out:n})}var jr=h("ZodCodec",(e,n)=>{Lr.init(e,n),fr.init(e,n)});function Eh(e,n,i){return new jr({type:"pipe",in:e,out:n,transform:i.decode,reverseTransform:i.encode})}function Ah(e){let n=e._zod.def;return new jr({type:"pipe",in:n.out,out:n.in,transform:n.reverseTransform,reverseTransform:n.transform})}var Kd=h("ZodPreprocess",(e,n)=>{Lr.init(e,n),pc.init(e,n)}),Jd=h("ZodReadonly",(e,n)=>{mc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Xl(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Xd(e){return new Jd({type:"readonly",innerType:e})}var qd=h("ZodTemplateLiteral",(e,n)=>{fc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Al(e,i,o,t)});function Oh(e,n){return new qd({type:"template_literal",parts:e,..._.normalizeParams(n)})}var Yd=h("ZodLazy",(e,n)=>{gr.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Yl(e,i,o,t),e.unwrap=()=>e._zod.def.getter()});function Qd(e){return new Yd({type:"lazy",getter:e})}var ep=h("ZodPromise",(e,n)=>{hc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>ql(e,i,o,t),e.unwrap=()=>e._zod.def.innerType});function Nh(e){return new ep({type:"promise",innerType:e})}var tp=h("ZodFunction",(e,n)=>{gc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>Dl(e,i,o,t)});function zh(e){return new tp({type:"function",input:Array.isArray(e?.input)?Ad(e?.input):e?.input??Nr(Dt()),output:e?.output??Dt()})}var Ur=h("ZodCustom",(e,n)=>{vc.init(e,n),M.init(e,n),e._zod.processJSONSchema=(i,o,t)=>zl(e,i,o,t)});function Dh(e){let n=new q({check:"custom"});return n._zod.check=e,n}function Ch(e,n){return ol(Ur,e??(()=>!0),n)}function np(e,n={}){return al(Ur,e,n)}function rp(e,n){return sl(e,n)}var Lh=ul,jh=cl,ip=h("ZodInstanceOf",(e,n)=>{Ur.init(e,n)},{properties(e,n){return this.check(fn(e,n))}});function Uh(e,n={}){let i=new ip({type:"custom",check:"custom",fn:o=>o instanceof e,abort:!0,..._.normalizeParams(n)});return i._zod.bag.Class=e,i._zod.check=o=>{o.value instanceof e||o.issues.push({code:"invalid_type",expected:e.name,input:o.value,inst:i,path:[...i._zod.def.path??[]]})},i}var Rh=(...e)=>ll({Codec:jr,Boolean:Tn,String:Sn},...e);function Mh(e){let n=Qd(()=>An([Tr(e),fd(),gd(),bd(),Nr(n),Od(Tr(),n)]));return n}function Zh(e,n){return new Kd({type:"pipe",in:aa(e),out:n})}var Rx={invalid_type:"invalid_type",too_big:"too_big",too_small:"too_small",invalid_format:"invalid_format",not_multiple_of:"not_multiple_of",unrecognized_keys:"unrecognized_keys",invalid_union:"invalid_union",invalid_key:"invalid_key",invalid_element:"invalid_element",invalid_value:"invalid_value",custom:"custom"};function Mx(e){te({customError:e})}function Zx(){return te().customError}var op;op||(op={});var Rr={};Ke(Rr,{ZodISODate:()=>mt,ZodISODateTime:()=>pt,ZodISODuration:()=>gt,ZodISOTime:()=>ft,date:()=>Vx,datetime:()=>Fx,duration:()=>Bx,time:()=>Gx});function Fx(e){return kr(pt,e)}function Vx(e){return xr(mt,e)}function Gx(e){return wr(ft,e)}function Bx(e){return Ir(gt,e)}var P={...On,...Co,iso:Rr},Hx=new Set(["$schema","$ref","$defs","definitions","$id","id","$comment","$anchor","$vocabulary","$dynamicRef","$dynamicAnchor","type","enum","const","anyOf","oneOf","allOf","not","properties","required","additionalProperties","patternProperties","propertyNames","minProperties","maxProperties","items","prefixItems","additionalItems","minItems","maxItems","uniqueItems","contains","minContains","maxContains","minLength","maxLength","pattern","format","minimum","maximum","exclusiveMinimum","exclusiveMaximum","multipleOf","description","default","contentEncoding","contentMediaType","contentSchema","unevaluatedItems","unevaluatedProperties","if","then","else","dependentSchemas","dependentRequired","nullable","readOnly"]);function Wx(e,n){let i=e.$schema;return i==="https://json-schema.org/draft/2020-12/schema"?"draft-2020-12":i==="http://json-schema.org/draft-07/schema#"?"draft-7":i==="http://json-schema.org/draft-04/schema#"?"draft-4":n??"draft-2020-12"}function Fh(e,n){return e.map((i,o)=>oo).check(o=>{let t=o.value;if(typeof t!="object"||t===null||Array.isArray(t))return;let r=Object.getOwnPropertyNames(t);if(n.minProperties!==void 0&&r.length=${n.minProperties} properties`,input:t,inst:e,continue:!0}),n.maxProperties!==void 0&&r.length>n.maxProperties&&o.issues.push({origin:"object",code:"too_big",maximum:n.maxProperties,inclusive:!0,message:`Too big: expected object to have <=${n.maxProperties} properties`,input:t,inst:e,continue:!0}),n.keySchema)for(let a of r){let s=n.keySchema.safeParse(a);s.success||o.issues.push({code:"invalid_key",origin:"record",issues:s.error.issues,input:a,path:[a],continue:!0})}}).pipe(e)}function ap(e,n){if(e===null)return"z";let i=typeof e;if(i!=="object"){if(i==="number"&&Number.isNaN(e))return null;let o=String(e);return`${i[0]}${o.length}:${o}`}if(n.has(e))return null;n.add(e);try{if(Array.isArray(e)){let r=[];for(let a of e){let s=ap(a,n);if(s===null)return null;r.push(s)}return`a${r.length}:[${r.join(",")}]`}let o=Object.keys(e).sort(),t=[];for(let r of o){let a=ap(e[r],n);if(a===null)return null;t.push(`${r.length}:${r}=${a}`)}return`o${t.length}:{${t.join(",")}}`}finally{n.delete(e)}}var qx=new Set(["items","prefixItems","additionalItems","additionalProperties","contains","propertyNames","not","if","then","else","allOf","anyOf","oneOf","unevaluatedItems","unevaluatedProperties","contentSchema"]),Yx=new Set(["properties","patternProperties","dependentSchemas","dependencies","$defs","definitions"]);function Mr(e){return typeof e!="object"||e===null?!1:Array.isArray(e)?e.some(Mr):typeof e.$ref=="string"?!0:Object.entries(e).some(([n,i])=>qx.has(n)?Mr(i):!Yx.has(n)||typeof i!="object"||i===null?!1:Object.values(i).some(Mr))}function Vh(e){return e===1?"element":"elements"}function Qx(e,n){return P.transform(o=>o).check(o=>{let t=o.value;if(Array.isArray(t)){if(n.uniqueItems===!0){let r=new Map;for(let a=0;a=a)break;sn.maxContains&&o.issues.push({code:"custom",message:`Array must contain at most ${n.maxContains} matching ${Vh(n.maxContains)}`,input:t,continue:!0})}}}).pipe(e)}function Gh(e,n){if(e!==!1)return e===void 0||e===!0?P.any():he(e,n)}var ew=/^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;function Bh(e,n){if(e.not!==void 0){if(typeof e.not=="object"&&Object.keys(e.not).length===0)return P.never();throw new Error("not is not supported in Zod (except { not: {} } for never)")}if(e.unevaluatedItems!==void 0)throw new Error("unevaluatedItems is not supported");if(e.unevaluatedProperties!==void 0)throw new Error("unevaluatedProperties is not supported");if(e.if!==void 0||e.then!==void 0||e.else!==void 0)throw new Error("Conditional schemas (if/then/else) are not supported");if(e.dependentSchemas!==void 0||e.dependentRequired!==void 0)throw new Error("dependentSchemas and dependentRequired are not supported");if(e.$ref){let t=e.$ref;if(n.refs.has(t))return n.refs.get(t);if(n.processing.has(t))return P.lazy(()=>{if(!n.refs.has(t))throw new Error(`Circular reference not resolved: ${t}`);return n.refs.get(t)});n.processing.add(t);let r=Jx(t,n),a=he(r,n);return n.refs.set(t,a),n.processing.delete(t),a}if(e.enum!==void 0){let t=e.enum;if(n.version==="openapi-3.0"&&e.nullable===!0&&t.length===1&&t[0]===null)return P.null();if(t.length===0)return P.never();if(t.length===1)return P.literal(t[0]);if(t.every(a=>typeof a=="string"))return P.enum(t);let r=t.map(a=>P.literal(a));return r.length<2?r[0]:P.union([r[0],r[1],...r.slice(2)])}if(e.const!==void 0)return P.literal(e.const);let i=e.type;if(Array.isArray(i)){let t=i.map(r=>{let a={...e,type:r};return Bh(a,n)});return t.length===0?P.never():t.length===1?t[0]:P.union(t)}if(!i)return P.any();let o;switch(i){case"string":{let t=P.string();if(e.format){let r=e.format;r==="email"?t=t.check(P.email()):r==="uri"||r==="uri-reference"?t=t.check(P.url()):r==="uuid"||r==="guid"?t=t.check(P.uuid()):r==="date-time"?t=t.check(P.iso.datetime({offset:!0})):r==="date"?t=t.check(P.iso.date()):r==="time"?t=t.check(P.regex(ew)):r==="duration"?t=t.check(P.iso.duration()):r==="hostname"?t=t.check(P.hostname()):r==="ipv4"?t=t.check(P.ipv4()):r==="ipv6"?t=t.check(P.ipv6()):r==="mac"?t=t.check(P.mac()):r==="cidr"?t=t.check(P.cidrv4()):r==="cidr-v6"?t=t.check(P.cidrv6()):r==="base64"?t=t.check(P.base64()):r==="base64url"?t=t.check(P.base64url()):r==="e164"?t=t.check(P.e164()):r==="credit_card"?t=t.check(P.creditCard()):r==="iban"?t=t.check(P.iban()):r==="jwt"?t=t.check(P.jwt()):r==="emoji"?t=t.check(P.emoji()):r==="nanoid"?t=t.check(P.nanoid()):r==="cuid"?t=t.check(P.cuid()):r==="cuid2"?t=t.check(P.cuid2()):r==="ulid"?t=t.check(P.ulid()):r==="xid"?t=t.check(P.xid()):r==="ksuid"&&(t=t.check(P.ksuid()))}typeof e.minLength=="number"&&(t=t.min(e.minLength)),typeof e.maxLength=="number"&&(t=t.max(e.maxLength)),e.pattern&&(t=t.regex(new RegExp(e.pattern))),o=t;break}case"number":case"integer":{let t=i==="integer"?P.number().int():P.number();typeof e.minimum=="number"&&e.exclusiveMinimum!==!0&&(t=t.min(e.minimum)),typeof e.maximum=="number"&&e.exclusiveMaximum!==!0&&(t=t.max(e.maximum)),typeof e.exclusiveMinimum=="number"?t=t.gt(e.exclusiveMinimum):e.exclusiveMinimum===!0&&typeof e.minimum=="number"&&(t=t.gt(e.minimum)),typeof e.exclusiveMaximum=="number"?t=t.lt(e.exclusiveMaximum):e.exclusiveMaximum===!0&&typeof e.maximum=="number"&&(t=t.lt(e.maximum)),typeof e.multipleOf=="number"&&(t=t.multipleOf(e.multipleOf)),o=t;break}case"boolean":{o=P.boolean();break}case"null":{o=P.null();break}case"object":{let t={},r=e.properties||{},a=new Set(e.required||[]),s=typeof e.additionalProperties=="object"?he(e.additionalProperties,n):void 0;for(let[p,m]of Object.entries(r)){let f=he(m,n);se(t,p,a.has(p)?f:f.optional())}if(e.patternProperties){let p=e.patternProperties,m=Object.keys(p),f=[];for(let y of m){let x=he(p[y],n),I=P.string().regex(new RegExp(y));f.push(P.looseRecord(I,x))}let g=[];if(Object.keys(t).length>0&&g.push(P.object(t).passthrough()),g.push(...f),g.length===0)o=P.object({}).passthrough();else if(g.length===1)o=g[0];else{let y=P.intersection(g[0],g[1]);for(let x=2;xnew RegExp(b)),I=o;o=o.check(b=>{if(!Se(b.value))return;let T=[];for(let A of Object.keys(b.value))y.includes(A)||x.some(D=>D.test(A))||T.push(A);T.length&&b.issues.push({code:"unrecognized_keys",keys:T,input:b.value,inst:I})})}}else{let p=P.object(t);e.additionalProperties===!1?o=p.strict():s?o=p.catchall(s):o=p.passthrough()}let u=e.propertyNames!==void 0&&e.propertyNames!==!0,c=typeof e.minProperties=="number"?e.minProperties:void 0,d=typeof e.maxProperties=="number"?e.maxProperties:void 0;if(u||c!==void 0||d!==void 0){let p;if(u){let m=typeof e.propertyNames=="object"&&e.propertyNames.type===void 0?{type:"string",...e.propertyNames}:e.propertyNames;p=he(m,n)}o=Xx(o,{keySchema:p,minProperties:c,maxProperties:d})}break}case"array":{let t=e.prefixItems,r=e.items;if(t&&Array.isArray(t)){let a=typeof e.minItems=="number"?e.minItems:0,s=t.map(p=>he(p,n)),u=Fh(s,a),c=Array.isArray(r)?void 0:Gh(r,n),d=P.tuple(u);o=c?d.rest(c):d,typeof e.minItems=="number"&&(o=o.check(P.minLength(e.minItems))),typeof e.maxItems=="number"&&(o=o.check(P.maxLength(e.maxItems)))}else if(Array.isArray(r)){let a=typeof e.minItems=="number"?e.minItems:0,s=r.map(p=>he(p,n)),u=Fh(s,a),c=Gh(e.additionalItems,n),d=P.tuple(u);o=c?d.rest(c):d,typeof e.minItems=="number"&&(o=o.check(P.minLength(e.minItems))),typeof e.maxItems=="number"&&(o=o.check(P.maxLength(e.maxItems)))}else if(r!==void 0){let a=he(r,n),s=P.array(a);typeof e.minItems=="number"&&(s=s.min(e.minItems)),typeof e.maxItems=="number"&&(s=s.max(e.maxItems)),o=s}else o=P.array(P.any());(e.uniqueItems===!0||e.contains!==void 0)&&(o=Qx(o,{uniqueItems:e.uniqueItems===!0,containsSchema:e.contains!==void 0?he(e.contains,n):void 0,minContains:typeof e.minContains=="number"?e.minContains:void 0,maxContains:typeof e.maxContains=="number"?e.maxContains:void 0}));break}default:throw new Error(`Unsupported type: ${i}`)}return o}function he(e,n){if(typeof e=="boolean")return e?P.any():P.never();let i=Bh(e,n),o=e.type||e.enum!==void 0||e.const!==void 0;if(e.anyOf&&Array.isArray(e.anyOf)){let s=e.anyOf.map(c=>he(c,n)),u=P.union(s);i=o?P.intersection(i,u):u}if(e.oneOf&&Array.isArray(e.oneOf)){let s=e.oneOf.map(c=>he(c,n)),u=P.xor(s);i=o?P.intersection(i,u):u}if(e.allOf&&Array.isArray(e.allOf))if(e.allOf.length===0)i=o?i:P.any();else{let s=o?i:he(e.allOf[0],n),u=o?0:1;for(let c=u;c0&&n.registry.add(i,t),e.description&&(i=i.describe(e.description)),i}function Hh(e,n){if(typeof e=="boolean")return e?P.any():P.never();let i;try{i=JSON.parse(JSON.stringify(e))}catch{throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas")}let o=Wx(i,n?.defaultTarget),t=i.$defs||i.definitions||{},r={version:o,defs:t,refs:new Map,processing:new Set,rootSchema:i,registry:n?.registry??ue};return he(i,r)}var Wh=Symbol("z.visit/resolving");function Zr(e,n){let i=typeof n=="function"?n:(a,s)=>{let u=n[a._zod.def.type];return u?u(a,s):a},o=new Map;function t(a){let s=o.get(a);if(s===Wh)return new gr({type:"lazy",getter:()=>o.get(a)});if(s!==void 0)return s;o.set(a,Wh);let u=r(a),c=i(u,u!==a);return o.set(a,c),c}function r(a){let s=a._zod.def,u=s.type;switch(u){case"object":{let c=s.shape,d=Object.keys(c),p=!1,m={};for(let g of d){let y=t(c[g]);y!==c[g]&&(p=!0),m[g]=y}let f=s.catchall;return s.catchall&&(f=t(s.catchall),f!==s.catchall&&(p=!0)),p?W(a,{...s,shape:m,catchall:f}):a}case"array":{let c=t(s.element);return c===s.element?a:W(a,{...s,element:c})}case"tuple":{let c=s.items,d=!1,p=[];for(let f of c){let g=t(f);g!==f&&(d=!0),p.push(g)}let m=s.rest;return s.rest&&(m=t(s.rest),m!==s.rest&&(d=!0)),d?W(a,{...s,items:p,rest:m}):a}case"record":case"map":{let c=t(s.keyType),d=t(s.valueType);return c===s.keyType&&d===s.valueType?a:W(a,{...s,keyType:c,valueType:d})}case"set":{let c=t(s.valueType);return c===s.valueType?a:W(a,{...s,valueType:c})}case"union":{let c=s.options,d=!1,p=[];for(let m of c){let f=t(m);f!==m&&(d=!0),p.push(f)}return d?W(a,{...s,options:p}):a}case"intersection":{let c=t(s.left),d=t(s.right);return c===s.left&&d===s.right?a:W(a,{...s,left:c,right:d})}case"optional":case"nullable":case"default":case"prefault":case"catch":case"readonly":case"nonoptional":case"promise":case"success":{let c=t(s.innerType);return c===s.innerType?a:W(a,{...s,innerType:c})}case"pipe":{let c=t(s.in),d=t(s.out);return c===s.in&&d===s.out?a:W(a,{...s,in:c,out:d})}case"function":{let c=t(s.input),d=t(s.output);return c===s.input&&d===s.output?a:W(a,{...s,input:c,output:d})}case"lazy":{let c=s.getter,{_cachedInner:d,...p}=s;return W(a,{...p,getter:()=>t(c())})}case"template_literal":case"string":case"number":case"int":case"boolean":case"bigint":case"symbol":case"undefined":case"null":case"void":case"never":case"any":case"unknown":case"date":case"nan":case"enum":case"literal":case"file":case"transform":case"custom":return a;default:return a}}return t(e)}function Kh(e){return Zr(e,{object:n=>n.partial(),union:n=>{let i=n._zod.def;return i.discriminator===void 0?n:An(i.options)}})}function tw(e,n){if(!n?.length)return e;let i=e._zod.def;return W(e,ze(i,{checks:[...i.checks??[],...n]}),{parent:!0})}function Jh(e){return tw(e.out,e.checks)}function nw(e){return e.in._zod.traits.has("$ZodTransform")?Jh(e):e.in}function Xh(e){return Zr(e,{pipe:n=>nw(n._zod.def),default:(n,i)=>i?Ct(n._zod.def.innerType):n,catch:(n,i)=>i?n._zod.def.innerType:n})}function qh(e){return Zr(e,{pipe:n=>Jh(n._zod.def),prefault:(n,i)=>i?n._zod.def.innerType:n})}var sp={};Ke(sp,{bigint:()=>aw,boolean:()=>ow,date:()=>sw,number:()=>iw,string:()=>rw});function rw(e){return Ec(Sn,e)}function iw(e){return Cc(Pn,e)}function ow(e){return Fc(Tn,e)}function aw(e){return Gc(En,e)}function sw(e){return tl(Or,e)}var uw=new Set(["__proto__","prototype","constructor"]),Rt=l.string().min(1).max(256).refine(e=>!uw.has(e),"reserved storage key"),ca=l.json(),la=256*1024,cw=5*1024*1024,Yh=1024,Qh=l.strictObject({key:Rt}),ev=l.object({value:ca}),tv=l.strictObject({key:Rt,value:ca}),nv=l.object({ok:l.literal(!0)}),rv=l.strictObject({key:Rt}),iv=l.object({ok:l.literal(!0)}),ov=l.strictObject({}).optional(),av=l.object({keys:l.array(l.string()).max(Yh)}),sv=l.strictObject({key:Rt}),uv=l.object({value:l.string().nullable()}),cv=l.strictObject({key:Rt,value:l.string().max(64*1024)}),lv=l.object({ok:l.literal(!0)}),dv=l.strictObject({key:Rt}),pv=l.object({ok:l.literal(!0)}),mv=l.strictObject({}).optional(),fv=l.object({status:l.enum(["os-keyring","unavailable"])}),gv=l.strictObject({}).optional(),hv=l.object({settings:l.record(l.string(),ca)}),vv=l.strictObject({key:Rt,value:ca}),yv=l.object({ok:l.literal(!0)});var Nn="x-orca-enum-since";var et="1.4.160",up="1.4.245";var Fr="1.4.246";var bv=Fr,_v=Fr;var Le="1.4.248";var dw="Relative to the plugin root; `..` segments and Windows device names such as `CON` are refused.";function da(e,n,i){return ee(`${e} ${dw}`,n,i)}function ee(e,n,i){return i?{description:e,"x-orca-since":n,examples:[...i]}:{description:e,"x-orca-since":n}}var pw=/^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9\u00B9\u00B2\u00B3]|lpt[1-9\u00B9\u00B2\u00B3])(?:\..*)?$/i,mw=/[<>:"|?*]/,pa=String.raw`[^<>:"|?*\x00-\x1f\\/]*[^<>:"|?*\x00-\x1f\\/. ]`,cp=new RegExp(`^${pa}(?:[\\\\/]${pa})*$`),eN=new RegExp(`^${pa}(?:/${pa})*$`);function fw(e){return e.length===0||e==="."||e===".."?"empty and dot path segments are not allowed":e.endsWith(".")||e.endsWith(" ")?"path segments may not end with a dot or space":mw.test(e)||[...e].some(n=>n.charCodeAt(0)<=31)?"path segment contains a Windows-forbidden character or alternate-data-stream colon":pw.test(e)?"path segment is a Windows reserved device name":null}function gw(e){if(e.length===0||e.startsWith("/")||e.startsWith("\\"))return"must be a non-empty relative path";let n=e.split(/[\\/]/);for(let i of n){let o=fw(i);if(o)return o}return null}function lp(e){return gw(e)===null}var $v=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,hw=new Set(["__proto__","prototype","constructor"]),dp=64;function kv(e){return hw.has(e)}var pp="must be a portable relative path inside the plugin directory",zn=l.string().max(dp).regex($v,"must be kebab-case (a-z, 0-9, dashes)").refine(e=>!kv(e),"must not be a reserved name"),ma=l.string().min(1).max(1024).regex(cp,pp).refine(e=>!cp.test(e)||lp(e),pp),uN=l.string().min(1).max(1024).transform(e=>e.replace(/[\\/]+$/,"")).refine(lp,pp),xv=l.string().min(1).max(256).regex(/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/,"must be a portable command id");var vw="1.4.253",Vr=vw;var wv=8,yw=2*1024*1024,bw=8*1024*1024;var _w=/^[A-Za-z0-9](?:[A-Za-z0-9 _.-]{0,46}[A-Za-z0-9])?$/,$w=/^(?:[1-9]\d{0,2}|1000)(?: (?:[1-9]\d{0,2}|1000))?$/,kw=l.object({path:ma.regex(/\.woff2?$/i,"must be a .woff or .woff2 file").meta(da(`WOFF or WOFF2 file, at most ${yw/1024/1024} MB; its first bytes must match its extension. TTF, OTF and font collections are refused.`,Vr,["fonts/Geist-Variable.woff2"])),weight:l.string().regex($w,'must be a weight from 1 to 1000, or a range such as "100 900"').refine(e=>{let[n,i=n]=e.split(" ").map(Number);return n<=i},"must list the lower weight first").optional().meta(ee("CSS weight this file draws, or a range for a variable font. Defaults to `400`.",Vr,["400","100 900"])),style:l.enum(["normal","italic"]).optional().meta(ee("Whether this file is the italic face. Defaults to `normal`.",Vr))}).strict().meta({id:"FontFaceContribution",description:"One file of a font family."}),fN=l.object({family:l.string().regex(_w,"must be 1-48 ASCII letters, digits, spaces, dots, dashes or underscores").meta(ee("Family name shown in the font pickers. Orca adds the plugin id, so a plugin font never replaces an installed or built-in font of the same name.",Vr,["Geist"])),faces:l.array(kw).min(1).max(wv).meta(ee(`The family's files, at most ${wv}. All of a plugin's font files together may be at most ${bw/1024/1024} MB.`,Vr))}).strict().meta({id:"FontContribution",description:"A font family the user can pick for the terminal, editor or interface. It applies only once picked."});function Iv(e){return/ \([a-z0-9-]+\.[a-z0-9-]+\)$/i.test(e.trim())}var xw="Orca Nerd Font Symbols",ww=["SF Mono","Menlo","Monaco","Cascadia Mono","Consolas","DejaVu Sans Mono","Liberation Mono",xw,"Symbols Nerd Font Mono","MesloLGS Nerd Font","JetBrainsMono Nerd Font","Hack Nerd Font","monospace"];function Sv(e){let n=e.trim(),i=n?[`"${n}"`]:[],o=i.map(r=>r.toLowerCase()),t=Iv(n);for(let r of ww){let a=r.toLowerCase();(t||!o.some(s=>s.includes(a)))&&i.push(r==="monospace"?r:`"${r}"`)}return i.join(", ")}function Pv(e,n){let i=typeof e=="number"?e:Number.NaN;return Number.isFinite(i)?Math.min(900,Math.max(100,Math.round(i))):n}var Iw=["fira code","fira mono","jetbrains mono","jetbrainsmono","cascadia code","cascadia mono","iosevka","victor mono","hasklig","monoid","operator mono","dank mono","mononoki","pragmatapro","recursive","monolisa","commit mono","geist mono","maple mono","departure mono"];function Sw(e){if(!e)return!1;let n=e.split(",")[0]?.replace(/"/g,"").trim().toLowerCase()??"";return n?Iw.some(i=>n.includes(i)):!1}function Tv(e,n){return e==="on"?!0:e==="off"?!1:Sw(n)}function mp(e,n){return Math.max(8,Math.min(32,e+n))}function Ev(e,n){return mp(e-.5,n)}var Pw=10,Tw=24,Ew=1,Aw=3,Ow=8,Nw=400,zw=13;function Dw(e){return fp(e?.editorFontFamily)||Av(e?.terminalFontFamily)||"monospace"}function Av(e){return typeof e=="string"?e:""}function fp(e){return Av(e).trim()}function Cw(e){let n=fp(e?.editorFontFamily)||fp(e?.terminalFontFamily);return n.includes(",")?n:Sv(n)}function fa(e){return typeof e=="number"&&Number.isFinite(e)&&e>0}function Lw(e){let n=e?.editorFontSize;if(fa(n))return Math.min(Tw,Math.max(Pw,n));let i=e?.terminalFontSize;return fa(i)?i:zw}function jw(e,n){if(!fa(e))return;let i=e>=Ow?e/n:e;return Math.min(Aw,Math.max(Ew,i))}function Ov(e,n,i="editor"){let o=Lw(e),t=i==="diff"?Ev(o,n):mp(o,n),r=Dw(e),a=jw(e?.editorLineHeight,o);return{fontFamily:r,cssFontFamily:Cw(e),fontSize:t,fontWeight:fa(e?.editorFontWeight)?String(Pv(e.editorFontWeight,Nw)):"normal",lineHeight:a?Math.round(t*a):0,fontLigatures:Tv(Uw(e),r)}}function Uw(e){let n=e?.editorLigatures;return n==="on"||n==="auto"?n:"off"}function Nv(e){return e===!1?"off":"on"}function zv(e){return e?.editorMinimapEnabled??!1}var ce=["worker","panel","editor"],de=["worker"],Dn=["panel","editor"],G=e=>e;function Dv(e,n){return n==="worker"?e.since:Math.max(e.since,e.framesSince??0)}var Cv="1.4.256",ga=8*1024,gp=l.object({fontFamily:l.string().min(1).max(ga),cssFontFamily:l.string().min(1).max(ga),fontSize:l.number().finite().positive(),fontWeight:l.string().min(1).max(16),lineHeight:l.number().finite().nonnegative(),fontLigatures:l.boolean(),wordWrap:l.enum(["on","off"]),minimap:l.boolean()}).strict();function Lv(e,n){let i=typeof n=="number"&&Number.isFinite(n)?n:0,o=Ov(e,i,"editor");return{fontFamily:o.fontFamily.slice(0,ga),cssFontFamily:o.cssFontFamily.slice(0,ga),fontSize:o.fontSize,fontWeight:o.fontWeight,lineHeight:o.lineHeight,fontLigatures:o.fontLigatures,wordWrap:Nv(e?.editorWordWrap),minimap:zv(e)}}var jv={"editor.readTypography":G({summary:"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.",since:6,scope:"desktop",capability:null,mutation:!1,surfaces:ce,params:l.strictObject({}).optional(),result:gp})};var ht=["workspace.created","workspace.removed","agent.status.changed","workspace.changed","editor.typography.changed"],CN=ht.length,Rw={"workspace.created":up,"workspace.removed":up,"agent.status.changed":et,"workspace.changed":_v,"editor.typography.changed":Cv},ha=new Map([["worktree.created","workspace.created"],["worktree.removed","workspace.removed"]]);var LN=l.object({on:l.enum(ht).meta({...ee("Event delivered to the plugin worker.",et),[Nn]:Rw})}).strict().meta({id:"EventContribution",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."});var Mw=["success","failure","cancellation","superseded"],Uv=[...Mw,"interruption","unconfirmed"];var Cn=["working","blocked","waiting","done"];var rz=new Set(Cn);var vt=l.string().regex(/^[A-Za-z0-9_.:-]{1,128}$/,"is 1-128 letters, digits, or . _ : -");var va=12e4,sz=1024*1024,uz=256*1024;var Bw=l.object({id:l.string().min(1).max(64),surface:l.enum(["panel","editor"]),contributionId:l.string().min(1).max(256)}),cz=l.object({name:vt,input:l.unknown(),panel:Bw}),lz=l.object({event:vt,payload:l.unknown(),panel:l.string().min(1).max(64).optional()}),Rv=["storage","settings"],dz=l.object({area:l.enum(Rv),key:l.string().min(1).max(256)}),pz=l.discriminatedUnion("kind",[l.object({kind:l.literal("message"),event:vt,payload:l.unknown()}),l.object({kind:l.literal("event"),event:l.enum(ht),payload:l.unknown()}),l.object({kind:l.literal("changed"),area:l.enum(Rv),key:l.string()})]);var Hw=l.strictObject({name:vt,input:l.json().optional(),timeoutMs:l.number().int().min(1).max(va).optional()}),Ww=l.object({output:l.json()}),Kw=50,Jw=l.strictObject({}).optional(),Xw=l.object({agents:l.array(l.object({terminalId:l.string().min(1).max(1024).nullable(),title:l.string().max(512).nullable(),agent:l.string().max(128).nullable(),state:l.enum(Cn),stateStartedAt:l.number().finite()})).max(Kw)}),qw=l.strictObject({event:l.enum(ht)}),Yw=l.object({ok:l.literal(!0)}),Mv={"plugin.request":G({summary:"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.",since:2,scope:"plugin-private",capability:null,mutation:!1,surfaces:Dn,params:Hw,result:Ww}),"agents.list":G({summary:"The agents in the focused workspace: their terminal, title, agent kind and status. `agent.status.changed` reports each change after.",since:2,scope:"active-workspace",capability:"agent:read",mutation:!1,surfaces:ce,params:Jw,result:Xw}),"events.subscribe":G({summary:"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.",since:2,scope:"plugin-private",capability:null,mutation:!1,surfaces:Dn,params:qw,result:Yw})};var Qw=/[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]/u,eI=new Set(["‌","‍"]);function tI(e){return eI.has(e)||!Qw.test(e)}function Zv(e){return[...e].filter(tI).join("")}var nI=/[\t\n\v\f\r\u0085\u2028\u2029]+/g;function rI(e){return Zv(e.replace(nI," "))}function Gr(e){let n=e.overwrite(rI);return e.minLength?n.min(e.minLength,"must contain visible characters"):n}var L={maxNodes:5e3,maxDepth:64,maxTreeBytes:524288,maxTextLength:16384,maxLabelLength:1024,maxPropsPerNode:32,maxSelectOptions:500,maxEventArgs:8,maxCallbackIdLength:64,maxRadioOptions:50,maxTableColumns:32,maxTableRows:5e3,maxTreeItems:2e3,maxTreeItemDepth:16,maxKeyValueItems:200,maxDocumentLength:65536,maxEventArgItems:5e3},xz={maxBytes:640*1024,maxMessages:200,perMs:1e4,maxDepth:L.maxDepth*2+24,maxObjects:L.maxNodes*8+1024};var Be={render:"ui.render",event:"ui.event",error:"ui.error",ack:"ui.ack",ready:"ui.ready",hydrate:"ui.hydrate",visibility:"ui.visibility",persist:"ui.persist",retain:"ui.retain",snapshot:"ui.snapshot",restore:"ui.restore"};var iI={Input:{value:"value",change:"onChange"},Textarea:{value:"value",change:"onChange"},Switch:{value:"checked",change:"onChange"},Checkbox:{value:"checked",change:"onChange"},Select:{value:"value",change:"onChange"},RadioGroup:{value:"value",change:"onChange"},Slider:{value:"value",change:"onChange"},Tabs:{value:"value",change:"onChange"},Collapsible:{value:"open",change:"onChange"},Table:{value:"selected",change:"onSelectionChange"},Tree:{value:"selected",change:"onSelect"}},oI=new Map(Object.entries(iI));function hp(e){return oI.get(e)??null}var ya="$edit";var ba=/^[A-Za-z0-9_.:-]+$/,Fv=l.object({$callback:l.string().min(1).max(L.maxCallbackIdLength).regex(ba)}).strict(),Mt=["neutral","accent","success","warning","danger"],V=l.string().max(L.maxLabelLength),H=V.min(1),Y=Fv.optional(),je=l.string().regex(/^[a-z0-9-]{1,64}$/),Ee=l.enum(["none","xs","sm","md","lg"]).optional(),Br=l.enum(["start","center","end","stretch"]).optional(),tt=l.enum(Mt).optional(),_a=l.enum(["xs","sm","md"]).optional(),vp=l.enum(["solid","outline","ghost"]).optional(),N=l.boolean().optional(),Ln=l.string().max(L.maxTextLength),Hr=l.enum(["sm","md","lg","none"]).optional(),Zt=l.string().min(1).max(L.maxLabelLength),ve={[ya]:l.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional()};function R(e,n){return{props:e,children:n}}var yt=["activity","bar-chart-3","bell","blocks","book","bot","bug","calendar","cloud","code","database","file-text","flag","folder","gauge","globe","hammer","layers","lightbulb","package","plug","puzzle","rocket","star","terminal","wrench","zap","add","remove","delete","edit","refresh","search","settings","check","close","info","warning","error","open-external","copy","download","upload","play","pause","stop","more","expand","collapse","next","filter","branch","file","agent","link"],aI=Object.fromEntries(yt.slice(yt.indexOf("add")).map(e=>[e,Fr])),Cz=new Map(yt.map(e=>[e.replaceAll("-",""),e]));var sI=["html","components"],uI=/\.(?:m?js|tsx?|mts|jsx)$/i,Lz=l.object({id:zn.meta(ee("Panel id, unique among this plugin's panels. The reserved names `constructor` and `prototype` are refused.",et,["dashboard"])),title:Gr(l.string().min(1).max(256)).meta(ee("Title shown on the panel tab and its tooltip.",et,["Dashboard"])),icon:l.enum(yt).optional().meta({...ee("Icon shown in the right-sidebar activity bar. Defaults to `plug`.",et,["database"]),[Nn]:aI}),entry:ma.meta(da(`What the panel runs: an HTML page for \`ui: "html"\`, a \`.js\`/\`.mjs\` module for \`ui: "components"\`. Either may instead be a \`.ts\`/\`.tsx\`/\`.mts\`/\`.jsx\` file, which Orca transpiles when it loads (needs \`engines.orca\` \`>=${bv}\`; an \`html\` panel then gets a generated page around it). It is trusted code: install panels you trust.`,et,["dist/panel.html"])),ui:l.enum(sI).optional().meta(ee("How the panel draws its UI. `html` (default) shows `entry` as a web page in an isolated frame; `components` runs `entry` as a hidden module and Orca draws the component tree it returns with its own controls.",Fr,["components"]))}).strict().refine(e=>e.ui!=="components"||uI.test(e.entry),{path:["entry"],message:"a components panel entry must be a .js, .mjs, .ts, .tsx, .mts or .jsx module"}).meta({id:"PanelContribution",description:"A sidebar panel. Requires the `sandboxed-ui` execution profile or higher."});var cI="1.4.248";var lI=64,dI=256,jn=cI,Vv={text:Gr(l.string().max(lI)).optional().meta(ee("Short label shown in the status bar.",jn,["3 blocked"])),icon:l.enum(yt).optional().meta(ee("Icon shown before the text, from the panel icon set.",jn,["bell"])),tone:l.enum(Mt).optional().meta(ee("Colour of the item. Defaults to `neutral`.",jn,["warning"])),tooltip:Gr(l.string().max(dI)).optional().meta(ee("Shown on hover. Defaults to the plugin name.",jn))},Gz=l.object({id:zn.meta(ee("Item id, unique among this plugin's status bar items; `statusBar.update` names it.",jn,["blocked-count"])),...Vv,command:xv.optional().meta(ee("Id of a command this plugin contributes in `contributes.commands`, run when the item is pressed.",jn,["guard.showLog"]))}).strict().meta({id:"StatusBarItemContribution",description:"An item in the status bar. Requires the `declarative` profile; a `trusted-worker` can change it with `statusBar.update`."}),Gv=l.strictObject({...Vv,visible:l.boolean().optional()});var pI=l.strictObject({id:zn,...Gv.shape}),mI=l.object({ok:l.literal(!0)}),Bv={"statusBar.update":G({summary:"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.",since:2,scope:"plugin-private",capability:null,mutation:!1,surfaces:de,params:pI,result:mI})};function yp(e){return e==="linux"?"orca-ide":e==="win32"?"orca.cmd":"orca"}function fI(e){return{...e,launchCmd:e.launchCmd??e.detectCmd,expectedProcess:e.expectedProcess??e.detectCmd}}var gI={claude:{detectCmd:"claude",promptInjectionMode:"argv",pasteNeedsTypedRequest:!0,draftPromptFlag:"--prefill",preflightTrust:"claude",draftPasteReadySignal:"claude-composer-prompt",workerStartReadsComposerMarker:!0,ctrlEnterEncoding:"csi-u"},"claude-agent-teams":{detectCmd:"orca",detectCmdAliases:["orca-dev","orca-ide"],detectRequiredCommands:["claude"],detectUnsupportedRuntimes:["win32","wsl"],launchCmd:"orca claude-teams",launchCmdByPlatform:{linux:`${yp("linux")} claude-teams`,win32:`${yp("win32")} claude-teams`},expectedProcess:"claude",promptInjectionMode:"stdin-after-start",pasteNeedsTypedRequest:!0,preflightTrust:"claude",draftPasteReadySignal:"claude-composer-prompt",workerStartReadsComposerMarker:!0,ctrlEnterEncoding:"csi-u"},codebuddy:{detectCmd:"codebuddy",detectCmdAliases:["cbc"],promptInjectionMode:"argv"},openclaude:{detectCmd:"openclaude",promptInjectionMode:"argv",draftPromptFlag:"--prefill"},codex:{detectCmd:"codex",promptInjectionMode:"argv",windowsInputRecordPasteNewline:"alt-enter",preflightTrust:"codex",draftPasteReadySignal:"codex-composer-prompt",draftPasteReadyTimeoutMs:2e4,submitRetryDelayMs:1200},autohand:{detectCmd:"autohand",promptInjectionMode:"stdin-after-start"},ante:{detectCmd:"ante",promptInjectionMode:"stdin-after-start"},trae:{detectCmd:"traecli",promptInjectionMode:"argv",argvPromptSeparator:"--"},opencode:{detectCmd:"opencode",promptInjectionMode:"flag-prompt",draftPasteReadySignal:"render-cursor-after-bracketed-paste",submitPasteReadySignal:"opencode-agent-row",draftPasteReadyTimeoutMs:2e4,ctrlEnterEncoding:"csi-u",composerReadyCaptures:["opencode-1-18-32-timed-boot-slow","opencode-1-18-32-timed-boot-hidden-pane","opencode-1-18-32-timed-first-launch","opencode-cmd-2-0-21-timed-warm-server"]},opencode2:{detectCmd:"opencode2",launchCmd:"opencode2 --standalone",promptInjectionMode:"flag-prompt",draftPasteReadySignal:"render-cursor-after-bracketed-paste",submitPasteReadySignal:"opencode-agent-row",draftPasteReadyTimeoutMs:2e4,composerReadyCaptures:["opencode-2-0-18-timed-boot-hidden-pane","opencode-2-0-21-timed-cold-standalone","opencode-2-0-21-timed-cold-standalone-hidden-pane","opencode-2-0-21-timed-natural-load-enter-dropped","opencode-2-0-14-timed-cold-standalone"]},"mimo-code":{detectCmd:"mimo",promptInjectionMode:"flag-prompt",draftPasteReadySignal:"render-cursor-after-bracketed-paste"},pi:{detectCmd:"pi",promptInjectionMode:"argv",draftPromptEnvVar:"ORCA_PI_PREFILL",windowsShiftEnterEncoding:"csi-u"},omp:{detectCmd:"omp",promptInjectionMode:"argv",draftPromptEnvVar:"ORCA_OMP_PREFILL",windowsShiftEnterEncoding:"csi-u"},"prime-agent":{detectCmd:"prime-agent",promptInjectionMode:"argv",argvPromptSeparator:"--",windowsShiftEnterEncoding:"csi-u"},qoder:{detectCmd:"qodercli",detectCmdAliases:["qoder"],promptInjectionMode:"flag-prompt-interactive",preflightTrust:"qoder"},"qoder-cn":{detectCmd:"qoderclicn",detectCmdAliases:["qodercn"],promptInjectionMode:"flag-prompt-interactive",preflightTrust:"qoder-cn"},gemini:{detectCmd:"gemini",promptInjectionMode:"flag-prompt-interactive"},antigravity:{detectCmd:"agy",promptInjectionMode:"flag-prompt-interactive",preflightTrust:"antigravity",draftPasteReadySignal:"antigravity-composer-prompt",draftPasteReadyTimeoutMs:2e4,submitLineSettleMsPerLine:45},aider:{detectCmd:"aider",promptInjectionMode:"stdin-after-start"},goose:{detectCmd:"goose",promptInjectionMode:"stdin-after-start"},amp:{detectCmd:"amp",promptInjectionMode:"stdin-after-start"},kilo:{detectCmd:"kilo",promptInjectionMode:"stdin-after-start"},kiro:{detectCmd:"kiro-cli",launchCmd:"kiro-cli chat --tui",promptInjectionMode:"stdin-after-start"},crush:{detectCmd:"crush",promptInjectionMode:"stdin-after-start"},aug:{detectCmd:"auggie",promptInjectionMode:"stdin-after-start"},cline:{detectCmd:"cline",promptInjectionMode:"stdin-after-start"},freebuff:{detectCmd:"freebuff",promptInjectionMode:"stdin-after-start"},codebuff:{detectCmd:"codebuff",promptInjectionMode:"stdin-after-start"},"command-code":{detectCmd:"command-code",launchCmd:"command-code --trust",promptInjectionMode:"argv"},continue:{detectCmd:"cn",promptInjectionMode:"stdin-after-start"},cursor:{detectCmd:"cursor-agent",promptInjectionMode:"argv",preflightTrust:"cursor"},droid:{detectCmd:"droid",promptInjectionMode:"argv",windowsShiftEnterEncoding:"csi-u",ctrlEnterEncoding:"csi-u"},kimi:{preflightTrust:"kimi",detectCmd:"kimi",detectCmdAliases:["kimi-code"],promptInjectionMode:"stdin-after-start"},"mistral-vibe":{detectCmd:"vibe",detectCmdAliases:["mistral-vibe"],promptInjectionMode:"stdin-after-start"},"qwen-code":{detectCmd:"qwen",promptInjectionMode:"stdin-after-start",submitRetryDelayMs:1200},rovo:{detectCmd:"acli",launchCmd:"acli rovodev run",promptInjectionMode:"stdin-after-start"},hermes:{detectCmd:"hermes",launchCmd:"hermes --tui",promptInjectionMode:"hermes-query"},openclaw:{detectCmd:"openclaw",promptInjectionMode:"stdin-after-start"},copilot:{detectCmd:"copilot",promptInjectionMode:"flag-interactive",preflightTrust:"copilot",draftPasteReadySignal:"copilot-composer-prompt"},grok:{detectCmd:"grok",promptInjectionMode:"argv",argvPromptSeparator:"--",draftPasteReadySignal:"grok-composer-prompt",ctrlEnterEncoding:"csi-u"},muse:{detectCmd:"muse",launchCmd:"muse --trust-workspace",promptInjectionMode:"stdin-after-start"},dsh:{detectCmd:"dsh-tui",detectCmdAliases:["dst"],detectRequiredCommands:["dsh"],launchCmd:"dsh-tui .",expectedProcess:"dsh",promptInjectionMode:"stdin-after-start",draftPasteReadySignal:"dsh-composer-prompt",submitRetryDelayMs:1e3,submitRetryBudgetMs:3e4},zcode:{detectCmd:"zcode",expectedProcess:"zcode-cli",promptInjectionMode:"stdin-after-start",draftPasteReadySignal:"zcode-composer-prompt",composerReadyCaptures:["zcode-composer-ready"]},devin:{detectCmd:"devin",promptInjectionMode:"argv",argvPromptSeparator:"--"},jcode:{detectCmd:"jcode",launchCmd:"jcode",expectedProcess:"jcode",promptInjectionMode:"stdin-after-start"}},hI=Object.fromEntries(Object.entries(gI).map(([e,n])=>[e,fI(n)]));function Hv(e){return typeof e=="string"&&Object.hasOwn(hI,e)}var bp=["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"];var aD=new Set(bp);var vI=500,_p=2e3,Wr=1024,Wv=4096,Un=l.string().min(1).max(Wr),$p=l.string().min(1).max(Wr),yI=l.strictObject({}).optional(),bI=l.object({workspaces:l.array(l.object({id:Un,kind:l.enum(["worktree","folder"]),name:l.string().max(512),project:l.string().max(512).nullable().optional(),path:l.string().max(Wr),branch:l.string().max(512).nullable(),hostId:l.string().max(Wr).nullable()})).max(vI),truncated:l.boolean()}),_I=l.strictObject({workspaceId:Un}),$I=l.object({opened:l.literal(!0)}),kI=200,xI=[[0,31],[127,159],[173,173],[847,847],[1564,1564],[4447,4448],[6068,6069],[6155,6159],[8203,8207],[8232,8238],[8288,8303],[10240,10240],[12644,12644],[65024,65039],[65279,65279],[65440,65440],[65520,65531],[119155,119162],[917504,921599]],wI=e=>{let n=e.codePointAt(0)??0;return xI.some(([i,o])=>n>=i&&n<=o)},II=l.strictObject({workspaceId:Un,name:l.string().max(kI).refine(e=>e.trim().length>0,"name must not be blank").refine(e=>![...e].some(wI),"name must be one line of plain text")}),SI=l.object({name:l.string()}),PI=l.strictObject({workspaceId:Un,title:l.string().min(1).max(512).optional(),command:l.string().min(1).max(Wv).optional()}),TI=l.object({terminalId:$p}),EI=l.strictObject({terminalId:$p,maxLines:l.number().int().min(1).max(_p).optional()}),AI=l.object({lines:l.array(l.string()).max(_p),truncated:l.boolean()}),OI=l.strictObject({workspaceId:Un,agent:l.string().min(1).max(64).refine(Hv,"unknown agent"),prompt:l.string().min(1).max(Wv).optional()}),NI=l.object({terminalId:$p});function zI(e){let n=e.split(/[\\/]/);return!/^[a-z]:/i.test(e)&&n[0]!==""&&n.every(i=>i!=="..")&&n.some(i=>i!==""&&i!==".")}var DI=l.strictObject({workspaceId:Un,path:l.string().min(1).max(Wr).refine(zI,"path must be relative and inside the workspace")}),CI=l.object({opened:l.literal(!0)}),LI=l.strictObject({action:l.enum(bp)}),jI=l.object({ran:l.literal(!0)}),Kv={"workspace.list":G({summary:"Lists every workspace, Git worktrees and folders, on every host Orca reaches.",since:3,scope:"any-workspace",capability:"workspace:read",mutation:!1,surfaces:de,params:yI,result:bI}),"workspace.open":G({summary:"Shows a workspace in Orca, as clicking it in the sidebar does; a folder workspace needs the desktop app.",since:3,scope:"any-workspace",capability:"workspace:write",mutation:!0,surfaces:de,params:_I,result:$I}),"workspace.rename":G({summary:"Renames a workspace in Orca's sidebar, as renaming it by hand does; the name stays until it is renamed again.",since:5,scope:"any-workspace",capability:"workspace:write",mutation:!0,surfaces:de,params:II,result:SI}),"terminal.create":G({summary:"Opens a terminal in a workspace, optionally running a command, in the background.",since:3,scope:"any-workspace",capability:"terminal:send",mutation:!0,surfaces:de,params:PI,result:TI}),"terminal.read":G({summary:`Reads a terminal's newest output as plain text, at most ${_p} lines.`,since:3,scope:"explicit-terminal",capability:"terminal:read",mutation:!1,surfaces:de,params:EI,result:AI}),"agent.launch":G({summary:"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.",since:3,scope:"any-workspace",capability:"agent:launch",mutation:!0,surfaces:de,params:OI,result:NI}),"editor.open":G({summary:"Opens a file of a workspace in an editor tab of Orca's window; desktop app only. `workspace.open` shows the workspace.",since:3,scope:"any-workspace",capability:"workspace:write",mutation:!0,surfaces:de,params:DI,result:CI}),"commands.run":G({summary:"Runs one of Orca's built-in actions in its window, never a plugin's replacement of it; desktop app only.",since:3,scope:"desktop",capability:"workspace:write",mutation:!0,surfaces:de,params:LI,result:jI})};var kp=l.string().regex(/^[a-f0-9]{64}$/),Jv=l.string().min(1).max(16*1024),UI=l.strictObject({expectedRevision:kp,app:Jv,terminal:Jv}),Xv={"appearance.read":G({summary:"Reads the current desktop appearance revision for a conditional update.",since:4,scope:"desktop",capability:"appearance:write",mutation:!1,surfaces:de,params:l.strictObject({}).optional(),result:l.strictObject({revision:kp})}),"appearance.apply":G({summary:"Publishes owned app/editor and terminal colors and selects them in the desktop. Returns publication, not renderer acknowledgement.",since:4,scope:"desktop",capability:"appearance:write",mutation:!0,surfaces:de,params:UI,result:l.strictObject({revision:kp,themeId:l.templateLiteral(["plugin:",l.string()]),mode:l.enum(["dark","light"]),delivery:l.literal("published")})})};var qv={...Bv,...Xv,...Kv};var RI=4096,MI=50,Kr=512,nt=1024,ZI=l.strictObject({}).optional(),xp=l.object({branch:l.string().max(Kr),displayName:l.string().max(Kr),terminals:l.array(l.object({id:l.string().min(1).max(nt),title:l.string().max(Kr).optional()}).strict()).max(MI)}).strict().nullable(),FI=l.strictObject({terminalId:l.string().min(1).max(nt),text:l.string().min(1).max(RI),enter:l.boolean().default(!1)}),VI=l.object({accepted:l.boolean()}),GI=l.strictObject({title:l.string().min(1).max(120),body:l.string().max(1e3).optional()}),BI=l.object({delivered:l.boolean()}),wp={openExternal:"shell.openExternal",saveAs:"files.saveAs"},HI=8192,WI=["http:","https:","mailto:"],Yv=64*1024*1024,KI=l.strictObject({url:l.string().min(1).max(HI).refine(QI,"url must be an http:, https: or mailto: URL")}),JI=l.object({opened:l.boolean(),reason:l.enum(["cancelled","consent-unavailable"]).optional()}),XI=l.union([l.string().max(Yv),l.custom(e=>e instanceof ArrayBuffer||e instanceof Uint8Array,"data must be a string or ArrayBuffer").refine(e=>e.byteLength<=Yv,"data exceeds 64 MiB").meta({type:"ArrayBuffer"})]),qI=l.strictObject({suggestedName:l.string().min(1).max(255).optional(),mimeType:l.string().min(1).max(255).optional(),data:XI}),YI=l.object({status:l.enum(["saved","cancelled"])});function QI(e){try{return WI.includes(new URL(e).protocol)}catch{return!1}}var Ft={"workspace.readContext":G({summary:"Branch, name and terminal ids of the focused workspace, or null when none is focused.",since:0,scope:"active-workspace",capability:"workspace:read",mutation:!1,surfaces:ce,params:ZI,result:xp}),"terminal.sendText":G({summary:"Types text into one terminal named by id, optionally pressing Enter.",since:0,scope:"explicit-terminal",capability:"terminal:send",mutation:!0,surfaces:de,params:FI,result:VI}),"notifications.show":G({summary:"Shows a desktop notification.",since:0,scope:"desktop",capability:"notifications:show",mutation:!0,surfaces:ce,params:GI,result:BI}),"storage.get":G({summary:"Reads a JSON value from the plugin's private store; null when absent.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!1,framesSince:2,surfaces:ce,params:Qh,result:ev}),"storage.set":G({summary:"Writes a JSON value to the plugin's private store.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!0,framesSince:2,surfaces:ce,params:tv,result:nv}),"storage.delete":G({summary:"Removes a key from the plugin's private store.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!0,framesSince:2,surfaces:ce,params:rv,result:iv}),"storage.keys":G({summary:"Lists the keys in the plugin's private store.",since:0,scope:"plugin-private",capability:"storage:own",mutation:!1,framesSince:2,surfaces:ce,params:ov,result:av}),"secrets.get":G({summary:"Reads a secret from the plugin's encrypted secrets file; null when absent.",since:0,scope:"plugin-private",capability:"secrets:own",mutation:!1,framesSince:2,surfaces:ce,params:sv,result:uv}),"secrets.set":G({summary:"Stores a secret in the plugin's encrypted secrets file.",since:0,scope:"plugin-private",capability:"secrets:own",mutation:!0,framesSince:2,surfaces:ce,params:cv,result:lv}),"secrets.delete":G({summary:"Removes a secret from the plugin's encrypted secrets file.",since:0,scope:"plugin-private",capability:"secrets:own",mutation:!0,framesSince:2,surfaces:ce,params:dv,result:pv}),"secrets.protection":G({summary:"How secrets are protected at rest: `os-keyring`, or `unavailable` (no OS key store, or Linux `basic_text`), where `secrets.set` fails.",since:1,scope:"plugin-private",capability:"secrets:own",mutation:!1,framesSince:2,surfaces:ce,params:mv,result:fv}),"settings.get":G({summary:"Reads every setting the plugin owns.",since:0,scope:"plugin-private",capability:"settings:own",mutation:!1,framesSince:2,surfaces:ce,params:gv,result:hv}),"settings.set":G({summary:"Writes one setting the plugin owns.",since:0,scope:"plugin-private",capability:"settings:own",mutation:!0,framesSince:2,surfaces:ce,params:vv,result:yv}),"shell.openExternal":G({summary:"Opens an http:, https: or mailto: URL in the default browser. Needs a click or key press in the frame; desktop app only. Orca asks the person before an http(s) host the plugin was not allowed; `opened` is false if they cancel.",since:1,scope:"desktop",capability:null,userActivation:!0,mutation:!1,surfaces:Dn,params:KI,result:JI}),"files.saveAs":G({summary:"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.",since:1,scope:"desktop",capability:null,userActivation:!0,mutation:!0,surfaces:Dn,params:qI,result:YI}),...Mv,...qv,...jv},Qv=Object.keys(Ft).filter(ey);function ey(e){return Object.hasOwn(Ft,e)}function Ip(e){return ey(e)?Ft[e]:null}var ty=Math.max(...Qv.map(e=>Ft[e].since));function Jr(e){return Qv.filter(n=>Ft[n].surfaces.includes(e))}function $a(e,n){return Jr(e).filter(i=>n||Ft[i].userActivation!==!0)}function ka(e,n="panel"){return Math.max(0,...e.map(i=>Dv(Ft[i],n)))}var Ue=1,TD=l.number().refine(e=>e===Ue,{error:e=>typeof e.input=="number"&&e.input>Ue?`targets host API ${e.input}, but this Orca supports only ${Ue}; update Orca`:`must be ${Ue}`}).meta({const:Ue});var eS=new Set(["rate_limited","unavailable","timeout","conflict"]);function He(e,n=e,i){return Object.assign(new Error(n),{code:e,retryable:eS.has(e),...i===void 0?{}:{retryAfterMs:i}})}var tS=new Set(["plugin.request","events.subscribe","storage.get","storage.set","storage.delete","storage.keys","settings.get","settings.set","secrets.get","secrets.set","secrets.delete","secrets.protection"]);function xa(e,n){return typeof e=="object"&&e!==null&&n in e?Object.getOwnPropertyDescriptor(e,n)?.value:void 0}function ny(e){let n={storage:new Map(Object.entries(e.storage??{})),settings:new Map(Object.entries(e.settings??{})),secrets:new Map(Object.entries(e.secrets??{}))},i=new Set,o=(a,s,u)=>{if(u===void 0?n[a].delete(s):n[a].set(s,u),a!=="secrets")for(let c of i)c(a,s);return{ok:!0}},t=null;async function r(a){let s=String(xa(a,"name")),u=xa(a,"input");if(!e.requests||!Object.hasOwn(e.requests,s)){if(t)return{output:await t(s,u)??null};throw He("unknown_method",`no panel handler named ${s}`)}let c=e.requests[s];return{output:(typeof c=="function"?await c(u):c)??null}}return{handles:a=>tS.has(a),async answer(a,s){let u=String(xa(s,"key")),c=xa(s,"value");switch(a){case"plugin.request":return r(s);case"storage.get":return{value:n.storage.get(u)??null};case"storage.keys":return{keys:[...n.storage.keys()]};case"settings.get":return{settings:Object.fromEntries(n.settings)};case"secrets.get":return{value:n.secrets.get(u)??null};case"secrets.protection":return{status:"os-keyring"};case"storage.set":return o("storage",u,c);case"settings.set":return o("settings",u,c);case"secrets.set":return o("secrets",u,c);case"storage.delete":return o("storage",u,void 0);case"secrets.delete":return o("secrets",u,void 0);default:return{ok:!0}}},routeRequests(a){return t=a,()=>{t===a&&(t=null)}},onChange(a){return i.add(a),()=>i.delete(a)},snapshot:()=>({storage:Object.fromEntries(n.storage),settings:Object.fromEntries(n.settings),secrets:Object.fromEntries(n.secrets)})}}var wa=class extends Error{constructor(i,o){super(o);this.code=i}code};function nS(e){return typeof e=="function"}var ry={ok:!0};function rS(e){return e.result.safeParse(ry).success}var Sp={"workspace.readContext":null,"notifications.show":{delivered:!0},"shell.openExternal":{opened:!0},"files.saveAs":{status:"saved"},"agents.list":{agents:[]},"editor.readTypography":Lv({terminalFontSize:14},0)};function iy(e,n){let i=new Set(e.capabilities??n),o=new Map(Object.entries(e.results??{})),t=ny(e),r={panel:$a("panel",!0),worker:Jr("worker")},a=[],s=[],u=new Set,c=0,d=(g,y)=>{throw new wa(g,y)};async function p(g,y,x){let I=Ip(g);if(!I)return d("unknown_method",`${g} does not exist in this Orca`);if(!I.surfaces.includes(x))return d("not_permitted",`${g} is not available to ${x}s`);if(I.userActivation===!0){if(c===0)return d("not_permitted",`${g} needs a click or key press in the frame`);c-=1}if(I.capability&&!i.has(I.capability))return d("capability_denied",`${g} needs the ${I.capability} capability`);let b=I.params.safeParse(y);return b.success?b.data:d("invalid_params",b.error.issues[0]?.message??"invalid params")}async function m(g,y,x){let I;try{I=await p(g,y,x)}catch(E){throw E instanceof wa?(s.push({method:g,code:E.code,message:E.message}),He(E.code,E.message)):E}let b=Ip(g);if(!b)throw He("unknown_method",g);let T=!o.has(g)&&t.handles(g),A=rS(b);if(!o.has(g)&&!T&&!Object.hasOwn(Sp,g)&&!A)throw He("unavailable",`the test configured no result for ${g}`);let D=o.has(g)?o.get(g):Object.hasOwn(Sp,g)?Sp[g]:ry,w=T?await t.answer(g,I):nS(D)?await D(I):D,O=b.result.safeParse(w);if(!O.success)throw new TypeError(`mockHost: the result configured for ${g} does not match the host API: ${O.error.issues[0]?.message??"invalid result"}`);return O.data}let f=g=>({api:{major:Ue,minor:ka(r[g])},call(y,x){a.push({method:y,params:x});let I=m(y,x,g);return u.add(I),I.catch(()=>{}).finally(()=>u.delete(I)),I},has:y=>r[g].some(x=>x===y)});return{calls:a,refusals:s,capabilities:[...i],callsOf:g=>a.filter(y=>y.method===g).map(y=>y.params),respond:(g,y)=>o.set(g,y),grantGesture:()=>{c=1},data:()=>t.snapshot(),onDataChange:g=>t.onChange(g),routeRequests:g=>t.routeRequests(g),async idle(){for(;u.size>0;)await Promise.allSettled(u)},bridge:f("panel"),worker:f("worker")}}var iS=l.object({label:H,labelHidden:N,value:Ln.optional(),defaultValue:Ln.optional(),placeholder:V.optional(),rows:l.number().int().min(2).max(20).optional(),maxLength:l.number().int().positive().max(L.maxTextLength).optional(),invalid:N,disabled:N,onChange:Y,onSubmit:Y,...ve}).strict(),oS=l.object({label:H,description:V.optional(),checked:N,defaultChecked:N,disabled:N,onChange:Y,...ve}).strict(),aS=l.object({label:H,labelHidden:N,value:V.optional(),defaultValue:V.optional(),options:l.array(l.object({value:H,label:H,description:V.optional(),disabled:N}).strict()).max(L.maxRadioOptions),orientation:l.enum(["vertical","horizontal"]).optional(),disabled:N,onChange:Y,...ve}).strict(),Ia=l.number().refine(Number.isFinite,"must be finite"),sS=l.object({label:H,labelHidden:N,value:Ia.optional(),defaultValue:Ia.optional(),min:Ia.optional(),max:Ia.optional(),step:l.number().positive().refine(Number.isFinite).optional(),showValue:N,disabled:N,onChange:Y,...ve}).strict(),uS=l.object({label:H,description:V.optional(),error:V.optional(),required:N}).strict(),oy={Textarea:R(iS,"none"),Checkbox:R(oS,"none"),RadioGroup:R(aS,"none"),Slider:R(sS,"none"),Field:R(uS,"any")};var cS=l.object({label:H,value:V.optional(),defaultValue:V.optional(),onChange:Y,...ve}).strict(),lS=l.object({value:H,label:H,icon:je.optional(),disabled:N}).strict(),dS=l.object({title:H,description:V.optional(),open:N,defaultOpen:N,disabled:N,onChange:Y,...ve}).strict(),pS=l.object({label:H,gap:Ee}).strict(),mS=l.object({columns:l.number().int().min(1).max(6).optional(),minItemWidth:l.enum(["xs","sm","md","lg"]).optional(),gap:Ee}).strict(),ay={Tabs:R(cS,"tabs"),Tab:R(lS,"any"),Collapsible:R(dS,"any"),Toolbar:R(pS,"any"),Grid:R(mS,"any")};function Re(e){return typeof e=="object"&&e!==null}var Xr=["long","short","duration"],Pp=l.number().refine(Number.isFinite,"must be a finite time in milliseconds"),sy=l.object({type:l.literal("relative"),at:Pp,format:l.enum(Xr).optional()}).strict(),fS=[["day",864e5],["hour",36e5],["minute",6e4],["second",1e3]];function gS(e){let n=Math.abs(e),[i,o]=fS.find(([,t])=>n>=t)??["second",1e3];return{value:Math.trunc(e/o),unit:i}}function uy(e,n,i,o){let{value:t,unit:r}=gS(e-n);return i==="duration"?new Intl.NumberFormat(o,{style:"unit",unit:r,unitDisplay:"narrow"}).format(Math.abs(t)):new Intl.RelativeTimeFormat(o,{numeric:i==="long"?"auto":"always",style:i==="long"?"long":"narrow"}).format(t,r)}var Tp=l.string().min(1).max(64),hS=l.union([l.string().max(L.maxLabelLength),l.number(),l.boolean(),l.null(),sy]),cy=e=>new Set(e.map(n=>n.key)).size===e.length,vS=l.object({label:H,columns:l.array(l.object({key:Tp,label:H,align:l.enum(["start","center","end"]).optional(),width:l.enum(["auto","xs","sm","md","lg"]).optional(),minWidth:l.enum(["auto","xs","sm","md"]).optional(),hideBelow:l.enum(["sm","md"]).optional(),sortable:N}).strict()).min(1).max(L.maxTableColumns).refine(cy,"column keys must be unique"),rows:l.array(l.object({key:Zt,cells:l.record(Tp,hS.refine(e=>typeof e!="number"||Number.isFinite(e))).refine(e=>Object.keys(e).length<=L.maxTableColumns),disabled:N}).strict()).max(L.maxTableRows).refine(cy,"row keys must be unique"),sort:l.object({column:Tp,direction:l.enum(["asc","desc"])}).strict().optional(),selectionMode:l.enum(["none","single","multiple"]).optional(),selected:l.array(Zt).max(L.maxTableRows).optional(),defaultSelected:l.array(Zt).max(L.maxTableRows).optional(),hasMore:N,loading:N,emptyText:V.optional(),maxHeight:Hr,onSortChange:Y,onSelectionChange:Y,onRowAction:Y,onLoadMore:Y,...ve}).strict(),yS=l.object({key:Zt,label:H,description:V.optional(),icon:je.optional(),disabled:N,hasChildren:N,children:l.array(l.unknown()).optional()}).strict();function bS(e){let n=[],i=new Set,o=[{raw:e,out:n,depth:1}],t=0;for(let r=o.pop();r;r=o.pop()){if(r.depth>L.maxTreeItemDepth)return`items nest deeper than ${L.maxTreeItemDepth}`;for(let a of r.raw){if(++t>L.maxTreeItems)return`more than ${L.maxTreeItems} items`;let s=yS.safeParse(Re(a)?a:null);if(!s.success)return"each item needs a key and a label";let{children:u,...c}=s.data;if(i.has(c.key))return`duplicate item key ${JSON.stringify(c.key.slice(0,64))}`;i.add(c.key);let d=c;r.out.push(d),u&&(d.children=[],o.push({raw:u,out:d.children,depth:r.depth+1}))}}return n}var _S=l.array(l.unknown()).transform((e,n)=>{let i=bS(e);return typeof i=="string"?(n.addIssue({code:"custom",message:i}),l.NEVER):i}),$S=l.object({label:H,items:_S,selected:V.optional(),defaultSelected:V.optional(),expanded:l.array(Zt).max(L.maxTreeItems).optional(),defaultExpanded:l.array(Zt).max(L.maxTreeItems).optional(),maxHeight:Hr,onSelect:Y,onToggle:Y,onAction:Y,...ve}).strict(),ly={Table:R(vS,"none"),Tree:R($S,"none")};var dy=l.string().max(L.maxDocumentLength),kS=/\.(?:png|jpe?g|webp)$/i;function xS(e){return kS.test(e)&&!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(e)&&!/[\\\0?#]/.test(e)&&e.split("/").every(n=>n!==""&&n!=="."&&n!=="..")}var wS=l.object({content:dy}).strict(),IS=l.object({code:dy,language:l.string().regex(/^[A-Za-z0-9_+#.-]{1,64}$/).optional(),label:V.optional(),lineNumbers:N,wrap:N,maxHeight:Hr}).strict(),SS=l.object({src:l.string().max(512).refine(xS,"must be an image file in the plugin"),alt:V,size:l.enum(["xs","sm","md","lg","full"]).optional(),fit:l.enum(["contain","cover"]).optional(),rounded:N}).strict(),PS=l.object({label:V.optional(),items:l.array(l.object({label:H,value:l.union([V,l.number().refine(Number.isFinite),l.boolean(),l.null()]),tone:tt,code:N}).strict()).max(L.maxKeyValueItems)}).strict(),TS=l.object({tone:tt,title:V.optional(),icon:je.optional()}).strict(),ES=l.object({at:Pp,format:l.enum(Xr).optional(),tone:tt,muted:N}).strict(),AS=l.object({shape:l.enum(["text","block","circle"]).optional(),lines:l.number().int().min(1).max(10).optional(),label:V.optional()}).strict(),py={Markdown:R(wS,"none"),CodeView:R(IS,"none"),Image:R(SS,"none"),KeyValue:R(PS,"none"),Callout:R(TS,"any"),Skeleton:R(AS,"none"),RelativeTime:R(ES,"none")};var OS=4096,Rn=l.union([l.number().min(0).max(OS),l.string().regex(/^(100|[1-9]?[0-9])%$/)]).optional(),NS=["none","muted","card",...Mt],zS=l.object({direction:l.enum(["row","column"]).optional(),gap:Ee,padding:Ee,paddingX:Ee,paddingY:Ee,margin:Ee,align:Br,justify:l.enum(["start","center","end","between"]).optional(),wrap:N,grow:N,shrink:N,width:Rn,height:Rn,minWidth:Rn,minHeight:Rn,maxWidth:Rn,maxHeight:Rn,columns:l.number().int().min(1).max(6).optional(),scroll:l.enum(["x","y","both"]).optional(),border:l.union([l.boolean(),l.enum(Mt)]).optional(),radius:l.enum(["none","sm","md","lg","full"]).optional(),background:l.enum(NS).optional()}).strict(),my={Box:R(zS,"any")};var DS=yt,fy=new Set(DS),CS=l.object({gap:Ee,align:Br}).strict(),LS=l.object({gap:Ee,align:Br,justify:l.enum(["start","center","end","between"]).optional(),wrap:N}).strict(),jS=l.object({tone:tt,muted:N,size:_a,weight:l.enum(["regular","medium","semibold"]).optional(),truncate:N}).strict(),US=l.object({level:l.union([l.literal(1),l.literal(2),l.literal(3)]).optional()}).strict(),RS=l.object({variant:vp,tone:tt,size:_a,icon:je.optional(),tooltip:V.optional(),disabled:N,onPress:Y}).strict(),MS=l.object({icon:je,label:H,tooltip:V.optional(),variant:vp,tone:tt,size:_a,disabled:N,onPress:Y}).strict(),ZS=l.object({tone:tt}).strict(),FS=l.object({title:V.optional(),description:V.optional()}).strict(),VS=l.object({label:V.optional()}).strict(),GS=l.object({title:V.optional(),description:V.optional(),icon:je.optional(),current:N,disabled:N,onPress:Y}).strict(),BS=l.object({label:H,labelHidden:N,value:Ln.optional(),defaultValue:Ln.optional(),placeholder:V.optional(),type:l.enum(["text","search","password"]).optional(),maxLength:l.number().int().positive().max(L.maxTextLength).optional(),invalid:N,disabled:N,onChange:Y,onSubmit:Y,...ve}).strict(),HS=l.object({label:H,checked:N,defaultChecked:N,disabled:N,onChange:Y,...ve}).strict(),WS=l.object({label:H,labelHidden:N,value:V.optional(),defaultValue:V.optional(),placeholder:V.optional(),options:l.array(l.object({value:H,label:H,disabled:N}).strict()).max(L.maxSelectOptions),disabled:N,onChange:Y,...ve}).strict(),KS=l.object({orientation:l.enum(["horizontal","vertical"]).optional()}).strict(),JS=l.object({label:H,labelHidden:N,value:l.number().min(0).max(100).optional()}).strict(),XS=l.object({label:H,labelHidden:N}).strict(),qS=l.object({title:H,description:V.optional(),icon:je.optional()}).strict(),YS=l.object({href:l.string().max(2048).regex(/^(https?:\/\/|mailto:)/i).optional(),onPress:Y}).strict(),QS=l.object({block:N}).strict(),qr={Stack:R(CS,"any"),Row:R(LS,"any"),Text:R(jS,"inline"),Heading:R(US,"inline"),Button:R(RS,"text"),IconButton:R(MS,"none"),Badge:R(ZS,"text"),Card:R(FS,"any"),List:R(VS,"items"),ListItem:R(GS,"any"),Input:R(BS,"none"),Switch:R(HS,"none"),Select:R(WS,"none"),Separator:R(KS,"none"),Progress:R(JS,"none"),Spinner:R(XS,"none"),EmptyState:R(qS,"any"),Link:R(YS,"text"),Code:R(QS,"text"),...oy,...ay,...ly,...py,...my};function Ep(e){return typeof e=="string"&&Object.hasOwn(qr,e)}var bC=Object.keys(qr).filter(Ep),gy=new Set(["Text","Link","Code","Badge","RelativeTime"]),hy=new Set(["Button","IconButton","Input","Switch","Select","Link","ListItem","Textarea","Checkbox","RadioGroup","Slider","Tabs","Collapsible","Table","Tree","Markdown"]),Sa={items:"ListItem",tabs:"Tab"};var eP=new Set(["type","key","props","children"]),Ap=32,tP=8,nP=/^[A-Z][A-Za-z0-9]{0,63}$/,rP=20,iP=new Map([[Sa.items,"items"],[Sa.tabs,"tabs"]]),oP={ListItem:"List",Tab:"Tabs"};function vy(e){return e==="items"||e==="tabs"?Sa[e]:void 0}var Pa=class extends Error{};function ne(e,n){throw new Pa(`${e}: ${n}`)}function aP(e,n){return e===void 0?[]:(Array.isArray(e)||ne(n,"children must be an array"),e)}function sP(e,n,i){let o=qr[e].props.shape,t={};for(let[r,a]of Object.entries(n)){if(a===void 0)continue;if(!Object.hasOwn(o,r)){i(`${e}: ignored unknown prop "${r.slice(0,64)}"`);continue}let s=o[r].safeParse(a);if(!s.success){i(`${e}: ignored invalid ${r}`);continue}t[r]=s.data}for(let[r,a]of Object.entries(o))if(!Object.hasOwn(t,r)&&!a.safeParse(void 0).success)return i(`${e}: missing required ${r}; showing a placeholder`),null;return typeof t.icon=="string"&&!fy.has(t.icon)&&i(`${e}: unknown icon "${t.icon}"; showing a fallback glyph`),t}function yy(e){if(e===null)return{ok:!0,tree:null,nodes:0,warnings:[]};let n=new Set,i=c=>{n.sizeL.maxTextLength&&ne(I,"text is too long"),r+ ++a>L.maxNodes&&ne(I,`more than ${L.maxNodes} nodes`),s+=tP+Ne.length,s>L.maxTreeBytes&&ne(I,`tree exceeds ${L.maxTreeBytes} bytes`),x.push(Ne);continue}Re(d)||ne(I,"expected a node, string or number");let b=d.type,T=Ep(b);!T&&(typeof b!="string"||!nP.test(b))&&ne(I,`invalid component name ${JSON.stringify(String(b)).slice(0,80)}`),(f==="none"||f==="text")&&ne(I,`${m} accepts only text`),f==="inline"&&T&&!gy.has(b)&&ne(I,`${m} accepts only text, Text, Link, Code, Badge and RelativeTime`);let A=vy(f),D=typeof b=="string"?iP.get(b):void 0;A!==void 0&&b!==A&&ne(I,`${m} holds only ${A}`),D!==void 0&&D!==f&&ne(I,`${b} must be a direct child of ${oP[b]??"its container"}`),g&&hy.has(b)&&ne(I,`a ListItem with onPress cannot contain a ${b}`),++r+a>L.maxNodes&&ne(I,`more than ${L.maxNodes} nodes`),p>L.maxDepth&&ne(I,`deeper than ${L.maxDepth}`);for(let Ne of Object.keys(d))eP.has(Ne)||ne(I,`unknown node field ${Ne.slice(0,64)}`);let w=d.key;w!==void 0&&((typeof w!="string"||w.length>L.maxLabelLength)&&ne(I,"key must be a short string"),y.has(w)&&ne(I,`duplicate key ${JSON.stringify(w.slice(0,64))} among siblings`),y.add(w));let O=w===void 0?{}:{key:w};if(!T){i(`unknown component ${b}; showing a placeholder`),x.push({type:"Unsupported",...O,props:{name:b},children:[]}),s+=Ap+b.length;continue}let E=d.props??{};(!Re(E)||Object.keys(E).length>L.maxPropsPerNode)&&ne(I,"props must be an object with few fields");let B=sP(b,E,i);if(!B){x.push({type:"Unsupported",...O,props:{name:b},children:[]}),s+=Ap+b.length;continue}s+=Ap+JSON.stringify(B).length,s>L.maxTreeBytes&&ne(I,`tree exceeds ${L.maxTreeBytes} bytes`);let C=[],re={type:b,...O,props:B,children:C};x.push(re);let ye=aP(d.children,I);ye.length>L.maxNodes&&ne(I,`more than ${L.maxNodes} children`);let Oe=new Set,ni=g||b==="ListItem"&&B.onPress!==void 0;for(let Ne=ye.length-1;Ne>=0;Ne--)t.push({raw:ye[Ne],depth:p+1,parentType:b,policy:qr[b].children,inPressable:ni,siblingKeys:Oe,out:C,path:`${I}.children.${Ne}`})}}catch(c){if(c instanceof Pa)return{ok:!1,error:c.message};throw c}let u=o[0];return typeof u=="object"?{ok:!0,tree:u,nodes:r,warnings:[...n]}:{ok:!1,error:"tree: expected a node"}}var Mn=l.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),Vt=l.string().min(1).max(64),Op=l.union([l.string().max(L.maxTextLength),l.number().refine(Number.isFinite),l.boolean(),l.null()]),by=l.array(Op).max(L.maxEventArgItems),uP=l.union([Op,by,l.record(l.string().max(64),l.union([Op,by])).refine(e=>Object.keys(e).length<=16,"too many fields")]),OC=l.object({document:Vt,revision:Mn,callback:l.string().min(1).max(L.maxCallbackIdLength).regex(ba),args:l.array(uP).max(L.maxEventArgs),edit:Mn.optional()}).strict(),cP=l.object({document:Vt,revision:Mn,tree:l.unknown()}).strict(),_y=l.object({document:Vt.optional(),revision:Mn,message:l.string().max(L.maxLabelLength*4),stack:l.string().max(8192).optional()}).strict();function $y(e){let n=cP.safeParse(e);if(!n.success)return{ok:!1,document:null,revision:null,error:"malformed render"};let{document:i,revision:o}=n.data,t=yy(n.data.tree);return t.ok?{...t,document:i,revision:o}:{ok:!1,document:i,revision:o,error:t.error}}var NC=l.object({document:Vt,revision:Mn,accepted:l.boolean(),retryAfterMs:l.number().int().nonnegative().max(6e4).optional()}).strict(),zC=l.object({document:Vt}).strict(),ky=/^[A-Za-z0-9_.:-]{1,128}$/,lP=["plugin","workspace"],Np=l.number().int().min(0).max(1e6),DC=l.object({version:Np,value:l.json()}).strict().refine(e=>JSON.stringify(e.value).length<=la,"persisted value is too large"),xy=l.object({document:Vt,entries:l.array(l.object({scope:l.enum(lP),key:l.string().regex(ky),version:Np,value:l.json()}).strict()).min(1).max(64),hydration:Mn.optional()}).strict(),CC=l.object({document:Vt,entries:l.array(l.object({key:l.string().regex(ky),version:Np,value:l.json().optional()}).strict().refine(e=>JSON.stringify(e.value??null).length<=la,"retained value is too large")).min(1).max(64)}).strict();import{installOrcaDeclarativeRuntime as wP}from"./orca-runtime.mjs";var dP={Stack:"group",Row:"group",Card:"region",Text:"text",Heading:"heading",Button:"button",IconButton:"button",Badge:"status",List:"list",ListItem:"listitem",Input:"textbox",Switch:"switch",Select:"combobox",Separator:"separator",Progress:"progressbar",Spinner:"status",EmptyState:"group",Link:"link",Code:"code",Textarea:"textbox",Checkbox:"checkbox",RadioGroup:"radiogroup",Slider:"slider",Field:"group",Tabs:"tablist",Tab:"tab",Collapsible:"group",Toolbar:"toolbar",Grid:"group",Table:"grid",Tree:"tree",Markdown:"document",CodeView:"code",Image:"img",KeyValue:"list",Callout:"note",Skeleton:"status",RelativeTime:"time",Box:"group",Unsupported:"unsupported"};function Iy(e,n){if(typeof e!="number")return"";let i=Xr.find(o=>o===n)??"short";return uy(e,Date.now(),i,"en")}function pP(e){return typeof e=="object"&&e!==null&&"at"in e?Iy(e.at,"format"in e?e.format:void 0):String(e??"")}var mP=["title","description","content","code","error","alt"];function fP(e){let n=[],i=[e.rows,e.items].flatMap(o=>Array.isArray(o)?o:[]);for(let o=i.shift();o!==void 0;o=i.shift()){if(typeof o!="object"||o===null)continue;let t={...o};typeof t.cells=="object"&&t.cells!==null&&n.push(...Object.values(t.cells).map(pP));for(let r of["label","value"])t[r]!==void 0&&t[r]!==null&&n.push(String(t[r]));Array.isArray(t.children)&&i.push(...t.children)}return n}function Sy(e){if(typeof e=="string")return e;let n=e.props,i=mP.map(o=>n[o]).filter(o=>typeof o=="string");return e.type==="RelativeTime"?Iy(n.at,n.format):e.type==="Tab"?typeof n.label=="string"?n.label:"":[...i,...fP(n),...e.children.map(Sy)].join(" ")}var zp=e=>e.replace(/\s+/g," ").trim();function gP(e){let n=e.props,i=n.label??n.title;return typeof i=="string"?i:void 0}function hP(e,n){let i=e.props,o=e.type==="ListItem"&&i.onPress!==void 0;return{type:e.type,key:e.key?.startsWith("#")?void 0:e.key,role:o?"button":dP[e.type]??"generic",label:gP(e),text:zp(Sy(e)),props:i,disabled:i.disabled===!0,path:n}}function Ta(e){let n=[],i=(o,t)=>{n.push(hP(o,t)),o.children.forEach((r,a)=>{typeof r!="string"&&i(r,`${t}/${r.key??`#${a}`}`)})};return e&&i(e,e.key??"#0"),n}function wy(e,n){return e===void 0?!1:typeof n=="string"?zp(e)===zp(n):n.test(e)}function Py(e,n){return(n.type===void 0||e.type===n.type)&&(n.role===void 0||e.role===n.role)&&(n.text===void 0||wy(e.text,n.text))&&(n.label===void 0||wy(e.label,n.label))&&(n.testId===void 0||e.key===n.testId)}function Ty(e){return e.filter(n=>!e.some(i=>i.path.startsWith(`${n.path}/`)))}function Ey(e){return Object.entries(e).map(([n,i])=>`${n}: ${i instanceof RegExp?String(i):JSON.stringify(i)}`).join(", ")}function Dp(e){if(typeof e=="string")return e;let n={};for(let[i,o]of Object.entries(e.props)){if(i.startsWith("$"))continue;let t=typeof o=="object"&&o!==null&&"$callback"in o;n[i]=t?"[handler]":o}return{type:e.type,...e.key&&!e.key.startsWith("#")?{key:e.key}:{},...Object.keys(n).length>0?{props:n}:{},...e.children.length>0?{children:e.children.map(Dp)}:{}}}var vP={value:"defaultValue",checked:"defaultChecked",open:"defaultOpen",selected:"defaultSelected"};function Cp(e){return typeof e=="string"||typeof e=="boolean"||typeof e=="number"||Array.isArray(e)&&e.every(n=>typeof n=="string")}function yP(e){switch(e.type){case"Switch":case"Checkbox":case"Collapsible":return!1;case"Slider":return typeof e.props.min=="number"?e.props.min:0;case"Table":return[];default:return""}}function Ay(){let e=new Map;function n(t){let r=hp(t.type);if(!r)throw new Error(`a ${t.type} has no value; expected a form control, Tabs or Table`);return r}function i(t){let r=e.get(t.path);if(r)return r;let{value:a}=n(t),s=t.props[a],u=t.props[vP[a]??""],d={value:Cp(s)?s:Cp(u)?u:yP(t),sent:0};return e.set(t.path,d),d}function o(t,r){let{value:a,change:s}=n(t);if(t.props[a]!==void 0&&t.props[s]===void 0)throw new Error(`the ${t.type} is read-only: it has a ${a} and no ${s}`);let u=i(t);return u.sent+=1,u.value=r,{value:r,id:u.sent}}return{observe(t){let r=new Map;for(let a of t){let s=hp(a.type);if(!s)continue;let u=e.get(a.path)??i(a),c=a.props[s.value],d=a.props[ya];Cp(c)&&(typeof d=="number"?d:0)>=u.sent&&(u.value=c),r.set(a.path,u)}e=r},valueOf:t=>i(t).value,edit:o,typeInto(t,r,a){if(t.type!=="Input"&&t.type!=="Textarea")throw new Error(`type: a ${t.type} takes no text; expected an Input or Textarea`);let s=a?"":String(i(t).value),u=[];a&&u.push(o(t,s));for(let c of r)s+=c,u.push(o(t,s));return u},toggle(t){if(!["Switch","Checkbox","Collapsible"].includes(t.type))throw new Error(`toggle: expected a Switch, Checkbox or Collapsible, not a ${t.type}`);return o(t,!i(t).value)}}}function Oy(e){let n=new Map,i=(s,u)=>{let c=n.get(s)??new Set;return c.add(u),n.set(s,c),{dispose:()=>c.delete(u)}},o=(s,u)=>{for(let c of n.get(s)??[])c(u)},t=s=>u=>typeof u=="object"&&u!==null&&s in u?Object.getOwnPropertyDescriptor(u,s)?.value:void 0,r=()=>{};return{facades:{plugin:{request:(s,u,c)=>{let d=typeof c=="object"&&c!==null&&"timeoutMs"in c?c.timeoutMs:void 0;return e("plugin.request",{name:s,...u===void 0?{}:{input:u},...typeof d=="number"?{timeoutMs:d}:{}}).then(t("output"))},on:(s,u)=>i(`message:${s}`,u)},storage:{get:s=>e("storage.get",{key:s}).then(u=>t("value")(u)??null),set:(s,u)=>e("storage.set",{key:s,value:u}).then(r),delete:s=>e("storage.delete",{key:s}).then(r),keys:async()=>{let s=t("keys")(await e("storage.keys"));return Array.isArray(s)?s.map(String):[]},onDidChange:s=>i("changed:storage",u=>s({key:String(u)}))},settings:{get:async s=>{let u=t("settings")(await e("settings.get"));return s===void 0?u:t(s)(u)},set:(s,u)=>e("settings.set",{key:s,value:u}).then(r),onDidChange:s=>i("changed:settings",u=>s({key:String(u)}))},secrets:{get:async s=>{let u=t("value")(await e("secrets.get",{key:s}));return typeof u=="string"?u:null},set:(s,u)=>e("secrets.set",{key:s,value:u}).then(r),delete:s=>e("secrets.delete",{key:s}).then(r)}},post:(s,u)=>o(`message:${s}`,u),changed:(s,u)=>o(`changed:${s}`,u)}}var Ny=Symbol.for("orca.testing.stored");function zy(e,n){return Object.freeze({[Ny]:!0,version:n,value:e})}function bP(e){return typeof e=="object"&&e!==null&&Ny in e}function Dy(e,n){for(let i of["plugin","workspace"])for(let[o,t]of Object.entries(n?.[i]??{}))e[i].set(o,bP(t)?{version:t.version,value:t.value}:{version:1,value:t})}var _P={sm:360,md:480};function Cy(e,n){let i=e.filter(o=>!o.hideBelow||n>=_P[o.hideBelow]);return i.length>0?i:e.slice(0,1)}var Lp=(e,n)=>e.path.startsWith(`${n.path}/`);function Ea(e,n){return n.filter(i=>i.type==="Tab"&&Lp(i,e)&&!i.path.slice(e.path.length+1).includes("/"))}function jp(e,n,i){let o=Ea(e,n);return o.find(t=>t.props.value===i)??o.find(t=>!t.disabled)}function Ly(e,n){let i=[];for(let o of e)if(!i.some(t=>Lp(o,t))&&(o.type==="Collapsible"&&n(o)!==!0&&i.push(o),o.type==="Tabs")){let t=jp(o,e,n(o));i.push(...Ea(o,e).filter(r=>r!==t))}return e.filter(o=>!i.some(t=>Lp(o,t)))}function jy(e){return typeof e=="object"&&e!==null&&"key"in e&&typeof e.key=="string"}function Up(e){return Array.isArray(e.props.rows)?e.props.rows.filter(jy):[]}function Rp(e){let n=[],i=Array.isArray(e.props.items)?[...e.props.items]:[];for(let o=i.shift();o!==void 0;o=i.shift())if(jy(o)){n.push(o);let t="children"in o?o.children:void 0;Array.isArray(t)&&i.push(...t)}return n}function Ae(e,n,i){if(e.type!==n)throw new Error(`${i}: expected a ${n}, not a ${e.type}`)}function Mp(e,n,i){let o=Up(e).find(t=>t.key===n);if(!o||o.disabled===!0)throw new Error(`${i}: the Table has no enabled row "${n}"`);return o}function Aa(e,n,i){let o=Rp(e).find(t=>t.key===n);if(!o)throw new Error(`${i}: the Tree has no item "${n}"`);return o}function Uy(e){return e.hasChildren===!0||Array.isArray(e.children)&&e.children.length>0}function Ry(e,n){let o=(Array.isArray(e.props.columns)?e.props.columns:[]).find(a=>typeof a=="object"&&a!==null&&"key"in a&&a.key===n);if(!o||typeof o!="object"||!("sortable"in o)||o.sortable!==!0)throw new Error(`sort: the Table has no sortable column "${n}"`);let t=e.props.sort,r=typeof t=="object"&&t!==null&&"column"in t&&t.column===n&&"direction"in t&&t.direction==="asc";return{column:n,direction:r?"desc":"asc"}}function $P(e,n){return e.type==="Tabs"?Ea(e,n).map(o=>({value:o.props.value,disabled:o.disabled})):(Array.isArray(e.props.options)?e.props.options:[]).map(o=>typeof o=="object"&&o!==null&&"value"in o?{value:o.value,disabled:"disabled"in o&&o.disabled===!0}:{value:void 0,disabled:!0})}function My({host:e,controls:n,resolve:i,elements:o,dispatch:t,settled:r}){let a=new Map,s=d=>{let p=a.get(d.path);if(!p){let m=d.props.expanded??d.props.defaultExpanded;p=new Set(Array.isArray(m)?m.filter(f=>typeof f=="string"):[]),a.set(d.path,p)}return p},u=async(d,p,m,f)=>{let g=n.edit(d,m);t(p,[f],g.id),await r()},c=async(d,p,m,f)=>{let g=i(d,f);if(Ae(g,"Tree",f),!Uy(Aa(g,p,f)))throw new Error(`${f}: the Tree item "${p}" has no children`);let y=s(g);y.has(p)!==m&&(m?y.add(p):y.delete(p),t(g.props.onToggle,[{key:p,expanded:m}]),await r())};return{async press(d){let p=i(d,"press"),{href:m,onPress:f}=p.props;if(p.type==="Link"&&typeof m=="string")e.grantGesture(),e.bridge.call("shell.openExternal",{url:m}).catch(()=>{});else{if(f===void 0)throw new Error(`press: the ${p.type} has no onPress`);e.grantGesture()}t(f,[]),await r()},async type(d,p,m={}){let f=i(d,"type");for(let g of n.typeInto(f,p,m.replace===!0))t(f.props.onChange,[g.value],g.id),await r();m.submit&&(t(f.props.onSubmit,[n.valueOf(f)]),await r())},async submit(d){let p=i(d,"submit");t(p.props.onSubmit,[n.valueOf(p)]),await r()},async toggle(d){let p=i(d,"toggle"),m=n.toggle(p);t(p.props.onChange,[m.value],m.id),await r()},async select(d,p){let m=i(d,"select");if(!["Select","RadioGroup","Tabs"].includes(m.type))throw new Error(`select: expected a Select, RadioGroup or Tabs, not a ${m.type}`);let f=$P(m,o()).find(g=>g.value===p);if(!f||f.disabled)throw new Error(`select: the ${m.type} has no enabled option "${p}"`);await u(m,m.props.onChange,p,p)},async slide(d,p){let m=i(d,"slide");Ae(m,"Slider","slide");let f=typeof m.props.min=="number"?m.props.min:0,g=typeof m.props.max=="number"?m.props.max:100;if(!Number.isFinite(p)||pg)throw new Error(`slide: ${p} is outside the Slider's ${f} to ${g}`);await u(m,m.props.onChange,p,p)},async sort(d,p){let m=i(d,"sort");if(Ae(m,"Table","sort"),m.props.onSortChange===void 0)throw new Error("sort: the Table has no onSortChange");t(m.props.onSortChange,[Ry(m,p)]),await r()},async selectRows(d,p){let m=i(d,"selectRows");Ae(m,"Table","selectRows");let f=m.props.selectionMode??"none";if(f==="none"||f==="single"&&p.length>1)throw new Error(`selectRows: the Table's selectionMode is ${String(f)}`);p.forEach(g=>Mp(m,g,"selectRows")),await u(m,m.props.onSelectionChange,[...p],{keys:[...p]})},async activateRow(d,p){let m=i(d,"activateRow");if(Ae(m,"Table","activateRow"),Mp(m,p,"activateRow"),m.props.onRowAction===void 0)throw new Error("activateRow: the Table has no onRowAction");t(m.props.onRowAction,[{key:p}]),await r()},async loadMore(d){let p=i(d,"loadMore");if(Ae(p,"Table","loadMore"),p.props.hasMore!==!0||p.props.onLoadMore===void 0)throw new Error("loadMore: the Table has no more rows (hasMore and onLoadMore)");t(p.props.onLoadMore,[]),await r()},expand:(d,p)=>c(d,p,!0,"expand"),collapse:(d,p)=>c(d,p,!1,"collapse"),async selectItem(d,p){let m=i(d,"selectItem");if(Ae(m,"Tree","selectItem"),Aa(m,p,"selectItem").disabled===!0)throw new Error(`selectItem: the Tree item "${p}" is disabled`);await u(m,m.props.onSelect,p,{key:p})},async activateItem(d,p){let m=i(d,"activateItem");if(Ae(m,"Tree","activateItem"),Aa(m,p,"activateItem").disabled===!0||!m.props.onAction)throw new Error(`activateItem: the Tree item "${p}" cannot be activated`);t(m.props.onAction,[{key:p}]),await r()},expandedOf:d=>{let p=i(d,"expandedOf");return Ae(p,"Tree","expandedOf"),Rp(p).map(m=>m.key).filter(m=>s(p).has(m))},columnsOf:(d,p={})=>{let m=i(d,"columnsOf");Ae(m,"Table","columnsOf");let g=(Array.isArray(m.props.columns)?m.props.columns:[]).flatMap(y=>{if(typeof y!="object"||y===null||!("key"in y))return[];let x="hideBelow"in y?y.hideBelow:void 0;return typeof y.key=="string"?[{key:y.key,hideBelow:x==="sm"||x==="md"?x:void 0}]:[]});return Cy(g,p.width??Number.POSITIVE_INFINITY).map(y=>y.key)},rowsOf:d=>{let p=i(d,"rowsOf");return Ae(p,"Table","rowsOf"),Up(p).map(m=>m.key)}}}var Me=e=>{let n=(e??"").replace(/\s+/g," ").trim();return JSON.stringify(n.length>60?`${n.slice(0,59)}…`:n)},kP=e=>e==="danger"||e==="warning",Zy=e=>Array.isArray(e)?e.length:0;function xP(e,n){let{props:i}=e;switch(e.type){case"Heading":return`Heading ${Me(e.text)}`;case"Callout":return`Callout ${String(i.tone??"neutral")} ${Me(e.label??e.text)}`;case"EmptyState":return`EmptyState ${Me(e.label)}`;case"Table":return`Table ${Me(e.label)}: ${Zy(i.rows)} row(s)${i.hasMore===!0?", more":""}`;case"Tree":return`Tree ${Me(e.label)}: ${Zy(i.items)} top-level item(s)`;case"Tabs":return`Tabs ${Me(e.label)}: ${Me(n.get(e.path))} selected`;case"Skeleton":case"Spinner":return`${e.type} ${Me(e.label??"Loading")}`;case"Text":return i.tone==="danger"||i.tone==="warning"?`Text ${String(i.tone)} ${Me(e.text)}`:null;case"Box":{let o=[i.background,i.border].find(kP);return o?`Box ${o} ${Me(e.text)}`:null}default:return null}}function Fy(e,n){let i=[];for(let o of e){if(o.type==="List"){let r=e.filter(a=>a.type==="ListItem"&&a.path.startsWith(`${o.path}/`)).length;i.push(`List ${Me(o.label)}: ${r} item(s)`);continue}let t=xP(o,n);t&&i.push(t)}return i.length>12?[...i.slice(0,12),`… ${i.length-12} more`]:i}function Vy(e){return"path"in e}var IP=100,SP=()=>new Promise(e=>typeof setImmediate=="function"?setImmediate(e):setTimeout(e,0));async function Gy(e,n){let i=n.host,o=new Map,t=(w,O)=>{for(let E of o.get(w)??[])E(O)},r=[],a=[],s=[],u={plugin:new Map,workspace:new Map};Dy(u,n.persisted);let c=n.visible??!0,d={document:"",revision:0,tree:null},p=Ay(),m=Oy(i.bridge.call),f=i.onDataChange(m.changed);function g(w,O){if(w===Be.ready)t(Be.hydrate,{panel:{id:n.panelId??"panel"},visible:c,plugin:Object.fromEntries(u.plugin),workspace:Object.fromEntries(u.workspace)});else if(w===Be.render){let E=$y(O);if(!E.ok){a.push({kind:"refused",message:E.error}),E.document!==null&&E.revision!==null&&t(Be.ack,{document:E.document,revision:E.revision,accepted:!1});return}s.push(...E.warnings.filter(B=>!s.includes(B))),d={document:E.document,revision:E.revision,tree:E.tree},p.observe(Ta(E.tree)),t(Be.ack,{document:E.document,revision:E.revision,accepted:!0})}else if(w===Be.error){let E=_y.safeParse(O);a.push(E.success?{kind:"plugin",message:E.data.message,...E.data.stack?{stack:E.data.stack}:{}}:{kind:"plugin",message:String(O)})}else if(w===Be.persist){let E=xy.safeParse(O);if(!E.success){a.push({kind:"refused",message:"persisted state Orca would not store"});return}for(let B of E.data.entries)u[B.scope].set(B.key,{version:B.version,value:B.value})}}let y=wP({post:g,awaitHydration:!0,colorScheme:n.theme??"dark",schedule:w=>r.push(w),bridge:{api:i.bridge.api,locale:n.locale??"en",host:{call:i.bridge.call,has:i.bridge.has},...m.facades,on(w,O){let E=o.get(w)??new Set;return E.add(O),o.set(w,E),{dispose:()=>E.delete(O)}}}});async function x(){for(let w=0;wLy(Ta(d.tree),w=>p.valueOf(w)),b=w=>Ty(I().filter(O=>Py(O,w)));function T(w,O){let E=Vy(w)?I().find(B=>B.path===w.path):b(w)[0];if(!E)throw new Error(Vy(w)?`${O}: the element is no longer in the panel`:`${O}: nothing in the panel matches { ${Ey(w)} }`);if(E.disabled)throw new Error(`${O}: the ${E.type} is disabled`);return E}function A(w,O,E){let B=typeof w=="object"&&w!==null&&"$callback"in w?w.$callback:null;typeof B=="string"&&t(Be.event,{document:d.document,revision:d.revision,callback:B,args:O,...E===void 0?{}:{edit:E}})}await Promise.resolve(typeof e=="function"?{default:e}:e).then(w=>y.boot(w)).catch(w=>a.push({kind:"plugin",message:w instanceof Error?w.message:String(w),...w instanceof Error&&w.stack?{stack:w.stack}:{}})),await x();let D=My({host:i,controls:p,resolve:T,elements:I,dispatch:A,settled:x});return{host:i,errors:a,warnings:s,get refusals(){return i.refusals},find:w=>b(w)[0]??null,findAll:b,tree:()=>d.tree?Dp(d.tree):null,drawnTree:()=>d.tree,summary:()=>{let w=Ta(d.tree),O=I(),E=new Map(O.filter(B=>B.type==="Tabs").map(B=>[B.path,jp(B,w,p.valueOf(B))?.label??""]));return Fy(O,E)},get persisted(){let w=O=>Object.fromEntries([...u[O]].map(([E,B])=>[E,B.value]));return{plugin:w("plugin"),workspace:w("workspace"),storage:i.data().storage}},settled:x,...D,valueOf:w=>p.valueOf(T(w,"valueOf")),async emit(w,O){t(w,O),await x()},async post(w,O){m.post(w,O),await x()},async setVisible(w){c=w,t(Be.visibility,{visible:w}),await x()},async rerender(){y.rerender(),await x()},async unmount(){y.dispose(),o.clear(),f()}}}var By={workspaceId:l.string().min(1).max(2048),kind:l.enum(["worktree","folder"])},PP=l.object({...By,path:l.string().min(1).max(32*1024),branch:l.string().max(1024)}),TP=l.object({...By,path:l.string().min(1).max(32*1024)}),EP=l.object({workspaceId:l.string().min(1).max(2048).nullable(),paneKey:l.string().min(1).max(2048),terminalId:l.string().min(1).max(nt).optional(),title:l.string().max(Kr).optional(),state:l.enum(Cn),receivedAt:l.number().finite().positive(),mainAgent:l.object({state:l.enum(Cn),outcome:l.enum(Uv).optional(),stateStartedAt:l.number().finite()}).optional()}),AP=l.object({context:xp}),Zp={"workspace.created":PP,"workspace.removed":TP,"agent.status.changed":EP,"workspace.changed":AP,"editor.typography.changed":gp};var Yr=["terminal.send","workspace.create.before","workspace.create.after","agent.launch.before","agent.prompt.before","agent.tool.before","agent.tool.after","editor.save.before"],y0=Yr.length,OP={"terminal.send":Le,"workspace.create.before":Le,"workspace.create.after":Le,"agent.launch.before":Le,"agent.prompt.before":Le,"agent.tool.before":Le,"agent.tool.after":Le,"editor.save.before":Le};function Oa(e){return Yr.some(n=>n===e)}var b0=l.object({event:l.enum(Yr).meta({...ee("Hook the worker handles with `orca.hooks.on`.",Le),[Nn]:OP}),onError:l.enum(["pass","deny"]).optional().meta(ee("What Orca does when this plugin's hook throws, times out, crashes or does not start: `pass` (the default) carries on as if it passed; `deny` refuses the action, naming the plugin, so a guard fails closed. Ignored for hooks that only observe.",Le))}).strict().meta({id:"HookContribution",description:"A hook Orca runs through the worker before (or after) it acts. Requires `main` and the `trusted-worker` execution profile."});var k0=1024*1024,x0=new TextEncoder,w0=new Uint8Array(0);var O0=16*1024*1024,N0=16*1024*1024;var zP=256*1024;var U0=16*1024,Qr=16*1024*1024;var bt=2e3,Zn=l.strictObject({deny:l.string().min(1).max(500)}),DP=l.object({terminalId:l.string().min(1).max(nt),text:l.string().max(Qr),enter:l.boolean(),inputKind:l.enum(["driving","orchestration","launch"])}),CP=l.object({kind:l.enum(["worktree","folder"]),projectId:l.string().min(1).max(2048),name:l.string().min(1).max(1024),branch:l.string().min(1).max(1024).optional(),base:l.string().min(1).max(1024).optional()}),LP=l.object({workspaceId:l.string().min(1).max(2048),kind:l.enum(["worktree","folder"]),path:l.string().min(1).max(32*1024),branch:l.string().max(1024)}),jP=l.record(l.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(256).refine(e=>!e.toUpperCase().startsWith("ORCA_"),"ORCA_* variables are reserved"),l.string().max(32*1024)).refine(e=>Object.keys(e).length<=64,"at most 64 variables"),UP=l.enum(["worktree","folder"]),RP=l.object({agent:l.string().min(1).max(64),workspaceId:l.string().min(1).max(2048).nullable(),workspaceKind:UP.nullable(),terminalId:l.string().min(1).max(nt).optional(),command:l.string().min(1).max(Qr),env:jP}),MP=l.object({agent:l.string().min(1).max(64).nullable(),terminalId:l.string().min(1).max(nt),prompt:l.string().max(Qr),inputKind:l.enum(["driving","orchestration","launch"])}),Hy={agent:l.string().min(1).max(64),workspaceId:l.string().min(1).max(2048).nullable(),terminalId:l.string().min(1).max(nt).optional(),tool:l.string().min(1).max(256),input:l.unknown(),callId:l.string().min(1).max(256).optional()},ZP=l.object(Hy),FP=l.object({...Hy,output:l.unknown().optional()}),VP=l.object({workspaceId:l.string().min(1).max(2048).nullable(),path:l.string().min(1).max(32*1024),languageId:l.string().min(1).max(128).optional(),text:l.string().max(Qr)}),ei={"terminal.send":{payload:DP,result:Zn,mutable:["text","enter"],deadlineMs:bt,summary:"Text Orca, the CLI or a plugin is about to send to a terminal. A person typing (on the desktop, a phone or a paired client) is never hooked, and prompts Orca submits to an agent take `agent.prompt.before` instead. Writes to one terminal stay in order while a hook decides. Rewrite `text` or `enter` with `next`, or return `{ deny }` to refuse the send."},"workspace.create.before":{payload:CP,result:Zn,mutable:["name","branch","base"],deadlineMs:bt,summary:"A worktree or folder workspace is about to be created. Change `name`, `branch` or `base` with `next`, or return `{ deny }` to refuse it."},"workspace.create.after":{payload:LP,result:null,mutable:[],deadlineMs:bt,summary:"A worktree or folder workspace was created. Observe only: Orca does not wait for it, and results are ignored."},"agent.launch.before":{payload:RP,result:Zn,mutable:["command","env"],deadlineMs:bt,summary:"Orca is about to start an agent CLI in a terminal, from any launcher (new tab, workspace create, CLI, automation, orchestration). Change `command` or add `env` with `next`, or return `{ deny }` to refuse the launch. Restores of saved sessions are not hooked."},"agent.prompt.before":{payload:MP,result:Zn,mutable:["prompt"],deadlineMs:bt,summary:"Orca is about to submit a prompt to an agent in a terminal: a launch prompt, an orchestration dispatch, an issue or task prompt, or `orca terminal send` to an agent. A prompt a person submits is never hooked, and writes to one terminal stay in order while it decides. Rewrite `prompt` with `next`, or return `{ deny }` to refuse it."},"agent.tool.before":{payload:ZP,result:Zn,mutable:["input"],deadlineMs:bt,summary:"An agent in an Orca terminal is about to run a tool. Claude Code waits for the answer: return `{ deny }` to refuse the call (the agent sees the reason) or change `input` with `next`; the person is still asked as usual. Other agents only report the call, so their results are ignored. Agents on SSH or WSL hosts are not hooked yet."},"agent.tool.after":{payload:FP,result:null,mutable:[],deadlineMs:bt,summary:"An agent in an Orca terminal finished a tool call (successfully or not). Observe only: Orca does not wait for it."},"editor.save.before":{payload:VP,result:Zn,mutable:["text"],deadlineMs:bt,summary:"The person is about to save a text file from an Orca editor tab (an explicit save, not autosave). Change `text` with `next` (format on save) or return `{ deny }` to refuse the save, which shows your reason. Edits made while the hook ran stay unsaved."}};var Na=class{constructor(n){this.event=n}event},H0=l.object({event:l.enum(Yr),payload:l.unknown()}),Wy=l.discriminatedUnion("decision",[l.object({decision:l.literal("continue"),payload:l.unknown()}),l.object({decision:l.literal("result"),result:l.unknown()})]);async function Ky(e,n){let i=n;for(let o of e){let r=await o(i,a=>new Na(a??i));if(r instanceof Na)i=r.event;else if(r!=null)return{decision:"result",result:r}}return{decision:"continue",payload:i}}function GP(e){return e instanceof Error?e.message:String(e)}function Jy(e,n,i="timed out"){let o,t=new Promise((r,a)=>{o=setTimeout(()=>a(new Error(`${i} after ${n}ms`)),n)});return Promise.race([e,t]).finally(()=>clearTimeout(o))}function BP(e,n,i){let o=ei[e];if(!Re(n)||!Re(i))return{ok:!1,reason:"next() was given something other than the event object"};let t={...n};for(let a of o.mutable)Object.hasOwn(i,a)&&(t[a]=i[a]);let r=o.payload.safeParse(t);return r.success?{ok:!0,payload:r.data}:{ok:!1,reason:r.error.issues[0]?.message??"invalid change"}}var Fp=class extends Error{};async function Xy(e){let{event:n}=e,i=ei[n],o=e.deadlineMs??i.deadlineMs,t=e.payload;for(let r of e.participants){let a=e.now(),s=()=>Math.round(e.now()-a),u=i.result!==null&&e.failsClosed?.(r)===!0,c=(f,g=!1)=>u&&!g?(e.log(r,`hook ${n} denied after ${s()}ms (onError: deny): ${f}`),{kind:"deny",pluginKey:r,reason:`${r} failed and denies on error: ${f}`}):(e.log(r,`hook ${n} skipped after ${s()}ms: ${f}`),null),d;try{if(e.start){let f=e.startTimeoutMs??o;await Jy(e.start(r),f,"worker did not start")}d=await Jy(e.invoke(r,t,o),o)}catch(f){let g=c(GP(f),f instanceof Fp);if(g)return g;continue}let p=Wy.safeParse(d);if(!p.success){let f=c("the worker answered with something other than a hook decision");if(f)return f;continue}if(p.data.decision==="continue"){let f=BP(n,t,p.data.payload);f.ok?t=f.payload:e.log(r,`hook ${n}: change ignored: ${f.reason}`);continue}if(i.result===null)continue;let m=i.result.safeParse(p.data.result);if(!m.success){if(u){let f=c(`malformed result: ${m.error.issues[0]?.message??"invalid"}`);if(f)return f}e.log(r,`hook ${n}: result ignored: return next(event) or { deny: reason }`);continue}return{kind:"deny",pluginKey:r,reason:m.data.deny}}return{kind:"continue",payload:t}}function HP(e){return typeof e!="object"||e===null||!("workspaceId"in e)||"worktreeId"in e?e:{...e,worktreeId:e.workspaceId}}function ti(e,n){return typeof e=="object"&&e!==null&&n in e?Object.getOwnPropertyDescriptor(e,n)?.value:void 0}function za(e,n){return e.add(n),{dispose:()=>{e.delete(n)}}}var WP=new Set(Jr("worker"));function qy(){return{commands:new Map,events:new Map,hooks:new Map,panelRequests:new Map,dataListeners:{storage:new Set,settings:new Set}}}function Yy(e){let{tables:n,hostCall:i}=e,o=async(t,r)=>{await i(t,r)};return Object.freeze({api:Object.freeze({major:Ue,minor:ty}),commands:Object.freeze({register(t,r){return n.commands.set(t,r),{dispose:()=>{n.commands.delete(t)}}}}),events:Object.freeze({on(t,r){let a=ha.get(t)??t,s=n.events.get(a)??new Set;n.events.set(a,s);let u=a!==t||t==="agent.status.changed",c=za(s,p=>r(u?HP(p):p));if(t!=="workspace.changed")return c;let d=!0;return i("workspace.readContext").then(p=>d&&r({context:p})).catch(()=>{}),{dispose:()=>{d=!1,c.dispose()}}}}),hooks:Object.freeze({on(t,r){if(!Oa(t))throw new TypeError(`orca.hooks.on: unknown hook ${String(t)}`);if(typeof r!="function")throw new TypeError("orca.hooks.on needs a handler function");let a=n.hooks.get(t)??new Set;return n.hooks.set(t,a),za(a,r)}}),host:Object.freeze({call:(t,r,a)=>i(t,r,a),has:t=>WP.has(t)}),panels:Object.freeze({handle(t,r){if(vt.parse(t),typeof r!="function")throw new TypeError("orca.panels.handle needs a handler function");return n.panelRequests.set(t,r),{dispose:()=>{n.panelRequests.get(t)===r&&n.panelRequests.delete(t)}}},post(t,r,a){vt.parse(t),e.postToPanels({event:t,payload:r,...a?.panel?{panel:a.panel}:{}})}}),storage:Object.freeze({get:async t=>ti(await i("storage.get",{key:t}),"value")??null,set:(t,r)=>o("storage.set",{key:t,value:r}),delete:t=>o("storage.delete",{key:t}),keys:async()=>{let t=ti(await i("storage.keys"),"keys");return Array.isArray(t)?t.filter(r=>typeof r=="string"):[]},onDidChange:t=>za(n.dataListeners.storage,t)}),settings:Object.freeze({get:async t=>{let r=ti(await i("settings.get"),"settings"),a=typeof r=="object"&&r!==null?{...r}:{};return t===void 0?a:ti(a,t)},set:(t,r)=>o("settings.set",{key:t,value:r}),onDidChange:t=>za(n.dataListeners.settings,t)}),secrets:Object.freeze({get:async t=>{let r=ti(await i("secrets.get",{key:t}),"value");return typeof r=="string"?r:null},set:(t,r)=>o("secrets.set",{key:t,value:r}),delete:t=>o("secrets.delete",{key:t})}),grantedCapabilities:Object.freeze([...e.grantedCapabilities]),log:t=>e.log(String(t))})}var Qy={id:"test-panel",surface:"panel",contributionId:"panel"};function KP(e){return Object.hasOwn(Zp,e)}function JP(e){return e instanceof Error?e.message:String(e)}async function eb(e,n){let{host:i}=n,o=new Set(n.events),t=n.hooks.map(b=>typeof b=="string"?{event:b}:b),r=new Set(t.map(b=>b.event)),a=new Set(t.filter(b=>b.onError==="deny").map(b=>b.event)),s=qy(),u=[],c=[],d=!0,p=e.default;if(typeof p!="function")throw new TypeError(typeof e.activate=="function"?"mountWorker: the module exports activate by name, but Orca calls the default export: write `export default function activate(orca)`":"mountWorker: the module has no default-exported activate(orca) function");let m=e.deactivate;if(m!==void 0&&typeof m!="function")throw new TypeError("mountWorker: the module exports a deactivate that is not a function");let f=Yy({tables:s,hostCall:(b,T)=>i.worker.call(b,T),postToPanels:b=>{c.push(b)},log:b=>{u.push(b)},grantedCapabilities:i.capabilities}),g=i.onDataChange((b,T)=>{for(let A of s.dataListeners[b])A({key:T})});await p(f);let y=n.commands?[...s.commands.keys()].find(b=>!n.commands?.includes(b)):void 0;if(y!==void 0)throw new Error(`registered undeclared command ${y}: list it in contributes.commands`);async function x(b,T,A){let D=s.panelRequests.get(b);if(!D)throw He("unknown_method",`no panel handler named ${b}`);let w={...Qy,contributionId:A??Qy.contributionId};return D(T,{panel:w,signal:new AbortController().signal})}let I=i.routeRequests((b,T)=>x(b,T));return{host:i,logs:u,posts:c,get refusals(){return i.refusals},async emit(b,T){if(!KP(b))throw new Error(`emit: "${b}" is not an event Orca sends to workers`);if(!o.has(b))throw new Error(`emit: "${b}" never arrives: it is not in contributes.events`);let A=Zp[b].safeParse(T);if(!A.success)throw new TypeError(`emit: that ${b} payload is not one Orca sends: ${A.error.issues[0]?.message??""}`);let D=s.events.get(ha.get(b)??b)??[];await Promise.all([...D].map(async w=>w(A.data))),await i.idle()},hooks:{async fire(b,T){if(!Oa(b))throw new Error(`hooks.fire: "${b}" is not a hook Orca runs`);if(!r.has(b))throw new Error(`hooks.fire: "${b}" never runs: it is not in contributes.hooks`);let A=ei[b].payload.safeParse(T);if(!A.success)throw new TypeError(`hooks.fire: that ${b} event is not one Orca sends: ${A.error.issues[0]?.message??""}`);let D=await Xy({event:b,payload:A.data,participants:["worker"],invoke:(w,O)=>Ky(s.hooks.get(b)??[],O),log:(w,O)=>{u.push(O)},now:()=>performance.now(),failsClosed:()=>a.has(b)});return await i.idle(),D.kind==="deny"?{kind:"deny",reason:D.reason}:{kind:"continue",event:D.payload}}},async command(b,T){let A=s.commands.get(b);if(!A)throw new Error(`command: the worker registered no "${b}"`);let D=await A(T,{signal:new AbortController().signal});return await i.idle(),D},async request(b,T,A={}){let D=await x(b,T,A.panel);return await i.idle(),D},async deactivate(){if(d){d=!1,g(),I(),s.events.clear(),s.hooks.clear(),s.commands.clear(),s.panelRequests.clear();try{await(typeof m=="function"?m():void 0)}catch(b){throw new Error(`deactivate threw: ${JP(b)}`,{cause:b})}await i.idle()}}}}import{test as dj,describe as pj,beforeEach as mj,afterEach as fj}from"node:test";import{default as hj}from"node:assert/strict";import{runInThisContext as rT}from"node:vm";function tb(e){if(typeof e!="object"||e===null||!("on"in e)||!("document"in e))return!1;let n=e.document;return typeof e.on=="function"&&typeof n=="object"&&n!==null&&"session"in n&&typeof n.session=="function"&&"applyEdit"in n&&typeof n.applyEdit=="function"}var We=1;var Gp=["invalid_request","invalid_params","unknown_method","capability_denied","consent_required","not_permitted","rate_limited","unavailable","action_failed","cancelled","timeout","conflict","too_large"],XP=new Set(["rate_limited","unavailable","timeout","conflict"]);function qP(e){return Gp.find(n=>n===e)??"action_failed"}function _t(e,n,i){let o=qP(e);return{code:o,message:n.slice(0,8192),retryable:XP.has(o),...i===void 0?{}:{retryAfterMs:i}}}var Vp=l.number().int().nonnegative(),nb=l.string().min(1).max(128),YP=l.object({code:l.string().max(64),message:l.string().max(8192),retryable:l.boolean(),retryAfterMs:l.number().int().nonnegative().optional()}),hL=l.discriminatedUnion("kind",[l.object({orca:l.literal(We),kind:l.literal("req"),id:Vp,method:nb,params:l.unknown().optional()}),l.object({orca:l.literal(We),kind:l.literal("res"),id:Vp,result:l.unknown().optional(),error:YP.optional()}),l.object({orca:l.literal(We),kind:l.literal("evt"),event:nb,payload:l.unknown().optional()}),l.object({orca:l.literal(We),kind:l.literal("cancel"),id:Vp})]);var Gt={ready:"ready",hello:"hello",theme:"theme",ping:"ping",pong:"pong",editorKeydown:"editor.keydown",editorDocument:"editor.document",editorSubscribed:"editor.subscribed",editorMerges:"editor.merges",push:"plugin.push"},Fn={applyEdit:"editor.applyEdit",flush:"editor.flush"},rb=64*1024;var QP=l.string().min(32).max(128),$L=l.object({sessionToken:QP,action:l.string().min(1),params:l.unknown().optional()}).strict();var Bp=64*1024*1024,PL=16*1024*1024;var Hp=1e3,eT=l.strictObject({baseVersion:l.number().int().nonnegative(),content:l.discriminatedUnion("kind",[l.strictObject({kind:l.literal("text"),text:l.string()}),l.strictObject({kind:l.literal("bytes"),bytes:l.instanceof(ArrayBuffer)})])});function ib(e){let n=eT.safeParse(e);return n.success?n.data:null}var Wp=_t("conflict","The file changed outside this editor; edit the latest document."),ob=_t("too_large","The edit is larger than Orca accepts; the file keeps its last accepted edit.");var ab="__orcaPluginFrameTransport",sb="__orcaDeclarativePost";var ub=` var domainEvents = ${JSON.stringify(ht)} var subscribed = new Set() var pluginListeners = new Map() @@ -106,7 +106,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs. var params = { name: String(name) } if (input !== undefined) params.input = input if (typeof o.timeoutMs === 'number' && o.timeoutMs > 0) { - params.timeoutMs = Math.min(Math.ceil(o.timeoutMs), ${da}) + params.timeoutMs = Math.min(Math.ceil(o.timeoutMs), ${va}) } return call('plugin.request', params, { signal: o.signal }).then(pick('output')) }, @@ -136,7 +136,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs. set: function (key, value) { return call('secrets.set', { key: String(key), value: String(value) }).then(done) }, delete: function (key) { return call('secrets.delete', { key: String(key) }).then(done) } }) -`;var Fy=` +`;var cb=` function start() { if (helloSeen && latest) starting.splice(0).forEach(function (resolve) { resolve(latest) }) } @@ -359,7 +359,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs. }) }) } -`;var By="orca-design-tokens";function gP(e,n){let i=ha(e,n!=="parent"),o=JSON.stringify({protocol:Ke,surface:e,api:{major:Ue,minor:va(i)},methods:i,codes:jp,events:Ft,editor:Vn,user:n==="guest"?hp:null,tokensStyleId:By,maxPending:256,deadlineMs:12e4,userDeadlineMs:30*6e4,userMethodList:Object.values(hp),transportGlobal:n==="parent"?null:Zy,declarativePost:n==="declarative-guest"?Vy:null}).replaceAll("<","\\u003c");return`;(function (C) { +`;var lb="orca-design-tokens";function tT(e,n){let i=$a(e,n!=="parent"),o=JSON.stringify({protocol:We,surface:e,api:{major:Ue,minor:ka(i)},methods:i,codes:Gp,events:Gt,editor:Fn,user:n==="guest"?wp:null,tokensStyleId:lb,maxPending:256,deadlineMs:12e4,userDeadlineMs:30*6e4,userMethodList:Object.values(wp),transportGlobal:n==="parent"?null:ab,declarativePost:n==="declarative-guest"?sb:null}).replaceAll("<","\\u003c");return`;(function (C) { var E = C.events var nextId = 0 var pending = new Map() @@ -494,7 +494,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs. }), on: on } -${Gy} if (C.user) { +${ub} if (C.user) { orca.shell = Object.freeze({ openExternal: function (url) { return userCall(C.user.openExternal, { url: String(url) }) } }) @@ -517,7 +517,7 @@ ${Gy} if (C.user) { Object.defineProperty(orca, 'locale', { get: function () { return locale || navigator.language }, enumerable: true }) -${Fy} if (C.surface === 'editor') { +${cb} if (C.surface === 'editor') { window.addEventListener('keydown', function (event) { if (event.repeat || !(event.metaKey || event.ctrlKey)) return post({ kind: 'evt', event: E.editorKeydown, payload: { @@ -535,16 +535,16 @@ ${Fy} if (C.surface === 'editor') { } post({ kind: 'evt', event: E.ready, payload: { protocol: C.protocol } }) })(${o}) -`}function Hy(e,n="panel",i={}){let o=i.declarative?"declarative-guest":"guest",t=i.theme,r=t?.colorScheme==="light"||t?.colorScheme==="dark"?t.colorScheme:"",a=typeof t?.tokens=="string"?t.tokens.replace(/[{}<>]/g,""):"";return` +`}function db(e,n="panel",i={}){let o=i.declarative?"declarative-guest":"guest",t=i.theme,r=t?.colorScheme==="light"||t?.colorScheme==="dark"?t.colorScheme:"",a=typeof t?.tokens=="string"?t.tokens.replace(/[{}<>]/g,""):"";return` - + +${tT(n,o)} -${e}`}var hP=new TextEncoder;function Mp(e,n){return e.length>n?n+1:hP.encode(e).byteLength}function Ky(e,n=Uy,i={}){let o=i.maxObjects??1e4,t=i.maxDepth??100,r=new WeakSet,a=0,s=0,c=d=>{a=Math.min(n+1,a+d)},u=(d,p)=>{if(a>n)return;if(d===null){c(1);return}switch(typeof d){case"undefined":case"boolean":c(1);return;case"number":c(8);return;case"bigint":c(Mp(d.toString(),n-a));return;case"string":c(Mp(d,n-a));return;case"symbol":case"function":a=n+1;return;case"object":break}let m=d;if(r.has(m)){c(8);return}if(r.add(m),s+=1,s>o||p>t){a=n+1;return}if(m instanceof ArrayBuffer){c(m.byteLength);return}if(typeof SharedArrayBuffer<"u"&&m instanceof SharedArrayBuffer){c(m.byteLength);return}if(ArrayBuffer.isView(m)){c(16),u(m.buffer,p+1);return}if(typeof Blob<"u"&&m instanceof Blob){c(m.size);return}if(m instanceof Date){c(8);return}if(m instanceof RegExp){u(m.source,p+1),u(m.flags,p+1);return}if(m instanceof Map){c(8);for(let[f,g]of m)if(c(4),u(f,p+1),u(g,p+1),a>n)return;return}if(m instanceof Set){c(8);for(let f of m)if(c(4),u(f,p+1),a>n)return;return}if(Array.isArray(m)){c(8);for(let f of m)if(c(4),u(f,p+1),a>n)return;return}try{let f=Object.getPrototypeOf(m);if(f!==Object.prototype&&f!==null){a=n+1;return}for(let g of Object.keys(m))if(c(4),c(Mp(g,n-a)),u(m[g],p+1),a>n)return}catch{a=n+1}};return u(e,0),a}var yP=e=>typeof e=="object"&&e!==null,Wy=/