refactor(runtime): owner transports, the creation default and a focus-read ratchet (#27275)

* refactor(runtime): owner transports, the creation default and a focus-read ratchet

Adds runtimeTargetForOwnerEnvironment, runtimeTargetForOwnerHostId and
runtimeTargetForWorkspaceOwner so routed call sites can get a transport from
the resource's owner, and defaultCreationHost as the one sanctioned reader of
the Active Server setting for creation flows with no source row.

The owner-routing ratchet gains a second per-file counter for reads of the
setting (member reads, the focus-routing helpers and defaultCreationHost), so
swapping a helper for a direct read can no longer lower a count. Baseline rows
may carry a trailing note that survives --prune.

* chore(lint): pin defaultCreationHost's own read in the focus-read baseline

* fix(lint): the focus-read ratchet counts shared focus helpers and destructuring reads

* fix(lint): discover functions that read the default host instead of hand-listing them

A hand list let a look-alike such as getAutomationListTarget replace
getActiveRuntimeTarget and lower both ratchets. Every exported top-level
function whose body reads the setting or calls a seed helper now counts as a
reader, and nested destructuring reads count too.

* fix(lint): readers are found through imports and returned values

Readers are keyed by defining file and resolved through imports and
re-exports, so a same-named function elsewhere is not mistaken for one, and
a function that returns a reader's result (a wrapper of a wrapper) is found
too. Components are not readers, and a declaration ends at the next
top-level statement.

* fix(lint): a reader called through a namespace or dynamic import still counts

* fix(lint): renamed, parenthesized and .then() dynamic imports bind readers too

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
OrcaWin
2026-10-10 10:27:37 -07:00
committed by GitHub
co-authored by m4air
parent 5c7c493042
commit 75dfd75460
9 changed files with 1372 additions and 61 deletions
+648
View File
@@ -0,0 +1,648 @@
# Renderer reads of the Active Server setting (member, element and destructuring reads of
# activeRuntimeEnvironmentId) plus calls of exported functions that read it, per file.
# This is a RATCHET: counts may only go DOWN. Only creation flows with no source row may read
# the default host, through defaultCreationHost. Everything else routes by the owner.
# Prune after removing reads: pnpm check:owner-routing-ratchet --prune
2 src/renderer/src/App.tsx
3 src/renderer/src/app-shell/use-app-shell-services.ts
1 src/renderer/src/app-shell/use-visible-review-refresh-reporting.ts
1 src/renderer/src/components/Landing.tsx
1 src/renderer/src/components/LinearIssueWorkspace.tsx
1 src/renderer/src/components/NativeChatResumeOnRestartGroups.tsx
1 src/renderer/src/components/NativeChatResumeOnRestartModal.tsx
2 src/renderer/src/components/QuickOpen.tsx
1 src/renderer/src/components/activity/ActivityPrototypePage.tsx
1 src/renderer/src/components/activity/activity-scope-filter-controls.tsx
3 src/renderer/src/components/activity/activity-thread-actions.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/automation-orca-save.ts
1 src/renderer/src/components/automations/automations-page-fixtures.ts
1 src/renderer/src/components/automations/use-automation-source-availability.ts
1 src/renderer/src/components/automations/use-automation-source-host-availability.ts
2 src/renderer/src/components/automations/use-automations-page-controller.ts
1 src/renderer/src/components/automations/use-automations-page-destination-form.ts
2 src/renderer/src/components/automations/use-automations-page-setup-state.ts
1 src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.tsx
1 src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.tsx
1 src/renderer/src/components/browser-pane/browser-reopen-on-server.ts
1 src/renderer/src/components/browser-pane/stream-remote/remote-browser-page-pane.tsx
1 src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-page-lifecycle.ts
1 src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-page-stream.ts
1 src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-stream-activation.ts
2 src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.ts
1 src/renderer/src/components/browser-pane/workspace-doc/workspace-doc-page-pane.tsx
2 src/renderer/src/components/cmd-j/quick-action-context.ts
1 src/renderer/src/components/editor/EditorAutosaveController.tsx
2 src/renderer/src/components/editor/EditorContent.tsx
1 src/renderer/src/components/editor/EditorDiffFileSurface.tsx
1 src/renderer/src/components/editor/EditorEditFileSurface.tsx
1 src/renderer/src/components/editor/EditorMarkdownFileSurface.tsx
2 src/renderer/src/components/editor/EditorPanel.tsx
4 src/renderer/src/components/editor/ExternalFileChangeBanner.tsx
3 src/renderer/src/components/editor/ExternalFileChangeCompareDialog.tsx
1 src/renderer/src/components/editor/MarkdownPreview.tsx
1 src/renderer/src/components/editor/MonacoGutterContextMenu.tsx
4 src/renderer/src/components/editor/RichMarkdownEditor.tsx
1 src/renderer/src/components/editor/combined-diff/CombinedDiffViewer.tsx
4 src/renderer/src/components/editor/combined-diff/load-sections/fetch-combined-diff-section.ts
1 src/renderer/src/components/editor/combined-diff/load-sections/use-combined-diff-section-loader.ts
2 src/renderer/src/components/editor/csv/CsvPagedViewer.tsx
1 src/renderer/src/components/editor/csv/csv-file-content.ts
1 src/renderer/src/components/editor/csv/csv-link-routing.ts
2 src/renderer/src/components/editor/editor-autosave-controller-test-fixture.ts
7 src/renderer/src/components/editor/editor-dropped-file-open.ts
1 src/renderer/src/components/editor/editor-header-file-rename.ts
1 src/renderer/src/components/editor/editor-model-lifetime-fixture.ts
6 src/renderer/src/components/editor/editor-restored-tab-conflict-scan.ts
1 src/renderer/src/components/editor/editor-save-queue.ts
1 src/renderer/src/components/editor/local-image-src-reader.ts
3 src/renderer/src/components/editor/markdown-document-list-request.ts
2 src/renderer/src/components/editor/markdown-preview-link-actions.ts
1 src/renderer/src/components/editor/markdown-preview-local-images.ts
1 src/renderer/src/components/editor/markdown-preview-system-link-action.ts
1 src/renderer/src/components/editor/restored-editor-workspace-runtime-owner.ts
1 src/renderer/src/components/editor/rich-markdown-editor-config.ts
2 src/renderer/src/components/editor/rich-markdown-image-context.ts
4 src/renderer/src/components/editor/rich-markdown-image-insert.ts
1 src/renderer/src/components/editor/rich-markdown-paste-handler.ts
3 src/renderer/src/components/editor/rich-markdown-paste-image.ts
1 src/renderer/src/components/editor/use-markdown-preview-components.tsx
1 src/renderer/src/components/editor/use-markdown-preview-foundation.ts
1 src/renderer/src/components/editor/use-markdown-preview-source-foundation.ts
4 src/renderer/src/components/editor/useEditorPanelContentState.ts
5 src/renderer/src/components/editor/useEditorPanelDiffContentLoader.ts
7 src/renderer/src/components/editor/useEditorPanelFileContentLoader.ts
1 src/renderer/src/components/editor/useLocalImagePick.ts
5 src/renderer/src/components/editor/useLocalImageSrc.ts
5 src/renderer/src/components/editor/useMarkdownDocuments.ts
1 src/renderer/src/components/editor/useRichMarkdownProgrammaticSync.ts
1 src/renderer/src/components/editor/useRichMarkdownReconcileRoundTrip.ts
2 src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx
1 src/renderer/src/components/feature-wall/FeatureWallBrowserAction.tsx
1 src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx
1 src/renderer/src/components/feature-wall/FeatureWallTourSurface.tsx
2 src/renderer/src/components/feature-wall/use-feature-wall-task-source-presentation.ts
2 src/renderer/src/components/feature-wall/use-integration-connection-status.ts
1 src/renderer/src/components/floating-terminal/use-floating-terminal-panel-controller.ts
1 src/renderer/src/components/floating-terminal/use-floating-terminal-panel-store-state.ts
1 src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx
4 src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts
7 src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx
2 src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx
5 src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx
6 src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-request-actions.ts
1 src/renderer/src/components/github-project/ProjectCellIssueType.tsx
2 src/renderer/src/components/github-project/ProjectCellRepositoryEditors.tsx
2 src/renderer/src/components/github-project/ProjectPicker.tsx
4 src/renderer/src/components/github-project/project-picker-runtime.ts
1 src/renderer/src/components/github-project/slug-dialog/AssigneesEditor.tsx
1 src/renderer/src/components/github-project/slug-dialog/Comments.tsx
1 src/renderer/src/components/github-project/slug-dialog/LabelsEditor.tsx
1 src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx
2 src/renderer/src/components/github-project/useProjectPickerBrowse.ts
2 src/renderer/src/components/github-project/useProjectViewTable.ts
3 src/renderer/src/components/github/PRAssigneesPanel.tsx
2 src/renderer/src/components/github/PRFilterDropdowns.tsx
3 src/renderer/src/components/github/github-rate-limit-display.tsx
5 src/renderer/src/components/github/github-work-item-edit-mutations.ts
3 src/renderer/src/components/gitlab/gitlab-rate-limit-display.tsx
1 src/renderer/src/components/jira-connect-dialog.tsx
1 src/renderer/src/components/landing-preflight-runtime.ts
1 src/renderer/src/components/linear-api-key-dialog.tsx
2 src/renderer/src/components/native-chat-resume-modal.test-support.tsx
1 src/renderer/src/components/native-chat-resume-tree.test-support.ts
2 src/renderer/src/components/native-chat/NativeChatComposer.tsx
1 src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
1 src/renderer/src/components/native-chat/NativeChatTranscriptChrome.tsx
1 src/renderer/src/components/native-chat/NativeChatVisualFrame.tsx
1 src/renderer/src/components/native-chat/claude-model-switch-confirmation.ts
4 src/renderer/src/components/native-chat/native-chat-file-link-existence.ts
1 src/renderer/src/components/native-chat/native-chat-file-link.ts
1 src/renderer/src/components/native-chat/native-chat-http-link-source-owner.ts
1 src/renderer/src/components/native-chat/native-chat-observed-send.ts
2 src/renderer/src/components/native-chat/native-chat-runtime-image-send.ts
2 src/renderer/src/components/native-chat/native-chat-runtime-owner.ts
6 src/renderer/src/components/native-chat/native-chat-runtime-send.ts
2 src/renderer/src/components/native-chat/native-chat-session-option-discovery.ts
2 src/renderer/src/components/native-chat/use-native-chat-file-link-click.ts
1 src/renderer/src/components/native-chat/use-native-chat-file-link-existence.tsx
6 src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts
1 src/renderer/src/components/native-chat/use-native-chat-mention-files.ts
2 src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.ts
4 src/renderer/src/components/native-chat/use-native-chat-pty-composer-send.ts
2 src/renderer/src/components/native-chat/use-native-chat-session-option-command.ts
1 src/renderer/src/components/new-workspace/smart-workspace-repo-slug.ts
1 src/renderer/src/components/new-workspace/use-jira-source-connection.ts
2 src/renderer/src/components/new-workspace/use-linear-source-connection.ts
1 src/renderer/src/components/new-workspace/use-smart-workspace-secondary-searches.ts
2 src/renderer/src/components/new-workspace/use-work-item-source-foundation.ts
1 src/renderer/src/components/new-workspace/use-work-item-source-search.ts
1 src/renderer/src/components/ports/WorkspacePortScanner.tsx
1 src/renderer/src/components/pull-request-page/actions/merge-actions.ts
1 src/renderer/src/components/pull-request-page/actions/panel.tsx
2 src/renderer/src/components/pull-request-page/conversation/tab.tsx
3 src/renderer/src/components/pull-request-page/edit/issue-updates.ts
8 src/renderer/src/components/pull-request-page/edit/section.tsx
4 src/renderer/src/components/pull-request-page/reviewers/panel.tsx
4 src/renderer/src/components/pull-request-page/reviewers/request-actions.ts
2 src/renderer/src/components/quick-open-file-list-test-harness.ts
6 src/renderer/src/components/quick-open-file-list.ts
4 src/renderer/src/components/quick-open-file-navigation.ts
2 src/renderer/src/components/quick-open-recent-validation.ts
3 src/renderer/src/components/repo/CreateFromPicker.tsx
1 src/renderer/src/components/right-sidebar/ChecksPanel.tsx
3 src/renderer/src/components/right-sidebar/FileExplorer.tsx
1 src/renderer/src/components/right-sidebar/FileExplorerFilesTreePane.tsx
1 src/renderer/src/components/right-sidebar/FolderWorkspacePrChecksRow.tsx
1 src/renderer/src/components/right-sidebar/ai-vault-original-pane-actions.ts
1 src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts
3 src/renderer/src/components/right-sidebar/checks-panel-hosted-review-click-routing.ts
1 src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-controller-state.tsx
1 src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-create-review.tsx
2 src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-generation.tsx
3 src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-git-status-effects.tsx
3 src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-manual-refresh.tsx
1 src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-review-state.tsx
2 src/renderer/src/components/right-sidebar/file-explorer-deletion-editors.ts
1 src/renderer/src/components/right-sidebar/file-explorer-directory-listing.ts
4 src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts
1 src/renderer/src/components/right-sidebar/file-explorer-row-context-menu.tsx
1 src/renderer/src/components/right-sidebar/git-status-file-watch-refresh.ts
5 src/renderer/src/components/right-sidebar/git-status-refresh.ts
1 src/renderer/src/components/right-sidebar/push-target-upstream-refresh-cache.ts
2 src/renderer/src/components/right-sidebar/source-control/ai/use-ai.ts
5 src/renderer/src/components/right-sidebar/source-control/commit/use-bulk-actions.ts
1 src/renderer/src/components/right-sidebar/source-control/commit/use-commit-action.ts
2 src/renderer/src/components/right-sidebar/source-control/commit/use-commit-message-generation.ts
1 src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts
7 src/renderer/src/components/right-sidebar/source-control/commit/use-entry-mutations.ts
1 src/renderer/src/components/right-sidebar/source-control/listing/entry-context-menu.tsx
2 src/renderer/src/components/right-sidebar/source-control/listing/use-file-listing.ts
2 src/renderer/src/components/right-sidebar/source-control/listing/use-submodule-status.ts
1 src/renderer/src/components/right-sidebar/source-control/listing/use-worktree-context.ts
1 src/renderer/src/components/right-sidebar/source-control/panel/use-panel-foundation.ts
2 src/renderer/src/components/right-sidebar/source-control/panel/use-panel-model.ts
2 src/renderer/src/components/right-sidebar/source-control/panel/use-panel-state.ts
1 src/renderer/src/components/right-sidebar/source-control/review/create-pr-intent-run-snapshot.ts
1 src/renderer/src/components/right-sidebar/source-control/review/use-create-pr-intent-commit-message.ts
1 src/renderer/src/components/right-sidebar/source-control/review/use-create-pr-intent-probes.ts
1 src/renderer/src/components/right-sidebar/source-control/review/use-create-pr-intent-review.ts
2 src/renderer/src/components/right-sidebar/source-control/review/use-pull-request-generation.ts
1 src/renderer/src/components/right-sidebar/source-control/sync/git-history-commit-context-menu.tsx
1 src/renderer/src/components/right-sidebar/source-control/sync/use-base-ref-default.ts
1 src/renderer/src/components/right-sidebar/source-control/sync/use-branch-compare.ts
2 src/renderer/src/components/right-sidebar/source-control/sync/use-conflict-abort.ts
2 src/renderer/src/components/right-sidebar/source-control/sync/use-git-history-commit-actions.ts
2 src/renderer/src/components/right-sidebar/source-control/sync/use-git-history.ts
1 src/renderer/src/components/right-sidebar/source-control/sync/use-status-refresh.ts
1 src/renderer/src/components/right-sidebar/stale-conflict-operation-poll.ts
2 src/renderer/src/components/right-sidebar/use-create-pull-request-base-ref-discovery.ts
2 src/renderer/src/components/right-sidebar/use-create-pull-request-field-generation.ts
2 src/renderer/src/components/right-sidebar/use-create-pull-request-field-seeding.ts
1 src/renderer/src/components/right-sidebar/use-file-explorer-ignored-paths.ts
1 src/renderer/src/components/right-sidebar/use-file-explorer-name-filter.ts
2 src/renderer/src/components/right-sidebar/use-file-explorer-node-commands.ts
6 src/renderer/src/components/right-sidebar/use-file-explorer-tree-pane-state.ts
3 src/renderer/src/components/right-sidebar/use-git-status-upstream-ref-watch.ts
3 src/renderer/src/components/right-sidebar/useFileDeletion.ts
1 src/renderer/src/components/right-sidebar/useFileDuplicate.ts
2 src/renderer/src/components/right-sidebar/useFileExplorerDragDrop.ts
2 src/renderer/src/components/right-sidebar/useFileExplorerHandlers.ts
4 src/renderer/src/components/right-sidebar/useFileExplorerImport.ts
3 src/renderer/src/components/right-sidebar/useFileExplorerInlineInput.ts
1 src/renderer/src/components/right-sidebar/useFileExplorerMoveDrop.ts
2 src/renderer/src/components/right-sidebar/useFileExplorerTree.ts
2 src/renderer/src/components/right-sidebar/useFileExplorerWatch.ts
4 src/renderer/src/components/right-sidebar/useFileSearchPanel.ts
4 src/renderer/src/components/right-sidebar/useFileSearchRunner.ts
1 src/renderer/src/components/right-sidebar/useFileSearchScope.ts
2 src/renderer/src/components/right-sidebar/useGitStatusPolling.ts
3 src/renderer/src/components/settings/BaseRefPicker.tsx
2 src/renderer/src/components/settings/BrowserPane.tsx
1 src/renderer/src/components/settings/CliSkillRuntimeSetup.tsx
1 src/renderer/src/components/settings/CommitMessageAiPane.tsx
1 src/renderer/src/components/settings/GeneralPane.tsx
1 src/renderer/src/components/settings/IntegrationsPane.tsx
3 src/renderer/src/components/settings/LinearAgentSkillPane.tsx
1 src/renderer/src/components/settings/McpConfigSection.tsx
1 src/renderer/src/components/settings/RepositoryForkSyncSection.tsx
2 src/renderer/src/components/settings/RepositoryGitHubAccountSection.tsx
1 src/renderer/src/components/settings/RepositoryHooksSection.tsx
1 src/renderer/src/components/settings/RepositoryIconPicker.tsx
4 src/renderer/src/components/settings/RuntimeEnvironmentsPane.tsx
1 src/renderer/src/components/settings/Settings.tsx
1 src/renderer/src/components/settings/SettingsSetupGuidePane.tsx
1 src/renderer/src/components/settings/ShortcutsPane.tsx
2 src/renderer/src/components/settings/TasksPane.tsx
1 src/renderer/src/components/settings/WorkspaceDirectorySetting.tsx
1 src/renderer/src/components/settings/bitbucket-credentials-dialog.tsx
1 src/renderer/src/components/settings/bitbucket-integration-card.tsx
3 src/renderer/src/components/settings/cli-source-control-integration-cards.tsx
3 src/renderer/src/components/settings/jira-integration-card.tsx
1 src/renderer/src/components/settings/linear-agent-skill-install-cta.tsx
1 src/renderer/src/components/settings/mcp-config-inspection.ts
2 src/renderer/src/components/settings/provider-account-scope.ts
1 src/renderer/src/components/settings/settings-persistence-test-fixture.ts
2 src/renderer/src/components/settings/settings-setup-guide-progress.ts
1 src/renderer/src/components/settings/source-control-preflight-card-status.ts
2 src/renderer/src/components/settings/task-tracker-integration-cards.tsx
2 src/renderer/src/components/settings/token-source-control-integration-cards.tsx
2 src/renderer/src/components/settings/use-integration-provider-status-refresh.ts
3 src/renderer/src/components/settings/use-repository-issue-command.ts
1 src/renderer/src/components/settings/use-runtime-environment-mutation-actions.ts
2 src/renderer/src/components/settings/use-settings-navigation-model.ts
3 src/renderer/src/components/settings/use-settings-store-model.ts
2 src/renderer/src/components/settings/use-settings-terminal-model.ts
4 src/renderer/src/components/settings/use-task-source-provider-readiness.ts
1 src/renderer/src/components/setup-guide/SetupGuideModal.tsx
1 src/renderer/src/components/setup-guide/SetupGuideTelemetryObserver.tsx
1 src/renderer/src/components/setup-guide/setup-guide-progress-readiness.ts
3 src/renderer/src/components/setup-guide/use-setup-guide-progress.ts
1 src/renderer/src/components/sidebar/DeleteWorktreeDialog.tsx
3 src/renderer/src/components/sidebar/LinearAgentSkillSetupPrompt.tsx
1 src/renderer/src/components/sidebar/SavedHostScopeWidenGate.tsx
1 src/renderer/src/components/sidebar/SetupGuideSidebarEntry.tsx
1 src/renderer/src/components/sidebar/SidebarAgentsList.tsx
1 src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.tsx
1 src/renderer/src/components/sidebar/SidebarTaskNavButton.tsx
1 src/renderer/src/components/sidebar/WorktreeCardAgents.tsx
2 src/renderer/src/components/sidebar/WorktreeList.tsx
1 src/renderer/src/components/sidebar/WorktreeRowOpenInSubMenu.tsx
1 src/renderer/src/components/sidebar/add-repo-runtime-owner.ts
1 src/renderer/src/components/sidebar/linear-agent-skill-runtime.ts
2 src/renderer/src/components/sidebar/rendered-sidebar-worktree-order.ts
2 src/renderer/src/components/sidebar/sidebar-host-options.ts
1 src/renderer/src/components/sidebar/sidebar-project-drop.ts
2 src/renderer/src/components/sidebar/use-add-repo-host-selection.ts
1 src/renderer/src/components/sidebar/use-delete-worktree-status-hydration.ts
1 src/renderer/src/components/sidebar/use-sidebar-host-scope-options.ts
1 src/renderer/src/components/sidebar/use-visible-workspace-kanban-worktree-ids.ts
1 src/renderer/src/components/sidebar/use-workspace-kanban-board-projection.ts
1 src/renderer/src/components/sidebar/use-workspace-reference-details.ts
1 src/renderer/src/components/sidebar/useAddRepoCloneFlow.ts
1 src/renderer/src/components/sidebar/useCreateRepo.ts
2 src/renderer/src/components/sidebar/useSidebarProjectDrop.ts
1 src/renderer/src/components/sidebar/visible-worktree-options-from-state.ts
2 src/renderer/src/components/sidebar/visible-worktrees.ts
1 src/renderer/src/components/sidebar/workspace-options-menu-items.tsx
2 src/renderer/src/components/sidebar/workspace-reference-details.ts
1 src/renderer/src/components/sidebar/workspace-reference-refresh-targets.ts
1 src/renderer/src/components/sidebar/worktree-card-secondary-rows.tsx
1 src/renderer/src/components/sidebar/worktree-filter-visibility.ts
2 src/renderer/src/components/sidebar/worktree-list/listing/use-filters.ts
2 src/renderer/src/components/sidebar/worktree-list/listing/use-folder-path-statuses.ts
1 src/renderer/src/components/sidebar/worktree-list/listing/use-section-rows.ts
1 src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx
1 src/renderer/src/components/skills/SkillsPage.tsx
7 src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx
1 src/renderer/src/components/status-bar/CodexSwitcherMenu.tsx
1 src/renderer/src/components/status-bar/SshStatusSegment.tsx
1 src/renderer/src/components/status-bar/StatusBarSurface.tsx
2 src/renderer/src/components/status-bar/provider-account-sync-key.ts
1 src/renderer/src/components/status-bar/status-bar-claude-accounts.ts
1 src/renderer/src/components/status-bar/status-bar-codex-accounts.ts
1 src/renderer/src/components/status-bar/usage-provider-settings-target.ts
7 src/renderer/src/components/status-bar/use-codex-switcher-controller.ts
1 src/renderer/src/components/status-bar/use-status-bar-controller.ts
1 src/renderer/src/components/status-bar/use-workspace-space-git-refresh-action.ts
1 src/renderer/src/components/status-bar/use-workspace-space-manager-panel.ts
1 src/renderer/src/components/tab-bar/EditorFileTab.tsx
1 src/renderer/src/components/tab-bar/TabBar.tsx
2 src/renderer/src/components/tab-bar/TabBarCreateEntry.tsx
3 src/renderer/src/components/tab-bar/tab-bar-windows-shell-launch-test-harness.ts
2 src/renderer/src/components/tab-bar/tab-create-entry-action.ts
3 src/renderer/src/components/tab-bar/tab-create-entry-local-path.ts
1 src/renderer/src/components/tab-bar/use-tab-bar-runtime-model-worktree-write-probe.ts
4 src/renderer/src/components/tab-bar/use-tab-bar-runtime-model.ts
2 src/renderer/src/components/tab-bar/web-runtime-tab-move-mirror.ts
1 src/renderer/src/components/tab-bar/windows-shell-menu-visibility.ts
1 src/renderer/src/components/tab-group/AiVaultSessionDropLayer.tsx
3 src/renderer/src/components/tab-group/useTabGroupActivationCommands.ts
5 src/renderer/src/components/tab-group/useTabGroupCreationCommands.ts
4 src/renderer/src/components/task-page-github-reviewer-actions.ts
2 src/renderer/src/components/task-page/TaskPage.tsx
2 src/renderer/src/components/task-page/github/AssigneesCell.tsx
2 src/renderer/src/components/task-page/github/MergeCell.tsx
2 src/renderer/src/components/task-page/github/ReviewCell.tsx
1 src/renderer/src/components/task-page/github/StatusCell.tsx
2 src/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.ts
1 src/renderer/src/components/terminal-pane/pty-connection/pane-serializer-settle.ts
1 src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts
1 src/renderer/src/components/terminal-pane/retire-unbound-runtime-terminal-pane.ts
2 src/renderer/src/components/terminal-pane/terminal-drop-runtime-owner.ts
8 src/renderer/src/components/terminal-pane/terminal-file-link-actions.ts
1 src/renderer/src/components/terminal-pane/terminal-file-link-hit-testing.ts
2 src/renderer/src/components/terminal-pane/terminal-file-link-target.ts
6 src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts
3 src/renderer/src/components/terminal-pane/terminal-file-path-mapping.ts
1 src/renderer/src/components/terminal-pane/terminal-fit-restore.ts
2 src/renderer/src/components/terminal-pane/terminal-host-workspace-file.ts
1 src/renderer/src/components/terminal-pane/terminal-input-host-platform.ts
2 src/renderer/src/components/terminal-pane/terminal-link-handlers.ts
1 src/renderer/src/components/terminal-pane/terminal-link-open-hints.ts
1 src/renderer/src/components/terminal-pane/terminal-link-provider-buffer-fixtures.ts
1 src/renderer/src/components/terminal-pane/terminal-native-file-drop.ts
2 src/renderer/src/components/terminal-pane/terminal-pane-close-admission.ts
1 src/renderer/src/components/terminal-pane/terminal-pane-menu-paste.ts
1 src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts
1 src/renderer/src/components/terminal-pane/terminal-pane-pane-links.ts
1 src/renderer/src/components/terminal-pane/terminal-pane-paste-execution.ts
1 src/renderer/src/components/terminal-pane/terminal-pane-paste-listeners.ts
3 src/renderer/src/components/terminal-pane/terminal-path-existence-batch.ts
2 src/renderer/src/components/terminal-pane/terminal-worktree-path-link.ts
1 src/renderer/src/components/terminal-pane/use-terminal-pane-close-actions.ts
1 src/renderer/src/components/terminal-pane/use-terminal-pane-controller.ts
1 src/renderer/src/components/terminal-pane/use-terminal-pane-mount-lifecycle.ts
1 src/renderer/src/components/terminal-pane/use-terminal-pane-paste-listeners.ts
1 src/renderer/src/components/terminal-workspace-model.ts
1 src/renderer/src/components/terminal/pty-running-work-probe.ts
1 src/renderer/src/components/terminal/running-terminal-close-guard.ts
1 src/renderer/src/components/terminal/terminal-tab-create.ts
1 src/renderer/src/components/terminal/window-close-running-work.ts
2 src/renderer/src/components/use-quick-open-interaction.ts
3 src/renderer/src/components/use-task-page-github-issue-draft.ts
2 src/renderer/src/components/use-task-page-source-availability.ts
2 src/renderer/src/components/use-task-page-store-bindings.ts
2 src/renderer/src/components/use-terminal-activation-actions.ts
2 src/renderer/src/components/use-terminal-bulk-close-actions.ts
8 src/renderer/src/components/use-terminal-create-actions.ts
2 src/renderer/src/components/use-worktree-jump-palette-controller.ts
2 src/renderer/src/components/use-worktree-jump-palette-filter.ts
3 src/renderer/src/components/use-worktree-jump-palette-quick-actions.ts
2 src/renderer/src/components/use-worktree-jump-palette-store-state.ts
2 src/renderer/src/hooks/automation-dispatch-handler.ts
1 src/renderer/src/hooks/automation-dispatch-workspace.ts
4 src/renderer/src/hooks/composer-state/attachment-drop-state.ts
1 src/renderer/src/hooks/composer-state/composer-attachment-drop-state.ts
1 src/renderer/src/hooks/composer-state/composer-navigation-actions.ts
3 src/renderer/src/hooks/composer-state/composer-source-state.ts
1 src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts
2 src/renderer/src/hooks/composer-state/composer-target-state.ts
1 src/renderer/src/hooks/composer-state/github-provider-selection.ts
2 src/renderer/src/hooks/composer-state/github-submit-resolution.ts
1 src/renderer/src/hooks/composer-state/gitlab-provider-selection.ts
1 src/renderer/src/hooks/composer-state/provider-runtime-sync.ts
1 src/renderer/src/hooks/composer-state/quick-creation-execution.ts
1 src/renderer/src/hooks/composer-state/workspace-identity-state.ts
1 src/renderer/src/hooks/editor-external-watch-disk-verification.ts
2 src/renderer/src/hooks/editor-external-watch-targets.ts
1 src/renderer/src/hooks/editor-runtime-file-watch.ts
1 src/renderer/src/hooks/ipc-events-close-routing-test-harness.ts
1 src/renderer/src/hooks/ipc-events-terminal-create-test-harness.ts
6 src/renderer/src/hooks/ipc-events-test-harness.ts
2 src/renderer/src/hooks/ipc-events/app-lifetime-ipc-bridge.ts
1 src/renderer/src/hooks/ipc-events/browser-state-ipc-bridge.ts
1 src/renderer/src/hooks/ipc-events/runtime-environment-subscription-selection.ts
1 src/renderer/src/hooks/ipc-events/settings-sidebar-ipc-bridge.ts
2 src/renderer/src/hooks/ipc-events/tab-lifecycle-ipc-bridge.ts
3 src/renderer/src/hooks/use-active-skill-discovery-runtime-target.ts
1 src/renderer/src/hooks/useActiveProjectSkillRuntime.ts
2 src/renderer/src/hooks/useEditorExternalWatch.ts
2 src/renderer/src/hooks/useGitHubRepoMetadata.ts
4 src/renderer/src/hooks/useGitHubSlugMetadata.ts
1 src/renderer/src/hooks/useInstalledAgentSkills.ts
1 src/renderer/src/hooks/useIpcEvents.ts
1 src/renderer/src/hooks/useIssueMetadata.ts
1 src/renderer/src/hooks/useLinearProviderConnected.ts
7 src/renderer/src/hooks/useSettingsNavigationMetadata.ts
1 src/renderer/src/hooks/useUnreadDockBadge.ts
2 src/renderer/src/hooks/useWindowsTerminalCapabilityOwnerKey.ts
10 src/renderer/src/lib/activate-ai-vault-structured-session.ts
1 src/renderer/src/lib/active-agent-note-send.ts
1 src/renderer/src/lib/active-agent-note-target.ts
1 src/renderer/src/lib/agent-background-session-launch-host.ts
1 src/renderer/src/lib/agent-draft-paste-content.ts
1 src/renderer/src/lib/agent-draft-readiness.ts
2 src/renderer/src/lib/agent-followup-delivery.ts
1 src/renderer/src/lib/agent-hibernation-coordinator-test-fixture.ts
3 src/renderer/src/lib/agent-hibernation-coordinator.ts
5 src/renderer/src/lib/agent-paste-draft.ts
1 src/renderer/src/lib/agent-ready-wait.ts
1 src/renderer/src/lib/automation-session-observer.ts
1 src/renderer/src/lib/client-creation-action-error.ts
3 src/renderer/src/lib/client-creation-action-policy.ts
3 src/renderer/src/lib/codex-pane-selection-lane.ts
2 src/renderer/src/lib/codex-session-restart.ts
1 src/renderer/src/lib/default-creation-host.ts # the sanctioned reader of the default host
1 src/renderer/src/lib/doc-preview-grants.ts
3 src/renderer/src/lib/ensure-hooks-confirmed.ts
1 src/renderer/src/lib/execute-open-editor-path-move.ts
1 src/renderer/src/lib/execution-host-facts.ts
6 src/renderer/src/lib/file-preview.ts
1 src/renderer/src/lib/file-search-result-owner.ts
2 src/renderer/src/lib/folder-workspace-runtime-owner.ts
1 src/renderer/src/lib/github-pr-start-point.ts
1 src/renderer/src/lib/github-source-runtime-context.ts
2 src/renderer/src/lib/github-work-item-source-lookup.ts
1 src/renderer/src/lib/host-mirrored-pane-liveness.ts
1 src/renderer/src/lib/hosted-review-repo-read.ts
6 src/renderer/src/lib/http-link-destinations.ts
3 src/renderer/src/lib/http-link-routing.ts
2 src/renderer/src/lib/launch-agent-background-session.ts
1 src/renderer/src/lib/launch-agent-execution-context.ts
2 src/renderer/src/lib/launch-agent-in-new-tab.ts
1 src/renderer/src/lib/launch-agent-web-host-tab.ts
2 src/renderer/src/lib/launch-ai-vault-session.ts
1 src/renderer/src/lib/launch-work-item-direct-preflight.ts
2 src/renderer/src/lib/launch-work-item-direct.ts
1 src/renderer/src/lib/launch-worktree-background-terminals.ts
3 src/renderer/src/lib/local-file-access.ts
2 src/renderer/src/lib/local-preflight-context.ts
1 src/renderer/src/lib/markdown-document-templates.ts
1 src/renderer/src/lib/new-workspace.ts
1 src/renderer/src/lib/open-agent-message-sender.ts
2 src/renderer/src/lib/provider-runtime-context.ts
1 src/renderer/src/lib/remote-workspace-drop-test-fixtures.ts
2 src/renderer/src/lib/rename-file.ts
4 src/renderer/src/lib/repo-runtime-owner.ts
2 src/renderer/src/lib/repo-slug-cache.ts
3 src/renderer/src/lib/repo-slug-index.ts
2 src/renderer/src/lib/resolve-owner.ts
1 src/renderer/src/lib/runtime-session-mirror-owners.ts
1 src/renderer/src/lib/runtime-session-mirror-targets.ts
1 src/renderer/src/lib/single-runtime-legacy-owner.ts
3 src/renderer/src/lib/structured-agent-session-tab-activation.ts
1 src/renderer/src/lib/tab-number-shortcuts.ts
1 src/renderer/src/lib/terminal-tab-owner-activation.ts
4 src/renderer/src/lib/terminal-worktree-route.ts
3 src/renderer/src/lib/unread-badge-count-selector.ts
1 src/renderer/src/lib/user-opened-local-path.ts
2 src/renderer/src/lib/web-runtime-worktree-terminal-after-wake.ts
1 src/renderer/src/lib/workspace-browser-tab-close.ts
4 src/renderer/src/lib/workspace-browser-tab-open.ts
3 src/renderer/src/lib/workspace-file-host-routing.ts
1 src/renderer/src/lib/workspace-tab-palette-activation.ts
1 src/renderer/src/lib/workspace-terminal-host-authority.ts
1 src/renderer/src/lib/worktree-activation-pty-inventory.ts
3 src/renderer/src/lib/worktree-activation.ts
1 src/renderer/src/lib/worktree-creation-agent-seeds.ts
1 src/renderer/src/lib/worktree-creation-flow-execute.ts
1 src/renderer/src/lib/worktree-creation-flow-startup.ts
1 src/renderer/src/lib/worktree-creation-flow.ts
1 src/renderer/src/lib/worktree-creation-hook-preparation.ts
1 src/renderer/src/lib/worktree-operation-route.ts
1 src/renderer/src/lib/worktree-row-open-in-owner.ts
3 src/renderer/src/lib/worktree-runtime-owner.ts
1 src/renderer/src/lib/worktree-sort-order-host-split.ts
1 src/renderer/src/runtime/close-mirrored-editor-tab.ts
3 src/renderer/src/runtime/mobile-markdown-bridge.ts
1 src/renderer/src/runtime/runtime-client-target.ts
2 src/renderer/src/runtime/runtime-file-import-client.ts
3 src/renderer/src/runtime/runtime-file-metadata-client.ts
8 src/renderer/src/runtime/runtime-file-mutation-client.ts
2 src/renderer/src/runtime/runtime-file-range-client.ts
10 src/renderer/src/runtime/runtime-file-read-client.ts
6 src/renderer/src/runtime/runtime-file-routing.ts
5 src/renderer/src/runtime/runtime-file-search-client.ts
1 src/renderer/src/runtime/runtime-file-upload-client.ts
1 src/renderer/src/runtime/runtime-file-watch-client.ts
2 src/renderer/src/runtime/runtime-git-client-context.ts
5 src/renderer/src/runtime/runtime-git-diff-client.ts
5 src/renderer/src/runtime/runtime-git-generation-client.ts
6 src/renderer/src/runtime/runtime-git-status-client.ts
10 src/renderer/src/runtime/runtime-git-sync-client.ts
9 src/renderer/src/runtime/runtime-git-working-tree-client.ts
3 src/renderer/src/runtime/runtime-hooks-client.ts
17 src/renderer/src/runtime/runtime-jira-client.ts
2 src/renderer/src/runtime/runtime-jira-summary-client.ts
1 src/renderer/src/runtime/runtime-jira-target.ts
4 src/renderer/src/runtime/runtime-jira-user-fields-client.ts
8 src/renderer/src/runtime/runtime-linear-client.ts
5 src/renderer/src/runtime/runtime-linear-issue-mutations.ts
12 src/renderer/src/runtime/runtime-linear-project-client.ts
1 src/renderer/src/runtime/runtime-path-existence-batch.ts
3 src/renderer/src/runtime/runtime-repo-client.ts
3 src/renderer/src/runtime/runtime-terminal-inspection.ts
1 src/renderer/src/runtime/runtime-terminal-stream.ts
1 src/renderer/src/runtime/runtime-terminal-verified-input.ts
5 src/renderer/src/runtime/use-runtime-session-mirror-environment-key.ts
2 src/renderer/src/runtime/web-runtime-browser-creation.ts
1 src/renderer/src/runtime/web-runtime-initial-terminal-bootstrap-dispatch.ts
1 src/renderer/src/runtime/web-runtime-session-tab-lifecycle.ts
1 src/renderer/src/runtime/web-runtime-session-tab-move.ts
2 src/renderer/src/runtime/web-runtime-terminal-actions.ts
1 src/renderer/src/runtime/web-runtime-terminal-create-operation.ts
3 src/renderer/src/runtime/web-runtime-terminal-creation.ts
1 src/renderer/src/runtime/web-runtime-worktree-activation.ts
1 src/renderer/src/runtime/web-session-tabs-sync/active-session-subscription.ts
2 src/renderer/src/runtime/web-session-tabs-sync/global-session-subscription.ts
1 src/renderer/src/runtime/web-session-tabs-sync/tracking-decisions.ts
2 src/renderer/src/runtime/web-session-tabs-sync/use-web-session-tabs-sync.ts
2 src/renderer/src/store/folder-workspaces/folder-path-status-actions.ts
2 src/renderer/src/store/folder-workspaces/folder-workspace-catalog-actions.ts
5 src/renderer/src/store/folder-workspaces/folder-workspace-mutations.ts
1 src/renderer/src/store/folder-workspaces/folder-workspace-routing.ts
1 src/renderer/src/store/github/cache-identity.ts
3 src/renderer/src/store/github/project-actions.ts
2 src/renderer/src/store/github/project-field-mutations.ts
2 src/renderer/src/store/github/project-row-actions.ts
1 src/renderer/src/store/github/pull-request-execution.ts
2 src/renderer/src/store/github/refresh-routing-actions.ts
3 src/renderer/src/store/github/repository-routing.ts
2 src/renderer/src/store/github/visible-hosted-review-refresh-targets.ts
2 src/renderer/src/store/github/work-item-aggregate-actions.ts
3 src/renderer/src/store/github/work-item-fetch-actions.ts
1 src/renderer/src/store/github/work-item-mutation-actions.ts
3 src/renderer/src/store/github/work-item-routing.ts
9 src/renderer/src/store/index.ts
6 src/renderer/src/store/project-groups/nested-repository-operations.ts
2 src/renderer/src/store/project-groups/project-group-catalog-actions.ts
7 src/renderer/src/store/project-groups/project-group-mutations.ts
1 src/renderer/src/store/projects/project-host-routing.ts
1 src/renderer/src/store/projects/project-update.ts
2 src/renderer/src/store/repos/all-host-repo-catalog-actions.ts
1 src/renderer/src/store/repos/owner-routing.ts
2 src/renderer/src/store/repos/repo-add-actions.ts
2 src/renderer/src/store/repos/repo-catalog-actions.ts
2 src/renderer/src/store/repos/repo-removal.ts
2 src/renderer/src/store/repos/repo-update.ts
2 src/renderer/src/store/repos/runtime-repo-catalog-actions.ts
2 src/renderer/src/store/repos/safe-auto-fork-sync.ts
1 src/renderer/src/store/slices/browser-slice-test-harness.ts
14 src/renderer/src/store/slices/browser/browser-cookie-import-actions.ts
1 src/renderer/src/store/slices/browser/browser-host-actions.ts
7 src/renderer/src/store/slices/browser/browser-host-state.ts
1 src/renderer/src/store/slices/browser/browser-page-conversion-actions.ts
12 src/renderer/src/store/slices/browser/browser-profile-import-actions.ts
11 src/renderer/src/store/slices/browser/browser-profile-list-actions.ts
6 src/renderer/src/store/slices/browser/browser-tab-actions.ts
1 src/renderer/src/store/slices/browser/create-browser-slice.ts
1 src/renderer/src/store/slices/browser/paired-browser-tab-creator.ts
3 src/renderer/src/store/slices/diff-comment-persistence.ts
2 src/renderer/src/store/slices/editor-slice-test-harness.ts
4 src/renderer/src/store/slices/editor/actions/git-remote-push-pull.ts
1 src/renderer/src/store/slices/editor/actions/git-remote-status.ts
8 src/renderer/src/store/slices/editor/actions/git-remote-sync.ts
2 src/renderer/src/store/slices/editor/actions/markdown-link-action.ts
1 src/renderer/src/store/slices/editor/actions/markdown-preview-actions.ts
1 src/renderer/src/store/slices/editor/actions/open-file-action.ts
1 src/renderer/src/store/slices/editor/actions/open-file-apply.ts
2 src/renderer/src/store/slices/editor/create-editor-slice.ts
1 src/renderer/src/store/slices/editor/tabs/untitled-file-cleanup.ts
1 src/renderer/src/store/slices/github-cache-key.ts
2 src/renderer/src/store/slices/github-slice-test-harness.ts
1 src/renderer/src/store/slices/github.ts
1 src/renderer/src/store/slices/hosted-review-cache-identity.ts
4 src/renderer/src/store/slices/hosted-review.ts
1 src/renderer/src/store/slices/jira-collection-read-actions.ts
13 src/renderer/src/store/slices/jira-connection-actions.ts
4 src/renderer/src/store/slices/jira-issue-read-actions.ts
5 src/renderer/src/store/slices/jira-read-coordination.ts
3 src/renderer/src/store/slices/linear/linear-custom-view-detail-actions.ts
3 src/renderer/src/store/slices/linear/linear-custom-view-issue-actions.ts
3 src/renderer/src/store/slices/linear/linear-custom-view-list-actions.ts
3 src/renderer/src/store/slices/linear/linear-custom-view-project-actions.ts
3 src/renderer/src/store/slices/linear/linear-issue-detail-actions.ts
6 src/renderer/src/store/slices/linear/linear-issue-list-actions.ts
3 src/renderer/src/store/slices/linear/linear-project-detail-actions.ts
3 src/renderer/src/store/slices/linear/linear-project-issue-actions.ts
3 src/renderer/src/store/slices/linear/linear-project-list-actions.ts
13 src/renderer/src/store/slices/linear/linear-slice-connection-actions.ts
4 src/renderer/src/store/slices/linear/linear-slice-scope.ts
3 src/renderer/src/store/slices/linear/linear-slice-status-actions.ts
3 src/renderer/src/store/slices/linear/linear-team-actions.ts
2 src/renderer/src/store/slices/preflight.ts
1 src/renderer/src/store/slices/project-group-owner-routing.ts
1 src/renderer/src/store/slices/repo-host-identity.ts
1 src/renderer/src/store/slices/repo-reorder-host-split.ts
10 src/renderer/src/store/slices/repos.ts
1 src/renderer/src/store/slices/runtime-status.ts
1 src/renderer/src/store/slices/settings-owner-hydration-publication.ts
5 src/renderer/src/store/slices/settings.ts
8 src/renderer/src/store/slices/store-test-helpers.ts
2 src/renderer/src/store/slices/tabs/tabs-host-mirroring.ts
2 src/renderer/src/store/slices/terminal-tab-retirement.ts
1 src/renderer/src/store/slices/worktree-visibility-owner-settings.ts
3 src/renderer/src/store/slices/worktree-visibility-settings-write.ts
3 src/renderer/src/store/slices/worktrees-slice-test-harness.ts
13 src/renderer/src/store/slices/worktrees.ts
2 src/renderer/src/store/slices/worktrees/create/create-worktree.ts
2 src/renderer/src/store/slices/worktrees/create/prefetch-worktree-create-base.ts
2 src/renderer/src/store/slices/worktrees/listing/detected-worktree-provider-request.ts
6 src/renderer/src/store/slices/worktrees/listing/detected-worktree-refresh.ts
4 src/renderer/src/store/slices/worktrees/listing/fetch-all-worktrees.ts
2 src/renderer/src/store/slices/worktrees/listing/fetch-detected-worktrees.ts
5 src/renderer/src/store/slices/worktrees/listing/fetch-worktrees.ts
1 src/renderer/src/store/slices/worktrees/listing/known-ssh-worktree-fetch.ts
1 src/renderer/src/store/slices/worktrees/listing/worktree-host-ownership.ts
2 src/renderer/src/store/slices/worktrees/listing/worktree-owner-settings.ts
2 src/renderer/src/store/slices/worktrees/metadata/hosted-review-push-target-ensure.ts
4 src/renderer/src/store/slices/worktrees/metadata/hosted-review-push-target.ts
4 src/renderer/src/store/slices/worktrees/metadata/update-worktree-meta.ts
3 src/renderer/src/store/slices/worktrees/metadata/worktree-lineage-actions.ts
7 src/renderer/src/store/slices/worktrees/metadata/worktree-lineage-refresh.ts
1 src/renderer/src/store/slices/worktrees/metadata/worktree-meta-persist.ts
2 src/renderer/src/store/slices/worktrees/session/worktree-terminal-reference.ts
1 src/renderer/src/store/slices/worktrees/teardown/force-delete-preserved-branch.ts
1 src/renderer/src/store/slices/worktrees/teardown/missing-worktree-terminal-teardown.ts
2 src/renderer/src/store/slices/worktrees/teardown/remove-worktree.ts
2 src/renderer/src/store/terminals/terminal-active-workspace-creation.ts
1 src/renderer/src/store/terminals/terminal-pane-hibernation.ts
1 src/renderer/src/store/terminals/terminal-shutdown.ts
1 src/renderer/src/store/terminals/terminal-workspace-routing.ts
3 src/renderer/src/web/preload-api/web-preference-normalization.ts
16 src/renderer/src/web/preload-api/web-preferences-store.ts
6 src/renderer/src/web/preload-api/web-settings-api.ts
1 src/renderer/src/web/web-preload-api.ts
+422 -59
View File
@@ -4,33 +4,351 @@ import path from 'node:path'
import process from 'node:process'
import { pathToFileURL } from 'node:url'
// Ratchet for renderer code that routes host-owned work by the "Active Server" focus setting
// instead of by the resource's own host. The three helpers are counted together so renaming one
// into another never lowers the count. Per-file counts may only go down.
// Two ratchets for renderer code that routes host-owned work by the "Active Server" setting
// instead of by the resource's own host. Per-file counts may only go down.
// - Owner routing: the three focus-routing helpers, counted together so renaming one into another
// never lowers the count.
// - Focus reads: every read of the setting plus every call of an exported function that reads it
// for the caller (discovered, not hand-listed), so swapping one form for another never lowers it.
const BASELINE_PATH = 'config/owner-routing-baseline.txt'
const SCAN_ROOT = 'src/renderer/src'
const HELPERS = '(?:getActiveRuntimeTarget|legacyRouteFromSettings|settingsForRuntimeOwner)'
const HELPER_NAMES = 'getActiveRuntimeTarget|legacyRouteFromSettings|settingsForRuntimeOwner'
const HELPERS = `(?:${HELPER_NAMES})`
const IMPORT_EXPORT_LIST = /\b(?:import|export)\s+(?:type\s+)?\{[^}]*\}/g
// Calls and value uses (`.map(helper)`); definitions and type queries are not routing.
const FOCUS_ROUTING_USE = new RegExp(`(?<!(?:function|typeof)\\s+)\\b${HELPERS}\\b`, 'g')
const FOCUS_ROUTING_ALIAS = new RegExp(`\\b${HELPERS}\\s+as\\b`)
// Seed readers; every function that reads the setting or calls a reader joins them by discovery.
const SEED_FOCUS_READERS = [...HELPER_NAMES.split('|'), 'defaultCreationHost']
const FOCUS_SCAN_ROOTS = [SCAN_ROOT, 'src/shared']
/** Imports and re-exports are not uses; an aliased one is reported by {@link hasFocusRoutingAlias}. */
export function countFocusRoutingCalls(sourceText) {
return sourceText.replace(IMPORT_EXPORT_LIST, '').match(FOCUS_ROUTING_USE)?.length ?? 0
}
/** An alias hides later calls from the count, so it is refused outright. */
export function hasFocusRoutingAlias(sourceText) {
return (sourceText.match(IMPORT_EXPORT_LIST) ?? []).some((list) => FOCUS_ROUTING_ALIAS.test(list))
// Element reads after a PascalCase name or `>` are indexed types (`GlobalSettings['…']`), not reads.
const SETTING_MEMBER_READ =
/\??\.\s*activeRuntimeEnvironmentId\b|(?<!(?:\b[A-Z][\w$]*|>)\s*)\[\s*['"]activeRuntimeEnvironmentId['"]\s*\]/g
// `const { activeRuntimeEnvironmentId } = s`, `{ activeRuntimeEnvironmentId: id }: T = s` or nested
// `{ settings: { activeRuntimeEnvironmentId } } = state`; object literals and `({ … }) =>`
// parameters are not reads of the setting.
const SETTING_DESTRUCTURE_READ =
/\{[^{}]*\bactiveRuntimeEnvironmentId\b[^{}]*\}(?:\s*\})*\s*(?::[^=;{}]*)?=(?![=>])/g
// A top-level declaration: `[export] [async] function name` or `[export] const|let name =`.
const TOP_LEVEL_DECLARATION =
/^(export\s+)?(?:default\s+)?(?:async\s+)?(?:function\s*\*?\s*([\w$]+)|(?:const|let)\s+([\w$]+)\b)/gm
// Any new statement at column 0 ends a declaration; closing brackets and continuations do not.
const TOP_LEVEL_BOUNDARY = /^[^\s)}\]]/gm
// Member calls only, so dotted string keys (`'auto.hooks.useX.abc'`) are not uses.
const MEMBER_NAME = /\.\s*([\w$]+)\s*(?:\?\.\s*)?\(/g
function readerUsePattern(names) {
return names.size === 0
? null
: new RegExp(
`(?<!(?:function|typeof|const|let)\\s*\\*?\\s+)\\b(?:${[...names].join('|')})\\b`,
'g'
)
}
function countSettingReads(body) {
return (
(body.match(SETTING_MEMBER_READ)?.length ?? 0) +
(body.match(SETTING_DESTRUCTURE_READ)?.length ?? 0)
)
}
/** Top-level declarations with their source text, up to the next top-level statement. */
export function topLevelDeclarations(sourceText) {
const boundaries = [...sourceText.matchAll(TOP_LEVEL_BOUNDARY)].map((match) => match.index)
return [...sourceText.matchAll(TOP_LEVEL_DECLARATION)].map((match) => {
const end = boundaries.find((index) => index > match.index) ?? sourceText.length
return {
name: match[2] ?? match[3],
exported: Boolean(match[1]),
text: sourceText.slice(match.index, end)
}
})
}
// Statements that hand a value on: `return …` or an arrow's expression body, each running to the
// first newline outside brackets. Object-literal results (`=> ({ … })`, `return { … }`) are
// skipped: slice creators and hooks return bags of actions, which pulls in the whole store.
const VALUE_STATEMENT_START = /\breturn\s+(?!\(?\s*\{)|=>\s*(?!\(?\s*\{)/g
function valueStatements(body) {
const statements = []
for (const match of body.matchAll(VALUE_STATEMENT_START)) {
let depth = 0
let end = match.index + match[0].length
for (; end < body.length; end += 1) {
const char = body[end]
if ('([{'.includes(char)) {
depth += 1
} else if (')]}'.includes(char)) {
if (depth === 0) {
break
}
depth -= 1
} else if (char === '\n' && depth === 0) {
break
}
}
statements.push(body.slice(match.index, end))
}
return statements.join('\n')
}
const IMPORT_STATEMENT =
/\bimport\s+(?:type\s+)?(?:[\w$]+\s*,\s*)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g
const REEXPORT_STATEMENT = /\bexport\s+(?:type\s+)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g
// `const { reader } = (await import('…'))` and `import('…').then(({ reader }) =>` bind like a
// named import; `{ reader: local }` renames like `as`.
const DYNAMIC_IMPORT_DESTRUCTURE =
/\{([^{}]*)\}\s*=\s*\(?\s*await\s+import\(\s*['"]([^'"]+)['"]\s*\)(?:\s*\))?/g
const DYNAMIC_IMPORT_THEN =
/\bimport\(\s*['"]([^'"]+)['"]\s*\)\s*\.then\(\s*(?:async\s*)?\(\s*\{([^{}]*)\}/g
const STAR_REEXPORT_STATEMENT = /\bexport\s+\*\s+from\s*['"]([^'"]+)['"]/g
const LOCAL_EXPORT_LIST = /\bexport\s+\{([^}]*)\}(?!\s*from)/g
function specifiers(list) {
return list
.split(',')
.map((part) => part.trim().replace(/^type\s+/, ''))
.filter(Boolean)
.map((part) => {
const [imported, local = imported] = part.split(/\s+as\s+|\s*:\s*/)
return { imported: imported.trim(), local: local.trim() }
})
}
function resolveModule(spec, fromRel, files) {
let base
if (spec.startsWith('@/')) {
base = `${SCAN_ROOT}/${spec.slice(2)}`
} else if (spec.startsWith('.')) {
base = path.posix.join(path.posix.dirname(fromRel), spec)
} else {
return null
}
base = base.replace(/\.(?:m?js|tsx?)$/, '')
return (
['.ts', '.tsx', '/index.ts', '/index.tsx'].map((ext) => base + ext).find((c) => files.has(c)) ??
null
)
}
function parseModule(rel, text, files) {
const imports = new Map()
for (const [, list, spec] of [
...text.matchAll(IMPORT_STATEMENT),
...text.matchAll(DYNAMIC_IMPORT_DESTRUCTURE),
...[...text.matchAll(DYNAMIC_IMPORT_THEN)].map(([all, from, names]) => [all, names, from])
]) {
const from = resolveModule(spec, rel, files)
for (const { imported, local } of specifiers(list)) {
imports.set(local, { from, name: imported })
}
}
const reexports = new Map()
for (const [, list, spec] of text.matchAll(REEXPORT_STATEMENT)) {
const from = resolveModule(spec, rel, files)
for (const { imported, local } of specifiers(list)) {
reexports.set(local, { from, name: imported })
}
}
const starFrom = [...text.matchAll(STAR_REEXPORT_STATEMENT)].map(([, spec]) =>
resolveModule(spec, rel, files)
)
const listed = new Map()
for (const [, list] of text.matchAll(LOCAL_EXPORT_LIST)) {
for (const { imported, local } of specifiers(list)) {
listed.set(imported, local)
}
}
const declarations = topLevelDeclarations(text.replace(IMPORT_EXPORT_LIST, '')).map((decl) => ({
...decl,
exportedAs: decl.exported ? decl.name : (listed.get(decl.name) ?? null)
}))
return { imports, reexports, starFrom, declarations }
}
/**
* Functions that read the setting for their caller, keyed by defining file so a same-named
* function elsewhere (a test harness's `useAppStore`) is not mistaken for one. Seeds, then every
* exported top-level function whose body reads the setting or calls a seed, then, to a fixpoint,
* every one that returns a reader's result. Hand lists miss look-alikes such as a copy of
* `getActiveRuntimeTarget` under another name; discovery does not. Following every call instead
* of returned values pulls in most of the renderer through the store.
*/
export function discoverFocusReaders(sources) {
const files = new Set(sources.keys())
const modules = new Map([...sources].map(([rel, text]) => [rel, parseModule(rel, text, files)]))
const seeds = new Set(SEED_FOCUS_READERS)
const seedUse = readerUsePattern(seeds)
const readerKeys = new Set()
const resolveExport = (rel, name, depth = 0) => {
const mod = rel ? modules.get(rel) : null
if (!mod || depth > 8) {
return null
}
if (mod.declarations.some((decl) => decl.exportedAs === name)) {
return `${rel}#${name}`
}
const re = mod.reexports.get(name)
if (re) {
return resolveExport(re.from, re.name, depth + 1)
}
for (const from of mod.starFrom) {
const key = resolveExport(from, name, depth + 1)
if (key) {
return key
}
}
return null
}
const resolveLocal = (rel, name) => {
const mod = modules.get(rel)
if (mod.declarations.some((decl) => decl.exportedAs && decl.name === name)) {
return `${rel}#${mod.declarations.find((decl) => decl.name === name).exportedAs}`
}
const imported = mod.imports.get(name)
return imported ? resolveExport(imported.from, imported.name) : null
}
const isReader = (rel, name) => seeds.has(name) || readerKeys.has(resolveLocal(rel, name))
const pending = []
for (const [rel, mod] of modules) {
for (const { name, exportedAs, text } of mod.declarations) {
// Components render; a `<Pane />` that reads focus inside is not a read by its parent.
if (!exportedAs || /^[A-Z]/.test(exportedAs)) {
continue
}
seedUse.lastIndex = 0
if (countSettingReads(text) > 0 || seedUse.test(text)) {
readerKeys.add(`${rel}#${exportedAs}`)
continue
}
const statements = valueStatements(text)
const passed = new Set(statements.match(/[\w$]+/g))
passed.delete(name)
const members = [...statements.matchAll(MEMBER_NAME)].map(([, member]) => member)
pending.push({ rel, key: `${rel}#${exportedAs}`, passed: [...passed], members })
}
}
// Exported reader names, for `ns.reader(…)` through a namespace or dynamic import.
const memberNames = new Set(seeds)
const addReader = (key) => {
readerKeys.add(key)
memberNames.add(key.slice(key.indexOf('#') + 1))
}
for (const key of readerKeys) {
addReader(key)
}
let changed = true
while (changed) {
changed = false
for (let i = pending.length - 1; i >= 0; i -= 1) {
const { rel, key, passed, members } = pending[i]
if (
passed.some((id) => isReader(rel, id)) ||
members.some((member) => memberNames.has(member))
) {
addReader(key)
pending.splice(i, 1)
changed = true
}
}
}
const namesByFile = new Map()
return {
has: (name) => memberNames.has(name),
memberNames,
/** Names that refer to a reader inside `rel`: seeds, its own readers and imported ones. */
namesFor(rel) {
if (!namesByFile.has(rel)) {
const mod = modules.get(rel)
const local = [
...(mod?.declarations.map((decl) => decl.name) ?? []),
...(mod?.imports.keys() ?? [])
]
namesByFile.set(
rel,
new Set([...seeds, ...local.filter((name) => mod && isReader(rel, name))])
)
}
return namesByFile.get(rel)
}
}
}
/**
* Reads of the setting (member, element and destructuring reads) plus every use of a name that
* refers to a reader. Object-literal keys are writes and are not counted.
*/
export function countFocusSettingReads(
sourceText,
readerNames = new Set(SEED_FOCUS_READERS),
memberNames = new Set()
) {
const body = sourceText
.replace(IMPORT_EXPORT_LIST, '')
// A placeholder, so a dangling `const` does not read as a declaration of the next call.
.replace(DYNAMIC_IMPORT_DESTRUCTURE, 'dynamicImport')
.replace(DYNAMIC_IMPORT_THEN, 'dynamicImport')
const pattern = readerUsePattern(readerNames)
// `ns.reader(…)` through a namespace or dynamic import; names already counted bare are skipped.
const members = [...body.matchAll(MEMBER_NAME)].filter(
([, member]) => memberNames.has(member) && !readerNames.has(member)
).length
return countSettingReads(body) + (pattern ? (body.match(pattern)?.length ?? 0) : 0) + members
}
/** An alias hides later calls from the name-based helper count, so it is refused outright. */
export function hasFocusRoutingAlias(sourceText) {
const alias = new RegExp(`\\b(?:${SEED_FOCUS_READERS.join('|')})\\s+as\\b`)
return (sourceText.match(IMPORT_EXPORT_LIST) ?? []).some((list) => alias.test(list))
}
export const RATCHETS = [
{
name: 'owner-routing',
baselinePath: 'config/owner-routing-baseline.txt',
count: (text) => countFocusRoutingCalls(text),
header: [
'# Renderer call sites that route by the Active Server focus setting:',
'# getActiveRuntimeTarget( + legacyRouteFromSettings( + settingsForRuntimeOwner(, per file.',
'# This is a RATCHET: counts may only go DOWN. Route new work by the resource owner instead.',
'# Prune after removing sites: pnpm check:owner-routing-ratchet --prune'
],
advice:
"Route by the resource's owner (resolveOwner + callHostRoute) instead of the Active Server setting."
},
{
name: 'focus-setting-read',
baselinePath: 'config/focus-setting-read-baseline.txt',
count: (text, rel, readers) =>
countFocusSettingReads(text, readers.namesFor(rel), readers.memberNames),
header: [
'# Renderer reads of the Active Server setting (member, element and destructuring reads of',
'# activeRuntimeEnvironmentId) plus calls of exported functions that read it, per file.',
'# This is a RATCHET: counts may only go DOWN. Only creation flows with no source row may read',
'# the default host, through defaultCreationHost. Everything else routes by the owner.',
'# Prune after removing reads: pnpm check:owner-routing-ratchet --prune'
],
advice:
"Route by the resource's owner. Only a creation flow with no source row may use defaultCreationHost."
}
]
export function isScannedPath(rel) {
return /\.(ts|tsx)$/.test(rel) && !/\.(test|spec)\.tsx?$/.test(rel)
}
/** `<count> <path>` lines; `#` comments and blanks ignored. */
/** `<count> <path> [# note]` lines; `#` comment lines and blanks ignored. */
export function parseBaseline(text) {
const counts = new Map()
for (const raw of text.split('\n')) {
@@ -44,19 +362,26 @@ export function parseBaseline(text) {
return counts
}
export function formatBaseline(counts) {
const header = [
'# Renderer call sites that route by the Active Server focus setting:',
'# getActiveRuntimeTarget( + legacyRouteFromSettings( + settingsForRuntimeOwner(, per file.',
'# This is a RATCHET: counts may only go DOWN. Route new work by the resource owner instead.',
'# Prune after removing sites: pnpm check:owner-routing-ratchet --prune',
''
].join('\n')
/** Trailing `# note` per row, so a pruned baseline keeps why an entry is still allowed. */
export function parseBaselineNotes(text) {
const notes = new Map()
for (const raw of text.split('\n')) {
const match = /^\s*\d+\s+(\S+)\s+#\s*(.+?)\s*$/.exec(raw)
if (match) {
notes.set(match[1], match[2])
}
}
return notes
}
export function formatBaseline(counts, header = RATCHETS[0].header, notes = new Map()) {
const rows = [...counts]
.filter(([, count]) => count > 0)
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([file, count]) => `${count} ${file}`)
return `${header}${rows.join('\n')}\n`
.map(([file, count]) =>
notes.has(file) ? `${count} ${file} # ${notes.get(file)}` : `${count} ${file}`
)
return `${[...header, ''].join('\n')}${rows.join('\n')}${rows.length > 0 ? '\n' : ''}`
}
/** `grown`: above baseline (fails). `shrunk`: below baseline, must be pruned so it cannot regrow. */
@@ -76,82 +401,120 @@ export function diffCounts(current, baseline) {
return { grown: grown.sort(byFile), shrunk: shrunk.sort(byFile) }
}
export function collectCurrentCounts(root = process.cwd()) {
const tracked = execFileSync('git', ['ls-files', SCAN_ROOT], {
function readTrackedSources(root, scanRoot) {
const tracked = execFileSync('git', ['ls-files', scanRoot], {
cwd: root,
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024
})
.split('\n')
.filter((rel) => rel && isScannedPath(rel))
const sources = new Map()
for (const rel of tracked) {
try {
sources.set(rel, fs.readFileSync(path.join(root, rel), 'utf8'))
} catch {
// Deleted in the working tree but still tracked.
}
}
return sources
}
// One read and one discovery per root per run; `--prune` then `main` reuse them.
const scanCache = new Map()
function scanSources(root) {
if (!scanCache.has(root)) {
const sources = new Map(
FOCUS_SCAN_ROOTS.flatMap((scanRoot) => [...readTrackedSources(root, scanRoot)])
)
scanCache.set(root, { sources, readers: discoverFocusReaders(sources) })
}
return scanCache.get(root)
}
export function collectCurrentCounts(root = process.cwd(), count = RATCHETS[0].count) {
const { sources, readers } = scanSources(root)
const counts = new Map()
const aliased = []
for (const rel of tracked) {
let source
try {
source = fs.readFileSync(path.join(root, rel), 'utf8')
} catch {
for (const [rel, source] of sources) {
if (!rel.startsWith(`${SCAN_ROOT}/`)) {
continue
}
const count = countFocusRoutingCalls(source)
if (count > 0) {
counts.set(rel, count)
const found = count(source, rel, readers)
if (found > 0) {
counts.set(rel, found)
}
if (hasFocusRoutingAlias(source)) {
aliased.push(rel)
}
}
return { counts, aliased }
return { counts, aliased, readers }
}
function total(counts) {
return [...counts.values()].reduce((sum, count) => sum + count, 0)
}
export function main(root = process.cwd()) {
const baselineFile = path.join(root, BASELINE_PATH)
function checkRatchet(root, ratchet) {
const baselineFile = path.join(root, ratchet.baselinePath)
if (!fs.existsSync(baselineFile)) {
console.error(`::error::Missing ${BASELINE_PATH}.`)
return 1
console.error(`::error::Missing ${ratchet.baselinePath}.`)
return { ok: false, total: 0 }
}
const baseline = parseBaseline(fs.readFileSync(baselineFile, 'utf8'))
const { counts: current, aliased } = collectCurrentCounts(root)
const { counts: current } = collectCurrentCounts(root, ratchet.count)
const { grown, shrunk } = diffCounts(current, baseline)
for (const { file, now, allowed } of grown) {
console.error(
`::error file=${file}::${ratchet.name}: ${now} use(s), baseline allows ${allowed}. ${ratchet.advice}`
)
}
for (const { file, now, allowed } of shrunk) {
console.error(
`::error file=${file}::${ratchet.name}: ${now} use(s), baseline still allows ${allowed}. Run: pnpm check:owner-routing-ratchet --prune`
)
}
return { ok: grown.length === 0 && shrunk.length === 0, total: total(current) }
}
export function main(root = process.cwd()) {
const { aliased } = collectCurrentCounts(root)
for (const file of aliased) {
console.error(
`::error file=${file}::A focus-routing helper is imported or exported under another name, which hides its calls from this ratchet. Use the original name.`
)
}
for (const { file, now, allowed } of grown) {
console.error(
`::error file=${file}::${now} focus-routed call(s), baseline allows ${allowed}. Route by the resource's owner (resolveOwner + callHostRoute) instead of the Active Server setting.`
let ok = aliased.length === 0
for (const ratchet of RATCHETS) {
const result = checkRatchet(root, ratchet)
ok = ok && result.ok
if (result.ok) {
console.log(`${ratchet.name} ratchet OK — ${result.total} use(s).`)
}
}
return ok ? 0 : 1
}
export function prune(root = process.cwd()) {
for (const ratchet of RATCHETS) {
const baselineFile = path.join(root, ratchet.baselinePath)
const text = fs.existsSync(baselineFile) ? fs.readFileSync(baselineFile, 'utf8') : ''
const baseline = parseBaseline(text)
const { counts: current } = collectCurrentCounts(root, ratchet.count)
// Lowers entries only; growth still has to be fixed in the code.
const pruned = new Map(
[...baseline].map(([file, allowed]) => [file, Math.min(allowed, current.get(file) ?? 0)])
)
fs.writeFileSync(baselineFile, formatBaseline(pruned, ratchet.header, parseBaselineNotes(text)))
console.log(`Pruned ${ratchet.baselinePath} to ${total(pruned)} use(s).`)
}
for (const { file, now, allowed } of shrunk) {
console.error(
`::error file=${file}::${now} focus-routed call(s), baseline still allows ${allowed}. Run: pnpm check:owner-routing-ratchet --prune`
)
}
if (aliased.length > 0 || grown.length > 0 || shrunk.length > 0) {
return 1
}
console.log(`Owner-routing ratchet OK — ${total(current)} focus-routed call site(s).`)
return 0
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const root = process.cwd()
if (process.argv[2] === '--prune') {
const baselineFile = path.join(root, BASELINE_PATH)
const baseline = parseBaseline(fs.readFileSync(baselineFile, 'utf8'))
const { counts: current } = collectCurrentCounts(root)
// Lowers entries only; growth still has to be fixed in the code.
const pruned = new Map(
[...baseline].map(([file, allowed]) => [file, Math.min(allowed, current.get(file) ?? 0)])
)
fs.writeFileSync(baselineFile, formatBaseline(pruned))
console.log(`Pruned ${BASELINE_PATH} to ${total(pruned)} call site(s).`)
process.exit(main(root))
prune(root)
}
process.exit(main(root))
}
@@ -2,11 +2,14 @@ import { describe, expect, it } from 'vitest'
import {
countFocusRoutingCalls,
countFocusSettingReads,
diffCounts,
discoverFocusReaders,
formatBaseline,
hasFocusRoutingAlias,
isScannedPath,
parseBaseline
parseBaseline,
parseBaselineNotes
} from './check-owner-routing-ratchet.mjs'
describe('countFocusRoutingCalls', () => {
@@ -39,6 +42,172 @@ describe('countFocusRoutingCalls', () => {
})
})
describe('countFocusSettingReads', () => {
it('counts setting reads, the helpers and the creation default together', () => {
const src = [
"import { defaultCreationHost } from './default-creation-host'",
'const a = settings?.activeRuntimeEnvironmentId',
'const b = state.settings.activeRuntimeEnvironmentId',
"const c = settings['activeRuntimeEnvironmentId']",
'const d = getActiveRuntimeTarget(settings)',
'const e = defaultCreationHost(settings)',
'export function defaultCreationHost(settings) {}'
].join('\n')
expect(countFocusSettingReads(src)).toBe(5)
})
it('discovers readers through imports, so a look-alike or a wrapper lowers nothing', () => {
const readers = discoverFocusReaders(
new Map([
[
'src/shared/execution-host.ts',
'export function getSettingsFocusedExecutionHostId(settings) {\n return settings?.activeRuntimeEnvironmentId\n}\n'
],
[
'src/renderer/src/a.ts',
[
"import { getSettingsFocusedExecutionHostId } from '../../shared/execution-host'",
'export function getAutomationListTarget(settings) {',
' const id = settings?.activeRuntimeEnvironmentId?.trim()',
" return id ? { kind: 'environment', environmentId: id } : { kind: 'local' }",
'}',
'export const cacheKey = (s) => `k:${getActiveRuntimeTarget(s).kind}`',
'export function browserHostId(state, override) {',
' return override ?? getSettingsFocusedExecutionHostId(state.settings)',
'}',
'export function wrappedTwice(state) {',
' return browserHostId(state, null)',
'}',
'export function ownerTarget(row) {',
' return row.owner',
'}',
'export function SettingsPane() {',
' return getAutomationListTarget(null)',
'}'
].join('\n')
],
[
'src/renderer/src/harness.ts',
'export const ownerTarget = (s) => s.activeRuntimeEnvironmentId'
],
[
'src/renderer/src/b.ts',
[
"import { browserHostId, wrappedTwice, ownerTarget, SettingsPane } from './a'",
'const a = wrappedTwice(state)',
'const b = ownerTarget(row)',
'const c = <SettingsPane />'
].join('\n')
]
])
)
const inA = readers.namesFor('src/renderer/src/a.ts')
for (const name of ['getAutomationListTarget', 'cacheKey', 'browserHostId', 'wrappedTwice']) {
expect(inA.has(name)).toBe(true)
}
expect(inA.has('ownerTarget')).toBe(false)
expect(inA.has('SettingsPane')).toBe(false)
// `ownerTarget` from `./a` is not the harness's same-named reader.
expect(
[...readers.namesFor('src/renderer/src/b.ts')].filter((n) => !n.startsWith('get'))
).toEqual(expect.arrayContaining(['browserHostId', 'wrappedTwice']))
expect(readers.namesFor('src/renderer/src/b.ts').has('ownerTarget')).toBe(false)
expect(
countFocusSettingReads(
'const t = runtimeTargetForOwnerHostId(getSettingsFocusedExecutionHostId(s))',
readers.namesFor('src/renderer/src/a.ts')
)
).toBe(1)
expect(
countFocusSettingReads(
"import { wrappedTwice } from './a'\nconst t = wrappedTwice(state)",
readers.namesFor('src/renderer/src/b.ts')
)
).toBe(1)
expect(countFocusSettingReads('export const cacheKey = (s) => s', inA)).toBe(0)
})
it('counts a reader called through a namespace or dynamic import', () => {
const readers = discoverFocusReaders(
new Map([
[
'src/renderer/src/routing.ts',
'export function focusedHostId(s) {\n return s.activeRuntimeEnvironmentId\n}\n'
],
[
'src/renderer/src/wrap.ts',
"import * as rr from './routing'\nexport function hostOf(s) {\n return rr.focusedHostId(s)\n}\n"
],
[
'src/renderer/src/use.ts',
"const { focusedHostId } = await import('./routing')\nconst a = focusedHostId(s)\n"
]
])
)
expect(readers.has('hostOf')).toBe(true)
expect(
countFocusSettingReads(
"import * as rr from './routing'\nconst a = rr.focusedHostId(s)\nt('auto.rr.focusedHostId.x')",
readers.namesFor('src/renderer/src/other.ts'),
readers.memberNames
)
).toBe(1)
expect(
countFocusSettingReads(
"const { focusedHostId } = await import('./routing')\nconst a = focusedHostId(s)\n",
readers.namesFor('src/renderer/src/use.ts'),
readers.memberNames
)
).toBe(1)
for (const [rel, text] of [
['src/renderer/src/r1.ts', "const { focusedHostId: g } = await import('./routing')\ng(s)"],
[
'src/renderer/src/r2.ts',
"const { focusedHostId } = (await import('./routing'))\nfocusedHostId(s)"
],
[
'src/renderer/src/r3.ts',
"void import('./routing').then(({ focusedHostId }) => focusedHostId(s))"
]
]) {
const withFile = discoverFocusReaders(
new Map([
[
'src/renderer/src/routing.ts',
'export function focusedHostId(s) {\n return s.activeRuntimeEnvironmentId\n}\n'
],
[rel, text]
])
)
expect(countFocusSettingReads(text, withFile.namesFor(rel), withFile.memberNames)).toBe(1)
}
})
it('counts destructuring reads', () => {
expect(countFocusSettingReads('const { activeRuntimeEnvironmentId } = s')).toBe(1)
expect(
countFocusSettingReads('const { settings: { activeRuntimeEnvironmentId } } = state')
).toBe(1)
expect(
countFocusSettingReads(
"const { theme, activeRuntimeEnvironmentId: id }: Pick<GlobalSettings, 'theme'> = s"
)
).toBe(1)
})
it('does not count writes, keys, props or type positions', () => {
const src = [
'const owner = { activeRuntimeEnvironmentId: id }',
'settings = { ...settings, activeRuntimeEnvironmentId: null }',
"type T = GlobalSettings['activeRuntimeEnvironmentId']",
'updateSettings({ activeRuntimeEnvironmentId: null })',
'const f = ({ activeRuntimeEnvironmentId }) => activeRuntimeEnvironmentId',
'if (a === b) { x = { activeRuntimeEnvironmentId } }'
].join('\n')
expect(countFocusSettingReads(src)).toBe(0)
})
})
describe('hasFocusRoutingAlias', () => {
it('refuses an aliased import or re-export, which would hide its calls', () => {
expect(hasFocusRoutingAlias("import { getActiveRuntimeTarget as route } from './rpc'")).toBe(
@@ -47,6 +216,7 @@ describe('hasFocusRoutingAlias', () => {
expect(
hasFocusRoutingAlias("export {\n settingsForRuntimeOwner as owner\n} from './target'")
).toBe(true)
expect(hasFocusRoutingAlias("import { defaultCreationHost as host } from './d'")).toBe(true)
expect(hasFocusRoutingAlias("import { getActiveRuntimeTarget } from './rpc'")).toBe(false)
})
})
@@ -75,6 +245,17 @@ describe('baseline', () => {
)
})
it('keeps a row note through a prune', () => {
const text = formatBaseline(
new Map([['src/a.ts', 1]]),
['# header'],
new Map([['src/a.ts', 'waits for V4b']])
)
expect(text).toBe('# header\n1 src/a.ts # waits for V4b\n')
expect(parseBaseline(text)).toEqual(new Map([['src/a.ts', 1]]))
expect(parseBaselineNotes(text)).toEqual(new Map([['src/a.ts', 'waits for V4b']]))
})
it('fails growth, including a new file, and asks to prune shrinkage', () => {
const { grown, shrunk } = diffCounts(
new Map([
@@ -0,0 +1,13 @@
import { describe, expect, it } from 'vitest'
import { defaultCreationHost } from './default-creation-host'
describe('defaultCreationHost', () => {
it('is this computer unless a server is chosen', () => {
expect(defaultCreationHost(null)).toEqual({ kind: 'local' })
expect(defaultCreationHost({ activeRuntimeEnvironmentId: null })).toEqual({ kind: 'local' })
expect(defaultCreationHost({ activeRuntimeEnvironmentId: ' env-a ' })).toEqual({
kind: 'environment',
environmentId: 'env-a'
})
})
})
@@ -0,0 +1,13 @@
import type { GlobalSettings } from '../../../shared/global-settings-types'
import type { RuntimeClientTarget } from '@/runtime/runtime-client-target'
/**
* The "Default host for new projects" setting. Only a creation flow with no source row reads it;
* everything else routes by the resource's owner.
*/
export function defaultCreationHost(
settings: Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined
): RuntimeClientTarget {
const environmentId = settings?.activeRuntimeEnvironmentId?.trim()
return environmentId ? { kind: 'environment', environmentId } : { kind: 'local' }
}
@@ -7,6 +7,7 @@ import {
resetLegacyFocusFallbackCountForTest,
resolveOwner,
resolveOwnerWithLegacyFocus,
runtimeTargetForWorkspaceOwner,
type WorkspaceOwnerRef
} from './resolve-owner'
import type {
@@ -241,3 +242,33 @@ describe('resolveOwner characterization', () => {
})
})
})
describe('runtimeTargetForWorkspaceOwner', () => {
const settings = { activeRuntimeEnvironmentId: 'env-other' }
it('dials the owner, not the focused server', () => {
const rows: WorktreeOperationRouteState = {
settings,
repos: [{ id: 'repo-1', executionHostId: 'runtime:env-a' }],
worktreesByRepo: { 'repo-1': [worktree('runtime:env-a', 'env-a')] }
}
expect(runtimeTargetForWorkspaceOwner(rows, { workspaceId: WORKTREE_ID })).toEqual({
kind: 'environment',
environmentId: 'env-a'
})
})
it('rides this app for local and direct SSH owners', () => {
const sshRows: WorktreeOperationRouteState = {
settings,
repos: [{ id: 'repo-1', connectionId: 'box' }],
worktreesByRepo: { 'repo-1': [worktree('ssh:box')] }
}
expect(runtimeTargetForWorkspaceOwner(sshRows, { workspaceId: WORKTREE_ID })).toEqual({
kind: 'local'
})
})
it('has no transport when no row names the workspace', () => {
expect(runtimeTargetForWorkspaceOwner({ settings }, { workspaceId: WORKTREE_ID })).toBeNull()
})
})
+10
View File
@@ -26,6 +26,7 @@ import {
} from './worktree-operation-route'
import { getNestedSshTargetIdForFolderWorkspace } from './folder-workspace-runtime-owner'
import { parseWorkspaceKey } from '../../../shared/workspace-scope'
import { hostRouteForAuthority, type RuntimeClientTarget } from '@/runtime/runtime-client-target'
/** A worktree id, a `folder:` workspace key, or the floating workspace; `hostId` when the row names one. */
export type WorkspaceOwnerRef = { workspaceId: string; hostId?: ExecutionHostId }
@@ -105,6 +106,15 @@ export function resolveOwner(
return resolveRoute({ ...state, settings: null }, ref)
}
/** Transport to the workspace's owner, or `null` when the rows name none or disagree. */
export function runtimeTargetForWorkspaceOwner(
state: WorktreeOperationRouteState,
ref: WorkspaceOwnerRef
): RuntimeClientTarget | null {
const match = resolveOwner(state, ref)
return match.kind === 'resolved' ? hostRouteForAuthority(match.owner).target : null
}
let legacyFocusFallbackCount = 0
/** How many answers focus has changed since start; a slice drops its fallback once this stays 0. */
@@ -1,6 +1,11 @@
import { describe, expect, it } from 'vitest'
import { UNRESOLVED_OWNER_HOST_ID } from '../../../shared/execution-host'
import { hostRouteForAuthority, runtimeTargetForExecutionHostId } from './runtime-client-target'
import {
hostRouteForAuthority,
runtimeTargetForExecutionHostId,
runtimeTargetForOwnerEnvironment,
runtimeTargetForOwnerHostId
} from './runtime-client-target'
describe('runtimeTargetForExecutionHostId', () => {
it('keeps the answers for plain host ids', () => {
@@ -35,3 +40,25 @@ describe('hostRouteForAuthority', () => {
})
})
})
describe('owner transports', () => {
it('maps an owner environment id, with null as this app', () => {
expect(runtimeTargetForOwnerEnvironment(null)).toEqual({ kind: 'local' })
expect(runtimeTargetForOwnerEnvironment(' ')).toEqual({ kind: 'local' })
expect(runtimeTargetForOwnerEnvironment(' env-a ')).toEqual({
kind: 'environment',
environmentId: 'env-a'
})
})
it('routes local and direct SSH owners through this app and never dials the sentinel', () => {
expect(runtimeTargetForOwnerHostId('local')).toEqual({ kind: 'local' })
expect(runtimeTargetForOwnerHostId('ssh:box')).toEqual({ kind: 'local' })
expect(runtimeTargetForOwnerHostId('runtime:env%2Fa')).toEqual({
kind: 'environment',
environmentId: 'env/a'
})
expect(runtimeTargetForOwnerHostId(UNRESOLVED_OWNER_HOST_ID)).toBeNull()
expect(runtimeTargetForOwnerHostId(undefined)).toBeNull()
})
})
@@ -1,4 +1,5 @@
import type { GlobalSettings } from '../../../shared/global-settings-types'
import { parseRoutableExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host'
import {
parseHostAuthorityKey,
type HostAuthority,
@@ -14,6 +15,30 @@ export function getActiveRuntimeTarget(
return environmentId ? { kind: 'environment', environmentId } : { kind: 'local' }
}
/** Transport to an owner's runtime environment; `null` is this app (local or direct SSH). */
export function runtimeTargetForOwnerEnvironment(
environmentId: string | null
): RuntimeClientTarget {
const ownerId = environmentId?.trim()
return ownerId ? { kind: 'environment', environmentId: ownerId } : { kind: 'local' }
}
/**
* Transport to the host that owns a row: a server's own runtime, or this app for local and direct
* SSH (which ride this app's IPC with a `connectionId`). `null` when the id names no routable host.
*/
export function runtimeTargetForOwnerHostId(
hostId: ExecutionHostId | null | undefined
): RuntimeClientTarget | null {
const parsed = parseRoutableExecutionHostId(hostId)
if (!parsed) {
return null
}
return parsed.kind === 'runtime'
? { kind: 'environment', environmentId: parsed.environmentId }
: { kind: 'local' }
}
/**
* RPC target for a dispatchable host; direct SSH and the unresolved-owner sentinel have none. A
* nested authority key routes to the server that owns the SSH target.