diff --git a/config/focus-setting-read-baseline.txt b/config/focus-setting-read-baseline.txt index 796e601cdc0..e45e3582fd7 100644 --- a/config/focus-setting-read-baseline.txt +++ b/config/focus-setting-read-baseline.txt @@ -4,9 +4,12 @@ # 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 +5 src/renderer/src/app-shell/use-app-shell-services.ts +9 src/renderer/src/components/JiraIssueWorkspace.tsx 1 src/renderer/src/components/Landing.tsx -1 src/renderer/src/components/LinearIssueWorkspace.tsx +2 src/renderer/src/components/LinearIssueTextEditor.tsx +3 src/renderer/src/components/LinearIssueWorkspace.tsx +3 src/renderer/src/components/LinearItemDrawer.tsx 1 src/renderer/src/components/NativeChatResumeOnRestartGroups.tsx 1 src/renderer/src/components/activity/ActivityPrototypePage.tsx 1 src/renderer/src/components/activity/activity-scope-filter-controls.tsx @@ -22,15 +25,27 @@ 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-egress-indicator.tsx +1 src/renderer/src/components/browser-pane/assemble-chrome/browser-page-viewport-overlays.tsx 1 src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.tsx +1 src/renderer/src/components/browser-pane/assemble-chrome/ssh-routed-browser-page-gate.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/use-ssh-workspace-browser-route.ts +1 src/renderer/src/components/browser-pane/workspace-doc/HtmlDocPreview.tsx 1 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 +2 src/renderer/src/components/dashboard/dashboard-card-terminal-input.ts +1 src/renderer/src/components/dashboard/launch-dashboard-agent.ts +1 src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts +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 1 src/renderer/src/components/editor/MonacoGutterContextMenu.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 @@ -41,10 +56,11 @@ 1 src/renderer/src/components/editor/markdown-preview-link-actions.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 +2 src/renderer/src/components/editor/rich-markdown-editor-click-routing.ts 4 src/renderer/src/components/editor/useEditorPanelContentState.ts 5 src/renderer/src/components/editor/useEditorPanelDiffContentLoader.ts 1 src/renderer/src/components/editor/useEditorPanelFileContentLoader.ts -1 src/renderer/src/components/editor/useMarkdownDocuments.ts +2 src/renderer/src/components/editor/useMarkdownDocuments.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 @@ -54,27 +70,49 @@ 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/jira-connect-dialog.tsx +1 src/renderer/src/components/jira-user-picker.tsx 1 src/renderer/src/components/landing-preflight-runtime.ts 1 src/renderer/src/components/linear-api-key-dialog.tsx +3 src/renderer/src/components/linear-issue-project-selector.tsx +2 src/renderer/src/components/linear-issue-sub-issues.tsx +2 src/renderer/src/components/linear-issue-workspace-detail-state.ts +2 src/renderer/src/components/linear-item-drawer-comment-footer.tsx +6 src/renderer/src/components/linear-item-drawer-edit-controller.tsx +1 src/renderer/src/components/linear-item-drawer-edit-section.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 +1 src/renderer/src/components/native-chat/NativeChatAgentMessageSenders.tsx +1 src/renderer/src/components/native-chat/NativeChatComposer.tsx 1 src/renderer/src/components/native-chat/NativeChatResolvedView.tsx 1 src/renderer/src/components/native-chat/NativeChatVisualFrame.tsx +1 src/renderer/src/components/native-chat/StructuredAgentSessionAttentionBridge.tsx +1 src/renderer/src/components/native-chat/StructuredAgentSessionPaneOverlayLayer.tsx +1 src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.tsx +5 src/renderer/src/components/native-chat/native-chat-agent-message-sender-label.ts +2 src/renderer/src/components/native-chat/native-chat-attachment-upload.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 +3 src/renderer/src/components/native-chat/native-chat-http-link-source-owner.ts 2 src/renderer/src/components/native-chat/native-chat-runtime-owner.ts 2 src/renderer/src/components/native-chat/native-chat-session-option-discovery.ts +1 src/renderer/src/components/native-chat/native-chat-skill-discovery-context.ts +1 src/renderer/src/components/native-chat/use-native-chat-file-drops.ts +1 src/renderer/src/components/native-chat/use-native-chat-link-actions.ts +1 src/renderer/src/components/native-chat/use-native-chat-session-options.ts +2 src/renderer/src/components/native-chat/use-native-chat-workspace-file-drop.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 +4 src/renderer/src/components/new-workspace/use-linear-source-connection.ts +1 src/renderer/src/components/new-workspace/use-smart-workspace-name-field-controller.ts +2 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/quick-open-file-list-test-harness.ts 3 src/renderer/src/components/repo/CreateFromPicker.tsx -1 src/renderer/src/components/right-sidebar/ChecksPanel.tsx +4 src/renderer/src/components/right-sidebar/ChecksPanel.tsx 2 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-resume-in-chat-workspace.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 @@ -90,21 +128,24 @@ 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-flows.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 +3 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 +3 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-flows.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/review/use-review-flows.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 @@ -119,10 +160,11 @@ 1 src/renderer/src/components/right-sidebar/use-file-explorer-ignored-paths.ts 2 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 +1 src/renderer/src/components/right-sidebar/useCreatePullRequestDialogFields.ts 4 src/renderer/src/components/right-sidebar/useFileSearchPanel.ts -1 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 +2 src/renderer/src/components/right-sidebar/useFileSearchRunner.ts +2 src/renderer/src/components/right-sidebar/useFileSearchScope.ts +3 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 @@ -135,8 +177,9 @@ 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 +2 src/renderer/src/components/settings/Settings.tsx 1 src/renderer/src/components/settings/SettingsSetupGuidePane.tsx +1 src/renderer/src/components/settings/SettingsSidebar.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 @@ -166,27 +209,37 @@ 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/SetupScriptPromptCard.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/WorkspaceAttachmentSearch.tsx +1 src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx +1 src/renderer/src/components/sidebar/WorktreeCardPorts.tsx 2 src/renderer/src/components/sidebar/WorktreeList.tsx 1 src/renderer/src/components/sidebar/WorktreeRowOpenInSubMenu.tsx +1 src/renderer/src/components/sidebar/WorktreeVisibilityDialog.tsx 1 src/renderer/src/components/sidebar/add-repo-runtime-owner.ts +2 src/renderer/src/components/sidebar/delete-worktree-flow.ts 1 src/renderer/src/components/sidebar/linear-agent-skill-runtime.ts 2 src/renderer/src/components/sidebar/rendered-sidebar-worktree-order.ts +1 src/renderer/src/components/sidebar/setup-script-prompt-render-state.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 +2 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/use-worktree-card-controller.ts +1 src/renderer/src/components/sidebar/use-worktree-card-review-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 +3 src/renderer/src/components/sidebar/workspace-board-task-status-sync.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 @@ -195,52 +248,94 @@ 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/rows/ProjectGroupDialogs.tsx 1 src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx +1 src/renderer/src/components/sidebar/worktree-visibility-host-target.ts 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/PortsStatusSegment.tsx 1 src/renderer/src/components/status-bar/SshStatusSegment.tsx 1 src/renderer/src/components/status-bar/StatusBarSurface.tsx +1 src/renderer/src/components/status-bar/ports-status-popover-rows.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 +2 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/status-bar/workspace-space-decision-details.ts +1 src/renderer/src/components/tab-bar/QuickLaunchButton.tsx 1 src/renderer/src/components/tab-bar/TabBar.tsx +1 src/renderer/src/components/tab-bar/TabBarItemRow.tsx 3 src/renderer/src/components/tab-bar/tab-bar-windows-shell-launch-test-harness.ts -3 src/renderer/src/components/tab-bar/use-tab-bar-runtime-model.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 2 src/renderer/src/components/tab-group/useTabGroupActivationCommands.ts 5 src/renderer/src/components/tab-group/useTabGroupCreationCommands.ts -1 src/renderer/src/components/task-page/TaskPage.tsx +1 src/renderer/src/components/tab-group/workspace-tab-close-commands.ts +1 src/renderer/src/components/task-page-jira-status-order.ts +2 src/renderer/src/components/task-page-linear-issue-model.tsx +11 src/renderer/src/components/task-page/TaskPage.tsx +2 src/renderer/src/components/task-page/jira/IssueAssigneeField.tsx +1 src/renderer/src/components/task-page/linear/Filters.tsx +1 src/renderer/src/components/terminal-pane/TerminalPaneSurface.tsx +1 src/renderer/src/components/terminal-pane/pending-split-close-test-fixture.ts +1 src/renderer/src/components/terminal-pane/pty-connection/agent-idle-working-handlers.ts +1 src/renderer/src/components/terminal-pane/pty-connection/cold-restore-resume-startup.ts +1 src/renderer/src/components/terminal-pane/pty-connection/direct-ssh-retry-status.ts +1 src/renderer/src/components/terminal-pane/pty-connection/interrupt-input-intent.ts +1 src/renderer/src/components/terminal-pane/pty-connection/pane-agent-identity.ts 1 src/renderer/src/components/terminal-pane/retire-unbound-runtime-terminal-pane.ts +1 src/renderer/src/components/terminal-pane/terminal-ctrl-arrow-conpty.ts 2 src/renderer/src/components/terminal-pane/terminal-drop-runtime-owner.ts 1 src/renderer/src/components/terminal-pane/terminal-fit-restore.ts -1 src/renderer/src/components/terminal-pane/terminal-input-host-platform.ts +2 src/renderer/src/components/terminal-pane/terminal-input-host-platform.ts +1 src/renderer/src/components/terminal-pane/terminal-keyboard-hook.ts +1 src/renderer/src/components/terminal-pane/terminal-keyboard-runtime.ts 1 src/renderer/src/components/terminal-pane/terminal-link-open-hints.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-manager-options.ts +2 src/renderer/src/components/terminal-pane/terminal-pane-menu-paste.ts +3 src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.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 -1 src/renderer/src/components/terminal-pane/use-terminal-pane-controller.ts +2 src/renderer/src/components/terminal-pane/use-terminal-pane-close-actions.ts +2 src/renderer/src/components/terminal-pane/use-terminal-pane-controller.ts +1 src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.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/terminal-tab-create.ts +1 src/renderer/src/components/terminal/terminal-tab-actions.ts +4 src/renderer/src/components/terminal/terminal-tab-bulk-actions.ts +2 src/renderer/src/components/terminal/terminal-tab-create.ts +2 src/renderer/src/components/use-new-linear-issue-projects.ts +4 src/renderer/src/components/use-task-page-jira-creation-metadata.ts +4 src/renderer/src/components/use-task-page-jira-issue-creation.ts +2 src/renderer/src/components/use-task-page-jira-list-effects.ts +2 src/renderer/src/components/use-task-page-jira-list-state.ts +4 src/renderer/src/components/use-task-page-linear-board.ts +6 src/renderer/src/components/use-task-page-linear-creation-state.ts +4 src/renderer/src/components/use-task-page-linear-issue-creation.ts +2 src/renderer/src/components/use-task-page-linear-project-creation.ts +2 src/renderer/src/components/use-task-page-provider-metadata.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 +1 src/renderer/src/components/use-terminal-controller.ts 8 src/renderer/src/components/use-terminal-create-actions.ts +1 src/renderer/src/components/use-terminal-watcher-effects.ts 2 src/renderer/src/components/use-worktree-jump-palette-controller.ts +1 src/renderer/src/components/use-worktree-jump-palette-create-action.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 +1 src/renderer/src/components/use-worktree-jump-palette-task-url.ts +1 src/renderer/src/components/worktree-jump-palette-create-worktree.ts 1 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 @@ -252,7 +347,8 @@ 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 -2 src/renderer/src/hooks/editor-external-watch-targets.ts +1 src/renderer/src/hooks/editor-external-watch-target-rows.ts +3 src/renderer/src/hooks/editor-external-watch-targets.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 @@ -261,29 +357,41 @@ 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/ipc-events/terminal-request-ipc-bridge.ts +2 src/renderer/src/hooks/use-active-skill-discovery-runtime-target.ts +1 src/renderer/src/hooks/use-terminal-quick-command-hosts.ts 1 src/renderer/src/hooks/useActiveProjectSkillRuntime.ts +2 src/renderer/src/hooks/useAgentDetectionTarget.ts 1 src/renderer/src/hooks/useEditorExternalWatch.ts 1 src/renderer/src/hooks/useInstalledAgentSkills.ts 1 src/renderer/src/hooks/useIpcEvents.ts -1 src/renderer/src/hooks/useIssueMetadata.ts +4 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 -3 src/renderer/src/lib/activate-ai-vault-structured-session.ts +4 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-detection-target-inventory.ts 1 src/renderer/src/lib/agent-hibernation-coordinator-test-fixture.ts 3 src/renderer/src/lib/agent-hibernation-coordinator.ts +2 src/renderer/src/lib/agent-launch-route-input.ts +1 src/renderer/src/lib/agent-message-send.ts +1 src/renderer/src/lib/ai-vault-resume-platform.ts +1 src/renderer/src/lib/ai-vault-tab-title-requests.ts +2 src/renderer/src/lib/ai-vault-tab-title-sync-inputs.ts 1 src/renderer/src/lib/client-creation-action-error.ts 3 src/renderer/src/lib/client-creation-action-policy.ts +1 src/renderer/src/lib/codex-account-restart-startup.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 +1 src/renderer/src/lib/empty-workspace-default-agent-chat.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 +3 src/renderer/src/lib/execute-open-editor-path-move.ts +1 src/renderer/src/lib/execution-host-display-label.ts +2 src/renderer/src/lib/execution-host-facts.ts 6 src/renderer/src/lib/file-preview.ts 2 src/renderer/src/lib/folder-workspace-runtime-owner.ts 1 src/renderer/src/lib/github-pr-start-point.ts @@ -293,29 +401,42 @@ 1 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-agent-web-host-tab.ts 2 src/renderer/src/lib/launch-ai-vault-session.ts +1 src/renderer/src/lib/launch-structured-agent-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 +1 src/renderer/src/lib/linear-issue-url-lookup.ts 2 src/renderer/src/lib/local-preflight-context.ts +3 src/renderer/src/lib/open-agent-message-sender.ts 2 src/renderer/src/lib/provider-runtime-context.ts +2 src/renderer/src/lib/remap-open-editor-tabs-for-path-change.ts 1 src/renderer/src/lib/remote-workspace-drop-test-fixtures.ts 4 src/renderer/src/lib/repo-runtime-owner.ts 2 src/renderer/src/lib/resolve-owner.ts +2 src/renderer/src/lib/resume-sleeping-agent-session.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/runtime-workspace-file-route.ts 1 src/renderer/src/lib/single-runtime-legacy-owner.ts -1 src/renderer/src/lib/structured-agent-session-tab-activation.ts +1 src/renderer/src/lib/sleeping-agent-session-launch.ts +1 src/renderer/src/lib/sleeping-record-execution-host-scope.ts +1 src/renderer/src/lib/structured-agent-session-launch.ts +1 src/renderer/src/lib/structured-agent-session-provisional-tab.ts +4 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-close-route.ts 1 src/renderer/src/lib/terminal-tab-owner-activation.ts -4 src/renderer/src/lib/terminal-worktree-route.ts +5 src/renderer/src/lib/terminal-worktree-route.ts 3 src/renderer/src/lib/unread-badge-count-selector.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 +5 src/renderer/src/lib/workspace-browser-tab-open.ts +1 src/renderer/src/lib/workspace-file-drag-source.ts +1 src/renderer/src/lib/workspace-tab-commands.ts 1 src/renderer/src/lib/workspace-tab-palette-activation.ts -1 src/renderer/src/lib/workspace-terminal-host-authority.ts +3 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 @@ -323,11 +444,14 @@ 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-initial-terminal-seeding.ts +1 src/renderer/src/lib/worktree-jump-navigation.ts +2 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 +6 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 +1 src/renderer/src/runtime/local-structured-session-tabs-sync/snapshot-apply.ts 1 src/renderer/src/runtime/runtime-client-target.ts 2 src/renderer/src/runtime/runtime-git-client-context.ts 5 src/renderer/src/runtime/runtime-git-diff-client.ts @@ -339,12 +463,15 @@ 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 +5 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 +6 src/renderer/src/runtime/runtime-linear-issue-mutations.ts 12 src/renderer/src/runtime/runtime-linear-project-client.ts 3 src/renderer/src/runtime/runtime-repo-client.ts +3 src/renderer/src/runtime/structured-agent-session-owner.ts +1 src/renderer/src/runtime/structured-conversation-name.ts 5 src/renderer/src/runtime/use-runtime-session-mirror-environment-key.ts +1 src/renderer/src/runtime/use-worktree-runtime-target.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 @@ -356,41 +483,45 @@ 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 +3 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 +6 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/repository-routing.ts -1 src/renderer/src/store/github/visible-hosted-review-refresh-targets.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 2 src/renderer/src/store/github/work-item-routing.ts -7 src/renderer/src/store/index.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 +10 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 +1 src/renderer/src/store/repos/converted-ssh-browser-pages.ts +2 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 +3 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-close-actions.ts +16 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 +10 src/renderer/src/store/slices/browser/browser-host-state.ts +2 src/renderer/src/store/slices/browser/browser-page-conversion-actions.ts +1 src/renderer/src/store/slices/browser/browser-page-focus-actions.ts +14 src/renderer/src/store/slices/browser/browser-profile-import-actions.ts +14 src/renderer/src/store/slices/browser/browser-profile-list-actions.ts +7 src/renderer/src/store/slices/browser/browser-tab-actions.ts +1 src/renderer/src/store/slices/browser/browser-tab-focus-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 +5 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 @@ -402,63 +533,80 @@ 1 src/renderer/src/store/slices/editor/create-editor-slice.ts 1 src/renderer/src/store/slices/github-cache-key.ts 1 src/renderer/src/store/slices/hosted-review-cache-identity.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 +5 src/renderer/src/store/slices/jira-collection-read-actions.ts +31 src/renderer/src/store/slices/jira-connection-actions.ts +8 src/renderer/src/store/slices/jira-issue-read-actions.ts +6 src/renderer/src/store/slices/jira-read-coordination.ts +3 src/renderer/src/store/slices/jira.ts +1 src/renderer/src/store/slices/linear-slice-test-harness.ts +11 src/renderer/src/store/slices/linear.ts +5 src/renderer/src/store/slices/linear/linear-custom-view-detail-actions.ts +5 src/renderer/src/store/slices/linear/linear-custom-view-issue-actions.ts +6 src/renderer/src/store/slices/linear/linear-custom-view-list-actions.ts +5 src/renderer/src/store/slices/linear/linear-custom-view-project-actions.ts +1 src/renderer/src/store/slices/linear/linear-invalidation-actions.ts +2 src/renderer/src/store/slices/linear/linear-issue-cache-actions.ts +6 src/renderer/src/store/slices/linear/linear-issue-detail-actions.ts +9 src/renderer/src/store/slices/linear/linear-issue-list-actions.ts +5 src/renderer/src/store/slices/linear/linear-project-detail-actions.ts +5 src/renderer/src/store/slices/linear/linear-project-issue-actions.ts +6 src/renderer/src/store/slices/linear/linear-project-list-actions.ts +33 src/renderer/src/store/slices/linear/linear-slice-connection-actions.ts +5 src/renderer/src/store/slices/linear/linear-slice-scope.ts +9 src/renderer/src/store/slices/linear/linear-slice-status-actions.ts +6 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 +2 src/renderer/src/store/slices/settings-owner-hydration-publication.ts 5 src/renderer/src/store/slices/settings.ts -6 src/renderer/src/store/slices/store-test-helpers.ts +8 src/renderer/src/store/slices/store-test-helpers.ts +1 src/renderer/src/store/slices/tabs/tabs-close-actions.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 -12 src/renderer/src/store/slices/worktrees.ts -2 src/renderer/src/store/slices/worktrees/create/create-worktree.ts +15 src/renderer/src/store/slices/worktrees.ts +3 src/renderer/src/store/slices/worktrees/create/create-worktree.ts +1 src/renderer/src/store/slices/worktrees/create/created-worktree-state-merge.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 +7 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 +4 src/renderer/src/store/slices/worktrees/listing/fetch-detected-worktrees.ts +7 src/renderer/src/store/slices/worktrees/listing/fetch-worktrees.ts +1 src/renderer/src/store/slices/worktrees/listing/fetched-worktree-merge.ts +2 src/renderer/src/store/slices/worktrees/listing/known-ssh-worktree-fetch.ts +2 src/renderer/src/store/slices/worktrees/listing/worktree-host-ownership.ts +3 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 +5 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 +3 src/renderer/src/store/slices/worktrees/metadata/worktree-lineage-host-merge.ts +8 src/renderer/src/store/slices/worktrees/metadata/worktree-lineage-refresh.ts 1 src/renderer/src/store/slices/worktrees/metadata/worktree-meta-persist.ts -1 src/renderer/src/store/slices/worktrees/session/worktree-terminal-reference.ts +1 src/renderer/src/store/slices/worktrees/session/worktree-slice-lookups.ts +3 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 +1 src/renderer/src/store/slices/worktrees/teardown/purge-stale-runtime-host-state.ts 2 src/renderer/src/store/slices/worktrees/teardown/remove-worktree.ts +1 src/renderer/src/store/slices/worktrees/teardown/worktree-purge-state.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-tab-attention.ts +1 src/renderer/src/store/terminals/terminal-tab-close-providers.ts 1 src/renderer/src/store/terminals/terminal-workspace-routing.ts +1 src/renderer/src/web/preload-api/web-app-api.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 +22 src/renderer/src/web/preload-api/web-preferences-store.ts +13 src/renderer/src/web/preload-api/web-settings-api.ts +9 src/renderer/src/web/preload-api/web-ui-api.ts +3 src/renderer/src/web/preload-api/web-workspace-session-api.ts +3 src/renderer/src/web/web-preload-api.ts diff --git a/config/owner-routing-baseline.txt b/config/owner-routing-baseline.txt index 7928dee8823..05f10d68f0d 100644 --- a/config/owner-routing-baseline.txt +++ b/config/owner-routing-baseline.txt @@ -10,13 +10,8 @@ 1 src/renderer/src/components/settings/RepositoryIconPicker.tsx 1 src/renderer/src/components/setup-guide/setup-guide-progress-readiness.ts 1 src/renderer/src/components/sidebar/add-repo-runtime-owner.ts -1 src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx -1 src/renderer/src/components/status-bar/use-codex-switcher-controller.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/use-active-skill-discovery-runtime-target.ts -1 src/renderer/src/hooks/useIssueMetadata.ts -1 src/renderer/src/hooks/useSettingsNavigationMetadata.ts 1 src/renderer/src/lib/github-pr-start-point.ts 1 src/renderer/src/lib/worktree-creation-flow-startup.ts 1 src/renderer/src/runtime/runtime-git-client-context.ts @@ -26,8 +21,6 @@ 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 -1 src/renderer/src/runtime/runtime-jira-target.ts -1 src/renderer/src/runtime/runtime-linear-client.ts 3 src/renderer/src/runtime/runtime-repo-client.ts 1 src/renderer/src/store/folder-workspaces/folder-path-status-actions.ts 2 src/renderer/src/store/folder-workspaces/folder-workspace-catalog-actions.ts diff --git a/config/scripts/check-owner-routing-ratchet.mjs b/config/scripts/check-owner-routing-ratchet.mjs index 0c78a91b6c5..b557cbdd518 100644 --- a/config/scripts/check-owner-routing-ratchet.mjs +++ b/config/scripts/check-owner-routing-ratchet.mjs @@ -18,7 +18,14 @@ 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(`(?\s*(?!\(?\s*\{)/g -function valueStatements(body) { +function statementsFrom(body, start) { const statements = [] - for (const match of body.matchAll(VALUE_STATEMENT_START)) { + for (const match of body.matchAll(start)) { let depth = 0 let end = match.index + match[0].length for (; end < body.length; end += 1) { @@ -95,9 +102,21 @@ function valueStatements(body) { break } } - statements.push(body.slice(match.index, end)) + statements.push({ name: match[1], text: body.slice(match.index, end) }) } - return statements.join('\n') + return statements +} + +// `const id = reader(…)` counts only when `id` is then returned (assign-then-return). +const LOCAL_ASSIGNMENT_START = /\b(?:const|let)\s+([\w$]+)\s*(?::[^=\n]*)?=(?![=>])/g + +function valueStatements(body) { + const returned = statementsFrom(body, VALUE_STATEMENT_START).map(({ text }) => text) + const returnedIds = new Set(returned.join('\n').match(/[\w$]+/g)) + const assigned = statementsFrom(body, LOCAL_ASSIGNMENT_START) + .filter(({ name }) => returnedIds.has(name)) + .map(({ text }) => text) + return [...returned, ...assigned].join('\n') } const IMPORT_STATEMENT = diff --git a/config/scripts/check-owner-routing-ratchet.test.mjs b/config/scripts/check-owner-routing-ratchet.test.mjs index 89dc6e2b5c2..8ffb9bd5da2 100644 --- a/config/scripts/check-owner-routing-ratchet.test.mjs +++ b/config/scripts/check-owner-routing-ratchet.test.mjs @@ -183,6 +183,39 @@ describe('countFocusSettingReads', () => { } }) + it('discovers a reader assigned to a local that is then returned', () => { + const readers = discoverFocusReaders( + new Map([ + [ + 'src/renderer/src/focus.ts', + 'export function focusedId(s) {\n return s.activeRuntimeEnvironmentId\n}\n' + ], + [ + 'src/renderer/src/hook.ts', + [ + "import { focusedId } from './focus'", + 'export function useTarget(s) {', + ' const id = focusedId(s)', + ' const unrelated = focusedId(s)', + ' return id ? { id } : null', + '}', + 'export function sideEffectOnly(s) {', + ' const id = focusedId(s)', + ' log(id)', + ' return null', + '}' + ].join('\n') + ] + ]) + ) + expect(readers.namesFor('src/renderer/src/hook.ts').has('useTarget')).toBe(true) + expect(readers.namesFor('src/renderer/src/hook.ts').has('sideEffectOnly')).toBe(false) + }) + + it('counts the default scope source as a read', () => { + expect(countFocusSettingReads('linearConnect(defaultScopeSource(get().settings), key)')).toBe(1) + }) + it('counts destructuring reads', () => { expect(countFocusSettingReads('const { activeRuntimeEnvironmentId } = s')).toBe(1) expect( diff --git a/src/renderer/src/components/JiraIssueWorkspace.tsx b/src/renderer/src/components/JiraIssueWorkspace.tsx index e8676b14b69..36960d519cb 100644 --- a/src/renderer/src/components/JiraIssueWorkspace.tsx +++ b/src/renderer/src/components/JiraIssueWorkspace.tsx @@ -31,6 +31,7 @@ import { translate } from '@/i18n/i18n' import { JiraIssueMetadataBar, JiraIssueWorkspaceHeader } from './jira-issue-workspace-chrome' import { JiraIssueCommentComposer, JiraIssueWorkspaceContent } from './jira-issue-workspace-content' import { getJiraIssueWorkspaceActions } from './jira-issue-workspace-actions' +import { defaultScopeSource } from '@/lib/default-creation-host' type JiraIssueWorkspaceProps = { issue: JiraIssue | null @@ -46,7 +47,7 @@ export default function JiraIssueWorkspace({ sourceContext }: JiraIssueWorkspaceProps): React.JSX.Element { const settings = useAppStore((s) => s.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const patchJiraIssue = useAppStore((s) => s.patchJiraIssue) const [fullIssue, setFullIssue] = useState(null) const [issueLoading, setIssueLoading] = useState(false) diff --git a/src/renderer/src/components/LinearIssueTextEditor.tsx b/src/renderer/src/components/LinearIssueTextEditor.tsx index 3bc96a93cd0..2a15d3e2f52 100644 --- a/src/renderer/src/components/LinearIssueTextEditor.tsx +++ b/src/renderer/src/components/LinearIssueTextEditor.tsx @@ -20,6 +20,7 @@ import { resolveLinearIssueTextDraftState } from './linear-issue-text-draft-state' import { translate } from '@/i18n/i18n' +import { defaultScopeSource } from '@/lib/default-creation-host' type LinearIssueTextEditorProps = { issue: LinearIssue @@ -37,7 +38,7 @@ export function LinearIssueTextEditor({ sourceContext }: LinearIssueTextEditorProps): React.JSX.Element { const settings = useAppStore((s) => s.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const patchLinearIssue = useAppStore((s) => s.patchLinearIssue) const [draftState, setDraftState] = useState(() => createLinearIssueTextDraftState(issue)) const [savingField, setSavingField] = useState(null) diff --git a/src/renderer/src/components/LinearIssueWorkspace.tsx b/src/renderer/src/components/LinearIssueWorkspace.tsx index cafe1be1506..dc874807c12 100644 --- a/src/renderer/src/components/LinearIssueWorkspace.tsx +++ b/src/renderer/src/components/LinearIssueWorkspace.tsx @@ -17,6 +17,7 @@ import { LinearIssueSubIssues } from './linear-issue-sub-issues' import { useLinearIssueWorkspaceDetail } from './linear-issue-workspace-detail-state' import { LinearIssueWorkspaceHeader } from './linear-issue-workspace-header' import { LinearIssueWorkspaceSidebar } from './linear-issue-workspace-sidebar' +import { defaultScopeSource } from '@/lib/default-creation-host' type LinearIssueWorkspaceProps = { issue: LinearIssue | null @@ -49,7 +50,7 @@ function LinearIssueWorkspaceIssue({ requestKey }: LinearIssueWorkspaceIssueProps): React.JSX.Element { const settings = useAppStore((state) => state.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const allWorktrees = useAllWorktrees() const folderWorkspaces = useAppStore((state) => state.folderWorkspaces) const attachmentWorkspaces = useMemo( diff --git a/src/renderer/src/components/LinearItemDrawer.tsx b/src/renderer/src/components/LinearItemDrawer.tsx index 6cb74ef71b1..f1051444a7b 100644 --- a/src/renderer/src/components/LinearItemDrawer.tsx +++ b/src/renderer/src/components/LinearItemDrawer.tsx @@ -16,6 +16,7 @@ import { type LinearLocalComment } from '@/components/linear-item-drawer-types' import { renderLinearItemDrawerSheet } from '@/components/linear-item-drawer-sheet' +import { defaultScopeSource } from '@/lib/default-creation-host' export { LinearIssueEditSection } from '@/components/linear-item-drawer-edit-section' export { LinearIssueCommentFooter } from '@/components/linear-item-drawer-comment-footer' @@ -37,7 +38,7 @@ export default function LinearItemDrawer({ const hasEditedRef = useRef(false) const optimisticCommentsRef = useRef([]) const settings = useAppStore((s) => s.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const allWorktrees = useAllWorktrees() const folderWorkspaces = useAppStore((s) => s.folderWorkspaces) const attachmentWorkspaces = useMemo( diff --git a/src/renderer/src/components/jira-user-picker.tsx b/src/renderer/src/components/jira-user-picker.tsx index a9770dffec6..1f8be2c78e9 100644 --- a/src/renderer/src/components/jira-user-picker.tsx +++ b/src/renderer/src/components/jira-user-picker.tsx @@ -3,11 +3,9 @@ import { LoaderCircle } from 'lucide-react' import { Input } from '@/components/ui/input' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import { jiraSearchUsers } from '@/runtime/runtime-jira-client' +import { jiraSearchUsers, type RuntimeJiraSettings } from '@/runtime/runtime-jira-client' import { translate } from '@/i18n/i18n' -import type { GlobalSettings } from '../../../shared/global-settings-types' import type { JiraUser } from '../../../shared/jira-types' -import type { TaskSourceContext } from '../../../shared/task-source-context' const USER_SEARCH_DEBOUNCE_MS = 250 @@ -60,7 +58,7 @@ export function JiraUserPicker({ fixedOptions, searchUsers }: { - providerSettings: TaskSourceContext | GlobalSettings | null + providerSettings: RuntimeJiraSettings siteId?: string | null value: string selectedUser: JiraUser | null diff --git a/src/renderer/src/components/linear-issue-project-selector.tsx b/src/renderer/src/components/linear-issue-project-selector.tsx index 4bdf2de9927..e9af0508464 100644 --- a/src/renderer/src/components/linear-issue-project-selector.tsx +++ b/src/renderer/src/components/linear-issue-project-selector.tsx @@ -12,6 +12,7 @@ import { useAppStore } from '@/store' import type { LinearIssue } from '../../../shared/linear/issue-types' import type { LinearProjectSummary } from '../../../shared/linear/project-types' import type { TaskSourceContext } from '../../../shared/task-source-context' +import { defaultScopeSource } from '@/lib/default-creation-host' export function LinearIssueProjectSelector({ issue, @@ -23,7 +24,7 @@ export function LinearIssueProjectSelector({ sourceContext?: TaskSourceContext | null }): React.JSX.Element { const settings = useAppStore((state) => state.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const patchLinearIssue = useAppStore((state) => state.patchLinearIssue) const [open, setOpen] = useState(false) const [query, setQuery] = useState('') diff --git a/src/renderer/src/components/linear-issue-sub-issues.tsx b/src/renderer/src/components/linear-issue-sub-issues.tsx index 8d08f338f43..63c0dca7856 100644 --- a/src/renderer/src/components/linear-issue-sub-issues.tsx +++ b/src/renderer/src/components/linear-issue-sub-issues.tsx @@ -11,6 +11,7 @@ import { linearCreateSubIssue } from '@/runtime/runtime-linear-issue-mutations' import { useAppStore } from '@/store' import type { LinearIssue, LinearIssueChildSummary } from '../../../shared/linear/issue-types' import type { TaskSourceContext } from '../../../shared/task-source-context' +import { defaultScopeSource } from '@/lib/default-creation-host' function mergeLinearSubIssues( serverSubIssues: LinearIssueChildSummary[] | undefined, @@ -34,7 +35,7 @@ export function LinearIssueSubIssues({ sourceContext?: TaskSourceContext | null }): React.JSX.Element { const settings = useAppStore((state) => state.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const fetchLinearIssue = useAppStore((state) => state.fetchLinearIssue) const [open, setOpen] = useState(false) const [title, setTitle] = useState('') diff --git a/src/renderer/src/components/linear-item-drawer-comment-footer.tsx b/src/renderer/src/components/linear-item-drawer-comment-footer.tsx index 6f6a12fe573..41e349a812d 100644 --- a/src/renderer/src/components/linear-item-drawer-comment-footer.tsx +++ b/src/renderer/src/components/linear-item-drawer-comment-footer.tsx @@ -15,6 +15,7 @@ import { linearAddIssueComment } from '@/runtime/runtime-linear-issue-mutations' import { translate } from '@/i18n/i18n' import type { TaskSourceContext } from '../../../shared/task-source-context' import type { LinearLocalComment } from '@/components/linear-item-drawer-types' +import { defaultScopeSource } from '@/lib/default-creation-host' export function LinearIssueCommentFooter({ issueId, @@ -30,7 +31,7 @@ export function LinearIssueCommentFooter({ sourceContext?: TaskSourceContext | null }): React.JSX.Element { const settings = useAppStore((s) => s.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const submitShortcutLabel = getScreenSubmitShortcutLabel() const [body, setBody] = useState('') const [submitting, setSubmitting] = useState(false) diff --git a/src/renderer/src/components/linear-item-drawer-edit-controller.tsx b/src/renderer/src/components/linear-item-drawer-edit-controller.tsx index f36a3893cfd..773ce5f9fff 100644 --- a/src/renderer/src/components/linear-item-drawer-edit-controller.tsx +++ b/src/renderer/src/components/linear-item-drawer-edit-controller.tsx @@ -12,6 +12,7 @@ import { linearUpdateIssue } from '@/runtime/runtime-linear-issue-mutations' import { translate } from '@/i18n/i18n' import { formatLinearEstimateInput } from '@/components/linear-item-drawer-edit-controls' import type { LinearIssueEditSectionProps } from '@/components/linear-item-drawer-types' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useLinearIssueEditController({ issue, @@ -24,7 +25,7 @@ export function useLinearIssueEditController({ const [estimatePopoverOpen, setEstimatePopoverOpen] = useState(false) const patchLinearIssue = useAppStore((s) => s.patchLinearIssue) const settings = useAppStore((s) => s.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const { isPending, run } = useImmediateMutation() const { diff --git a/src/renderer/src/components/settings/use-settings-store-model.ts b/src/renderer/src/components/settings/use-settings-store-model.ts index f56e5cb8c28..71312bec172 100644 --- a/src/renderer/src/components/settings/use-settings-store-model.ts +++ b/src/renderer/src/components/settings/use-settings-store-model.ts @@ -32,6 +32,7 @@ import { removeSettingsProjectFromAllHosts } from './settings-project-list' import { resolveSettingsHostScope, type SettingsHostChoice } from './settings-host-scope' +import { defaultScopeHost } from '@/lib/default-creation-host' export function useSettingsStoreModel() { const settings = useAppStore((s) => s.settings) @@ -148,7 +149,9 @@ export function useSettingsStoreModel() { // Why: session-only (deliberately not persisted) unlock — Option-click the Experimental page title reveals the hidden group. const [hiddenExperimentalUnlocked, setHiddenExperimentalUnlocked] = useState(false) const [settingsHostChoice, setSettingsHostChoice] = useState(null) - const defaultRuntimeEnvironmentId = settings?.activeRuntimeEnvironmentId + const defaultScope = defaultScopeHost(settings) + const defaultRuntimeEnvironmentId = + defaultScope.kind === 'environment' ? defaultScope.environmentId : null // Why: keyed on ids so status-only catalog updates keep the scope (and its loads) stable. const savedEnvironmentIdsKey = runtimeEnvironments.map((environment) => environment.id).join('\n') // Why: the web client's only host is its server, so it never offers a choice. diff --git a/src/renderer/src/components/sidebar/use-workspace-board-task-status-sync.ts b/src/renderer/src/components/sidebar/use-workspace-board-task-status-sync.ts index aad80bce3a2..ad2f391a727 100644 --- a/src/renderer/src/components/sidebar/use-workspace-board-task-status-sync.ts +++ b/src/renderer/src/components/sidebar/use-workspace-board-task-status-sync.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react' import { toast } from 'sonner' import { useAppStore } from '@/store' -import { getSettingsForWorktreeRuntimeOwner } from '@/lib/worktree-runtime-owner' +import { runtimeTargetForWorkspaceOwner } from '@/lib/resolve-owner' import { translate } from '@/i18n/i18n' import type { WorkspaceStatus, Worktree } from '../../../../shared/worktree/types' import { @@ -131,7 +131,10 @@ export function useWorkspaceBoardTaskStatusSync(args: { targetStatus: request.targetStatus, worktreesById: args.worktreesById, getSettingsForWorktree: (worktreeId) => - getSettingsForWorktreeRuntimeOwner(useAppStore.getState(), worktreeId), + // Why: a workspace no row places on a server is this computer's. + runtimeTargetForWorkspaceOwner(useAppStore.getState(), { workspaceId: worktreeId }) ?? { + kind: 'local' + }, getLatestWorkspaceStatus: (worktreeId) => useAppStore.getState().getKnownWorktreeById(worktreeId)?.workspaceStatus }) diff --git a/src/renderer/src/components/sidebar/workspace-board-task-status-sync.test.ts b/src/renderer/src/components/sidebar/workspace-board-task-status-sync.test.ts index 880b03289d9..6ad46c25672 100644 --- a/src/renderer/src/components/sidebar/workspace-board-task-status-sync.test.ts +++ b/src/renderer/src/components/sidebar/workspace-board-task-status-sync.test.ts @@ -85,7 +85,7 @@ function setup(overrides: Partial = {}) { worktreeIds: [item.id], targetStatus: target, worktreesById: new Map([[item.id, item]]), - settings: { activeRuntimeEnvironmentId: 'runtime-1' }, + settings: { kind: 'environment', environmentId: 'runtime-1' }, getLatestWorkspaceStatus: () => target.id, deps: { getIssue, teamStates, updateIssue } }) @@ -99,17 +99,17 @@ describe('syncWorkspaceBoardTaskStatuses', () => { await expect(run()).resolves.toEqual({ updated: 1, skipped: 0, failed: 0, messages: [] }) expect(getIssue).toHaveBeenCalledWith( - { activeRuntimeEnvironmentId: 'runtime-1' }, + { kind: 'environment', environmentId: 'runtime-1' }, 'ORC-1', 'workspace-1' ) expect(teamStates).toHaveBeenCalledWith( - { activeRuntimeEnvironmentId: 'runtime-1' }, + { kind: 'environment', environmentId: 'runtime-1' }, 'team-1', 'workspace-1' ) expect(updateIssue).toHaveBeenCalledWith( - { activeRuntimeEnvironmentId: 'runtime-1' }, + { kind: 'environment', environmentId: 'runtime-1' }, 'issue-1', { stateId: 'state-review' }, 'workspace-1' @@ -126,15 +126,15 @@ describe('syncWorkspaceBoardTaskStatuses', () => { worktreeIds: [item.id], targetStatus: target, worktreesById: new Map([[item.id, item]]), - settings: null, + settings: { kind: 'local' }, getLatestWorkspaceStatus: () => target.id, deps: { getIssue, teamStates, updateIssue } }) - expect(getIssue).toHaveBeenCalledWith(null, 'ORC-1', undefined) - expect(teamStates).toHaveBeenCalledWith(null, 'team-1', 'issue-workspace') + expect(getIssue).toHaveBeenCalledWith({ kind: 'local' }, 'ORC-1', undefined) + expect(teamStates).toHaveBeenCalledWith({ kind: 'local' }, 'team-1', 'issue-workspace') expect(updateIssue).toHaveBeenCalledWith( - null, + { kind: 'local' }, 'issue-1', { stateId: 'state-review' }, 'issue-workspace' @@ -152,7 +152,8 @@ describe('syncWorkspaceBoardTaskStatuses', () => { const teamStates = vi.fn().mockResolvedValue([state()]) const updateIssue = vi.fn<() => Promise>().mockResolvedValue({ ok: true }) const getSettingsForWorktree = vi.fn((worktreeId: string) => ({ - activeRuntimeEnvironmentId: worktreeId.startsWith('repo-a') ? 'runtime-a' : 'runtime-b' + kind: 'environment' as const, + environmentId: worktreeId.startsWith('repo-a') ? 'runtime-a' : 'runtime-b' })) await syncWorkspaceBoardTaskStatuses({ @@ -162,7 +163,7 @@ describe('syncWorkspaceBoardTaskStatuses', () => { [first.id, first], [second.id, second] ]), - settings: { activeRuntimeEnvironmentId: 'focused-runtime' }, + settings: { kind: 'environment', environmentId: 'focused-runtime' }, getSettingsForWorktree, getLatestWorkspaceStatus: () => target.id, deps: { getIssue, teamStates, updateIssue } @@ -172,51 +173,51 @@ describe('syncWorkspaceBoardTaskStatuses', () => { expect(getSettingsForWorktree).toHaveBeenCalledWith(second.id) expect(getIssue).toHaveBeenNthCalledWith( 1, - { activeRuntimeEnvironmentId: 'runtime-a' }, + { kind: 'environment', environmentId: 'runtime-a' }, 'ORC-1', 'workspace-1' ) expect(getIssue).toHaveBeenNthCalledWith( 2, - { activeRuntimeEnvironmentId: 'runtime-b' }, + { kind: 'environment', environmentId: 'runtime-b' }, 'ORC-2', 'workspace-1' ) expect(updateIssue).toHaveBeenNthCalledWith( 1, - { activeRuntimeEnvironmentId: 'runtime-a' }, + { kind: 'environment', environmentId: 'runtime-a' }, 'issue-1', { stateId: 'state-review' }, 'workspace-1' ) expect(updateIssue).toHaveBeenNthCalledWith( 2, - { activeRuntimeEnvironmentId: 'runtime-b' }, + { kind: 'environment', environmentId: 'runtime-b' }, 'issue-2', { stateId: 'state-review' }, 'workspace-1' ) }) - it('preserves null settings from the moved worktree resolver', async () => { + it('uses this computer when the moved worktree resolver names it', async () => { const { item, target, getIssue, teamStates, updateIssue } = setup() - const getSettingsForWorktree = vi.fn(() => null) + const getSettingsForWorktree = vi.fn(() => ({ kind: 'local' as const })) await syncWorkspaceBoardTaskStatuses({ worktreeIds: [item.id], targetStatus: target, worktreesById: new Map([[item.id, item]]), - settings: { activeRuntimeEnvironmentId: 'focused-runtime' }, + settings: { kind: 'environment', environmentId: 'focused-runtime' }, getSettingsForWorktree, getLatestWorkspaceStatus: () => target.id, deps: { getIssue, teamStates, updateIssue } }) expect(getSettingsForWorktree).toHaveBeenCalledWith(item.id) - expect(getIssue).toHaveBeenCalledWith(null, 'ORC-1', 'workspace-1') - expect(teamStates).toHaveBeenCalledWith(null, 'team-1', 'workspace-1') + expect(getIssue).toHaveBeenCalledWith({ kind: 'local' }, 'ORC-1', 'workspace-1') + expect(teamStates).toHaveBeenCalledWith({ kind: 'local' }, 'team-1', 'workspace-1') expect(updateIssue).toHaveBeenCalledWith( - null, + { kind: 'local' }, 'issue-1', { stateId: 'state-review' }, 'workspace-1' @@ -230,7 +231,7 @@ describe('syncWorkspaceBoardTaskStatuses', () => { worktreeIds: [item.id], targetStatus: target, worktreesById: new Map([[item.id, item]]), - settings: null, + settings: { kind: 'local' }, getLatestWorkspaceStatus: () => target.id, deps: { getIssue, teamStates, updateIssue } }) @@ -286,7 +287,7 @@ describe('syncWorkspaceBoardTaskStatuses', () => { worktreeIds: [item.id], targetStatus: target, worktreesById: new Map([[item.id, item]]), - settings: null, + settings: { kind: 'local' }, getLatestWorkspaceStatus: () => 'done', deps: { getIssue, teamStates, updateIssue } }) @@ -312,7 +313,7 @@ describe('syncWorkspaceBoardTaskStatuses', () => { worktreeIds: [item.id], targetStatus: targetStatus(), worktreesById: new Map([[item.id, item]]), - settings: null, + settings: { kind: 'local' }, getLatestWorkspaceStatus: () => 'in-review', deps: { getIssue, teamStates, updateIssue } }) @@ -323,7 +324,7 @@ describe('syncWorkspaceBoardTaskStatuses', () => { worktreeIds: [item.id], targetStatus: targetStatus({ id: 'done', label: 'Done' }), worktreesById: new Map([[item.id, item]]), - settings: null, + settings: { kind: 'local' }, getLatestWorkspaceStatus: () => 'done', deps: { getIssue, teamStates, updateIssue } }) @@ -336,14 +337,14 @@ describe('syncWorkspaceBoardTaskStatuses', () => { expect(updateIssue).toHaveBeenNthCalledWith( 1, - null, + { kind: 'local' }, 'issue-1', { stateId: 'state-review' }, 'workspace-1' ) expect(updateIssue).toHaveBeenNthCalledWith( 2, - null, + { kind: 'local' }, 'issue-1', { stateId: 'state-done' }, 'workspace-1' diff --git a/src/renderer/src/components/sidebar/workspace-board-task-status-sync.ts b/src/renderer/src/components/sidebar/workspace-board-task-status-sync.ts index b4e76ca0a17..399c8a5e6cd 100644 --- a/src/renderer/src/components/sidebar/workspace-board-task-status-sync.ts +++ b/src/renderer/src/components/sidebar/workspace-board-task-status-sync.ts @@ -177,9 +177,10 @@ async function syncLinearWorktreeStatus( return skipped(result) } - const settings = args.getSettingsForWorktree + // Why: no source has always meant this computer. + const settings = (args.getSettingsForWorktree ? args.getSettingsForWorktree(worktreeId) - : args.settings + : args.settings) ?? { kind: 'local' } const linkedWorkspaceId = worktree.linkedLinearIssueWorkspaceId ?? undefined try { diff --git a/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx b/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx index af3603359a8..0f6b2a83329 100644 --- a/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx +++ b/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx @@ -8,7 +8,7 @@ import { import { useAppStore } from '../../store' import type { ClaudeRateLimitAccountsState } from '../../../../shared/managed-account-types' import type { ProviderRateLimits } from '../../../../shared/rate-limit-types' -import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' +import { defaultScopeHost } from '@/lib/default-creation-host' import { fetchProviderAccountsSnapshot, selectClaudeProviderAccount @@ -81,7 +81,8 @@ export function ClaudeSwitcherMenu({ const settings = useAppStore((s) => s.settings) const runtimeEnvironments = useAppStore((s) => s.runtimeEnvironments) const hasActiveRuntimeEnvironment = Boolean(settings?.activeRuntimeEnvironmentId?.trim()) - const runtimeTarget = useMemo(() => getActiveRuntimeTarget(settings), [settings]) + // Why: the status bar has no host picker; it shows the default scope host's accounts. + const runtimeTarget = useMemo(() => defaultScopeHost(settings), [settings]) const providerAccountHostLabel = hasActiveRuntimeEnvironment ? (runtimeEnvironments.find( (environment) => environment.id === settings?.activeRuntimeEnvironmentId?.trim() diff --git a/src/renderer/src/components/status-bar/use-codex-switcher-controller.ts b/src/renderer/src/components/status-bar/use-codex-switcher-controller.ts index 60f4addb66b..61743ad5095 100644 --- a/src/renderer/src/components/status-bar/use-codex-switcher-controller.ts +++ b/src/renderer/src/components/status-bar/use-codex-switcher-controller.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { CodexRateLimitAccountsState } from '../../../../shared/managed-account-types' import type { ProviderRateLimits } from '../../../../shared/rate-limit-types' import { useAppStore } from '../../store' -import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' +import { defaultScopeHost } from '@/lib/default-creation-host' import { fetchProviderAccountsSnapshot, selectCodexProviderAccount @@ -68,7 +68,8 @@ export function useCodexSwitcherController(codex: ProviderRateLimits) { const settings = useAppStore((s) => s.settings) const runtimeEnvironments = useAppStore((s) => s.runtimeEnvironments) const hasActiveRuntimeEnvironment = Boolean(settings?.activeRuntimeEnvironmentId?.trim()) - const runtimeTarget = useMemo(() => getActiveRuntimeTarget(settings), [settings]) + // Why: the status bar has no host picker; it shows the default scope host's accounts. + const runtimeTarget = useMemo(() => defaultScopeHost(settings), [settings]) const providerAccountHostLabel = hasActiveRuntimeEnvironment ? (runtimeEnvironments.find( (environment) => environment.id === settings?.activeRuntimeEnvironmentId?.trim() diff --git a/src/renderer/src/components/task-page-jira-grouping.test.ts b/src/renderer/src/components/task-page-jira-grouping.test.ts index fbfa5e5b069..8d9386c6050 100644 --- a/src/renderer/src/components/task-page-jira-grouping.test.ts +++ b/src/renderer/src/components/task-page-jira-grouping.test.ts @@ -159,7 +159,9 @@ describe('Jira issue status grouping', () => { throw new Error('Expected one Jira project scope') } - await expect(loadTaskPageJiraProjectStatusOrder(null, 'runtime:old', scope)).resolves.toEqual({ + await expect( + loadTaskPageJiraProjectStatusOrder({ kind: 'local' }, 'runtime:old', scope) + ).resolves.toEqual({ statusIdsByColumn: [] }) expect(warn).toHaveBeenCalledWith('[jira] Failed to load project status order:', error) diff --git a/src/renderer/src/components/task-page-linear-issue-model.tsx b/src/renderer/src/components/task-page-linear-issue-model.tsx index 96c4024b3c8..dfe30b6be58 100644 --- a/src/renderer/src/components/task-page-linear-issue-model.tsx +++ b/src/renderer/src/components/task-page-linear-issue-model.tsx @@ -18,6 +18,7 @@ import { getLinearStateMarkerStyle } from '@/components/linear-state-pill-style' import { LoaderCircle, ChevronDown } from 'lucide-react' +import { defaultScopeSource } from '@/lib/default-creation-host' export type LinearProjectTab = 'overview' | 'issues' export type LinearGroupSection = { key: string @@ -84,7 +85,7 @@ export function LinearStateCell({ sourceContext?: TaskSourceContext | null }): React.JSX.Element { const settings = useAppStore((s) => s.settings) - const providerSettings = sourceContext ?? settings + const providerSettings = sourceContext ?? defaultScopeSource(settings) const patchLinearIssue = useAppStore((s) => s.patchLinearIssue) const states = useTeamStates(issue.team.id, providerSettings, issue.workspaceId) const [open, setOpen] = useState(false) diff --git a/src/renderer/src/components/task-page/jira/IssueAssigneeField.test.tsx b/src/renderer/src/components/task-page/jira/IssueAssigneeField.test.tsx index 3bff2e62614..1b297185db1 100644 --- a/src/renderer/src/components/task-page/jira/IssueAssigneeField.test.tsx +++ b/src/renderer/src/components/task-page/jira/IssueAssigneeField.test.tsx @@ -31,7 +31,12 @@ it('drops old provider candidates before another host with identical project IDs fireEvent.click(screen.getByRole('button', { name: 'Assignee' })) await screen.findByRole('button', { name: 'Local user' }) expect(screen.getByRole('button', { name: 'Assign to me (Local me)' })).toBeTruthy() - expect(search).toHaveBeenCalledWith(null, 'PRJ', '', 'same-site') + expect(search).toHaveBeenCalledWith( + { kind: 'default-scope', settings: null }, + 'PRJ', + '', + 'same-site' + ) search.mockReturnValueOnce(new Promise(() => {})) rerender( diff --git a/src/renderer/src/components/task-page/jira/IssueAssigneeField.tsx b/src/renderer/src/components/task-page/jira/IssueAssigneeField.tsx index f5091cfe142..1a6e6822936 100644 --- a/src/renderer/src/components/task-page/jira/IssueAssigneeField.tsx +++ b/src/renderer/src/components/task-page/jira/IssueAssigneeField.tsx @@ -6,6 +6,7 @@ import { hasJiraAssigneeCreateField } from '@/components/task-page-jira-create-f import { getJiraProjectSelectionKey } from '@/components/task-page-jira-project-selection' import { jiraListAssignableUsersForProject } from '@/runtime/runtime-jira-client' import { translate } from '@/i18n/i18n' +import { defaultScopeSource } from '@/lib/default-creation-host' export function TaskPageJiraIssueAssigneeField({ model @@ -34,7 +35,7 @@ export function TaskPageJiraIssueAssigneeField({ setNewJiraIssueAssignee, newJiraIssueSubmitting } = model - const providerSettings = jiraTaskSourceContext ?? settings + const providerSettings = jiraTaskSourceContext ?? defaultScopeSource(settings) const projectKey = newJiraIssueTargetProject?.key const projectSiteId = newJiraIssueTargetProject?.siteId const searchAssignableUsers = useCallback( diff --git a/src/renderer/src/components/task-page/linear/Filters.tsx b/src/renderer/src/components/task-page/linear/Filters.tsx index bc2ebfc159b..5f63ad1ba5d 100644 --- a/src/renderer/src/components/task-page/linear/Filters.tsx +++ b/src/renderer/src/components/task-page/linear/Filters.tsx @@ -8,6 +8,7 @@ import { Plus, LoaderCircle, RefreshCw, Search, X } from 'lucide-react' import LinearIssueAttributeFilterDropdowns from '@/components/linear-issue-attribute-filter-dropdowns' import { Input } from '@/components/ui/input' import { shouldSuppressEnterSubmit } from '@/lib/new-workspace-enter-guard' +import { defaultScopeSource } from '@/lib/default-creation-host' export function TaskPageLinearFilters({ model }: { @@ -215,7 +216,7 @@ export function TaskPageLinearFilters({ selectedTeamIds={[...linearTeamSelection]} availableTeams={linearTeamOptions} teamsSettled={availableTeams.length > 0} - settings={linearTaskSourceContext ?? settings} + settings={linearTaskSourceContext ?? defaultScopeSource(settings)} /> ) : null}
diff --git a/src/renderer/src/components/use-new-linear-issue-projects.ts b/src/renderer/src/components/use-new-linear-issue-projects.ts new file mode 100644 index 00000000000..3ae64a3473c --- /dev/null +++ b/src/renderer/src/components/use-new-linear-issue-projects.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react' +import type { GlobalSettings } from '../../../shared/global-settings-types' +import type { LinearProjectSummary } from '../../../shared/linear/project-types' +import type { TaskSourceContext } from '../../../shared/task-source-context' +import { defaultScopeSource } from '@/lib/default-creation-host' +import { linearListProjects } from '@/runtime/runtime-linear-project-client' + +/** Projects offered by the new Linear issue dialog for its target team. */ +export function useNewLinearIssueProjects(args: { + open: boolean + targetTeam: { workspaceId?: string | null } | null + selectedLinearWorkspaceId: string + linearTaskSourceContext: TaskSourceContext | null + settings: Pick | null +}) { + const { open, targetTeam, selectedLinearWorkspaceId, linearTaskSourceContext, settings } = args + const [newLinearIssueProjects, setNewLinearIssueProjects] = useState([]) + const [newLinearIssueProjectsLoading, setNewLinearIssueProjectsLoading] = useState(false) + useEffect(() => { + let cancelled = false + if (!open || !targetTeam) { + setNewLinearIssueProjects([]) + setNewLinearIssueProjectsLoading(false) + return + } + setNewLinearIssueProjectsLoading(true) + const targetWorkspaceId = + targetTeam.workspaceId || + (selectedLinearWorkspaceId !== 'all' ? selectedLinearWorkspaceId : null) + // Why: with no Tasks source chosen, the list comes from the default scope host. + const source = linearTaskSourceContext ?? defaultScopeSource(settings) + linearListProjects(source, undefined, 100, targetWorkspaceId) + .then((p) => { + if (!cancelled) { + setNewLinearIssueProjects(p.items) + } + }) + .catch(() => {}) + .finally(() => { + if (!cancelled) { + setNewLinearIssueProjectsLoading(false) + } + }) + return () => { + // Why: project lists are workspace-scoped; stale responses must not populate the composer after a team/workspace switch. + cancelled = true + } + }, [open, targetTeam, linearTaskSourceContext, settings, selectedLinearWorkspaceId]) + return { + newLinearIssueProjects, + setNewLinearIssueProjects, + newLinearIssueProjectsLoading, + setNewLinearIssueProjectsLoading + } +} diff --git a/src/renderer/src/components/use-task-page-jira-creation-metadata.ts b/src/renderer/src/components/use-task-page-jira-creation-metadata.ts index dac221ed0e1..00157cfeb32 100644 --- a/src/renderer/src/components/use-task-page-jira-creation-metadata.ts +++ b/src/renderer/src/components/use-task-page-jira-creation-metadata.ts @@ -3,6 +3,7 @@ import { useEffect } from 'react' import { jiraListIssueTypes, jiraListCreateFields } from '@/runtime/runtime-jira-client' import { toast } from 'sonner' import { translate } from '@/i18n/i18n' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageJiraCreationMetadata(model: TaskPageJiraCreationStateModel) { const { settings, @@ -29,7 +30,7 @@ export function useTaskPageJiraCreationMetadata(model: TaskPageJiraCreationState setAvailableJiraIssueTypes([]) setJiraIssueTypesLoading(true) void jiraListIssueTypes( - jiraTaskSourceContext ?? settings, + jiraTaskSourceContext ?? defaultScopeSource(settings), newJiraIssueTargetProject.id, newJiraIssueTargetProject.siteId ) @@ -84,7 +85,7 @@ export function useTaskPageJiraCreationMetadata(model: TaskPageJiraCreationState setJiraCreateFieldsError(null) setNewJiraIssueCustomFieldValues({}) void jiraListCreateFields( - jiraTaskSourceContext ?? settings, + jiraTaskSourceContext ?? defaultScopeSource(settings), newJiraIssueTargetProject.id, newJiraIssueTargetType.id, newJiraIssueTargetProject.siteId diff --git a/src/renderer/src/components/use-task-page-jira-issue-creation.ts b/src/renderer/src/components/use-task-page-jira-issue-creation.ts index af8ce0f7156..e51687793d3 100644 --- a/src/renderer/src/components/use-task-page-jira-issue-creation.ts +++ b/src/renderer/src/components/use-task-page-jira-issue-creation.ts @@ -7,6 +7,7 @@ import { import { jiraCreateIssue, jiraGetIssue } from '@/runtime/runtime-jira-client' import { toast } from 'sonner' import { translate } from '@/i18n/i18n' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageJiraIssueCreation(model: TaskPageLinearIssueCreationModel) { const { settings, @@ -53,7 +54,7 @@ export function useTaskPageJiraIssueCreation(model: TaskPageLinearIssueCreationM setNewJiraIssueSubmitting(true) const submitProviderRuntimeContextKey = providerRuntimeContextKey try { - const result = await jiraCreateIssue(jiraTaskSourceContext ?? settings, { + const result = await jiraCreateIssue(jiraTaskSourceContext ?? defaultScopeSource(settings), { siteId: newJiraIssueTargetProject.siteId, projectId: newJiraIssueTargetProject.id, issueTypeId: newJiraIssueTargetType.id, @@ -93,7 +94,7 @@ export function useTaskPageJiraIssueCreation(model: TaskPageLinearIssueCreationM setNewJiraIssueAssignee(null) setJiraRefreshNonce((n) => n + 1) void jiraGetIssue( - jiraTaskSourceContext ?? settings, + jiraTaskSourceContext ?? defaultScopeSource(settings), result.key, newJiraIssueTargetProject.siteId ) diff --git a/src/renderer/src/components/use-task-page-jira-list-effects.ts b/src/renderer/src/components/use-task-page-jira-list-effects.ts index 6cdd282b051..160578a2c0a 100644 --- a/src/renderer/src/components/use-task-page-jira-list-effects.ts +++ b/src/renderer/src/components/use-task-page-jira-list-effects.ts @@ -8,6 +8,7 @@ import { import { createTaskPageJiraLoadFailureState } from '@/components/task-page-jira-load-state' import { searchTaskPageJiraIssues } from '@/components/task-page-jira-search' import { JIRA_ITEM_LIMIT, TASK_SEARCH_DEBOUNCE_MS } from './task-page-source-context' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageJiraListEffects(model: TaskPageLinearCollectionEffectsModel) { const { settings, @@ -107,7 +108,7 @@ export function useTaskPageJiraListEffects(model: TaskPageLinearCollectionEffect projectScope ) void loadTaskPageJiraProjectStatusOrder( - jiraTaskSourceContext ?? settings, + jiraTaskSourceContext ?? defaultScopeSource(settings), jiraTaskSourceScopeKey, projectScope ).then((order) => { diff --git a/src/renderer/src/components/use-task-page-jira-list-state.ts b/src/renderer/src/components/use-task-page-jira-list-state.ts index 7c471ffcfb4..9d7168c12c4 100644 --- a/src/renderer/src/components/use-task-page-jira-list-state.ts +++ b/src/renderer/src/components/use-task-page-jira-list-state.ts @@ -10,6 +10,7 @@ import type { JiraPrioritiesBySite } from './jira-issue-sorter' import { jiraListPriorities } from '@/runtime/runtime-jira-client' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageJiraListState(model: TaskPageLinearViewStateModel) { const { settings, jiraConnected, selectedJiraSiteId, taskSource, jiraTaskSourceContext } = model // Jira tab state @@ -51,7 +52,7 @@ export function useTaskPageJiraListState(model: TaskPageLinearViewStateModel) { try { return [ siteId, - await jiraListPriorities(jiraTaskSourceContext ?? settings, siteId) + await jiraListPriorities(jiraTaskSourceContext ?? defaultScopeSource(settings), siteId) ] as const } catch { return [siteId, [] as JiraPriority[]] as const diff --git a/src/renderer/src/components/use-task-page-linear-board.ts b/src/renderer/src/components/use-task-page-linear-board.ts index cd4a149cd6c..af1a42c9a13 100644 --- a/src/renderer/src/components/use-task-page-linear-board.ts +++ b/src/renderer/src/components/use-task-page-linear-board.ts @@ -17,6 +17,7 @@ import { findLinearWorkflowStateForStatus, getLinearStatusSectionState } from './task-page-linear-issue-model' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageLinearBoard(model: TaskPageLinearListProjectionModel) { const { settings, @@ -114,7 +115,7 @@ export function useTaskPageLinearBoard(model: TaskPageLinearListProjectionModel) } try { const states = await linearTeamStates( - linearTaskSourceContext ?? settings, + linearTaskSourceContext ?? defaultScopeSource(settings), issue.team.id, issue.workspaceId ) @@ -151,7 +152,7 @@ export function useTaskPageLinearBoard(model: TaskPageLinearListProjectionModel) }) applyFallbackState(nextState) const result = await linearUpdateIssue( - linearTaskSourceContext ?? settings, + linearTaskSourceContext ?? defaultScopeSource(settings), issue.id, { stateId: workflowState.id diff --git a/src/renderer/src/components/use-task-page-linear-creation-state.ts b/src/renderer/src/components/use-task-page-linear-creation-state.ts index f7aef435c0e..674478b75a3 100644 --- a/src/renderer/src/components/use-task-page-linear-creation-state.ts +++ b/src/renderer/src/components/use-task-page-linear-creation-state.ts @@ -2,10 +2,10 @@ import type { TaskPageJiraListProjectionModel } from './use-task-page-jira-list- import { useState, useMemo, useEffect, type SetStateAction } from 'react' import { useTeamMembers, useTeamLabels, useTeamStates } from '@/hooks/useIssueMetadata' import { useTaskCreationDraftRetention } from '@/components/use-task-creation-draft-retention' -import type { LinearProjectSummary } from '../../../shared/linear/project-types' -import { linearListProjects } from '@/runtime/runtime-linear-project-client' +import { useNewLinearIssueProjects } from './use-new-linear-issue-projects' import { useContextualTour } from '@/components/contextual-tours/use-contextual-tour' import { writeNewLinearIssueDraft, writeNewLinearProjectDraft } from './task-page-draft-storage' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageLinearCreationState(model: TaskPageJiraListProjectionModel) { const { settings, @@ -39,12 +39,12 @@ export function useTaskPageLinearCreationState(model: TaskPageJiraListProjection ) const newLinearProjectMembers = useTeamMembers( newLinearProjectOpen ? (newLinearProjectTargetTeam?.id ?? null) : null, - settings, + defaultScopeSource(settings), newLinearProjectTargetTeam?.workspaceId ) const newLinearProjectLabels = useTeamLabels( newLinearProjectOpen ? (newLinearProjectTargetTeam?.id ?? null) : null, - settings, + defaultScopeSource(settings), newLinearProjectTargetTeam?.workspaceId ) const setNewLinearProjectTeamId = (id: string | null): void => { @@ -86,43 +86,18 @@ export function useTaskPageLinearCreationState(model: TaskPageJiraListProjection () => availableTeams.find((t) => t.id === newLinearIssueTeamId) ?? availableTeams[0] ?? null, [availableTeams, newLinearIssueTeamId] ) - const [newLinearIssueProjects, setNewLinearIssueProjects] = useState([]) - const [newLinearIssueProjectsLoading, setNewLinearIssueProjectsLoading] = useState(false) - useEffect(() => { - let cancelled = false - if (!newLinearIssueOpen || !linearConnected || !newLinearIssueTargetTeam) { - setNewLinearIssueProjects([]) - setNewLinearIssueProjectsLoading(false) - return - } - setNewLinearIssueProjectsLoading(true) - const targetWorkspaceId = - newLinearIssueTargetTeam.workspaceId || - (selectedLinearWorkspaceId !== 'all' ? selectedLinearWorkspaceId : null) - linearListProjects(linearTaskSourceContext ?? settings, undefined, 100, targetWorkspaceId) - .then((p) => { - if (!cancelled) { - setNewLinearIssueProjects(p.items) - } - }) - .catch(() => {}) - .finally(() => { - if (!cancelled) { - setNewLinearIssueProjectsLoading(false) - } - }) - return () => { - // Why: project lists are workspace-scoped; stale responses must not populate the composer after a team/workspace switch. - cancelled = true - } - }, [ - linearConnected, - newLinearIssueOpen, - newLinearIssueTargetTeam, + const { + newLinearIssueProjects, + setNewLinearIssueProjects, + newLinearIssueProjectsLoading, + setNewLinearIssueProjectsLoading + } = useNewLinearIssueProjects({ + open: newLinearIssueOpen && linearConnected, + targetTeam: newLinearIssueTargetTeam, + selectedLinearWorkspaceId, linearTaskSourceContext, - settings, - selectedLinearWorkspaceId - ]) + settings + }) const setNewLinearIssueTeamId = (value: SetStateAction): void => { const id = typeof value === 'function' ? value(newLinearIssueTeamId) : value setNewLinearIssueTeamIdState(id) @@ -139,17 +114,17 @@ export function useTaskPageLinearCreationState(model: TaskPageJiraListProjection } const newLinearStates = useTeamStates( linearConnected ? newLinearIssueTargetTeam?.id || null : null, - settings, + defaultScopeSource(settings), newLinearIssueTargetTeam?.workspaceId ) const newLinearMembers = useTeamMembers( linearConnected ? newLinearIssueTargetTeam?.id || null : null, - settings, + defaultScopeSource(settings), newLinearIssueTargetTeam?.workspaceId ) const newLinearLabels = useTeamLabels( linearConnected ? newLinearIssueTargetTeam?.id || null : null, - settings, + defaultScopeSource(settings), newLinearIssueTargetTeam?.workspaceId ) useEffect(() => { diff --git a/src/renderer/src/components/use-task-page-linear-issue-creation.ts b/src/renderer/src/components/use-task-page-linear-issue-creation.ts index 2886c50bfe0..dd42b4bd8c1 100644 --- a/src/renderer/src/components/use-task-page-linear-issue-creation.ts +++ b/src/renderer/src/components/use-task-page-linear-issue-creation.ts @@ -4,6 +4,7 @@ import { toast } from 'sonner' import { translate } from '@/i18n/i18n' import { linearCreateIssue, linearGetIssue } from '@/runtime/runtime-linear-issue-mutations' import { useAppStore } from '@/store' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageLinearIssueCreation(model: TaskPageLinearProjectCreationModel) { const { settings, @@ -57,17 +58,20 @@ export function useTaskPageLinearIssueCreation(model: TaskPageLinearProjectCreat setNewLinearIssueSubmitting(true) const submitProviderRuntimeContextKey = providerRuntimeContextKey try { - const result = await linearCreateIssue(linearTaskSourceContext ?? settings, { - teamId: newLinearIssueTargetTeam.id, - title, - description: newLinearIssueBody || undefined, - workspaceId: newLinearIssueTargetTeam.workspaceId, - stateId: newLinearIssueStateId || undefined, - priority: newLinearIssuePriority, - assigneeId: newLinearIssueAssigneeId || undefined, - projectId: newLinearIssueProjectId || null, - labelIds: newLinearIssueLabelIds.length > 0 ? newLinearIssueLabelIds : undefined - }) + const result = await linearCreateIssue( + linearTaskSourceContext ?? defaultScopeSource(settings), + { + teamId: newLinearIssueTargetTeam.id, + title, + description: newLinearIssueBody || undefined, + workspaceId: newLinearIssueTargetTeam.workspaceId, + stateId: newLinearIssueStateId || undefined, + priority: newLinearIssuePriority, + assigneeId: newLinearIssueAssigneeId || undefined, + projectId: newLinearIssueProjectId || null, + labelIds: newLinearIssueLabelIds.length > 0 ? newLinearIssueLabelIds : undefined + } + ) if (submitProviderRuntimeContextKey !== providerRuntimeContextKeyRef.current) { return } @@ -105,7 +109,7 @@ export function useTaskPageLinearIssueCreation(model: TaskPageLinearProjectCreat // Why: auto-select the new issue so the user sees exactly what was filed (mirrors the GitHub create-issue flow). void linearGetIssue( - linearTaskSourceContext ?? settings, + linearTaskSourceContext ?? defaultScopeSource(settings), result.id, newLinearIssueTargetTeam.workspaceId ) diff --git a/src/renderer/src/components/use-task-page-linear-project-creation.ts b/src/renderer/src/components/use-task-page-linear-project-creation.ts index 56c2776fe44..8fdeb988709 100644 --- a/src/renderer/src/components/use-task-page-linear-project-creation.ts +++ b/src/renderer/src/components/use-task-page-linear-project-creation.ts @@ -3,6 +3,7 @@ import { useCallback } from 'react' import { linearCreateProject } from '@/runtime/runtime-linear-project-client' import { toast } from 'sonner' import { translate } from '@/i18n/i18n' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageLinearProjectCreation(model: TaskPageGitHubIssueCreationModel) { const { settings, @@ -47,19 +48,22 @@ export function useTaskPageLinearProjectCreation(model: TaskPageGitHubIssueCreat } setNewLinearProjectSubmitting(true) try { - const result = await linearCreateProject(linearTaskSourceContext ?? settings, { - name, - description: newLinearProjectDescription.trim() || undefined, - content: newLinearProjectContent.trim() || undefined, - teamIds: [newLinearProjectTargetTeam.id], - workspaceId: newLinearProjectTargetTeam.workspaceId, - leadId: newLinearProjectLeadId || undefined, - memberIds: newLinearProjectMemberIds.length > 0 ? newLinearProjectMemberIds : undefined, - labelIds: newLinearProjectLabelIds.length > 0 ? newLinearProjectLabelIds : undefined, - priority: newLinearProjectPriority, - startDate: newLinearProjectStartDate || undefined, - targetDate: newLinearProjectTargetDate || undefined - }) + const result = await linearCreateProject( + linearTaskSourceContext ?? defaultScopeSource(settings), + { + name, + description: newLinearProjectDescription.trim() || undefined, + content: newLinearProjectContent.trim() || undefined, + teamIds: [newLinearProjectTargetTeam.id], + workspaceId: newLinearProjectTargetTeam.workspaceId, + leadId: newLinearProjectLeadId || undefined, + memberIds: newLinearProjectMemberIds.length > 0 ? newLinearProjectMemberIds : undefined, + labelIds: newLinearProjectLabelIds.length > 0 ? newLinearProjectLabelIds : undefined, + priority: newLinearProjectPriority, + startDate: newLinearProjectStartDate || undefined, + targetDate: newLinearProjectTargetDate || undefined + } + ) if (!result.ok) { toast.error( result.error || diff --git a/src/renderer/src/components/use-task-page-provider-metadata.ts b/src/renderer/src/components/use-task-page-provider-metadata.ts index 57f01c6126d..9816f95a612 100644 --- a/src/renderer/src/components/use-task-page-provider-metadata.ts +++ b/src/renderer/src/components/use-task-page-provider-metadata.ts @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react' import type { LinearTeam } from '../../../shared/linear/workspace-types' import type { JiraProject } from '../../../shared/jira-types' import { jiraListProjects } from '@/runtime/runtime-jira-client' +import { defaultScopeSource } from '@/lib/default-creation-host' export function useTaskPageProviderMetadata(model: TaskPageResumeRestorationModel) { const { settings, @@ -75,7 +76,7 @@ export function useTaskPageProviderMetadata(model: TaskPageResumeRestorationMode let cancelled = false setAvailableJiraProjects([]) setJiraProjectsLoading(true) - void jiraListProjects(jiraTaskSourceContext ?? settings, selectedJiraSiteId) + void jiraListProjects(jiraTaskSourceContext ?? defaultScopeSource(settings), selectedJiraSiteId) .then((projects) => { if (!cancelled) { setAvailableJiraProjects(projects) diff --git a/src/renderer/src/hooks/use-active-skill-discovery-runtime-target.ts b/src/renderer/src/hooks/use-active-skill-discovery-runtime-target.ts index 90f1f91b039..f5edafed206 100644 --- a/src/renderer/src/hooks/use-active-skill-discovery-runtime-target.ts +++ b/src/renderer/src/hooks/use-active-skill-discovery-runtime-target.ts @@ -1,6 +1,9 @@ import { useMemo } from 'react' import { getSingleFocusedRuntimeEnvironmentId } from '@/lib/single-runtime-legacy-owner' -import { getActiveRuntimeTarget, type RuntimeClientTarget } from '@/runtime/runtime-rpc-client' +import { + runtimeTargetForOwnerEnvironment, + type RuntimeClientTarget +} from '@/runtime/runtime-client-target' import { useAppStore } from '@/store' /** Distinguishes "not known yet" from "known to be the local host", without @@ -36,9 +39,7 @@ export function useActiveSkillDiscoveryRuntimeTarget(): RuntimeClientTarget | nu }) return useMemo( () => - ownerKey === UNRESOLVED - ? null - : getActiveRuntimeTarget({ activeRuntimeEnvironmentId: parseOwnerKey(ownerKey) }), + ownerKey === UNRESOLVED ? null : runtimeTargetForOwnerEnvironment(parseOwnerKey(ownerKey)), [ownerKey] ) } diff --git a/src/renderer/src/hooks/useIssueMetadata.test.tsx b/src/renderer/src/hooks/useIssueMetadata.test.tsx index e040ee78cb5..5c2952c3d86 100644 --- a/src/renderer/src/hooks/useIssueMetadata.test.tsx +++ b/src/renderer/src/hooks/useIssueMetadata.test.tsx @@ -200,7 +200,7 @@ describe('useIssueMetadata hooks', () => { function StatesProbe(): null { renders += 1 // Fresh settings object each render — the storm trigger. - const metadata = useTeamStates('team-1', { activeRuntimeEnvironmentId: null }, 'ws-1') + const metadata = useTeamStates('team-1', { kind: 'local' }, 'ws-1') states = metadata.data return null } @@ -220,7 +220,7 @@ describe('useIssueMetadata hooks', () => { function StatesProbe(): null { renders += 1 - const metadata = useTeamStates('team-1', { activeRuntimeEnvironmentId: null }, 'ws-1') + const metadata = useTeamStates('team-1', { kind: 'local' }, 'ws-1') error = metadata.error return null } @@ -254,11 +254,7 @@ describe('useIssueMetadata hooks', () => { }) function MultiProbe(): null { - const metadata = useTeamsStates( - ['team-fe', 'team-be'], - { activeRuntimeEnvironmentId: null }, - 'ws-1' - ) + const metadata = useTeamsStates(['team-fe', 'team-be'], { kind: 'local' }, 'ws-1') states = metadata.data as { id: string; name: string }[] return null } diff --git a/src/renderer/src/hooks/useIssueMetadata.ts b/src/renderer/src/hooks/useIssueMetadata.ts index 2ce65c3055f..1d77f388329 100644 --- a/src/renderer/src/hooks/useIssueMetadata.ts +++ b/src/renderer/src/hooks/useIssueMetadata.ts @@ -1,17 +1,15 @@ import { useEffect, useMemo, useRef, useState } from 'react' -import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' import { linearTeamLabels, linearTeamMembers, linearTeamStates } from '@/runtime/runtime-linear-project-client' -import type { RuntimeLinearSettings } from '@/runtime/runtime-linear-client' +import { getLinearRuntimeTarget, type RuntimeLinearSettings } from '@/runtime/runtime-linear-client' import type { LinearLabel, LinearMember, LinearWorkflowState } from '../../../shared/linear/workspace-types' -import { getTaskSourceRuntimeSettings } from '../../../shared/task-source-context' import { unionLinearMetadataById } from '../components/linear-issue-attribute-filter-team-ids' import { clearMetadataRequestStore, @@ -28,14 +26,15 @@ const linearStateStore = createMetadataRequestStore() const linearLabelStore = createMetadataRequestStore() const linearMemberStore = createMetadataRequestStore() +// Why: an omitted source has always meant this computer. +const THIS_COMPUTER: RuntimeLinearSettings = { kind: 'local' } + function linearMetadataCacheKey( teamId: string, - settings: RuntimeLinearSettings, + settings: RuntimeLinearSettings | undefined, workspaceId?: string | null ): string { - const runtimeSettings = - settings && 'kind' in settings ? getTaskSourceRuntimeSettings(settings) : settings - const target = getActiveRuntimeTarget(runtimeSettings) + const target = getLinearRuntimeTarget(settings ?? THIS_COMPUTER) const workspaceKey = workspaceId ?? 'selected' return target.kind === 'environment' ? `runtime:${target.environmentId}:${workspaceKey}:${teamId}` @@ -189,21 +188,19 @@ const loadTeamStates = ( teamId: string, workspaceId: string | null | undefined ): Promise => - linearTeamStates(settings, teamId, workspaceId).then((states) => states as LinearWorkflowState[]) + linearTeamStates(settings ?? THIS_COMPUTER, teamId, workspaceId) const loadTeamLabels = ( settings: RuntimeLinearSettings | undefined, teamId: string, workspaceId: string | null | undefined -): Promise => - linearTeamLabels(settings, teamId, workspaceId).then((labels) => labels as LinearLabel[]) +): Promise => linearTeamLabels(settings ?? THIS_COMPUTER, teamId, workspaceId) const loadTeamMembers = ( settings: RuntimeLinearSettings | undefined, teamId: string, workspaceId: string | null | undefined -): Promise => - linearTeamMembers(settings, teamId, workspaceId).then((members) => members as LinearMember[]) +): Promise => linearTeamMembers(settings ?? THIS_COMPUTER, teamId, workspaceId) export function useTeamsStates( teamIds: readonly string[], diff --git a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts index bda7a5d407c..1e03d291dc5 100644 --- a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts +++ b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts @@ -18,7 +18,7 @@ import { isWindowsTerminalCapabilityHost, useWindowsTerminalCapabilities } from '@/lib/windows-terminal-capabilities' -import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' +import { defaultScopeHost } from '@/lib/default-creation-host' import { useAppStore } from '@/store' import { useProjectHostSetupProjection } from '@/store/selectors' import type { ProjectGroupingModel } from '@/components/sidebar/worktree-list/grouping/project-grouping' @@ -130,7 +130,8 @@ export function useSettingsNavigationMetadata(): SettingsNavSection[] { const windowsTerminalCapabilityOwnerKey = useWindowsTerminalCapabilityOwnerKey( settings?.activeRuntimeEnvironmentId ) - const runtimeTarget = getActiveRuntimeTarget(settings) + // Why: navigation is shared with the palette, which has no host picker. + const runtimeTarget = defaultScopeHost(settings) const capabilityLoadTarget = isWebClient ? { kind: 'local' as const } : runtimeTarget const windowsTerminalCapabilities = useWindowsTerminalCapabilities( isWindows || isWebClient || runtimeTarget.kind === 'environment', diff --git a/src/renderer/src/lib/default-creation-host.test.ts b/src/renderer/src/lib/default-creation-host.test.ts index 05ee3573905..0537648793c 100644 --- a/src/renderer/src/lib/default-creation-host.test.ts +++ b/src/renderer/src/lib/default-creation-host.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest' -import { defaultCreationHost } from './default-creation-host' +import { + defaultCreationHost, + defaultScopeHost, + defaultScopeSource, + rowLessSourceTarget +} from './default-creation-host' describe('defaultCreationHost', () => { it('is this computer unless a server is chosen', () => { @@ -11,3 +16,23 @@ describe('defaultCreationHost', () => { }) }) }) + +describe('row-less sources', () => { + it('starts on the default host until a source or host is named', () => { + const settings = { activeRuntimeEnvironmentId: 'env-a' } + expect(defaultScopeHost(settings)).toEqual({ kind: 'environment', environmentId: 'env-a' }) + expect(rowLessSourceTarget(defaultScopeSource(settings))).toEqual({ + kind: 'environment', + environmentId: 'env-a' + }) + expect(rowLessSourceTarget({ kind: 'local' })).toEqual({ kind: 'local' }) + expect( + rowLessSourceTarget({ + kind: 'task-source', + provider: 'linear', + projectId: 'p', + hostId: 'runtime:env-b' + }) + ).toEqual({ kind: 'environment', environmentId: 'env-b' }) + }) +}) diff --git a/src/renderer/src/lib/default-creation-host.ts b/src/renderer/src/lib/default-creation-host.ts index 9fe9e06cff8..5170686a3d1 100644 --- a/src/renderer/src/lib/default-creation-host.ts +++ b/src/renderer/src/lib/default-creation-host.ts @@ -1,13 +1,45 @@ import type { GlobalSettings } from '../../../shared/global-settings-types' +import type { TaskSourceContext } from '../../../shared/task-source-context' import type { RuntimeClientTarget } from '@/runtime/runtime-client-target' +import { taskSourceRuntimeTarget } from './task-source-runtime-target' + +type DefaultHostSettings = Pick | null | undefined + +// The one read of the setting; the exported readers below are the sanctioned ways to reach it. +function readDefaultHost(settings: DefaultHostSettings): RuntimeClientTarget { + const environmentId = settings?.activeRuntimeEnvironmentId?.trim() + return environmentId ? { kind: 'environment', environmentId } : { kind: 'local' } +} /** * 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 | null | undefined -): RuntimeClientTarget { - const environmentId = settings?.activeRuntimeEnvironmentId?.trim() - return environmentId ? { kind: 'environment', environmentId } : { kind: 'local' } +export function defaultCreationHost(settings: DefaultHostSettings): RuntimeClientTarget { + return readDefaultHost(settings) +} + +/** + * Initial host of a row-less source (Linear, Jira, the status-bar account switchers) or a settings + * scope, until the page's own host picker overrides it. + */ +export function defaultScopeHost(settings: DefaultHostSettings): RuntimeClientTarget { + return readDefaultHost(settings) +} + +/** "No source chosen yet": a row-less source that runs on the default scope host. */ +export type DefaultScopeSource = { kind: 'default-scope'; settings: DefaultHostSettings } + +export function defaultScopeSource(settings: DefaultHostSettings): DefaultScopeSource { + return { kind: 'default-scope', settings } +} + +/** A row-less source: its task-source context, an explicit host, or the default scope host. */ +export type RowLessSource = TaskSourceContext | RuntimeClientTarget | DefaultScopeSource + +export function rowLessSourceTarget(source: RowLessSource): RuntimeClientTarget { + if (source.kind === 'task-source') { + return taskSourceRuntimeTarget(source) + } + return source.kind === 'default-scope' ? readDefaultHost(source.settings) : source } diff --git a/src/renderer/src/lib/linear-issue-url-lookup.ts b/src/renderer/src/lib/linear-issue-url-lookup.ts index e2ea3ed9aef..14f76da309a 100644 --- a/src/renderer/src/lib/linear-issue-url-lookup.ts +++ b/src/renderer/src/lib/linear-issue-url-lookup.ts @@ -33,7 +33,8 @@ export async function lookupLinearIssueUrl({ knownStatus, sourceContext, fetchLinearIssue, - readLinearStatus = linearStatus + // Why: no source context has always read this computer's Linear status. + readLinearStatus = (context) => linearStatus(context ?? { kind: 'local' }) }: { intent: LinearIssueUrlIntent knownStatus: Pick< diff --git a/src/renderer/src/runtime/runtime-jira-client.test.ts b/src/renderer/src/runtime/runtime-jira-client.test.ts index 5718f8cfff3..82c456d6ba8 100644 --- a/src/renderer/src/runtime/runtime-jira-client.test.ts +++ b/src/renderer/src/runtime/runtime-jira-client.test.ts @@ -177,7 +177,9 @@ describe('runtime Jira client search bounds', () => { }) it('rejects oversized local Jira search before IPC', async () => { - await expect(jiraSearchIssues(null, 'secret-token-value'.repeat(1024), 30)).resolves.toEqual([]) + await expect( + jiraSearchIssues({ kind: 'local' }, 'secret-token-value'.repeat(1024), 30) + ).resolves.toEqual([]) expect(jiraSearchIssuesLocal).not.toHaveBeenCalled() expect(runtimeCall).not.toHaveBeenCalled() @@ -186,7 +188,7 @@ describe('runtime Jira client search bounds', () => { it('rejects oversized runtime Jira assignee search before RPC', async () => { await expect( jiraListAssignableUsers( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, 'ORCA-1', 'x'.repeat(9 * 1024), 'site-1' @@ -200,7 +202,7 @@ describe('runtime Jira client search bounds', () => { it('routes local Jira user search through IPC', async () => { jiraSearchUsersLocal.mockResolvedValue([{ accountId: 'account-1', displayName: 'Ada' }]) - await expect(jiraSearchUsers(null, 'Ada', 'site-1')).resolves.toEqual([ + await expect(jiraSearchUsers({ kind: 'local' }, 'Ada', 'site-1')).resolves.toEqual([ { accountId: 'account-1', displayName: 'Ada' } ]) @@ -222,7 +224,7 @@ describe('runtime Jira client search bounds', () => { }) await expect( - jiraSearchUsers({ activeRuntimeEnvironmentId: 'env-1' }, 'Ada', 'site-1') + jiraSearchUsers({ kind: 'environment', environmentId: 'env-1' }, 'Ada', 'site-1') ).resolves.toEqual([{ accountId: 'account-1', displayName: 'Ada' }]) expect(runtimeCall).toHaveBeenNthCalledWith( 1, @@ -241,9 +243,9 @@ describe('runtime Jira client search bounds', () => { it('degrades remote Jira user search when the host lacks the capability', async () => { runtimeCall.mockResolvedValue(createRuntimeStatusWithoutJiraUserFieldsCapability()) - await expect(jiraSearchUsers({ activeRuntimeEnvironmentId: 'env-1' }, 'Ada')).resolves.toEqual( - [] - ) + await expect( + jiraSearchUsers({ kind: 'environment', environmentId: 'env-1' }, 'Ada') + ).resolves.toEqual([]) expect(runtimeCall).toHaveBeenCalledTimes(1) expect(runtimeCall).not.toHaveBeenCalledWith( expect.objectContaining({ method: 'jira.searchUsers' }) @@ -255,7 +257,7 @@ describe('runtime Jira client search bounds', () => { await expect( jiraCreateIssue( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, { projectId: 'project-1', issueTypeId: 'type-1', @@ -285,7 +287,7 @@ describe('runtime Jira client search bounds', () => { await expect( jiraCreateIssue( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, { projectId: 'project-1', issueTypeId: 'type-1', title: 'Issue' } ) ).resolves.toMatchObject({ ok: true, key: 'ORCA-1' }) @@ -309,7 +311,7 @@ describe('runtime Jira client search bounds', () => { await expect( jiraCreateIssue( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, { projectId: 'project-1', issueTypeId: 'type-1', @@ -338,13 +340,13 @@ describe('runtime Jira client search bounds', () => { it('uses project-scoped search locally and rejects oversized queries before IPC', async () => { jiraProjectUsersLocal.mockResolvedValue([]) - await jiraListAssignableUsersForProject(null, '100', 'Ada', 'site-1') + await jiraListAssignableUsersForProject({ kind: 'local' }, '100', 'Ada', 'site-1') expect(jiraProjectUsersLocal).toHaveBeenCalledWith({ projectIdOrKey: '100', query: 'Ada', siteId: 'site-1' }) - await jiraListAssignableUsersForProject(null, '100', 'x'.repeat(20_000), 'site-1') + await jiraListAssignableUsersForProject({ kind: 'local' }, '100', 'x'.repeat(20_000), 'site-1') expect(jiraProjectUsersLocal).toHaveBeenCalledTimes(1) }) @@ -355,7 +357,7 @@ describe('runtime Jira client search bounds', () => { : { id: 'rpc-1', ok: true, result: [], _meta: { runtimeId: 'remote-runtime' } } ) await jiraListAssignableUsersForProject( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, '100', 'Ada', 'site-1' @@ -372,7 +374,7 @@ describe('runtime Jira client search bounds', () => { runtimeCall.mockReset().mockResolvedValue(createRuntimeStatusWithoutJiraUserFieldsCapability()) await expect( jiraListAssignableUsersForProject( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, '100', 'Ada', 'site-1' @@ -408,10 +410,10 @@ describe('runtime Jira client search bounds', () => { ) await expect( - jiraGetIssue({ activeRuntimeEnvironmentId: 'env-1' }, 'ORCA-1', 'site-1') + jiraGetIssue({ kind: 'environment', environmentId: 'env-1' }, 'ORCA-1', 'site-1') ).resolves.toMatchObject({ key: 'ORCA-1' }) await expect( - jiraIssueComments({ activeRuntimeEnvironmentId: 'env-1' }, 'ORCA-1', 'site-1') + jiraIssueComments({ kind: 'environment', environmentId: 'env-1' }, 'ORCA-1', 'site-1') ).resolves.toMatchObject([{ id: 'comment-1' }]) expect(runtimeSubscribe).toHaveBeenNthCalledWith( diff --git a/src/renderer/src/runtime/runtime-jira-target.ts b/src/renderer/src/runtime/runtime-jira-target.ts index 610ac82c0b1..c7874b68a90 100644 --- a/src/renderer/src/runtime/runtime-jira-target.ts +++ b/src/renderer/src/runtime/runtime-jira-target.ts @@ -1,24 +1,8 @@ -import type { GlobalSettings } from '../../../shared/global-settings-types' -import { - getTaskSourceRuntimeSettings, - type TaskSourceContext -} from '../../../shared/task-source-context' -import { getActiveRuntimeTarget } from './runtime-rpc-client' +import type { RuntimeClientTarget } from './runtime-client-target' +import { rowLessSourceTarget, type RowLessSource } from '@/lib/default-creation-host' -export type RuntimeJiraSettings = - | Pick - | TaskSourceContext - | null - | undefined +export type RuntimeJiraSettings = RowLessSource -function isTaskSourceRuntimeSettings(settings: RuntimeJiraSettings): settings is TaskSourceContext { - return settings !== null && settings !== undefined && 'kind' in settings -} - -export function getJiraRuntimeTarget( - settings: RuntimeJiraSettings -): ReturnType { - return getActiveRuntimeTarget( - isTaskSourceRuntimeSettings(settings) ? getTaskSourceRuntimeSettings(settings) : settings - ) +export function getJiraRuntimeTarget(source: RuntimeJiraSettings): RuntimeClientTarget { + return rowLessSourceTarget(source) } diff --git a/src/renderer/src/runtime/runtime-linear-client.test.ts b/src/renderer/src/runtime/runtime-linear-client.test.ts index 77e4bbbf957..6310d4e1e1e 100644 --- a/src/renderer/src/runtime/runtime-linear-client.test.ts +++ b/src/renderer/src/runtime/runtime-linear-client.test.ts @@ -106,22 +106,23 @@ describe('runtime linear client', () => { }) linearCreateProjectLocal.mockResolvedValue({ ok: true, project: { id: 'project-1' } }) - await expect(linearStatus({ activeRuntimeEnvironmentId: null })).resolves.toEqual({ + await expect(linearStatus({ kind: 'local' })).resolves.toEqual({ connected: false, viewer: null }) - await expect( - linearSearchIssues({ activeRuntimeEnvironmentId: null }, 'bug', 10) - ).resolves.toEqual([{ id: 'issue-1' }]) - await expect( - linearListIssues({ activeRuntimeEnvironmentId: null }, 'all', 72, 'workspace-1') - ).resolves.toEqual({ items: [{ id: 'issue-2' }], hasMore: true }) + await expect(linearSearchIssues({ kind: 'local' }, 'bug', 10)).resolves.toEqual([ + { id: 'issue-1' } + ]) + await expect(linearListIssues({ kind: 'local' }, 'all', 72, 'workspace-1')).resolves.toEqual({ + items: [{ id: 'issue-2' }], + hasMore: true + }) await linearCreateSubIssue( - { activeRuntimeEnvironmentId: null }, + { kind: 'local' }, { parentIssueId: 'issue-1', teamId: 'team-1', title: 'Child task' } ) await linearCreateProject( - { activeRuntimeEnvironmentId: null }, + { kind: 'local' }, { name: 'Roadmap', teamIds: ['team-1'], workspaceId: 'workspace-1' } ) @@ -152,23 +153,19 @@ describe('runtime linear client', () => { it('wraps legacy local Linear issue list arrays as collection results', async () => { linearListIssuesLocal.mockResolvedValue([{ id: 'legacy-issue' }]) - await expect( - linearListIssues({ activeRuntimeEnvironmentId: null }, 'assigned', 20) - ).resolves.toEqual({ items: [{ id: 'legacy-issue' }] }) + await expect(linearListIssues({ kind: 'local' }, 'assigned', 20)).resolves.toEqual({ + items: [{ id: 'legacy-issue' }] + }) }) it('rejects oversized local Linear search queries before IPC', async () => { await expect( - linearSearchIssues( - { activeRuntimeEnvironmentId: null }, - 'secret-token-value'.repeat(1024), - 10 - ) + linearSearchIssues({ kind: 'local' }, 'secret-token-value'.repeat(1024), 10) ).resolves.toEqual([]) - await expect( - linearListProjects({ activeRuntimeEnvironmentId: null }, 'x'.repeat(9 * 1024), 10) - ).resolves.toEqual({ items: [] }) + await expect(linearListProjects({ kind: 'local' }, 'x'.repeat(9 * 1024), 10)).resolves.toEqual({ + items: [] + }) expect(linearSearchIssuesLocal).not.toHaveBeenCalled() expect(linearListProjectsLocal).not.toHaveBeenCalled() @@ -179,7 +176,7 @@ describe('runtime linear client', () => { delete (window.api.linear as { listProjects?: unknown }).listProjects await expect( - linearListProjects({ activeRuntimeEnvironmentId: null }, 'roadmap', 10, 'workspace-1') + linearListProjects({ kind: 'local' }, 'roadmap', 10, 'workspace-1') ).resolves.toEqual({ items: [] }) }) @@ -192,47 +189,25 @@ describe('runtime linear client', () => { linearListCustomViewIssuesLocal.mockResolvedValueOnce({ items: [{ id: 'issue-2' }] }) linearListCustomViewProjectsLocal.mockResolvedValueOnce({ items: [{ id: 'project-2' }] }) - await linearListProjects({ activeRuntimeEnvironmentId: null }, 'roadmap', 10, 'workspace-1', { + await linearListProjects({ kind: 'local' }, 'roadmap', 10, 'workspace-1', { force: true }) - await linearGetProject({ activeRuntimeEnvironmentId: null }, 'project-1', 'workspace-1', { + await linearGetProject({ kind: 'local' }, 'project-1', 'workspace-1', { + force: true + }) + await linearListProjectIssues({ kind: 'local' }, 'project-1', 10, 'workspace-1', { + force: true + }) + await linearListCustomViews({ kind: 'local' }, 'project', 10, 'workspace-1', { force: true }) + await linearGetCustomView({ kind: 'local' }, 'view-1', 'project', 'workspace-1', { + force: true + }) + await linearListCustomViewIssues({ kind: 'local' }, 'view-1', 10, 'workspace-1', { + force: true + }) + await linearListCustomViewProjects({ kind: 'local' }, 'view-2', 10, 'workspace-1', { force: true }) - await linearListProjectIssues( - { activeRuntimeEnvironmentId: null }, - 'project-1', - 10, - 'workspace-1', - { force: true } - ) - await linearListCustomViews( - { activeRuntimeEnvironmentId: null }, - 'project', - 10, - 'workspace-1', - { force: true } - ) - await linearGetCustomView( - { activeRuntimeEnvironmentId: null }, - 'view-1', - 'project', - 'workspace-1', - { force: true } - ) - await linearListCustomViewIssues( - { activeRuntimeEnvironmentId: null }, - 'view-1', - 10, - 'workspace-1', - { force: true } - ) - await linearListCustomViewProjects( - { activeRuntimeEnvironmentId: null }, - 'view-2', - 10, - 'workspace-1', - { force: true } - ) expect(linearListProjectsLocal).toHaveBeenCalledWith({ query: 'roadmap', @@ -299,10 +274,10 @@ describe('runtime linear client', () => { _meta: { runtimeId: 'runtime-1' } }) - await linearStatus({ activeRuntimeEnvironmentId: 'env-1' }) - await linearSearchIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'bug', 10, 'all') + await linearStatus({ kind: 'environment', environmentId: 'env-1' }) + await linearSearchIssues({ kind: 'environment', environmentId: 'env-1' }, 'bug', 10, 'all') await expect( - linearListIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'all', 72, 'workspace-1') + linearListIssues({ kind: 'environment', environmentId: 'env-1' }, 'all', 72, 'workspace-1') ).resolves.toEqual({ items: [{ id: 'issue-2' }], hasMore: true }) expect(runtimeEnvironmentCall).toHaveBeenNthCalledWith(1, { @@ -337,7 +312,7 @@ describe('runtime linear client', () => { }) await expect( - linearListIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'assigned', 20) + linearListIssues({ kind: 'environment', environmentId: 'env-1' }, 'assigned', 20) ).resolves.toEqual({ items: [{ id: 'legacy-issue' }] }) expect(runtimeEnvironmentCall).toHaveBeenCalledWith({ @@ -366,7 +341,7 @@ describe('runtime linear client', () => { ) await expect( - linearListIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'all', 20, 'workspace-1', { + linearListIssues({ kind: 'environment', environmentId: 'env-1' }, 'all', 20, 'workspace-1', { stateIds: ['state-1'], priorities: [], assignee: null, @@ -390,7 +365,7 @@ describe('runtime linear client', () => { }) await expect( - linearListIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'all', 20, 'workspace-1', { + linearListIssues({ kind: 'environment', environmentId: 'env-1' }, 'all', 20, 'workspace-1', { stateIds: [], priorities: [2], assignee: null, @@ -425,7 +400,7 @@ describe('runtime linear client', () => { }) await expect( - linearListIssues({ activeRuntimeEnvironmentId: 'env-1' }, 'assigned', 20) + linearListIssues({ kind: 'environment', environmentId: 'env-1' }, 'assigned', 20) ).resolves.toEqual({ items: [] }) }) @@ -487,17 +462,17 @@ describe('runtime linear client', () => { }) await linearCreateIssue( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, { teamId: 'team-1', title: 'Fix bug', workspaceId: 'workspace-1' } ) await linearUpdateIssue( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, 'issue-1', { estimate: 5, priority: 2 }, 'workspace-1' ) await linearCreateSubIssue( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, { parentIssueId: 'issue-1', teamId: 'team-1', @@ -506,10 +481,15 @@ describe('runtime linear client', () => { projectId: 'project-1' } ) - await linearListTeams({ activeRuntimeEnvironmentId: 'env-1' }, 'all') - await linearListProjects({ activeRuntimeEnvironmentId: 'env-1' }, 'roadmap', 10, 'workspace-1') + await linearListTeams({ kind: 'environment', environmentId: 'env-1' }, 'all') + await linearListProjects( + { kind: 'environment', environmentId: 'env-1' }, + 'roadmap', + 10, + 'workspace-1' + ) await linearCreateProject( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, { name: 'Roadmap', description: 'Summary', @@ -518,7 +498,7 @@ describe('runtime linear client', () => { priority: 2 } ) - await linearSelectWorkspace({ activeRuntimeEnvironmentId: 'env-1' }, 'workspace-1') + await linearSelectWorkspace({ kind: 'environment', environmentId: 'env-1' }, 'workspace-1') expect(runtimeEnvironmentCall).toHaveBeenNthCalledWith(1, { selector: 'env-1', @@ -619,39 +599,44 @@ describe('runtime linear client', () => { _meta: { runtimeId: 'runtime-1' } }) - await linearGetProject({ activeRuntimeEnvironmentId: 'env-1' }, 'project-1', 'workspace-1', { - force: true - }) + await linearGetProject( + { kind: 'environment', environmentId: 'env-1' }, + 'project-1', + 'workspace-1', + { + force: true + } + ) await linearListProjectIssues( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, 'project-1', 10, 'workspace-1', { force: true } ) await linearListCustomViews( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, 'project', 10, 'workspace-1', { force: true } ) await linearGetCustomView( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, 'view-1', 'project', 'workspace-1', { force: true } ) await linearListCustomViewIssues( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, 'view-1', 10, 'workspace-1', { force: true } ) await linearListCustomViewProjects( - { activeRuntimeEnvironmentId: 'env-1' }, + { kind: 'environment', environmentId: 'env-1' }, 'view-2', 10, 'workspace-1', diff --git a/src/renderer/src/runtime/runtime-linear-client.ts b/src/renderer/src/runtime/runtime-linear-client.ts index 745a4d35b3a..e4576172d07 100644 --- a/src/renderer/src/runtime/runtime-linear-client.ts +++ b/src/renderer/src/runtime/runtime-linear-client.ts @@ -1,4 +1,3 @@ -import type { GlobalSettings } from '../../../shared/global-settings-types' import type { LinearIssue } from '../../../shared/linear/issue-types' import type { LinearProjectDetail } from '../../../shared/linear/project-types' import type { @@ -7,15 +6,9 @@ import type { LinearViewer, LinearWorkspaceSelection } from '../../../shared/linear/workspace-types' -import { - callRuntimeRpc, - getActiveRuntimeTarget, - runtimeEnvironmentSupportsCapability -} from './runtime-rpc-client' -import { - getTaskSourceRuntimeSettings, - type TaskSourceContext -} from '../../../shared/task-source-context' +import { callRuntimeRpc, runtimeEnvironmentSupportsCapability } from './runtime-rpc-client' +import type { RuntimeClientTarget } from './runtime-client-target' +import { rowLessSourceTarget, type RowLessSource } from '@/lib/default-creation-host' import { isRuntimeProviderSearchQueryWithinLimit } from './runtime-provider-search-bounds' import type { LinearIssueAttributeFilter } from '../../../shared/linear/issue-attribute-filter' import { @@ -24,11 +17,7 @@ import { } from '../../../shared/linear/issue-attribute-filter' import { LINEAR_ISSUE_ATTRIBUTE_FILTER_RUNTIME_CAPABILITY } from '../../../shared/protocol-version' -export type RuntimeLinearSettings = - | Pick - | TaskSourceContext - | null - | undefined +export type RuntimeLinearSettings = RowLessSource // Why: mixed-version remotes must not look like an empty filtered result. The // Linear store swallows most read failures; this typed error is rethrown so UI @@ -62,20 +51,8 @@ export function linearReadForce(options?: LinearReadOptions): { force: true } | return options?.force ? { force: true } : {} } -function isTaskSourceRuntimeSettings( - settings: RuntimeLinearSettings -): settings is TaskSourceContext { - return settings !== null && settings !== undefined && 'kind' in settings -} - -export function getLinearRuntimeTarget( - settings: RuntimeLinearSettings -): ReturnType { - // Why: task source context makes provider ownership explicit; legacy callers - // still pass focused runtime settings until Tasks finishes migrating. - return getActiveRuntimeTarget( - isTaskSourceRuntimeSettings(settings) ? getTaskSourceRuntimeSettings(settings) : settings - ) +export function getLinearRuntimeTarget(source: RuntimeLinearSettings): RuntimeClientTarget { + return rowLessSourceTarget(source) } function normalizeLinearIssueCollectionResult( diff --git a/src/renderer/src/store/slices/jira-connection-actions.ts b/src/renderer/src/store/slices/jira-connection-actions.ts index 8eab27c758e..1d01e79ec77 100644 --- a/src/renderer/src/store/slices/jira-connection-actions.ts +++ b/src/renderer/src/store/slices/jira-connection-actions.ts @@ -22,6 +22,7 @@ import { jiraStatusUpdate, nextJiraStatusReadGeneration } from './jira-read-coordination' +import { defaultScopeSource } from '@/lib/default-creation-host' type JiraConnectionActions = Pick< JiraSlice, @@ -62,7 +63,7 @@ export function createJiraConnectionActions( set({ jiraStatusChecked: false }) } try { - const status = await jiraStatus(get().settings) + const status = await jiraStatus(defaultScopeSource(get().settings)) if ( mutationGeneration !== currentJiraMutationGeneration() || !isCurrentJiraStatusRead(statusReadGeneration) || @@ -100,7 +101,7 @@ export function createJiraConnectionActions( const requestGeneration = beginJiraMutation() const contextKey = getProviderRuntimeContextKey(get().settings) try { - const result = await jiraConnect(get().settings, args) + const result = await jiraConnect(defaultScopeSource(get().settings), args) if ( result.ok && isCurrentJiraMutation(requestGeneration) && @@ -132,14 +133,14 @@ export function createJiraConnectionActions( const requestGeneration = beginJiraMutation() const contextKey = getProviderRuntimeContextKey(get().settings) try { - const result = await jiraTestConnection(get().settings, siteId) + const result = await jiraTestConnection(defaultScopeSource(get().settings), siteId) if ( !isCurrentJiraMutation(requestGeneration) || !isCurrentJiraRuntimeContext(contextKey, get().settings) ) { return result } - const status = await jiraStatus(get().settings) + const status = await jiraStatus(defaultScopeSource(get().settings)) if ( isCurrentJiraMutation(requestGeneration) && isCurrentJiraRuntimeContext(contextKey, get().settings) @@ -155,7 +156,7 @@ export function createJiraConnectionActions( selectJiraSite: async (siteId) => { const requestGeneration = beginJiraMutation() const contextKey = getProviderRuntimeContextKey(get().settings) - const status = await jiraSelectSite(get().settings, siteId) + const status = await jiraSelectSite(defaultScopeSource(get().settings), siteId) if ( !isCurrentJiraMutation(requestGeneration) || getProviderRuntimeContextKey(get().settings) !== contextKey @@ -169,7 +170,7 @@ export function createJiraConnectionActions( disconnectJira: async (siteId) => { const requestGeneration = beginJiraMutation() const contextKey = getProviderRuntimeContextKey(get().settings) - await jiraDisconnect(get().settings, siteId) + await jiraDisconnect(defaultScopeSource(get().settings), siteId) if ( !isCurrentJiraMutation(requestGeneration) || !isCurrentJiraRuntimeContext(contextKey, get().settings) @@ -177,7 +178,7 @@ export function createJiraConnectionActions( return } clearJiraInflightRequests() - const status = await jiraStatus(get().settings) + const status = await jiraStatus(defaultScopeSource(get().settings)) if ( !isCurrentJiraMutation(requestGeneration) || !isCurrentJiraRuntimeContext(contextKey, get().settings) diff --git a/src/renderer/src/store/slices/jira-read-coordination.ts b/src/renderer/src/store/slices/jira-read-coordination.ts index 6d38ff652eb..b677552fbd9 100644 --- a/src/renderer/src/store/slices/jira-read-coordination.ts +++ b/src/renderer/src/store/slices/jira-read-coordination.ts @@ -13,6 +13,8 @@ import { } from '../../../../shared/task-source-context' import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' import { parseJiraStatusError } from '../../../../shared/jira-status-error' +import { defaultScopeSource } from '@/lib/default-creation-host' +import type { RowLessSource } from '@/lib/default-creation-host' const CACHE_TTL = 60_000 const MAX_CACHE_ENTRIES = 500 @@ -29,7 +31,7 @@ export type SharedJiraSummaryRequest = InflightJiraReadRequest } export type JiraReadScope = { - settings: AppState['settings'] | TaskSourceContext | null + settings: RowLessSource contextKey: string cachePrefix: string | null explicitSource: boolean @@ -195,7 +197,8 @@ export function getJiraReadScope( ): JiraReadScope { if (!sourceContext) { return { - settings, + // Why: no Tasks source chosen yet; the default scope host is the row-less source's host. + settings: defaultScopeSource(settings), contextKey: getProviderRuntimeContextKey(settings), cachePrefix: null, explicitSource: false @@ -214,11 +217,15 @@ export function scopedJiraCacheKey(scope: JiraReadScope, key: string): string { return scope.cachePrefix ? `${scope.cachePrefix}::${key}` : key } -function jiraConnectionRevisionContextKey( - settings: AppState['settings'] | TaskSourceContext | null -): string { +function jiraConnectionRevisionContextKey(source: RowLessSource): string { return getProviderRuntimeContextKey( - settings && 'kind' in settings ? getTaskSourceRuntimeSettings(settings) : settings + source.kind === 'default-scope' + ? source.settings + : source.kind === 'task-source' + ? getTaskSourceRuntimeSettings(source) + : { + activeRuntimeEnvironmentId: source.kind === 'environment' ? source.environmentId : null + } ) } diff --git a/src/renderer/src/store/slices/linear-invalidation.test.ts b/src/renderer/src/store/slices/linear-invalidation.test.ts index 06220136d48..3a39ae92b0c 100644 --- a/src/renderer/src/store/slices/linear-invalidation.test.ts +++ b/src/renderer/src/store/slices/linear-invalidation.test.ts @@ -150,7 +150,11 @@ describe('createLinearSlice invalidation', () => { expect(store.getState().linearListCache).toEqual({}) expect(store.getState().linearProjectIssueCache).toEqual({}) expect(store.getState().linearCustomViewIssueCache).toEqual({}) - expect(linearGetIssue).toHaveBeenCalledWith(null, 'issue-id', 'workspace-1') + expect(linearGetIssue).toHaveBeenCalledWith( + { kind: 'default-scope', settings: null }, + 'issue-id', + 'workspace-1' + ) }) it('connect invalidates cached Linear rows and waits for refreshed status', async () => { diff --git a/src/renderer/src/store/slices/linear-scoped-collection-cache.test.ts b/src/renderer/src/store/slices/linear-scoped-collection-cache.test.ts index 46bb849b2de..6c3bf6ce2a6 100644 --- a/src/renderer/src/store/slices/linear-scoped-collection-cache.test.ts +++ b/src/renderer/src/store/slices/linear-scoped-collection-cache.test.ts @@ -133,9 +133,15 @@ describe('createLinearSlice caching', () => { hasMore: true }) - expect(linearListProjectIssues).toHaveBeenCalledWith(null, 'project-1', 120, 'workspace-1', { - force: undefined - }) + expect(linearListProjectIssues).toHaveBeenCalledWith( + { kind: 'default-scope', settings: null }, + 'project-1', + 120, + 'workspace-1', + { + force: undefined + } + ) expect( store.getState().linearProjectIssueCache['workspace-1::project-issues::project-1::120']?.data ).toMatchObject({ items: [{ id: 'LIN-120' }] }) @@ -234,9 +240,15 @@ describe('createLinearSlice caching', () => { hasMore: true }) - expect(linearListCustomViewIssues).toHaveBeenCalledWith(null, 'view-1', 120, 'workspace-1', { - force: undefined - }) + expect(linearListCustomViewIssues).toHaveBeenCalledWith( + { kind: 'default-scope', settings: null }, + 'view-1', + 120, + 'workspace-1', + { + force: undefined + } + ) expect( store.getState().linearCustomViewIssueCache['workspace-1::custom-view-issues::view-1::120'] ?.data @@ -315,9 +327,15 @@ describe('createLinearSlice caching', () => { store.getState().fetchLinearCustomView('view-1', 'workspace-1', 'project', { force: true }) ).resolves.toMatchObject({ id: 'view-1' }) - expect(linearGetCustomView).toHaveBeenCalledWith(null, 'view-1', 'project', 'workspace-1', { - force: true - }) + expect(linearGetCustomView).toHaveBeenCalledWith( + { kind: 'default-scope', settings: null }, + 'view-1', + 'project', + 'workspace-1', + { + force: true + } + ) await vi.waitFor(() => { expect(store.getState().linearStatus.credentialError).toBeUndefined() }) diff --git a/src/renderer/src/store/slices/linear/linear-slice-connection-actions.ts b/src/renderer/src/store/slices/linear/linear-slice-connection-actions.ts index c799e190c75..434bf1bbce7 100644 --- a/src/renderer/src/store/slices/linear/linear-slice-connection-actions.ts +++ b/src/renderer/src/store/slices/linear/linear-slice-connection-actions.ts @@ -17,6 +17,7 @@ import { } from './linear-slice-request-state' import { isCurrentLinearRuntimeContext } from './linear-slice-scope' import { linearStatusScopeSignature } from './linear-cache' +import { defaultScopeSource } from '@/lib/default-creation-host' export function createLinearConnectionActions( set: LinearSliceSet, @@ -34,16 +35,14 @@ export function createLinearConnectionActions( const requestGeneration = beginLinearMutation() const contextKey = getProviderRuntimeContextKey(get().settings) try { - const result = (await linearTestConnection(get().settings, workspaceId)) as - | { ok: true; viewer: LinearViewer } - | { ok: false; error: string } + const result = await linearTestConnection(defaultScopeSource(get().settings), workspaceId) if ( !isCurrentLinearMutation(requestGeneration) || !isCurrentLinearRuntimeContext(contextKey, get().settings) ) { return result } - const status = await linearStatus(get().settings) + const status = await linearStatus(defaultScopeSource(get().settings)) if ( isCurrentLinearMutation(requestGeneration) && isCurrentLinearRuntimeContext(contextKey, get().settings) @@ -83,7 +82,7 @@ export function createLinearConnectionActions( const requestGeneration = beginLinearMutation() const contextKey = getProviderRuntimeContextKey(get().settings) try { - const result = await linearConnect(get().settings, apiKey) + const result = await linearConnect(defaultScopeSource(get().settings), apiKey) if ( result.ok && isCurrentLinearMutation(requestGeneration) && @@ -103,7 +102,7 @@ export function createLinearConnectionActions( linearCustomViewIssueCache: {}, linearCustomViewProjectCache: {} }) - const status = await linearStatus(get().settings) + const status = await linearStatus(defaultScopeSource(get().settings)) if ( !isCurrentLinearMutation(requestGeneration) || !isCurrentLinearRuntimeContext(contextKey, get().settings) @@ -140,7 +139,7 @@ export function createLinearConnectionActions( selectLinearWorkspace: async (workspaceId) => { const requestGeneration = beginLinearMutation() const contextKey = getProviderRuntimeContextKey(get().settings) - const status = await linearSelectWorkspace(get().settings, workspaceId) + const status = await linearSelectWorkspace(defaultScopeSource(get().settings), workspaceId) if ( !isCurrentLinearMutation(requestGeneration) || !isCurrentLinearRuntimeContext(contextKey, get().settings) @@ -169,7 +168,7 @@ export function createLinearConnectionActions( disconnectLinear: async () => { const requestGeneration = beginLinearMutation() const contextKey = getProviderRuntimeContextKey(get().settings) - await linearDisconnect(get().settings) + await linearDisconnect(defaultScopeSource(get().settings)) if ( !isCurrentLinearMutation(requestGeneration) || !isCurrentLinearRuntimeContext(contextKey, get().settings) @@ -198,14 +197,14 @@ export function createLinearConnectionActions( disconnectLinearWorkspace: async (workspaceId) => { const requestGeneration = beginLinearMutation() const contextKey = getProviderRuntimeContextKey(get().settings) - await linearDisconnectWorkspace(get().settings, workspaceId) + await linearDisconnectWorkspace(defaultScopeSource(get().settings), workspaceId) if ( !isCurrentLinearMutation(requestGeneration) || !isCurrentLinearRuntimeContext(contextKey, get().settings) ) { return } - const status = await linearStatus(get().settings) + const status = await linearStatus(defaultScopeSource(get().settings)) if ( !isCurrentLinearMutation(requestGeneration) || !isCurrentLinearRuntimeContext(contextKey, get().settings) diff --git a/src/renderer/src/store/slices/linear/linear-slice-scope.ts b/src/renderer/src/store/slices/linear/linear-slice-scope.ts index 6a36b740c64..29e6d9cab72 100644 --- a/src/renderer/src/store/slices/linear/linear-slice-scope.ts +++ b/src/renderer/src/store/slices/linear/linear-slice-scope.ts @@ -11,6 +11,8 @@ import { } from '../../../../../shared/linear/issue-attribute-filter' import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context' import { getLinearCacheGeneration, getLinearMutationGeneration } from './linear-slice-request-state' +import { defaultScopeSource } from '@/lib/default-creation-host' +import type { RowLessSource } from '@/lib/default-creation-host' export function normalizeListAttributeFilter( attributeFilter?: LinearIssueAttributeFilter | null @@ -22,7 +24,7 @@ export function normalizeListAttributeFilter( } export type LinearReadScope = { - settings: AppState['settings'] | TaskSourceContext | null + settings: RowLessSource contextKey: string cachePrefix: string | null explicitSource: boolean @@ -55,7 +57,8 @@ export function getLinearReadScope( ): LinearReadScope { if (!sourceContext) { return { - settings, + // Why: no Tasks source chosen yet; the default scope host is the row-less source's host. + settings: defaultScopeSource(settings), contextKey: getProviderRuntimeContextKey(settings), cachePrefix: null, explicitSource: false diff --git a/src/renderer/src/store/slices/linear/linear-slice-status-actions.ts b/src/renderer/src/store/slices/linear/linear-slice-status-actions.ts index 5fa5be61945..1c9311de241 100644 --- a/src/renderer/src/store/slices/linear/linear-slice-status-actions.ts +++ b/src/renderer/src/store/slices/linear/linear-slice-status-actions.ts @@ -12,6 +12,7 @@ import { } from './linear-slice-request-state' import { linearStatusScopeSignature } from './linear-cache' import { isCurrentLinearRuntimeContext } from './linear-slice-scope' +import { defaultScopeSource } from '@/lib/default-creation-host' export function createLinearStatusActions( set: LinearSliceSet, @@ -30,7 +31,7 @@ export function createLinearStatusActions( const mutationGeneration = getLinearMutationGeneration() const statusReadGeneration = nextLinearStatusReadGeneration() - const request = linearStatus(get().settings) + const request = linearStatus(defaultScopeSource(get().settings)) .then((status) => { if ( mutationGeneration !== getLinearMutationGeneration() ||