From b07b3d318a4e1d8bc6ac9268f8a3abc2ae509e3e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 10 Nov 2026 06:30:52 -0800 Subject: [PATCH] refactor(renderer): group startup platform and runtime owners (#40777) * refactor(renderer): group startup platform and runtime owners * refactor(renderer): reconcile imports and retained validation metadata * test: clear runtime pairing revisions between repo generation cases --- .github/workflows/performance-contracts.yml | 1 - config/flaky-test-pattern-baseline.txt | 4 +- config/react-compiler-bailout-baseline.txt | 2 +- config/recurring-timer-baseline.txt | 6 +- config/reliability-gates.jsonc | 133 ++-- .../scripts/ci/sharding/ci-shard-timings.json | 77 +-- .../vitest-node-runtime-files.mjs | 2 - config/tsconfig.node.json | 1 - config/tsconfig.tc.web.json | 1 - config/tsconfig.test.pane-launch.json | 1 - config/tsconfig.web.json | 1 - config/vitest.performance.config.ts | 1 - .../src/app-shell/AppBackgroundServices.tsx | 2 +- .../src/app-shell/AppNotificationCards.tsx | 2 +- .../src/app-shell/AppRootSurfaces.tsx | 4 +- .../src/app-shell/AppWorkspaceShell.tsx | 6 +- .../src/app-shell/app-command-handlers.ts | 4 +- .../appearance/window-material-runtime.ts | 2 +- .../platform}/client-environment-info.ts | 2 +- .../platform}/ios-web-platform.ts | 0 .../platform}/paired-web-client-host.ts | 4 +- .../passive-macos-app-data-access.ts | 2 +- .../platform}/renderer-app-platform.ts | 0 .../platform}/user-agent-platform.ts | 0 .../platform}/web-client-location.ts | 0 .../app-shell/shutdown-checkpoint-persist.ts | 2 +- ...tdown-checkpoint-restart-lifecycle.test.ts | 142 ---- .../shutdown-checkpoint-failure-toast.ts | 2 +- .../shutdown}/shutdown-checkpoint-guard.ts | 4 +- .../startup}/active-view-persist.ts | 4 +- .../{lib => app-shell/startup}/e2e-config.ts | 2 +- .../startup}/lazy-chunk-recovery-reload.ts | 6 +- ...azy-with-retry.never-landed-reload.test.ts | 0 .../startup}/lazy-with-retry.ts | 0 .../startup}/react-renderer-root.ts | 0 .../startup}/startup-ui-hydration.ts | 4 +- .../updates}/update-check-click-options.ts | 4 +- .../updates}/updater-beforeunload.ts | 8 +- .../updates}/updater-message-copy.ts | 132 +++- .../app-shell/use-app-session-persistence.ts | 10 +- .../app-shell/use-app-startup-hydration.ts | 4 +- .../use-onboarding-and-feature-tips.ts | 4 +- ...use-visible-hosted-review-refresh.test.tsx | 54 -- .../use-visible-hosted-review-refresh.ts | 6 +- .../worktree-jump-palette-preload.ts | 4 +- .../attention/agent-finish-drop-breadcrumb.ts | 2 +- .../src/components/AgentProgressRing.tsx | 2 +- .../src/components/AgentQuestionIcon.tsx | 2 +- src/renderer/src/components/AgentStateDot.tsx | 2 +- .../src/components/AgentWorkingSpinner.tsx | 4 +- .../components/AiVaultTabTitleSyncGate.tsx | 4 +- .../src/components/DetachedHeadBadge.tsx | 2 +- .../src/components/FirstLaunchBanner.tsx | 6 +- src/renderer/src/components/Landing.tsx | 4 +- .../LinearIssueMarkdownDescriptionEditor.tsx | 6 +- .../components/LinearIssueMarkdownToolbar.tsx | 2 +- .../src/components/LinearIssueTextEditor.tsx | 9 +- .../LinuxPackageInstallRecoveryCard.tsx | 2 +- .../src/components/LoadingSpinner.tsx | 2 +- .../components/NewWorkspaceComposerCard.tsx | 8 +- .../components/NewWorkspaceComposerModal.tsx | 4 +- .../src/components/OrcaSignInProgress.tsx | 4 +- .../src/components/ShortcutKeyCombo.tsx | 2 +- .../TerminalLegacyEditorSurface.tsx | 2 +- .../src/components/UnexpectedSignoutCard.tsx | 4 +- src/renderer/src/components/UpdateCard.tsx | 4 +- .../activity/activity-thread-context-menu.tsx | 4 +- .../activity/activity-thread-controls.tsx | 2 +- .../activity/activity-thread-detail-pane.tsx | 2 +- .../activity-thread-hover-card-summary.tsx | 4 +- .../activity/activity-thread-list-pane.tsx | 2 +- .../activity-thread-list-resize-handle.tsx | 2 +- .../activity/activity-thread-list-toolbar.tsx | 2 +- .../activity/activity-thread-row.tsx | 2 +- .../src/components/agent/AgentCombobox.tsx | 2 +- .../agent/AgentLaunchSetupFooter.tsx | 2 +- .../agent/AgentLaunchSetupPanel.tsx | 2 +- .../components/agent/AgentSettingsDialog.tsx | 9 +- .../agent/AgentSurfaceLaunchButton.tsx | 2 +- .../components/agent/agent-combobox-items.tsx | 2 +- .../agent/agent-combobox-surface-items.tsx | 2 +- .../agent/icons/agent-icon-glyphs.tsx | 2 +- .../AgentDefaultSurfacePreviews.tsx | 2 +- .../launch-settings/AgentPermissionMenu.tsx | 2 +- .../artifacts/ArtifactCollection.tsx | 4 +- .../components/artifacts/ArtifactListRow.tsx | 9 +- .../artifacts/ArtifactListSearchField.tsx | 2 +- .../artifacts/ArtifactListTableHeader.tsx | 4 +- .../artifacts/ArtifactListToolbar.tsx | 2 +- .../artifacts/ArtifactPublishButton.tsx | 4 +- .../artifacts/ArtifactPublishedLinkPanel.tsx | 2 +- .../artifacts/ArtifactsPageSkeleton.tsx | 7 +- .../automations/AutomationsPageSkeleton.tsx | 4 +- .../authoring/AutomationCustomCronPanel.tsx | 2 +- .../AutomationEditorPromptEditor.tsx | 2 +- .../authoring/AutomationProjectCombobox.tsx | 2 +- .../authoring/AutomationSchedulePicker.tsx | 2 +- .../AutomationSetupDecisionField.tsx | 2 +- .../authoring/AutomationTimeField.tsx | 2 +- .../authoring/WorkspaceCombobox.tsx | 2 +- .../automations/automation-page-parts.tsx | 2 +- .../automations-page-test-harness.tsx | 2 +- .../details/AutomationPromptDisclosure.tsx | 2 +- .../hosts/AutomationHostBadges.tsx | 2 +- .../hosts/AutomationHostFilterNotice.tsx | 2 +- .../hosts/AutomationOwnerConflictNotice.tsx | 2 +- .../hosts/automation-host-client.ts | 2 +- .../hosts/automation-host-recovery.ts | 2 +- ...se-automation-host-catalog-source-state.ts | 2 +- .../listing/AutomationListEmptyView.tsx | 2 +- .../listing/AutomationListExternalRow.tsx | 9 +- .../listing/AutomationListLastRunCell.tsx | 2 +- .../listing/AutomationListLocalRow.tsx | 9 +- .../listing/AutomationListSearchField.tsx | 2 +- .../listing/AutomationListSortHeader.tsx | 2 +- .../listing/AutomationListTableHeader.tsx | 2 +- .../listing/AutomationListToolbar.tsx | 2 +- .../listing/AutomationsListPanel.tsx | 4 +- .../runs/AutomationRunDetailsPage.tsx | 2 +- .../automations/runs/AutomationRunHistory.tsx | 2 +- .../use-automations-page-refresh.ts | 2 +- .../src/components/browser-favicon.tsx | 2 +- ...tHostedBrowserPagePane.dead-guest.test.tsx | 255 ------- .../ReopenBrowserPageOnServerButton.tsx | 2 +- .../annotate/MarkupDrawButton.tsx | 2 +- .../browser-pane/annotate/MarkupOverlay.tsx | 2 +- .../browser-pane/annotate/MarkupToolbar.tsx | 2 +- .../annotate/browser-page-annotation-tray.tsx | 2 +- .../pending-browser-annotation-card.tsx | 5 +- .../assemble-chrome/BrowserAddressBar.tsx | 4 +- .../BrowserImportHintButton.tsx | 4 +- .../BrowserMobileDriverOverlay.tsx | 2 +- .../browser-annotate-split-button.tsx | 2 +- .../browser-egress-indicator.tsx | 4 +- .../browser-extension-action-menu.tsx | 2 +- .../browser-extension-toolbar.tsx | 4 +- .../browser-link-status-bubble.tsx | 2 +- .../browser-navigation-control-row.tsx | 2 +- .../browser-page-chrome-banners.tsx | 2 +- .../browser-page-extension-menu-items.tsx | 2 +- .../browser-page-feedback-badge.tsx | 2 +- .../assemble-chrome/browser-page-pane.tsx | 4 +- .../browser-client-page-guest-metadata.ts | 2 +- ...-client-page-metadata-publish-e2e-fault.ts | 2 +- .../browser-user-agent-migration-notice.ts | 4 +- .../browser-client-page-guest-loss.ts | 2 +- .../browser-load-failure-auto-retry.tsx | 2 +- .../use-browser-page-webview-url-sync.ts | 2 +- .../browser-new-tab-workspace-card.tsx | 2 +- .../remote-browser-screencast-subscription.ts | 2 +- .../cmd-j/PaletteFilterFieldOptions.tsx | 2 +- .../src/components/cmd-j/palette-results.ts | 5 +- .../src/components/confirmation-dialog.tsx | 4 +- .../confirmation-skip-preference.ts | 2 +- .../ContextualTourOverlay.tsx | 8 +- .../ContextualTourOverlaySurface.tsx | 4 +- .../ContextualTourProgressDots.tsx | 2 +- .../crash-report/CrashReportDialog.tsx | 4 +- .../AgentCardLastMessages.tsx | 2 +- .../AgentDashboardToolbar.tsx | 2 +- .../dashboard-popout/AgentKanbanBoard.tsx | 4 +- .../dashboard-popout/AgentKanbanCard.tsx | 2 +- .../dashboard-popout/AgentTerminalDialog.tsx | 2 +- .../dashboard-popout/AgentTerminalPreview.tsx | 4 +- .../dashboard-popout/DashboardHostBadge.tsx | 2 +- .../dashboard-preview-link-interaction.ts | 2 +- .../use-agent-dashboard-telemetry.test.ts | 120 ---- .../use-agent-dashboard-telemetry.ts | 6 +- .../DashboardAgentChildDisclosure.tsx | 2 +- .../dashboard/DashboardAgentRow.tsx | 2 +- .../dashboard/DashboardAgentRowMessage.tsx | 2 +- .../dashboard/DashboardAgentRowToolStep.tsx | 2 +- .../DashboardAgentRowTrailingControls.tsx | 2 +- .../components/dictation/DictationGrapes.tsx | 2 +- .../dictation/DictationIndicator.tsx | 6 +- .../dictation/DictationPillShell.tsx | 4 +- .../dictation/dictation-meter-store.ts | 2 +- .../components/dictation/dictation-toasts.ts | 4 +- .../dismiss/EyeOffDismissButton.tsx | 2 +- .../src/components/editor/ChangesModeView.tsx | 2 +- .../components/editor/ConflictComponents.tsx | 2 +- .../editor/ConflictReviewFileTree.tsx | 2 +- .../EditorContent.monaco-lifecycle.test.tsx | 282 -------- .../editor/EditorFileLoadErrorView.tsx | 2 +- .../editor/EditorPanelHeaderPath.tsx | 2 +- .../src/components/editor/ImageViewer.tsx | 2 +- .../components/editor/MediaViewer.test.tsx | 32 +- .../src/components/editor/MediaViewer.tsx | 5 +- .../components/editor/MermaidViewer.test.tsx | 124 ---- .../src/components/editor/MermaidViewer.tsx | 5 +- .../components/editor/MonacoCodeExcerpt.tsx | 2 +- .../MonacoEditor.owner-view-state.test.tsx | 177 ----- .../src/components/editor/MonacoEditor.tsx | 2 +- .../components/editor/PanZoomImageSurface.tsx | 2 +- .../editor/PluginEditorNeedsDesktopNotice.tsx | 2 +- .../panel/CheckRunCopyButton.tsx | 2 +- ...kRunDetailsPanel.captured-failure.test.tsx | 164 ----- ...CheckRunDetailsPanel.log-outcomes.test.tsx | 337 --------- .../check-details/panel/CheckRunJobs.tsx | 7 +- .../reading/check-details-reading-cache.ts | 5 +- .../reading/check-details-reading-owner.ts | 5 +- .../check-details-reading-viewport.test.ts | 193 ------ .../reading/check-details-reading-viewport.ts | 5 +- .../combined-diff-file-tree-row.tsx | 2 +- .../browse-files/combined-diff-file-tree.tsx | 2 +- .../diff/large-files/ImageDiffViewer.tsx | 2 +- .../editor/diff/monaco-view/DiffViewer.tsx | 7 +- .../useDiffViewerFirstChangeAutoScroll.ts | 2 +- .../editor/diff/sections/DiffSectionBody.tsx | 4 +- .../components/editor/editor-lazy-views.ts | 2 +- .../ExternalFileChangeCompareDialog.tsx | 2 +- .../editor-external-change-telemetry.ts | 2 +- .../git-graph/git-graph-commit-action-copy.ts | 2 +- .../editor/git-graph/git-graph-ref-menu.tsx | 4 +- .../editor/git-graph/git-graph-tab.tsx | 2 +- .../viewport/VirtualMarkdownPreviewBody.tsx | 2 +- .../use-markdown-preview-scroll-anchor.ts | 5 +- .../use-markdown-preview-scroll-viewport.ts | 5 +- .../navigation/MarkdownTableOfContentsRow.tsx | 2 +- .../closed-editor-model-lifetime.test.ts | 102 +-- .../retention/closed-editor-tab-disposal.ts | 2 +- ...closed-editor-view-state-retention.test.ts | 65 +- .../editor-model-lifetime-fixture.ts | 2 +- .../retention/editor-view-state-cache.ts} | 0 .../editor/monaco-view-state-persistence.ts | 2 +- .../editor/notebook/cells/IpynbCellEditor.tsx | 2 +- .../notebook/cells/IpynbCellToolbar.tsx | 2 +- .../useIpynbScrollRestoration.test.tsx | 39 -- .../document/useIpynbScrollRestoration.ts | 5 +- .../notebook/kernel/IpynbKernelToolbar.tsx | 2 +- .../kernel/ipynb-kernel-setup-commands.ts | 2 +- .../notebook/outputs/IpynbCellOutputs.tsx | 2 +- .../editor/pdf-view-position.test.ts | 22 +- .../components/editor/pdf-view-position.ts | 2 +- .../pdf-viewer-failed-load-recovery.test.tsx | 504 -------------- .../components/editor/pdf-viewer-session.ts | 5 +- .../review-note-delivery/NotesSendMenu.tsx | 4 +- .../ReviewNotesSendMenuContent.tsx | 2 +- .../commands/RichMarkdownSlashMenu.tsx | 2 +- .../commands/RichMarkdownToolbarButton.tsx | 2 +- .../doc-links/RichMarkdownDocLinkMenu.tsx | 2 +- ...hMarkdownErrorBoundary.lazy-chunk.test.tsx | 109 --- .../instance/RichMarkdownErrorBoundary.tsx | 6 +- .../viewport/useEditorScrollRestore.ts | 5 +- .../use-pdf-viewer-find-shortcut.test.tsx | 164 ----- .../editor/useEditorPanelFileLoadRetry.ts | 2 +- .../emulator-pane/emulator-pane-toolbar.tsx | 2 +- .../emulator-pane/emulator-screen-surface.tsx | 2 +- ...bleRenderErrorBoundary.lazy-chunk.test.tsx | 113 --- ...ErrorBoundary.never-landed-reload.test.tsx | 85 --- .../RecoverableRenderErrorBoundary.tsx | 6 +- .../ChatUiHereCornerCard.test.tsx | 87 --- .../ChatUiHereFeatureTipVisual.tsx | 2 +- .../feature-tips/ChatUiHereTip.test.tsx | 98 --- .../feature-tips/FeatureTipDialogFrame.tsx | 2 +- .../feature-tips/FeatureTipsModal.tsx | 4 +- .../feature-tips/feature-tip-telemetry.ts | 2 +- .../AgentCapabilitiesSetupAction.tsx | 6 +- .../AgentCapabilityStatusBadges.tsx | 2 +- .../AgentsOrchestrationVisual.tsx | 2 +- .../BrowserAgentVerificationPane.tsx | 2 +- .../feature-wall/BrowserPricingStoryboard.tsx | 2 +- .../feature-wall/BrowserStoryboardWindow.tsx | 2 +- .../feature-wall/EditorAnimatedVisual.tsx | 2 +- .../feature-wall/FeatureWallBody.tsx | 2 +- .../feature-wall/FeatureWallBrowserAction.tsx | 4 +- .../feature-wall/FeatureWallClickRing.tsx | 2 +- .../feature-wall/FeatureWallPreview.tsx | 2 +- .../feature-wall/FeatureWallRail.tsx | 2 +- .../FeatureWallSetupChecklist.tsx | 6 +- .../FeatureWallSetupStepVisuals.tsx | 2 +- .../feature-wall/FeatureWallTourPanel.tsx | 2 +- .../feature-wall/FeatureWallTourSurface.tsx | 6 +- .../FullDiskAccessSetupPrompt.tsx | 2 +- .../ReviewNotesAnimatedVisual.tsx | 2 +- .../feature-wall/ReviewShipAnimatedVisual.tsx | 2 +- .../feature-wall/WorkbenchAnimatedVisual.tsx | 2 +- .../feature-wall/WorkbenchTerminalPanes.tsx | 2 +- .../WorkbenchTerminalSplitMenu.tsx | 2 +- .../feature-wall/WorkspacesAnimatedVisual.tsx | 2 +- .../OrchestrationPage.tsx | 2 +- .../agents-orchestration/StatusesPage.tsx | 4 +- .../UsageAccountsCard.tsx | 6 +- .../agents-orchestration/UsagePage.tsx | 2 +- .../orchestration-cards.tsx | 2 +- .../feature-wall/connect-integration-step.tsx | 2 +- .../feature-wall/review-notes-diff-rows.tsx | 2 +- .../feature-wall/review-pr-view-animation.ts | 2 +- .../use-editor-animated-visual-animation.ts | 2 +- ...use-feature-wall-tour-keyboard-shortcut.ts | 2 +- .../use-feature-wall-tour-telemetry.ts | 2 +- .../use-workbench-terminal-storyboard.ts | 2 +- .../floating-workspace-availability.ts | 2 +- .../use-floating-terminal-create-actions.ts | 4 +- .../use-floating-terminal-focus-lifecycle.ts | 2 +- .../discuss-item/comment-reply-form.tsx | 2 +- .../conversation-tab-activity.tsx | 2 +- .../conversation-tab-comment-card.tsx | 2 +- .../discuss-item/conversation-tab.tsx | 4 +- .../discuss-item/gh-comment-composer.tsx | 2 +- .../gh-edit-section-assignees.tsx | 2 +- .../gh-edit-section-labels.tsx | 2 +- .../gh-edit-section-status-popover.tsx | 2 +- .../github-labels-settings-link.tsx | 2 +- .../checks-tab-check-details.tsx | 2 +- .../checks-tab-check-row.tsx | 2 +- .../checks-tab-header.tsx | 2 +- .../inspect-pull-request/checks-tab.tsx | 2 +- .../pr-actions-merge-menu.tsx | 2 +- .../land-pull-request/pr-actions-panel.tsx | 4 +- .../pr-reviewers-picker-row.tsx | 2 +- .../github-item-dialog-issue-header.tsx | 2 +- .../github-project/ProjectBoard.tsx | 2 +- .../github-project/ProjectBoardCard.tsx | 2 +- .../ProjectCellRepositoryEditors.tsx | 2 +- .../ProjectCellSelectionEditors.tsx | 2 +- .../github-project/ProjectGroupHeader.tsx | 2 +- .../github-project/ProjectPickerPanels.tsx | 2 +- .../github-project/ProjectRoadmap.tsx | 2 +- .../github-project/ProjectRoadmapBar.tsx | 2 +- .../components/github-project/ProjectRow.tsx | 2 +- .../github-project/ProjectViewList.tsx | 2 +- .../github-project/ProjectViewStates.tsx | 2 +- .../github-project/ProjectViewToolbar.tsx | 2 +- .../slug-dialog/AssigneesEditor.tsx | 2 +- .../slug-dialog/LabelsEditor.tsx | 2 +- .../components/github/CommentCodeContext.tsx | 4 +- .../components/github/CommentReactions.tsx | 2 +- .../github/GitHubMarkdownComposer.tsx | 6 +- .../github/IssueSourceIndicator.tsx | 2 +- .../components/github/IssueSourceSelector.tsx | 2 +- .../components/github/PRAssigneesPanel.tsx | 4 +- .../components/github/PRFilterDropdowns.tsx | 2 +- .../src/components/github/PRFilterPickers.tsx | 2 +- .../components/github/PRFilterSections.tsx | 2 +- .../components/github/PRViewedCheckbox.tsx | 2 +- .../github/github-image-upload.test.ts | 68 -- .../components/github/github-image-upload.ts | 2 +- .../github-markdown-composer-tabbar.tsx | 2 +- .../github/github-markdown-image-input.tsx | 2 +- .../github/github-rate-limit-display.tsx | 4 +- .../components/github/github-user-avatar.tsx | 2 +- .../github/work-item-state-badge.tsx | 2 +- .../components/gitlab-item-dialog-parts.tsx | 2 +- .../gitlab-description-tab.tsx | 2 +- .../gitlab-item-dialog-view.tsx | 2 +- .../gitlab/gitlab-rate-limit-display.tsx | 4 +- .../components/host-picker/HostCombobox.tsx | 2 +- .../components/integration-status-pill.tsx | 2 +- .../integration-card-presentation.tsx | 2 +- .../presentation/integration-card-shell.tsx | 2 +- .../bitbucket-credentials-dialog.tsx | 2 +- .../bitbucket-integration-card.tsx | 2 +- .../src/components/jira-connect-dialog.tsx | 4 +- .../components/landing-preflight-runtime.ts | 4 +- .../src/components/linear-api-key-dialog.tsx | 4 +- .../linear-custom-view-table-content.tsx | 2 +- ...linear-issue-attribute-filter-sections.tsx | 2 +- .../linear-item-drawer-comment-footer.tsx | 7 +- .../linear-item-drawer-edit-chips-layout.tsx | 2 +- ...ear-item-drawer-edit-properties-layout.tsx | 2 +- .../src/components/linear-priority-icon.tsx | 2 +- .../linear-project-table-content.tsx | 2 +- .../linear-project-view-surfaces.tsx | 2 +- .../src/components/linear-scope-selector.tsx | 2 +- .../link-actions/LinkActionPopover.tsx | 4 +- .../machine-identity/MachineNameField.tsx | 4 +- .../MacosUpdateRequirementHost.tsx | 6 +- .../update-card/update-card-error-model.ts | 2 +- .../components/markdown-github-callout.tsx | 2 +- .../src/components/mobile/MobileHero.tsx | 2 +- .../mobile/MobileHeroPairingStep.tsx | 2 +- .../src/components/mobile/PhoneCarousel.tsx | 4 +- .../mobile/WindowsFirewallNotice.tsx | 2 +- .../mobile-relay-mint-failure-notice.tsx | 4 +- .../MobilePairingConnectionOptions.tsx | 2 +- .../pairing/MobilePairingPathOption.tsx | 2 +- .../mobile/pairing/MobileRelayBetaNotice.tsx | 2 +- .../components/mobile/slides/HomeSlide.tsx | 2 +- .../mobile/slides/WorktreeListSlide.tsx | 2 +- .../native-chat/NativeChatResolvedView.tsx | 2 +- .../NativeChatStructuredSession.tsx | 2 +- .../appearance/use-native-chat-font-size.ts | 2 +- .../native-chat-session-attachment-drop.ts | 2 +- .../controls/NativeChatComposerActions.tsx | 2 +- .../controls/NativeChatComposerNotices.tsx | 2 +- .../controls/native-chat-composer-notice.ts | 2 +- .../editor/NativeChatComposerField.tsx | 2 +- .../editor/native-chat-mounted-editor-view.ts | 2 +- .../native-chat-composer-focus-intent.ts | 2 +- ...native-chat-composer-reveal-focus.test.tsx | 643 ------------------ .../use-native-chat-composer-reveal-focus.ts | 2 +- .../pickers/NativeChatAutocompleteMenus.tsx | 2 +- .../turns/NativeChatWorkingStatus.tsx | 2 +- .../host-access/NativeChatInstallHero.tsx | 2 +- .../host-access/NativeChatInstallTile.tsx | 2 +- .../native-chat-host-setup.test.tsx | 6 +- .../host-access/native-chat-host-setup.ts | 4 +- .../actions/native-chat-file-link-toasts.ts | 2 +- .../markdown/NativeChatMarkdown.tsx | 2 +- .../markdown/code/NativeChatCodeBlock.tsx | 2 +- .../images/NativeChatImageAttachments.tsx | 2 +- .../media/images/native-chat-image-copy.ts | 2 +- .../text/NativeChatTextAttachmentPreview.tsx | 2 +- .../text/NativeChatTextAttachments.test.tsx | 166 ----- .../text/native-chat-text-attachment-read.ts | 4 +- .../actions/NativeChatAgentControls.tsx | 2 +- .../messages/actions/NativeChatCopyButton.tsx | 2 +- .../diagnostics/NativeChatProviderDetails.tsx | 2 +- .../messages/notices/NativeChatNoticeRow.tsx | 2 +- .../NativeChatPullRequestEventLine.tsx | 2 +- .../rows/NativeChatAgentMessageCard.tsx | 2 +- .../rows/NativeChatAssistantMessageRow.tsx | 2 +- .../rows/NativeChatMessageTimestamp.tsx | 2 +- .../messages/rows/NativeChatTranscriptRow.tsx | 2 +- .../rows/NativeChatUserMessageFold.tsx | 2 +- .../rows/NativeChatUserMessageRow.tsx | 2 +- .../paste/use-native-chat-paste-bridge.ts | 2 +- .../paste/use-native-chat-pasted-text-file.ts | 2 +- .../prompts/NativeChatApprovalCard.tsx | 2 +- .../prompts/NativeChatAwaitingInputRow.tsx | 2 +- .../prompts/NativeChatQuestionCard.tsx | 2 +- .../prompts/NativeChatQuestionOption.tsx | 2 +- .../queue/NativeChatQueuedMessageCard.tsx | 2 +- .../queue/NativeChatQueuedMessageList.tsx | 2 +- .../goals/NativeChatThreadGoalBanner.tsx | 2 +- .../controls/NativeChatSessionOptionRows.tsx | 2 +- .../NativeChatSessionOptionWriteGroup.tsx | 2 +- .../read/native-chat-session-transport.ts | 2 +- .../subagents/NativeChatSubagentRun.tsx | 2 +- .../NativeChatSubagentSectionHead.tsx | 2 +- .../telemetry/native-chat-telemetry.ts | 2 +- .../tools/diffs/NativeChatDiffCard.tsx | 2 +- .../tools/diffs/NativeChatDiffView.tsx | 2 +- .../tools/lines/NativeChatToolAnnotations.tsx | 2 +- .../tools/lines/NativeChatToolFileTarget.tsx | 2 +- .../tools/lines/NativeChatToolIcon.tsx | 2 +- .../tools/lines/NativeChatToolLine.tsx | 2 +- .../tools/lines/NativeChatToolOutput.tsx | 2 +- .../NativeChatOpenedPullRequestCard.tsx | 2 +- .../tools/runs/NativeChatToolRun.tsx | 2 +- .../tools/runs/NativeChatToolRunMembers.tsx | 4 +- .../tasks/NativeChatBackgroundTaskRun.tsx | 2 +- .../tools/tasks/NativeChatTaskList.tsx | 2 +- .../usage/NativeChatContextUsageRing.tsx | 2 +- .../find/use-native-chat-find.test.tsx | 279 -------- .../viewport/find/use-native-chat-find.ts | 2 +- .../viewport/rail/NativeChatMessageRail.tsx | 2 +- ...ativeChatStructuredWorkerLifetimeStrip.tsx | 2 +- .../src/components/network/AddressPicker.tsx | 2 +- .../new-workspace/ComposerBranchChoice.tsx | 2 +- .../ComposerParentWorktreePicker.tsx | 4 +- .../NewWorkspaceComposerAdvancedSection.tsx | 2 +- .../NewWorkspaceComposerAgentSection.tsx | 2 +- .../NewWorkspaceComposerFooter.tsx | 2 +- .../NewWorkspaceComposerNameSection.tsx | 2 +- .../NewWorkspaceComposerProjectSection.tsx | 2 +- .../new-workspace/ProjectCombobox.tsx | 2 +- .../new-workspace/ProjectComboboxRow.tsx | 2 +- .../new-workspace/RunTargetCombobox.tsx | 2 +- .../new-workspace/RunTargetComboboxRow.tsx | 2 +- .../new-workspace/RunTargetField.tsx | 2 +- .../new-workspace/RunTargetSubmenus.tsx | 2 +- .../new-workspace/SetProjectLocationForms.tsx | 2 +- .../SmartWorkspaceSourceResultList.tsx | 2 +- .../new-workspace/WorkspaceSetupSection.tsx | 2 +- .../smart-workspace-name-field-surface.tsx | 2 +- .../smart-workspace-name-input-surface.tsx | 2 +- .../onboarding/AgentPickerButton.tsx | 2 +- .../src/components/onboarding/AgentStep.tsx | 4 +- .../onboarding/AgentStepSurfaceSections.tsx | 4 +- .../onboarding/AgentSurfaceStep.tsx | 2 +- .../onboarding/FeatureSetupInlineTerminal.tsx | 2 +- .../onboarding/IntegrationsStep.tsx | 4 +- .../components/onboarding/OnboardingFlow.tsx | 9 +- ...eCommandTerminal.command-finished.test.tsx | 135 ---- .../onboarding/OnboardingLanguageSwitcher.tsx | 6 +- .../onboarding/ThemeLookCarousel.tsx | 2 +- .../components/onboarding/ThemeModeTiles.tsx | 2 +- .../src/components/onboarding/ThemeStep.tsx | 2 +- .../onboarding/WindowsTerminalStep.tsx | 2 +- .../onboarding/agent-picked-payload.ts | 3 +- .../onboarding/onboarding-feature-setup.ts | 2 +- .../onboarding-folder-agent-startup.ts | 2 +- .../onboarding-project-checklist.ts | 2 +- .../onboarding/theme-chrome-preview.tsx | 2 +- .../onboarding/use-onboarding-flow-actions.ts | 4 +- .../use-onboarding-flow-persistence.ts | 4 +- .../use-onboarding-flow-telemetry.ts | 2 +- .../onboarding/use-onboarding-flow.ts | 6 +- .../orchestration-crew/CrewAgentIcons.tsx | 2 +- .../orchestration-crew/CrewRowDetails.tsx | 2 +- .../orchestration-crew/CrewRowView.tsx | 2 +- .../OrchestrationCrewPanel.tsx | 2 +- .../PlainSshTerminalPanels.tsx | 2 +- .../plugins/PluginContributionIcon.tsx | 2 +- .../plugins/frames/plugin-frame-element.tsx | 2 +- .../plugins/list/PluginCatalogEmptyState.tsx | 2 +- .../components/plugins/list/PluginRowArt.tsx | 2 +- .../components/plugins/page/PluginAuthor.tsx | 2 +- .../plugins/view/PluginShotReplayMount.tsx | 2 +- .../components/ports/WorkspacePortScanner.tsx | 7 +- .../src/components/pr-check-empty-details.tsx | 2 +- .../pull-request-page/actions/panel.tsx | 4 +- .../pull-request-page/checks/details.tsx | 2 +- .../pull-request-page/checks/layouts.tsx | 2 +- .../pull-request-page/checks/row.tsx | 2 +- .../pull-request-page/checks/toolbar.tsx | 2 +- .../pull-request-page/comments/composer.tsx | 4 +- .../pull-request-page/comments/reply-form.tsx | 4 +- .../conversation/comment-card.tsx | 2 +- .../conversation/comments-list.tsx | 2 +- .../conversation/description.tsx | 2 +- .../pull-request-page/conversation/tab.tsx | 4 +- .../pull-request-page/edit/section.tsx | 4 +- .../pull-request-page/files/view-restore.ts | 2 +- .../pull-request-page/mentions/textarea.tsx | 2 +- .../pull-request-page/page/header.tsx | 2 +- .../reviewers/picker-row.tsx | 2 +- .../src/components/repo/CreateFromPicker.tsx | 2 +- .../components/repo/NestedRepoChecklist.tsx | 2 +- .../src/components/repo/RepoBadgeLabel.tsx | 2 +- .../src/components/repo/RepoCombobox.tsx | 4 +- .../components/repo/repo-fork-indicator.tsx | 2 +- .../src/components/repo/repo-icon.tsx | 2 +- .../right-sidebar/activity-bar-buttons.tsx | 2 +- .../accounts/AiVaultSessionAccount.tsx | 2 +- .../ai-vault/panel/AiVaultPanelControls.tsx | 2 +- .../scanning/AiVaultScanProgressStrip.tsx | 2 +- .../scanning/ai-vault-saved-session-lists.ts | 2 +- ...-session-conditional-refresh-hook.test.tsx | 314 --------- .../ai-vault-session-publication-gate.ts | 5 +- .../ai-vault/search/use-ai-vault-search.ts | 2 +- .../ai-vault-session-cli-fork.ts | 2 +- .../session-list/AiVaultSessionDetails.tsx | 2 +- .../session-list/AiVaultSessionRow.tsx | 2 +- .../session-list/AiVaultSessionSubagents.tsx | 2 +- .../AiVaultSessionVirtualList.tsx | 2 +- .../session-list/AiVaultVirtualRow.tsx | 2 +- .../SessionRowTrailingActions.tsx | 4 +- .../session-list/ai-vault-session-time.tsx | 2 +- .../comments/comment-controls.tsx | 2 +- .../checks-panel/comments/comment-group.tsx | 2 +- .../checks-panel/comments/comment-row.tsx | 2 +- .../checks-panel/comments/comments-list.tsx | 2 +- .../comments/pr-comment-presentation.ts | 2 +- .../comments/right-panel-comment-composer.tsx | 2 +- .../comments/use-comments-list-state.tsx | 2 +- .../checks-panel/logs/check-job-log-tail.tsx | 2 +- .../logs/use-check-log-reading-position.ts | 2 +- .../presentation/check-run-details.tsx | 2 +- .../presentation/check-run-time.tsx | 2 +- .../checks-panel/presentation/checks-list.tsx | 4 +- .../presentation/review-header.tsx | 2 +- .../presentation/triage-strip.tsx | 2 +- .../checks-panel-pr-refresh-breadcrumb.ts | 3 +- .../refresh/use-checks-detail-timer.ts | 2 +- .../use-checks-panel-foreground-effects.tsx | 2 +- .../refresh/use-checks-panel-polling.test.tsx | 104 --- .../use-checks-panel-terminal-worktree.ts | 2 +- .../content-search/SearchResultItems.tsx | 2 +- .../content-search/useFileSearchRunner.ts | 2 +- .../mutations/useFileExplorerImport.ts | 2 +- .../file-explorer/panel/FileExplorer.tsx | 2 +- .../panel/FileExplorerBackgroundMenu.tsx | 2 +- .../file-explorer/panel/FileExplorerRow.tsx | 4 +- .../panel/FileExplorerToolbar.tsx | 2 +- .../panel/FileExplorerVirtualRows.tsx | 2 +- .../panel/file-explorer-read-error.ts | 2 +- .../panel/file-explorer-row-file-transfer.ts | 2 +- .../use-file-explorer-background-menu.ts | 2 +- .../tree/FileExplorerFilesTreePane.tsx | 2 +- .../folder-workspace-repo-worktree-create.ts | 4 +- .../right-sidebar/host-scoped-ports-panel.tsx | 4 +- .../creation/CreateHostedReviewBasePicker.tsx | 2 +- .../creation/CreateHostedReviewComposer.tsx | 2 +- .../CreateHostedReviewComposerFields.tsx | 2 +- .../FolderWorkspacePrChecksPanel.tsx | 2 +- .../FolderWorkspacePrChecksRow.tsx | 4 +- .../provider-actions/GitHubPRStackMap.tsx | 2 +- .../provider-actions/HostedReviewActions.tsx | 2 +- .../src/components/right-sidebar/index.tsx | 6 +- .../right-sidebar/local-port-section.tsx | 2 +- .../local-workspace-ports-panel.tsx | 4 +- .../plugin-panels/PluginPanel.tsx | 2 +- .../health/plugin-panel-watchdog.ts | 5 +- .../right-sidebar-panel-content.tsx | 4 +- ...ebar-titlebar-drag-regions.render.test.tsx | 397 ----------- .../right-sidebar-top-activity-bar.tsx | 2 +- .../ai/SourceControlAgentActionDialogForm.tsx | 2 +- .../commit/commit-action-menu.tsx | 2 +- .../commit/commit-message-composer.tsx | 2 +- .../source-control/commit/commit-notices.tsx | 2 +- .../source-control/commit/commit-shortcut.ts | 2 +- .../source-control-entry-failure-toast.ts | 4 +- .../source-control/commit/use-bulk-actions.ts | 2 +- .../commit/use-discard-confirmation.ts | 2 +- .../source-control/listing/action-button.tsx | 2 +- .../listing/branch-entry-row.tsx | 2 +- .../source-control/listing/conflict-badge.tsx | 2 +- .../listing/conflict-status-cards.tsx | 2 +- .../listing/diff-line-counts.tsx | 2 +- .../source-control/listing/section-header.tsx | 2 +- .../listing/sequencer-recovery-controls.tsx | 4 +- .../listing/submodule-placeholder-row.tsx | 2 +- .../listing/tree-directory-rows.tsx | 2 +- .../listing/uncommitted-entry-row.tsx | 2 +- .../source-control/notes/notes-shelf.tsx | 2 +- .../panel/branch-context-row.tsx | 2 +- .../panel/header-review-actions.tsx | 2 +- .../source-control/panel/header-toolbar.tsx | 2 +- .../panel/source-control-fix-split-button.tsx | 2 +- .../review/hosted-review-header-chrome.tsx | 2 +- .../source-control-primary-action-tooltip.tsx | 2 +- .../review/use-review-push-target.ts | 2 +- .../source-control/sync/git-history-panel.tsx | 2 +- .../source-control/sync/git-history-row.tsx | 2 +- .../sync/git-status-file-watch-refresh.ts | 4 +- .../sync/git-status-push-signal-refresh.ts | 4 +- .../sync/stale-conflict-operation-poll.ts | 5 +- .../source-control/sync/use-branch-compare.ts | 4 +- .../sync/use-git-status-window-presence.ts | 2 +- .../sync/use-refresh-on-host-contact.ts | 2 +- .../use-worktree-git-status-observation.ts | 7 +- .../right-sidebar/ssh-forwarded-port-row.tsx | 2 +- .../ssh-forwarded-ports-section.tsx | 4 +- .../right-sidebar/ssh-ports-panel.tsx | 4 +- .../right-sidebar/ssh-unrestored-port-row.tsx | 2 +- .../use-session-search-status.ts | 2 +- .../settings/accounts/AccountsPane.tsx | 2 +- .../accounts/orca/OrcaAccountSettingsPane.tsx | 2 +- .../providers/CursorAccountsSection.tsx | 2 +- .../providers/GrokAccountsSection.tsx | 2 +- .../accounts/scope/accounts-pane-runtime.ts | 2 +- .../accounts/usage/MiniMaxAccountsSection.tsx | 2 +- .../usage/ZcodePlanAccountsSection.tsx | 4 +- .../AdvancedNetworkSettingsSection.tsx | 2 +- .../agents/launch/AgentCatalogRow.tsx | 2 +- .../agents/launch/AgentDefaultSetting.tsx | 2 +- .../agents/launch/AgentDetectionCatalog.tsx | 2 +- .../permissions/AgentPermissionsSetting.tsx | 2 +- .../CodexTerminalServerIsolationSetting.tsx | 2 +- .../surfaces/AgentDefaultSurfaceSection.tsx | 2 +- .../settings/appearance/AppIconSelector.tsx | 2 +- .../AppearanceAdvancedDisclosure.tsx | 2 +- .../settings/appearance/AppearancePane.tsx | 4 +- .../settings/appearance/AppearanceSection.tsx | 2 +- .../appearance-system-presence-search.ts | 4 +- .../appearance/fonts/installed-font-list.ts | 2 +- .../themes/AppearanceThemeControl.tsx | 2 +- .../artifacts/ArtifactsSettingsPane.tsx | 2 +- .../settings/browser/BrowserPane.tsx | 4 +- .../browser/BrowserUserAgentSetting.tsx | 2 +- .../settings/browser/browser-search.ts | 2 +- .../BrowserClientHostedRemoteSetting.tsx | 2 +- .../BrowserSshWorkspaceRoutingSetting.tsx | 2 +- .../BrowserTerminalLinkActionsSetting.tsx | 2 +- .../settings/browser/use/BrowserUsePane.tsx | 2 +- .../chat/ChatPermissionSetting.test.tsx | 124 ---- .../chat/use-chat-permission-settings.ts | 2 +- .../LocalNetworkConnectionTest.tsx | 2 +- .../FloatingWorkspacePane.tsx | 2 +- .../form-controls/SettingsFormControls.tsx | 2 +- .../SettingsSearchableSelect.tsx | 2 +- .../GeneralWorkspaceSettingsSection.tsx | 2 +- .../settings/general/OpenInMenuSetting.tsx | 2 +- .../general/WorkspaceDirectorySetting.tsx | 2 +- .../settings/host-rows/ServerListRow.tsx | 2 +- .../use-settings-host-navigation.ts | 2 +- .../emulator/MobileEmulatorExamples.tsx | 2 +- .../emulator/MobileEmulatorSettingsPane.tsx | 2 +- .../pairing/MobilePairingSetupSection.tsx | 2 +- .../settings/navigation/SettingsSidebar.tsx | 7 +- .../agent-skill-nav-install-status.ts | 2 +- .../navigation}/open-plugins-settings.ts | 2 +- .../settings-navigation-foundations.ts | 5 +- .../navigation}/settings-navigation-types.ts | 4 +- .../use-settings-navigation-model.ts | 2 +- .../OrchestrationSkillAgentCoverage.tsx | 2 +- .../settings/page/orca-logo-settings-icon.tsx | 2 +- .../settings/page/settings-page-renderer.tsx | 2 +- .../settings/page/settings-view-model.ts | 2 +- .../page/use-settings-page-effects.ts | 2 +- .../settings/page/use-settings-store-model.ts | 4 +- .../settings/privacy/PrivacyPane.tsx | 6 +- .../privacy/diagnostics-bundle-error-copy.ts | 2 +- .../quick-commands/QuickCommandsList.tsx | 2 +- .../QuickCommandsScopeFilter.tsx | 2 +- .../hosts/RepositoryHostSetupsSection.tsx | 2 +- .../repository-host-add-project-steps.tsx | 2 +- .../identity/RepositoryIconColorSection.tsx | 2 +- .../identity/RepositoryIconTabs.tsx | 2 +- .../repository/mcp/McpConfigSection.tsx | 4 +- .../worktrees/WorktreeSymlinksSection.tsx | 2 +- .../worktrees/sparse-preset-settings-row.tsx | 2 +- .../server-rows/runtime-server-row-menu.tsx | 5 +- .../server-rows/runtime-server-row.tsx | 2 +- .../section-layout/SearchableSetting.tsx | 2 +- .../settings/section-layout/SettingsGroup.tsx | 2 +- .../section-layout/SettingsSection.tsx | 2 +- .../SessionHistoryComputerRow.tsx | 2 +- .../SessionHistorySettingsPane.test.tsx | 150 ---- .../SessionHistorySettingsPane.tsx | 2 +- .../SessionSearchAdvancedSection.tsx | 2 +- .../shortcuts/ShortcutCommandBlock.tsx | 2 +- .../settings/shortcuts/ShortcutFilterRail.tsx | 2 +- .../shortcuts/ShortcutRecorderButton.tsx | 2 +- .../settings/shortcuts/ShortcutRowsList.tsx | 2 +- .../sharing/ShareSkillsSettingsPane.tsx | 2 +- .../AutoRenameBranchFromWorkSetting.tsx | 2 +- .../src/components/settings/ssh/SshPane.tsx | 2 +- .../server-actions/SshTargetServerStatus.tsx | 2 +- .../settings/ssh/targets/SshTargetCard.tsx | 2 +- .../ssh/targets/use-ssh-bulk-remove.ts | 2 +- .../settings/tasks/TaskSourceProviderCard.tsx | 2 +- .../tasks/TaskSourceShowInTasksStep.tsx | 2 +- .../settings/tasks/TaskSourceStepRow.tsx | 2 +- .../TerminalPane.section-lifetime.test.tsx | 211 ------ .../settings/terminal/TerminalPane.tsx | 4 +- .../appearance/TerminalAppearanceSection.tsx | 2 +- .../appearance/TerminalWindowSection.tsx | 2 +- .../TerminalWindowsAdministratorSection.tsx | 2 +- .../terminal/themes/TerminalThemeSections.tsx | 2 +- .../terminal/themes/WarpThemeImportModal.tsx | 2 +- .../updates/GeneralRemoteServerUpdates.tsx | 5 +- .../updates/GeneralUpdateSettingsSection.tsx | 7 +- .../settings/updates/ReleaseBuildPicker.tsx | 2 +- .../updates/ReleaseChannelSection.tsx | 2 +- .../runtimes/EphemeralVmRuntimeRow.tsx | 2 +- .../voice/OpenAiCompatibleProviderDialog.tsx | 2 +- .../voice/OpenAiTranscriptionSettingsRow.tsx | 2 +- .../settings/voice/VoiceMicrophoneSetting.tsx | 6 +- .../components/settings/voice/VoicePane.tsx | 2 +- .../settings/voice/VoiceSpeechModelRow.tsx | 2 +- .../voice/VoiceSpeechModelSection.tsx | 2 +- .../voice/use-voice-microphone-test.ts | 2 +- .../settings/wsl/hosts/WslHostsSection.tsx | 2 +- .../setup-guide/SetupGuideProgressRing.tsx | 2 +- .../setup-guide/use-setup-guide-telemetry.ts | 2 +- .../shared/MacFolderAccessFixDialog.test.tsx | 103 --- .../shared/MacFolderAccessFixDialog.tsx | 2 +- .../sidebar/AgentDashboardSidebarEntry.tsx | 4 +- .../components/sidebar/CommentMarkdown.tsx | 2 +- .../sidebar/CommentMermaidBlock.tsx | 2 +- .../components/sidebar/HostRemoveDialog.tsx | 4 +- .../sidebar/LinearAgentSkillSetupPrompt.tsx | 4 +- .../sidebar/LocalGitToolchainScanBanner.tsx | 4 +- .../sidebar/MarkdownImageLightbox.tsx | 2 +- .../sidebar/SetupGuideSidebarEntry.tsx | 4 +- .../sidebar/SetupScriptCodeBlock.tsx | 2 +- .../sidebar/SetupScriptPromptCard.tsx | 4 +- .../sidebar/SetupScriptPromptCardViews.tsx | 2 +- .../sidebar/SetupScriptReviewDialog.tsx | 2 +- .../src/components/sidebar/SidebarHeader.tsx | 4 +- .../src/components/sidebar/SidebarNav.tsx | 6 +- .../sidebar/SidebarSettingsHelpMenu.tsx | 9 +- .../sidebar/SidebarTaskNavButton.tsx | 4 +- .../sidebar/comment-markdown-lazy.tsx | 4 +- .../ssh/AddRemoteHostSshConfigPicker.tsx | 2 +- .../wsl/AddWslHostDialogBody.tsx | 2 +- .../host-path-browser/RemoteFileBrowser.tsx | 2 +- .../RemoteFileBrowserBreadcrumbs.tsx | 2 +- .../RemoteFileBrowserEntryList.tsx | 2 +- src/renderer/src/components/sidebar/index.tsx | 6 +- .../project-added-default-checkout.ts | 2 +- .../completion/use-complete-git-repo-add.ts | 2 +- .../project-admission/create/useCreateRepo.ts | 2 +- .../dialog/AddRepoCreateStep.tsx | 2 +- .../dialog/AddRepoStartSteps.tsx | 2 +- .../project-admission/dialog/AddRepoSteps.tsx | 2 +- .../dialog/useAddRepoCloneFlow.ts | 2 +- .../dialog/useAddRepoLocalFolderFlow.ts | 2 +- .../dialog/useAddRepoServerPathFlow.ts | 2 +- .../host-selection/AddRepoHostSelector.tsx | 2 +- .../host-selection/AddRepoWslHostOffer.tsx | 2 +- .../use-add-repo-host-selection.ts | 4 +- .../nested-import/track-nested-folder-open.ts | 2 +- .../useAddRepoNestedImportFlow.ts | 2 +- .../setup-script-prompt-exposure-telemetry.ts | 2 +- .../sidebar/sidebar-activity-badge.tsx | 2 +- .../SidebarFeedbackDialog.test.tsx | 240 ------- .../SidebarFeedbackDialog.tsx | 4 +- .../SidebarFeedbackImageAttachments.tsx | 10 +- .../feedback-image-attachments.test.ts | 76 +-- .../feedback-image-attachments.ts | 16 +- .../feedback-image-shrink.ts | 0 .../use-feedback-image-drop.ts | 2 +- ...se-sidebar-feedback-environment-prefill.ts | 2 +- .../use-sidebar-feedback-images.ts | 2 +- .../sidebar/truncated-sidebar-label.tsx | 2 +- .../context-menu/WorktreeContextMenuView.tsx | 4 +- .../use-worktree-context-menu-model.tsx | 4 +- .../delete-worktree-preference-toast.ts | 2 +- .../parenting/WorktreeParentPickerPopover.tsx | 4 +- .../drag/WorkspaceKanbanPinDropTarget.tsx | 2 +- .../use-workspace-kanban-selection.ts | 2 +- .../surface/WorkspaceKanbanSettingsMenu.tsx | 2 +- .../viewport/WorkspaceKanbanStatusLane.tsx | 2 +- .../workspace-card/activity/CacheTimer.tsx | 4 +- .../activity/WorktreeCardAgents.tsx | 4 +- .../activity/prompt-cache-countdown-clock.ts | 2 +- .../worktree-card-compact-agent-row.tsx | 2 +- .../activity/worktree-card-compact-agents.tsx | 2 +- .../worktree-card-idle-child-agents.tsx | 2 +- .../use-worktree-card-activation-actions.ts | 3 +- .../use-worktree-card-lifecycle-effects.ts | 7 +- .../details/WorktreeCardDetailSection.tsx | 2 +- .../WorktreeCardHoverIdentityHeader.tsx | 2 +- .../host/WorktreeCardSshHostControl.tsx | 4 +- .../host/WorktreeHostContextBadge.tsx | 2 +- .../identity/AutoRenameFailedDialog.tsx | 4 +- .../identity/WorktreeLineageToggle.tsx | 2 +- .../identity/WorktreeTitleInlineRename.tsx | 2 +- .../identity/worktree-card-header-items.tsx | 2 +- .../metadata/WorktreeCardMetaBadges.tsx | 2 +- .../WorktreeCardMetadataStatusBadges.tsx | 2 +- .../metadata/WorktreeMetaDialogDraft.tsx | 7 +- .../review/WorkspaceAttachmentSearch.tsx | 2 +- .../review/WorktreeCardReferenceRow.tsx | 4 +- .../use-workspace-reference-details.test.tsx | 63 +- .../review/worktree-review-presentation.tsx | 2 +- .../surface/StatusIndicator.tsx | 2 +- .../surface/WorktreeCardStatusSlot.tsx | 6 +- .../surface/WorktreeCardVmStateBadge.tsx | 2 +- .../surface/worktree-card-parent-content.tsx | 2 +- .../surface/worktree-card-status-lane.tsx | 2 +- .../surface/worktree-card-surface.tsx | 2 +- .../template/card-primitives.tsx | 2 +- .../folder-workspace-composer-submit.ts | 3 +- .../WorkspaceStatusAppearancePopover.tsx | 2 +- .../presentation/WorktreeStatusMenuItems.tsx | 2 +- .../WorktreeVisibilityGlobalSettingsLink.tsx | 4 +- .../controls/FilterToggleRow.tsx | 2 +- .../controls/ProjectsCollapseAllButton.tsx | 4 +- .../drag/WorktreeSidebarDropIndicator.tsx | 2 +- .../ImportedWorktreesVisibilityLine.tsx | 2 +- .../listing/NewExternalWorktreesInboxLine.tsx | 2 +- .../rows/FolderPathStatusIndicator.tsx | 2 +- .../worktree-list/rows/HostSectionHeader.tsx | 2 +- .../worktree-list/rows/PendingWorktreeRow.tsx | 4 +- .../rows/ProjectHeaderActions.tsx | 2 +- .../rows/RepoScanUnavailableIndicator.tsx | 4 +- .../rows/RepoWslDistroStoppedIndicator.tsx | 4 +- .../worktree-list/rows/SectionHeader.tsx | 2 +- .../sidebar/worktree-list/rows/item-row.tsx | 2 +- .../rows/project-group-header-actions.tsx | 2 +- .../rows/virtual-row-dispatch.tsx | 2 +- .../WorktreeListScrollToTopButton.tsx | 2 +- .../src/components/skills/SkillsPage.tsx | 4 +- .../components/skills/catalog/SkillRow.tsx | 2 +- .../skills/catalog/SkillsFilterToolbar.tsx | 2 +- .../skills/catalog/SkillsPageHeader.tsx | 2 +- .../skills/catalog/SkillsSelectionHeader.tsx | 2 +- .../skills/catalog/skills-page-states.tsx | 2 +- .../skills/deletion/SkillDeleteResultBand.tsx | 2 +- .../freshness/SkillFreshnessStatusPill.tsx | 2 +- .../installation/SkillInstallAgentPicker.tsx | 2 +- .../installation/SkillInstallRiskNotice.tsx | 2 +- .../SkillInstallWorkspaceCombobox.tsx | 2 +- .../installation/SkillManagedInstallRow.tsx | 2 +- .../installation/SkillPackageChecklist.tsx | 2 +- .../skills/setup/AgentSkillSetupPanel.tsx | 2 +- .../setup/BundledSkillInstallStatus.tsx | 2 +- .../components/skills/setup/SkillSetupRow.tsx | 2 +- .../skills/setup/WslAgentSkillSetupPanel.tsx | 2 +- .../setup/WslLinearAgentSkillInstallCta.tsx | 2 +- .../setup/linear-agent-skill-install-cta.tsx | 2 +- .../sharing/SkillDescriptionDisclosure.tsx | 2 +- .../sharing/SkillSharePackageSummary.tsx | 2 +- .../components/sparse/SparsePresetChooser.tsx | 2 +- .../ssh-host-form/SshHostAdvancedFields.tsx | 2 +- .../open-standalone-terminal.ts | 4 +- .../status-bar/AccountSwitchRowItem.tsx | 2 +- .../status-bar/PluginsStatusSegment.tsx | 2 +- ...rtsStatusSegment.render-stability.test.tsx | 90 --- .../components/status-bar/ResourcePtyRow.tsx | 2 +- .../status-bar/SshHostFindPopover.tsx | 2 +- .../status-bar/StatusBarSurface.tsx | 4 +- .../status-bar/resource-usage-metrics.tsx | 2 +- .../resource-usage-popover-body.tsx | 2 +- .../resource-usage-popover-summary.tsx | 2 +- .../resource-usage-session-rows.tsx | 2 +- .../resource-usage-status-trigger.tsx | 2 +- .../status-bar/status-bar-runtime-targets.ts | 2 +- .../status-bar/use-resource-view-visible.ts | 5 +- .../status-bar/use-status-bar-controller.ts | 4 +- .../workspace-space-breakdown-list.tsx | 2 +- .../workspace-space-decision-hover-card.tsx | 2 +- .../workspace-space-manager-table.tsx | 2 +- .../status-bar/workspace-space-metrics.tsx | 2 +- .../workspace-space-selection-controls.tsx | 2 +- .../status-bar/workspace-space-treemap.tsx | 2 +- .../workspace-space-worktree-row.tsx | 7 +- .../src/components/tab-bar/BrowserTab.tsx | 6 +- .../src/components/tab-bar/EditorFileTab.tsx | 4 +- .../tab-bar/NativeChatNamingFailure.test.tsx | 144 ---- .../tab-bar/NativeChatNamingFailure.tsx | 4 +- .../components/tab-bar/QuickLaunchButton.tsx | 4 +- .../components/tab-bar/RecentTabSwitcher.tsx | 4 +- .../src/components/tab-bar/SortableTab.tsx | 4 +- .../components/tab-bar/TabBarCreateEntry.tsx | 4 +- .../tab-bar/TabBarCreateEntryRow.tsx | 2 +- .../tab-bar/TabBarQuickCommandItem.tsx | 2 +- .../tab-bar/TabBarQuickCommandsMenu.tsx | 2 +- .../tab-bar/TerminalTabLeadingIcon.tsx | 2 +- .../tab-bar/TerminalTabSplitMenuSection.tsx | 2 +- .../tab-bar/agent-menu-launch-facts.ts | 2 +- .../tab-bar/native-chat-cli-fork-run.ts | 4 +- .../tab-bar/tab-create-entry-action.ts | 4 +- .../tab-session-history-switch.test.ts | 6 +- .../tab-bar/use-allow-auto-close.ts | 2 +- ...use-new-tab-agent-menu-rows-owner.test.tsx | 53 +- .../tab-bar/use-tab-strip-slot-props.ts | 2 +- .../components/tab-group/TabGroupPanel.tsx | 6 +- .../src/components/task-page/ListChrome.tsx | 2 +- .../components/task-page/PaginationBar.tsx | 2 +- .../src/components/task-page/SourceBar.tsx | 2 +- .../task-page/github/AssigneesCell.tsx | 4 +- .../task-page/github/ChecksCell.tsx | 2 +- .../components/task-page/github/Filters.tsx | 4 +- .../task-page/github/IssueDialog.tsx | 2 +- .../task-page/github/IssueSelectors.tsx | 2 +- .../src/components/task-page/github/List.tsx | 4 +- .../components/task-page/github/MergeCell.tsx | 4 +- .../task-page/github/ModeControls.tsx | 2 +- .../task-page/github/ReviewCell.tsx | 4 +- .../task-page/github/ReviewerPicker.tsx | 2 +- .../src/components/task-page/github/Rows.tsx | 2 +- .../task-page/github/StatusCell.tsx | 4 +- ...ask-page-github-work-item-status-badge.tsx | 2 +- .../components/task-page/gitlab/Filters.tsx | 2 +- .../gitlab/use-task-page-gitlab-loading.ts | 2 +- .../src/components/task-page/jira/Filters.tsx | 4 +- .../components/task-page/jira/IssueDialog.tsx | 4 +- .../task-page/jira/task-page-jira-board.tsx | 2 +- .../jira/task-page-jira-issue-cells.tsx | 2 +- .../jira/task-page-jira-issue-list.tsx | 2 +- .../jira/task-page-jira-sort-controls.tsx | 2 +- .../use-task-page-jira-summary-generation.ts | 2 +- .../components/task-page/linear/Filters.tsx | 4 +- .../task-page/linear/IssueBoardColumns.tsx | 2 +- .../task-page/linear/IssueDialog.tsx | 2 +- .../task-page/linear/IssueTable.tsx | 2 +- .../task-page/linear/IssueToolbar.tsx | 2 +- .../task-page/linear/ProjectDialog.tsx | 4 +- .../task-page/linear/ProjectFields.tsx | 2 +- .../linear/task-page-linear-issue-model.tsx | 4 +- .../task-page/task-page-source-context.tsx | 2 +- .../task-page/use-task-page-store-bindings.ts | 4 +- .../task-project-source-combobox.tsx | 2 +- .../pane-top-banner/PaneTopWarningBanner.tsx | 2 +- .../terminal-clipboard-event-paste.ts | 2 +- .../use-terminal-context-menu-trigger.ts | 2 +- .../use-terminal-pane-store-bindings.ts | 2 +- .../startup/terminal-startup-timing.ts | 2 +- .../terminal-parser-handler-guard.test.ts | 2 +- .../terminal-parser-handler-guard.ts | 2 +- .../errors/terminal-error-ipc-unwrap.ts | 2 +- .../errors/terminal-spawn-error-display.ts | 2 +- .../errors/terminal-ssh-sign-in-error.ts | 2 +- .../input/file-drop/terminal-drop-shell.ts | 2 +- .../terminal-native-file-drop-destination.ts | 2 +- .../file-drop/terminal-native-file-drop.ts | 2 +- .../regular-terminal-focus-ownership.test.ts | 99 +-- .../focus/regular-terminal-focus-ownership.ts | 2 +- .../terminal-ios-hangul-preedit-fixture.ts | 2 +- .../input/terminal-pane-pane-input.ts | 2 +- .../ownership/use-layout-owner-pane-tree.ts | 2 +- .../split/terminal-pane-split-completion.ts | 2 +- .../terminal-remote-file-download-open.ts | 2 +- .../mobile-driver/MobileDriverOverlay.tsx | 2 +- .../mounting/terminal-pane-created-handler.ts | 2 +- .../mounting/terminal-pane-mount-cleanup.ts | 2 +- .../use-terminal-pane-mount-lifecycle.ts | 2 +- .../terminal-parking-e2e-overrides.ts | 2 +- .../use-terminal-tab-cold-parking.test.ts | 2 +- ...rminal-park-verdict-flip-telemetry.test.ts | 228 +------ .../terminal-park-verdict-flip-telemetry.ts | 2 +- .../codex-terminal-server-isolation-notice.ts | 2 +- .../pty-applied-size-read-e2e-delay.ts | 2 +- .../pty-connection/input/pty-input-forward.ts | 4 +- .../e2e-terminal-pty-harness.ts | 2 +- .../pty-connect-diagnostics.ts | 2 +- .../pty-connection/run-deferred-connect.ts | 7 +- .../spawn/main-resumed-sleep-pane.ts | 2 +- .../pty-dispatcher-push-reattach.test.ts | 81 --- .../delivery/terminal-delivery-watchdog.ts | 2 +- .../delivery/terminal-pty-ack-gate.ts | 2 +- .../pty-transport/ipc/ipc-pty-connect.ts | 2 +- .../ipc/terminal-pty-pre-spawn-e2e-barrier.ts | 2 +- .../output/pty-side-effect-pending-census.ts | 2 +- .../terminal-pane-surface-ownership.test.ts | 133 ---- ...-pty-transport-attach-subscription.test.ts | 401 ----------- .../terminal-freeze-breadcrumbs.ts | 2 +- ...l-pane-recovery-unsettled-fallback.test.ts | 100 --- .../recovery/terminal-pane-recovery.test.ts | 627 +---------------- .../recovery/terminal-pane-recovery.ts | 2 +- .../snapshots/replay-guard.test.ts | 175 +---- .../terminal-pane/snapshots/replay-guard.ts | 2 +- .../snapshots/saved-terminal-output-replay.ts | 2 +- .../worker-lifetime/WorkerAutoCloseBanner.tsx | 5 +- .../TerminalQuickCommandActionToggle.tsx | 2 +- .../TerminalQuickCommandAdvancedSection.tsx | 2 +- .../TerminalQuickCommandContentSection.tsx | 2 +- .../TerminalQuickCommandDialog.tsx | 5 +- src/renderer/src/components/ui/accordion.tsx | 2 +- src/renderer/src/components/ui/badge.tsx | 2 +- .../src/components/ui/button-group.tsx | 2 +- src/renderer/src/components/ui/button.tsx | 2 +- src/renderer/src/components/ui/card.tsx | 2 +- src/renderer/src/components/ui/checkbox.tsx | 2 +- .../src/components/ui/collapsible.tsx | 2 +- .../src/components/ui/color-picker.tsx | 2 +- src/renderer/src/components/ui/command.tsx | 4 +- .../src/components/ui/context-menu.tsx | 4 +- src/renderer/src/components/ui/dialog.tsx | 4 +- .../src/components/ui/dropdown-menu.tsx | 4 +- src/renderer/src/components/ui/hover-card.tsx | 2 +- src/renderer/src/components/ui/input.tsx | 2 +- src/renderer/src/components/ui/label.tsx | 2 +- src/renderer/src/components/ui/popover.tsx | 4 +- src/renderer/src/components/ui/progress.tsx | 2 +- .../src/components/ui/radio-group.tsx | 2 +- .../src/components/ui/repo-multi-combobox.tsx | 2 +- .../src/components/ui/scroll-area.tsx | 2 +- src/renderer/src/components/ui/select.tsx | 4 +- src/renderer/src/components/ui/separator.tsx | 2 +- src/renderer/src/components/ui/sheet.tsx | 4 +- src/renderer/src/components/ui/slider.tsx | 2 +- src/renderer/src/components/ui/switch.tsx | 2 +- src/renderer/src/components/ui/tabs.tsx | 2 +- src/renderer/src/components/ui/textarea.tsx | 2 +- .../src/components/ui/toaster-modal-layer.ts | 2 +- .../src/components/ui/toggle-group.tsx | 2 +- src/renderer/src/components/ui/toggle.tsx | 2 +- src/renderer/src/components/ui/tooltip.tsx | 2 +- .../ui/use-overlay-focus-scope.test.tsx | 47 -- .../components/ui/use-overlay-focus-scope.ts | 2 +- .../use-terminal-editor-close-foundation.ts | 2 +- ...tor-close-foundation.window-close.test.tsx | 26 +- .../components/use-terminal-parking-pass.ts | 4 +- .../use-terminal-window-lifecycle.ts | 6 +- .../window-close-request-coordinator.ts | 2 +- ...orkspace-cleanup-candidate-row-details.tsx | 2 +- ...rkspace-cleanup-candidate-row-trailing.tsx | 2 +- .../workspace-cleanup-candidate-row.tsx | 2 +- .../workspace-cleanup-confirm-remove.tsx | 2 +- .../workspace-cleanup-confirm-stop-agents.tsx | 2 +- .../workspace-cleanup-dialog-header.tsx | 2 +- .../workspace-cleanup-facet-controls.tsx | 2 +- .../workspace-cleanup-filter-bar.tsx | 2 +- .../workspace-cleanup-metadata-chip.tsx | 2 +- .../workspace-cleanup-status-pill.tsx | 2 +- .../WorkspaceEmojiPickerButton.tsx | 4 +- .../WorkspaceEmojiSuggestionPopover.tsx | 2 +- .../PendingNativeChatCreation.tsx | 4 +- .../worktree-creation/VmBuildFailureCard.tsx | 2 +- .../worktree-creation/VmBuildReveal.tsx | 2 +- .../worktree-creation/VmBuildSheet.tsx | 2 +- .../worktree-creation/WorktreeSetupSteps.tsx | 2 +- .../worktree-creation-clock.ts | 2 +- ...worktree-jump-palette-selection-actions.ts | 2 +- ...ee-jump-palette-browser-simulator-rows.tsx | 2 +- .../worktree-jump-palette-entry.tsx | 2 +- .../worktree-jump-palette-extra-row.tsx | 2 +- .../worktree-jump-palette-primitives.tsx | 2 +- ...ktree-jump-palette-project-action-rows.tsx | 2 +- ...orktree-jump-palette-workspace-tab-row.tsx | 2 +- .../worktree-jump-palette-worktree-row.tsx | 2 +- .../components/wsl-host-mark/WslHostMark.tsx | 4 +- .../wsl-host-stopped/WslHostStoppedNotice.tsx | 2 +- .../components/wsl-move/WslMoveResultBody.tsx | 2 +- .../wsl-move/use-wsl-move-quit-guard.test.ts | 36 +- .../wsl-move/use-wsl-move-quit-guard.ts | 2 +- .../wsl-move/wsl-move-dialog-close.ts | 2 +- .../wsl-move/wsl-move-dialog-store.test.ts | 111 +-- .../wsl-move/wsl-move-dialog-store.ts | 4 +- .../crash-reporting}/crash-breadcrumb-data.ts | 2 +- .../crash-breadcrumb-recorder.ts | 2 +- .../crash-reporting}/crash-diagnostics.ts | 5 +- .../react-error-boundary-reporting.ts | 6 +- .../memory/renderer-memory-profile.test.ts | 32 + .../memory}/renderer-memory-profile.ts | 0 .../memory}/renderer-memory-sampling.ts | 10 +- .../state-collection-byte-estimate.test.ts | 0 .../memory}/state-collection-byte-estimate.ts | 0 .../react-commit-cascade-observer.ts | 6 +- ...eact-commit-cascade-store-write-samples.ts | 0 .../react-commit-cascade-telemetry.ts | 10 +- .../react-devtools-commit-hook-shim.ts | 0 .../react-commits}/react-grab-dev-gate.ts | 0 .../telemetry}/feature-education-telemetry.ts | 14 +- .../telemetry}/telemetry.ts | 9 +- .../usePrimarySelectionPaste.ts | 2 +- .../src/hooks/composer-drop-failure-toast.ts | 2 +- .../composer-state/attachment-drop-state.ts | 4 +- .../composer-state/quick-startup-plan.ts | 3 +- .../agent-status-event-applicator.ts | 2 +- .../ssh-managed-server-refusal-toast.ts | 4 +- .../workspace-shortcut-ipc-bridge.ts | 4 +- ...settings-navigation-capability-sections.ts | 2 +- .../settings-navigation-interface-sections.ts | 2 +- .../settings-navigation-remote-sections.ts | 2 +- .../settings-navigation-workflow-sections.ts | 2 +- .../runtime/use-onboarding-skill-runtime.ts | 2 +- .../src/hooks/use-list-multi-selection.ts | 2 +- src/renderer/src/hooks/use-now.ts | 2 +- .../src/hooks/use-window-stream-visibility.ts | 2 +- .../src/hooks/useGitHubOperationScope.ts | 4 +- ...IpcEvents-agent-status-hook-titles.test.ts | 635 ----------------- ...useMacTccAttributionSeveredNotice.test.tsx | 528 -------------- .../useMacTccAttributionSeveredNotice.ts | 4 +- .../src/hooks/useMacosTccPromptNotice.ts | 4 +- .../src/hooks/useReadOnScreenAttention.ts | 4 +- .../hooks/useSettingsNavigationMetadata.ts | 10 +- .../useWindowsTerminalCapabilityOwnerKey.ts | 2 +- .../src/i18n/language-readiness.test.ts | 2 +- src/renderer/src/i18n/language-readiness.ts | 2 +- .../claude/claude-pane-account-folder.ts | 2 +- .../claude-session-account-move.test.ts | 29 +- .../agent-hibernation-coordinator.ts | 5 +- .../agent-background-session-timeout-toast.ts | 2 +- ...ent-launch-caller-prompt-transport.test.ts | 104 --- .../agent-launch-caller-routing.test.ts | 288 -------- .../launch-agent-in-new-tab-test-fixture.ts | 7 +- ...h-agent-in-new-tab-windows-quoting.test.ts | 5 +- .../launch-agent-in-new-tab.test-fixture.ts | 5 +- .../launch/new-tab/launch-agent-in-new-tab.ts | 2 +- ...agent-new-tab-host-route-readiness.test.ts | 101 +-- ...unch-agent-structured-chat-test-fixture.ts | 5 +- .../launch-agent-paste-timeout-notice.ts | 2 +- .../routing/agent-launch-route-input.test.ts | 77 +-- ...ent-launch-route-registered-agents.test.ts | 40 +- ...i-vault-antigravity-reference-host.test.ts | 4 +- .../resume/history/ai-vault-resume-refusal.ts | 2 +- .../ai-vault-session-resume-preparation.ts | 2 +- .../resume/sleeping-agent-session-launch.ts | 2 +- .../src/lib/{utils.ts => class-names.ts} | 0 .../src/lib/clipboard/clipboard-file-path.ts | 4 +- .../src/lib/codex-pane-selection-lane.ts | 2 +- .../src/lib/crash-diagnostics.test.ts | 168 ----- .../editor/appearance/code-font-smoothing.ts | 2 +- .../lib/editor/monaco/monaco-setup-steps.ts | 2 +- .../lib/feature-education-telemetry.test.ts | 63 -- .../lib/files/os-open/open-in-app-catalog.tsx | 2 +- .../lib/files/os-open/windows-path-access.ts | 2 +- .../{ => keyboard}/screen-submit-shortcut.ts | 4 +- .../{ => keyboard}/tab-number-shortcuts.ts | 6 +- .../src/lib/launch-ai-vault-session.ts | 5 +- .../lib/lazy-chunk-recovery-reload.test.ts | 39 -- .../src/lib/lazy-with-retry.preload.test.tsx | 37 - src/renderer/src/lib/lazy-with-retry.test.ts | 298 -------- .../list-multi-selection.ts | 0 .../list-row-interaction.ts | 0 .../list-table-layout.ts | 0 .../use-scroll-edge-fades.ts | 2 +- .../src/lib/local-preflight-context.ts | 2 +- .../close-all-context-menus.ts | 0 .../focus-keeping-press.ts | 0 .../force-layout-reflow.ts | 0 .../overlay-focus-ownership.ts | 2 +- .../pane-fit-continuation-retry.ts | 2 +- .../src/lib/pane-manager/pane-fit.test.ts | 138 ---- .../lib/pane-manager/pane-manager-registry.ts | 2 +- .../lib/pane-manager/pane-overlay-focus.ts | 2 +- .../pane-terminal-output-queue-backlog.ts | 2 +- ...rminal-output-scheduler-ack-credit.test.ts | 190 ------ .../pane-terminal-output-scheduler-debug.ts | 2 +- ...l-output-scheduler-disposed-writes.test.ts | 97 --- ...l-output-scheduler-queue-retention.test.ts | 202 ------ ...tput-scheduler-synchronized-frames.test.ts | 209 ------ .../pane-terminal-output-scheduler.test.ts | 52 -- .../terminal-link-provider-guard.test.ts | 34 - .../terminal-link-provider-guard.ts | 3 +- .../pane-manager/xterm-parse-barrier.test.ts | 2 +- .../xterm-write-callback-guard.test.ts | 78 --- .../xterm-write-callback-guard.ts | 2 +- ...-commit-cascade-observer.react185.test.tsx | 131 ---- .../lib/react-commit-cascade-observer.test.ts | 50 -- .../react-commit-cascade-telemetry.test.ts | 170 ----- .../react-error-boundary-reporting.test.ts | 98 --- .../src/lib/renderer-app-platform.test.ts | 31 - .../src/lib/renderer-memory-profile.test.ts | 90 --- src/renderer/src/lib/sha256.ts | 2 - .../src/lib/shutdown-checkpoint-guard.test.ts | 85 --- .../bundled-skill-install-error-message.ts | 2 +- .../lib/typing-latency/diagnostic-summary.ts | 2 +- .../input-quiet-scheduler.ts | 0 .../offscreen-animation-pause.ts | 0 .../window-park-visibility.ts | 0 .../window-visibility-interval.ts | 0 .../window-visibility-timeout-poller.ts | 0 .../lib/window-visibility-interval.test.ts | 129 ---- .../window-visibility-timeout-poller.test.ts | 137 ---- .../github/github-work-item-target-request.ts | 2 +- .../work-items/task-provider-error-copy.ts | 2 +- .../launch-work-item-direct-agent.ts | 2 +- .../launch-work-item-direct-test-fixture.ts | 5 +- .../folder-workspace-runtime-owner.ts | 2 +- .../ownership/single-runtime-legacy-owner.ts | 2 +- .../workspace-terminal-host-authority.ts | 2 +- .../ownership/worktree-operation-route.ts | 2 +- .../ownership/worktree-runtime-owner.ts | 2 +- src/renderer/src/main.tsx | 14 +- src/renderer/src/popout.tsx | 16 +- .../agent-orchestration}/worker-auto-close.ts | 6 +- .../web-runtime-browser-creation-e2e-fault.ts | 2 +- ...ion-browser-create-split-placement.test.ts | 304 --------- .../src/{lib => runtime/errors}/ipc-error.ts | 6 +- .../grants/runtime-terminal-artifact-grant.ts | 2 +- .../imports/runtime-file-upload-client.ts | 2 +- ...e-session-terminal-pane-delegation.test.ts | 458 ------------- ...ntime-agent-launch-options.test-fixture.ts | 2 +- ...session-terminal-workspace-routing.test.ts | 194 ------ ...te-runtime-terminal-snapshot-controller.ts | 2 +- .../remote-runtime-terminal-e2e-control.ts | 2 +- .../remote-runtime-terminal-multiplexer.ts | 4 +- .../managed-orcad-api-availability.ts | 2 +- .../updates/remote-server-updater-polling.ts | 2 +- .../configuration/mcp-config-inspection.ts | 2 +- .../local-sessions/local-structured-chats.ts | 2 +- .../src/runtime/repo-hooks/orca-hook-trust.ts | 1 - .../window-visibility-subscription-parking.ts | 2 +- .../paired-host-client-capabilities.ts | 2 +- ...runtime-session-tab-activate-close.test.ts | 227 ------- ...n-mirror-hydration-frame-ordering.test.tsx | 178 +---- .../inventory/web-runtime-session.test.ts | 176 ----- ...on-tabs-sync-completion-on-reveal.test.tsx | 155 ----- ...on-tabs-sync-visibility-collision.test.tsx | 387 +---------- ...ssion-tabs-sync-window-visibility.test.tsx | 223 +----- .../warm-worktree-subscriptions.ts | 7 +- src/renderer/src/space.tsx | 4 +- .../src/ssh/ssh-host-server-status-copy.ts | 6 +- .../src/startup/startup-degraded-recovery.ts | 4 +- .../react-commit-cascade-write-probe.test.ts | 4 +- .../react-commit-cascade-write-probe.ts | 2 +- .../store/github/refresh/worktree-refresh.ts | 2 +- src/renderer/src/store/index.ts | 6 +- .../store/plugins/appearance/plugin-fonts.ts | 2 +- .../plugins/frames/plugin-frame-isolation.ts | 4 +- .../store/projects/project-host-routing.ts | 2 +- .../pending-runtime-repo-removal-replay.ts | 4 +- .../store/repos/repo-removal-failure-toast.ts | 2 +- .../repos/repos-all-hosts-generation.test.ts | 8 +- .../editor/checks/check-run-details-owner.ts | 2 +- .../slices/editor/opening/open-file-apply.ts | 2 +- .../editor/opening/untitled-file-creation.ts | 2 +- .../store/slices/preflight/preflight.test.ts | 216 +----- .../slices/ui/ui-agent-send-target.test.ts | 334 +-------- .../store/slices/ui/ui-slice-agent-actions.ts | 2 +- .../slices/ui/ui-slice-contract-contextual.ts | 2 +- .../slices/ui/ui-slice-settings-actions.ts | 2 +- .../listing/worktree-host-ownership.ts | 2 +- .../worktrees/session/set-active-worktree.ts | 5 +- .../worktrees/teardown/remove-worktree.ts | 2 +- .../terminals/active-tab-owner-worktree.ts | 2 +- .../terminal-active-workspace-creation.ts | 2 +- .../terminals/terminal-quick-command-hosts.ts | 2 +- .../active-workspace-create-actions.test.ts | 254 ------- .../non-secure-context-crypto-stub.ts | 0 src/renderer/src/web/main.tsx | 2 +- .../web/web-runtime-connection-heartbeat.ts | 2 +- .../wsl-move/wsl-move-storage-dispositions.ts | 2 +- ...agent-permission-legacy-spawn.unit.test.ts | 512 -------------- tests/e2e/helpers/settings-pane-visits.ts | 2 +- .../launch-agent-web-terminal.test-fixture.ts | 7 +- .../terminal-agent-pane-launch.unit.test.ts | 586 ---------------- ...ssion-terminal-host-authority.unit.test.ts | 395 ----------- 1268 files changed, 1848 insertions(+), 19246 deletions(-) rename src/renderer/src/{lib => app-shell/platform}/client-environment-info.ts (96%) rename src/renderer/src/{lib => app-shell/platform}/ios-web-platform.ts (100%) rename src/renderer/src/{lib => app-shell/platform}/paired-web-client-host.ts (91%) rename src/renderer/src/{lib => app-shell/platform}/passive-macos-app-data-access.ts (97%) rename src/renderer/src/{lib => app-shell/platform}/renderer-app-platform.ts (100%) rename src/renderer/src/{lib => app-shell/platform}/user-agent-platform.ts (100%) rename src/renderer/src/{lib => app-shell/platform}/web-client-location.ts (100%) delete mode 100644 src/renderer/src/app-shell/shutdown-checkpoint-restart-lifecycle.test.ts rename src/renderer/src/{lib => app-shell/shutdown}/shutdown-checkpoint-failure-toast.ts (95%) rename src/renderer/src/{lib => app-shell/shutdown}/shutdown-checkpoint-guard.ts (93%) rename src/renderer/src/{lib => app-shell/startup}/active-view-persist.ts (69%) rename src/renderer/src/{lib => app-shell/startup}/e2e-config.ts (94%) rename src/renderer/src/{lib => app-shell/startup}/lazy-chunk-recovery-reload.ts (91%) rename src/renderer/src/{lib => app-shell/startup}/lazy-with-retry.never-landed-reload.test.ts (100%) rename src/renderer/src/{lib => app-shell/startup}/lazy-with-retry.ts (100%) rename src/renderer/src/{lib => app-shell/startup}/react-renderer-root.ts (100%) rename src/renderer/src/{lib => app-shell/startup}/startup-ui-hydration.ts (94%) rename src/renderer/src/{lib => app-shell/updates}/update-check-click-options.ts (85%) rename src/renderer/src/{lib => app-shell/updates}/updater-beforeunload.ts (89%) rename src/renderer/src/{lib => app-shell/updates}/updater-message-copy.ts (53%) delete mode 100644 src/renderer/src/app-shell/use-visible-hosted-review-refresh.test.tsx delete mode 100644 src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx delete mode 100644 src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.test.ts delete mode 100644 src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx delete mode 100644 src/renderer/src/components/editor/MermaidViewer.test.tsx delete mode 100644 src/renderer/src/components/editor/MonacoEditor.owner-view-state.test.tsx delete mode 100644 src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.captured-failure.test.tsx delete mode 100644 src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx delete mode 100644 src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts rename src/renderer/src/{lib/scroll-cache.ts => components/editor/model-lifetime/retention/editor-view-state-cache.ts} (100%) delete mode 100644 src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.test.tsx delete mode 100644 src/renderer/src/components/editor/pdf-viewer-failed-load-recovery.test.tsx delete mode 100644 src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.lazy-chunk.test.tsx delete mode 100644 src/renderer/src/components/editor/use-pdf-viewer-find-shortcut.test.tsx delete mode 100644 src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.lazy-chunk.test.tsx delete mode 100644 src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.never-landed-reload.test.tsx delete mode 100644 src/renderer/src/components/feature-tips/ChatUiHereCornerCard.test.tsx delete mode 100644 src/renderer/src/components/feature-tips/ChatUiHereTip.test.tsx delete mode 100644 src/renderer/src/components/github/github-image-upload.test.ts delete mode 100644 src/renderer/src/components/native-chat/composer/focus/native-chat-composer-reveal-focus.test.tsx delete mode 100644 src/renderer/src/components/native-chat/media/text/NativeChatTextAttachments.test.tsx delete mode 100644 src/renderer/src/components/native-chat/viewport/find/use-native-chat-find.test.tsx delete mode 100644 src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.command-finished.test.tsx delete mode 100644 src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-session-conditional-refresh-hook.test.tsx delete mode 100644 src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-polling.test.tsx delete mode 100644 src/renderer/src/components/right-sidebar/right-sidebar-titlebar-drag-regions.render.test.tsx delete mode 100644 src/renderer/src/components/settings/chat/ChatPermissionSetting.test.tsx rename src/renderer/src/{lib => components/settings/navigation}/open-plugins-settings.ts (91%) rename src/renderer/src/{lib => components/settings/navigation}/settings-navigation-types.ts (97%) delete mode 100644 src/renderer/src/components/settings/session-history/SessionHistorySettingsPane.test.tsx delete mode 100644 src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx delete mode 100644 src/renderer/src/components/shared/MacFolderAccessFixDialog.test.tsx delete mode 100644 src/renderer/src/components/sidebar/sidebar-feedback/SidebarFeedbackDialog.test.tsx rename src/renderer/src/{lib => components/sidebar/sidebar-feedback}/feedback-image-attachments.test.ts (56%) rename src/renderer/src/{lib => components/sidebar/sidebar-feedback}/feedback-image-attachments.ts (95%) rename src/renderer/src/{lib => components/sidebar/sidebar-feedback}/feedback-image-shrink.ts (100%) delete mode 100644 src/renderer/src/components/status-bar/PortsStatusSegment.render-stability.test.tsx delete mode 100644 src/renderer/src/components/tab-bar/NativeChatNamingFailure.test.tsx delete mode 100644 src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-dispatcher-push-reattach.test.ts delete mode 100644 src/renderer/src/components/terminal-pane/pty-transport/recovery/terminal-pane-surface-ownership.test.ts delete mode 100644 src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts delete mode 100644 src/renderer/src/components/terminal-pane/render-health/recovery/terminal-pane-recovery-unsettled-fallback.test.ts delete mode 100644 src/renderer/src/components/ui/use-overlay-focus-scope.test.tsx rename src/renderer/src/{lib => diagnostics/crash-reporting}/crash-breadcrumb-data.ts (97%) rename src/renderer/src/{lib => diagnostics/crash-reporting}/crash-breadcrumb-recorder.ts (86%) rename src/renderer/src/{lib => diagnostics/crash-reporting}/crash-diagnostics.ts (92%) rename src/renderer/src/{lib => diagnostics/crash-reporting}/react-error-boundary-reporting.ts (95%) create mode 100644 src/renderer/src/diagnostics/memory/renderer-memory-profile.test.ts rename src/renderer/src/{lib => diagnostics/memory}/renderer-memory-profile.ts (100%) rename src/renderer/src/{lib => diagnostics/memory}/renderer-memory-sampling.ts (95%) rename src/renderer/src/{lib => diagnostics/memory}/state-collection-byte-estimate.test.ts (100%) rename src/renderer/src/{lib => diagnostics/memory}/state-collection-byte-estimate.ts (100%) rename src/renderer/src/{lib => diagnostics/react-commits}/react-commit-cascade-observer.ts (91%) rename src/renderer/src/{lib => diagnostics/react-commits}/react-commit-cascade-store-write-samples.ts (100%) rename src/renderer/src/{lib => diagnostics/react-commits}/react-commit-cascade-telemetry.ts (92%) rename src/renderer/src/{lib => diagnostics/react-commits}/react-devtools-commit-hook-shim.ts (100%) rename src/renderer/src/{lib => diagnostics/react-commits}/react-grab-dev-gate.ts (100%) rename src/renderer/src/{lib => diagnostics/telemetry}/feature-education-telemetry.ts (93%) rename src/renderer/src/{lib => diagnostics/telemetry}/telemetry.ts (90%) delete mode 100644 src/renderer/src/hooks/useIpcEvents-agent-status-hook-titles.test.ts delete mode 100644 src/renderer/src/hooks/useMacTccAttributionSeveredNotice.test.tsx delete mode 100644 src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-prompt-transport.test.ts delete mode 100644 src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-routing.test.ts rename src/renderer/src/lib/{utils.ts => class-names.ts} (100%) delete mode 100644 src/renderer/src/lib/crash-diagnostics.test.ts delete mode 100644 src/renderer/src/lib/feature-education-telemetry.test.ts rename src/renderer/src/lib/{ => keyboard}/screen-submit-shortcut.ts (86%) rename src/renderer/src/lib/{ => keyboard}/tab-number-shortcuts.ts (94%) delete mode 100644 src/renderer/src/lib/lazy-chunk-recovery-reload.test.ts delete mode 100644 src/renderer/src/lib/lazy-with-retry.preload.test.tsx delete mode 100644 src/renderer/src/lib/lazy-with-retry.test.ts rename src/renderer/src/lib/{ => list-interaction}/list-multi-selection.ts (100%) rename src/renderer/src/lib/{ => list-interaction}/list-row-interaction.ts (100%) rename src/renderer/src/lib/{ => list-interaction}/list-table-layout.ts (100%) rename src/renderer/src/lib/{ => list-interaction}/use-scroll-edge-fades.ts (98%) rename src/renderer/src/lib/{ => overlay-interaction}/close-all-context-menus.ts (100%) rename src/renderer/src/lib/{ => overlay-interaction}/focus-keeping-press.ts (100%) rename src/renderer/src/lib/{ => overlay-interaction}/force-layout-reflow.ts (100%) rename src/renderer/src/lib/{ => overlay-interaction}/overlay-focus-ownership.ts (98%) delete mode 100644 src/renderer/src/lib/pane-manager/pane-fit.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-ack-credit.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-disposed-writes.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-synchronized-frames.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts delete mode 100644 src/renderer/src/lib/pane-manager/xterm-write-callback-guard.test.ts delete mode 100644 src/renderer/src/lib/react-commit-cascade-observer.react185.test.tsx delete mode 100644 src/renderer/src/lib/react-commit-cascade-observer.test.ts delete mode 100644 src/renderer/src/lib/react-commit-cascade-telemetry.test.ts delete mode 100644 src/renderer/src/lib/react-error-boundary-reporting.test.ts delete mode 100644 src/renderer/src/lib/renderer-app-platform.test.ts delete mode 100644 src/renderer/src/lib/renderer-memory-profile.test.ts delete mode 100644 src/renderer/src/lib/sha256.ts delete mode 100644 src/renderer/src/lib/shutdown-checkpoint-guard.test.ts rename src/renderer/src/lib/{ => window-activity}/input-quiet-scheduler.ts (100%) rename src/renderer/src/lib/{ => window-activity}/offscreen-animation-pause.ts (100%) rename src/renderer/src/lib/{ => window-activity}/window-park-visibility.ts (100%) rename src/renderer/src/lib/{ => window-activity}/window-visibility-interval.ts (100%) rename src/renderer/src/lib/{ => window-activity}/window-visibility-timeout-poller.ts (100%) delete mode 100644 src/renderer/src/lib/window-visibility-interval.test.ts delete mode 100644 src/renderer/src/lib/window-visibility-timeout-poller.test.ts rename src/renderer/src/{lib => runtime/agent-orchestration}/worker-auto-close.ts (96%) delete mode 100644 src/renderer/src/runtime/browser/creation/web-runtime-session-browser-create-split-placement.test.ts rename src/renderer/src/{lib => runtime/errors}/ipc-error.ts (93%) delete mode 100644 src/renderer/src/runtime/hosted-terminals/actions/web-runtime-session-terminal-pane-delegation.test.ts delete mode 100644 src/renderer/src/runtime/hosted-terminals/creation/web-runtime-session-terminal-workspace-routing.test.ts delete mode 100644 src/renderer/src/runtime/web-session-tabs-sync/closing/web-runtime-session-tab-activate-close.test.ts delete mode 100644 src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts delete mode 100644 src/renderer/src/runtime/web-session-tabs-sync/subscriptions/web-session-tabs-sync-completion-on-reveal.test.tsx delete mode 100644 src/renderer/src/store/workspaces/creation/active-workspace-create-actions.test.ts rename src/renderer/src/{lib => test-support/browser-crypto}/non-secure-context-crypto-stub.ts (100%) delete mode 100644 tests/e2e/cross-version-wire/agent-permission-legacy-spawn.unit.test.ts delete mode 100644 tests/e2e/terminal-agent-pane-launch.unit.test.ts delete mode 100644 tests/e2e/web-runtime-session-terminal-host-authority.unit.test.ts diff --git a/.github/workflows/performance-contracts.yml b/.github/workflows/performance-contracts.yml index e8132e2aa79..2c453310526 100644 --- a/.github/workflows/performance-contracts.yml +++ b/.github/workflows/performance-contracts.yml @@ -29,7 +29,6 @@ on: - 'src/main/codex-usage/scanner-incremental-append.test.ts' - 'src/main/usage/jsonl-file-reader.test.ts' - 'src/main/usage/jsonl-file-snapshot.test.ts' - - 'src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts' - 'src/main/claude-usage/scanner-incremental-append.test.ts' - 'src/main/claude-usage/store-checkpoint-persistence.test.ts' diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index 1945221c423..8ee00e478c2 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -30,7 +30,6 @@ 3 test-no-in-test-import-actual:src/renderer/src/components/terminal-pane/snapshots/remote-snapshot-alt-screen-host-chain.test.ts 1 test-no-in-test-import-actual:src/renderer/src/hooks/useIpcEvents-agent-status-pane-teardown.test.ts 1 test-no-in-test-import-actual:src/renderer/src/hooks/useIpcEvents-agent-status-snapshot-hydration.test.ts -1 test-no-in-test-import-actual:src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-routing.test.ts 1 test-no-polling-loop:src/main/browser/cookies/browser-cookie-import-concurrency.test.ts 1 test-no-polling-loop:src/main/claude/claude-stop-ack-race.test.ts 1 test-no-polling-loop:src/main/claude/claude-stream-json-connection.test.ts @@ -635,7 +634,7 @@ 1 test-no-wall-clock:src/renderer/src/components/native-chat/session/structured/delivery/NativeChatMessageList.unsent-message.test.tsx 3 test-no-wall-clock:src/renderer/src/components/native-chat/surfaces/integration/NativeChatMessageList.test.tsx 2 test-no-wall-clock:src/renderer/src/components/native-chat/tools/tasks/NativeChatStructuredSessionStatus.test.tsx -3 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/review/use-workspace-reference-details.test.tsx +1 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/review/use-workspace-reference-details.test.tsx 1 test-no-wall-clock:src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.lineage-agent-expansion-coupling.test.tsx 2 test-no-wall-clock:src/renderer/src/components/task-page/jira/task-page-jira-cache-selectors.test.ts 2 test-no-wall-clock:src/renderer/src/components/terminal-pane/pty-connection/emulation/fish-color-scheme-child-stdin.node-pty.test.ts @@ -645,7 +644,6 @@ 1 test-no-wall-clock:src/renderer/src/components/worktree-jump/WorktreeJumpPalette.recent-tabs.behavior.test.tsx 4 test-no-wall-clock:src/renderer/src/components/worktree-jump/WorktreeJumpPalette.recent-tabs.test.tsx 2 test-no-wall-clock:src/renderer/src/hooks/ipc-events/agent-status-terminal-title-tab-write.test.ts -1 test-no-wall-clock:src/renderer/src/hooks/useIpcEvents-agent-status-hook-titles.test.ts 1 test-no-wall-clock:src/renderer/src/lib/paired-runtime-closed-tab-resume.test.ts 2 test-no-wall-clock:src/renderer/src/lib/use-tab-agent-observed-signal-dispatch.test.tsx 2 test-no-wall-clock:src/renderer/src/lib/wake-sleeping-agents-live-unmounted-pane.test.ts diff --git a/config/react-compiler-bailout-baseline.txt b/config/react-compiler-bailout-baseline.txt index a6e8fdf9418..12aa339d614 100644 --- a/config/react-compiler-bailout-baseline.txt +++ b/config/react-compiler-bailout-baseline.txt @@ -707,7 +707,7 @@ 6 src/renderer/src/hooks/useVirtualizedScrollAnchor.ts 1 src/renderer/src/lib/agent-session/launch/inventory/host-chat-program-acquisition.ts 1 src/renderer/src/lib/files/search/quick-open-file-history.ts -2 src/renderer/src/lib/worker-auto-close.ts +2 src/renderer/src/runtime/agent-orchestration/worker-auto-close.ts 1 src/renderer/src/runtime/native-chat/naming/structured-chat-name-observation.ts 1 src/renderer/src/store/plugins/appearance/plugin-icon-themes.ts 5 src/renderer/src/store/retained-views/app-state-view.ts diff --git a/config/recurring-timer-baseline.txt b/config/recurring-timer-baseline.txt index e48d5c80d66..6e17989d975 100644 --- a/config/recurring-timer-baseline.txt +++ b/config/recurring-timer-baseline.txt @@ -85,9 +85,9 @@ 1 src/renderer/src/components/terminal-pane/render-health/terminal-render-desync-sentinel.ts 1 src/renderer/src/hooks/use-now.ts 1 src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.ts -1 src/renderer/src/lib/crash-diagnostics.ts -1 src/renderer/src/lib/window-visibility-interval.ts -1 src/renderer/src/lib/worker-auto-close.ts +1 src/renderer/src/diagnostics/crash-reporting/crash-diagnostics.ts +1 src/renderer/src/lib/window-activity/window-visibility-interval.ts +1 src/renderer/src/runtime/agent-orchestration/worker-auto-close.ts 1 src/renderer/src/runtime/hosts/wsl/wsl-refused-read-watch.ts 1 src/shared/remote-runtime/control/remote-runtime-shared-control-app-heartbeat.ts 1 src/shared/remote-runtime/remote-runtime-socket-liveness.ts diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 211712f364c..90b27e1136b 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -1089,7 +1089,7 @@ "id": "settings.terminal-section-lifetime", "title": "Terminal settings search preserves matching section state", "maturity": "experimental", - "protection": "partial", + "protection": "none", "owner": "terminal-runtime", "layer": "renderer-unit", "surfaces": [ @@ -1099,66 +1099,43 @@ ], "platforms": ["macos", "linux", "windows"], "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], - "coveredPlatforms": ["macos"], + "coveredPlatforms": [], "coveredProviders": [], - "coverageNotes": "Actual TerminalPane, ManageSessionsSection, health listener and scrollback form with mocked store/provider and display/action boundaries. Unix/Windows and Mac-client/Windows-host flags simulated; no physical provider, process-stop or native input coverage.", + "coverageNotes": "Module-layout wave1 retires the terminal-section lifetime owning suite and its complete dated receipt. The remaining TCC attribution notice suite does not establish the search-lifetime invariant. No current protection, replacement coverage, native/provider execution or fresh evidence is claimed. Historical scope before retirement: Actual TerminalPane, ManageSessionsSection, health listener and scrollback form with mocked store/provider and display/action boundaries. Unix/Windows and Mac-client/Windows-host flags simulated; no physical provider, process-stop or native input coverage.", "motivatingLinks": [ "https://github.com/stablyai/orca/blob/main/src/renderer/src/components/settings/terminal/TerminalPane.tsx" ], "invariant": "A matching settings section retains its state when preceding sections are filtered away; a section removed from search releases its owner and refreshes when shown again. A retained section still refreshes when another surface invalidates the session inventory. Diagnostic inventory never becomes authority for process death.", - "oracle": "Across 15 matched search edits the matched section keeps the same DOM node and the inventory/health read counts do not grow, instead of six reads each. A foreign inventory invalidation still reads once on the retained section and not after unmount. A draft and pending target survive matched search; actual hide/reopen reloads, explicit Refresh still reads, and hidden/unmounted health listeners do not fire.", - "commands": [ - "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx" - ], - "testFiles": [ - "src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx", - "src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx" - ], - "assertionRefs": [ - { - "file": "src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx", - "assertions": [ - "refreshes a retained section when another surface invalidates the inventory", - "refreshes explicitly, reloads genuinely hidden sections, and removes focus listeners", - "preserves an uncommitted scrollback draft while its section remains visible", - "retains a pending action target only while its section remains visible" - ] - } - ], - "evidenceRuns": [ - { - "date": "2026-09-26", - "runner": "local", - "platform": "macos", - "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/TerminalTccAttributionNotice.test.tsx", - "currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx", - "result": "passed", - "durationSeconds": 1.48, - "summary": "12 tests/2 suites pass after the shared SettingsSectionStack adoption and the inventory-invalidation subscription. Read-count oracles restated as node identity plus non-growing read counts; original main fails all three simulated platform cases, the draft, the pending target and the foreign-invalidation refresh." - } - ], + "oracle": "Target oracle, currently unprotected after whole-suite retirement: Across 15 matched search edits the matched section keeps the same DOM node and the inventory/health read counts do not grow, instead of six reads each. A foreign inventory invalidation still reads once on the retained section and not after unmount. A draft and pending target survive matched search; actual hide/reopen reloads, explicit Refresh still reads, and hidden/unmounted health listeners do not fire.", + "commands": [], + "testFiles": [], + "assertionRefs": [], + "evidenceRuns": [], "runtimeBudget": { "p95Seconds": 10, "scope": "Focused renderer-unit suites, p95 not established." }, "flakeHistory": { "status": "not-started", - "evidence": "Author and independent local evidence only; no CI soak." + "evidence": "No current search-lifetime suite or soak evidence remains. Historical statement before retirement: Author and independent local evidence only; no CI soak." }, "redGreenEvidence": { - "status": "complete", - "evidence": "Original new suite fails all three simulated platform read-count cases, draft and pending target; one cleanup/refresh control passes. Candidate6pass. Independent scratch5pass/original4fail1control." + "status": "missing", + "evidence": "The owning search-lifetime suite was retired whole. Its historical red/green result does not validate the remaining unrelated TCC attribution notice suite." }, "performanceBudget": { "required": true, - "evidence": "Actual child inventory and health reads 6 to 1 across 15 matched search edits. Fixed small section filter/map now lives in the shared SettingsSectionStack; no new polling, cache or subprocess. One added in-renderer invalidation listener replaces the incidental search-driven refresh, so a retained section stays as fresh as the signal every other inventory reader already uses. No packaged CPU/frame/heap claim." + "evidence": "Historical budget account only; no current selector or fresh performance measurement: Actual child inventory and health reads 6 to 1 across 15 matched search edits. Fixed small section filter/map now lives in the shared SettingsSectionStack; no new polling, cache or subprocess. One added in-renderer invalidation listener replaces the incidental search-driven refresh, so a retained section stays as fresh as the signal every other inventory reader already uses. No packaged CPU/frame/heap claim." }, "knownGaps": [ "CI soak pending.", "No physical local/daemon/SSH/WSL/remote-runtime provider, native Linux/Windows or process-stop behavior exercised; routing, PTY ownership, commands and wire formats unchanged.", "Matched searches keep the loaded snapshot until existing Refresh, completed mutation, a foreign inventory invalidation or genuine hide/reopen; health also refreshes on focus/revision.", "Pending action target and drafts remain while their parent section matches, including individual controls hidden inside a retained section.", - "No packaged app latency, heap, real focus or pending destructive-mutation completion evidence. Hidden Electron component visual proof is separate from the gate." + "No packaged app latency, heap, real focus or pending destructive-mutation completion evidence. Hidden Electron component visual proof is separate from the gate.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx. Lost alarms: refreshes a retained section when another surface invalidates the inventory; refreshes explicitly, reloads genuinely hidden sections, and removes focus listeners; preserves an uncommitted scrollback draft while its section remains visible; retains a pending action target only while its section remains visible. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope.", + "Module-layout wave1 retires this complete historical receipt because its explicit current selector was reduced. It does not validate the reduced suite. Original receipt verbatim:\n{\n \"date\": \"2026-09-26\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/TerminalTccAttributionNotice.test.tsx\",\n \"currentCommand\": \"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx\",\n \"result\": \"passed\",\n \"durationSeconds\": 1.48,\n \"summary\": \"12 tests/2 suites pass after the shared SettingsSectionStack adoption and the inventory-invalidation subscription. Read-count oracles restated as node identity plus non-growing read counts; original main fails all three simulated platform cases, the draft, the pending target and the foreign-invalidation refresh.\"\n }", + "Module-layout wave1 historical red/green statement before owning-suite retirement, retained verbatim without current validation credit: {\"status\":\"complete\",\"evidence\":\"Original new suite fails all three simulated platform read-count cases, draft and pending target; one cleanup/refresh control passes. Candidate6pass. Independent scratch5pass/original4fail1control.\"}" ], "promotionCriteria": [ "Retain count, real draft, pending target and cleanup oracles; obtain stable CI history and relevant full-app/provider evidence before promotion." @@ -5858,14 +5835,12 @@ "invariant": "Append-only external file growth changes only Monaco's model suffix, retaining the viewport, selection, Find state, and renderer liveness above the append point; read-only live tails do not create undo history, while editable external updates remain undoable and arbitrary rewrites continue to replace the model content.", "oracle": "Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit1709 retires changed-source assertion and execution scope without equivalent replacement coverage. Existing unchanged-selector receipts remain scoped to their original sources. Historical prior-source oracle: Focused tests assert one post-mount content owner and actual lifecycle remount ordering across retained path models. Real-Monaco tests cover undo behavior for read-only live tails versus editable files. Standalone Node timing and private undo-retention budgets at 9 and 50 MiB were retired by audit1677 and provide no current alarm. The Electron scenario compares legacy setValue with watcher appends, checking visible ranges, selection, Find state, scroll offset, non-undoability, renderer survival and memory budgets. Mock-only edit-shape assertions were retired during test-value audit.", "commands": [ - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/editor/monaco-content-sync.undo-history.test.ts", "node --expose-gc ./node_modules/vitest/vitest.mjs bench --pool=threads", "pnpm run test:e2e -- tests/e2e/agent-session-log-tail-stability.spec.ts --workers=1" ], "testFiles": [ "src/renderer/src/components/editor/monaco-content-sync.undo-history.test.ts", - "src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx", "tests/e2e/agent-session-log-tail-stability.spec.ts" ], "assertionRefs": [ @@ -5875,12 +5850,6 @@ "a real Monaco read-only live-tail append leaves canUndo false while an ordinary external update remains undoable" ] }, - { - "file": "src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx", - "assertions": [ - "a same-pane path switch unmounts the prior outer Monaco before real mount reconciliation refreshes the stale target; the prior retained model content and undo sentinel remain untouched" - ] - }, { "file": "tests/e2e/agent-session-log-tail-stability.spec.ts", "assertions": [ @@ -5931,7 +5900,8 @@ "Audit1677 removes low-value test projections, mock wiring, private-state assertions, inventories and duplicate examples without equivalent replacement coverage. Per-source reviews record retained public behavior and refusal boundaries with injected/native/remote limits. Affected historical receipts are retired; gates without active current evidence are downgraded.", "No live Windows or Linux View Log evidence yet.", "Historical retired benchmark receipt (not active evidence): {\"date\":\"2026-07-12\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"node --expose-gc ./node_modules/vitest/vitest.mjs bench src/renderer/src/components/editor/monaco-content-sync.bench.ts --pool=threads\",\"result\":\"passed\",\"durationSeconds\":75,\"summary\":\"Forced-GC, settled, alternating fresh-model Monaco p95: 9 MiB append 4.02-5.51ms versus replacement 81.42-83.76ms; 50 MiB append 22.72-26.60ms versus replacement 445.11-449.21ms.\"}", - "Historical retired benchmark receipt (not active evidence): {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"node --expose-gc ./node_modules/vitest/vitest.mjs bench src/renderer/src/components/editor/monaco-content-sync.undo-retention.bench.ts --pool=threads\",\"result\":\"passed\",\"durationSeconds\":5,\"summary\":\"The undoable 50 MiB control retained 104,858,630 undo-service bytes and 104,857,790 ArrayBuffer bytes; the read-only live-tail arm retained zero of both and remained non-undoable.\"}" + "Historical retired benchmark receipt (not active evidence): {\"date\":\"2026-07-13\",\"runner\":\"local\",\"platform\":\"macos\",\"command\":\"node --expose-gc ./node_modules/vitest/vitest.mjs bench src/renderer/src/components/editor/monaco-content-sync.undo-retention.bench.ts --pool=threads\",\"result\":\"passed\",\"durationSeconds\":5,\"summary\":\"The undoable 50 MiB control retained 104,858,630 undo-service bytes and 104,857,790 ArrayBuffer bytes; the read-only live-tail arm retained zero of both and remained non-undoable.\"}", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx. Lost alarms: unmounts the prior path before reconciling a stale retained target model; keeps the diff editor mounted when a save updates the modified content; still remounts the diff editor for an explicit reload request. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Quarantine the Electron scenario if it flakes without a product or harness bug; demote if viewport/Find drift, renderer loss, or Electron memory retention exceeds the registered budgets." }, @@ -7196,7 +7166,7 @@ "commands": [ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/agent-session/resume/resume-sleeping-agent-session.test.ts", "pnpm exec vitest run --config config/vitest.config.ts tests/e2e/completed-worker-retirement-resume.unit.test.ts --reporter=verbose", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/agent-session/resume/resume-sleeping-agent-session.test.ts src/main/providers/local-pty/local-pty-provider-spawn-session.test.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/ipc/pty/pty-pane-reservation-settlement.test.ts src/main/runtime/construction/orca-runtime.test.ts src/renderer/src/lib/pane-manager/pane-fit.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/agent-session/resume/resume-sleeping-agent-session.test.ts src/main/providers/local-pty/local-pty-provider-spawn-session.test.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/ipc/pty/pty-pane-reservation-settlement.test.ts src/main/runtime/construction/orca-runtime.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", "pnpm exec electron-vite build --mode e2e && SKIP_BUILD=1 pnpm exec playwright test tests/e2e/live-background-terminal-mount-authority.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1", "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/agent-session/lifecycle/structured-agent-cli-writers.test.ts src/main/runtime/rpc/methods/structured-sessions/creation/structured-agent-session-adoption-replay.test.ts src/main/providers/agent-foreground-process-git-bash.win32.test.ts" ], @@ -7208,7 +7178,6 @@ "src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts", "src/main/ipc/pty/pty-pane-reservation-settlement.test.ts", "src/main/runtime/construction/orca-runtime.test.ts", - "src/renderer/src/lib/pane-manager/pane-fit.test.ts", "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", "tests/e2e/live-background-terminal-mount-authority.spec.ts", @@ -7241,12 +7210,6 @@ "hibernated stable panes with cleared live PTY bindings are skipped" ] }, - { - "file": "src/renderer/src/lib/pane-manager/pane-fit.test.ts", - "assertions": [ - "withheld hidden-window animation frames exhaust the bounded fit retry and release its continuation" - ] - }, { "file": "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-hidden-replay.test.ts", "assertions": [ @@ -7332,7 +7295,8 @@ "Audit2056 (sources2054/2055): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-part-14.spec'). src/main/runtime/orca-runtime-tests/terminal-side-effect-facts-part-03.spec.ts: arms the stale-title timer for a seeded working title; arms the Command Code scrape from the noted spawn command; prefers the tracked title over a stale renderer lastTitle in the hydration seed; writes recent output into the current runtime output map; uses a later title handler replacement with the runtime receiver; calls the current injected listener with the runtime receiver; keeps optional reconciliation lazy after its callback is removed. src/main/runtime/orca-runtime-tests/mobile-session-tabs-part-14.spec.ts: lists them, so a paired client can mirror a migrated editor; closes one by retiring it from the host session; keeps an unsaved draft unless the close is forced; shares aggregate work but leaves a replacement pending promise alone; lets a targeted refresh finish while an aggregate refresh waits; refuses a close when the store lacks %s before the guard; keeps a later missing %s on the original caught failure path. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed.", "Audit2061 (sources2059/2060): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec'); await import('./orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec'); await import('./orca-runtime-tests/worktree-removal-execution-host.spec'); await import('./orca-runtime-tests/terminal-spawn-dispatch.spec'). src/main/runtime/orca-runtime-tests/headless-snapshots-part-02.spec.ts: replaces a cwd parsed before late WSL context with the provider cwd; keeps a live WSL cwd that arrives during late-context snapshot recovery; infers local reconstructed WSL context from a WSL UNC worktree; clears stale terminal file URI hostnames after empty-host OSC7 cwd updates; serializes SSH headless OSC7 cwd as POSIX when the desktop runtime is on Windows; keeps Windows SSH OSC7 cwd as a drive path when the desktop runtime is POSIX; serializes Windows SSH headless OSC7 cwd as a drive path on POSIX desktops; infers restored SSH connection identity from app-scoped PTY ids. src/main/runtime/orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec.ts: survives the rebuild that closing a saved editor tab forces; keeps its place in the tab order when the host lists the workspace again; lists tabs to phones in the order desktop clients draw after a rebuild; keeps the place a window gave a chat across a graph resend before the chat is restored. src/main/runtime/orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec.ts: lists every persisted terminal of a windowless host after a cold restart; lists every persisted terminal in the all-worktrees inventory after a cold restart; lists persisted editor tabs beside the terminals after a cold restart; restores a persisted split group layout on the cold rebuild; restores the saved active group of a split even when it is not the first group; falls back to the active tab group when the saved active group was not restored; keeps the headless group active when there is no persisted split; keeps phone and CLI creates in the group they joined across a cold restart. src/main/runtime/orca-runtime-tests/worktree-removal-failed-retry.spec.ts: lists the leftover with its error, though Git no longer does; takes the normal delete once Git registers a checkout at the path again; joins a retry another client started while this Delete listed Git; shows the failed row with its error, not the scan cached before the delete. src/main/runtime/orca-runtime-tests/worktree-removal-execution-host.spec.ts: refuses an SSH row whose host is unreachable instead of deleting here; refuses a runtime row with no nested SSH target rather than deleting locally; refuses a runtime row that also names a nested SSH target; retains runtime purge dispatch in a recovery job; uses the runtime helper override before resuming an interrupted removal; uses the runtime helper override before retrying a failed removal; captures the watcher callback and retains its job-host receiver; reads the current runtime removal queue. src/main/runtime/orca-runtime-tests/terminal-spawn-dispatch.spec.ts: reports the spawn request before it leaves for the pty controller; reports it even when the spawn itself then fails; does not report it when the create fails before any spawn request; captures the admission method before reading the incarnation; captures the record method before evaluating its state object; skips database payload serialization when no database exists; skips a throwing database payload when no database exists; keeps the database method and receiver captured before its payload; captures the canonical map method before a binding getter changes it; completes the captured replacement fence without another map lookup. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed.", "Module layout policy correction: retire the remaining program-status-osc.spec.ts event-projection case (production/mock-derived pane-key oracle) and sign-in timeout case (real setTimeout wait with Date-only fake clocks). The runtime test registrar no longer imports that file. No replacement coverage or fresh execution is claimed for these losses; historical receipts retain their original source scope.", - "Module layout policy correction: retire the two remaining worktree-ps-agent-row-dismissal.spec.ts registrations. The remint/dismissal/exit case computed expected snapshotVersion increments from a production snapshot and used real-clock vi.waitFor; the legacy OSC case derived its expected paneKey from the production layout-owner switch. The runtime registrar no longer imports this file. These public projection scenarios have no replacement coverage or fresh execution claimed; all historical receipts and earlier loss notes retain their original source scope." + "Module layout policy correction: retire the two remaining worktree-ps-agent-row-dismissal.spec.ts registrations. The remint/dismissal/exit case computed expected snapshotVersion increments from a production snapshot and used real-clock vi.waitFor; the legacy OSC case derived its expected paneKey from the production layout-owner switch. The runtime registrar no longer imports this file. These public projection scenarios have no replacement coverage or fresh execution claimed; all historical receipts and earlier loss notes retain their original source scope.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/lib/pane-manager/pane-fit.test.ts. Lost alarms: runs the continuation when the reveal fit arrives on a different pane object; does not let a stale handle cancel its deferred replacement; resolves failure when hidden-window animation frames are withheld. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Demote or quarantine if failures are non-actionable or if a duplicate resume escape occurs outside the modeled matrix." }, @@ -7370,8 +7334,8 @@ "invariant": "For every claim-capable execution route, one provider-session identity has at most one live PTY owner and one canonical host surface across concurrent clients, retries, reconnects, and stale publications. For paired structured fresh and resume requests, the authenticated owning runtime creates in background without a renderer window; activate=true focuses the exact requested leaf only on the requesting viewer, while activate=false changes no viewer focus. A live orphan may be adopted only when the controller proves its exact handle and incarnation, its worktree and host owner match, no competing visual owner exists, and a host topology CAS wins. A viewer may classify completion only from successful host/provider inspection or explicit lifecycle evidence; transport, handle, or provider unavailability remains unknown and breaks any consecutive-idle proof. A physical exit retires that exact incarnation durably so stale client state and host restart cannot recreate it. Mixed-version routes select the unchanged legacy request before any authority side effect or execution-owner-local filesystem access.", "oracle": "Audit2694 source-only review: Remove14 authored/14 expanded cases in the renderer completion wrapper family, including the complete bell/query-reply/stale-input factory, without replacement. Retain29 authored/34 expanded cases with unchanged callback bodies. Three removals are the existing pending2203 overlap and must be adopted/count-reconciled once; eleven are fresh removals. No current-source runtime or gate execution is supplied by this packet; dated evidence retains its original source scope and does not validate the reduced factory bytes. No equivalent replacement, physical provider, mixed-version, mobile, rendered UI, cross-platform, soak or measured-performance result is claimed. The existing command and testFiles selector retain tests/pty-connection-completion-and-notification.test.ts; no wrapper-specific assertionRef is removed. Historical selector results do not validate its changed reachable factories. Historical prior account (preserved verbatim): Audit2133 current-parent preparation: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2095 current-parent preparation2126: Current reduced sources/selectors intentionally retire the alarms listed in knownGaps; prior dated runs and replacement claims do not validate the changed set. Historical prior account (preserved verbatim): Audit2090 (sources2088/2089): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2086 (sources2082/2083): Current reduced suites intentionally retire the alarms listed in knownGaps; prior dated execution and replacement claims do not validate changed source bytes or selectors. Historical prior account (preserved verbatim): Audit2076 (sources2073/2074): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime-tests/terminal-handles-part-02.spec.ts: recovers a moved SSH pane through the tab it now sits in, not the one its lease froze; reports a plain expired orphan; does not report a pane whose only recent lease was superseded; does not report a pane whose only recent lease had its relay id recycled; picks the eligible orphan over a superseded predecessor that matches first; collects the same tabs the per-tab reader reports, in one sweep of the leases; reports no candidate when no lease names any of the worktree tabs. src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts: starts unavailable with no authoritative window; reports the configured Windows terminal shell on status; reports floating workspace availability from settings on status; polls floating tabs with targeted PTY liveness and no repo/provider inventory; hydrates persisted tabs when the store cannot report repos; advertises browser screencast only when a renderer window is available; routes mobile Codex reset consumption through the account mutation coordinator; maps a definite pre-provider rejection into an authoritative current snapshot. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed. Frozen current literal-import graph reaches these changed test bytes; unresolved local and computed edges remain debt, and current reachability is not historical execution or equivalent replacement evidence. Historical prior account (preserved verbatim): Audit2003: Delete the complete pure orphan-topology suite and its file membership; preserve the other selectors from its combined command after archiving the original command string. Lost alarms are synthetic current-layout preservation, one-anchor grafting and ambiguous-anchor appending examples. Do not transfer their ownership/topology scope to sibling suites. Preserve all5 independent historical receipts exactly. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, physical provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1983: Delete the mocked agent-session suite and its prospective selector and aliases. Retire all3 whole dated changed-selector receipts, preserving their original raw objects in historical gaps without transferring counts or durations. Preserve5 unrelated receipts. Changed-source prior results do not validate reduced-source bytes. No fresh execution, equivalent replacement, red/green, measured budget, soak, provider/PTY, rendered, live remote/mobile or cross-platform result is claimed. Historical prior account (preserved verbatim): Audit1931 (sources1928/1929): Partial agent-operation service suite retains only concurrent retry joining with changed-fingerprint conflict. Retire its complete three-title assertion object: old-owner legacy fallback before side effects, nested SSH Pi transcript refusal and completed/ambiguous physical-commit replay fences are no longer backed. Other probe/launch option/ledger/failure/remote-inventory examples disappear as detailed below. No actual provider spawn, live SSH or physical commit proof is retained by this source. Preserve meaningful remaining selectors/assertions and unchanged dated receipts within their original scope. Reduced selectors have no fresh gate execution evidence in this metadata packet. No equivalent replacement, physical watcher/PTY/provider, live SSH/remote/mobile, rendered, cross-platform, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Audit1911: The complete provider PTY-inspection mock suite is removed: missing-provider rejection, stale-PTY renderer unverifiable mapping, preserved supplied renderer failures, and supplied transport-loss mapping alarms are intentionally lost. No actual terminal teardown, daemon error or remote transport was exercised. Only the deleted explicit source tokens, testFile and whole assertionRef are removed. Meaningful remaining source selectors/assertions and unaffected dated receipts retain their original scope. No equivalent replacement, fresh reduced-gate, native/platform/provider, soak or measured-budget result is claimed. Historical prior account (preserved verbatim): Retained exact unchanged-source receipts remain the only current execution evidence. Reduced selectors have no fresh execution result. Historical pre-audit oracle account for changed sources: Existing exact unchanged-selector receipts remain the only current execution evidence. Changed source selectors have no fresh execution result. Historical prior-source oracle account: The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): The retained unchanged-source receipts are the only current execution evidence; reduced-source assertions await validation. Historical oracle account (not a current claim for changed sources): Race independent clients and repeated operation IDs, then assert one physical spawn and one canonical PTY/surface; inject exit-before-reply, provider disconnect, conflicting claim scope, old daemon/relay capabilities, reused handles, stale incarnations, owner mismatch, and topology revision conflict; assert safe adoption or explicit failure without a second spawn or wrong-process attachment. Run fresh/resume with activate true/false against an isolated headed desktop host and a separate paired client, then against isolated headless serve: assert host presentation stays background, only the requesting viewer focuses the exact leaf, inactive calls preserve client/DOM focus, a same-version publication replay cannot lose focus intent, and sibling-first split publication cannot consume exact-leaf intent. Restore legacy split panes and groups beside a newer host-owned tab, preserving exact predecessor/new/successor order, output, input, resize, titles, tab/leaf identity, active group, and multi-client convergence. For completion, drive a known running agent through outer transport loss, authoritative provider rejection, reconnect, explicit stop, real exit status, and successful hook completion; assert unavailable evidence never dispatches completion and two fresh authoritative idle samples are required after the gap. After exact exit, assert terminal and tab listings omit the surface, a stale publication cannot restore it, restart cannot resurrect it, exact tokened fixture PIDs are dead, and unrelated tabs/processes survive until scoped cleanup.", "commands": [ - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts src/renderer/src/runtime/web-session-tabs-sync/navigation/web-session-tabs-sync-focus-intent.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/runtime/web-session-tabs-sync/navigation/web-session-tabs-sync-focus-intent.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts", "pnpm exec vitest run --config config/vitest.config.ts tests/e2e/remote-terminal-tab-retirement.unit.test.ts", "pnpm run build:cli && pnpm run build:electron-vite && node config/scripts/diagnostics/remote-agent-authority/remote-agent-session-authority-repro.mjs", "node --check config/scripts/development/remote-session-cleanup/remote-agent-session-process-cleanup.mjs && node config/scripts/diagnostics/remote-agent-authority/remote-agent-session-authority-repro.mjs", @@ -7400,7 +7364,6 @@ "src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts", - "src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", "tests/e2e/remote-agent-session-focus-authority.spec.ts", "config/scripts/diagnostics/remote-agent-authority/remote-agent-session-authority-repro.mjs", "config/scripts/development/remote-session-cleanup/remote-agent-session-process-cleanup.mjs", @@ -7482,10 +7445,6 @@ "same-epoch stale publications cannot resurrect the retired surface after reconnect" ] }, - { - "file": "src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", - "assertions": ["confirms only the exact handoff after its post-create list completes"] - }, { "file": "src/renderer/src/runtime/web-session-tabs-sync/navigation/web-session-tabs-sync-focus-intent.test.ts", "assertions": [ @@ -7631,7 +7590,8 @@ "Audit2061 (sources2059/2060): Intentionally retire these low-value alarms without replacement: src/main/runtime/orca-runtime.test.ts: ordinary registration cleanup removes only await import('./orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec'); await import('./orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec'); await import('./orca-runtime-tests/worktree-removal-execution-host.spec'); await import('./orca-runtime-tests/terminal-spawn-dispatch.spec'). src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-completion-and-notification.test.ts: ordinary registration cleanup removes only import { registerPtyConnectionSshHookStatusAuthorityTests } from '../host-ownership/pty-connection-ssh-hook-status-authority.spec'; registerPtyConnectionSshHookStatusAuthorityTests(). src/main/runtime/orca-runtime-tests/headless-snapshots-part-02.spec.ts: replaces a cwd parsed before late WSL context with the provider cwd; keeps a live WSL cwd that arrives during late-context snapshot recovery; infers local reconstructed WSL context from a WSL UNC worktree; clears stale terminal file URI hostnames after empty-host OSC7 cwd updates; serializes SSH headless OSC7 cwd as POSIX when the desktop runtime is on Windows; keeps Windows SSH OSC7 cwd as a drive path when the desktop runtime is POSIX; serializes Windows SSH headless OSC7 cwd as a drive path on POSIX desktops; infers restored SSH connection identity from app-scoped PTY ids. src/main/runtime/orca-runtime-tests/mobile-session-tabs-headless-chat-rebuild.spec.ts: survives the rebuild that closing a saved editor tab forces; keeps its place in the tab order when the host lists the workspace again; lists tabs to phones in the order desktop clients draw after a rebuild; keeps the place a window gave a chat across a graph resend before the chat is restored. src/main/runtime/orca-runtime-tests/mobile-session-tabs-cold-serve-hydrate.spec.ts: lists every persisted terminal of a windowless host after a cold restart; lists every persisted terminal in the all-worktrees inventory after a cold restart; lists persisted editor tabs beside the terminals after a cold restart; restores a persisted split group layout on the cold rebuild; restores the saved active group of a split even when it is not the first group; falls back to the active tab group when the saved active group was not restored; keeps the headless group active when there is no persisted split; keeps phone and CLI creates in the group they joined across a cold restart. src/main/runtime/orca-runtime-tests/worktree-removal-failed-retry.spec.ts: lists the leftover with its error, though Git no longer does; takes the normal delete once Git registers a checkout at the path again; joins a retry another client started while this Delete listed Git; shows the failed row with its error, not the scan cached before the delete. src/main/runtime/orca-runtime-tests/worktree-removal-execution-host.spec.ts: refuses an SSH row whose host is unreachable instead of deleting here; refuses a runtime row with no nested SSH target rather than deleting locally; refuses a runtime row that also names a nested SSH target; retains runtime purge dispatch in a recovery job; uses the runtime helper override before resuming an interrupted removal; uses the runtime helper override before retrying a failed removal; captures the watcher callback and retains its job-host receiver; reads the current runtime removal queue. src/main/runtime/orca-runtime-tests/terminal-spawn-dispatch.spec.ts: reports the spawn request before it leaves for the pty controller; reports it even when the spawn itself then fails; does not report it when the create fails before any spawn request; captures the admission method before reading the incarnation; captures the record method before evaluating its state object; skips database payload serialization when no database exists; skips a throwing database payload when no database exists; keeps the database method and receiver captured before its payload; captures the canonical map method before a binding getter changes it; completes the captured replacement fence without another map lookup. src/renderer/src/components/terminal-pane/pty-connection/host-ownership/pty-connection-ssh-hook-status-authority.spec.ts: leaves SSH IPC OSC 9999 status ownership in the main runtime. Existing unrelated evidenceRuns retain their original scope; unchanged dated receipt objects do not validate changed source bytes or reduced selectors. No fresh gate execution or historical import-closure equivalence is claimed.", "Module layout policy correction: retire the remaining program-status-osc.spec.ts event-projection case (production/mock-derived pane-key oracle) and sign-in timeout case (real setTimeout wait with Date-only fake clocks). The runtime test registrar no longer imports that file. No replacement coverage or fresh execution is claimed for these losses; historical receipts retain their original source scope.", "Module layout policy correction: retire the two remaining worktree-ps-agent-row-dismissal.spec.ts registrations. The remint/dismissal/exit case computed expected snapshotVersion increments from a production snapshot and used real-clock vi.waitFor; the legacy OSC case derived its expected paneKey from the production layout-owner switch. The runtime registrar no longer imports this file. These public projection scenarios have no replacement coverage or fresh execution claimed; all historical receipts and earlier loss notes retain their original source scope.", - "Audit2709 removes src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts: intentionally retire waiting out an older inventory and sharing a fresh post-operation request among concurrent callers. No equivalent replacement or fresh execution is claimed. Dated receipts remain historical evidence for their unchanged selectors only; they do not validate the reduced commands or full current gate. Original prospective command before removal: pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts" + "Audit2709 removes src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts: intentionally retire waiting out an older inventory and sharing a fresh post-operation request among concurrent callers. No equivalent replacement or fresh execution is claimed. Dated receipts remain historical evidence for their unchanged selectors only; they do not validate the reduced commands or full current gate. Original prospective command before removal: pnpm exec vitest run --config config/vitest.config.ts src/shared/claimed-agent-pty-owner.test.ts src/main/daemon/providers/adapter/testing/daemon-pty-adapter.test.ts src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts src/main/runtime/terminal/surface/orca-runtime-terminal-retirement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-session-launch.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts. Lost alarms: confirms only the exact handoff after its post-create list completes. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Keep experimental or demote if the focused gate flakes without a product or harness bug, if a retry can physically spawn twice, if a stale exit/publication can replace or resurrect a terminal, or if mixed-version fallback occurs after an authority side effect." }, @@ -9157,36 +9117,34 @@ "providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"], "coveredPlatforms": ["macos"], "coveredProviders": [], - "coverageNotes": "Renderer unit coverage proves replay-guard lost-completion and certified-wedge events carry correlatable tab, worktree, durable leaf, pane, and redacted PTY identity without exposing path-bearing values. Full provider lifecycle attribution and diagnostics-bundle artifact proof remain gaps.", + "coverageNotes": "Retained renderer unit cases exercise guard release after a silent stalled parse and hardcoded breadcrumb event/pane identity with a fake terminal and mocked recorder. The identity-rich hashing/redaction registration was retired whole. No hashed tab/worktree/leaf identity, path-redaction, diagnostics-bundle export, live provider or packaged runtime coverage is claimed.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/6800", "https://github.com/stablyai/orca/issues/6773" ], "invariant": "Terminal lifecycle anomalies around reattach, restore, replay wedges, provider ownership, stale liveness, and fallback routing must leave compact, deduped, privacy-safe breadcrumbs in crash diagnostics so future reports can be attributed from evidence. Replay anomalies must distinguish pane managers and PTYs without recording path-bearing worktree or session identities.", - "oracle": "Drop a replay write completion while allowing its FIFO probe to parse, then require the lost-completion breadcrumb to include pane ID, stable hashes for tab/worktree/leaf identity, and a path-redacted PTY ID. Existing wedge tests require both lost-completion and certified-dead paths to record their distinct event names. Full pane transition traces and diagnostics-bundle artifact proof remain follow-ups.", + "oracle": "Simulate replay and probe completions that never arrive, advance fake timers through both quiet windows, and require the real replay guard to transition from engaged to released while the mocked recorder receives terminal_replay_guard_wedged_release with paneId 1. This does not establish correlation hashing, path redaction or diagnostics-bundle export.", "commands": [ - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts" + "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts --maxWorkers=2" ], "testFiles": ["src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts"], "assertionRefs": [ { "file": "src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts", "assertions": [ - "records correlatable replay identity without exposing worktree or PTY paths", "releases after the probe itself never parses (wedged pipeline) and reports it" ] } ], "evidenceRuns": [ { - "date": "2026-07-29", + "date": "2026-11-10", "runner": "local", "platform": "macos", - "command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/replay-guard.test.ts", "result": "passed", - "durationSeconds": 0.201, - "summary": "The focused file passed 29 tests; replay anomaly breadcrumbs preserved event classification while adding hashed tab/worktree/leaf correlation and a path-redacted PTY identity.", - "currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts" + "command": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts --maxWorkers=2", + "durationSeconds": 1.52, + "summary": "Retained replay-guard suite passed 19 cases on the module-layout wave1 candidate. The named breadcrumb case preserves real replay guard release and the hardcoded wedged event/pane1 with a fake terminal and mocked recorder. No identity-hash/redaction, live provider, diagnostics-bundle, fresh red/green, performance or CI-soak claim." } ], "runtimeBudget": { @@ -9195,11 +9153,11 @@ }, "flakeHistory": { "status": "unknown", - "evidence": "The replay-guard unit slice passed locally once; no CI soak history yet." + "evidence": "Retained selector has no CI soak history; fresh focused execution is recorded separately." }, "redGreenEvidence": { - "status": "partial", - "evidence": "Tests fail if replay anomalies stop recording their event name, omit pane/terminal correlation, or expose the fixture's path-bearing worktree and PTY prefixes. Needs diagnostics-bundle artifact proof and full transition-trace evidence before promotion." + "status": "missing", + "evidence": "No fresh revert/fault control was run for the retained selector. Historical richer-oracle statement before retirement is preserved in knownGaps and is not current reduced-selector evidence." }, "performanceBudget": { "required": true, @@ -9214,7 +9172,10 @@ "The replay identity schema has not yet been exercised in a live Electron/provider failure.", "Current coverage records anomaly breadcrumbs only, not a full pane lifecycle state-machine trace.", "Current coverage does not prove crash/diagnostics bundle export includes the breadcrumb.", - "Current coverage does not assert forbidden transitions across live Electron/provider flows." + "Current coverage does not assert forbidden transitions across live Electron/provider flows.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts. Lost alarms: a wedged release hands the terminal to pane recovery; a healthy parse completion never notifies pane recovery; HOLDS the guard while a slow replay is still parsing — a probe is queued, never a blind release; records correlatable replay identity without exposing worktree or PTY paths; holds past the probe deadline while sibling completions prove the parser alive; settles guards on a disposed xterm without certifying a wedge. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope.", + "Module-layout wave1 retires this complete historical receipt because its explicit current selector was reduced. It does not validate the reduced suite. Original receipt verbatim:\n{\n \"date\": \"2026-07-29\",\n \"runner\": \"local\",\n \"platform\": \"macos\",\n \"command\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/replay-guard.test.ts\",\n \"result\": \"passed\",\n \"durationSeconds\": 0.201,\n \"summary\": \"The focused file passed 29 tests; replay anomaly breadcrumbs preserved event classification while adding hashed tab/worktree/leaf correlation and a path-redacted PTY identity.\",\n \"currentCommand\": \"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/snapshots/replay-guard.test.ts\"\n }", + "Module-layout wave1 historical richer-oracle metadata before identity-registration retirement, retained verbatim without current validation credit: {\"coverageNotes\":\"Renderer unit coverage proves replay-guard lost-completion and certified-wedge events carry correlatable tab, worktree, durable leaf, pane, and redacted PTY identity without exposing path-bearing values. Full provider lifecycle attribution and diagnostics-bundle artifact proof remain gaps.\",\"oracle\":\"Drop a replay write completion while allowing its FIFO probe to parse, then require the lost-completion breadcrumb to include pane ID, stable hashes for tab/worktree/leaf identity, and a path-redacted PTY ID. Existing wedge tests require both lost-completion and certified-dead paths to record their distinct event names. Full pane transition traces and diagnostics-bundle artifact proof remain follow-ups.\",\"redGreenEvidence\":{\"status\":\"partial\",\"evidence\":\"Tests fail if replay anomalies stop recording their event name, omit pane/terminal correlation, or expose the fixture's path-bearing worktree and PTY prefixes. Needs diagnostics-bundle artifact proof and full transition-trace evidence before promotion.\"}}" ], "demotionRule": "Cannot promote if diagnostics are console-only, unbounded, or missing from support artifacts." }, @@ -9663,8 +9624,7 @@ "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/paired-reconnect-multi-pane-materialization.test.ts src/renderer/src/runtime/web-session-tabs-sync/agent-status/paired-reconnect-sidebar-agent-count.test.ts --reporter=dot", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts src/renderer/src/components/terminal-pane/mounting/queued-pane-startup.test.ts src/renderer/src/components/terminal-pane/parking/one-shot-park-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/store/terminals/terminal-pty-identity-replacement.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-detach-attach-handoff.test.ts", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts --reporter=dot", - "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-expired-pane-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-outcome-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/remote-runtime-pty-recovery-state.test.ts src/shared/remote-runtime/remote-runtime-socket-liveness.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-socket-generation.test.ts src/shared/remote-runtime/remote-runtime-client-error-classification.test.ts src/main/runtime/rpc/remote-runtime-server-heartbeat.test.ts src/main/runtime/terminal/launch/orca-runtime-terminal-create-idempotency.test.ts", + "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-expired-pane-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-outcome-recovery.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/remote-runtime-pty-recovery-state.test.ts src/shared/remote-runtime/remote-runtime-socket-liveness.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts src/shared/remote-runtime/control/remote-runtime-shared-control-socket-generation.test.ts src/shared/remote-runtime/remote-runtime-client-error-classification.test.ts src/main/runtime/rpc/remote-runtime-server-heartbeat.test.ts src/main/runtime/terminal/launch/orca-runtime-terminal-create-idempotency.test.ts", "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/store/slices/ui/workspace-navigation-history-hydration.test.ts src/renderer/src/store/repos/repos-ssh-host-reconciliation.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts --reporter=dot", "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/ssh/ssh.test.ts src/main/ipc/worktrees-ssh-repo-owner-resolution.test.ts src/main/ssh/connection/ssh-connection-manager.test.ts src/main/ssh/connection/ssh-connection.test.ts src/preload/ssh-authority-forwarding.test.ts src/renderer/src/runtime/rpc/subscriptions/runtime-client-events.test.ts src/renderer/src/runtime/hosts/ssh/runtime-environment-ssh-state.test.ts src/shared/ssh-retained-payload-admission.test.ts --reporter=dot", "pnpm exec electron-vite build --mode e2e", @@ -9680,7 +9640,6 @@ "src/renderer/src/components/terminal-pane/parking/one-shot-park-replay.test.ts", "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-detach-attach-handoff.test.ts", - "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts", "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-expired-pane-recovery.test.ts", @@ -9728,15 +9687,6 @@ "replacement polling and each in-flight request share a 15-second deadline, then accepted snapshots own recovery without input re-arming polling" ] }, - { - "file": "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts", - "assertions": [ - "cold restored-terminal subscription failure retries and resumes snapshot, output, and input without a fatal error", - "a canonical close before subscription readiness opens exactly one replacement stream without surfacing a fatal error", - "cached terminal pixels remain disconnected until authoritative replay completes", - "authoritative current-stream replay clears every distinct error surfaced by that transport" - ] - }, { "file": "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts", "assertions": [ @@ -9927,7 +9877,8 @@ "Audit2195 removes the low-value startup admission boolean and host deferral predicate inventories from background-terminal-worktree-mount.test.ts. The reduced source retains concrete targeted mount, prune and reveal ownership transitions, not those oracle assertions. Existing selectors remain unexecuted here; no equivalent replacement or new runtime/provider/platform/budget result is claimed. Original retired assertion object verbatim:\n{\n \"file\": \"src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts\",\n \"assertions\": [\n \"startup waits for hydration before mounting terminal panes while degraded mode remains interactive\",\n \"only an explicit local execution host can defer cold activation\",\n \"SSH, remote-runtime, and unresolved owners remain eager\"\n ]\n }", "Audit2709 removes src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts: intentionally retire waiting out an older inventory and sharing a fresh post-operation request among concurrent callers. No equivalent replacement or fresh execution is claimed. Dated receipts remain historical evidence for their unchanged selectors only; they do not validate the reduced commands or full current gate. Original prospective command before removal: pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts src/renderer/src/components/terminal-pane/mounting/queued-pane-startup.test.ts src/renderer/src/components/terminal-pane/parking/one-shot-park-replay.test.ts src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-spawn-and-startup.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-host-surface-replacement.test.ts src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-stream-reconnect.test.ts src/renderer/src/runtime/web-session-tabs-sync/inventory/remote-runtime-session-tabs-inflight.test.ts src/renderer/src/store/terminals/terminal-pty-identity-replacement.test.ts", "Audit3086: retire the destroyed fresh-session fallback settlement/non-destructive reattach alias and its direct selector; retain the detach/attach selector and assertion unchanged. The retained admission cases cover adopt-before-data and explicitly buffered-death admission without a new spawn, without replacing fallback retirement. July 28 evidence was already archived by audit3077; no duplicate retirement or new execution is claimed.", - "Audit3096: remove the deleted reconnect-banner selector and its two UI aliases. The reduced recovery-state selector retains only the deadline cancelling pending backoff and explicit retry owning a new epoch alias; healthy-replacement stale-schedule and independent caller-cutoff cancellation aliases retire. Other retained state cases cover reentrant retry, owner settlement and same-epoch parked revival, without replacing those removed alarms. Prior admission-selector retirement, archived July 28 receipt, unrelated selectors/claims/receipts and old gaps remain unchanged; no fresh execution is claimed." + "Audit3096: remove the deleted reconnect-banner selector and its two UI aliases. The reduced recovery-state selector retains only the deadline cancelling pending backoff and explicit retry owning a new epoch alias; healthy-replacement stale-schedule and independent caller-cutoff cancellation aliases retire. Other retained state cases cover reentrant retry, owner settlement and same-epoch parked revival, without replacing those removed alarms. Prior admission-selector retirement, archived July 28 receipt, unrelated selectors/claims/receipts and old gaps remain unchanged; no fresh execution is claimed.", + "Module-layout wave1 intentionally retires complete prohibited owning registrations in src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts. Lost alarms: attaches to an existing remote runtime terminal handle; reports a rejected multiplex write through the pane recovery callback; does not report a rejected write from a superseded multiplex stream; does not report attachment health until the authoritative PTY snapshot arrives; drops its side-effect gauge from the census on %s; recovers when the first restored-terminal subscription attempt is offline; recovers when the runtime closes before a restored subscription becomes ready; surfaces a fatal error during subscription setup exactly once; reports viewer disconnect as an unverifiable remote exit; clears every retained transport error when the current stream becomes healthy. No equivalent replacement coverage or pass for a reduced selector is claimed. All unrelated selectors and historical receipts retain their original scope." ], "demotionRule": "Cannot promote if provider failure can close panes or if the oracle is screenshot-only." }, diff --git a/config/scripts/ci/sharding/ci-shard-timings.json b/config/scripts/ci/sharding/ci-shard-timings.json index 3b6e0e6bf81..9ed17e62079 100644 --- a/config/scripts/ci/sharding/ci-shard-timings.json +++ b/config/scripts/ci/sharding/ci-shard-timings.json @@ -4622,11 +4622,9 @@ "src/preload/usage-provider-api.test.ts": 46, "src/renderer/src/app-shell/app-command-handlers-tab-rename.test.ts": 1786, "src/renderer/src/app-shell/shutdown-checkpoint-persist.test.ts": 65, - "src/renderer/src/app-shell/shutdown-checkpoint-restart-lifecycle.test.ts": 210, "src/renderer/src/app-shell/startup-window-parity.test.ts": 2306, "src/renderer/src/app-shell/use-profile-state-save-delay-notice.sonner.test.tsx": 355, "src/renderer/src/app-shell/use-profile-state-save-delay-notice.test.tsx": 293, - "src/renderer/src/app-shell/use-visible-hosted-review-refresh.test.tsx": 572, "src/renderer/src/app-shell/visible-review-reporting.test.ts": 1872, "src/renderer/src/app-shell/workspace-view-cross-client-sync.test.tsx": 1170, "src/renderer/src/app-shell/worktree-creation-chat-handoff.test.tsx": 2824, @@ -4711,7 +4709,6 @@ "src/renderer/src/components/automations/runs/use-automation-runs-dashboard.test.tsx": 854, "src/renderer/src/components/automations/use-selected-automation-run-history.test.tsx": 2104, "src/renderer/src/components/browser-favicon.test.tsx": 333, - "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx": 6219, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx": 5826, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.failure-remount.test.tsx": 3085, "src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx": 4575, @@ -4808,7 +4805,6 @@ "src/renderer/src/components/dashboard-popout/preview-terminal-box-fit-lifecycle.test.ts": 192, "src/renderer/src/components/dashboard-popout/preview-terminal-ime-bridge-kitty-bytes.test.ts": 479, "src/renderer/src/components/dashboard-popout/remote-runtime-terminal-preview-api.test.ts": 81, - "src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.test.ts": 2108, "src/renderer/src/components/dashboard-popout/useDashboardSnapshot.test.tsx": 432, "src/renderer/src/components/dashboard/AgentDashboardDrawer.focus.test.tsx": 2567, "src/renderer/src/components/dashboard/AgentDashboardDrawer.test.tsx": 1981, @@ -4830,14 +4826,11 @@ "src/renderer/src/components/diff-comments/diff-comment-popover-outside-click.test.tsx": 1889, "src/renderer/src/components/diff-comments/useDiffCommentDecorator.lifecycle.test.tsx": 2669, "src/renderer/src/components/diff-comments/useDiffCommentDecorator.range-selection.test.tsx": 1782, - "src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.captured-failure.test.tsx": 2412, "src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.copy.test.tsx": 3108, - "src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx": 3230, "src/renderer/src/components/editor/DiagramViewerModal.test.tsx": 2539, "src/renderer/src/components/editor/diff/sections/DiffSectionBody.word-wrap-lifecycle.test.tsx": 3036, "src/renderer/src/components/editor/diff/monaco-view/DiffViewer.word-wrap-lifecycle.test.tsx": 2499, "src/renderer/src/components/editor/EditorConflictReviewSurface.live-record.test.tsx": 1193, - "src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx": 3162, "src/renderer/src/components/editor/EditorEditFileSurface.monaco-containment.test.tsx": 2666, "src/renderer/src/components/editor/EditorFileLoadErrorView.test.tsx": 660, "src/renderer/src/components/editor/EditorFileLocationMenuItems.test.tsx": 846, @@ -4849,17 +4842,13 @@ "src/renderer/src/components/editor/notebook/document/IpynbRendering.test.tsx": 2143, "src/renderer/src/components/editor/markdown-preview/surface/MarkdownPreview.link-routing.interaction.test.tsx": 1127, "src/renderer/src/components/editor/MediaViewer.test.tsx": 308, - "src/renderer/src/components/editor/MermaidViewer.test.tsx": 3079, - "src/renderer/src/components/editor/MonacoEditor.owner-view-state.test.tsx": 7073, "src/renderer/src/components/editor/PdfFind.test.tsx": 581, "src/renderer/src/components/editor/PluginBytesCustomEditorSurface.test.tsx": 641, "src/renderer/src/components/editor/PluginEditorNeedsDesktopNotice.test.tsx": 2107, "src/renderer/src/components/editor/PluginFileTypeSuggestion.test.tsx": 2062, "src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownEditor.focus-leave.test.tsx": 4927, - "src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.lazy-chunk.test.tsx": 367, "src/renderer/src/components/editor/ai-vault-session-log-content.test.tsx": 1899, "src/renderer/src/components/editor/check-details/actions/check-annotation-path.test.ts": 53, - "src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts": 259, "src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-lifetime.test.ts": 7921, "src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-shell.test.tsx": 4492, "src/renderer/src/components/editor/model-lifetime/retention/closed-editor-tab-disposal.test.ts": 121, @@ -4963,7 +4952,6 @@ "src/renderer/src/components/editor/monaco-programmatic-sync.test.ts": 46, "src/renderer/src/components/editor/pdf-orca-media-range-transport.test.ts": 282, "src/renderer/src/components/editor/pdf-view-position.test.ts": 74, - "src/renderer/src/components/editor/pdf-viewer-failed-load-recovery.test.tsx": 2363, "src/renderer/src/components/editor/pdf-viewer-zoom-ownership.test.tsx": 789, "src/renderer/src/components/editor/plugin-custom-editor-host.test.ts": 480, "src/renderer/src/components/editor/rich-markdown/raw-source/raw-markdown-html-line-break.test.ts": 2188, @@ -5034,7 +5022,6 @@ "src/renderer/src/components/editor/use-monaco-editor-mount.cursor-owner.test.tsx": 1332, "src/renderer/src/components/editor/use-monaco-editor-mount.stale-cache.test.tsx": 877, "src/renderer/src/components/editor/use-monaco-reveal-scheduler.test.tsx": 273, - "src/renderer/src/components/editor/use-pdf-viewer-find-shortcut.test.tsx": 907, "src/renderer/src/components/editor/use-preserve-section-during-external-edit.test.tsx": 268, "src/renderer/src/components/editor/rich-markdown/tables/use-rich-markdown-table-context-menu.test.tsx": 2238, "src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.test.tsx": 2018, @@ -5042,7 +5029,6 @@ "src/renderer/src/components/editor/useEditorPanelHostUnresolvedContentState.test.tsx": 1952, "src/renderer/src/components/editor/notebook/kernel/useIpynbCellExecution.test.tsx": 695, "src/renderer/src/components/editor/notebook/document/useIpynbDocumentEditing.test.tsx": 2192, - "src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.test.tsx": 323, "src/renderer/src/components/editor/rich-markdown/links/useLinkBubble.test.tsx": 493, "src/renderer/src/components/editor/rich-markdown/images/useLocalImagePick.test.tsx": 1319, "src/renderer/src/components/editor/document-images/useLocalImageSrc.test.ts": 736, @@ -5066,11 +5052,7 @@ "src/renderer/src/components/emulator-pane/emulator-screen-stream-content.test.tsx": 408, "src/renderer/src/components/emulator-pane/use-emulator-pane-session.test.tsx": 1968, "src/renderer/src/components/emulator-pane/use-mobile-emulator-tab-intro-actions.test.tsx": 1828, - "src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.lazy-chunk.test.tsx": 451, - "src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.never-landed-reload.test.tsx": 449, - "src/renderer/src/components/feature-tips/ChatUiHereCornerCard.test.tsx": 2798, "src/renderer/src/components/feature-tips/ChatUiHereFeatureTipVisual.test.tsx": 1093, - "src/renderer/src/components/feature-tips/ChatUiHereTip.test.tsx": 6788, "src/renderer/src/components/feature-tips/feature-tip-modal-state.test.ts": 244, "src/renderer/src/components/feature-wall/FeatureWallSetupWorkflowActions.test.tsx": 2028, "src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts": 466, @@ -5215,7 +5197,6 @@ "src/renderer/src/components/native-chat/attachments/intake/native-chat-composer-drop-scope.test.tsx": 1409, "src/renderer/src/components/native-chat/attachments/intake/native-chat-composer-ime-file-drop.test.tsx": 1326, "src/renderer/src/components/native-chat/drafts/native-chat-composer-prompt-recall.test.tsx": 1724, - "src/renderer/src/components/native-chat/composer/focus/native-chat-composer-reveal-focus.test.tsx": 526, "src/renderer/src/components/native-chat/attachments/intake/native-chat-composer-workspace-file-drop.test.tsx": 1166, "src/renderer/src/components/native-chat/drafts/native-chat-draft-retention.test.ts": 1415, "src/renderer/src/components/native-chat/attachments/intake/native-chat-external-attachments-destination.test.tsx": 951, @@ -5279,7 +5260,6 @@ "src/renderer/src/components/native-chat/conversation/projection/use-native-chat-context-clear-floor.test.tsx": 271, "src/renderer/src/components/native-chat/pane/actions/use-native-chat-context-menu.targets.test.tsx": 3071, "src/renderer/src/components/native-chat/attachments/intake/use-native-chat-external-attachments.test.tsx": 797, - "src/renderer/src/components/native-chat/viewport/find/use-native-chat-find.test.tsx": 1458, "src/renderer/src/components/native-chat/session/status/use-native-chat-hook-status.test.ts": 1927, "src/renderer/src/components/native-chat/host-access/use-native-chat-host-outage.test.tsx": 2131, "src/renderer/src/components/native-chat/drafts/use-native-chat-launch-draft-adoption.test.tsx": 319, @@ -5328,7 +5308,6 @@ "src/renderer/src/components/new-workspace/use-recent-project-ids.test.ts": 1730, "src/renderer/src/components/new-workspace/use-smart-workspace-name-field-foundation.source.test.tsx": 2290, "src/renderer/src/components/new-workspace/work-item-source-selection.test.ts": 1736, - "src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.command-finished.test.tsx": 681, "src/renderer/src/components/onboarding/OnboardingLanguageLoading.test.tsx": 2167, "src/renderer/src/components/onboarding/onboarding-feature-setup.test.ts": 76, "src/renderer/src/components/pet/pet-blob-cache.test.ts": 64, @@ -5392,7 +5371,6 @@ "src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-panel-session-list-request.test.tsx": 3044, "src/renderer/src/components/right-sidebar/ai-vault/search/ai-vault-search-session.test.ts": 201, "src/renderer/src/components/right-sidebar/ai-vault/accounts/ai-vault-session-account-render.test.tsx": 2885, - "src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-session-conditional-refresh-hook.test.tsx": 2370, "src/renderer/src/components/right-sidebar/ai-vault/session-list/ai-vault-session-filters.test.ts": 105, "src/renderer/src/components/right-sidebar/ai-vault/scope/ai-vault-session-projects.test.ts": 117, "src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-session-refresh.test.ts": 2034, @@ -5408,7 +5386,6 @@ "src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.test.ts": 321, "src/renderer/src/components/right-sidebar/checks-panel/comments/review-comment-markdown.test.tsx": 2171, "src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-manual-refresh-recovery.test.tsx": 2909, - "src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-polling.test.tsx": 2189, "src/renderer/src/lib/repository-refresh/coalesced-poll-runner.test.ts": 61, "src/renderer/src/components/right-sidebar/file-explorer/mutations/file-explorer-delete-undo-precondition.test.tsx": 2023, "src/renderer/src/components/right-sidebar/file-explorer/tree/file-explorer-dir-pages.test.ts": 1767, @@ -5430,7 +5407,6 @@ "src/renderer/src/components/right-sidebar/plugin-panels/health/plugin-panel-watchdog.test.ts": 116, "src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-ai-launch-ack.test.ts": 118, "src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.test.tsx": 3290, - "src/renderer/src/components/right-sidebar/right-sidebar-titlebar-drag-regions.render.test.tsx": 1044, "src/renderer/src/components/right-sidebar/source-control/ai/runSourceControlAgentActionStart.test.ts": 1917, "src/renderer/src/components/right-sidebar/source-control/review/source-control-create-pr-intent-flow.test.ts": 71, "src/renderer/src/components/right-sidebar/source-control/review/source-control-manual-review-url.test.ts": 77, @@ -5475,7 +5451,6 @@ "src/renderer/src/components/settings/browser/BrowserSitePermissionsSetting.test.tsx": 739, "src/renderer/src/components/settings/browser/BrowserUserAgentSetting.test.tsx": 2325, "src/renderer/src/components/settings/chat/ChatNamingSetting.test.tsx": 2454, - "src/renderer/src/components/settings/chat/ChatPermissionSetting.test.tsx": 834, "src/renderer/src/components/settings/chat/ChatSettingsSection.test.tsx": 2718, "src/renderer/src/lib/skills/runtime/skill-runtime-command.test.ts": 1670, "src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx": 1025, @@ -5488,7 +5463,6 @@ "src/renderer/src/components/settings/advanced/network/NodeDistMirrorDialog.test.tsx": 2412, "src/renderer/src/components/plugins/list/PluginsSettingsSection.lifecycle.test.tsx": 5402, "src/renderer/src/components/settings/runtime-environments/pairing/RuntimePairingGeneratorForm.test.tsx": 461, - "src/renderer/src/components/settings/session-history/SessionHistorySettingsPane.test.tsx": 1474, "src/renderer/src/components/settings/appearance/fonts/FontAutocomplete.test.tsx": 1202, "src/renderer/src/components/settings/form-controls/SettingsFormControls.segmented-control.test.tsx": 730, "src/renderer/src/components/settings/shortcuts/ShortcutFilterRail.test.ts": 273, @@ -5503,7 +5477,6 @@ "src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts": 549, "src/renderer/src/components/settings/terminal/appearance/TerminalContrastSetting.test.tsx": 798, "src/renderer/src/components/settings/terminal/session-management/TerminalLimitSetting.test.tsx": 1015, - "src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx": 1237, "src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx": 401, "src/renderer/src/components/settings/voice/VoiceMicrophoneSetting.test.tsx": 957, "src/renderer/src/components/settings/voice/VoicePane.test.tsx": 1042, @@ -5538,7 +5511,6 @@ "src/renderer/src/components/session-search/use-session-search-status.test.tsx": 477, "src/renderer/src/components/settings/accounts/usage/use-zcode-plan-credentials.test.tsx": 880, "src/renderer/src/components/setup-guide/SetupGuideModal.mount-gating.test.tsx": 2348, - "src/renderer/src/components/shared/MacFolderAccessFixDialog.test.tsx": 1356, "src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts": 1925, "src/renderer/src/components/shared/useDaemonActions.test.tsx": 332, "src/renderer/src/components/sidebar/host-admission/ssh/AddRemoteHostDialog.config-picker.test.tsx": 1178, @@ -5560,7 +5532,6 @@ "src/renderer/src/components/sidebar/SeveredTerminalDaemonBanner.test.tsx": 574, "src/renderer/src/components/sidebar/Sidebar.test.tsx": 833, "src/renderer/src/components/sidebar/SidebarAgentsList.test.tsx": 2985, - "src/renderer/src/components/sidebar/sidebar-feedback/SidebarFeedbackDialog.test.tsx": 2529, "src/renderer/src/components/sidebar/StalledTerminalDaemonBanner.test.tsx": 320, "src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneGrid.test.tsx": 360, "src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx": 2032, @@ -5663,7 +5634,6 @@ "src/renderer/src/components/stats/usage-daily-chart.test.tsx": 333, "src/renderer/src/components/stats/usage-overview-model.test.ts": 364, "src/renderer/src/components/status-bar/ClaudeSwitcherMenu.owner-lifetime.test.tsx": 2317, - "src/renderer/src/components/status-bar/PortsStatusSegment.render-stability.test.tsx": 2161, "src/renderer/src/components/status-bar/SshStatusSegment.large-host-list.test.tsx": 1838, "src/renderer/src/components/status-bar/StatusBarUsageChangeNotices.test.tsx": 785, @@ -5685,7 +5655,6 @@ "src/renderer/src/components/status-bar/use-resource-pty-actions.test.tsx": 879, "src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx": 742, "src/renderer/src/components/tab-bar/CopyTabIdMenuItem.test.tsx": 428, - "src/renderer/src/components/tab-bar/NativeChatNamingFailure.test.tsx": 1062, "src/renderer/src/components/tab-bar/TabBarCreateEntry.search.test.tsx": 3224, "src/renderer/src/components/tab-bar/TabBarItemRow.test.tsx": 2116, "src/renderer/src/components/tab-bar/TabBarQuickCommandItem.test.tsx": 384, @@ -5808,7 +5777,6 @@ "src/renderer/src/components/terminal-pane/layout/close/pending-pane-close-confirmation.test.ts": 4695, "src/renderer/src/components/terminal-pane/layout/close/pending-runtime-pane-close.test.ts": 3487, "src/renderer/src/components/terminal-pane/layout/close/pending-split-close.test.ts": 4680, - "tests/e2e/terminal-agent-pane-launch.unit.test.ts": 8045, "src/renderer/src/components/terminal-pane/snapshots/pty-buffer-serializer.test.ts": 53, "src/renderer/src/components/terminal-pane/pty-connection/agent-identity/pty-connection-command-finished-late-done.test.ts": 10940, "src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-completion-and-notification.test.ts": 18380, @@ -5856,7 +5824,6 @@ "src/renderer/src/components/terminal-pane/pty-connection/host-ownership/unserved-ssh-pane-bridge.test.ts": 70, "src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-dispatcher-delivery-resync.test.ts": 85, "src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-dispatcher-pi-routing.test.ts": 1362, - "src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-dispatcher-push-reattach.test.ts": 108, "src/renderer/src/components/terminal-pane/pty-transport/input/pty-input-write-queue.test.ts": 787, "src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-model-restore-channel.test.ts": 44, "src/renderer/src/components/terminal-pane/pty-transport/output/pty-output-processor-program-activity.test.ts": 160, @@ -5893,7 +5860,6 @@ "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-query-reply-immediate.test.ts": 1556, "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/remote-runtime-pty-recovery-state.test.ts": 85, "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-activation-inventory-fallback.test.ts": 1311, - "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts": 2346, "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts": 14353, "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-outcome-recovery.test.ts": 2041, "src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-empty-initial-snapshot.test.ts": 1491, @@ -5994,10 +5960,8 @@ "src/renderer/src/components/terminal-pane/layout/reconciliation/terminal-pane-frame-reconciliation.test.ts": 903, "src/renderer/src/components/terminal-pane/host-state/terminal-pane-host-state-memo.test.ts": 4534, "src/renderer/src/components/terminal-pane/mounting/terminal-pane-mount-bootstrap.test.ts": 1723, - "src/renderer/src/components/terminal-pane/render-health/recovery/terminal-pane-recovery-unsettled-fallback.test.ts": 831, "src/renderer/src/components/terminal-pane/render-health/recovery/terminal-pane-recovery.test.ts": 949, "src/renderer/src/components/terminal-pane/layout/split/terminal-pane-split-request-routing.test.ts": 2015, - "src/renderer/src/components/terminal-pane/pty-transport/recovery/terminal-pane-surface-ownership.test.ts": 426, "src/renderer/src/components/terminal-pane/layout/detach/terminal-pane-tab-detach-main-commit.test.ts": 2090, "src/renderer/src/components/terminal-pane/layout/detach/terminal-pane-tab-detach-main-move.test.ts": 2147, "src/renderer/src/components/terminal-pane/parking/verdict/terminal-park-verdict-flip-telemetry.test.ts": 123, @@ -6187,7 +6151,6 @@ "src/renderer/src/hooks/useImmediateMutation.test.ts": 618, "src/renderer/src/hooks/skills/discovery/useInstalledAgentSkills.react.test.tsx": 2093, "src/renderer/src/hooks/useIpcEvents-agent-status-connection-attribution.test.ts": 5510, - "src/renderer/src/hooks/useIpcEvents-agent-status-hook-titles.test.ts": 5621, "src/renderer/src/hooks/useIpcEvents-agent-status-pane-teardown.test.ts": 5268, "src/renderer/src/hooks/useIpcEvents-agent-status-queue-ordering.test.ts": 3680, "src/renderer/src/hooks/useIpcEvents-agent-status-snapshot-hydration.test.ts": 7403, @@ -6204,7 +6167,6 @@ "src/renderer/src/hooks/useIpcEvents-runtime-environment-selectors.test.ts": 530, "src/renderer/src/hooks/useIpcEvents-session-tab-close-request.test.ts": 2252, "src/renderer/src/hooks/useIssueMetadata.test.tsx": 344, - "src/renderer/src/hooks/useMacTccAttributionSeveredNotice.test.tsx": 2358, "src/renderer/src/hooks/useMetadataListRequest.test.tsx": 266, "src/renderer/src/hooks/useModalReturnFocus.test.tsx": 1938, "src/renderer/src/hooks/clipboard/primary-selection/usePrimarySelectionPaste.terminal-native-paste.test.tsx": 388, @@ -6240,8 +6202,6 @@ "src/renderer/src/lib/agent-session/hibernation/agent-hibernation-pane-age.test.ts": 41, "src/renderer/src/lib/agent-session/hibernation/agent-hibernation-planner.test.ts": 341, "src/renderer/src/lib/agent-session/hibernation/agent-hibernation-runtime-liveness-race.test.ts": 1914, - "src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-prompt-transport.test.ts": 1326, - "src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-routing.test.ts": 1266, "src/renderer/src/lib/agent-session/launch/pane/agent-launch-pane-verdict-application.test.ts": 1869, "src/renderer/src/lib/agent-session/launch/routing/agent-launch-route-input.test.ts": 1659, "src/renderer/src/lib/agent-session/launch/routing/agent-launch-route-registered-agents.test.ts": 1749, @@ -6312,7 +6272,6 @@ "src/renderer/src/lib/source-control/review/comments/comment-body-submit-state.test.ts": 55, "src/renderer/src/lib/composer-submit-cancellation.test.ts": 41, "src/renderer/src/lib/workspace/ownership/connection-context.test.ts": 1817, - "src/renderer/src/lib/crash-diagnostics.test.ts": 176, "src/renderer/src/lib/create-untitled-markdown.test.ts": 566, "src/renderer/src/lib/desktop-created-server-tabs.test.ts": 1933, "src/renderer/src/lib/workspace/ownership/direct-ssh-target-scope.test.ts": 48, @@ -6324,8 +6283,7 @@ "src/renderer/src/runtime/repo-hooks/ensure-hooks-confirmed.test.ts": 799, "src/renderer/src/lib/simulator/tabs/ensure-simulator-tab-behavior.test.ts": 3601, "src/renderer/src/runtime/hosts/platform/execution-host-facts.test.ts": 1796, - "src/renderer/src/lib/feature-education-telemetry.test.ts": 52, - "src/renderer/src/lib/feedback-image-attachments.test.ts": 557, + "src/renderer/src/components/sidebar/sidebar-feedback/feedback-image-attachments.test.ts": 557, "src/renderer/src/lib/files/search/file-search-selection.test.ts": 51, "src/renderer/src/lib/finished-agent-resume-resurrection.test.ts": 1872, "src/renderer/src/components/terminal-pane/parking/retention/foreground-terminal-tabs.test.ts": 55, @@ -6351,10 +6309,7 @@ "src/renderer/src/lib/workspace/creation/work-item/launch-work-item-direct-accepted.test.tsx": 2754, "src/renderer/src/lib/workspace/creation/work-item/launch-work-item-direct-defaults.test.ts": 1594, "src/renderer/src/lib/workspace/creation/work-item/launch-work-item-direct-setup.test.tsx": 3435, - "src/renderer/src/lib/lazy-chunk-recovery-reload.test.ts": 246, - "src/renderer/src/lib/lazy-with-retry.never-landed-reload.test.ts": 194, - "src/renderer/src/lib/lazy-with-retry.preload.test.tsx": 208, - "src/renderer/src/lib/lazy-with-retry.test.ts": 282, + "src/renderer/src/app-shell/startup/lazy-with-retry.never-landed-reload.test.ts": 194, "src/renderer/src/lib/work-items/linear/linear-board-drag-payload.test.ts": 58, "src/renderer/src/lib/work-items/linear/linear-issue-workspace-attachment.test.ts": 99, "src/renderer/src/lib/link-source-local-open-owner.test.ts": 433, @@ -6384,7 +6339,6 @@ "src/renderer/src/lib/pane-manager/pane-cursor-blink-suspension.test.ts": 429, "src/renderer/src/lib/pane-manager/pane-drag-window-blur.test.ts": 618, "src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts": 94, - "src/renderer/src/lib/pane-manager/pane-fit.test.ts": 166, "src/renderer/src/lib/pane-manager/pane-inline-image-recovery.test.ts": 61, "src/renderer/src/lib/pane-manager/pane-inline-images.test.ts": 74, "src/renderer/src/lib/pane-manager/pane-layout-geometry-apply.test.ts": 897, @@ -6394,11 +6348,6 @@ "src/renderer/src/lib/pane-manager/pane-terminal-foreground-render-settle.test.ts": 43, "src/renderer/src/lib/pane-manager/pane-terminal-mouse-wheel.test.ts": 88, "src/renderer/src/lib/pane-manager/pane-terminal-output-queue-chunks.test.ts": 44, - "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-ack-credit.test.ts": 173, - "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-disposed-writes.test.ts": 121, - "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts": 301, - "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-synchronized-frames.test.ts": 213, - "src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts": 236, "src/renderer/src/lib/pane-manager/pane-tree-equalization-parity.test.ts": 182, "src/renderer/src/lib/pane-manager/pane-tree-equalization-scaling.test.ts": 239, "src/renderer/src/lib/pane-manager/pane-tree-match-moves.test.ts": 662, @@ -6415,7 +6364,6 @@ "src/renderer/src/lib/pane-manager/terminal-indic-cluster-ranges.test.ts": 50, "src/renderer/src/lib/pane-manager/terminal-kitty-file-reader.test.ts": 83, "src/renderer/src/lib/pane-manager/terminal-ligatures-addon.test.ts": 59, - "src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts": 48, "src/renderer/src/lib/pane-manager/terminal-linkifier-hover-reset-on-write.test.ts": 59, "src/renderer/src/lib/pane-manager/terminal-mouse-encoding-tracker.test.ts": 95, "src/renderer/src/lib/pane-manager/terminal-mouse-report-finite-coordinates.test.ts": 272, @@ -6433,7 +6381,6 @@ "src/renderer/src/lib/pane-manager/xterm-instance-disposed.test.ts": 57, "src/renderer/src/lib/pane-manager/xterm-parse-barrier.test.ts": 154, "src/renderer/src/lib/pane-manager/xterm-user-scrolling-contract.test.ts": 1266, - "src/renderer/src/lib/pane-manager/xterm-write-callback-guard.test.ts": 53, "src/renderer/src/components/terminal-pane/session-restore/spawn-deferral/pane-spawn-deferral-drain.test.ts": 61, "src/renderer/src/components/terminal-pane/mounting/parked-terminal-host-hydration.test.ts": 274, "src/renderer/src/lib/text-input/paste/paste-payload-metadata.test.ts": 53, @@ -6448,15 +6395,10 @@ "src/renderer/src/lib/workspace/creation/project-clone-url-prefill.test.ts": 61, "src/renderer/src/lib/workspace/creation/project-host-workspace-target.jira-mapping.test.ts": 76, "src/renderer/src/lib/workspace/creation/project-host-workspace-target.test.ts": 92, - "src/renderer/src/lib/react-commit-cascade-observer.react185.test.tsx": 285, - "src/renderer/src/lib/react-commit-cascade-observer.test.ts": 189, - "src/renderer/src/lib/react-commit-cascade-telemetry.test.ts": 159, - "src/renderer/src/lib/react-error-boundary-reporting.test.ts": 55, "src/renderer/src/lib/editor/file-operations/remap-open-editor-tabs-for-path-change.test.ts": 2178, "src/renderer/src/lib/rename-user-named-tab-access.test.ts": 2804, "src/renderer/src/lib/renderer-agent-status-observation-ingress.test.ts": 81, - "src/renderer/src/lib/renderer-app-platform.test.ts": 197, - "src/renderer/src/lib/renderer-memory-profile.test.ts": 70, + "src/renderer/src/diagnostics/memory/renderer-memory-profile.test.ts": 70, "src/renderer/src/lib/repo-search.test.ts": 54, "src/renderer/src/lib/repo-slug-cache.test.ts": 52, "src/renderer/src/lib/repo-slug-index.test.ts": 2240, @@ -6479,7 +6421,6 @@ "src/renderer/src/lib/script-textarea-rows.test.ts": 93, "src/renderer/src/lib/session-write-subscriber-acknowledgment.test.ts": 1823, "src/renderer/src/lib/session-write-subscriber.test.ts": 1882, - "src/renderer/src/lib/shutdown-checkpoint-guard.test.ts": 209, "src/renderer/src/lib/sidebar-ended-creations.test.ts": 1889, "src/renderer/src/lib/sidebar-pending-layer.test.ts": 338, "src/renderer/src/lib/sidebar-shadow/workspace-activity-shadow.test.ts": 1728, @@ -6490,7 +6431,7 @@ "src/renderer/src/lib/simulator/tabs/simulator-tab-palette-activation.test.ts": 1903, "src/renderer/src/lib/source-control/remote/source-control-remote-error.test.ts": 340, "src/renderer/src/lib/ssh-config-passive-sync.test.ts": 1513, - "src/renderer/src/lib/state-collection-byte-estimate.test.ts": 86, + "src/renderer/src/diagnostics/memory/state-collection-byte-estimate.test.ts": 86, "src/renderer/src/lib/agent-session/launch/structured-agent-session-launch-draft.test.ts": 1042, "src/renderer/src/lib/agent-session/launch/structured-agent-session-launch-prerequisites.test.ts": 2427, "src/renderer/src/lib/agent-session/options/structured-agent-session-selection-lifetime.test.tsx": 2947, @@ -6515,8 +6456,6 @@ "src/renderer/src/lib/use-tab-agent-opencode-native-title.test.ts": 2093, "src/renderer/src/lib/use-tab-agent.test.ts": 1961, "src/renderer/src/lib/wake-sleeping-agents-live-unmounted-pane.test.ts": 2432, - "src/renderer/src/lib/window-visibility-interval.test.ts": 48, - "src/renderer/src/lib/window-visibility-timeout-poller.test.ts": 207, "src/renderer/src/runtime/hosts/capabilities/windows-terminal-capabilities-race.test.ts": 450, "src/renderer/src/lib/workspace/activation/workspace-activation-path-gate.test.ts": 2107, "src/renderer/src/lib/browser/workspace/workspace-browser-tab-open-placement.test.ts": 1996, @@ -6625,11 +6564,6 @@ "src/renderer/src/runtime/browser/creation/web-runtime-browser-creation-placement.test.ts": 1927, "src/renderer/src/runtime/browser/creation/web-runtime-browser-link-open.test.ts": 2217, "src/renderer/src/runtime/browser/creation/web-runtime-browser-link-session.test.ts": 1964, - "src/renderer/src/runtime/browser/creation/web-runtime-session-browser-create-split-placement.test.ts": 949, - "src/renderer/src/runtime/web-session-tabs-sync/closing/web-runtime-session-tab-activate-close.test.ts": 1426, - "src/renderer/src/runtime/hosted-terminals/actions/web-runtime-session-terminal-pane-delegation.test.ts": 1341, - "src/renderer/src/runtime/hosted-terminals/creation/web-runtime-session-terminal-workspace-routing.test.ts": 768, - "src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts": 743, "src/renderer/src/runtime/web-session-tabs-sync/closing/web-session-close-replay.test.ts": 1119, "src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-open-files-batch-equivalence.test.ts": 1478, "src/renderer/src/runtime/web-session-tabs-sync/identity/web-session-restored-placeholder-identity.test.ts": 985, @@ -6638,7 +6572,6 @@ "src/renderer/src/runtime/web-session-tabs-sync/subscriptions/web-session-tabs-stream-end-resubscribe.test.tsx": 2282, "src/renderer/src/runtime/web-session-tabs-sync/agent-status/web-session-tabs-sync-agent-status.test.ts": 987, "src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-browser-tabs.test.ts": 925, - "src/renderer/src/runtime/web-session-tabs-sync/subscriptions/web-session-tabs-sync-completion-on-reveal.test.tsx": 2249, "src/renderer/src/runtime/web-session-tabs-sync/identity/web-session-tabs-sync-cross-runtime-worktree.test.ts": 964, "src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-ownership.test.ts": 1902, "src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-tabs.test.ts": 1073, @@ -6732,7 +6665,6 @@ "src/renderer/src/store/slices/browser/pages/browser-workspace-doc-location.test.ts": 2007, "src/renderer/src/store/slices/browser/browser.test.ts": 2001, "src/renderer/src/store/slices/worktrees/teardown/bulk-worktree-purge-terminal-maps-leak.test.ts": 1727, - "src/renderer/src/store/workspaces/creation/active-workspace-create-actions.test.ts": 1879, "src/renderer/src/store/terminals/codex-restart-notice-lifecycle.test.ts": 1685, "src/renderer/src/store/slices/tabs/content-tabs-from-main-frames.test.ts": 2025, "src/renderer/src/store/slices/tabs/content-tabs-paired-server-workspace.test.ts": 1923, @@ -8009,7 +7941,6 @@ "tests/e2e/terminal-binding-crash-hydration.unit.test.ts": 1769, "tests/e2e/terminal-foreground-confirmation.unit.test.ts": 644, "tests/e2e/terminal-perf-presentation.unit.test.ts": 60, - "tests/e2e/web-runtime-session-terminal-host-authority.unit.test.ts": 1061, "tests/e2e/worktree-status-warning.unit.test.ts": 2331, "tests/tools/branch-line-total-renderer.test.mjs": 1222, "tests/tools/omp-close-lifecycle.test.mjs": 403, diff --git a/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs b/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs index a095551ab09..d7542ed8616 100644 --- a/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs +++ b/config/scripts/test-runtime/sqlite-admission/vitest-node-runtime-files.mjs @@ -27,8 +27,6 @@ const NODE_RUNTIME_INCLUDE = [ 'src/main/browser/extensions/native-messaging/browser-extension-native-host-process-backpressure.test.ts', 'src/main/daemon/providers/lifecycle/testing/daemon-init-provider-installation.test.ts', 'src/renderer/src/components/terminal-pane/pty-transport/input/pty-input-write-queue.test.ts', - 'src/renderer/src/lib/react-commit-cascade-observer.test.ts', - 'src/renderer/src/lib/react-commit-cascade-telemetry.test.ts', // Asserts payloads are collected; Bun's GC scans the stack conservatively and may keep one alive. 'src/shared/ws-inbound-message-queue.test.ts', 'src/renderer/src/lib/agent-session/history/title-sync/ai-vault-tab-title-sync-inputs.test.ts', diff --git a/config/tsconfig.node.json b/config/tsconfig.node.json index 5702c01cc47..f68039491a0 100644 --- a/config/tsconfig.node.json +++ b/config/tsconfig.node.json @@ -24,7 +24,6 @@ "../src/cli/runtime/runtime-pid-liveness.ts", "../src/renderer/src/store/slices/worktrees/create/worktree-create-payload.ts", "../src/renderer/src/runtime/repo-hooks/orca-hook-trust.ts", - "../src/renderer/src/lib/sha256.ts", "../src/renderer/src/components/native-chat/native-chat-resolution-receipt.ts", "../src/renderer/src/components/native-chat/structured-agent-question-projection.ts", "../src/renderer/src/components/native-chat/subagents/native-chat-subagent-delegation.ts", diff --git a/config/tsconfig.tc.web.json b/config/tsconfig.tc.web.json index d7542c5e628..a8d6f7c30cb 100644 --- a/config/tsconfig.tc.web.json +++ b/config/tsconfig.tc.web.json @@ -3,7 +3,6 @@ "exclude": [ "../src/renderer/src/lib/agent-session/launch/new-tab/launch-agent-in-new-tab-main-surface.test.ts", "../src/renderer/src/components/task-page/linear/task-page-linear-detail-query.test.tsx", - "../tests/e2e/terminal-agent-pane-launch.unit.test.ts", "../src/renderer/src/runtime/provider-accounts/observations/provider-account-observation.test-fixture.ts", "../src/shared/**/*.test.ts", "../src/shared/**/*.test.tsx", diff --git a/config/tsconfig.test.pane-launch.json b/config/tsconfig.test.pane-launch.json index 63896e6d839..0885af36f1d 100644 --- a/config/tsconfig.test.pane-launch.json +++ b/config/tsconfig.test.pane-launch.json @@ -5,7 +5,6 @@ "../src/main/runtime/orchestration/terminal-authority/structured-sessions/structured-chat-coordinator-mail.test.ts", "../src/main/runtime/rpc/methods/orchestration/orchestration-check-ambiguous-caller.test.ts", "../src/renderer/src/components/task-page/linear/task-page-linear-detail-query.test.tsx", - "../tests/e2e/terminal-agent-pane-launch.unit.test.ts", "../src/renderer/src/runtime/observed-provider-accounts.test.tsx", "../src/renderer/src/components/status-bar/ClaudeSwitcherMenu.observation.test.tsx", "../src/renderer/src/runtime/provider-accounts/observations/provider-account-observation.test-fixture.ts", diff --git a/config/tsconfig.web.json b/config/tsconfig.web.json index 02a420684d3..8b5c4e283c7 100644 --- a/config/tsconfig.web.json +++ b/config/tsconfig.web.json @@ -13,7 +13,6 @@ "exclude": [ "../src/renderer/src/lib/agent-session/launch/new-tab/launch-agent-in-new-tab-main-surface.test.ts", "../src/renderer/src/components/task-page/linear/task-page-linear-detail-query.test.tsx", - "../tests/e2e/terminal-agent-pane-launch.unit.test.ts", "../src/renderer/src/runtime/provider-accounts/observations/provider-account-observation.test-fixture.ts" ], "compilerOptions": { diff --git a/config/vitest.performance.config.ts b/config/vitest.performance.config.ts index ceb2d73b083..a068105564d 100644 --- a/config/vitest.performance.config.ts +++ b/config/vitest.performance.config.ts @@ -17,7 +17,6 @@ const contracts = [ 'src/main/codex-usage/scanner-incremental-append.test.ts', 'src/main/usage/jsonl-file-reader.test.ts', 'src/main/usage/jsonl-file-snapshot.test.ts', - 'src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts', 'config/scripts/static-analysis/oxlint-contracts/app-store-performance-plugin.test.mjs', 'config/scripts/static-analysis/oxlint-contracts/quadratic-buffer-concat-plugin.test.mjs', 'src/main/claude-usage/scanner-incremental-append.test.ts', diff --git a/src/renderer/src/app-shell/AppBackgroundServices.tsx b/src/renderer/src/app-shell/AppBackgroundServices.tsx index 0fa98b6070d..6bb876961c5 100644 --- a/src/renderer/src/app-shell/AppBackgroundServices.tsx +++ b/src/renderer/src/app-shell/AppBackgroundServices.tsx @@ -1,5 +1,5 @@ import { Suspense } from 'react' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { AgentHibernationGate } from '../components/AgentHibernationGate' import { AiVaultTabTitleSyncGate } from '../components/AiVaultTabTitleSyncGate' import RetainedAgentsSyncGate from '../components/dashboard/RetainedAgentsSyncGate' diff --git a/src/renderer/src/app-shell/AppNotificationCards.tsx b/src/renderer/src/app-shell/AppNotificationCards.tsx index ea36809b65e..cdaf8cbd54c 100644 --- a/src/renderer/src/app-shell/AppNotificationCards.tsx +++ b/src/renderer/src/app-shell/AppNotificationCards.tsx @@ -1,6 +1,6 @@ import { Suspense, type JSX } from 'react' import type { TopLevelView } from '../../../shared/ui-chrome-types' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { CodexSharedSettingsNoticeCard } from '../components/CodexSharedSettingsNoticeCard' import { NotificationCardStack } from '../components/NotificationCardStack' import { StarNagCard } from '../components/StarNagCard' diff --git a/src/renderer/src/app-shell/AppRootSurfaces.tsx b/src/renderer/src/app-shell/AppRootSurfaces.tsx index 916fb186043..4e71408df47 100644 --- a/src/renderer/src/app-shell/AppRootSurfaces.tsx +++ b/src/renderer/src/app-shell/AppRootSurfaces.tsx @@ -4,7 +4,7 @@ import { OnboardingLanguageLoading } from '../components/onboarding/OnboardingLa import { useLanguageReadiness } from '../i18n/language-readiness' import { memo, Suspense } from 'react' import { createPortal } from 'react-dom' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { useTranslate } from '@/i18n/use-translate' import { RecoverableRenderErrorBoundary } from '../components/error-boundaries/RecoverableRenderErrorBoundary' import NewWorkspaceComposerModal from '../components/NewWorkspaceComposerModal' @@ -20,7 +20,7 @@ import { ZoomOverlay } from '../components/ZoomOverlay' import { WslMoveDialogHost } from '../components/wsl-move/WslMoveDialog' import { MacosUpdateRequirementHost } from '../components/macos-update-requirement/MacosUpdateRequirementHost' import { shouldRenderPetOverlay } from '../components/pet/pet-overlay-visibility' -import { useAppStore } from '../store' +import { useAppStore } from '../store/index' import type { UpdateStatus } from '../../../shared/update-status-types' import { useLazyModalMounts } from './use-lazy-modal-mounts' import { AppNotificationCards } from './AppNotificationCards' diff --git a/src/renderer/src/app-shell/AppWorkspaceShell.tsx b/src/renderer/src/app-shell/AppWorkspaceShell.tsx index ac4293de10e..2e038a6e551 100644 --- a/src/renderer/src/app-shell/AppWorkspaceShell.tsx +++ b/src/renderer/src/app-shell/AppWorkspaceShell.tsx @@ -1,12 +1,12 @@ import { memo, Suspense, useCallback, useRef } from 'react' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { useTranslate } from '@/i18n/use-translate' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { usePluginViewCaptureStore } from '@/store/plugins/capture/plugin-view-capture' import { pendingCreationOpensNativeChat } from '@/lib/workspace/creation/pending-worktree-creation' import { showsFloatingRightSidebarToggle } from '@/lib/right-sidebar-visibility' import Sidebar from '../components/Sidebar' -import RightSidebar from '../components/right-sidebar' +import RightSidebar from '../components/right-sidebar/index' import { RecoverableRenderErrorBoundary } from '../components/error-boundaries/RecoverableRenderErrorBoundary' import { FloatingTerminalToggleButton } from '../components/floating-terminal/FloatingTerminalToggleButton' import { TerminalWorkbenchContainer } from '../components/TerminalWorkbenchContainer' diff --git a/src/renderer/src/app-shell/app-command-handlers.ts b/src/renderer/src/app-shell/app-command-handlers.ts index f880e57a27b..22807ffc08f 100644 --- a/src/renderer/src/app-shell/app-command-handlers.ts +++ b/src/renderer/src/app-shell/app-command-handlers.ts @@ -16,10 +16,10 @@ import { deleteHoveredWorkspaceImmediately, resolveHoveredWorkspaceDeleteTarget } from '../components/sidebar/workspace-actions/deletion/hovered-workspace-delete' -import { useAppStore } from '../store' +import { useAppStore } from '../store/index' import type { usePluginCommands } from '@/store/plugins/catalog/plugin-contribution-catalog' import { isGitRepoKind } from '../../../shared/repo-kind' -import { openPluginsSettings } from '@/lib/open-plugins-settings' +import { openPluginsSettings } from '@/components/settings/navigation/open-plugins-settings' import { resolveActiveTabRenameTarget } from './active-tab-rename-target' import { createPluginsSettingsShortcutHandler } from './plugin-settings-shortcut-handler' import type { diff --git a/src/renderer/src/app-shell/appearance/window-material-runtime.ts b/src/renderer/src/app-shell/appearance/window-material-runtime.ts index c21e63fe549..26652aed987 100644 --- a/src/renderer/src/app-shell/appearance/window-material-runtime.ts +++ b/src/renderer/src/app-shell/appearance/window-material-runtime.ts @@ -1,5 +1,5 @@ import type { WindowMaterial, WindowMaterialHost } from '../../../../shared/window-material' -import { getRendererAppPlatform } from '../../lib/renderer-app-platform' +import { getRendererAppPlatform } from '../platform/renderer-app-platform' let material: WindowMaterial | undefined diff --git a/src/renderer/src/lib/client-environment-info.ts b/src/renderer/src/app-shell/platform/client-environment-info.ts similarity index 96% rename from src/renderer/src/lib/client-environment-info.ts rename to src/renderer/src/app-shell/platform/client-environment-info.ts index fda7a1a295b..3e4f5cef314 100644 --- a/src/renderer/src/lib/client-environment-info.ts +++ b/src/renderer/src/app-shell/platform/client-environment-info.ts @@ -1,7 +1,7 @@ import { formatClientEnvironmentFooter, type ClientEnvironmentInfo -} from '../../../shared/client-environment-info' +} from '../../../../shared/client-environment-info' import { getRendererAppPlatform } from './renderer-app-platform' export async function resolveClientEnvironmentInfo(): Promise { diff --git a/src/renderer/src/lib/ios-web-platform.ts b/src/renderer/src/app-shell/platform/ios-web-platform.ts similarity index 100% rename from src/renderer/src/lib/ios-web-platform.ts rename to src/renderer/src/app-shell/platform/ios-web-platform.ts diff --git a/src/renderer/src/lib/paired-web-client-host.ts b/src/renderer/src/app-shell/platform/paired-web-client-host.ts similarity index 91% rename from src/renderer/src/lib/paired-web-client-host.ts rename to src/renderer/src/app-shell/platform/paired-web-client-host.ts index 9b7a0bc8860..af617064ec0 100644 --- a/src/renderer/src/lib/paired-web-client-host.ts +++ b/src/renderer/src/app-shell/platform/paired-web-client-host.ts @@ -2,8 +2,8 @@ import { parseExecutionHostId, toRuntimeExecutionHostId, type ExecutionHostId -} from '../../../shared/execution-host' -import { isPairedWebClientWindow } from './desktop-window-chrome' +} from '../../../../shared/execution-host' +import { isPairedWebClientWindow } from '../../lib/desktop-window-chrome' type HostRouteState = { runtimeEnvironments?: readonly { id: string }[] } diff --git a/src/renderer/src/lib/passive-macos-app-data-access.ts b/src/renderer/src/app-shell/platform/passive-macos-app-data-access.ts similarity index 97% rename from src/renderer/src/lib/passive-macos-app-data-access.ts rename to src/renderer/src/app-shell/platform/passive-macos-app-data-access.ts index 03f636fbeec..5c0bf9dfa50 100644 --- a/src/renderer/src/lib/passive-macos-app-data-access.ts +++ b/src/renderer/src/app-shell/platform/passive-macos-app-data-access.ts @@ -1,7 +1,7 @@ import type { ActiveRightSidebarTab, RightSidebarExplorerView -} from '../../../shared/ui-chrome-types' +} from '../../../../shared/ui-chrome-types' import type { OpenFile } from '@/store/slices/editor/types/open-file' const MAC_APP_DATA_SEGMENT_RE = /(^|\/)Library\/(Containers|Group Containers)\// diff --git a/src/renderer/src/lib/renderer-app-platform.ts b/src/renderer/src/app-shell/platform/renderer-app-platform.ts similarity index 100% rename from src/renderer/src/lib/renderer-app-platform.ts rename to src/renderer/src/app-shell/platform/renderer-app-platform.ts diff --git a/src/renderer/src/lib/user-agent-platform.ts b/src/renderer/src/app-shell/platform/user-agent-platform.ts similarity index 100% rename from src/renderer/src/lib/user-agent-platform.ts rename to src/renderer/src/app-shell/platform/user-agent-platform.ts diff --git a/src/renderer/src/lib/web-client-location.ts b/src/renderer/src/app-shell/platform/web-client-location.ts similarity index 100% rename from src/renderer/src/lib/web-client-location.ts rename to src/renderer/src/app-shell/platform/web-client-location.ts diff --git a/src/renderer/src/app-shell/shutdown-checkpoint-persist.ts b/src/renderer/src/app-shell/shutdown-checkpoint-persist.ts index 7865c4ab8a9..fc68ea42f6f 100644 --- a/src/renderer/src/app-shell/shutdown-checkpoint-persist.ts +++ b/src/renderer/src/app-shell/shutdown-checkpoint-persist.ts @@ -1,7 +1,7 @@ import type { PersistedUIState } from '../../../shared/persisted-ui-state-types' import { formatShutdownCheckpointFailureReason } from '../../../shared/renderer-shutdown-events' import type { WorkspaceSessionHostSnapshot } from '../lib/workspace/persistence/workspace-session-host-persistence' -import { recordRendererCrashBreadcrumb } from '../lib/crash-breadcrumb-recorder' +import { recordRendererCrashBreadcrumb } from '../diagnostics/crash-reporting/crash-breadcrumb-recorder' export type ShutdownCheckpointStageArgs = { sessions: WorkspaceSessionHostSnapshot[] diff --git a/src/renderer/src/app-shell/shutdown-checkpoint-restart-lifecycle.test.ts b/src/renderer/src/app-shell/shutdown-checkpoint-restart-lifecycle.test.ts deleted file mode 100644 index 1632187546b..00000000000 --- a/src/renderer/src/app-shell/shutdown-checkpoint-restart-lifecycle.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -// @vitest-environment happy-dom -import { afterEach, describe, expect, it, vi } from 'vitest' -import { - ORCA_APP_RESTART_ABORTED_EVENT, - ORCA_APP_RESTART_STARTED_EVENT, - ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT, - ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT -} from '../../../shared/updater-renderer-events' -import { - ORCA_RENDERER_SHUTDOWN_CHECKPOINT_ABORTED_EVENT, - ORCA_RENDERER_UNLOAD_PREVENTED_EVENT -} from '../../../shared/renderer-shutdown-events' -import { prepareRendererForAppRestart } from '../../../shared/renderer-restart-preparation' -import { - createShutdownCheckpointBeforeUnloadHandler, - createShutdownCheckpointGuard -} from '../lib/shutdown-checkpoint-guard' -import { - isIntentionalAppRestartInProgress, - registerUpdaterBeforeUnloadBypass -} from '../lib/updater-beforeunload' -import { - createShutdownCheckpointPersist, - type ShutdownCheckpointPersistDeps -} from './shutdown-checkpoint-persist' - -type LifecycleHarness = { - cleanup: () => void - prepare: () => Promise - stageBeforeUnloadSync: ReturnType -} - -type LifecycleHarnessOverrides = Partial< - Pick -> - -function createLifecycleHarness( - startedEventName: string, - abortedEventName: string, - overrides: LifecycleHarnessOverrides = {} -): LifecycleHarness { - const stageBeforeUnloadSync = vi.fn((args: { sessions: unknown[] }) => { - if (args.sessions.length > 0) { - throw new Error('deterministic full-stage failure') - } - }) - const persist = createShutdownCheckpointPersist({ - shouldCaptureSession: () => true, - captureTerminalBuffers: vi.fn(), - captureSleepingAgentSessions: vi.fn(), - buildSessionSnapshots: () => [{ state: { activeTabId: 't1' } }] as never, - buildUiPatch: () => ({ activeView: 'workspace' }) as never, - hasDirtyOpenFiles: () => false, - isDegradableShutdownInProgress: isIntentionalAppRestartInProgress, - stageBeforeUnloadSync, - ...overrides - }) - const guard = createShutdownCheckpointGuard(persist.run, persist.abandonAttempt) - const checkpoint = createShutdownCheckpointBeforeUnloadHandler(guard) - const cleanupRestartTracking = registerUpdaterBeforeUnloadBypass() - window.addEventListener('beforeunload', checkpoint) - window.addEventListener( - ORCA_RENDERER_SHUTDOWN_CHECKPOINT_ABORTED_EVENT, - guard.abortAfterCheckpointFailure - ) - window.addEventListener(abortedEventName, guard.abandonAttempt) - window.addEventListener(ORCA_RENDERER_UNLOAD_PREVENTED_EVENT, guard.abandonAttempt) - return { - stageBeforeUnloadSync, - prepare: () => - prepareRendererForAppRestart(window, { - startedEventName, - abortedEventName, - awaitCheckpoint: () => Promise.resolve() - }), - cleanup: () => { - cleanupRestartTracking() - window.removeEventListener('beforeunload', checkpoint) - window.removeEventListener( - ORCA_RENDERER_SHUTDOWN_CHECKPOINT_ABORTED_EVENT, - guard.abortAfterCheckpointFailure - ) - window.removeEventListener(abortedEventName, guard.abandonAttempt) - window.removeEventListener(ORCA_RENDERER_UNLOAD_PREVENTED_EVENT, guard.abandonAttempt) - } - } -} - -describe('shutdown checkpoint restart lifecycle', () => { - const cleanupFns: (() => void)[] = [] - - afterEach(() => { - cleanupFns.splice(0).forEach((cleanup) => cleanup()) - vi.restoreAllMocks() - }) - - it.each([ - { - lifecycle: 'app restart', - startedEventName: ORCA_APP_RESTART_STARTED_EVENT, - abortedEventName: ORCA_APP_RESTART_ABORTED_EVENT - }, - { - lifecycle: 'updater install', - startedEventName: ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT, - abortedEventName: ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT - } - ])( - 'preserves retry-then-degrade across a checkpoint-caused $lifecycle abort', - async ({ startedEventName, abortedEventName }) => { - vi.spyOn(console, 'error').mockImplementation(() => {}) - const harness = createLifecycleHarness(startedEventName, abortedEventName) - cleanupFns.push(harness.cleanup) - - await expect(harness.prepare()).rejects.toThrow('deterministic full-stage failure') - expect(isIntentionalAppRestartInProgress()).toBe(false) - await expect(harness.prepare()).resolves.toBeUndefined() - - expect(harness.stageBeforeUnloadSync).toHaveBeenCalledTimes(3) - expect(harness.stageBeforeUnloadSync).toHaveBeenLastCalledWith({ - sessions: [], - ui: { activeView: 'workspace' } - }) - } - ) - - it('abandons retry state when a later restart attempt is independently canceled', async () => { - vi.spyOn(console, 'error').mockImplementation(() => {}) - const harness = createLifecycleHarness( - ORCA_APP_RESTART_STARTED_EVENT, - ORCA_APP_RESTART_ABORTED_EVENT - ) - cleanupFns.push(harness.cleanup) - - await expect(harness.prepare()).rejects.toThrow('deterministic full-stage failure') - window.dispatchEvent(new Event(ORCA_APP_RESTART_STARTED_EVENT)) - window.dispatchEvent(new Event(ORCA_APP_RESTART_ABORTED_EVENT)) - await expect(harness.prepare()).rejects.toThrow('deterministic full-stage failure') - - expect(harness.stageBeforeUnloadSync).toHaveBeenCalledTimes(2) - }) -}) diff --git a/src/renderer/src/lib/shutdown-checkpoint-failure-toast.ts b/src/renderer/src/app-shell/shutdown/shutdown-checkpoint-failure-toast.ts similarity index 95% rename from src/renderer/src/lib/shutdown-checkpoint-failure-toast.ts rename to src/renderer/src/app-shell/shutdown/shutdown-checkpoint-failure-toast.ts index 155389f7b4e..8f08f58b14d 100644 --- a/src/renderer/src/lib/shutdown-checkpoint-failure-toast.ts +++ b/src/renderer/src/app-shell/shutdown/shutdown-checkpoint-failure-toast.ts @@ -1,6 +1,6 @@ import { toast } from 'sonner' import { translate } from '@/i18n/i18n' -import { consumeShutdownCheckpointFailureReason } from '../../../shared/renderer-shutdown-events' +import { consumeShutdownCheckpointFailureReason } from '../../../../shared/renderer-shutdown-events' export function showShutdownCheckpointFailureToast( canceledAction: 'quit' | 'reload' = 'quit' diff --git a/src/renderer/src/lib/shutdown-checkpoint-guard.ts b/src/renderer/src/app-shell/shutdown/shutdown-checkpoint-guard.ts similarity index 93% rename from src/renderer/src/lib/shutdown-checkpoint-guard.ts rename to src/renderer/src/app-shell/shutdown/shutdown-checkpoint-guard.ts index 74c7be22ce3..37eff4da062 100644 --- a/src/renderer/src/lib/shutdown-checkpoint-guard.ts +++ b/src/renderer/src/app-shell/shutdown/shutdown-checkpoint-guard.ts @@ -4,8 +4,8 @@ import { ORCA_RENDERER_SHUTDOWN_CHECKPOINT_FAILED_EVENT, ORCA_RENDERER_UNLOAD_PREVENTED_EVENT, publishShutdownCheckpointFailureReason -} from '../../../shared/renderer-shutdown-events' -import { recordRendererCrashBreadcrumb } from './crash-breadcrumb-recorder' +} from '../../../../shared/renderer-shutdown-events' +import { recordRendererCrashBreadcrumb } from '../../diagnostics/crash-reporting/crash-breadcrumb-recorder' export type ShutdownCheckpointGuard = { persistOnce: () => boolean diff --git a/src/renderer/src/lib/active-view-persist.ts b/src/renderer/src/app-shell/startup/active-view-persist.ts similarity index 69% rename from src/renderer/src/lib/active-view-persist.ts rename to src/renderer/src/app-shell/startup/active-view-persist.ts index 3b2b4a4d349..75c86f571ce 100644 --- a/src/renderer/src/lib/active-view-persist.ts +++ b/src/renderer/src/app-shell/startup/active-view-persist.ts @@ -1,5 +1,5 @@ -import type { PersistedUIState } from '../../../shared/persisted-ui-state-types' -import type { TopLevelView } from '../../../shared/ui-chrome-types' +import type { PersistedUIState } from '../../../../shared/persisted-ui-state-types' +import type { TopLevelView } from '../../../../shared/ui-chrome-types' type ActiveViewUnloadState = { activeView: TopLevelView diff --git a/src/renderer/src/lib/e2e-config.ts b/src/renderer/src/app-shell/startup/e2e-config.ts similarity index 94% rename from src/renderer/src/lib/e2e-config.ts rename to src/renderer/src/app-shell/startup/e2e-config.ts index 15d19bbced2..fd0b1769bba 100644 --- a/src/renderer/src/lib/e2e-config.ts +++ b/src/renderer/src/app-shell/startup/e2e-config.ts @@ -1,4 +1,4 @@ -import { createE2EConfig } from '../../../shared/e2e-config' +import { createE2EConfig } from '../../../../shared/e2e-config' const rendererE2EExposeStore = String(import.meta.env.VITE_EXPOSE_STORE) === 'true' // The paired API installs after module initialization; read the build-gated test URL before caching configuration. diff --git a/src/renderer/src/lib/lazy-chunk-recovery-reload.ts b/src/renderer/src/app-shell/startup/lazy-chunk-recovery-reload.ts similarity index 91% rename from src/renderer/src/lib/lazy-chunk-recovery-reload.ts rename to src/renderer/src/app-shell/startup/lazy-chunk-recovery-reload.ts index 64e78fc283a..52984e70057 100644 --- a/src/renderer/src/lib/lazy-chunk-recovery-reload.ts +++ b/src/renderer/src/app-shell/startup/lazy-chunk-recovery-reload.ts @@ -1,9 +1,9 @@ -import { prepareRendererForAppRestart } from '../../../shared/renderer-restart-preparation' -import { ORCA_RENDERER_UNLOAD_PREVENTED_EVENT } from '../../../shared/renderer-shutdown-events' +import { prepareRendererForAppRestart } from '../../../../shared/renderer-restart-preparation' +import { ORCA_RENDERER_UNLOAD_PREVENTED_EVENT } from '../../../../shared/renderer-shutdown-events' import { ORCA_APP_RESTART_ABORTED_EVENT, ORCA_APP_RESTART_STARTED_EVENT -} from '../../../shared/updater-renderer-events' +} from '../../../../shared/updater-renderer-events' // Bare reloads are vetoed by dirty tabs; restart preparation backs them up first. diff --git a/src/renderer/src/lib/lazy-with-retry.never-landed-reload.test.ts b/src/renderer/src/app-shell/startup/lazy-with-retry.never-landed-reload.test.ts similarity index 100% rename from src/renderer/src/lib/lazy-with-retry.never-landed-reload.test.ts rename to src/renderer/src/app-shell/startup/lazy-with-retry.never-landed-reload.test.ts diff --git a/src/renderer/src/lib/lazy-with-retry.ts b/src/renderer/src/app-shell/startup/lazy-with-retry.ts similarity index 100% rename from src/renderer/src/lib/lazy-with-retry.ts rename to src/renderer/src/app-shell/startup/lazy-with-retry.ts diff --git a/src/renderer/src/lib/react-renderer-root.ts b/src/renderer/src/app-shell/startup/react-renderer-root.ts similarity index 100% rename from src/renderer/src/lib/react-renderer-root.ts rename to src/renderer/src/app-shell/startup/react-renderer-root.ts diff --git a/src/renderer/src/lib/startup-ui-hydration.ts b/src/renderer/src/app-shell/startup/startup-ui-hydration.ts similarity index 94% rename from src/renderer/src/lib/startup-ui-hydration.ts rename to src/renderer/src/app-shell/startup/startup-ui-hydration.ts index 894ab91c162..624fefeba59 100644 --- a/src/renderer/src/lib/startup-ui-hydration.ts +++ b/src/renderer/src/app-shell/startup/startup-ui-hydration.ts @@ -5,8 +5,8 @@ import { DEFAULT_SHOW_SLEEPING_WORKSPACES, DEFAULT_STATUS_BAR_ITEMS, DEFAULT_WORKTREE_CARD_PROPERTIES -} from '../../../shared/constants' -import type { PersistedUIState } from '../../../shared/persisted-ui-state-types' +} from '../../../../shared/constants' +import type { PersistedUIState } from '../../../../shared/persisted-ui-state-types' export function hydratePersistedUIAfterStartupRead({ persistedUI, diff --git a/src/renderer/src/lib/update-check-click-options.ts b/src/renderer/src/app-shell/updates/update-check-click-options.ts similarity index 85% rename from src/renderer/src/lib/update-check-click-options.ts rename to src/renderer/src/app-shell/updates/update-check-click-options.ts index 59869b1de96..282e004346b 100644 --- a/src/renderer/src/lib/update-check-click-options.ts +++ b/src/renderer/src/app-shell/updates/update-check-click-options.ts @@ -1,5 +1,5 @@ -import type { UpdateCheckOptions } from '../../../shared/update-status-types' -import { getShortcutPlatform } from './keyboard/shortcut-platform' +import type { UpdateCheckOptions } from '../../../../shared/update-status-types' +import { getShortcutPlatform } from '../../lib/keyboard/shortcut-platform' type UpdateCheckClickEvent = Pick diff --git a/src/renderer/src/lib/updater-beforeunload.ts b/src/renderer/src/app-shell/updates/updater-beforeunload.ts similarity index 89% rename from src/renderer/src/lib/updater-beforeunload.ts rename to src/renderer/src/app-shell/updates/updater-beforeunload.ts index 42f3e72587b..d728bf22506 100644 --- a/src/renderer/src/lib/updater-beforeunload.ts +++ b/src/renderer/src/app-shell/updates/updater-beforeunload.ts @@ -3,15 +3,11 @@ import { ORCA_APP_RESTART_STARTED_EVENT, ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT, ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT -} from '../../../shared/updater-renderer-events' -import { ORCA_RENDERER_SHUTDOWN_CHECKPOINT_ABORTED_EVENT } from '../../../shared/renderer-shutdown-events' +} from '../../../../shared/updater-renderer-events' +import { ORCA_RENDERER_SHUTDOWN_CHECKPOINT_ABORTED_EVENT } from '../../../../shared/renderer-shutdown-events' let intentionalAppRestartInProgress = false -export function isUpdaterQuitAndInstallInProgress(): boolean { - return isIntentionalAppRestartInProgress() -} - export function isIntentionalAppRestartInProgress(): boolean { return intentionalAppRestartInProgress } diff --git a/src/renderer/src/lib/updater-message-copy.ts b/src/renderer/src/app-shell/updates/updater-message-copy.ts similarity index 53% rename from src/renderer/src/lib/updater-message-copy.ts rename to src/renderer/src/app-shell/updates/updater-message-copy.ts index 76a90f2745f..7d1cca61568 100644 --- a/src/renderer/src/lib/updater-message-copy.ts +++ b/src/renderer/src/app-shell/updates/updater-message-copy.ts @@ -1,99 +1,163 @@ import { translate } from '@/i18n/i18n' -import type { LinuxPackageCommandUnavailableReason } from '../../../shared/update-status-types' +import type { LinuxPackageCommandUnavailableReason } from '../../../../shared/update-status-types' import { - UPDATER_FAILURE_COPY as EN, + UPDATER_FAILURE_COPY, type UpdaterFailureCode -} from '../../../shared/updater-failure-copy' +} from '../../../../shared/updater-failure-copy' type Param = (name: string) => string | null type CountParam = (name: string) => number | null // null = required params missing; caller shows the host's English message. const COPY: Record string | null> = { - releaseNotReady: () => translate('updates.failure.releaseNotReady', EN.releaseNotReady), + releaseNotReady: () => + translate('updates.failure.releaseNotReady', UPDATER_FAILURE_COPY.releaseNotReady), updateServerUnreachable: () => - translate('updates.failure.updateServerUnreachable', EN.updateServerUnreachable), + translate( + 'updates.failure.updateServerUnreachable', + UPDATER_FAILURE_COPY.updateServerUnreachable + ), channelUnsupportedPlatform: (param) => { const channel = param('channel') return channel === null ? null - : translate('updates.failure.channelUnsupportedPlatform', EN.channelUnsupportedPlatform, { - channel - }) + : translate( + 'updates.failure.channelUnsupportedPlatform', + UPDATER_FAILURE_COPY.channelUnsupportedPlatform, + { + channel + } + ) }, unsignedDevChannel: (param) => { const channel = param('channel') return channel === null ? null - : translate('updates.failure.unsignedDevChannel', EN.unsignedDevChannel, { channel }) + : translate('updates.failure.unsignedDevChannel', UPDATER_FAILURE_COPY.unsignedDevChannel, { + channel + }) }, linuxPackageMissing: () => - translate('updates.failure.linuxPackageMissing', EN.linuxPackageMissing), + translate('updates.failure.linuxPackageMissing', UPDATER_FAILURE_COPY.linuxPackageMissing), linuxPackageNotRegular: () => - translate('updates.failure.linuxPackageNotRegular', EN.linuxPackageNotRegular), + translate( + 'updates.failure.linuxPackageNotRegular', + UPDATER_FAILURE_COPY.linuxPackageNotRegular + ), linuxPackageHashMismatch: () => - translate('updates.failure.linuxPackageHashMismatch', EN.linuxPackageHashMismatch), + translate( + 'updates.failure.linuxPackageHashMismatch', + UPDATER_FAILURE_COPY.linuxPackageHashMismatch + ), linuxPackageReadFailed: () => - translate('updates.failure.linuxPackageReadFailed', EN.linuxPackageReadFailed), - linuxPackageNoSudo: () => translate('updates.failure.linuxPackageNoSudo', EN.linuxPackageNoSudo), + translate( + 'updates.failure.linuxPackageReadFailed', + UPDATER_FAILURE_COPY.linuxPackageReadFailed + ), + linuxPackageNoSudo: () => + translate('updates.failure.linuxPackageNoSudo', UPDATER_FAILURE_COPY.linuxPackageNoSudo), linuxPackageNoPackageManager: () => - translate('updates.failure.linuxPackageNoPackageManager', EN.linuxPackageNoPackageManager), + translate( + 'updates.failure.linuxPackageNoPackageManager', + UPDATER_FAILURE_COPY.linuxPackageNoPackageManager + ), linuxPackageInvalidPath: () => - translate('updates.failure.linuxPackageInvalidPath', EN.linuxPackageInvalidPath), + translate( + 'updates.failure.linuxPackageInvalidPath', + UPDATER_FAILURE_COPY.linuxPackageInvalidPath + ), linuxPackageMarkerUnusable: () => - translate('updates.failure.linuxPackageMarkerUnusable', EN.linuxPackageMarkerUnusable), + translate( + 'updates.failure.linuxPackageMarkerUnusable', + UPDATER_FAILURE_COPY.linuxPackageMarkerUnusable + ), linuxPackageExternallyManaged: () => - translate('updates.failure.linuxPackageExternallyManaged', EN.linuxPackageExternallyManaged), + translate( + 'updates.failure.linuxPackageExternallyManaged', + UPDATER_FAILURE_COPY.linuxPackageExternallyManaged + ), linuxPackageManualInstall: () => - translate('updates.failure.linuxPackageManualInstall', EN.linuxPackageManualInstall), + translate( + 'updates.failure.linuxPackageManualInstall', + UPDATER_FAILURE_COPY.linuxPackageManualInstall + ), linuxPackageMetadataUnusable: () => - translate('updates.failure.linuxPackageMetadataUnusable', EN.linuxPackageMetadataUnusable), + translate( + 'updates.failure.linuxPackageMetadataUnusable', + UPDATER_FAILURE_COPY.linuxPackageMetadataUnusable + ), unknownReleaseChannel: (param) => { const channel = param('channel') return channel === null ? null - : translate('updates.failure.unknownReleaseChannel', EN.unknownReleaseChannel, { channel }) + : translate( + 'updates.failure.unknownReleaseChannel', + UPDATER_FAILURE_COPY.unknownReleaseChannel, + { channel } + ) }, invalidReleaseTag: (param) => { const tag = param('tag') return tag === null ? null - : translate('updates.failure.invalidReleaseTag', EN.invalidReleaseTag, { tag }) + : translate('updates.failure.invalidReleaseTag', UPDATER_FAILURE_COPY.invalidReleaseTag, { + tag + }) }, releaseRepoNotFound: (param) => { const repo = param('repo') return repo === null ? null - : translate('updates.failure.releaseRepoNotFound', EN.releaseRepoNotFound, { repo }) + : translate('updates.failure.releaseRepoNotFound', UPDATER_FAILURE_COPY.releaseRepoNotFound, { + repo + }) }, releaseListFailed: (param, count) => { const channel = param('channel') const status = count('status') return channel === null || status === null ? null - : translate('updates.failure.releaseListFailed', EN.releaseListFailed, { channel, status }) + : translate('updates.failure.releaseListFailed', UPDATER_FAILURE_COPY.releaseListFailed, { + channel, + status + }) }, releaseListUnreadable: (param) => { const channel = param('channel') return channel === null ? null - : translate('updates.failure.releaseListUnreadable', EN.releaseListUnreadable, { channel }) + : translate( + 'updates.failure.releaseListUnreadable', + UPDATER_FAILURE_COPY.releaseListUnreadable, + { channel } + ) }, - rateLimitedSoon: () => translate('updates.failure.rateLimitedSoon', EN.rateLimitedSoon), + rateLimitedSoon: () => + translate('updates.failure.rateLimitedSoon', UPDATER_FAILURE_COPY.rateLimitedSoon), rateLimitedSoonSignedOut: () => - translate('updates.failure.rateLimitedSoonSignedOut', EN.rateLimitedSoonSignedOut), + translate( + 'updates.failure.rateLimitedSoonSignedOut', + UPDATER_FAILURE_COPY.rateLimitedSoonSignedOut + ), rateLimitedInMinute: () => - translate('updates.failure.rateLimitedInMinute', EN.rateLimitedInMinute), + translate('updates.failure.rateLimitedInMinute', UPDATER_FAILURE_COPY.rateLimitedInMinute), rateLimitedInMinuteSignedOut: () => - translate('updates.failure.rateLimitedInMinuteSignedOut', EN.rateLimitedInMinuteSignedOut), + translate( + 'updates.failure.rateLimitedInMinuteSignedOut', + UPDATER_FAILURE_COPY.rateLimitedInMinuteSignedOut + ), rateLimitedInMinutes: (_param, count) => { const minutes = count('count') return minutes === null ? null - : translate('updates.failure.rateLimitedInMinutes', EN.rateLimitedInMinutes, { - count: minutes, - defaultValue_one: 'GitHub rate limit reached. Try again in about {{count}} minute.' - }) + : translate( + 'updates.failure.rateLimitedInMinutes', + UPDATER_FAILURE_COPY.rateLimitedInMinutes, + { + count: minutes, + defaultValue_one: 'GitHub rate limit reached. Try again in about {{count}} minute.' + } + ) }, rateLimitedInMinutesSignedOut: (_param, count) => { const minutes = count('count') @@ -101,7 +165,7 @@ const COPY: Record stri ? null : translate( 'updates.failure.rateLimitedInMinutesSignedOut', - EN.rateLimitedInMinutesSignedOut, + UPDATER_FAILURE_COPY.rateLimitedInMinutesSignedOut, { count: minutes, defaultValue_one: diff --git a/src/renderer/src/app-shell/use-app-session-persistence.ts b/src/renderer/src/app-shell/use-app-session-persistence.ts index d420f4a0093..9fca4439f46 100644 --- a/src/renderer/src/app-shell/use-app-session-persistence.ts +++ b/src/renderer/src/app-shell/use-app-session-persistence.ts @@ -1,12 +1,12 @@ import { useEffect } from 'react' -import { useAppStore } from '../store' +import { useAppStore } from '../store/index' import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../shared/layout-owner-switch' import { createSessionWriteSubscriber } from '../lib/workspace/persistence/session-write-subscriber' -import { buildActiveViewUnloadPatch } from '../lib/active-view-persist' +import { buildActiveViewUnloadPatch } from './startup/active-view-persist' import { isIntentionalAppRestartInProgress, registerUpdaterBeforeUnloadBypass -} from '../lib/updater-beforeunload' +} from './updates/updater-beforeunload' import { buildWorkspaceSessionPayload, shouldPersistWorkspaceSession @@ -18,8 +18,8 @@ import { import { createShutdownCheckpointBeforeUnloadHandler, createShutdownCheckpointGuard -} from '../lib/shutdown-checkpoint-guard' -import { showShutdownCheckpointFailureToast } from '../lib/shutdown-checkpoint-failure-toast' +} from './shutdown/shutdown-checkpoint-guard' +import { showShutdownCheckpointFailureToast } from './shutdown/shutdown-checkpoint-failure-toast' import { isPairedWebClientWindow } from '../lib/desktop-window-chrome' import { createShutdownCheckpointPersist } from './shutdown-checkpoint-persist' import { shutdownBufferCaptures } from '../components/terminal-pane/snapshots/shutdown-buffer-captures' diff --git a/src/renderer/src/app-shell/use-app-startup-hydration.ts b/src/renderer/src/app-shell/use-app-startup-hydration.ts index a8c203d9de5..f8aa1abb34d 100644 --- a/src/renderer/src/app-shell/use-app-startup-hydration.ts +++ b/src/renderer/src/app-shell/use-app-startup-hydration.ts @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react' import { restoreLocalStructuredChatsAtStartup } from '@/runtime/native-chat/local-sessions/local-structured-chats' import { syncZoomCSSVar } from '@/lib/ui-zoom' import { installCodexDetachedPaneRestartExecutor } from '@/components/terminal-pane/account-restart/detached/codex-detached-pane-restart-scheduler' -import { useAppStore } from '../store' +import { useAppStore } from '../store/index' import { reconcileHydratedWorkspaceTabModels } from './reconcile-hydrated-workspace-tab-models' import { useStartupActions } from './use-app-startup-actions' import { waitForNativeChatDraftsAtStartup } from './native-chat-draft-startup' @@ -14,7 +14,7 @@ import { collectFolderWorkspaceKeysFromSession, collectWorktreeHydrationRepoIdsFromSession } from '../lib/workspace/persistence/workspace-session-hydration-keys' -import { hydratePersistedUIAfterStartupRead } from '../lib/startup-ui-hydration' +import { hydratePersistedUIAfterStartupRead } from './startup/startup-ui-hydration' import { logRendererStartupDiagnostic, timeRendererStartupStep, diff --git a/src/renderer/src/app-shell/use-onboarding-and-feature-tips.ts b/src/renderer/src/app-shell/use-onboarding-and-feature-tips.ts index 3394b68510b..7f8ef1f0d9d 100644 --- a/src/renderer/src/app-shell/use-onboarding-and-feature-tips.ts +++ b/src/renderer/src/app-shell/use-onboarding-and-feature-tips.ts @@ -13,8 +13,8 @@ import { trackCmdJPaletteFeatureTipShown, trackOrcaCliFeatureTipShown } from '../components/feature-tips/feature-tip-telemetry' -import { useAppStore } from '../store' -import { isWebClientLocation } from '../lib/web-client-location' +import { useAppStore } from '../store/index' +import { isWebClientLocation } from './platform/web-client-location' import type { OnboardingState } from '../../../shared/onboarding-state-types' export type OnboardingGate = ReturnType diff --git a/src/renderer/src/app-shell/use-visible-hosted-review-refresh.test.tsx b/src/renderer/src/app-shell/use-visible-hosted-review-refresh.test.tsx deleted file mode 100644 index 65ddda49d3d..00000000000 --- a/src/renderer/src/app-shell/use-visible-hosted-review-refresh.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -// @vitest-environment happy-dom -import { cleanup, renderHook } from '@testing-library/react' -import { afterEach, expect, it, vi } from 'vitest' -import { useVisibleHostedReviewRefresh } from './use-visible-hosted-review-refresh' - -const mocks = vi.hoisted(() => ({ - refresh: vi.fn(async () => true), - getState: vi.fn(() => ({})), - subscribe: vi.fn(() => vi.fn()), - web: vi.fn(() => false) -})) -vi.mock('@/store', () => ({ - useAppStore: { getState: mocks.getState, subscribe: mocks.subscribe } -})) -vi.mock('@/lib/web-client-location', () => ({ isWebClientLocation: mocks.web })) -vi.mock('@/store/slices/hosted-review/refresh/visible-hosted-review-refresh-targets', () => ({ - visibleHostedReviewRefreshInputsChanged: () => true, - getVisibleHostedReviewWorkspaces: () => [], - getVisibleHostedReviewRefreshTargets: () => [ - { - key: 'review', - revision: 'head', - selected: true, - intervalMs: 60_000, - fetchedAt: null, - refresh: mocks.refresh - } - ] -})) -afterEach(() => { - cleanup() - vi.useRealTimers() - vi.restoreAllMocks() -}) - -it('waits for workspace readiness and stops timers/subscription when disabled', async () => { - vi.useFakeTimers() - vi.spyOn(document, 'visibilityState', 'get').mockReturnValue('visible') - const hook = renderHook(({ enabled }) => useVisibleHostedReviewRefresh({ enabled }), { - initialProps: { enabled: false } - }) - await vi.advanceTimersByTimeAsync(600_000) - expect(mocks.refresh).not.toHaveBeenCalled() - expect(mocks.subscribe).not.toHaveBeenCalled() - hook.rerender({ enabled: true }) - await vi.advanceTimersByTimeAsync(0) - expect(mocks.refresh).toHaveBeenCalledOnce() - const unsubscribe = mocks.subscribe.mock.results[0].value - hook.rerender({ enabled: false }) - expect(unsubscribe).toHaveBeenCalledOnce() - expect(vi.getTimerCount()).toBe(0) - await vi.advanceTimersByTimeAsync(600_000) - expect(mocks.refresh).toHaveBeenCalledOnce() -}) diff --git a/src/renderer/src/app-shell/use-visible-hosted-review-refresh.ts b/src/renderer/src/app-shell/use-visible-hosted-review-refresh.ts index 2ebfa9857ac..01a33014bd2 100644 --- a/src/renderer/src/app-shell/use-visible-hosted-review-refresh.ts +++ b/src/renderer/src/app-shell/use-visible-hosted-review-refresh.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react' -import { useAppStore } from '@/store' -import { isWindowVisible } from '@/lib/window-visibility-interval' -import { isWebClientLocation } from '@/lib/web-client-location' +import { useAppStore } from '@/store/index' +import { isWindowVisible } from '@/lib/window-activity/window-visibility-interval' +import { isWebClientLocation } from '@/app-shell/platform/web-client-location' import { createVisibleHostedReviewRefreshScheduler } from '@/store/slices/hosted-review/refresh/visible-hosted-review-refresh-scheduler' import { getWorkspaceReferenceRefreshTargets } from '@/components/sidebar/workspace-card/review/workspace-reference-refresh-targets' import { diff --git a/src/renderer/src/app-shell/worktree-jump-palette-preload.ts b/src/renderer/src/app-shell/worktree-jump-palette-preload.ts index c1438604988..cc058334e79 100644 --- a/src/renderer/src/app-shell/worktree-jump-palette-preload.ts +++ b/src/renderer/src/app-shell/worktree-jump-palette-preload.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react' -import { scheduleAfterInputQuiet } from '@/lib/input-quiet-scheduler' -import { lazyWithPreload } from '@/lib/lazy-with-retry' +import { scheduleAfterInputQuiet } from '@/lib/window-activity/input-quiet-scheduler' +import { lazyWithPreload } from '@/app-shell/startup/lazy-with-retry' export const WorktreeJumpPalette = lazyWithPreload( () => import('../components/worktree-jump/WorktreeJumpPalette') diff --git a/src/renderer/src/attention/agent-finish-drop-breadcrumb.ts b/src/renderer/src/attention/agent-finish-drop-breadcrumb.ts index 9c42d0049e4..195a5672a3a 100644 --- a/src/renderer/src/attention/agent-finish-drop-breadcrumb.ts +++ b/src/renderer/src/attention/agent-finish-drop-breadcrumb.ts @@ -1,4 +1,4 @@ -import { recordRendererCrashBreadcrumb } from '@/lib/crash-breadcrumb-recorder' +import { recordRendererCrashBreadcrumb } from '@/diagnostics/crash-reporting/crash-breadcrumb-recorder' /** Why a finished agent turn did not mark its workspace unread. */ export type AgentFinishDropCause = diff --git a/src/renderer/src/components/AgentProgressRing.tsx b/src/renderer/src/components/AgentProgressRing.tsx index 51becb59da2..07c6e6b297f 100644 --- a/src/renderer/src/components/AgentProgressRing.tsx +++ b/src/renderer/src/components/AgentProgressRing.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' export type AgentProgressRingTone = 'working' | 'attention' diff --git a/src/renderer/src/components/AgentQuestionIcon.tsx b/src/renderer/src/components/AgentQuestionIcon.tsx index 880fd6dcb55..2da63e9823f 100644 --- a/src/renderer/src/components/AgentQuestionIcon.tsx +++ b/src/renderer/src/components/AgentQuestionIcon.tsx @@ -1,6 +1,6 @@ import React from 'react' import { MessageCircleQuestion } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' // Share the question glyph and --agent-question token across sidebar, tabs, dashboard and kanban; callers choose size. diff --git a/src/renderer/src/components/AgentStateDot.tsx b/src/renderer/src/components/AgentStateDot.tsx index f9449bdfb64..3772ef44f3a 100644 --- a/src/renderer/src/components/AgentStateDot.tsx +++ b/src/renderer/src/components/AgentStateDot.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Activity, CircleCheck, CircleDashed } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { translate } from '@/i18n/i18n' import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision' import { AgentQuestionIcon } from '@/components/AgentQuestionIcon' diff --git a/src/renderer/src/components/AgentWorkingSpinner.tsx b/src/renderer/src/components/AgentWorkingSpinner.tsx index 763380067a4..56ce94ad0e5 100644 --- a/src/renderer/src/components/AgentWorkingSpinner.tsx +++ b/src/renderer/src/components/AgentWorkingSpinner.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { cn } from '@/lib/utils' -import { observeOffscreenAnimation } from '@/lib/offscreen-animation-pause' +import { cn } from '@/lib/class-names' +import { observeOffscreenAnimation } from '@/lib/window-activity/offscreen-animation-pause' const SPINNER_ANIMATION_NAME = 'agent-spinner-rotate' diff --git a/src/renderer/src/components/AiVaultTabTitleSyncGate.tsx b/src/renderer/src/components/AiVaultTabTitleSyncGate.tsx index e69b4864d11..edfd0f50901 100644 --- a/src/renderer/src/components/AiVaultTabTitleSyncGate.tsx +++ b/src/renderer/src/components/AiVaultTabTitleSyncGate.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { startAiVaultTabTitleSync } from '@/lib/agent-session/history/title-sync/ai-vault-tab-title-sync' -import { scheduleAfterInputQuiet } from '@/lib/input-quiet-scheduler' -import { useAppStore } from '@/store' +import { scheduleAfterInputQuiet } from '@/lib/window-activity/input-quiet-scheduler' +import { useAppStore } from '@/store/index' const TITLE_SYNC_DELAY_MS = 1_000 const TITLE_SYNC_QUIET_MS = 1_500 diff --git a/src/renderer/src/components/DetachedHeadBadge.tsx b/src/renderer/src/components/DetachedHeadBadge.tsx index 6671735514d..4f834cdc32e 100644 --- a/src/renderer/src/components/DetachedHeadBadge.tsx +++ b/src/renderer/src/components/DetachedHeadBadge.tsx @@ -2,7 +2,7 @@ import React from 'react' import { GitCommitHorizontal } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { WorktreeGitIdentityDisplay } from '../../../shared/worktree/git-identity-display' type DetachedHeadDisplay = Extract diff --git a/src/renderer/src/components/FirstLaunchBanner.tsx b/src/renderer/src/components/FirstLaunchBanner.tsx index 1a40a437175..17fbc55609a 100644 --- a/src/renderer/src/components/FirstLaunchBanner.tsx +++ b/src/renderer/src/components/FirstLaunchBanner.tsx @@ -4,7 +4,11 @@ import { useState } from 'react' import { X } from 'lucide-react' import { Button } from './ui/button' -import { acknowledgeBanner, PRIVACY_URL, setOptIn as telemetrySetOptIn } from '../lib/telemetry' +import { + acknowledgeBanner, + PRIVACY_URL, + setOptIn as telemetrySetOptIn +} from '../diagnostics/telemetry/telemetry' import { useMountedRef } from '@/hooks/useMountedRef' import { useTranslate } from '@/i18n/use-translate' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' diff --git a/src/renderer/src/components/Landing.tsx b/src/renderer/src/components/Landing.tsx index c2d660f3f17..f3b1c67e35d 100644 --- a/src/renderer/src/components/Landing.tsx +++ b/src/renderer/src/components/Landing.tsx @@ -1,7 +1,7 @@ import { useEffect, useEffectEvent, useMemo, useRef, useState } from 'react' import { AlertTriangle, ExternalLink, FolderPlus, GitBranchPlus, Star, X } from 'lucide-react' -import { cn } from '../lib/utils' -import { useAppStore } from '../store' +import { cn } from '../lib/class-names' +import { useAppStore } from '../store/index' import { isGitRepoKind } from '../../../shared/repo-kind' import type { Repo } from '../../../shared/repo-types' import { diff --git a/src/renderer/src/components/LinearIssueMarkdownDescriptionEditor.tsx b/src/renderer/src/components/LinearIssueMarkdownDescriptionEditor.tsx index 438a3d7681e..c041cf0be09 100644 --- a/src/renderer/src/components/LinearIssueMarkdownDescriptionEditor.tsx +++ b/src/renderer/src/components/LinearIssueMarkdownDescriptionEditor.tsx @@ -14,11 +14,11 @@ import { } from '@/components/editor/rich-markdown/document/rich-markdown-source-transport' import { LinearIssueMarkdownToolbar } from '@/components/LinearIssueMarkdownToolbar' import { RichMarkdownTableControls } from '@/components/editor/rich-markdown/tables/RichMarkdownTableControls' -import { isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' -import { cn } from '@/lib/utils' +import { isScreenSubmitShortcut } from '@/lib/keyboard/screen-submit-shortcut' +import { cn } from '@/lib/class-names' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { getRichMarkdownSpellcheckAttribute, useRichMarkdownSpellcheckAttribute diff --git a/src/renderer/src/components/LinearIssueMarkdownToolbar.tsx b/src/renderer/src/components/LinearIssueMarkdownToolbar.tsx index 16e6a84229f..973a1b7e4f7 100644 --- a/src/renderer/src/components/LinearIssueMarkdownToolbar.tsx +++ b/src/renderer/src/components/LinearIssueMarkdownToolbar.tsx @@ -17,7 +17,7 @@ import { } from 'lucide-react' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/LinearIssueTextEditor.tsx b/src/renderer/src/components/LinearIssueTextEditor.tsx index 110eb89794b..1a8aa83266b 100644 --- a/src/renderer/src/components/LinearIssueTextEditor.tsx +++ b/src/renderer/src/components/LinearIssueTextEditor.tsx @@ -7,9 +7,12 @@ import { toast } from 'sonner' import { LinearIssueMarkdownDescriptionEditor } from '@/components/LinearIssueMarkdownDescriptionEditor' import { useMountedRef } from '@/hooks/useMountedRef' -import { cn } from '@/lib/utils' -import { useAppStore } from '@/store' -import { getScreenSubmitShortcutLabel, isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' +import { cn } from '@/lib/class-names' +import { useAppStore } from '@/store/index' +import { + getScreenSubmitShortcutLabel, + isScreenSubmitShortcut +} from '@/lib/keyboard/screen-submit-shortcut' import { linearUpdateIssue } from '@/runtime/linear/runtime-linear-issue-client' import type { LinearIssue } from '../../../shared/linear/issue-types' import type { TaskSourceContext } from '../../../shared/work-items/task-source-context' diff --git a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx index 3c4d9686999..978cd18b89b 100644 --- a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx +++ b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx @@ -7,7 +7,7 @@ import type { import { UpdateErrorCardContent } from './UpdateErrorCardContent' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' -import { linuxPackageCommandUnavailableText } from '@/lib/updater-message-copy' +import { linuxPackageCommandUnavailableText } from '@/app-shell/updates/updater-message-copy' import { useMountedRef } from '@/hooks/useMountedRef' function copiedNote(packageFileName: string): string { diff --git a/src/renderer/src/components/LoadingSpinner.tsx b/src/renderer/src/components/LoadingSpinner.tsx index 0b91dd04be6..40e0dcaceab 100644 --- a/src/renderer/src/components/LoadingSpinner.tsx +++ b/src/renderer/src/components/LoadingSpinner.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Hourglass, Loader2 } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' // Reduced motion uses an hourglass rather than a stuck spinner. Hide it only when motion is allowed; hidden plus motion-reduce:block left it hidden. export function LoadingSpinner({ className }: { className?: string }): React.JSX.Element { diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.tsx index 3a898de67f3..fc28448d151 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.tsx @@ -1,17 +1,17 @@ import React from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' -import { useAppStore } from '@/store' -import { cn } from '@/lib/utils' +import { useAppStore } from '@/store/index' +import { cn } from '@/lib/class-names' import { getAgentCatalog } from '@/lib/agent-catalog/agent-catalog' -import { getScreenSubmitModifierLabel } from '@/lib/screen-submit-shortcut' +import { getScreenSubmitModifierLabel } from '@/lib/keyboard/screen-submit-shortcut' import { resolveProjectCloneUrlPrefill } from '@/lib/workspace/creation/project-clone-url-prefill' import { useContextualTour } from '@/components/contextual-tours/use-contextual-tour' import { AddRemoteHostDialog, type AddRemoteHostMode } from '@/components/sidebar/host-admission/ssh/AddRemoteHostDialog' -import { lazyWithRetry } from '@/lib/lazy-with-retry' +import { lazyWithRetry } from '@/app-shell/startup/lazy-with-retry' import type * as SetProjectLocationDialogModule from '@/components/new-workspace/SetProjectLocationDialog' import { unwrapRuntimeRpcResult } from '@/runtime/rpc/calls/runtime-rpc-result' import { withUiConnectTimeout } from '@/ssh/ssh-connect-ui-timeout' diff --git a/src/renderer/src/components/NewWorkspaceComposerModal.tsx b/src/renderer/src/components/NewWorkspaceComposerModal.tsx index c0a27c4122c..d36e3994a86 100644 --- a/src/renderer/src/components/NewWorkspaceComposerModal.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerModal.tsx @@ -13,7 +13,7 @@ import { agentChoicePending } from '@/lib/agent-session/launch/inventory/host-ch import type { AgentLaunchSurface } from '@/lib/agent-session/launch/routing/agent-session-launch-plan' import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useAppStore } from '@/store/index' -import { lazyWithRetry } from '@/lib/lazy-with-retry' +import { lazyWithRetry } from '@/app-shell/startup/lazy-with-retry' import { Dialog, DialogContent, @@ -35,7 +35,7 @@ import { } from '@/lib/workspace/creation/quick-workspace-agent-selection' import { useAgentsOfferedForLaunch } from '@/lib/agent-session/launch/inventory/host-chat-program-acquisition' import { shouldAllowComposerEnterSubmitTarget } from '@/lib/workspace/creation/new-workspace-enter-guard' -import { isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' +import { isScreenSubmitShortcut } from '@/lib/keyboard/screen-submit-shortcut' import type { TuiAgent } from '../../../shared/agent-launch/catalog/tui-agent' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/OrcaSignInProgress.tsx b/src/renderer/src/components/OrcaSignInProgress.tsx index 10caa6e6dae..371b9a585f5 100644 --- a/src/renderer/src/components/OrcaSignInProgress.tsx +++ b/src/renderer/src/components/OrcaSignInProgress.tsx @@ -3,8 +3,8 @@ import { toast } from 'sonner' import { Button } from './ui/button' import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' -import { useAppStore } from '../store' +import { cn } from '@/lib/class-names' +import { useAppStore } from '../store/index' async function copySignInLink(link: string): Promise { try { diff --git a/src/renderer/src/components/ShortcutKeyCombo.tsx b/src/renderer/src/components/ShortcutKeyCombo.tsx index 396680ad38b..c9696840ea9 100644 --- a/src/renderer/src/components/ShortcutKeyCombo.tsx +++ b/src/renderer/src/components/ShortcutKeyCombo.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' function KeyCap({ diff --git a/src/renderer/src/components/TerminalLegacyEditorSurface.tsx b/src/renderer/src/components/TerminalLegacyEditorSurface.tsx index 33fcd96dd30..25a906a1fec 100644 --- a/src/renderer/src/components/TerminalLegacyEditorSurface.tsx +++ b/src/renderer/src/components/TerminalLegacyEditorSurface.tsx @@ -1,5 +1,5 @@ import { Suspense } from 'react' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { useTranslate } from '@/i18n/use-translate' import type { TerminalController } from './use-terminal-controller' diff --git a/src/renderer/src/components/UnexpectedSignoutCard.tsx b/src/renderer/src/components/UnexpectedSignoutCard.tsx index 6526bbab29b..86dc10bc0f8 100644 --- a/src/renderer/src/components/UnexpectedSignoutCard.tsx +++ b/src/renderer/src/components/UnexpectedSignoutCard.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react' import { BookOpen, ChevronDown, CircleUserRound, Files, Smartphone, X } from 'lucide-react' -import { useAppStore } from '../store' +import { useAppStore } from '../store/index' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { Button } from './ui/button' import { Card } from './ui/card' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible' diff --git a/src/renderer/src/components/UpdateCard.tsx b/src/renderer/src/components/UpdateCard.tsx index 95105a3408b..f32f72c6e5d 100644 --- a/src/renderer/src/components/UpdateCard.tsx +++ b/src/renderer/src/components/UpdateCard.tsx @@ -2,11 +2,11 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { X } from 'lucide-react' import type { ChangelogData } from '../../../shared/update-status-types' import { usePrefersReducedMotion } from '@/hooks/usePrefersReducedMotion' -import { useAppStore } from '../store' +import { useAppStore } from '../store/index' import { Card } from './ui/card' import { Button } from './ui/button' import { useTranslate } from '@/i18n/use-translate' -import { updaterMessageText } from '@/lib/updater-message-copy' +import { updaterMessageText } from '@/app-shell/updates/updater-message-copy' import { UpdateCardStateContent } from './maintenance/update-card/UpdateCardStateContent' import { buildUpdateCardErrorModel } from './maintenance/update-card/update-card-error-model' import { diff --git a/src/renderer/src/components/activity/activity-thread-context-menu.tsx b/src/renderer/src/components/activity/activity-thread-context-menu.tsx index 725fceb20b7..9bfb38d71ae 100644 --- a/src/renderer/src/components/activity/activity-thread-context-menu.tsx +++ b/src/renderer/src/components/activity/activity-thread-context-menu.tsx @@ -10,10 +10,10 @@ import { } from '@/components/ui/context-menu' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '@/lib/close-all-context-menus' +import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '@/lib/overlay-interaction/close-all-context-menus' import { formatClipboardFilePath } from '@/lib/clipboard/clipboard-file-path' import { getWorktreeRowLocalPathOpenOwner } from '@/lib/workspace/ownership/worktree-row-open-in-owner' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { clearActivityThread, clearCompletedActivity, diff --git a/src/renderer/src/components/activity/activity-thread-controls.tsx b/src/renderer/src/components/activity/activity-thread-controls.tsx index d9520854b5f..f90a355788e 100644 --- a/src/renderer/src/components/activity/activity-thread-controls.tsx +++ b/src/renderer/src/components/activity/activity-thread-controls.tsx @@ -3,7 +3,7 @@ import { ChevronDown } from 'lucide-react' import { AgentStateDot } from '@/components/AgentStateDot' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useNow } from '@/hooks/use-now' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { formatShortTimeAgo } from '@/lib/short-time-ago' import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel' import type { Repo } from '../../../../shared/repo-types' diff --git a/src/renderer/src/components/activity/activity-thread-detail-pane.tsx b/src/renderer/src/components/activity/activity-thread-detail-pane.tsx index db2fab50f99..171c7e448fa 100644 --- a/src/renderer/src/components/activity/activity-thread-detail-pane.tsx +++ b/src/renderer/src/components/activity/activity-thread-detail-pane.tsx @@ -1,7 +1,7 @@ import React from 'react' import { MessageSquareText, TerminalSquare } from 'lucide-react' import { AgentTypeIcon } from '@/components/agent/icons/agent-type-icon' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { EventRepoBadge, ThreadAgentStateIndicator } from './activity-thread-controls' import type { ActivityTerminalPortalSlotId, AgentPaneThread } from './activity-thread-types' diff --git a/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx b/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx index 663bc53bed5..0d02ee03145 100644 --- a/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx +++ b/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useMemo } from 'react' import { Cloud, Copy, FolderGit2, GitBranch, Laptop, LocateFixed, Server } from 'lucide-react' import { toast } from 'sonner' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { RepoBadgeMark } from '@/components/repo/RepoBadgeLabel' import { AgentTypeIcon } from '@/components/agent/icons/agent-type-icon' import { formatAgentTypeLabel } from '../../../../shared/agent-status/presentation/agent-type-label' @@ -11,7 +11,7 @@ import { translate } from '@/i18n/i18n' import { useTranslate } from '@/i18n/use-translate' import { formatClipboardFilePath } from '@/lib/clipboard/clipboard-file-path' import { getWorktreeRowLocalPathOpenOwner } from '@/lib/workspace/ownership/worktree-row-open-in-owner' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { getExecutionHostLabel, getWorktreeExecutionHostId, diff --git a/src/renderer/src/components/activity/activity-thread-list-pane.tsx b/src/renderer/src/components/activity/activity-thread-list-pane.tsx index 473f72e8d3a..13168f4f233 100644 --- a/src/renderer/src/components/activity/activity-thread-list-pane.tsx +++ b/src/renderer/src/components/activity/activity-thread-list-pane.tsx @@ -6,7 +6,7 @@ import { useVirtualizer, type Range } from '@tanstack/react-virtual' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { ActivityThreadListToolbar } from './activity-thread-list-toolbar' import { diff --git a/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx b/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx index 810a2b47853..65f1c34e455 100644 --- a/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx +++ b/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' export function ActivityThreadListResizeHandle({ diff --git a/src/renderer/src/components/activity/activity-thread-list-toolbar.tsx b/src/renderer/src/components/activity/activity-thread-list-toolbar.tsx index 49bf1348cc2..80e9cdd09d6 100644 --- a/src/renderer/src/components/activity/activity-thread-list-toolbar.tsx +++ b/src/renderer/src/components/activity/activity-thread-list-toolbar.tsx @@ -11,7 +11,7 @@ import { } from '@/components/ui/select' import { Toggle } from '@/components/ui/toggle' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { ActivityThreadOptionsMenu } from './activity-thread-controls' import type { diff --git a/src/renderer/src/components/activity/activity-thread-row.tsx b/src/renderer/src/components/activity/activity-thread-row.tsx index ef6e21f58fa..e1f43c68f97 100644 --- a/src/renderer/src/components/activity/activity-thread-row.tsx +++ b/src/renderer/src/components/activity/activity-thread-row.tsx @@ -4,7 +4,7 @@ import { AgentTypeIcon } from '@/components/agent/icons/agent-type-icon' import { formatAgentTypeLabel } from '../../../../shared/agent-status/presentation/agent-type-label' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision' import '../sidebar/workspace-card/activity/worktree-card-empty-inputs' diff --git a/src/renderer/src/components/agent/AgentCombobox.tsx b/src/renderer/src/components/agent/AgentCombobox.tsx index a91dbaf4751..ca201f1ae47 100644 --- a/src/renderer/src/components/agent/AgentCombobox.tsx +++ b/src/renderer/src/components/agent/AgentCombobox.tsx @@ -11,7 +11,7 @@ import { getAgentPickerCommandValue, searchAgentPickerEntries } from '@/lib/agent-catalog/agent-picker-search' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { TuiAgent } from '../../../../shared/agent-launch/catalog/tui-agent' import * as state from './agent-combobox-command-state' import { diff --git a/src/renderer/src/components/agent/AgentLaunchSetupFooter.tsx b/src/renderer/src/components/agent/AgentLaunchSetupFooter.tsx index 82edb8b3792..c716668ece6 100644 --- a/src/renderer/src/components/agent/AgentLaunchSetupFooter.tsx +++ b/src/renderer/src/components/agent/AgentLaunchSetupFooter.tsx @@ -2,7 +2,7 @@ import { terminalPendingLabel } from '../tab-bar/new-tab-agent-menu-model' import { useImperativeHandle, useRef, type Ref } from 'react' import { CornerDownLeft, MessageSquare, SquareTerminal } from 'lucide-react' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/agent/AgentLaunchSetupPanel.tsx b/src/renderer/src/components/agent/AgentLaunchSetupPanel.tsx index e3bcdbd25d0..e72c21251f4 100644 --- a/src/renderer/src/components/agent/AgentLaunchSetupPanel.tsx +++ b/src/renderer/src/components/agent/AgentLaunchSetupPanel.tsx @@ -21,7 +21,7 @@ import { } from '@/components/ui/command' import { useTranslate } from '@/i18n/use-translate' import { agentModelChoiceChips } from './launch-settings/agent-model-choice-chips' -import { isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' +import { isScreenSubmitShortcut } from '@/lib/keyboard/screen-submit-shortcut' import { isImeOwnedKeyboardEvent } from '@/lib/text-input/ime/ime-composition-keyboard-event' import { AgentDefaultArgsInput } from './launch-settings/AgentLaunchDefaultsEditor' import { NativeChatSessionOptionRows } from '../native-chat/session/options/controls/NativeChatSessionOptionRows' diff --git a/src/renderer/src/components/agent/AgentSettingsDialog.tsx b/src/renderer/src/components/agent/AgentSettingsDialog.tsx index 415ba2ff320..a7e10158096 100644 --- a/src/renderer/src/components/agent/AgentSettingsDialog.tsx +++ b/src/renderer/src/components/agent/AgentSettingsDialog.tsx @@ -1,7 +1,10 @@ import { useAppStateValue, useAppStateViewActive } from '@/store/retained-views/app-state-view' import React, { useCallback } from 'react' -import { useAppStore } from '@/store' -import { getOverlayFocusOwners, overlayFocusContains } from '@/lib/overlay-focus-ownership' +import { useAppStore } from '@/store/index' +import { + getOverlayFocusOwners, + overlayFocusContains +} from '@/lib/overlay-interaction/overlay-focus-ownership' import { Dialog, DialogContent, @@ -15,7 +18,7 @@ import { isWindowsTerminalCapabilityHost, useLocalWindowsTerminalCapabilities } from '@/runtime/hosts/capabilities/windows-terminal-capabilities' -import { isWebClientLocation } from '@/lib/web-client-location' +import { isWebClientLocation } from '@/app-shell/platform/web-client-location' import { useWindowsTerminalCapabilityOwnerKey } from '@/hooks/useWindowsTerminalCapabilityOwnerKey' import { resolveSettingsHostScope } from '@/components/settings/host-scope/settings-host-scope' diff --git a/src/renderer/src/components/agent/AgentSurfaceLaunchButton.tsx b/src/renderer/src/components/agent/AgentSurfaceLaunchButton.tsx index 100dc494258..cc260ec60a0 100644 --- a/src/renderer/src/components/agent/AgentSurfaceLaunchButton.tsx +++ b/src/renderer/src/components/agent/AgentSurfaceLaunchButton.tsx @@ -1,6 +1,6 @@ import React from 'react' import { MessageSquare, SquareTerminal } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' type AgentSurfaceLaunchButtonProps = Omit, 'children' | 'title'> & { title: string diff --git a/src/renderer/src/components/agent/agent-combobox-items.tsx b/src/renderer/src/components/agent/agent-combobox-items.tsx index 347adbf7da6..0d18046c13a 100644 --- a/src/renderer/src/components/agent/agent-combobox-items.tsx +++ b/src/renderer/src/components/agent/agent-combobox-items.tsx @@ -7,7 +7,7 @@ import { ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' type ItemRenderArgs = { diff --git a/src/renderer/src/components/agent/agent-combobox-surface-items.tsx b/src/renderer/src/components/agent/agent-combobox-surface-items.tsx index 0749ff09194..b9f66b0cb67 100644 --- a/src/renderer/src/components/agent/agent-combobox-surface-items.tsx +++ b/src/renderer/src/components/agent/agent-combobox-surface-items.tsx @@ -13,7 +13,7 @@ import { rowLabel, rowTitle } from '@/components/tab-bar/tab-agent-row-presentation' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' import type { TuiAgent } from '../../../../shared/agent-launch/catalog/tui-agent' diff --git a/src/renderer/src/components/agent/icons/agent-icon-glyphs.tsx b/src/renderer/src/components/agent/icons/agent-icon-glyphs.tsx index be70a55196e..2cc72aa94f0 100644 --- a/src/renderer/src/components/agent/icons/agent-icon-glyphs.tsx +++ b/src/renderer/src/components/agent/icons/agent-icon-glyphs.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { cn } from '../../../lib/utils' +import { cn } from '../../../lib/class-names' export function PiIcon({ size = 14, diff --git a/src/renderer/src/components/agent/launch-settings/AgentDefaultSurfacePreviews.tsx b/src/renderer/src/components/agent/launch-settings/AgentDefaultSurfacePreviews.tsx index 64e73ff1657..aa9c0a5cf9a 100644 --- a/src/renderer/src/components/agent/launch-settings/AgentDefaultSurfacePreviews.tsx +++ b/src/renderer/src/components/agent/launch-settings/AgentDefaultSurfacePreviews.tsx @@ -19,7 +19,7 @@ import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { AgentIcon } from '@/components/agent/icons/AgentIcon' import { buildFontFamily } from '../../../../../shared/monospace-font-family' import { resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' // The tip's chat miniature, finished and denser so it fits a tile; nothing here animates. const CHAT_SCALE: CSSProperties & { '--chat-font-size': string } = { '--chat-font-size': '10px' } diff --git a/src/renderer/src/components/agent/launch-settings/AgentPermissionMenu.tsx b/src/renderer/src/components/agent/launch-settings/AgentPermissionMenu.tsx index 5755c37a089..cbce5d28108 100644 --- a/src/renderer/src/components/agent/launch-settings/AgentPermissionMenu.tsx +++ b/src/renderer/src/components/agent/launch-settings/AgentPermissionMenu.tsx @@ -1,5 +1,5 @@ import { useTranslate } from '../../../i18n/use-translate' -import { cn } from '../../../lib/utils' +import { cn } from '../../../lib/class-names' import { ChevronDown } from 'lucide-react' import { Button } from '../../ui/button' import { Label } from '../../ui/label' diff --git a/src/renderer/src/components/artifacts/ArtifactCollection.tsx b/src/renderer/src/components/artifacts/ArtifactCollection.tsx index 08663157396..564400b5542 100644 --- a/src/renderer/src/components/artifacts/ArtifactCollection.tsx +++ b/src/renderer/src/components/artifacts/ArtifactCollection.tsx @@ -3,12 +3,12 @@ import { Loader2 } from 'lucide-react' import type { ArtifactListItem } from '../../../../shared/artifacts/artifacts' import { Button } from '@/components/ui/button' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { clampArtifactListSearchQuery, filterArtifactsBySearchQuery } from './artifact-list-search' import { ArtifactListRows } from './ArtifactListRows' import { ArtifactListTableHeader } from './ArtifactListTableHeader' import { ArtifactListToolbar } from './ArtifactListToolbar' -import { LIST_TABLE_CONTAINER_CLASS } from '@/lib/list-table-layout' +import { LIST_TABLE_CONTAINER_CLASS } from '@/lib/list-interaction/list-table-layout' export function ArtifactCollection({ artifacts, diff --git a/src/renderer/src/components/artifacts/ArtifactListRow.tsx b/src/renderer/src/components/artifacts/ArtifactListRow.tsx index 47e63f1b3eb..c1088a175fe 100644 --- a/src/renderer/src/components/artifacts/ArtifactListRow.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListRow.tsx @@ -19,8 +19,11 @@ import { } from '@/components/ui/dropdown-menu' import { translate } from '@/i18n/i18n' import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' -import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction' +import { cn } from '@/lib/class-names' +import { + isPortaledRowMenuClick, + isRowActivationKey +} from '@/lib/list-interaction/list-row-interaction' import { artifactName, artifactTypeLabel, @@ -34,7 +37,7 @@ import { LIST_TABLE_ROW_CLASS, LIST_TABLE_ROW_DIVIDER_CLASS, LIST_TABLE_ROW_SELECTED_CLASS -} from '@/lib/list-table-layout' +} from '@/lib/list-interaction/list-table-layout' type ArtifactRowAction = { key: string diff --git a/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx b/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx index f33d9200119..05c54d1787f 100644 --- a/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx @@ -3,7 +3,7 @@ import { Search, X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' export function ArtifactListSearchField({ query, diff --git a/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx b/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx index 4033ff8921b..7b91c07ec6a 100644 --- a/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx @@ -1,7 +1,7 @@ -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { ARTIFACTS_TABLE_GRID_CLASS } from './artifacts-table-layout' -import { LIST_TABLE_HEADER_CLASS } from '@/lib/list-table-layout' +import { LIST_TABLE_HEADER_CLASS } from '@/lib/list-interaction/list-table-layout' export function ArtifactListTableHeader(): React.JSX.Element { const t = useTranslate() diff --git a/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx b/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx index 2c3272773f8..85680aac4ff 100644 --- a/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx @@ -2,7 +2,7 @@ import { RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { ArtifactListSearchField } from './ArtifactListSearchField' export function ArtifactListToolbar({ diff --git a/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx b/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx index d75832498cc..2d3d3b3f7e0 100644 --- a/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx +++ b/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx @@ -5,8 +5,8 @@ import { Button } from '@/components/ui/button' import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' -import { useAppStore } from '@/store' +import { cn } from '@/lib/class-names' +import { useAppStore } from '@/store/index' import { ArtifactPublishedLinkPanel } from './ArtifactPublishedLinkPanel' import { getPublishedArtifactLink } from './artifact-published-link-client' import { publishArtifactFromSurface } from './artifact-publish-flow' diff --git a/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx b/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx index ff9d79009a2..a37b18b1aa3 100644 --- a/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx +++ b/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx @@ -3,7 +3,7 @@ import { Check, Copy, ExternalLink, Loader2, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { copyArtifactLink, openArtifactInBrowser } from './artifact-link-actions' export function ArtifactPublishedLinkPanel({ diff --git a/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx b/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx index 919799b700d..663f3299a9a 100644 --- a/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx +++ b/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx @@ -1,7 +1,10 @@ -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { ARTIFACTS_TABLE_GRID_CLASS } from './artifacts-table-layout' -import { LIST_TABLE_CONTAINER_CLASS, LIST_TABLE_HEADER_CLASS } from '@/lib/list-table-layout' +import { + LIST_TABLE_CONTAINER_CLASS, + LIST_TABLE_HEADER_CLASS +} from '@/lib/list-interaction/list-table-layout' function SkeletonBar({ className }: { className?: string }): React.JSX.Element { return
diff --git a/src/renderer/src/components/automations/AutomationsPageSkeleton.tsx b/src/renderer/src/components/automations/AutomationsPageSkeleton.tsx index 10c081c2a23..360169b3a9d 100644 --- a/src/renderer/src/components/automations/AutomationsPageSkeleton.tsx +++ b/src/renderer/src/components/automations/AutomationsPageSkeleton.tsx @@ -1,12 +1,12 @@ import React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { AUTOMATIONS_TABLE_GRID_CLASS } from './listing/automations-table-layout' import { LIST_TABLE_CONTAINER_CLASS, LIST_TABLE_HEADER_CLASS, LIST_TABLE_STICKY_HEADER_CELL_CLASS, LIST_TABLE_STICKY_ROW_CELL_CLASS -} from '@/lib/list-table-layout' +} from '@/lib/list-interaction/list-table-layout' import { useTranslate } from '@/i18n/use-translate' function SkeletonBar({ className }: { className?: string }): React.JSX.Element { diff --git a/src/renderer/src/components/automations/authoring/AutomationCustomCronPanel.tsx b/src/renderer/src/components/automations/authoring/AutomationCustomCronPanel.tsx index 99451dab731..af8dadf7ccb 100644 --- a/src/renderer/src/components/automations/authoring/AutomationCustomCronPanel.tsx +++ b/src/renderer/src/components/automations/authoring/AutomationCustomCronPanel.tsx @@ -1,7 +1,7 @@ import React from 'react' import { CheckCircle2, CircleAlert } from 'lucide-react' import { Input } from '@/components/ui/input' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { describeAutomationSchedule } from '../../../../../shared/automations/scheduling/automation-schedules' import { getAutomationCronExpressionFields } from '../../../../../shared/automations/scheduling/automation-schedule-parsing' import { formatUiAutomationScheduleDescriptor } from '../details/automation-schedule-label' diff --git a/src/renderer/src/components/automations/authoring/AutomationEditorPromptEditor.tsx b/src/renderer/src/components/automations/authoring/AutomationEditorPromptEditor.tsx index 15bf790323d..242b01c70a0 100644 --- a/src/renderer/src/components/automations/authoring/AutomationEditorPromptEditor.tsx +++ b/src/renderer/src/components/automations/authoring/AutomationEditorPromptEditor.tsx @@ -7,7 +7,7 @@ import { isMonacoFindWidgetOpen } from '@/components/editor/monaco-find-widget' import { monacoTypographyOptions } from '@/lib/editor/appearance/editor-typography' import { useEditorTypography } from '@/hooks/use-editor-typography' import '@/lib/editor/monaco/monaco-setup' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { buildAutomationPromptEditorOptions } from './automation-editor-prompt-options' import { useMonacoTheme } from '@/hooks/use-monaco-theme' diff --git a/src/renderer/src/components/automations/authoring/AutomationProjectCombobox.tsx b/src/renderer/src/components/automations/authoring/AutomationProjectCombobox.tsx index 490c23c1751..0c25f2bfcbe 100644 --- a/src/renderer/src/components/automations/authoring/AutomationProjectCombobox.tsx +++ b/src/renderer/src/components/automations/authoring/AutomationProjectCombobox.tsx @@ -8,7 +8,7 @@ import { useAppStore } from '@/store/index' import { isGitRepoKind } from '../../../../../shared/repo-kind' import { getRepoExecutionHostId } from '../../../../../shared/execution-host' import { isRepoSearchQueryTooLarge, searchRepos } from '@/lib/repo-search' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useMountedRef } from '@/hooks/useMountedRef' import { useTranslate } from '@/i18n/use-translate' import { projectHostCountLabel } from '@/components/project-host-count-label' diff --git a/src/renderer/src/components/automations/authoring/AutomationSchedulePicker.tsx b/src/renderer/src/components/automations/authoring/AutomationSchedulePicker.tsx index 7a8147234fc..1b1657dd4a1 100644 --- a/src/renderer/src/components/automations/authoring/AutomationSchedulePicker.tsx +++ b/src/renderer/src/components/automations/authoring/AutomationSchedulePicker.tsx @@ -1,4 +1,4 @@ -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { Select, SelectContent, diff --git a/src/renderer/src/components/automations/authoring/AutomationSetupDecisionField.tsx b/src/renderer/src/components/automations/authoring/AutomationSetupDecisionField.tsx index a4ff868a06c..cd07c430940 100644 --- a/src/renderer/src/components/automations/authoring/AutomationSetupDecisionField.tsx +++ b/src/renderer/src/components/automations/authoring/AutomationSetupDecisionField.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Check, ChevronDown } from 'lucide-react' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { getVisibleAutomationSetupDecision } from './automation-setup-decision' import type { AutomationDraft } from './AutomationEditorDialog' diff --git a/src/renderer/src/components/automations/authoring/AutomationTimeField.tsx b/src/renderer/src/components/automations/authoring/AutomationTimeField.tsx index 68e4a13cadd..b347cc49d7e 100644 --- a/src/renderer/src/components/automations/authoring/AutomationTimeField.tsx +++ b/src/renderer/src/components/automations/authoring/AutomationTimeField.tsx @@ -1,7 +1,7 @@ import { ImeInput } from '@/lib/text-input/ime/ime-text-field' import React from 'react' import { Clock } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' const TIME_SHELL_CLASS = diff --git a/src/renderer/src/components/automations/authoring/WorkspaceCombobox.tsx b/src/renderer/src/components/automations/authoring/WorkspaceCombobox.tsx index a262dfe5940..a6126592095 100644 --- a/src/renderer/src/components/automations/authoring/WorkspaceCombobox.tsx +++ b/src/renderer/src/components/automations/authoring/WorkspaceCombobox.tsx @@ -9,7 +9,7 @@ import { CommandList } from '@/components/ui/command' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { Worktree } from '../../../../../shared/worktree/types' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/automations/automation-page-parts.tsx b/src/renderer/src/components/automations/automation-page-parts.tsx index 6019c4fe14a..297865a0052 100644 --- a/src/renderer/src/components/automations/automation-page-parts.tsx +++ b/src/renderer/src/components/automations/automation-page-parts.tsx @@ -1,7 +1,7 @@ import React from 'react' import type { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { AutomationRun } from '../../../../shared/automations/history/automation-run' import type { PendingAutomationSetupReview } from './authoring/automation-setup-review' import { translate } from '@/i18n/i18n' diff --git a/src/renderer/src/components/automations/automations-page-test-harness.tsx b/src/renderer/src/components/automations/automations-page-test-harness.tsx index cab3577d27c..1dbb50644f4 100644 --- a/src/renderer/src/components/automations/automations-page-test-harness.tsx +++ b/src/renderer/src/components/automations/automations-page-test-harness.tsx @@ -94,7 +94,7 @@ vi.mock('sonner', () => ({ } })) -vi.mock('@/lib/window-visibility-interval', () => ({ +vi.mock('@/lib/window-activity/window-visibility-interval', () => ({ installWindowVisibilityInterval: () => () => undefined })) diff --git a/src/renderer/src/components/automations/details/AutomationPromptDisclosure.tsx b/src/renderer/src/components/automations/details/AutomationPromptDisclosure.tsx index 6e8814d380e..92e384bb1be 100644 --- a/src/renderer/src/components/automations/details/AutomationPromptDisclosure.tsx +++ b/src/renderer/src/components/automations/details/AutomationPromptDisclosure.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useId, useRef, useState } from 'react' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' export function AutomationPromptDisclosure({ prompt }: { prompt: string }): React.JSX.Element { diff --git a/src/renderer/src/components/automations/hosts/AutomationHostBadges.tsx b/src/renderer/src/components/automations/hosts/AutomationHostBadges.tsx index 3b84f10f631..c2be0482feb 100644 --- a/src/renderer/src/components/automations/hosts/AutomationHostBadges.tsx +++ b/src/renderer/src/components/automations/hosts/AutomationHostBadges.tsx @@ -2,7 +2,7 @@ import React from 'react' import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel' import { Badge } from '@/components/ui/badge' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { AutomationHostCatalogEntry } from './automation-host-catalog-types' import { hostScopeDescriptor } from './automation-host-scope-descriptors' import { diff --git a/src/renderer/src/components/automations/hosts/AutomationHostFilterNotice.tsx b/src/renderer/src/components/automations/hosts/AutomationHostFilterNotice.tsx index 22e30e7ae75..329ef4b219a 100644 --- a/src/renderer/src/components/automations/hosts/AutomationHostFilterNotice.tsx +++ b/src/renderer/src/components/automations/hosts/AutomationHostFilterNotice.tsx @@ -3,7 +3,7 @@ import { Loader2, TriangleAlert } from 'lucide-react' import { Button } from '@/components/ui/button' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { AutomationHostStatusBadges } from './AutomationHostBadges' import type { AutomationHostFilterResolution } from './automation-host-filter-resolution' import { diff --git a/src/renderer/src/components/automations/hosts/AutomationOwnerConflictNotice.tsx b/src/renderer/src/components/automations/hosts/AutomationOwnerConflictNotice.tsx index 66d36784651..86037da11a0 100644 --- a/src/renderer/src/components/automations/hosts/AutomationOwnerConflictNotice.tsx +++ b/src/renderer/src/components/automations/hosts/AutomationOwnerConflictNotice.tsx @@ -1,7 +1,7 @@ import React from 'react' import { TriangleAlert } from 'lucide-react' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { recoveryActionLabel, diff --git a/src/renderer/src/components/automations/hosts/automation-host-client.ts b/src/renderer/src/components/automations/hosts/automation-host-client.ts index ccc395d8126..5c26da354ee 100644 --- a/src/renderer/src/components/automations/hosts/automation-host-client.ts +++ b/src/renderer/src/components/automations/hosts/automation-host-client.ts @@ -1,5 +1,5 @@ import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' -import { getPairedWebClientEnvironmentId } from '@/lib/paired-web-client-host' +import { getPairedWebClientEnvironmentId } from '@/app-shell/platform/paired-web-client-host' import { prepareAutomationLaunchSetup } from './automation-capability-probe' import type { Automation, diff --git a/src/renderer/src/components/automations/hosts/automation-host-recovery.ts b/src/renderer/src/components/automations/hosts/automation-host-recovery.ts index d88e3fd9714..85ce009ae60 100644 --- a/src/renderer/src/components/automations/hosts/automation-host-recovery.ts +++ b/src/renderer/src/components/automations/hosts/automation-host-recovery.ts @@ -1,6 +1,6 @@ /** Use descriptor recovery verbs; reconnect the failed runtime or SSH target, and route Update server to version management. */ -import type { SettingsNavigationTarget } from '@/lib/settings-navigation-types' +import type { SettingsNavigationTarget } from '@/components/settings/navigation/settings-navigation-types' import type { AutomationHostCatalogEntry } from './automation-host-catalog-types' import type { AutomationHostRecoveryAction } from './automation-host-status-descriptors' diff --git a/src/renderer/src/components/automations/hosts/use-automation-host-catalog-source-state.ts b/src/renderer/src/components/automations/hosts/use-automation-host-catalog-source-state.ts index 8c7195f1a25..d6417c6a75a 100644 --- a/src/renderer/src/components/automations/hosts/use-automation-host-catalog-source-state.ts +++ b/src/renderer/src/components/automations/hosts/use-automation-host-catalog-source-state.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' import { useAppStore } from '@/store/index' -import { getPairedWebClientEnvironmentId } from '@/lib/paired-web-client-host' +import { getPairedWebClientEnvironmentId } from '@/app-shell/platform/paired-web-client-host' import type { PublicKnownRuntimeEnvironment } from '../../../../../shared/remote-runtime/host-catalog/runtime-environments' import { getManagedOrcadSshTargetId } from '../../../../../shared/execution-host/managed-orcad-execution-host' import type { diff --git a/src/renderer/src/components/automations/listing/AutomationListEmptyView.tsx b/src/renderer/src/components/automations/listing/AutomationListEmptyView.tsx index 0b888cb16a7..9f3a330ca63 100644 --- a/src/renderer/src/components/automations/listing/AutomationListEmptyView.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListEmptyView.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { resolveAutomationListEmptyState, type AutomationListEmptyStateInput diff --git a/src/renderer/src/components/automations/listing/AutomationListExternalRow.tsx b/src/renderer/src/components/automations/listing/AutomationListExternalRow.tsx index b802be4ba44..d3e1f56aef4 100644 --- a/src/renderer/src/components/automations/listing/AutomationListExternalRow.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListExternalRow.tsx @@ -15,7 +15,7 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { ExternalAutomationAction, ExternalAutomationJob, @@ -36,8 +36,11 @@ import { LIST_TABLE_ROW_CLASS, LIST_TABLE_ROW_SELECTED_CLASS, LIST_TABLE_STICKY_ROW_CELL_CLASS -} from '@/lib/list-table-layout' -import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction' +} from '@/lib/list-interaction/list-table-layout' +import { + isPortaledRowMenuClick, + isRowActivationKey +} from '@/lib/list-interaction/list-row-interaction' import { getExternalAutomationLastRunSnapshot } from './automation-list-last-run' import { AutomationListLastRunCell } from './AutomationListLastRunCell' import { AutomationListStatusCell } from './AutomationListStatusCell' diff --git a/src/renderer/src/components/automations/listing/AutomationListLastRunCell.tsx b/src/renderer/src/components/automations/listing/AutomationListLastRunCell.tsx index e51e9743a24..d9809d2fe52 100644 --- a/src/renderer/src/components/automations/listing/AutomationListLastRunCell.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListLastRunCell.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { formatAutomationLastRunCell, type AutomationLastRunSnapshot diff --git a/src/renderer/src/components/automations/listing/AutomationListLocalRow.tsx b/src/renderer/src/components/automations/listing/AutomationListLocalRow.tsx index 85c2ec0f2b3..7d39557aca1 100644 --- a/src/renderer/src/components/automations/listing/AutomationListLocalRow.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListLocalRow.tsx @@ -20,7 +20,7 @@ import { import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { AgentIcon } from '@/components/agent/icons/AgentIcon' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { AutomationRun } from '../../../../../shared/automations/history/automation-run' import { getAutomationRunRepoId } from '../../../../../shared/automations/history/automation-run-identity' import { formatUiAutomationSchedule } from '../details/automation-schedule-label' @@ -50,8 +50,11 @@ import { LIST_TABLE_ROW_CLASS, LIST_TABLE_ROW_SELECTED_CLASS, LIST_TABLE_STICKY_ROW_CELL_CLASS -} from '@/lib/list-table-layout' -import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction' +} from '@/lib/list-interaction/list-table-layout' +import { + isPortaledRowMenuClick, + isRowActivationKey +} from '@/lib/list-interaction/list-row-interaction' import { AutomationListStatusCell } from './AutomationListStatusCell' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { useAppStore } from '@/store/index' diff --git a/src/renderer/src/components/automations/listing/AutomationListSearchField.tsx b/src/renderer/src/components/automations/listing/AutomationListSearchField.tsx index 9b6d5199bae..b393a320673 100644 --- a/src/renderer/src/components/automations/listing/AutomationListSearchField.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListSearchField.tsx @@ -3,7 +3,7 @@ import { Search, X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { isAutomationListArrowKey, shouldHandleAutomationListSearchArrowKey, diff --git a/src/renderer/src/components/automations/listing/AutomationListSortHeader.tsx b/src/renderer/src/components/automations/listing/AutomationListSortHeader.tsx index e1f39ee2683..6762b3598b8 100644 --- a/src/renderer/src/components/automations/listing/AutomationListSortHeader.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListSortHeader.tsx @@ -1,6 +1,6 @@ import React from 'react' import { ArrowDown, ArrowUp } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import type { AutomationListSort, AutomationListSortField } from './automation-list-view' diff --git a/src/renderer/src/components/automations/listing/AutomationListTableHeader.tsx b/src/renderer/src/components/automations/listing/AutomationListTableHeader.tsx index 69c261f30d9..76366e3b95a 100644 --- a/src/renderer/src/components/automations/listing/AutomationListTableHeader.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListTableHeader.tsx @@ -4,7 +4,7 @@ import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout' import { LIST_TABLE_HEADER_CLASS, LIST_TABLE_STICKY_HEADER_CELL_CLASS -} from '@/lib/list-table-layout' +} from '@/lib/list-interaction/list-table-layout' import { AutomationListSortHeader } from './AutomationListSortHeader' import type { AutomationListSort, AutomationListSortField } from './automation-list-view' diff --git a/src/renderer/src/components/automations/listing/AutomationListToolbar.tsx b/src/renderer/src/components/automations/listing/AutomationListToolbar.tsx index bbe24069a21..5c2e5252711 100644 --- a/src/renderer/src/components/automations/listing/AutomationListToolbar.tsx +++ b/src/renderer/src/components/automations/listing/AutomationListToolbar.tsx @@ -3,7 +3,7 @@ import { History, Plus, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { AutomationHostCatalogEntry } from '../hosts/automation-host-catalog-types' import type { AutomationListArrowKey } from './automation-list-keyboard-navigation' import { clampAutomationListSearchQueryInput } from './automation-list-search-query' diff --git a/src/renderer/src/components/automations/listing/AutomationsListPanel.tsx b/src/renderer/src/components/automations/listing/AutomationsListPanel.tsx index ac96f41f002..9fbf5b2bd28 100644 --- a/src/renderer/src/components/automations/listing/AutomationsListPanel.tsx +++ b/src/renderer/src/components/automations/listing/AutomationsListPanel.tsx @@ -1,7 +1,7 @@ import type { SetupTrustMismatch } from '../../../../../shared/worktree/create-types' import type { RuntimeEnvironmentSshBucket } from '../../../store/slices/ssh/runtime-environment-ssh' import React, { useRef } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { AutomationRun } from '../../../../../shared/automations/history/automation-run' import type { ExternalAutomationAction, @@ -51,7 +51,7 @@ import type { AutomationHostCatalogView } from '../hosts/use-automation-host-cat import type { AutomationHostRecoveryAction } from '../hosts/automation-host-status-descriptors' import type { AutomationHostCatalogEntry } from '../hosts/automation-host-catalog-types' import { useAutomationListFocusRecovery } from './use-automation-list-focus-recovery' -import { LIST_TABLE_CONTAINER_CLASS } from '@/lib/list-table-layout' +import { LIST_TABLE_CONTAINER_CLASS } from '@/lib/list-interaction/list-table-layout' import { useTranslate } from '@/i18n/use-translate' import { AutomationTemplateEmptyState } from '../authoring/AutomationTemplateEmptyState' import { AutomationListTableHeader } from './AutomationListTableHeader' diff --git a/src/renderer/src/components/automations/runs/AutomationRunDetailsPage.tsx b/src/renderer/src/components/automations/runs/AutomationRunDetailsPage.tsx index c393904acd9..6e6a91bce2b 100644 --- a/src/renderer/src/components/automations/runs/AutomationRunDetailsPage.tsx +++ b/src/renderer/src/components/automations/runs/AutomationRunDetailsPage.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Eye, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import CommentMarkdown from '@/components/sidebar/CommentMarkdown' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import type { Automation } from '../../../../../shared/automations/automation-definition' import type { AutomationRun } from '../../../../../shared/automations/history/automation-run' diff --git a/src/renderer/src/components/automations/runs/AutomationRunHistory.tsx b/src/renderer/src/components/automations/runs/AutomationRunHistory.tsx index 6fe9129addd..235f8c57bf8 100644 --- a/src/renderer/src/components/automations/runs/AutomationRunHistory.tsx +++ b/src/renderer/src/components/automations/runs/AutomationRunHistory.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import { useVirtualizer } from '@tanstack/react-virtual' import { Badge } from '@/components/ui/badge' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { AutomationRun } from '../../../../../shared/automations/history/automation-run' import type { Worktree } from '../../../../../shared/worktree/types' import { diff --git a/src/renderer/src/components/automations/use-automations-page-refresh.ts b/src/renderer/src/components/automations/use-automations-page-refresh.ts index d79192d22aa..9a9742d8172 100644 --- a/src/renderer/src/components/automations/use-automations-page-refresh.ts +++ b/src/renderer/src/components/automations/use-automations-page-refresh.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef } from 'react' -import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval' +import { installWindowVisibilityInterval } from '@/lib/window-activity/window-visibility-interval' import { useAppStore } from '@/store/index' import { AUTOMATIONS_CHANGED_EVENT } from '@/lib/automations-changed-window-event' import { diff --git a/src/renderer/src/components/browser-favicon.tsx b/src/renderer/src/components/browser-favicon.tsx index 86e1ba67c31..383a3ca1266 100644 --- a/src/renderer/src/components/browser-favicon.tsx +++ b/src/renderer/src/components/browser-favicon.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { Globe, Loader2 } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { displayableFaviconUrl } from '../../../shared/browser/browser-favicon-url' export function BrowserFavicon({ diff --git a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx b/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx deleted file mode 100644 index 30b1f23b1c6..00000000000 --- a/src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx +++ /dev/null @@ -1,255 +0,0 @@ -// @vitest-environment happy-dom -import { act, cleanup, render, screen } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { BrowserPage } from '../../../../shared/browser-workspace-types' - -const mocks = vi.hoisted(() => ({ - attach: vi.fn(), - detach: vi.fn(), - recordBreadcrumb: vi.fn() -})) - -vi.mock('./browser-client-page-renderer-installation', () => ({ - attachBrowserClientPageToViewport: mocks.attach -})) -vi.mock('@/lib/crash-breadcrumb-recorder', () => ({ - recordRendererCrashBreadcrumb: mocks.recordBreadcrumb -})) -vi.mock('sonner', () => ({ - toast: { error: vi.fn(), success: vi.fn(), loading: vi.fn(), message: vi.fn() } -})) - -import { TooltipProvider } from '@/components/ui/tooltip' -import { installClientHostedPaneApi } from './client-hosted-browser-pane-test-rig' -import { ClientHostedBrowserPagePane } from './ClientHostedBrowserPagePane' - -const PLACEMENT = { - kind: 'client' as const, - browserHostClientId: 'host-a', - browserHostGeneration: 3, - pageHostGeneration: 7 -} - -/** Verbatim from Electron 43.4.1: main destroyed the guest, the tag still holds its id. */ -function invalidGuestInstanceId(): Error { - return new Error('Invalid guestInstanceId: 7') -} - -/** Verbatim from Electron 43.4.1: focus() after the retained tag left the DOM. */ -function nullContentWindowFocus(): TypeError { - return new TypeError("Cannot read properties of null (reading 'focus')") -} - -function pageNavigationStart(): Event { - return Object.assign(new Event('did-start-navigation'), { - isMainFrame: true, - isInPlace: false, - url: 'https://example.com/next' - }) -} - -function page(overrides?: Partial): BrowserPage { - return { - id: 'page-a', - workspaceId: 'workspace-a', - worktreeId: 'worktree-a', - url: 'https://example.internal/', - title: 'Example', - loading: false, - faviconUrl: null, - canGoBack: false, - canGoForward: false, - loadError: null, - createdAt: 1, - ...overrides - } -} - -function createGuest(): Electron.WebviewTag & { - getURL: ReturnType - reload: ReturnType -} { - const webview = document.createElement('webview') as Electron.WebviewTag & { - getURL: ReturnType - reload: ReturnType - } - Object.assign(webview, { - // An attached guest has a WebContents id; focus waits for one. - getWebContentsId: vi.fn(() => 7), - getURL: vi.fn(() => 'https://example.internal/'), - getTitle: vi.fn(() => 'Example'), - isLoading: vi.fn(() => false), - canGoBack: vi.fn(() => false), - canGoForward: vi.fn(() => false), - focus: vi.fn(), - blur: vi.fn(), - goBack: vi.fn(), - goForward: vi.fn(), - reload: vi.fn(), - loadURL: vi.fn(async () => {}) - }) - mocks.attach.mockReturnValue({ - webview, - detach: mocks.detach, - nextMetadataRevision: vi.fn(() => 1) - }) - return webview -} - -function paneElement( - isActive: boolean, - options?: { browserTab?: BrowserPage; onUpdatePageState?: (id: string, state: unknown) => void } -): React.JSX.Element { - return ( - - - - ) -} - -let webview: ReturnType - -beforeEach(() => { - mocks.attach.mockReset() - mocks.detach.mockReset() - mocks.recordBreadcrumb.mockReset() - installClientHostedPaneApi() - webview = createGuest() -}) - -afterEach(() => { - cleanup() - vi.clearAllMocks() -}) - -describe('client-hosted browser pane over a dead guest', () => { - it('degrades to the unavailable notice when the guest was destroyed in main', () => { - webview.getURL.mockImplementation(() => { - throw invalidGuestInstanceId() - }) - - expect(() => render(paneElement(true))).not.toThrow() - expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() - expect(mocks.detach).toHaveBeenCalled() - expect(mocks.recordBreadcrumb).toHaveBeenCalledWith('browser_client_page_guest_unavailable', { - browserPageId: 'page-a', - pageHostGeneration: PLACEMENT.pageHostGeneration, - reason: 'unreadable', - tagConnected: false - }) - // Why: the catch is total, so the swallowed error must stay visible to diagnostics. - expect(mocks.recordBreadcrumb).toHaveBeenCalledWith('browser_client_page_guest_read_failed', { - errorName: 'Error', - errorMessage: 'Invalid guestInstanceId: 7' - }) - }) - - it('flips to the unavailable notice when the guest renderer goes away after attach', () => { - const onUpdatePageState = vi.fn() - render(paneElement(true, { browserTab: page({ loading: true }), onUpdatePageState })) - expect(screen.queryByText('Client-hosted browser unavailable')).toBeNull() - onUpdatePageState.mockClear() - - // The registry pulls the tag out of the DOM on this event without telling the pane. - webview.remove() - act(() => { - webview.dispatchEvent(new Event('render-process-gone')) - }) - - expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() - expect(mocks.detach).toHaveBeenCalled() - expect(onUpdatePageState).toHaveBeenCalledWith('page-a', { loading: false }) - expect(mocks.recordBreadcrumb).toHaveBeenCalledWith('browser_client_page_guest_unavailable', { - browserPageId: 'page-a', - pageHostGeneration: PLACEMENT.pageHostGeneration, - reason: 'render-process-gone', - tagConnected: false - }) - }) - - it('flips to the unavailable notice when main destroys the guest after attach', () => { - render(paneElement(true)) - - act(() => { - webview.dispatchEvent(new Event('destroyed')) - }) - - expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() - expect(mocks.recordBreadcrumb).toHaveBeenCalledWith( - 'browser_client_page_guest_unavailable', - expect.objectContaining({ reason: 'destroyed' }) - ) - // The chrome must not keep driving the dead tag: Reload routes to the notice, not a throw. - webview.reload.mockImplementation(() => { - throw invalidGuestInstanceId() - }) - expect(() => act(() => screen.getByRole('button', { name: 'Reload' }).click())).not.toThrow() - expect(webview.reload).not.toHaveBeenCalled() - }) - - it('stops the spinner when the guest dies as a load starts', () => { - const onUpdatePageState = vi.fn() - render(paneElement(true, { onUpdatePageState })) - onUpdatePageState.mockClear() - webview.getURL.mockImplementation(() => { - throw invalidGuestInstanceId() - }) - - act(() => { - webview.dispatchEvent(pageNavigationStart()) - }) - - expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() - // The navigation start writes loading:true first; the loss must be the last word. - expect(onUpdatePageState.mock.calls.at(-1)).toEqual(['page-a', { loading: false }]) - }) - - it('ignores queued load events after guest loss', () => { - const onUpdatePageState = vi.fn() - render(paneElement(true, { onUpdatePageState })) - act(() => webview.dispatchEvent(new Event('destroyed'))) - onUpdatePageState.mockClear() - - act(() => webview.dispatchEvent(pageNavigationStart())) - - expect(onUpdatePageState).not.toHaveBeenCalled() - expect(screen.getByText('Client-hosted browser unavailable')).toBeTruthy() - }) - - it('stops listening for guest loss once the pane lets go of the tag', () => { - const onUpdatePageState = vi.fn() - const view = render(paneElement(true, { onUpdatePageState })) - view.unmount() - onUpdatePageState.mockClear() - mocks.recordBreadcrumb.mockClear() - - webview.dispatchEvent(new Event('destroyed')) - - expect(onUpdatePageState).not.toHaveBeenCalled() - expect(mocks.recordBreadcrumb).not.toHaveBeenCalled() - }) - - it('survives activation focus after the retained tag left the DOM', () => { - const view = render(paneElement(false)) - webview.focus = vi.fn(() => { - throw nullContentWindowFocus() - }) - - expect(() => - act(() => { - view.rerender(paneElement(true)) - }) - ).not.toThrow() - expect(webview.focus).toHaveBeenCalled() - }) -}) diff --git a/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.tsx b/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.tsx index 044c72e5339..207a94ae24c 100644 --- a/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.tsx +++ b/src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.tsx @@ -4,7 +4,7 @@ import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { reopenBrowserPageOnServer } from './browser-reopen-on-server' /** Deferred so a fast local round-trip never flashes a spinner. */ diff --git a/src/renderer/src/components/browser-pane/annotate/MarkupDrawButton.tsx b/src/renderer/src/components/browser-pane/annotate/MarkupDrawButton.tsx index 3f4879b56ba..7b560e7c9b3 100644 --- a/src/renderer/src/components/browser-pane/annotate/MarkupDrawButton.tsx +++ b/src/renderer/src/components/browser-pane/annotate/MarkupDrawButton.tsx @@ -2,7 +2,7 @@ import React from 'react' import { PenTool } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { MarkupDrawHintPopover, useMarkupDrawHintState } from './markup-draw-hint-popover' diff --git a/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.tsx b/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.tsx index 73c73aac099..b41fa518dea 100644 --- a/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.tsx +++ b/src/renderer/src/components/browser-pane/annotate/MarkupOverlay.tsx @@ -3,7 +3,7 @@ import React, { useCallback, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { Check, X } from 'lucide-react' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import { MarkupToolbar } from './MarkupToolbar' import type { MarkupBaseImage } from './markup-base-image' diff --git a/src/renderer/src/components/browser-pane/annotate/MarkupToolbar.tsx b/src/renderer/src/components/browser-pane/annotate/MarkupToolbar.tsx index aaa1dc4cc09..6ff31b2cd0f 100644 --- a/src/renderer/src/components/browser-pane/annotate/MarkupToolbar.tsx +++ b/src/renderer/src/components/browser-pane/annotate/MarkupToolbar.tsx @@ -14,7 +14,7 @@ import { import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision' diff --git a/src/renderer/src/components/browser-pane/annotate/browser-page-annotation-tray.tsx b/src/renderer/src/components/browser-pane/annotate/browser-page-annotation-tray.tsx index 46c1fbb77b4..0c85bbe7caf 100644 --- a/src/renderer/src/components/browser-pane/annotate/browser-page-annotation-tray.tsx +++ b/src/renderer/src/components/browser-pane/annotate/browser-page-annotation-tray.tsx @@ -19,7 +19,7 @@ import { } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' -import { isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' +import { isScreenSubmitShortcut } from '@/lib/keyboard/screen-submit-shortcut' import { useTranslate } from '@/i18n/use-translate' import { GRAB_BUDGET, diff --git a/src/renderer/src/components/browser-pane/annotate/pending-browser-annotation-card.tsx b/src/renderer/src/components/browser-pane/annotate/pending-browser-annotation-card.tsx index 7da77dff10c..2734c73fa9b 100644 --- a/src/renderer/src/components/browser-pane/annotate/pending-browser-annotation-card.tsx +++ b/src/renderer/src/components/browser-pane/annotate/pending-browser-annotation-card.tsx @@ -5,7 +5,10 @@ import { Button } from '@/components/ui/button' import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' import { Label } from '@/components/ui/label' import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover' -import { getScreenSubmitModifierLabel, isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut' +import { + getScreenSubmitModifierLabel, + isScreenSubmitShortcut +} from '@/lib/keyboard/screen-submit-shortcut' import { GRAB_BUDGET, type BrowserAnnotationIntent, diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserAddressBar.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserAddressBar.tsx index 06fe9192e0d..27d7677b619 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserAddressBar.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserAddressBar.tsx @@ -2,9 +2,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Globe } from 'lucide-react' import { Input } from '@/components/ui/input' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { findPage } from '@/store/slices/browser/pages/browser-page-records' import { DEFAULT_SEARCH_ENGINE, type SearchEngine } from '../../../../../shared/browser-url' import type { BrowserPageDocLocation } from '../../../../../shared/browser-workspace-types' diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserImportHintButton.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserImportHintButton.tsx index 58bda179f1c..56a905e92ea 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserImportHintButton.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserImportHintButton.tsx @@ -17,8 +17,8 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' -import { useAppStore } from '@/store' -import { isLinuxUserAgent, isMacUserAgent } from '@/lib/user-agent-platform' +import { useAppStore } from '@/store/index' +import { isLinuxUserAgent, isMacUserAgent } from '@/app-shell/platform/user-agent-platform' import { getBrowserCookieImportSourceLabels } from '../../../../../shared/browser-cookie-import-sources' import { shouldShowBrowserImportHint } from './browser-import-hint-visibility' import { formatBrowserImportSummary } from './browser-detected-browsers-summary' diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx index 6767e8140bb..b6c7dc55355 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx @@ -1,6 +1,6 @@ import { useCallback, useRef, useState, type ReactElement } from 'react' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { RuntimeTerminalDriverState } from '../../../../../shared/runtime-session-contracts' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-annotate-split-button.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-annotate-split-button.tsx index d10eb448d1b..db953c79880 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-annotate-split-button.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-annotate-split-button.tsx @@ -20,7 +20,7 @@ import { } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { GrabIntent } from '../../../../../shared/browser/element-grab/browser-grab-types' import { MarkupDrawHintPopover, useMarkupDrawHintState } from '../annotate/markup-draw-hint-popover' diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-egress-indicator.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-egress-indicator.tsx index caac19220bd..fd2141b18b4 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-egress-indicator.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-egress-indicator.tsx @@ -1,14 +1,14 @@ import { useState } from 'react' import { Globe, Monitor, Server } from 'lucide-react' import { useTranslate } from '@/i18n/use-translate' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { Button } from '@/components/ui/button' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { BROWSER_CLIENT_HOSTED_REMOTE_SETTINGS_TARGET_ID, BROWSER_SSH_WORKSPACE_ROUTING_SETTINGS_TARGET_ID -} from '@/lib/settings-navigation-types' +} from '@/components/settings/navigation/settings-navigation-types' import { toRuntimeExecutionHostId, toSshExecutionHostId diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-action-menu.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-action-menu.tsx index dac44cfa2c2..65524ac57b4 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-action-menu.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-action-menu.tsx @@ -18,7 +18,7 @@ import { import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { browserExtensionRemovalDescription } from '@/components/browser-extension-removal-description' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { BrowserExtensionContextMenuItem } from '../../../../../shared/browser/extensions/browser-extension-context-menu-types' import type { BrowserExtensionActionResult } from '../../../../../shared/browser/extensions/browser-extension-types' import type { BrowserExtensionToolbarAction } from '../../../../../shared/browser/extensions/browser-extension-toolbar-types' diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-toolbar.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-toolbar.tsx index e87d5d6e89c..60ec2f415da 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-toolbar.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-extension-toolbar.tsx @@ -12,8 +12,8 @@ import { } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' -import { useAppStore } from '@/store' +import { cn } from '@/lib/class-names' +import { useAppStore } from '@/store/index' import type { BrowserExtensionToolbarAction, BrowserExtensionToolbarState diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-link-status-bubble.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-link-status-bubble.tsx index 9c1cc7ecd22..8b3c067ddcf 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-link-status-bubble.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-link-status-bubble.tsx @@ -1,5 +1,5 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { windowDipToCssPx } from '@/lib/ui-zoom' import { toDisplayUrl } from '../describe-page/browser-page-url-display' diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-navigation-control-row.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-navigation-control-row.tsx index 36de7804a45..31a33b7c25c 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-navigation-control-row.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-navigation-control-row.tsx @@ -1,7 +1,7 @@ import { ArrowLeft, ArrowRight, RefreshCw, X } from 'lucide-react' import { Button } from '@/components/ui/button' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { BROWSER_CHROME_ADDRESS_SLOT_ATTRIBUTE, BROWSER_CHROME_ADDRESS_SLOT_HEIGHT_CLASS diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-chrome-banners.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-chrome-banners.tsx index 4e346034015..4ce988d5d15 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-chrome-banners.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-chrome-banners.tsx @@ -1,5 +1,5 @@ import type { Dispatch, SetStateAction } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { CircleCheck, Copy, Crosshair, Send, Trash2 } from 'lucide-react' import { Button } from '@/components/ui/button' import { diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-extension-menu-items.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-extension-menu-items.tsx index 2906b0b8ba2..97b17cf8a5b 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-extension-menu-items.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-extension-menu-items.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { Check, ChevronRight } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { BrowserExtensionContextMenuItem, BrowserExtensionContextMenuPayload diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-feedback-badge.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-feedback-badge.tsx index 29c4a2e144f..c645530c5d8 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-feedback-badge.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-feedback-badge.tsx @@ -1,5 +1,5 @@ import { useEffect, useImperativeHandle, useRef, useState, type Ref } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { BROWSER_PAGE_ZOOM_FEEDBACK_MS } from '../describe-page/browser-annotation-geometry' export type BrowserPageFeedbackBadgeHandle = { diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-pane.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-pane.tsx index 015330ae64e..ca2157b01bf 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-pane.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/browser-page-pane.tsx @@ -1,9 +1,9 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import '@/lib/workspace/ownership/connection-context' import { getConnectionIdFromState } from '@/lib/workspace/ownership/connection-owner-resolution' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { useShortcutLabel } from '@/hooks/useShortcutLabel' import '../../../../../shared/constants' import { ORCA_BROWSER_BLANK_URL } from '../../../../../shared/browser/browser-blank-page' diff --git a/src/renderer/src/components/browser-pane/browser-client-page-guest-metadata.ts b/src/renderer/src/components/browser-pane/browser-client-page-guest-metadata.ts index aaafcd0c845..18c78708b2d 100644 --- a/src/renderer/src/components/browser-pane/browser-client-page-guest-metadata.ts +++ b/src/renderer/src/components/browser-pane/browser-client-page-guest-metadata.ts @@ -1,7 +1,7 @@ import type { BrowserLoadError } from '../../../../shared/browser-workspace-types' import type { BrowserPageFailLoadEvent } from './describe-page/browser-page-types' import { resolveBrowserWebviewLoadFailure } from './navigate/browser-webview-load-failure' -import { recordRendererCrashBreadcrumb } from '@/lib/crash-breadcrumb-recorder' +import { recordRendererCrashBreadcrumb } from '@/diagnostics/crash-reporting/crash-breadcrumb-recorder' import { redactKagiSessionToken } from '../../../../shared/browser-url' import type { BrowserClientPageMetadataSnapshot } from './browser-client-page-metadata-publisher' diff --git a/src/renderer/src/components/browser-pane/browser-client-page-metadata-publish-e2e-fault.ts b/src/renderer/src/components/browser-pane/browser-client-page-metadata-publish-e2e-fault.ts index 0b1d8b2cdf8..ab2a022f805 100644 --- a/src/renderer/src/components/browser-pane/browser-client-page-metadata-publish-e2e-fault.ts +++ b/src/renderer/src/components/browser-pane/browser-client-page-metadata-publish-e2e-fault.ts @@ -1,4 +1,4 @@ -import { e2eConfig } from '@/lib/e2e-config' +import { e2eConfig } from '@/app-shell/startup/e2e-config' /** Hold runtime metadata stale deterministically; normal publish lag is sub-second, and snapshot waits would miss both that window and outright failure. */ type MetadataPublishFaultApi = { diff --git a/src/renderer/src/components/browser-pane/browser-user-agent-migration-notice.ts b/src/renderer/src/components/browser-pane/browser-user-agent-migration-notice.ts index b618a4ea366..8932b5baba9 100644 --- a/src/renderer/src/components/browser-pane/browser-user-agent-migration-notice.ts +++ b/src/renderer/src/components/browser-pane/browser-user-agent-migration-notice.ts @@ -1,8 +1,8 @@ import { useEffect } from 'react' import { toast } from 'sonner' import { translate } from '@/i18n/i18n' -import { useAppStore } from '@/store' -import { BROWSER_USER_AGENT_SETTINGS_TARGET_ID } from '@/lib/settings-navigation-types' +import { useAppStore } from '@/store/index' +import { BROWSER_USER_AGENT_SETTINGS_TARGET_ID } from '@/components/settings/navigation/settings-navigation-types' export function useBrowserIdentityMigrationNotice(): void { useEffect(() => { diff --git a/src/renderer/src/components/browser-pane/host-guest/browser-client-page-guest-loss.ts b/src/renderer/src/components/browser-pane/host-guest/browser-client-page-guest-loss.ts index b9e2f6a21f6..baeab47a922 100644 --- a/src/renderer/src/components/browser-pane/host-guest/browser-client-page-guest-loss.ts +++ b/src/renderer/src/components/browser-pane/host-guest/browser-client-page-guest-loss.ts @@ -1,5 +1,5 @@ import type { MutableRefObject } from 'react' -import { recordRendererCrashBreadcrumb } from '@/lib/crash-breadcrumb-recorder' +import { recordRendererCrashBreadcrumb } from '@/diagnostics/crash-reporting/crash-breadcrumb-recorder' export type BrowserClientPageGuestLossReason = 'unreadable' | 'destroyed' | 'render-process-gone' diff --git a/src/renderer/src/components/browser-pane/navigate/browser-load-failure-auto-retry.tsx b/src/renderer/src/components/browser-pane/navigate/browser-load-failure-auto-retry.tsx index fe2ca83ab47..d29aceaa5fb 100644 --- a/src/renderer/src/components/browser-pane/navigate/browser-load-failure-auto-retry.tsx +++ b/src/renderer/src/components/browser-pane/navigate/browser-load-failure-auto-retry.tsx @@ -3,7 +3,7 @@ import type { BrowserLoadFailureDescription, BrowserLoadFailureRecovery } from '../../../../../shared/browser-load-failure' -import { isWindowVisible } from '@/lib/window-visibility-interval' +import { isWindowVisible } from '@/lib/window-activity/window-visibility-interval' import { translateLoadFailurePiece } from './browser-notices' export type BrowserLoadFailureAutoRetry = { diff --git a/src/renderer/src/components/browser-pane/navigate/use-browser-page-webview-url-sync.ts b/src/renderer/src/components/browser-pane/navigate/use-browser-page-webview-url-sync.ts index e75edf5a5fa..8e0ad0bcd32 100644 --- a/src/renderer/src/components/browser-pane/navigate/use-browser-page-webview-url-sync.ts +++ b/src/renderer/src/components/browser-pane/navigate/use-browser-page-webview-url-sync.ts @@ -11,7 +11,7 @@ import { applyBrowserPageViewportLayout, syncBrowserPageChromeInset } from '../host-guest/browser-page-viewport' -import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval' +import { installWindowVisibilityInterval } from '@/lib/window-activity/window-visibility-interval' import { shouldPollChromiumErrorPage } from './chromium-error-page-polling' import { isChromiumErrorPage } from '../describe-page/browser-page-url-display' import type { BrowserTabPageState } from '../describe-page/browser-page-types' diff --git a/src/renderer/src/components/browser-pane/new-tab/browser-new-tab-workspace-card.tsx b/src/renderer/src/components/browser-pane/new-tab/browser-new-tab-workspace-card.tsx index 8db21bb677a..783e4e55f28 100644 --- a/src/renderer/src/components/browser-pane/new-tab/browser-new-tab-workspace-card.tsx +++ b/src/renderer/src/components/browser-pane/new-tab/browser-new-tab-workspace-card.tsx @@ -6,7 +6,7 @@ import { useState } from 'react' import { ChevronRight, CircleDot, GitMerge, GitPullRequest } from 'lucide-react' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { LinearIcon } from '@/components/icons/LinearIcon' import { JiraIcon } from '@/components/icons/JiraIcon' import { getReviewStateTone } from '@/components/github/review-state-presentation' diff --git a/src/renderer/src/components/browser-pane/stream-remote/remote-browser-screencast-subscription.ts b/src/renderer/src/components/browser-pane/stream-remote/remote-browser-screencast-subscription.ts index d1eb1ab666b..1611e8172dd 100644 --- a/src/renderer/src/components/browser-pane/stream-remote/remote-browser-screencast-subscription.ts +++ b/src/renderer/src/components/browser-pane/stream-remote/remote-browser-screencast-subscription.ts @@ -4,7 +4,7 @@ import type { BrowserScreencastResult } from '../../../../../shared/browser/runtime/runtime-browser-contracts' import { withBrowserPaneUiRuntimeRpcSource } from '../../../../../shared/runtime-rpc-feature-interaction-source' -import { e2eConfig } from '@/lib/e2e-config' +import { e2eConfig } from '@/app-shell/startup/e2e-config' import { isRemoteBrowserPageMissingCode } from './remote-browser-stream-errors' import type { RemoteBrowserViewportSize } from './remote-browser-stream-tokens' diff --git a/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx b/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx index f3af842801a..5280b21ba45 100644 --- a/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx +++ b/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx @@ -2,7 +2,7 @@ import { ImeInput } from '@/lib/text-input/ime/ime-text-field' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useVirtualizer } from '@tanstack/react-virtual' import { Check, ChevronLeft, SearchIcon } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import type { PaletteFilterOption } from './palette-filter-options' import type { PaletteFilterField } from './palette-filter' diff --git a/src/renderer/src/components/cmd-j/palette-results.ts b/src/renderer/src/components/cmd-j/palette-results.ts index 2b98f6e08bd..082d5233a1b 100644 --- a/src/renderer/src/components/cmd-j/palette-results.ts +++ b/src/renderer/src/components/cmd-j/palette-results.ts @@ -1,6 +1,9 @@ import { translate } from '@/i18n/i18n' import type { Translate } from '@/i18n/use-translate' -import type { SettingsNavIcon, SettingsNavSection } from '@/lib/settings-navigation-types' +import type { + SettingsNavIcon, + SettingsNavSection +} from '@/components/settings/navigation/settings-navigation-types' import type { CmdJQuickAction } from './quick-actions' import { isClipboardTextByteLengthOverLimit } from '../../../../shared/clipboard-text' import { diff --git a/src/renderer/src/components/confirmation-dialog.tsx b/src/renderer/src/components/confirmation-dialog.tsx index 05dc78a514e..c760ead49f8 100644 --- a/src/renderer/src/components/confirmation-dialog.tsx +++ b/src/renderer/src/components/confirmation-dialog.tsx @@ -17,9 +17,9 @@ import { type ConfirmationDialogContextValue, type ConfirmationDialogOptions } from '@/components/confirmation-dialog-context' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' type ConfirmationDialogRequest = { id: number diff --git a/src/renderer/src/components/confirmation-skip-preference.ts b/src/renderer/src/components/confirmation-skip-preference.ts index c7eea096fd4..302d9701347 100644 --- a/src/renderer/src/components/confirmation-skip-preference.ts +++ b/src/renderer/src/components/confirmation-skip-preference.ts @@ -1,6 +1,6 @@ import { toast } from 'sonner' import { translate } from '@/i18n/i18n' -import type { SettingsNavTarget } from '@/lib/settings-navigation-types' +import type { SettingsNavTarget } from '@/components/settings/navigation/settings-navigation-types' import type { GlobalSettings } from '../../../shared/global-settings-types' /** Persists the preference and keeps its reversal one click away. */ diff --git a/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx b/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx index 73eb03b8bb4..7b1ba7bb562 100644 --- a/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx +++ b/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type JSX } from 'react' import { hasUndoneContextualTourAction } from '../../../../shared/contextual-tour-action-steps' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { runActiveNativeChatSplit } from '@/components/native-chat/pane/routing/native-chat-layout-actions' import { getContextualTour, @@ -11,7 +11,7 @@ import type { ContextualTourOutcome } from '../../../../shared/feature-education import { trackContextualTourOutcome, trackContextualTourShown -} from '@/lib/feature-education-telemetry' +} from '@/diagnostics/telemetry/feature-education-telemetry' import { isContextualTourAllowedForModal } from './contextual-tour-gate' import { areContextualTourRenderStatesEqual, @@ -25,12 +25,12 @@ import { handleContextualTourOverlayKeyDown, type ActiveTourRenderState } from './ContextualTourOverlaySurface' -import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval' +import { installWindowVisibilityInterval } from '@/lib/window-activity/window-visibility-interval' import { requestActiveTerminalPaneSplit } from '@/components/tab-bar/request-active-terminal-pane-split' import { performContextualTourStepAction } from './contextual-tour-step-actions' import { useContextualTourFocus } from './use-contextual-tour-focus' import { openWorkspaceCreationComposerWithTourHandoff } from './workspace-creation-tour-handoff' -import { BROWSER_CLIENT_HOSTED_REMOTE_SETTINGS_TARGET_ID } from '@/lib/settings-navigation-types' +import { BROWSER_CLIENT_HOSTED_REMOTE_SETTINGS_TARGET_ID } from '@/components/settings/navigation/settings-navigation-types' export function ContextualTourOverlay(): JSX.Element | null { const activeTourId = useAppStore((s) => s.activeContextualTourId) diff --git a/src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.tsx b/src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.tsx index e4184bb60b3..87ee6953377 100644 --- a/src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.tsx +++ b/src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.tsx @@ -11,8 +11,8 @@ import { import { ArrowLeft, ArrowRight, X } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' -import { cn } from '@/lib/utils' -import { useAppStore } from '@/store' +import { cn } from '@/lib/class-names' +import { useAppStore } from '@/store/index' import { getContextualTour, type ContextualTourId, diff --git a/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx b/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx index 302f4094562..dc784398dc7 100644 --- a/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx +++ b/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx @@ -1,5 +1,5 @@ import type { JSX } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' export function ContextualTourProgressDots({ diff --git a/src/renderer/src/components/crash-report/CrashReportDialog.tsx b/src/renderer/src/components/crash-report/CrashReportDialog.tsx index f28ef34dd3a..ecd1d071327 100644 --- a/src/renderer/src/components/crash-report/CrashReportDialog.tsx +++ b/src/renderer/src/components/crash-report/CrashReportDialog.tsx @@ -1,10 +1,10 @@ import { Suspense, useCallback, useEffect, useRef, useState } from 'react' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { useMountedRef } from '@/hooks/useMountedRef' import { REACT_ERROR_BOUNDARY_REPORT_AVAILABLE_EVENT, takePendingReactErrorBoundaryReport -} from '@/lib/react-error-boundary-reporting' +} from '@/diagnostics/crash-reporting/react-error-boundary-reporting' import type { CrashReportRecord } from '../../../../shared/crash-reporting' const CrashReportDialogSurface = lazy(() => diff --git a/src/renderer/src/components/dashboard-popout/AgentCardLastMessages.tsx b/src/renderer/src/components/dashboard-popout/AgentCardLastMessages.tsx index d5b6be31ec1..61680753967 100644 --- a/src/renderer/src/components/dashboard-popout/AgentCardLastMessages.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentCardLastMessages.tsx @@ -1,7 +1,7 @@ import { formatAgentTypeLabel } from '../../../../shared/agent-status/presentation/agent-type-label' import type { DashboardCard } from '../../../../shared/dashboard-snapshot' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' export function hasAgentCardLastMessages(card: DashboardCard): boolean { return Boolean(card.lastUserMessage || card.lastAgentMessage) diff --git a/src/renderer/src/components/dashboard-popout/AgentDashboardToolbar.tsx b/src/renderer/src/components/dashboard-popout/AgentDashboardToolbar.tsx index d5b393f270a..f225ad2fd67 100644 --- a/src/renderer/src/components/dashboard-popout/AgentDashboardToolbar.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentDashboardToolbar.tsx @@ -11,7 +11,7 @@ import { } from '@/components/ui/dropdown-menu' import { Input } from '@/components/ui/input' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { getWorkspaceStatusVisualMeta } from '../sidebar/workspace-status/presentation/workspace-status' import type { DashboardCard, DashboardFilterOptions } from '../../../../shared/dashboard-snapshot' import { diff --git a/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx b/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx index 65a1b766bf4..3982f058d2a 100644 --- a/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx @@ -7,9 +7,9 @@ import { type DashboardSnapshot } from '../../../../shared/dashboard-snapshot' import type { RepoIcon } from '../../../../shared/repo-icon' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { TooltipProvider } from '@/components/ui/tooltip' -import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval' +import { installWindowVisibilityInterval } from '@/lib/window-activity/window-visibility-interval' import { AgentKanbanCard } from './AgentKanbanCard' import { AgentDashboardToolbar } from './AgentDashboardToolbar' import { AgentTerminalDialog, type AgentRevealArgs } from './AgentTerminalDialog' diff --git a/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx b/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx index dc46ed8d2d5..3cd6f0788d7 100644 --- a/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx @@ -12,7 +12,7 @@ import { AgentQuestionIcon } from '@/components/AgentQuestionIcon' import { AgentStateDot } from '@/components/AgentStateDot' import { RepoIconGlyph } from '@/components/repo/repo-icon' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { dashboardCardDisplayState, type DashboardCard diff --git a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx index 560c0e3459a..47ae4dff869 100644 --- a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx @@ -14,7 +14,7 @@ import { AgentTerminalPreview } from './AgentTerminalPreview' import { AgentCardLastMessages, hasAgentCardLastMessages } from './AgentCardLastMessages' import { terminalPreviewUnavailableMessage } from './terminal-preview-unavailable-message' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' /** Routing payload for focusing an agent's pane in the main window. */ export type AgentRevealArgs = DashboardRevealAgentArgs diff --git a/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx b/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx index 9ef12243799..f5ced3a45ee 100644 --- a/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx @@ -16,8 +16,8 @@ import { syncPreviewTerminalLigatures } from './preview-terminal-ligatures' import type { DashboardCardTerminalInput } from '../../../../shared/dashboard-snapshot' import { terminalPreviewUnavailableMessage } from './terminal-preview-unavailable-message' import { resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme' -import { cn } from '@/lib/utils' -import { useAppStore } from '@/store' +import { cn } from '@/lib/class-names' +import { useAppStore } from '@/store/index' import { usePluginFontReady } from '@/store/plugins/appearance/plugin-fonts' import { createPreviewGridClaim } from './preview-grid-claim' import { createPreviewBoxFit } from './preview-terminal-box-fit' diff --git a/src/renderer/src/components/dashboard-popout/DashboardHostBadge.tsx b/src/renderer/src/components/dashboard-popout/DashboardHostBadge.tsx index cd8557f37f8..0e95bb2ee15 100644 --- a/src/renderer/src/components/dashboard-popout/DashboardHostBadge.tsx +++ b/src/renderer/src/components/dashboard-popout/DashboardHostBadge.tsx @@ -2,7 +2,7 @@ import { Server } from 'lucide-react' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { DashboardCardHostKind } from '../../../../shared/dashboard-snapshot' import { parseExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host' diff --git a/src/renderer/src/components/dashboard-popout/dashboard-preview-link-interaction.ts b/src/renderer/src/components/dashboard-popout/dashboard-preview-link-interaction.ts index afa28feafba..a75ae43fad9 100644 --- a/src/renderer/src/components/dashboard-popout/dashboard-preview-link-interaction.ts +++ b/src/renderer/src/components/dashboard-popout/dashboard-preview-link-interaction.ts @@ -14,7 +14,7 @@ import { getTerminalBufferPositionForMouseEvent } from '@/components/terminal-pa import { findHttpLinkAtTerminalMouseEvent } from '@/components/terminal-pane/links/http/terminal-url-link-hit-testing' import { findTerminalHttpLinkAtBufferPosition } from '../../../../shared/terminal/links/terminal-link-buffer-hit-testing' import { httpLinkDirectClickDestination } from '@/lib/browser/navigation/http-link-destinations' -import { isWindowsUserAgent } from '@/lib/user-agent-platform' +import { isWindowsUserAgent } from '@/app-shell/platform/user-agent-platform' import type { GlobalSettings } from '../../../../shared/global-settings-types' import { dashboardPreviewHttpActions, diff --git a/src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.test.ts b/src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.test.ts deleted file mode 100644 index 5b351691f32..00000000000 --- a/src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -// @vitest-environment happy-dom - -import { act, cleanup, renderHook } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { getDefaultSettings } from '../../../../shared/constants' -import type { TelemetryConsentState } from '../../../../shared/telemetry-consent-types' -import { useAppStore } from '@/store' - -const { track, getConsentState } = vi.hoisted(() => ({ - track: vi.fn(), - getConsentState: vi.fn() -})) -vi.mock('@/lib/telemetry', () => ({ track, trackWithResult: track, getConsentState })) - -import { useAgentDashboardTelemetry } from './use-agent-dashboard-telemetry' - -const initialState = useAppStore.getInitialState() - -async function flushConsent(): Promise { - await act(async () => { - await Promise.resolve() - }) -} - -function publishSavedConsent(optedIn: boolean | null): void { - useAppStore.setState((state) => ({ - settings: { - ...(state.settings ?? getDefaultSettings(process.cwd())), - telemetry: { optedIn, installId: 'test-install', existedBeforeTelemetryRelease: false } - } - })) -} - -describe('dashboard visible-use telemetry', () => { - let visibility: DocumentVisibilityState - - beforeEach(() => { - vi.useFakeTimers({ now: new Date('2026-10-22T12:00:00Z') }) - track.mockReset().mockResolvedValue(true) - getConsentState.mockReset().mockResolvedValue({ effective: 'enabled' }) - useAppStore.setState({ - settings: { - ...getDefaultSettings(process.cwd()), - telemetry: { - optedIn: true, - installId: 'test-install', - existedBeforeTelemetryRelease: false - } - } - }) - visibility = 'visible' - vi.spyOn(document, 'visibilityState', 'get').mockImplementation(() => visibility) - }) - - afterEach(() => { - cleanup() - useAppStore.setState(initialState, true) - vi.useRealTimers() - vi.restoreAllMocks() - }) - - it.each(['pending_banner', 'user_opt_out', 'do_not_track', 'orca_disabled', 'ci'] as const)( - 'creates no timer or daily-use attempts while blocked by %s', - async (reason) => { - getConsentState.mockResolvedValue( - reason === 'pending_banner' - ? { effective: 'pending_banner' } - : { effective: 'disabled', reason } - ) - renderHook(() => useAgentDashboardTelemetry('popout')) - await flushConsent() - - act(() => vi.advanceTimersByTime(24 * 60 * 60_000)) - - expect(vi.getTimerCount()).toBe(0) - expect(track.mock.calls).toEqual([['agent_dashboard_opened', { surface: 'popout' }]]) - } - ) - - it('starts and stops observing on consent broadcasts without counting a new opening', async () => { - publishSavedConsent(null) - getConsentState.mockResolvedValue({ effective: 'pending_banner' }) - renderHook(() => useAgentDashboardTelemetry('popout')) - await flushConsent() - expect(vi.getTimerCount()).toBe(0) - - getConsentState.mockResolvedValue({ effective: 'enabled' }) - act(() => { - publishSavedConsent(true) - }) - await flushConsent() - expect(vi.getTimerCount()).toBe(1) - expect(track.mock.calls.filter(([event]) => event === 'agent_dashboard_active')).toHaveLength(1) - - getConsentState.mockResolvedValue({ effective: 'disabled', reason: 'user_opt_out' }) - act(() => { - publishSavedConsent(false) - }) - await flushConsent() - expect(vi.getTimerCount()).toBe(0) - expect(track.mock.calls.filter(([event]) => event === 'agent_dashboard_opened')).toHaveLength(1) - }) - - it('does not install an observer after closing before the consent read finishes', async () => { - let resolveConsent: ((value: TelemetryConsentState) => void) | undefined - getConsentState.mockReturnValue( - new Promise((resolve) => { - resolveConsent = resolve - }) - ) - const { unmount } = renderHook(() => useAgentDashboardTelemetry('popout')) - unmount() - - resolveConsent?.({ effective: 'enabled' }) - await flushConsent() - - expect(vi.getTimerCount()).toBe(0) - expect(track.mock.calls).toEqual([['agent_dashboard_opened', { surface: 'popout' }]]) - }) -}) diff --git a/src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.ts b/src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.ts index 9f353179124..9fd788ea8cc 100644 --- a/src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.ts +++ b/src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.ts @@ -1,8 +1,8 @@ import { useEffect } from 'react' import type { AgentDashboardSurface } from '../../../../shared/telemetry-agent-dashboard-event-schemas' -import { getConsentState, track, trackWithResult } from '@/lib/telemetry' -import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval' -import { useAppStore } from '@/store' +import { getConsentState, track, trackWithResult } from '@/diagnostics/telemetry/telemetry' +import { installWindowVisibilityInterval } from '@/lib/window-activity/window-visibility-interval' +import { useAppStore } from '@/store/index' export function useAgentDashboardTelemetry(surface: AgentDashboardSurface): void { const optedIn = useAppStore((state) => state.settings?.telemetry?.optedIn) diff --git a/src/renderer/src/components/dashboard/DashboardAgentChildDisclosure.tsx b/src/renderer/src/components/dashboard/DashboardAgentChildDisclosure.tsx index 44842099050..e9de1e7fa30 100644 --- a/src/renderer/src/components/dashboard/DashboardAgentChildDisclosure.tsx +++ b/src/renderer/src/components/dashboard/DashboardAgentChildDisclosure.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react' import { ChevronDown } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { Button } from '@/components/ui/button' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/dashboard/DashboardAgentRow.tsx b/src/renderer/src/components/dashboard/DashboardAgentRow.tsx index 7d676e65533..a67e05ec0e3 100644 --- a/src/renderer/src/components/dashboard/DashboardAgentRow.tsx +++ b/src/renderer/src/components/dashboard/DashboardAgentRow.tsx @@ -1,5 +1,5 @@ import React, { useState, useCallback } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { opensClaudeParentConversation, viewClaudeAgentFromRow diff --git a/src/renderer/src/components/dashboard/DashboardAgentRowMessage.tsx b/src/renderer/src/components/dashboard/DashboardAgentRowMessage.tsx index 5ad38208446..2024cc53425 100644 --- a/src/renderer/src/components/dashboard/DashboardAgentRowMessage.tsx +++ b/src/renderer/src/components/dashboard/DashboardAgentRowMessage.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { CommentMarkdownAsync, preloadCommentMarkdown diff --git a/src/renderer/src/components/dashboard/DashboardAgentRowToolStep.tsx b/src/renderer/src/components/dashboard/DashboardAgentRowToolStep.tsx index e96843bd814..6ae783f9df5 100644 --- a/src/renderer/src/components/dashboard/DashboardAgentRowToolStep.tsx +++ b/src/renderer/src/components/dashboard/DashboardAgentRowToolStep.tsx @@ -1,5 +1,5 @@ import { Wrench } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' type DashboardAgentRowToolStepProps = { expanded: boolean diff --git a/src/renderer/src/components/dashboard/DashboardAgentRowTrailingControls.tsx b/src/renderer/src/components/dashboard/DashboardAgentRowTrailingControls.tsx index 07d1a10179d..28b90b79c86 100644 --- a/src/renderer/src/components/dashboard/DashboardAgentRowTrailingControls.tsx +++ b/src/renderer/src/components/dashboard/DashboardAgentRowTrailingControls.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react' import { ChevronDown, Send, X } from 'lucide-react' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { DashboardAgentChildDisclosure } from './DashboardAgentChildDisclosure' type DashboardAgentRowTrailingControlsProps = { diff --git a/src/renderer/src/components/dictation/DictationGrapes.tsx b/src/renderer/src/components/dictation/DictationGrapes.tsx index 8b80331c9b0..7b80ed415a1 100644 --- a/src/renderer/src/components/dictation/DictationGrapes.tsx +++ b/src/renderer/src/components/dictation/DictationGrapes.tsx @@ -1,4 +1,4 @@ -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { DICTATION_GRAPES } from '../../../../shared/dictation-audio-meter' type DictationGrapesProps = { diff --git a/src/renderer/src/components/dictation/DictationIndicator.tsx b/src/renderer/src/components/dictation/DictationIndicator.tsx index 43b0969d090..28121e94e28 100644 --- a/src/renderer/src/components/dictation/DictationIndicator.tsx +++ b/src/renderer/src/components/dictation/DictationIndicator.tsx @@ -4,9 +4,9 @@ import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useShortcutKeyDetails } from '@/hooks/useShortcutLabel' import { useTranslate } from '@/i18n/use-translate' -import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval' -import { cn } from '@/lib/utils' -import { useAppStore } from '@/store' +import { installWindowVisibilityInterval } from '@/lib/window-activity/window-visibility-interval' +import { cn } from '@/lib/class-names' +import { useAppStore } from '@/store/index' import { ArrowUp, Check, CornerDownLeft, Loader2 } from 'lucide-react' import { isDictationPillShown } from '../../../../shared/dictation-session-machine' import { dictationSeparators } from '../../../../shared/dictation-transcript-spacing' diff --git a/src/renderer/src/components/dictation/DictationPillShell.tsx b/src/renderer/src/components/dictation/DictationPillShell.tsx index dbfea6fc954..5cffcfac656 100644 --- a/src/renderer/src/components/dictation/DictationPillShell.tsx +++ b/src/renderer/src/components/dictation/DictationPillShell.tsx @@ -1,5 +1,5 @@ -import { KEEPS_FOCUS_ATTRIBUTE } from '@/lib/focus-keeping-press' -import { cn } from '@/lib/utils' +import { KEEPS_FOCUS_ATTRIBUTE } from '@/lib/overlay-interaction/focus-keeping-press' +import { cn } from '@/lib/class-names' import { useDictationPillBox, type DictationPillAnchor } from './dictation-pill-anchor' // Presses on the pill keep focus where the words are going. diff --git a/src/renderer/src/components/dictation/dictation-meter-store.ts b/src/renderer/src/components/dictation/dictation-meter-store.ts index 57b771190ff..0799072b952 100644 --- a/src/renderer/src/components/dictation/dictation-meter-store.ts +++ b/src/renderer/src/components/dictation/dictation-meter-store.ts @@ -1,4 +1,4 @@ -import { e2eConfig } from '@/lib/e2e-config' +import { e2eConfig } from '@/app-shell/startup/e2e-config' import { useStore } from 'zustand' import { createStore } from 'zustand/vanilla' import { diff --git a/src/renderer/src/components/dictation/dictation-toasts.ts b/src/renderer/src/components/dictation/dictation-toasts.ts index 4d16b528431..cf9145f7593 100644 --- a/src/renderer/src/components/dictation/dictation-toasts.ts +++ b/src/renderer/src/components/dictation/dictation-toasts.ts @@ -1,7 +1,7 @@ import { toast } from 'sonner' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { translate } from '@/i18n/i18n' -import { compactIpcErrorMessage } from '@/lib/ipc-error' +import { compactIpcErrorMessage } from '@/runtime/errors/ipc-error' import { hostDictationErrorMessage } from '../../../../shared/host-dictation-error-messages' import { SPEECH_MODEL_NEEDS_UPDATE_ERROR } from '../../../../shared/speech-model-retirement' diff --git a/src/renderer/src/components/dismiss/EyeOffDismissButton.tsx b/src/renderer/src/components/dismiss/EyeOffDismissButton.tsx index ddee49616c1..6c6949d80ca 100644 --- a/src/renderer/src/components/dismiss/EyeOffDismissButton.tsx +++ b/src/renderer/src/components/dismiss/EyeOffDismissButton.tsx @@ -1,6 +1,6 @@ import { EyeOff } from 'lucide-react' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' /** The eye-slash control that hides a teaching or info surface; its label names what goes away. */ export function EyeOffDismissButton({ diff --git a/src/renderer/src/components/editor/ChangesModeView.tsx b/src/renderer/src/components/editor/ChangesModeView.tsx index c24703feaac..0d6343d37a4 100644 --- a/src/renderer/src/components/editor/ChangesModeView.tsx +++ b/src/renderer/src/components/editor/ChangesModeView.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import type { OpenFile } from '@/store/slices/editor/types/open-file' import type { GitDiffResult } from '../../../../shared/git/diff/git-diff-compare-types' import type { GitStatusEntry } from '../../../../shared/git/status/git-status-types' diff --git a/src/renderer/src/components/editor/ConflictComponents.tsx b/src/renderer/src/components/editor/ConflictComponents.tsx index bc1eeeede53..a33ca8dda8b 100644 --- a/src/renderer/src/components/editor/ConflictComponents.tsx +++ b/src/renderer/src/components/editor/ConflictComponents.tsx @@ -11,7 +11,7 @@ import { } from 'lucide-react' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { ConflictReviewEntry } from '../../../../shared/editor-tab-view-types' import type { OpenFile } from '@/store/slices/editor/types/open-file' import type { diff --git a/src/renderer/src/components/editor/ConflictReviewFileTree.tsx b/src/renderer/src/components/editor/ConflictReviewFileTree.tsx index 9b68aa734a9..77647f54081 100644 --- a/src/renderer/src/components/editor/ConflictReviewFileTree.tsx +++ b/src/renderer/src/components/editor/ConflictReviewFileTree.tsx @@ -2,7 +2,7 @@ import React from 'react' import { ChevronDown, PanelLeftClose } from 'lucide-react' import { Button } from '@/components/ui/button' import { FileTypeIcon, FolderIcon } from '@/components/FileTypeIcon' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { buildSourceControlTree, compactSourceControlTree, diff --git a/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx b/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx deleted file mode 100644 index a9890849ccc..00000000000 --- a/src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx +++ /dev/null @@ -1,282 +0,0 @@ -// @vitest-environment happy-dom -import { cleanup, render } from '@testing-library/react' -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { OpenFile } from '@/store/slices/editor/types/open-file' - -const lifecycle = vi.hoisted(() => ({ - events: [] as string[], - diffModelKeys: [] as string[], - viewStateKeys: new Array(), - models: new Map(), - notebookProps: [] as { - fileId: string - filePath: string - worktreeId: string - scrollCacheKey: string - onContentChange: (content: string) => void - onSave: (content: string) => Promise - }[], - richMarkdownProps: [] as { - externalSshTargetId?: string - runtimeEnvironmentId?: string - worktreeId: string - }[], - mountedProps: [] as { - filePath: string - readOnly?: boolean - liveTail?: boolean - viewStateId?: string - }[] -})) - -vi.mock('@/lib/lazy-with-retry', async () => { - const React = await import('react') - const { syncContentOnMount } = await import('./monaco-content-sync') - return { - lazyWithRetry: (factory: () => Promise) => { - if (factory.toString().includes('/DiffViewer.tsx')) { - return function MockDiffViewer(props: { filePath: string; modifiedModelKey?: string }) { - lifecycle.diffModelKeys.push(props.modifiedModelKey ?? '') - /* oxlint-disable react-hooks/exhaustive-deps -- Mount-only by design: a prop-effect would hide a missing outer React remount. */ - React.useEffect(() => { - lifecycle.events.push(`mount-diff:${props.filePath}`) - return () => { - lifecycle.events.push(`unmount-diff:${props.filePath}`) - } - }, []) - /* oxlint-enable react-hooks/exhaustive-deps */ - return null - } - } - if (factory.toString().includes('/IpynbViewer.tsx')) { - return function MockIpynbViewer(props: (typeof lifecycle.notebookProps)[number]) { - lifecycle.notebookProps.push(props) - return null - } - } - if (factory.toString().includes('/RichMarkdownEditor.tsx')) { - return function MockRichMarkdownEditor( - props: (typeof lifecycle.richMarkdownProps)[number] - ) { - lifecycle.richMarkdownProps.push(props) - return null - } - } - if (!factory.toString().includes('/MonacoEditor.tsx')) { - return () => null - } - return function MockRetainedMonaco(props: { - filePath: string - content: string - readOnly?: boolean - liveTail?: boolean - viewStateId?: string - viewStateKey: string - }) { - lifecycle.viewStateKeys.push(props.viewStateKey) - /* oxlint-disable react-hooks/exhaustive-deps -- Mount-only by design: a prop-effect would hide a missing outer React remount. */ - React.useEffect(() => { - lifecycle.events.push(`mount:${props.filePath}`) - lifecycle.mountedProps.push({ - filePath: props.filePath, - readOnly: props.readOnly, - liveTail: props.liveTail, - viewStateId: props.viewStateId - }) - const retained = lifecycle.models.get(props.filePath) ?? { content: '', undo: [] } - const model = { - getValue: () => retained.content, - getEOL: () => '\n', - getFullModelRange: () => ({ - startLineNumber: 1, - startColumn: 1, - endLineNumber: 1, - endColumn: retained.content.length + 1 - }), - pushEditOperations: (_selections: unknown[], operations: { text: string }[]) => { - retained.content = operations[0]?.text ?? retained.content - } - } - // Use the real mount reconciler to observe key ordering with retained-model behavior intact. - syncContentOnMount( - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: content sync calls only the editor methods stubbed here. - { - getModel: () => model, - saveViewState: () => null, - restoreViewState: () => {}, - pushUndoStop: () => { - retained.undo.push('unexpected undo stop') - return true - } - } as never, - props.content - ) - lifecycle.models.set(props.filePath, retained) - return () => { - lifecycle.events.push(`unmount:${props.filePath}`) - } - }, []) - /* oxlint-enable react-hooks/exhaustive-deps */ - return null - } - } - } -}) -vi.mock('@/store', () => ({ - useAppStore: Object.assign( - (selector: (state: Record) => unknown) => - selector({ - worktreesByRepo: {}, - settings: {}, - markdownRichModeSizeOverride: {}, - setMarkdownRichModeSizeOverride: vi.fn(), - openFile: vi.fn(), - openMarkdownPreview: vi.fn(), - openConflictReviewFile: vi.fn(), - openConflictReview: vi.fn(), - closeFile: vi.fn(), - setRightSidebarTab: vi.fn(), - setPendingEditorReveal: vi.fn(), - reloadOpenCheckRunDetailsTab: vi.fn() - }), - { - getState: () => ({ - folderWorkspaces: [], - projectGroups: [], - repos: [], - settings: {}, - worktreesByRepo: {}, - openFile: vi.fn(), - openMarkdownPreview: vi.fn(), - openConflictReviewFile: vi.fn(), - openConflictReview: vi.fn(), - closeFile: vi.fn(), - setRightSidebarTab: vi.fn(), - setPendingEditorReveal: vi.fn(), - reloadOpenCheckRunDetailsTab: vi.fn() - }) - } - ) -})) - -import { EditorContent } from './EditorContent' - -function file(filePath: string, overrides: Partial = {}): OpenFile { - return { - id: filePath, - filePath, - relativePath: filePath.split('/').at(-1) ?? filePath, - worktreeId: 'repo::/repo', - language: 'typescript', - isDirty: false, - mode: 'edit', - ...overrides - } -} - -function props(activeFile: OpenFile, content: string) { - return { - activeFile, - viewStateScopeId: 'same-pane', - fileContents: { [activeFile.id]: { content, isBinary: false } }, - diffContents: {}, - editBuffers: {}, - openFiles: [activeFile], - worktreeEntries: [], - resolvedLanguage: 'typescript', - isMarkdown: false, - isMermaid: false, - isCsv: false, - isNotebook: false, - mdViewMode: 'rich' as const, - inlineMarkdownRenderState: null, - isChangesMode: false, - sideBySide: false, - pendingEditorReveal: null, - handleContentChange: vi.fn(), - handleContentChangeForFile: vi.fn(), - handleDirtyStateHint: vi.fn(), - handleSave: vi.fn(), - handleSaveForFile: vi.fn(), - reloadContent: vi.fn() - } -} - -function diffProps(activeFile: OpenFile, modifiedContent: string) { - return { - ...props(activeFile, ''), - diffContents: { - [activeFile.id]: { - kind: 'text' as const, - originalContent: '', - modifiedContent, - originalIsBinary: false as const, - modifiedIsBinary: false as const - } - } - } -} - -afterEach(() => { - cleanup() - lifecycle.events.length = 0 - lifecycle.diffModelKeys.length = 0 - lifecycle.viewStateKeys.length = 0 - lifecycle.models.clear() - lifecycle.mountedProps.length = 0 - lifecycle.notebookProps.length = 0 - lifecycle.richMarkdownProps.length = 0 -}) - -describe('EditorContent Monaco lifecycle boundary', () => { - it('unmounts the prior path before reconciling a stale retained target model', () => { - const first = file('/repo/first.ts') - const second = file('/repo/second.ts') - lifecycle.models.set(first.filePath, { content: 'first with edits', undo: ['first undo'] }) - lifecycle.models.set(second.filePath, { content: 'stale second', undo: ['second undo'] }) - const view = render() - - view.rerender() - - expect(lifecycle.events).toEqual([ - 'mount:/repo/first.ts', - 'unmount:/repo/first.ts', - 'mount:/repo/second.ts' - ]) - expect(lifecycle.models.get(second.filePath)).toEqual({ - content: 'fresh second', - undo: ['second undo'] - }) - expect(lifecycle.models.get(first.filePath)).toEqual({ - content: 'first with edits', - undo: ['first undo'] - }) - }) - - it('keeps the diff editor mounted when a save updates the modified content', () => { - // Saves refresh retained models without remounting the widget and flashing its loading placeholder. - const diff = file('/repo/notes.ts', { mode: 'diff', diffSource: 'unstaged' }) - - const view = render() - view.rerender() - - expect(lifecycle.events).toEqual(['mount-diff:/repo/notes.ts']) - expect(lifecycle.diffModelKeys).toHaveLength(2) - expect(lifecycle.diffModelKeys[1]).not.toBe(lifecycle.diffModelKeys[0]) - }) - - it('still remounts the diff editor for an explicit reload request', () => { - const diff = file('/repo/notes.ts', { mode: 'diff', diffSource: 'unstaged' }) - const view = render() - - view.rerender( - - ) - - expect(lifecycle.events).toEqual([ - 'mount-diff:/repo/notes.ts', - 'unmount-diff:/repo/notes.ts', - 'mount-diff:/repo/notes.ts' - ]) - }) -}) diff --git a/src/renderer/src/components/editor/EditorFileLoadErrorView.tsx b/src/renderer/src/components/editor/EditorFileLoadErrorView.tsx index 88632ff7709..613b8c3146f 100644 --- a/src/renderer/src/components/editor/EditorFileLoadErrorView.tsx +++ b/src/renderer/src/components/editor/EditorFileLoadErrorView.tsx @@ -2,7 +2,7 @@ import { AlertCircle, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { displayIpcErrorMessage } from '@/lib/ipc-error' +import { displayIpcErrorMessage } from '@/runtime/errors/ipc-error' import { isFileTooLargeCodeMessage } from '../../../../shared/file-write/file-write-disk-precondition' import { WORKTREE_HOST_UNRESOLVED_CODE } from './editor-panel-content-types' import { useStoppedWslHost } from '../wsl-host-stopped/use-stopped-wsl-host' diff --git a/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx b/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx index 9db39932b01..10f0f1f0765 100644 --- a/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx +++ b/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx @@ -14,7 +14,7 @@ import { isImeCompositionKeyDown } from '@/lib/text-input/ime/ime-composition-ke import { useTranslate } from '@/i18n/use-translate' import type { OpenFile } from '@/store/slices/editor/types/open-file' import { isVirtualEditorFile } from '@/store/slices/editor/tabs/editor-tab-content-type' -import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '@/lib/close-all-context-menus' +import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '@/lib/overlay-interaction/close-all-context-menus' import { useEditorHeaderFileRename } from './editor-header-file-rename' import { getEditorHeaderCopyState } from './editor-header' import { splitPathForDisplay } from './editor-path-display' diff --git a/src/renderer/src/components/editor/ImageViewer.tsx b/src/renderer/src/components/editor/ImageViewer.tsx index 1fbb791e991..df016270be5 100644 --- a/src/renderer/src/components/editor/ImageViewer.tsx +++ b/src/renderer/src/components/editor/ImageViewer.tsx @@ -1,6 +1,6 @@ import { Image as ImageIcon, Maximize2, RotateCcw, ZoomIn, ZoomOut } from 'lucide-react' import { type JSX, type SyntheticEvent, useMemo, useState } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import PdfViewer from './PdfViewer' import { IMAGE_VIEWER_ZOOM_STEP } from './image-viewer-zoom' import type { PanZoomSize } from '../../../../shared/pan-zoom-view' diff --git a/src/renderer/src/components/editor/MediaViewer.test.tsx b/src/renderer/src/components/editor/MediaViewer.test.tsx index 2e82cec6d80..3aaaba95902 100644 --- a/src/renderer/src/components/editor/MediaViewer.test.tsx +++ b/src/renderer/src/components/editor/MediaViewer.test.tsx @@ -3,7 +3,7 @@ import { StrictMode, act } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, describe, expect, it, vi } from 'vitest' -import { mediaPlaybackPositionCache } from '@/lib/scroll-cache' +import { mediaPlaybackPositionCache } from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import MediaViewer from './MediaViewer' vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback })) @@ -41,36 +41,6 @@ describe('MediaViewer', () => { return { root, video } } - it('keeps its source through Strict Mode setup and stops playback on unmount', async () => { - const pause = vi.spyOn(HTMLMediaElement.prototype, 'pause').mockImplementation(() => {}) - const load = vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(() => {}) - const container = document.createElement('div') - const root = createRoot(container) - await act(async () => - root.render( - - - - ) - ) - const video = container.querySelector('video') - expect(video?.getAttribute('src')).toBe('orca-media://file/token') - expect(video?.controls).toBe(true) - expect(video?.autoplay).toBe(false) - expect(video?.preload).toBe('metadata') - pause.mockClear() - load.mockClear() - await act(async () => root.unmount()) - expect(pause).toHaveBeenCalledTimes(1) - expect(load).toHaveBeenCalledTimes(1) - expect(video?.getAttribute('src')).toBeNull() - }) - it('retains the position when leaving before metadata and clamps it when the file shrinks', async () => { vi.spyOn(HTMLMediaElement.prototype, 'pause').mockImplementation(() => {}) vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(() => {}) diff --git a/src/renderer/src/components/editor/MediaViewer.tsx b/src/renderer/src/components/editor/MediaViewer.tsx index 12df5319be0..7878b63ede4 100644 --- a/src/renderer/src/components/editor/MediaViewer.tsx +++ b/src/renderer/src/components/editor/MediaViewer.tsx @@ -2,7 +2,10 @@ import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' import { useEffect, useRef, useState } from 'react' import { useTranslate } from '@/i18n/use-translate' import { Button } from '@/components/ui/button' -import { mediaPlaybackPositionCache, setWithLRU } from '@/lib/scroll-cache' +import { + mediaPlaybackPositionCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' export default function MediaViewer({ src, diff --git a/src/renderer/src/components/editor/MermaidViewer.test.tsx b/src/renderer/src/components/editor/MermaidViewer.test.tsx deleted file mode 100644 index b5af2054e3c..00000000000 --- a/src/renderer/src/components/editor/MermaidViewer.test.tsx +++ /dev/null @@ -1,124 +0,0 @@ -// @vitest-environment happy-dom -import { act, cleanup, render, waitFor } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { scrollTopCache } from '@/lib/scroll-cache' - -const mermaid = vi.hoisted(() => ({ - initialize: vi.fn(), - render: vi.fn<(id: string, content: string) => Promise<{ svg: string }>>() -})) -vi.mock('mermaid', () => ({ default: mermaid })) -vi.mock('dompurify', () => ({ default: { sanitize: (svg: string) => svg } })) - -import MermaidViewer from './MermaidViewer' - -const frames = new Map() -const resizes = new Set<() => void>() -let nextFrame = 0 - -beforeEach(() => { - scrollTopCache.clear() - frames.clear() - resizes.clear() - mermaid.render.mockReset() - vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => { - const id = ++nextFrame - frames.set(id, callback) - return id - }) - vi.stubGlobal('cancelAnimationFrame', (id: number) => frames.delete(id)) - vi.stubGlobal( - 'ResizeObserver', - class { - constructor(private callback: () => void) {} - observe(): void { - resizes.add(this.callback) - } - disconnect(): void { - resizes.delete(this.callback) - } - } - ) -}) - -afterEach(() => { - cleanup() - vi.unstubAllGlobals() - scrollTopCache.clear() -}) - -function diagramViewport( - container: HTMLElement, - renderedHeight = 1200, - visible = () => true -): HTMLElement { - const viewer = container.querySelector('.mermaid-viewer') - if (!(viewer instanceof HTMLElement)) { - throw new Error('Missing diagram viewport') - } - let scrollTop = 0 - Object.defineProperties(viewer, { - clientHeight: { get: () => (visible() ? 100 : 0) }, - scrollHeight: { - get: () => - !visible() - ? 0 - : viewer.querySelector('.mermaid-block')?.hasChildNodes() - ? renderedHeight - : 100 - }, - scrollTop: { - get: () => scrollTop, - set: (value: number) => { - scrollTop = Math.max(0, Math.min(value, viewer.scrollHeight - viewer.clientHeight)) - } - } - }) - return viewer -} - -function advanceFrames(count: number): void { - for (let index = 0; index < count; index += 1) { - const scheduled = [...frames.values()] - frames.clear() - scheduled.forEach((callback) => callback(index * 16)) - } -} - -describe('Mermaid diagram scroll restoration', () => { - it('restores the cached position when rendering finishes after the old retry window', async () => { - const pending = Promise.withResolvers<{ svg: string }>() - mermaid.render.mockReturnValueOnce(pending.promise) - scrollTopCache.set('/diagram.mmd:mermaid-diagram', 600) - const { container } = render( - - ) - const viewer = diagramViewport(container) - await waitFor(() => expect(mermaid.render).toHaveBeenCalledTimes(1)) - act(() => advanceFrames(40)) - expect(viewer.scrollTop).toBe(0) - - await act(async () => pending.resolve({ svg: 'Diagram' })) - - expect(viewer.scrollTop).toBe(600) - }) - - it('waits for a hidden workspace to become measurable after rendering completes', async () => { - const pending = Promise.withResolvers<{ svg: string }>() - mermaid.render.mockReturnValueOnce(pending.promise) - scrollTopCache.set('/diagram.mmd:mermaid-diagram', 600) - const { container } = render( - - ) - let visible = false - const viewer = diagramViewport(container, 1200, () => visible) - await waitFor(() => expect(mermaid.render).toHaveBeenCalledTimes(1)) - await act(async () => pending.resolve({ svg: 'Diagram' })) - expect(viewer.scrollTop).toBe(0) - - visible = true - act(() => resizes.forEach((callback) => callback())) - - expect(viewer.scrollTop).toBe(600) - }) -}) diff --git a/src/renderer/src/components/editor/MermaidViewer.tsx b/src/renderer/src/components/editor/MermaidViewer.tsx index f9f9d07ffad..3212b019124 100644 --- a/src/renderer/src/components/editor/MermaidViewer.tsx +++ b/src/renderer/src/components/editor/MermaidViewer.tsx @@ -1,5 +1,8 @@ import React, { useCallback, useLayoutEffect, useRef } from 'react' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import MermaidBlock from './MermaidBlock' import { useMermaidDiagramExpand } from './use-mermaid-diagram-expand' import { useDocumentDarkTheme } from '@/hooks/use-document-dark-theme' diff --git a/src/renderer/src/components/editor/MonacoCodeExcerpt.tsx b/src/renderer/src/components/editor/MonacoCodeExcerpt.tsx index 17ea73dd547..902d112d28d 100644 --- a/src/renderer/src/components/editor/MonacoCodeExcerpt.tsx +++ b/src/renderer/src/components/editor/MonacoCodeExcerpt.tsx @@ -5,7 +5,7 @@ import { editorTypographyTextStyle } from '@/lib/editor/appearance/editor-typography' import { useEditorTypography } from '@/hooks/use-editor-typography' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useMonacoTheme } from '@/hooks/use-monaco-theme' let pythonLanguageRegistrationPromise: Promise | null = null diff --git a/src/renderer/src/components/editor/MonacoEditor.owner-view-state.test.tsx b/src/renderer/src/components/editor/MonacoEditor.owner-view-state.test.tsx deleted file mode 100644 index 5fe29c1c11c..00000000000 --- a/src/renderer/src/components/editor/MonacoEditor.owner-view-state.test.tsx +++ /dev/null @@ -1,177 +0,0 @@ -// @vitest-environment happy-dom -import { act, cleanup, render, waitFor } from '@testing-library/react' -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' -import { afterEach, expect, it, vi } from 'vitest' -import type { AppState } from '@/store/types' -import { useStore } from 'zustand' -import { createEditorStore } from '@/store/slices/editor/editor-slice-test-harness' -import { makeWorktree } from '@/store/slices/worktrees/worktree.test-fixture' -import type { OpenFile } from '@/store/slices/editor/types/open-file' -import { editorSelectionCache, editorViewStateCache, scrollTopCache } from '@/lib/scroll-cache' -import { getDefaultSettings } from '../../../../shared/constants' - -const testState = vi.hoisted(() => { - const state: { store: ReturnType | null } = { store: null } - return state -}) - -vi.mock('@/store', () => { - const getStore = () => { - if (!testState.store) { - throw new Error('Missing test store') - } - return testState.store - } - return { - useAppStore: Object.assign( - (selector: (state: AppState) => unknown) => useStore(getStore(), selector), - { - getState: () => getStore().getState() - } - ) - } -}) -vi.mock('@/lib/editor/monaco/monaco-setup', async () => { - const { loader } = await import('@monaco-editor/react') - // Why: the real setup installs this guard; without it a disposed editor's pending word - // highlight rejects with Monaco's "Canceled" and the run reports unhandled errors. - const { installMonacoDelayerCancellationGuard } = - await import('@/lib/editor/monaco/monaco-delayer-cancellation-guard') - installMonacoDelayerCancellationGuard() - loader.config({ monaco }) - return {} -}) -vi.mock('./useContextualCopySetup', () => ({ - useContextualCopySetup: () => ({ setupCopy: vi.fn(), toastNode: null }) -})) -vi.mock('../diff-comments/useDiffCommentDecorator', () => ({ - useDiffCommentDecorator: vi.fn() -})) -vi.mock('sonner', () => ({ - toast: { info: vi.fn(), error: vi.fn(), success: vi.fn() } -})) - -import MonacoEditor from './MonacoEditor' - -const originalGetContext = HTMLCanvasElement.prototype.getContext -let creationListener: monaco.IDisposable | null = null - -afterEach(() => { - cleanup() - creationListener?.dispose() - creationListener = null - for (const model of monaco.editor.getModels()) { - model.dispose() - } - editorSelectionCache.clear() - editorViewStateCache.clear() - scrollTopCache.clear() - Reflect.set(HTMLCanvasElement.prototype, 'getContext', originalGetContext) - testState.store = null -}) - -it('preserves a draft, selected range and scroll when the same tab gains a known owner', async () => { - // Only text metrics are needed; this test does not inspect canvas painting. - Reflect.set(HTMLCanvasElement.prototype, 'getContext', () => ({ - webkitBackingStorePixelRatio: 1, - measureText: (value: string) => ({ width: value.length * 8 }), - fillRect: () => {}, - clearRect: () => {}, - fillText: () => {}, - setTransform: () => {}, - save: () => {}, - restore: () => {}, - beginPath: () => {}, - moveTo: () => {}, - lineTo: () => {}, - stroke: () => {}, - createImageData: (width: number, height: number) => ({ - data: new Uint8ClampedArray(width * height * 4) - }), - getImageData: (width: number, height: number) => ({ - data: new Uint8ClampedArray(width * height * 4) - }), - putImageData: () => {} - })) - const store = createEditorStore() - testState.store = store - const file: OpenFile = { - id: 'restored-file', - worktreeId: 'pending-workspace', - filePath: '/repo/restored.txt', - relativePath: 'restored.txt', - mode: 'edit', - language: 'plaintext', - isDirty: true - } - store.setState({ - openFiles: [file], - activeWorktreeId: null, - activeWorkspaceExecutionHostId: null, - runtimeEnvironments: [], - editorFontZoomLevel: 0, - editorDrafts: { [file.id]: 'unsaved draft' }, - repos: [], - worktreesByRepo: {}, - detectedWorktreesByRepo: {}, - settings: { ...getDefaultSettings('/fixture'), theme: 'dark', editorWordWrap: false } - }) - const widgets: monaco.editor.ICodeEditor[] = [] - creationListener = monaco.editor.onDidCreateEditor((instance) => widgets.push(instance)) - const props = { - fileId: file.id, - filePath: file.filePath, - viewStateKey: 'pane:restored-file', - relativePath: file.relativePath, - language: file.language, - content: Array.from({ length: 300 }, (_, index) => `line ${index + 1}`).join('\n'), - onContentChange: vi.fn(), - onSave: vi.fn() - } - const rendered = render() - await waitFor(() => expect(widgets).toHaveLength(1)) - const before = widgets[0] - if (!before) { - throw new Error('Missing initial editor') - } - const originalModel = before.getModel() - if (!originalModel) { - throw new Error('Missing original model') - } - const draft = `${props.content}\nunsaved draft` - rendered.rerender() - expect(originalModel.getValue()).toBe(draft) - expect(originalModel.canUndo()).toBe(true) - before.layout({ width: 600, height: 300 }) - const selection = new monaco.Selection(80, 2, 82, 5) - before.setSelection(selection) - before.setScrollTop(1500) - expect(before.getScrollTop()).toBe(1500) - - const openFiles = store.getState().openFiles - act(() => { - store.setState({ - worktreesByRepo: { - repo: [ - makeWorktree({ id: file.worktreeId, repoId: 'repo', path: '/repo', hostId: 'local' }) - ] - } - }) - }) - expect(store.getState().openFiles).toBe(openFiles) - await waitFor(() => expect(widgets).toHaveLength(2)) - const after = widgets[1] - if (!after) { - throw new Error('Missing resolved editor') - } - expect(after.getModel()).not.toBe(originalModel) - expect(after.getModel()?.getValue()).toBe(draft) - expect(after.getModel()?.canUndo()).toBe(false) - await waitFor(() => expect(after.getSelection()?.toString()).toBe(selection.toString())) - expect(after.getScrollTop()).toBe(1500) - expect(props.onContentChange).not.toHaveBeenCalled() - act(() => { - after.executeEdits('user-edit', [{ range: new monaco.Range(301, 14, 301, 14), text: '!' }]) - }) - expect(props.onContentChange).toHaveBeenCalledExactlyOnceWith(`${draft}!`) -}) diff --git a/src/renderer/src/components/editor/MonacoEditor.tsx b/src/renderer/src/components/editor/MonacoEditor.tsx index 90684d7bfb8..36f00cbb099 100644 --- a/src/renderer/src/components/editor/MonacoEditor.tsx +++ b/src/renderer/src/components/editor/MonacoEditor.tsx @@ -8,7 +8,7 @@ import { monacoTypographyOptions } from '@/lib/editor/appearance/editor-typograp import { useEditorTypography } from '@/hooks/use-editor-typography' import { useContextualCopySetup } from './useContextualCopySetup' import { MonacoGutterContextMenu } from './MonacoGutterContextMenu' -import { isLinuxUserAgent } from '../../lib/user-agent-platform' +import { isLinuxUserAgent } from '../../app-shell/platform/user-agent-platform' import { MAX_TOKENIZATION_LINE_LENGTH } from '@/lib/monaco-languages/monarch-embed-entry-budget' import { buildFileEditorWordWrapOptions } from './file-editor-word-wrap-options' import { resolveFileEditorMinimapEnabled } from '../../../../shared/editor/editor-typography' diff --git a/src/renderer/src/components/editor/PanZoomImageSurface.tsx b/src/renderer/src/components/editor/PanZoomImageSurface.tsx index cfb7b833ed6..a1c6c57ca87 100644 --- a/src/renderer/src/components/editor/PanZoomImageSurface.tsx +++ b/src/renderer/src/components/editor/PanZoomImageSurface.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { PanZoomSize } from '../../../../shared/pan-zoom-view' import type { PanZoomViewport } from './use-pan-zoom-viewport' diff --git a/src/renderer/src/components/editor/PluginEditorNeedsDesktopNotice.tsx b/src/renderer/src/components/editor/PluginEditorNeedsDesktopNotice.tsx index 40c70adcb98..d7cda76d857 100644 --- a/src/renderer/src/components/editor/PluginEditorNeedsDesktopNotice.tsx +++ b/src/renderer/src/components/editor/PluginEditorNeedsDesktopNotice.tsx @@ -2,7 +2,7 @@ import { Monitor } from 'lucide-react' import { Button } from '@/components/ui/button' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { isWebClientLocation } from '@/lib/web-client-location' +import { isWebClientLocation } from '@/app-shell/platform/web-client-location' import { useFileCustomEditors } from '@/store/plugins/file-editors/plugin-custom-editors' function detail(title: string, t: Translate = translate): string { diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunCopyButton.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunCopyButton.tsx index 88711bbd39f..51ea88ef36c 100644 --- a/src/renderer/src/components/editor/check-details/panel/CheckRunCopyButton.tsx +++ b/src/renderer/src/components/editor/check-details/panel/CheckRunCopyButton.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Check, Copy } from 'lucide-react' import { useClipboardTextCopyFeedback } from '@/hooks/clipboard/use-clipboard-text-copy-feedback' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.captured-failure.test.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.captured-failure.test.tsx deleted file mode 100644 index cfc76c8a7fb..00000000000 --- a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.captured-failure.test.tsx +++ /dev/null @@ -1,164 +0,0 @@ -// @vitest-environment happy-dom -import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { TooltipProvider } from '@/components/ui/tooltip' -import { useAppStore } from '@/store/index' -import { createEditorTabsStore } from '@/store/slices/editor/editor-slice-test-harness' -import { scrollTopCache } from '@/lib/scroll-cache' -import { CheckRunDetailsPanel } from './CheckRunDetailsPanel' -import { readingCheck, readingDetails } from '../reading/check-details-reading-fixture' -import { - capturedCheckLogExcerpt, - capturedCheckLogFailure -} from '../../../right-sidebar/checks-panel/logs/check-job-log-captured-fixture' - -vi.mock('@/i18n/i18n', () => ({ - i18n: { language: 'en' }, - getIntlLocale: () => 'en', - translate: (_key: string, fallback: string, values?: Record) => - fallback.replace(/{{(\w+)}}/g, (_match, key) => String(values?.[key] ?? '')) -})) -vi.mock('@/components/sidebar/CommentMarkdown', () => ({ - default: ({ content }: { content: string }) => content -})) -vi.mock('@/components/right-sidebar/source-control/panel/source-control-fix-split-button', () => ({ - SourceControlFixSplitButton: () => null -})) -vi.mock('../actions/check-run-details-fix-with-ai', () => ({ - useCheckRunDetailsFixWithAI: () => ({ - canFixWithAI: false, - disabledReason: null, - isFixing: false, - fixPrompt: '', - repoId: null, - connectionId: null, - launchPlatform: null, - savedAgentId: null, - savedCommandInputTemplate: null, - savedAgentArgs: [], - saveLaunchActionDefault: vi.fn(), - openSourceControlAiSettings: vi.fn(), - fixWithAI: vi.fn() - }) -})) - -const original = useAppStore.getState() -const writeClipboardText = vi.fn<(text: string) => Promise>() -const fetchDetails = vi.fn() - -beforeEach(() => { - writeClipboardText.mockReset().mockResolvedValue(undefined) - fetchDetails.mockReset() - Object.assign(window, { api: { ui: { writeClipboardText } } }) - const fixture = createEditorTabsStore().getState() - useAppStore.setState({ - openFiles: [], - repos: fixture.repos, - worktreesByRepo: fixture.worktreesByRepo, - fetchPRCheckDetails: fetchDetails - }) -}) -afterEach(() => { - cleanup() - useAppStore.setState(original, true) - scrollTopCache.clear() -}) - -describe('captured failure output in the details panel', () => { - it('keeps failure context first, successes collapsed, and Copy literal without another request', async () => { - const base = readingDetails(capturedCheckLogExcerpt, 'available') - const check = { ...readingCheck, url: 'https://github.com/acme/widgets/actions/runs/1/job/42' } - const details = { - ...base, - title: 'Provider title', - summary: 'Provider-written output', - annotations: [ - { - path: 'src/example.ts', - startLine: 1, - endLine: 1, - annotationLevel: 'failure', - title: null, - message: 'Provider annotation', - rawDetails: null - } - ], - jobs: [ - { - ...base.jobs[0], - name: 'Failed fixture job', - steps: [ - { - name: 'Test shard', - status: 'completed', - conclusion: 'failure', - startedAt: null, - completedAt: null - }, - { - name: 'Setup', - status: 'completed', - conclusion: 'success', - startedAt: null, - completedAt: null - } - ] - } - ] - } - useAppStore.getState().openCheckRunDetails('wt-1', 'captured', check, { - details, - loading: false, - error: null - }) - const { container } = render( - - - - ) - const text = container.textContent ?? '' - const parts = [ - 'Failed fixture job', - 'Test shard', - 'Log excerpt', - capturedCheckLogFailure.expectedDiagnostic, - '1 succeeded', - 'Output', - 'Provider-written output', - 'Annotations', - 'Provider annotation' - ] - const positions = parts.map((part) => text.indexOf(part)) - expect( - positions.every( - (position, index) => position >= 0 && (index === 0 || position > positions[index - 1]) - ) - ).toBe(true) - const pre = container.querySelector('pre') - expect(pre?.textContent).toBe(capturedCheckLogExcerpt) - expect(pre?.childNodes).toHaveLength(1) - const disclosure = screen.getByRole('button', { name: '1 succeeded' }) - expect(disclosure.getAttribute('aria-expanded')).toBe('false') - expect(screen.queryByText('Setup')).toBeNull() - expect(screen.getByRole('button', { name: 'Open details' })).toBeTruthy() - expect(fetchDetails).not.toHaveBeenCalled() - fireEvent.click(screen.getByRole('button', { name: 'Copy log excerpt' })) - await waitFor(() => - expect(writeClipboardText).toHaveBeenCalledExactlyOnceWith(capturedCheckLogExcerpt) - ) - fireEvent.click(disclosure) - expect(screen.getByText('Setup')).toBeTruthy() - expect(container.querySelector('pre')).toBe(pre) - expect(fetchDetails).not.toHaveBeenCalled() - }) -}) diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx deleted file mode 100644 index 97036bb23ed..00000000000 --- a/src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx +++ /dev/null @@ -1,337 +0,0 @@ -// @vitest-environment happy-dom -import { StrictMode } from 'react' -import { act, cleanup, fireEvent, render, screen, within } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { TooltipProvider } from '@/components/ui/tooltip' -import { useAppStore } from '@/store/index' -import { createEditorTabsStore } from '@/store/slices/editor/editor-slice-test-harness' -import type { PRCheckRunDetails } from '../../../../../../shared/github/check-types' -import { readingCheck, readingDetails } from '../reading/check-details-reading-fixture' -import { CheckRunDetailsPanel } from './CheckRunDetailsPanel' -import { CheckRunDetails } from '@/components/right-sidebar/checks-panel/presentation/check-run-details' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' -import { - checkDetailsReadingKey, - checkJobIdentities, - checkReadingIntentKey -} from '../reading/check-details-reading-cache' -import * as diagnosticPosition from '@/components/right-sidebar/checks-panel/logs/check-log-initial-diagnostic-position' - -vi.mock('@/i18n/i18n', () => ({ - getIntlLocale: () => 'en', - i18n: { language: 'en' }, - translate: (_key: string, fallback: string, values?: Record) => - fallback.replace(/{{(\w+)}}/g, (_match, key) => String(values?.[key] ?? '')) -})) -vi.mock('@/components/sidebar/CommentMarkdown', () => ({ - default: ({ content }: { content: string }) => content -})) -vi.mock('../actions/check-run-details-fix-with-ai', () => ({ - useCheckRunDetailsFixWithAI: () => ({ canFixWithAI: false }) -})) -const original = useAppStore.getState() -const id = 'wt-1::check-details::check-run:42' -const context = 'outcome-context' -const check = { ...readingCheck, url: 'https://provider.test/job/7', workflowRunId: 55 } -const retry = vi.fn() -const copy = vi.fn().mockResolvedValue(undefined) - -function StoredPanel({ scope = 'outcome-pane' }: { scope?: string }) { - const file = useAppStore((state) => state.openFiles.find((tab) => tab.id === id)) - const state = file?.checkRunDetails - return state && file ? ( -
- { - void useAppStore.getState().reloadOpenCheckRunDetailsTab(file.id) - }} - /> -
- ) : null -} -function payload(text: string | null = null, logState: 'error' | 'available' = 'error') { - const value = readingDetails(text, logState) - return { - ...value, - startedAt: '2026-10-10T12:00:00Z', - completedAt: '2026-10-10T12:05:00Z', - jobs: [ - { - ...value.jobs[0], - startedAt: '2026-10-10T12:00:00Z', - completedAt: '2026-10-10T12:05:00Z', - steps: [ - { - name: 'setup', - status: 'completed', - conclusion: 'success', - startedAt: null, - completedAt: null - } - ] - } - ] - } -} -function open(details: PRCheckRunDetails, requestId = 0) { - useAppStore.getState().openCheckRunDetails('wt-1', context, check, { - requestId, - details, - loading: false, - error: null - }) -} -function gate() { - let resolve: (details: PRCheckRunDetails | null) => void = () => {} - let reject: (reason: Error) => void = () => {} - const promise = new Promise((done, fail) => { - resolve = done - reject = fail - }) - return { promise, resolve, reject } -} -beforeEach(() => { - const fixture = createEditorTabsStore().getState() - useAppStore.setState({ - openFiles: [], - repos: fixture.repos, - worktreesByRepo: fixture.worktreesByRepo, - fetchPRCheckDetails: vi.fn() - }) - retry.mockReset() - copy.mockClear() - scrollTopCache.clear() - Object.assign(window, { api: { ui: { writeClipboardText: copy }, shell: { openUrl: vi.fn() } } }) - vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockReturnValue(400) - vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockReturnValue(200) - vi.spyOn(HTMLElement.prototype, 'scrollHeight', 'get').mockReturnValue(2000) - vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue( - new DOMRect(0, 0, 400, 200) - ) -}) -afterEach(() => { - cleanup() - useAppStore.setState(original, true) - scrollTopCache.clear() - vi.restoreAllMocks() -}) - -describe('synthetic outcome payloads through real sidebar and header actions', () => { - it.each([false, true])( - 'retries one combined request with unchanged identities, already open=%s', - async (alreadyOpen) => { - const first = payload() - first.jobs.push({ - ...first.jobs[0], - id: 8, - name: 'retained', - logState: 'available', - logTail: 'Error: retained synthetic excerpt' - }) - const next = { - ...first, - jobs: first.jobs.map((job) => ({ - ...job, - logState: 'available' as const, - logTail: job.logTail ?? 'Error: recovered synthetic excerpt' - })) - } - const pending = gate() - const fetch = vi.fn(() => pending.promise) - useAppStore.setState({ fetchPRCheckDetails: fetch }) - if (alreadyOpen) { - open(first) - } - render( - - - - - ) - expect( - screen.getByText("Couldn't load logs. Open View full details, then use Refresh to retry.") - ).toBeTruthy() - expect(screen.queryByRole('button', { name: 'Retry' })).toBeNull() - fireEvent.click(screen.getByRole('button', { name: 'View full details' })) - expect(useAppStore.getState().openFiles[0].checkRunDetails?.details).toBe(first) - expect(fetch).not.toHaveBeenCalled() - const panel = screen.getByTestId('outcome-pane') - const pre = panel.querySelector('pre') - const outer = panel.querySelector('[aria-busy].overflow-y-auto') - if (!(pre instanceof HTMLPreElement) || !(outer instanceof HTMLDivElement)) { - throw new Error('Missing retained reading nodes') - } - fireEvent.wheel(pre) - pre.scrollTop = 180 - fireEvent.scroll(pre) - outer.scrollTop = 320 - fireEvent.scroll(outer) - const geometry = { excerpt: pre.getBoundingClientRect().top, height: outer.clientHeight } - await act(async () => { - fireEvent.click(within(panel).getByRole('button', { name: 'Copy log excerpt' })) - }) - expect(copy).toHaveBeenCalledExactlyOnceWith(first.jobs[1].logTail) - expect(fetch).not.toHaveBeenCalled() - fireEvent.click(within(panel).getByRole('button', { name: 'Refresh' })) - expect(fetch).toHaveBeenCalledTimes(1) - expect(fetch).toHaveBeenCalledWith( - '/repo', - { checkRunId: 42, workflowRunId: 55, checkName: 'verify', url: check.url, prRepo: null }, - { repoId: 'repo-1', repoOwnerExecutionHostId: 'local' } - ) - const during = useAppStore.getState().openFiles[0].checkRunDetails - const positions = [...scrollTopCache] - fireEvent.click(screen.getByRole('button', { name: 'View full details' })) - act(() => { - useAppStore.getState().patchOpenCheckRunDetails('wt-1', context, check, { - requestId: 0, - details: first, - loading: false, - error: null - }) - }) - expect(useAppStore.getState().openFiles[0].checkRunDetails).toBe(during) - expect([...scrollTopCache]).toEqual(positions) - expect(panel.querySelector('pre')).toBe(pre) - expect(pre.scrollTop).toBe(180) - expect(outer.scrollTop).toBe(320) - expect(within(panel).getByText("Couldn't load logs. Use Refresh to retry.")).toBeTruthy() - await act(async () => pending.resolve(next)) - expect(useAppStore.getState().openFiles[0].checkRunDetails?.details).toBe(next) - expect(panel.querySelectorAll('pre')[1]).toBe(pre) - expect(pre.scrollTop).toBe(180) - expect(outer.scrollTop).toBe(320) - expect({ excerpt: pre.getBoundingClientRect().top, height: outer.clientHeight }).toEqual( - geometry - ) - expect(within(panel).queryByText("Couldn't load logs. Use Refresh to retry.")).toBeNull() - expect(within(panel).getByRole('button', { name: 'Open details' })).toBeTruthy() - const accepted = useAppStore.getState().openFiles[0].checkRunDetails - const saved = [...scrollTopCache] - fireEvent.click(screen.getByRole('button', { name: 'View full details' })) - expect(useAppStore.getState().openFiles[0].checkRunDetails).toBe(accepted) - expect([...scrollTopCache]).toEqual(saved) - expect(fetch).toHaveBeenCalledTimes(1) - expect(retry).not.toHaveBeenCalled() - expect(next.jobs.map(({ logState: _state, logTail: _tail, ...job }) => job)).toEqual( - first.jobs.map(({ logState: _state, logTail: _tail, ...job }) => job) - ) - } - ) - - it.each(['untouched', 'outer wheel', 'outer scroll', 'steps', 'evicted'])( - 'allows error-only first text exactly once when intent survives %s', - async (interaction) => { - const first = payload() - const next = payload( - 'prefix\nError: recovered synthetic diagnostic\n at synthetic.ts:1:2', - 'available' - ) - const pending = gate() - const fetch = vi.fn(() => pending.promise) - useAppStore.setState({ fetchPRCheckDetails: fetch }) - open(first) - const marker = vi.spyOn(diagnosticPosition, 'findInitialCheckLogDiagnostic') - const measure = vi - .spyOn(diagnosticPosition, 'measureInitialCheckLogScrollTop') - .mockReturnValue(600) - const { unmount } = render( - - - - - - ) - const panel = screen.getByTestId('outcome-pane') - const outer = panel.querySelector('[aria-busy].overflow-y-auto') - if (!(outer instanceof HTMLDivElement)) { - throw new Error('Missing outer scroller') - } - const identity = checkJobIdentities(first.jobs)[0].identity - const owner = checkDetailsReadingKey(id, 'outcome-pane', context) - expect(scrollTopCache.get(checkReadingIntentKey(owner, identity))).toBe(-1) - expect( - scrollTopCache.has( - checkReadingIntentKey(checkDetailsReadingKey(id, null, context), identity) - ) - ).toBe(false) - if (interaction === 'outer wheel') { - fireEvent.wheel(outer) - } - if (interaction === 'outer scroll') { - outer.scrollTop = 320 - fireEvent.scroll(outer) - } - if (interaction === 'steps') { - fireEvent.click(within(panel).getByRole('button', { name: '1 succeeded' })) - } - if (interaction === 'evicted') { - for (let index = 0; index < 21; index++) { - setWithLRU(scrollTopCache, `ordinary-${index}`, index) - } - } - fireEvent.click(within(panel).getByRole('button', { name: 'Refresh' })) - expect(panel.querySelector('pre')).toBeNull() - await act(async () => pending.resolve(next)) - expect(marker).toHaveBeenCalledTimes(interaction === 'untouched' ? 1 : 0) - expect(measure).toHaveBeenCalledTimes(interaction === 'untouched' ? 1 : 0) - expect(panel.querySelector('pre')?.scrollTop).toBe(interaction === 'untouched' ? 600 : 0) - expect(fetch).toHaveBeenCalledTimes(1) - unmount() - render( - - - - ) - expect(marker).toHaveBeenCalledTimes(interaction === 'untouched' ? 1 : 0) - expect(fetch).toHaveBeenCalledTimes(1) - } - ) - - it('claims an error-only intent in one pane and cancels only the interacted job', async () => { - const first = payload() - first.jobs.push({ ...first.jobs[0], id: 8, name: 'other-error' }) - const pending = gate() - useAppStore.setState({ fetchPRCheckDetails: vi.fn(() => pending.promise) }) - open(first) - const marker = vi.spyOn(diagnosticPosition, 'findInitialCheckLogDiagnostic') - vi.spyOn(diagnosticPosition, 'measureInitialCheckLogScrollTop').mockReturnValue(600) - render( - - - - - ) - const left = screen.getByTestId('left') - const right = screen.getByTestId('right') - fireEvent.click(within(left).getAllByRole('button', { name: '1 succeeded' })[0]) - fireEvent.click(within(right).getByRole('button', { name: 'Refresh' })) - const next = { - ...first, - jobs: first.jobs.map((job) => ({ - ...job, - logState: 'available' as const, - logTail: `Error: synthetic ${job.name}` - })) - } - await act(async () => pending.resolve(next)) - expect(marker).toHaveBeenCalledTimes(1) - expect([...left.querySelectorAll('pre')].map((pre) => pre.scrollTop)).toEqual([0, 600]) - expect([...right.querySelectorAll('pre')].map((pre) => pre.scrollTop)).toEqual([0, 0]) - }) -}) diff --git a/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx b/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx index 92094b986ab..dabe9cce32e 100644 --- a/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx +++ b/src/renderer/src/components/editor/check-details/panel/CheckRunJobs.tsx @@ -11,7 +11,7 @@ import { isGitLabAllowedFailureJob } from '@/components/github/pr-check-presenta import { CHECK_COLOR } from '@/components/right-sidebar/checks-panel/presentation/check-presentation' import { CheckJobLogTail } from '@/components/right-sidebar/checks-panel/logs/check-job-log-tail' import { useTranslate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { PRCheckDetail, PRCheckJob, @@ -27,7 +27,10 @@ import { CheckRunCopyButton } from './CheckRunCopyButton' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { checkJobIdentities, checkReadingStepsKey } from '../reading/check-details-reading-cache' import type { CheckDetailsReadingOwner } from '../reading/check-details-reading-owner' diff --git a/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts b/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts index 67a3d62dcdf..7d66f3b1106 100644 --- a/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts +++ b/src/renderer/src/components/editor/check-details/reading/check-details-reading-cache.ts @@ -1,6 +1,9 @@ import type { PRCheckJob, PRCheckRunDetails } from '../../../../../../shared/github/check-types' import type { OpenCheckRunDetailsState } from '../../../../../../shared/editor-tab-view-types' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { sha256 } from '../../../../../../shared/sha256' import { resolveStepOutcome } from '../../../../../../shared/source-control/checks/pr-check-step-status' diff --git a/src/renderer/src/components/editor/check-details/reading/check-details-reading-owner.ts b/src/renderer/src/components/editor/check-details/reading/check-details-reading-owner.ts index 4dd3ddd9326..040c5a226a7 100644 --- a/src/renderer/src/components/editor/check-details/reading/check-details-reading-owner.ts +++ b/src/renderer/src/components/editor/check-details/reading/check-details-reading-owner.ts @@ -1,6 +1,9 @@ import type { OpenFile } from '@/store/slices/editor/types/open-file' import { resolveStepOutcome } from '../../../../../../shared/source-control/checks/pr-check-step-status' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { cancelCheckReadingIntents, checkDetailsReadingKey, diff --git a/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts b/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts deleted file mode 100644 index 40013f2e4b2..00000000000 --- a/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -// @vitest-environment happy-dom -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' -import { - checkLogDigest, - checkReadingLogKey, - checkReadingOuterKey -} from './check-details-reading-cache' - -import { attachCheckReadingViewport } from './check-details-reading-viewport' -import { - measuredReadingNode, - readingDetails, - readingFixture -} from './check-details-reading-fixture' -import * as digest from '../../../../../../shared/sha256' - -const frames = new Map() -let nextFrame = 0 -beforeEach(() => { - scrollTopCache.clear() - nextFrame = 0 - vi.stubGlobal( - 'requestAnimationFrame', - vi.fn((callback: FrameRequestCallback) => { - frames.set(++nextFrame, callback) - return nextFrame - }) - ) - vi.stubGlobal( - 'cancelAnimationFrame', - vi.fn((id: number) => frames.delete(id)) - ) -}) -afterEach(() => { - frames.clear() - document.body.replaceChildren() - vi.restoreAllMocks() - vi.unstubAllGlobals() -}) -function flushFrames(): void { - const pending = [...frames.values()] - frames.clear() - for (const callback of pending) { - callback(0) - } -} - -describe('owned reading positions', () => { - it('retirement cancels a pending frame and rejects a captured callback after close and reopen', () => { - const { owner, store, file, logKey, text, identity } = readingFixture() - const node = measuredReadingNode(text, 0) - setWithLRU(scrollTopCache, logKey, 180) - const detachOwner = owner.attach() - const detach = attachCheckReadingViewport({ - node, - owner, - cacheKey: logKey, - job: identity, - text - }) - const callback = [...frames.values()][0] - store.setState({ openFiles: [] }) - expect(frames.size).toBe(0) - scrollTopCache.delete(logKey) - store.setState({ openFiles: [file] }) - Object.defineProperty(node, 'clientHeight', { configurable: true, value: 200 }) - callback(0) - node.dispatchEvent(new Event('scroll')) - detach() - detachOwner() - expect(scrollTopCache.has(logKey)).toBe(false) - }) - it.each(['outer', 'inner'])( - 'preserves retained %s target through zero layout and a real remount', - (kind) => { - const { owner, logKey, text, identity } = readingFixture() - const key = kind === 'outer' ? checkReadingOuterKey(owner.key) : logKey - const node = measuredReadingNode(kind === 'outer' ? '' : text) - Object.defineProperty(node, 'clientHeight', { configurable: true, value: 0 }) - setWithLRU(scrollTopCache, key, 320) - const detachOwner = owner.attach() - const options = { - node, - owner, - cacheKey: key, - ...(kind === 'inner' ? { job: identity, text } : {}) - } - const detach = attachCheckReadingViewport(options) - flushFrames() - Object.defineProperty(node, 'clientHeight', { configurable: true, value: 200 }) - node.dispatchEvent(new Event('scroll')) - detach() - expect(scrollTopCache.get(key)).toBe(320) - const remount = attachCheckReadingViewport(options) - expect(node.scrollTop).toBe(320) - remount() - detachOwner() - } - ) - - it('fences callbacks published before the observer and never rewinds on a reentrant notification', () => { - const { owner, store, file, logKey, text, identity } = readingFixture() - const node = measuredReadingNode(text) - let nested = false - const before = store.subscribe(() => { - node.scrollTop = 999 - node.dispatchEvent(new Event('scroll')) - if (!nested) { - nested = true - store.setState({ openFiles: [] }) - } - }) - const detachOwner = owner.attach() - const detach = attachCheckReadingViewport({ - node, - owner, - cacheKey: logKey, - job: identity, - text - }) - const writes = vi.spyOn(scrollTopCache, 'set') - store.setState({ openFiles: [{ ...file, isDirty: true }] }) - expect(writes).not.toHaveBeenCalled() - before() - scrollTopCache.delete(logKey) - store.setState({ openFiles: [file] }) - node.dispatchEvent(new Event('scroll')) - detach() - expect(scrollTopCache.has(logKey)).toBe(false) - detachOwner() - }) - - it('retires a changed digest before handoff and prevents queued old callbacks from recreating it', () => { - const { owner, store, file, logKey, text, identity } = readingFixture() - const node = measuredReadingNode(text) - const detachOwner = owner.attach() - const detach = attachCheckReadingViewport({ - node, - owner, - cacheKey: logKey, - job: identity, - text - }) - node.scrollTop = 180 - node.dispatchEvent(new Event('scroll')) - const details = readingDetails('Error: accepted new text') - const next = { ...file.checkRunDetails!, details } - store.setState({ openFiles: [{ ...file, checkRunDetails: next }] }) - const destination = checkReadingLogKey( - owner.key, - identity, - checkLogDigest(details.jobs[0].logTail ?? '') - ) - expect(scrollTopCache.get(destination)).toBe(180) - node.scrollTop = 900 - node.dispatchEvent(new Event('scroll')) - detach() - flushFrames() - expect(scrollTopCache.has(logKey)).toBe(false) - expect(scrollTopCache.get(destination)).toBe(180) - detachOwner() - }) - - it('does no hashing, marker work, Range work or frame scheduling during 100 scroll/unrelated notifications', () => { - const { owner, store, logKey, text, identity } = readingFixture() - const node = measuredReadingNode(text) - const add = vi.spyOn(node, 'addEventListener') - const remove = vi.spyOn(node, 'removeEventListener') - const detachOwner = owner.attach() - const detach = attachCheckReadingViewport({ - node, - owner, - cacheKey: logKey, - job: identity, - text - }) - const hash = vi.spyOn(digest, 'sha256') - const range = vi.spyOn(document, 'createRange') - for (let index = 1; index <= 100; index++) { - node.scrollTop = index - node.dispatchEvent(new Event('scroll')) - store.setState({ unrelated: index }) - } - expect(scrollTopCache.get(logKey)).toBe(100) - expect(hash).not.toHaveBeenCalled() - expect(range).not.toHaveBeenCalled() - expect(requestAnimationFrame).not.toHaveBeenCalled() - detach() - detachOwner() - expect(remove.mock.calls.map(([name]) => name)).toEqual(add.mock.calls.map(([name]) => name)) - }) -}) diff --git a/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.ts b/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.ts index 0b7c84b7b72..7c4d436c785 100644 --- a/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.ts +++ b/src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.ts @@ -1,4 +1,7 @@ -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { hasCheckReadingLayout } from '@/components/right-sidebar/checks-panel/logs/check-log-initial-diagnostic-position' import type { CheckDetailsReadingOwner } from './check-details-reading-owner' diff --git a/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-row.tsx b/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-row.tsx index e82211d0a6c..8a21ac07e1a 100644 --- a/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-row.tsx +++ b/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-row.tsx @@ -5,7 +5,7 @@ import { STATUS_LABELS } from '../../../../../../shared/git/status/git-status-la import type { SourceControlTreeNode } from '../../../../../../shared/source-control/listing/source-control-tree' import { FileTypeIcon, FolderIcon } from '@/components/FileTypeIcon' import { basename, dirname, joinPath } from '@/lib/path' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { WORKSPACE_FILE_PATH_MIME } from '@/lib/files/drop/workspace-file-drag' import { writeWorkspaceFileDragSourceForWorkspace } from '@/lib/files/drop/workspace-file-drag-source' import type { GitBranchChangeEntry } from '../../../../../../shared/git/diff/git-diff-compare-types' diff --git a/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree.tsx b/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree.tsx index 83c825bbe3d..5786cde7e40 100644 --- a/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree.tsx +++ b/src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Check, Filter, PanelLeftClose, Search } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' diff --git a/src/renderer/src/components/editor/diff/large-files/ImageDiffViewer.tsx b/src/renderer/src/components/editor/diff/large-files/ImageDiffViewer.tsx index 22d3f54e690..9029cc1154a 100644 --- a/src/renderer/src/components/editor/diff/large-files/ImageDiffViewer.tsx +++ b/src/renderer/src/components/editor/diff/large-files/ImageDiffViewer.tsx @@ -1,5 +1,5 @@ import type { JSX } from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import ImageViewer from '../../ImageViewer' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/editor/diff/monaco-view/DiffViewer.tsx b/src/renderer/src/components/editor/diff/monaco-view/DiffViewer.tsx index 9fb9bf3ab77..9af43207c3e 100644 --- a/src/renderer/src/components/editor/diff/monaco-view/DiffViewer.tsx +++ b/src/renderer/src/components/editor/diff/monaco-view/DiffViewer.tsx @@ -4,7 +4,10 @@ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSta import { DiffEditor, type DiffOnMount } from '@monaco-editor/react' import type { editor } from 'monaco-editor' import { useAppStore } from '@/store/index' -import { diffViewStateCache, setWithLRU } from '@/lib/scroll-cache' +import { + diffViewStateCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { monacoTypographyOptions } from '@/lib/editor/appearance/editor-typography' import { useEditorTypography } from '@/hooks/use-editor-typography' import { useContextualCopySetup } from '../../useContextualCopySetup' @@ -38,7 +41,7 @@ import { applyModelLanguage } from '@/lib/monaco-languages/plugin-syntax-editor-options' import { useMonacoTheme } from '@/hooks/use-monaco-theme' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { wholeFileDiffTintClass } from './whole-file-diff-tint' export default function DiffViewer({ diff --git a/src/renderer/src/components/editor/diff/monaco-view/useDiffViewerFirstChangeAutoScroll.ts b/src/renderer/src/components/editor/diff/monaco-view/useDiffViewerFirstChangeAutoScroll.ts index 4fcb253b8dc..daae50f4837 100644 --- a/src/renderer/src/components/editor/diff/monaco-view/useDiffViewerFirstChangeAutoScroll.ts +++ b/src/renderer/src/components/editor/diff/monaco-view/useDiffViewerFirstChangeAutoScroll.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react' import type { RefObject } from 'react' import type { editor } from 'monaco-editor' -import { diffViewStateCache } from '@/lib/scroll-cache' +import { diffViewStateCache } from '@/components/editor/model-lifetime/retention/editor-view-state-cache' type DiffViewerFirstChangeAutoScrollInput = { diffEditorRef: RefObject diff --git a/src/renderer/src/components/editor/diff/sections/DiffSectionBody.tsx b/src/renderer/src/components/editor/diff/sections/DiffSectionBody.tsx index fd9801a52e6..b0eec634f4c 100644 --- a/src/renderer/src/components/editor/diff/sections/DiffSectionBody.tsx +++ b/src/renderer/src/components/editor/diff/sections/DiffSectionBody.tsx @@ -1,10 +1,10 @@ import { DiffTooLargeNotice } from '../large-files/DiffTooLargeNotice' import { Suspense, useEffect, useLayoutEffect, useMemo, useRef } from 'react' import type { editor } from 'monaco-editor' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { AlertCircle, RefreshCw } from 'lucide-react' import { DiffEditor, type DiffOnMount } from '@monaco-editor/react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { Button } from '@/components/ui/button' import { combinedDiffSectionScrollbarOptions } from '../monaco-view/diff-editor-scrollbar-options' import { isCombinedDiffSizeUnknown } from '../../combined-diff/load-sections/combined-diff-on-demand-load' diff --git a/src/renderer/src/components/editor/editor-lazy-views.ts b/src/renderer/src/components/editor/editor-lazy-views.ts index 1d256cec6fa..73971845fb4 100644 --- a/src/renderer/src/components/editor/editor-lazy-views.ts +++ b/src/renderer/src/components/editor/editor-lazy-views.ts @@ -1,4 +1,4 @@ -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' // Share lazy component identities across surfaces; a second lazy wrapper over the same module causes separate boundaries and remounts. export const MonacoEditor = lazy(() => import('./MonacoEditor')) diff --git a/src/renderer/src/components/editor/external-changes/ExternalFileChangeCompareDialog.tsx b/src/renderer/src/components/editor/external-changes/ExternalFileChangeCompareDialog.tsx index 1ba9c61bfe4..77a10ab494e 100644 --- a/src/renderer/src/components/editor/external-changes/ExternalFileChangeCompareDialog.tsx +++ b/src/renderer/src/components/editor/external-changes/ExternalFileChangeCompareDialog.tsx @@ -9,7 +9,7 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry' +import { lazyWithRetry as lazy } from '@/app-shell/startup/lazy-with-retry' import { getConnectionIdForFile } from '@/lib/workspace/ownership/connection-context' import { editorTabFileAccess } from '@/lib/files/access/local-file-access' import { detectLanguage } from '../../../../../shared/editor-language/editor-language-detect' diff --git a/src/renderer/src/components/editor/external-changes/editor-external-change-telemetry.ts b/src/renderer/src/components/editor/external-changes/editor-external-change-telemetry.ts index 88770dbe900..35f6d88d50c 100644 --- a/src/renderer/src/components/editor/external-changes/editor-external-change-telemetry.ts +++ b/src/renderer/src/components/editor/external-changes/editor-external-change-telemetry.ts @@ -1,5 +1,5 @@ // Share path-free conflict enums across marking and banner actions so false-banner rates remain comparable by transport. -import { track } from '@/lib/telemetry' +import { track } from '@/diagnostics/telemetry/telemetry' import { getConnectionIdForFile } from '@/lib/workspace/ownership/connection-context' import type { OpenFile } from '@/store/slices/editor/types/open-file' diff --git a/src/renderer/src/components/editor/git-graph/git-graph-commit-action-copy.ts b/src/renderer/src/components/editor/git-graph/git-graph-commit-action-copy.ts index c2d33d145d7..7581c44fda4 100644 --- a/src/renderer/src/components/editor/git-graph/git-graph-commit-action-copy.ts +++ b/src/renderer/src/components/editor/git-graph/git-graph-commit-action-copy.ts @@ -1,5 +1,5 @@ import { translate } from '@/i18n/i18n' -import { displayIpcErrorMessage } from '@/lib/ipc-error' +import { displayIpcErrorMessage } from '@/runtime/errors/ipc-error' import type { Translate } from '@/i18n/use-translate' import type { GitCommitActionResult } from '../../../../../shared/git/commands/git-commit-action' diff --git a/src/renderer/src/components/editor/git-graph/git-graph-ref-menu.tsx b/src/renderer/src/components/editor/git-graph/git-graph-ref-menu.tsx index 428bec78c9e..30ada310811 100644 --- a/src/renderer/src/components/editor/git-graph/git-graph-ref-menu.tsx +++ b/src/renderer/src/components/editor/git-graph/git-graph-ref-menu.tsx @@ -11,14 +11,14 @@ import { } from '@/components/ui/context-menu' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { selectWorktreesByRepo } from '@/store/workspaces/catalog/workspace-record-fields' import type { gitGraphScope } from '@/lib/source-control/graph/git-graph-tab' import { gitGraphWorkspaceCatalog } from './git-graph-workspace-navigation' import { useTranslate } from '@/i18n/use-translate' import type { GitHistoryItemRef } from '../../../../../shared/git/history/git-history-types' import type { Worktree } from '../../../../../shared/worktree/types' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' export function useGitGraphWorkspaces(scope: ReturnType) { const worktrees = useAppStore(selectWorktreesByRepo) diff --git a/src/renderer/src/components/editor/git-graph/git-graph-tab.tsx b/src/renderer/src/components/editor/git-graph/git-graph-tab.tsx index 8e3abc51ba8..4e581dd97fc 100644 --- a/src/renderer/src/components/editor/git-graph/git-graph-tab.tsx +++ b/src/renderer/src/components/editor/git-graph/git-graph-tab.tsx @@ -22,7 +22,7 @@ import { useGitGraphHistory, gitGraphFilterSupported } from './use-git-graph-his import { GitGraphFilter } from './git-graph-filter' import { GitGraphDetails } from './git-graph-details' import { GitGraphRefs, useGitGraphWorkspaces } from './git-graph-ref-menu' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' const sameGroup = () => undefined diff --git a/src/renderer/src/components/editor/markdown-preview/viewport/VirtualMarkdownPreviewBody.tsx b/src/renderer/src/components/editor/markdown-preview/viewport/VirtualMarkdownPreviewBody.tsx index fb3a3ab6b70..6cfadb88ac5 100644 --- a/src/renderer/src/components/editor/markdown-preview/viewport/VirtualMarkdownPreviewBody.tsx +++ b/src/renderer/src/components/editor/markdown-preview/viewport/VirtualMarkdownPreviewBody.tsx @@ -12,7 +12,7 @@ import type { Components } from 'react-markdown' import { createProgrammaticScrollMarks } from '@/hooks/programmatic-scroll-marks' import { useTranslate } from '@/i18n/use-translate' import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision' -import { scrollTopCache } from '@/lib/scroll-cache' +import { scrollTopCache } from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { renderMarkdownPreviewTree } from '../render-tree/markdown-preview-render-tree' import { scrollMarkdownPreviewVirtualizer } from './markdown-preview-anchor-navigation' import type { MarkdownPreviewSearchInstance } from '../search/markdown-preview-search' diff --git a/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-anchor.ts b/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-anchor.ts index b167962df8b..2cb2162019a 100644 --- a/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-anchor.ts +++ b/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-anchor.ts @@ -5,7 +5,10 @@ import { type VirtualizedScrollAnchor } from '@/hooks/useVirtualizedScrollAnchor' import type { ProgrammaticScrollMarks } from '@/hooks/programmatic-scroll-marks' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import type { MarkdownPreviewBlock } from '../document-worker/markdown-preview-document-types' const anchors = new Map() diff --git a/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-viewport.ts b/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-viewport.ts index f84e079b797..befb6d9879b 100644 --- a/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-viewport.ts +++ b/src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-viewport.ts @@ -1,5 +1,8 @@ import { useLayoutEffect } from 'react' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import type { MarkdownPreviewFoundation } from '../surface/use-markdown-preview-foundation' export function useMarkdownPreviewScrollViewport({ diff --git a/src/renderer/src/components/editor/markdown/navigation/MarkdownTableOfContentsRow.tsx b/src/renderer/src/components/editor/markdown/navigation/MarkdownTableOfContentsRow.tsx index 68fc6bb4af8..a3bb3b562bc 100644 --- a/src/renderer/src/components/editor/markdown/navigation/MarkdownTableOfContentsRow.tsx +++ b/src/renderer/src/components/editor/markdown/navigation/MarkdownTableOfContentsRow.tsx @@ -1,5 +1,5 @@ import { ChevronRight } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { useTranslate } from '@/i18n/use-translate' import type { MarkdownTocItem } from './markdown-table-of-contents' import { isMarkdownTocItemExpanded } from './markdown-toc-collapse-state' diff --git a/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-lifetime.test.ts b/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-lifetime.test.ts index 770bebbe8de..7a6a5186e7c 100644 --- a/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-lifetime.test.ts +++ b/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-lifetime.test.ts @@ -2,7 +2,6 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import { afterEach, expect, it, vi } from 'vitest' import { - attachModelLifetimeView, createModelLifetimeFixture, modelLifetimeFile, modelLifetimeTextModel, @@ -13,7 +12,11 @@ import { getDiffViewerMonacoModelPaths, rememberDiffViewerModelPaths } from '../../diff/monaco-view/diff-monaco-model-disposal' -import { scrollTopCache, diffViewStateCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + diffViewStateCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { checkDetailsReadingKey, @@ -26,38 +29,6 @@ import { vi.mock('sonner', () => ({ toast: { info: vi.fn(), error: vi.fn(), success: vi.fn() } })) afterEach(resetModelLifetimeFixtures) -it('waits beyond the real detach stack and preserves its bounded scroll snapshot', async () => { - const { store, attach, add } = createModelLifetimeFixture() - const { file, model } = add('attached') - const detachView = attachModelLifetimeView(model) - attach() - store.getState().closeFile(file.id) - await Promise.resolve() - expect(model.isDisposed()).toBe(false) - scrollTopCache.set(file.filePath, 42) - detachView() - expect(model.isDisposed()).toBe(false) - await Promise.resolve() - expect(model.isDisposed()).toBe(true) - expect(scrollTopCache.get(file.filePath)).toBe(42) -}) - -it('protects a reopened URI owner from a pending detach callback', async () => { - const { store, attach, add } = createModelLifetimeFixture() - const { file, model } = add('reopened-old') - const detachView = attachModelLifetimeView(model) - attach() - store.getState().closeFile(file.id) - await Promise.resolve() - store.setState({ openFiles: [{ ...file, id: 'reopened-new' }] }) - detachView() - await Promise.resolve() - expect(model.isDisposed()).toBe(false) - store.getState().closeFile('reopened-new') - await Promise.resolve() - expect(model.isDisposed()).toBe(true) -}) - it('does not replay earlier model-disposal authority after first registry registration', async () => { const { store, bridge, attach } = createModelLifetimeFixture(false) const file = modelLifetimeFile('unloaded') @@ -95,36 +66,6 @@ it('does not let a stale HMR unregister clear a successor registration', async ( } }) -it('releases generated closed diff namespaces after detachment and preserves sibling prefixes', async () => { - const { store, attach } = createModelLifetimeFixture() - const file = modelLifetimeFile('diff-a', 'diff') - const sibling = modelLifetimeFile('diff-a-longer', 'diff') - const paths = getDiffViewerMonacoModelPaths({ - modelKey: file.id, - generationSuffix: ':large-diff-generation:2' - }) - const siblingPaths = getDiffViewerMonacoModelPaths({ - modelKey: sibling.id, - generationSuffix: '' - }) - const original = modelLifetimeTextModel(paths.originalModelPath) - const modified = modelLifetimeTextModel(paths.modifiedModelPath) - const live = modelLifetimeTextModel(siblingPaths.originalModelPath) - const detachView = attachModelLifetimeView(original) - store.setState({ openFiles: [file, sibling] }) - attach() - store.getState().closeFile(file.id) - await Promise.resolve() - expect(original.isDisposed()).toBe(false) - expect(modified.isDisposed()).toBe(true) - expect(live.isDisposed()).toBe(false) - detachView() - expect(original.isDisposed()).toBe(false) - await Promise.resolve() - expect(original.isDisposed()).toBe(true) - expect(live.isDisposed()).toBe(false) -}) - it('preserves queued model custody across registry removal and replacement', async () => { const { store, bridge, attach, add } = createModelLifetimeFixture(false) const unregister = bridge.register(monaco) @@ -143,27 +84,6 @@ it('preserves queued model custody across registry removal and replacement', asy } }) -it('preserves retained attachment custody across registry replacement', async () => { - const { store, bridge, attach, add } = createModelLifetimeFixture(false) - const unregister = bridge.register(monaco) - const { file, model } = add('attached-replacement') - const detachView = attachModelLifetimeView(model) - attach() - store.getState().closeFile(file.id) - await Promise.resolve() - unregister() - detachView() - await Promise.resolve() - expect(model.isDisposed()).toBe(false) - const unregisterNext = bridge.register(monaco) - try { - await Promise.resolve() - expect(model.isDisposed()).toBe(true) - } finally { - unregisterNext() - } -}) - it('preserves successor models at a captured URI across registry replacement', async () => { const { store, bridge, attach, add } = createModelLifetimeFixture(false) const unregister = bridge.register(monaco) @@ -309,18 +229,6 @@ it('sweeps never-mounted close-all and same-ID mode replacements without Monaco' expect(scrollTopCache.size).toBe(0) }) -it('spares a check ID reopened before the pending flush', async () => { - const { store, attach } = createModelLifetimeFixture(false) - const file = modelLifetimeFile('reopened', 'check-details') - store.setState({ openFiles: [file] }) - attach() - seed(file.id) - store.setState({ openFiles: [] }) - store.setState({ openFiles: [file] }) - await Promise.resolve() - expect(scrollTopCache.size).toBe(5) -}) - it('releases tab-scoped diff models when their file closes', async () => { const { store, attach } = createModelLifetimeFixture() const file = modelLifetimeFile('scoped-diff', 'diff') diff --git a/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-tab-disposal.ts b/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-tab-disposal.ts index 8092e8b1d4b..ef28c1b15be 100644 --- a/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-tab-disposal.ts +++ b/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-tab-disposal.ts @@ -1,5 +1,5 @@ import type { OpenFile } from '@/store/slices/editor/types/open-file' -import { scrollTopCache } from '@/lib/scroll-cache' +import { scrollTopCache } from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { disposeUnattachedMonacoModelsByPathPrefixes, forgetDiffViewerModelPaths, diff --git a/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-view-state-retention.test.ts b/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-view-state-retention.test.ts index 22c96ca76ab..1ca1c10b406 100644 --- a/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-view-state-retention.test.ts +++ b/src/renderer/src/components/editor/model-lifetime/retention/closed-editor-view-state-retention.test.ts @@ -6,7 +6,11 @@ import type { OpenFile } from '@/store/slices/editor/types/open-file' import { createEditorModelRegistry } from '@/lib/editor/monaco/editor-model-registry' import { attachClosedEditorTabCleanup } from '@/components/editor/model-lifetime/retention/closed-editor-tab-controller' import { toEditorModelUri } from '@/components/editor/model-lifetime/identity/editor-model-uri' -import { scrollTopCache, editorSelectionCache, pdfViewPositionCache } from '@/lib/scroll-cache' +import { + scrollTopCache, + editorSelectionCache, + pdfViewPositionCache +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' const disposeOwners: (() => void)[] = [] afterEach(() => { @@ -21,65 +25,6 @@ afterEach(() => { pdfViewPositionCache.clear() }) -function open(path: string, text = 'original') { - const file: OpenFile = { - id: path, - worktreeId: 'fixture', - filePath: path, - relativePath: path, - mode: 'edit', - language: 'plaintext', - isDirty: false - } - // Match the React wrapper’s path-to-Uri.parse conversion. - const model = monaco.editor.createModel( - text, - 'plaintext', - monaco.Uri.parse(toEditorModelUri(path)) - ) - const store = createStore(() => ({ - openFiles: [file], - worktreesByRepo: { fixture: [{ id: 'fixture', repoId: 'fixture', hostId: 'local' as const }] } - })) - const bridge = createEditorModelRegistry() - disposeOwners.push(bridge.register(monaco), attachClosedEditorTabCleanup(store, bridge)) - return { - model, - close: async () => { - store.setState({ openFiles: [] }) - await Promise.resolve() - expect(model.isDisposed()).toBe(true) - } - } -} - -it.each([ - '/tradeoff/undo-posix.txt', - 'file:///C:/tradeoff/undo-file-uri.txt', - 'C:/tradeoff/undo-raw-drive.txt', - 'C:\\tradeoff\\undo-raw-drive-backslash.txt', - '\\\\server\\share\\undo-unc.txt' -] as const)('restores closed-file undo after reopening %s', async (path) => { - const owner = open(path) - owner.model.pushEditOperations( - [], - [{ range: new monaco.Range(1, 1, 1, 1), text: 'saved ' }], - () => null - ) - owner.model.pushStackElement() - expect(owner.model.canUndo()).toBe(true) - const content = owner.model.getValue() - await owner.close() - const reopened = monaco.editor.createModel( - content, - 'plaintext', - monaco.Uri.parse(toEditorModelUri(path)) - ) - expect(reopened.canUndo()).toBe(true) - await reopened.undo() - expect(reopened.getValue()).toBe('original') -}) - // Disposal and ownership lookup must normalize Windows paths identically or one close can destroy another tab’s model. it('keeps a Windows-path model that a second open tab still owns', async () => { const filePath = 'C:\\repo\\shared.ts' diff --git a/src/renderer/src/components/editor/model-lifetime/retention/editor-model-lifetime-fixture.ts b/src/renderer/src/components/editor/model-lifetime/retention/editor-model-lifetime-fixture.ts index c1564d6759e..c31c32ad554 100644 --- a/src/renderer/src/components/editor/model-lifetime/retention/editor-model-lifetime-fixture.ts +++ b/src/renderer/src/components/editor/model-lifetime/retention/editor-model-lifetime-fixture.ts @@ -14,7 +14,7 @@ import { diffViewStateCache, editorViewStateCache, pdfViewPositionCache -} from '@/lib/scroll-cache' +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' const workspace = 'model-fixture::/fixture/workspace' const models: monaco.editor.ITextModel[] = [] diff --git a/src/renderer/src/lib/scroll-cache.ts b/src/renderer/src/components/editor/model-lifetime/retention/editor-view-state-cache.ts similarity index 100% rename from src/renderer/src/lib/scroll-cache.ts rename to src/renderer/src/components/editor/model-lifetime/retention/editor-view-state-cache.ts diff --git a/src/renderer/src/components/editor/monaco-view-state-persistence.ts b/src/renderer/src/components/editor/monaco-view-state-persistence.ts index 9a1a3719f07..e49ced11a52 100644 --- a/src/renderer/src/components/editor/monaco-view-state-persistence.ts +++ b/src/renderer/src/components/editor/monaco-view-state-persistence.ts @@ -5,7 +5,7 @@ import { editorViewStateCache, scrollTopCache, setWithLRU -} from '@/lib/scroll-cache' +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' type MonacoViewStateTrackingParams = { editorInstance: editor.IStandaloneCodeEditor diff --git a/src/renderer/src/components/editor/notebook/cells/IpynbCellEditor.tsx b/src/renderer/src/components/editor/notebook/cells/IpynbCellEditor.tsx index f9aef8b6d7e..84c96d49922 100644 --- a/src/renderer/src/components/editor/notebook/cells/IpynbCellEditor.tsx +++ b/src/renderer/src/components/editor/notebook/cells/IpynbCellEditor.tsx @@ -8,7 +8,7 @@ import { monacoTypographyOptions } from '@/lib/editor/appearance/editor-typography' import { useEditorTypography } from '@/hooks/use-editor-typography' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { installMonacoEditorFindShortcut } from '../../editor-shortcuts' import { syncContentUpdate } from '../../monaco-content-sync' import { diff --git a/src/renderer/src/components/editor/notebook/cells/IpynbCellToolbar.tsx b/src/renderer/src/components/editor/notebook/cells/IpynbCellToolbar.tsx index 5d81f4a5758..670084629aa 100644 --- a/src/renderer/src/components/editor/notebook/cells/IpynbCellToolbar.tsx +++ b/src/renderer/src/components/editor/notebook/cells/IpynbCellToolbar.tsx @@ -27,7 +27,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import type { ShortcutKeyComboDetails } from '@/hooks/useShortcutLabel' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { IpynbCellKind } from '../document/ipynb-parse' const CELL_KINDS: readonly IpynbCellKind[] = ['code', 'markdown', 'raw'] diff --git a/src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.test.tsx b/src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.test.tsx deleted file mode 100644 index b428425e048..00000000000 --- a/src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -// @vitest-environment happy-dom -import { cleanup, fireEvent, render, screen } from '@testing-library/react' -import { useRef } from 'react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { scrollTopCache } from '@/lib/scroll-cache' -import { useIpynbScrollRestoration } from './useIpynbScrollRestoration' - -function NotebookScrollSurface({ ready }: { ready: boolean }) { - const rootRef = useRef(null) - useIpynbScrollRestoration(rootRef, 'notebook', ready) - return ready ?
: null -} - -beforeEach(() => { - vi.useFakeTimers() - scrollTopCache.clear() -}) - -afterEach(() => { - cleanup() - vi.useRealTimers() - scrollTopCache.clear() -}) - -describe('notebook scroll restoration', () => { - it('remembers scroll when invalid source becomes a notebook without remounting the viewer', () => { - const view = render() - view.rerender() - const notebook = screen.getByTestId('notebook') - notebook.scrollTop = 500 - fireEvent.scroll(notebook) - vi.advanceTimersByTime(150) - expect(scrollTopCache.get('notebook')).toBe(500) - - view.unmount() - render() - expect(screen.getByTestId('notebook').scrollTop).toBe(500) - }) -}) diff --git a/src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.ts b/src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.ts index ab8187afc27..afeee13c249 100644 --- a/src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.ts +++ b/src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.ts @@ -1,5 +1,8 @@ import { useLayoutEffect, type RefObject } from 'react' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' export function useIpynbScrollRestoration( rootRef: RefObject, diff --git a/src/renderer/src/components/editor/notebook/kernel/IpynbKernelToolbar.tsx b/src/renderer/src/components/editor/notebook/kernel/IpynbKernelToolbar.tsx index df3684825f7..608c2933aee 100644 --- a/src/renderer/src/components/editor/notebook/kernel/IpynbKernelToolbar.tsx +++ b/src/renderer/src/components/editor/notebook/kernel/IpynbKernelToolbar.tsx @@ -24,7 +24,7 @@ import { } from '@/components/ui/dropdown-menu' import { translate } from '@/i18n/i18n' import { useTranslate, type Translate } from '@/i18n/use-translate' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import type { PythonEnvironment, PythonEnvironments diff --git a/src/renderer/src/components/editor/notebook/kernel/ipynb-kernel-setup-commands.ts b/src/renderer/src/components/editor/notebook/kernel/ipynb-kernel-setup-commands.ts index 6f8e4d548ff..7efc4148c4e 100644 --- a/src/renderer/src/components/editor/notebook/kernel/ipynb-kernel-setup-commands.ts +++ b/src/renderer/src/components/editor/notebook/kernel/ipynb-kernel-setup-commands.ts @@ -1,5 +1,5 @@ import { quotePosixShell } from '../../../../../../shared/shell/launch/shell-argument-quoting' -import { getRendererAppPlatform } from '@/lib/renderer-app-platform' +import { getRendererAppPlatform } from '@/app-shell/platform/renderer-app-platform' import { venvInterpreterSegments } from '../../../../../../shared/notebook-venv-location' import { quotePowerShellLiteral } from '../../../../../../shared/powershell-native-argument' diff --git a/src/renderer/src/components/editor/notebook/outputs/IpynbCellOutputs.tsx b/src/renderer/src/components/editor/notebook/outputs/IpynbCellOutputs.tsx index f2c9d0f0cb1..64854e84dc6 100644 --- a/src/renderer/src/components/editor/notebook/outputs/IpynbCellOutputs.tsx +++ b/src/renderer/src/components/editor/notebook/outputs/IpynbCellOutputs.tsx @@ -1,5 +1,5 @@ import type { ITheme } from '@xterm/xterm' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import '@/lib/terminal-theme' import { getTheme } from '@/lib/terminal-themes-data' import { diff --git a/src/renderer/src/components/editor/pdf-view-position.test.ts b/src/renderer/src/components/editor/pdf-view-position.test.ts index e174c4c691d..0f2cf803953 100644 --- a/src/renderer/src/components/editor/pdf-view-position.test.ts +++ b/src/renderer/src/components/editor/pdf-view-position.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { PdfViewPosition } from '@/lib/scroll-cache' +import type { PdfViewPosition } from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { createPdfViewPositionRecorder, PDF_POSITION_FLUSH_MS } from './pdf-view-position' describe('createPdfViewPositionRecorder', () => { @@ -38,27 +38,7 @@ describe('createPdfViewPositionRecorder', () => { expect(writes).toEqual([['a.pdf:pdf', { pageNumber: 7, top: 400, left: 20 }]]) }) - it('fires the debounced write once and reflects the latest of several rapid records', () => { - const recorder = makeRecorder() - recorder.arm() - recorder.record(location(2)) - recorder.record(location(3)) - recorder.record(location(4)) - expect(writes).toEqual([]) - vi.advanceTimersByTime(PDF_POSITION_FLUSH_MS) - expect(writes).toEqual([['a.pdf:pdf', { pageNumber: 4, top: 400, left: 20 }]]) - }) - // Restarting the timer would starve LRU refresh during continuous scrolling, when the entry most needs to stay warm. - it('does not restart the pending timer when a later record arrives', () => { - const recorder = makeRecorder() - recorder.arm() - recorder.record(location(2)) - vi.advanceTimersByTime(PDF_POSITION_FLUSH_MS - 50) - recorder.record(location(3)) - vi.advanceTimersByTime(50) - expect(writes).toEqual([['a.pdf:pdf', { pageNumber: 3, top: 400, left: 20 }]]) - }) it('ignores a malformed location without clearing the last good position', () => { const recorder = makeRecorder() diff --git a/src/renderer/src/components/editor/pdf-view-position.ts b/src/renderer/src/components/editor/pdf-view-position.ts index 300dc476491..be942ebc5a6 100644 --- a/src/renderer/src/components/editor/pdf-view-position.ts +++ b/src/renderer/src/components/editor/pdf-view-position.ts @@ -1,4 +1,4 @@ -import type { PdfViewPosition } from '@/lib/scroll-cache' +import type { PdfViewPosition } from '@/components/editor/model-lifetime/retention/editor-view-state-cache' /** Argument object for pdf.js `PDFViewer.scrollPageIntoView`. */ export type PdfScrollDestination = { diff --git a/src/renderer/src/components/editor/pdf-viewer-failed-load-recovery.test.tsx b/src/renderer/src/components/editor/pdf-viewer-failed-load-recovery.test.tsx deleted file mode 100644 index 94037acc869..00000000000 --- a/src/renderer/src/components/editor/pdf-viewer-failed-load-recovery.test.tsx +++ /dev/null @@ -1,504 +0,0 @@ -// @vitest-environment happy-dom - -import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react' -import { StrictMode } from 'react' -import { afterEach, describe, expect, it, vi } from 'vitest' -import PdfViewer from './PdfViewer' -import { pdfViewPositionCache } from '@/lib/scroll-cache' - -const getDocument = vi.hoisted(() => vi.fn()) -const shownDocuments = vi.hoisted((): unknown[] => []) -const viewers = vi.hoisted((): { currentScale: number; currentScaleValue: string }[] => []) -const eventBuses = vi.hoisted( - (): { - dispatch: (name: string, event: { location?: unknown; scale: number }) => void - listenerCount: () => number - }[] => [] -) -const scrollDestinations = vi.hoisted((): unknown[] => []) -const viewerSignals = vi.hoisted((): AbortSignal[] => []) -const localizationResources = vi.hoisted( - (): { active: boolean; destroy: ReturnType Promise>> }[] => [] -) - -vi.mock('pdfjs-dist', () => ({ - GlobalWorkerOptions: {}, - getDocument, - PDFDataRangeTransport: class {} -})) -vi.mock('pdfjs-dist/web/pdf_viewer.mjs', () => { - class EventBus { - listeners = new Map void>>() - constructor() { - eventBuses.push(this) - } - on(name: string, callback: (event: { location?: unknown; scale: number }) => void): void { - const listeners = this.listeners.get(name) ?? new Set() - listeners.add(callback) - this.listeners.set(name, listeners) - } - off(name: string, callback: (event: { location?: unknown; scale: number }) => void): void { - this.listeners.get(name)?.delete(callback) - } - dispatch(name: string, event: { location?: unknown; scale: number }): void { - for (const callback of this.listeners.get(name) ?? []) { - callback(event) - } - } - listenerCount(): number { - return [...this.listeners.values()].reduce((count, listeners) => count + listeners.size, 0) - } - } - class PDFLinkService { - setViewer(): void {} - setDocument(): void {} - } - class PDFFindController { - setDocument(): void {} - } - class PDFViewer { - pagesCount = 10 - currentScale = 1 - currentScaleValue = 'page-width' - l10n: { active: boolean; destroy: ReturnType Promise>> } - constructor(options: { container: HTMLDivElement; abortSignal?: AbortSignal }) { - viewers.push(this) - const observer = new MutationObserver(() => {}) - observer.observe(options.container, { childList: true, subtree: true }) - const resource = { active: true, destroy: vi.fn(() => Promise.resolve()) } - resource.destroy.mockImplementation(() => { - observer.disconnect() - resource.active = false - return Promise.resolve() - }) - this.l10n = resource - localizationResources.push(resource) - if (options.abortSignal) { - viewerSignals.push(options.abortSignal) - } - } - setDocument(doc: unknown): void { - shownDocuments.push(doc) - } - scrollPageIntoView(destination: unknown): void { - scrollDestinations.push(destination) - } - update(): void {} - } - return { EventBus, PDFLinkService, PDFFindController, PDFViewer } -}) -vi.mock('pdfjs-dist/web/pdf_viewer.css', () => ({})) -vi.mock('pdfjs-dist/build/pdf.worker.min.mjs?url', () => ({ default: '' })) -vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback })) -vi.mock('@/hooks/useShortcutLabel', () => ({ useShortcutLabel: () => '' })) -vi.mock('@/store', () => ({ - useAppStore: (selector: (state: { keybindings: Record }) => unknown) => - selector({ keybindings: {} }) -})) - -const ERROR_TEXT = 'Failed to load PDF preview' - -type PdfDocumentFixture = { name: string } | { index: number } - -function loadingTask(doc: PdfDocumentFixture | 'fail'): { - promise: Promise - destroy: ReturnType Promise>> -} { - return { - promise: - doc === 'fail' ? Promise.reject(new Error('Invalid PDF structure')) : Promise.resolve(doc), - destroy: vi.fn(() => Promise.resolve()) - } -} - -function pendingTask(): { - promise: Promise - resolve: (doc: PdfDocumentFixture) => void - destroy: ReturnType Promise>> -} { - let resolve: (doc: PdfDocumentFixture) => void = () => {} - const promise = new Promise((done) => { - resolve = done - }) - return { promise, resolve, destroy: vi.fn(() => Promise.resolve()) } -} - -async function finishTask( - task: ReturnType, - doc: PdfDocumentFixture -): Promise { - await act(async () => { - task.resolve(doc) - await task.promise - }) -} - -afterEach(() => { - cleanup() - getDocument.mockReset() - shownDocuments.length = 0 - viewers.length = 0 - eventBuses.length = 0 - scrollDestinations.length = 0 - viewerSignals.length = 0 - localizationResources.length = 0 - pdfViewPositionCache.clear() - localStorage.clear() -}) - -describe('PdfViewer when the file is rewritten', () => { - it('clears the previous version and reports a broken rewrite, then recovers', async () => { - const previous = { name: 'previous build' } - const next = { name: 'next build' } - getDocument - .mockImplementationOnce(() => loadingTask(previous)) - .mockImplementationOnce(() => loadingTask('fail')) - .mockImplementationOnce(() => loadingTask(next)) - const view = render() - await waitFor(() => expect(shownDocuments.at(-1)).toBe(previous)) - - view.rerender() - await waitFor(() => expect(view.queryByText(ERROR_TEXT)).toBeTruthy()) - expect(shownDocuments.at(-1)).toBeNull() - - view.rerender() - await waitFor(() => expect(shownDocuments.at(-1)).toBe(next)) - expect(view.queryByText(ERROR_TEXT)).toBeNull() - }) - - it.each([ - { content: '', error: ERROR_TEXT, label: 'empty' }, - { content: '%%%', error: 'Failed to decode PDF content', label: 'invalid base64' } - ])( - 'clears its previous document when rewritten with $label content', - async ({ content, error }) => { - const previous = { name: 'previous build' } - const task = loadingTask(previous) - getDocument.mockImplementationOnce(() => task) - const view = render() - await waitFor(() => expect(shownDocuments.at(-1)).toBe(previous)) - - view.rerender() - - expect(shownDocuments.at(-1)).toBeNull() - expect(view.queryByText(error)).toBeTruthy() - expect(task.destroy).toHaveBeenCalledTimes(1) - expect(getDocument).toHaveBeenCalledTimes(1) - } - ) - - it('recovers after invalid base64 without remounting the preview container', async () => { - const finished = { name: 'finished' } - getDocument.mockImplementationOnce(() => loadingTask(finished)) - const view = render() - await waitFor(() => expect(view.queryByText('Failed to decode PDF content')).toBeTruthy()) - const container = view.container.querySelector('.pdfViewer') - - view.rerender() - - await waitFor(() => expect(shownDocuments.at(-1)).toBe(finished)) - expect(view.queryByText('Failed to decode PDF content')).toBeNull() - expect(view.container.querySelector('.pdfViewer')).toBe(container) - }) - - it.each([ - { - label: 'file path', - filePath: 'b.pdf', - preferenceKey: 'owner-a', - scrollCacheKey: 'a:pdf', - sameBytes: false - }, - { - label: 'file path with identical bytes', - filePath: 'b.pdf', - preferenceKey: 'owner-a', - scrollCacheKey: 'a:pdf', - sameBytes: true - }, - { - label: 'execution owner with identical paths and bytes', - filePath: 'a.pdf', - preferenceKey: 'owner-b', - scrollCacheKey: 'a:pdf', - sameBytes: true - }, - { - label: 'scroll scope with identical paths and bytes', - filePath: 'a.pdf', - preferenceKey: 'owner-a', - scrollCacheKey: 'b:pdf', - sameBytes: true - } - ])( - 'clears the old document while loading another $label', - async ({ filePath, preferenceKey, scrollCacheKey, sameBytes }) => { - const previous = { name: 'previous' } - const previousTask = loadingTask(previous) - const nextTask = pendingTask() - getDocument.mockImplementationOnce(() => previousTask).mockImplementationOnce(() => nextTask) - const view = render( - - ) - await waitFor(() => expect(shownDocuments.at(-1)).toBe(previous)) - - view.rerender( - - ) - - expect(shownDocuments.at(-1)).toBeNull() - expect(previousTask.destroy).toHaveBeenCalledTimes(1) - expect(getDocument).toHaveBeenCalledTimes(2) - const next = { name: 'next' } - await finishTask(nextTask, next) - expect(shownDocuments.at(-1)).toBe(next) - } - ) - - it('ignores out-of-order results and destroys every superseded task once', async () => { - const first = pendingTask() - const second = pendingTask() - const third = pendingTask() - getDocument - .mockImplementationOnce(() => first) - .mockImplementationOnce(() => second) - .mockImplementationOnce(() => third) - const view = render() - view.rerender() - view.rerender() - const newest = { name: 'newest' } - await finishTask(third, newest) - await finishTask(second, { name: 'obsolete second' }) - await finishTask(first, { name: 'obsolete first' }) - - expect(shownDocuments.filter((doc) => doc !== null)).toEqual([newest]) - view.unmount() - for (const task of [first, second, third]) { - expect(task.destroy).toHaveBeenCalledTimes(1) - } - }) - - it('destroys a parsed task when closed before React commits the result', async () => { - const task = pendingTask() - getDocument.mockImplementationOnce(() => task) - const view = render() - - await act(async () => { - task.resolve({ name: 'parsed' }) - await task.promise - view.unmount() - }) - - expect(task.destroy).toHaveBeenCalledTimes(1) - }) - - it.each(['pending', 'displayed'] as const)( - 'destroys a $0 task exactly once on close', - async (phase) => { - const task = pendingTask() - getDocument.mockImplementationOnce(() => task) - const view = render() - if (phase === 'displayed') { - await finishTask(task, { name: 'displayed' }) - } - - view.unmount() - if (phase === 'pending') { - await finishTask(task, { name: 'closed' }) - } - - expect(task.destroy).toHaveBeenCalledTimes(1) - } - ) - - it('detaches before loading a replacement and restores the prior scroll and zoom', async () => { - const previous = { name: 'previous' } - const previousTask = loadingTask(previous) - let shownOnDestroy: unknown - previousTask.destroy.mockImplementation(() => { - shownOnDestroy = shownDocuments.at(-1) - return Promise.resolve() - }) - const nextTask = pendingTask() - let parsedAfterDestroy = false - getDocument - .mockImplementationOnce(() => previousTask) - .mockImplementationOnce(() => { - parsedAfterDestroy = previousTask.destroy.mock.calls.length === 1 - return nextTask - }) - const view = render( - - ) - await waitFor(() => expect(shownDocuments.at(-1)).toBe(previous)) - eventBuses.at(-1)?.dispatch('pagesinit', { scale: 1 }) - fireEvent.click(view.getByTitle('Zoom in')) - const position = { pageNumber: 7, top: 420, left: 12 } - eventBuses.at(-1)?.dispatch('updateviewarea', { scale: 1.25, location: position }) - view.rerender( - - ) - expect(shownDocuments.at(-1)).toBeNull() - expect(previousTask.destroy).toHaveBeenCalledTimes(1) - expect(parsedAfterDestroy).toBe(true) - expect(viewerSignals.at(-1)?.aborted).toBe(true) - expect(shownOnDestroy).toBeNull() - const next = { name: 'next' } - await finishTask(nextTask, next) - eventBuses.at(-1)?.dispatch('pagesinit', { scale: 1.25 }) - - expect(viewers.at(-1)?.currentScale).toBe(1.25) - expect(previousTask.destroy).toHaveBeenCalledTimes(1) - expect(viewerSignals[0]?.aborted).toBe(true) - expect(pdfViewPositionCache.get('main:pdf')).toEqual(position) - expect(scrollDestinations.at(-1)).toMatchObject({ - pageNumber: 7, - destArray: [null, { name: 'XYZ' }, 12, 420, null] - }) - view.unmount() - expect(nextTask.destroy).toHaveBeenCalledTimes(1) - expect(viewerSignals.every((signal) => signal.aborted)).toBe(true) - }) - - it('owns tasks across StrictMode effect replay', async () => { - const tasks: ReturnType[] = [] - getDocument.mockImplementation(() => { - const task = pendingTask() - tasks.push(task) - return task - }) - const view = render( - - - - ) - expect(tasks).toHaveLength(2) - const active = tasks.at(-1) - expect(active).toBeDefined() - if (active) { - await finishTask(active, { name: 'active' }) - } - - view.unmount() - for (const task of tasks) { - expect(task.destroy).toHaveBeenCalledTimes(1) - } - }) - - it('bounds active resources across 25 failed and successful rewrite cycles', async () => { - const tasks: ReturnType[] = [] - let activeTasks = 0 - let peakTasks = 0 - let peakLocalization = 0 - getDocument.mockImplementation(() => { - const index = tasks.length - const task = loadingTask(index % 2 ? 'fail' : { index }) - activeTasks += 1 - peakTasks = Math.max(peakTasks, activeTasks) - task.destroy.mockImplementation(() => { - activeTasks -= 1 - return Promise.resolve() - }) - tasks.push(task) - return task - }) - const view = render() - await waitFor(() => expect(viewers).toHaveLength(1)) - - for (let index = 0; index < 50; index += 1) { - await act(async () => { - view.rerender() - await Promise.resolve() - }) - const live = index % 2 === 0 ? 0 : 1 - expect(activeTasks).toBe(live) - expect(viewerSignals.filter((signal) => !signal.aborted)).toHaveLength(live) - peakLocalization = Math.max( - peakLocalization, - localizationResources.filter((resource) => resource.active).length - ) - expect(eventBuses.filter((bus) => bus.listenerCount() > 0)).toHaveLength(live) - if (live) { - expect(view.queryByText(ERROR_TEXT)).toBeNull() - } else { - expect(view.queryByText(ERROR_TEXT)).toBeTruthy() - } - } - - expect(peakTasks).toBe(1) - expect(peakLocalization).toBe(1) - expect(getDocument).toHaveBeenCalledTimes(51) - expect(viewers).toHaveLength(26) - view.unmount() - expect(activeTasks).toBe(0) - expect(viewerSignals.every((signal) => signal.aborted)).toBe(true) - expect(eventBuses.every((bus) => bus.listenerCount() === 0)).toBe(true) - expect(localizationResources.every((resource) => !resource.active)).toBe(true) - for (const resource of localizationResources) { - expect(resource.destroy).toHaveBeenCalledTimes(1) - } - for (const task of tasks) { - expect(task.destroy).toHaveBeenCalledTimes(1) - } - }) - - it('releases superseded tasks when asynchronous worker teardown finishes after a burst', async () => { - const tasks: ReturnType[] = [] - const finishDestroy: (() => void)[] = [] - getDocument.mockImplementation(() => { - const task = pendingTask() - task.destroy.mockImplementation( - () => - new Promise((resolve) => { - finishDestroy.push(resolve) - }) - ) - tasks.push(task) - return task - }) - const view = render() - for (let index = 0; index < 30; index += 1) { - view.rerender() - } - const newest = { name: 'newest' } - const active = tasks.at(-1) - expect(active).toBeDefined() - if (active) { - await finishTask(active, newest) - } - expect(shownDocuments.filter((doc) => doc !== null)).toEqual([newest]) - expect(viewers).toHaveLength(1) - - view.unmount() - await act(async () => { - for (const finish of finishDestroy) { - finish() - } - for (const task of tasks) { - task.resolve({ name: 'closed' }) - } - await Promise.all(tasks.map((task) => task.promise)) - }) - - expect(shownDocuments.filter((doc) => doc !== null)).toEqual([newest]) - expect(viewerSignals.every((signal) => signal.aborted)).toBe(true) - for (const task of tasks) { - expect(task.destroy).toHaveBeenCalledTimes(1) - await expect(task.destroy.mock.results[0]?.value).resolves.toBeUndefined() - } - }) -}) diff --git a/src/renderer/src/components/editor/pdf-viewer-session.ts b/src/renderer/src/components/editor/pdf-viewer-session.ts index ce439e92ac3..4b0c26c7d45 100644 --- a/src/renderer/src/components/editor/pdf-viewer-session.ts +++ b/src/renderer/src/components/editor/pdf-viewer-session.ts @@ -10,7 +10,10 @@ import { zoomPdfViewerWithWheel, type PdfScalePreference } from './pdf-scale-preference' -import { pdfViewPositionCache, setWithLRU } from '@/lib/scroll-cache' +import { + pdfViewPositionCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' import { buildPdfScrollDestination, clampPdfViewPosition, diff --git a/src/renderer/src/components/editor/review-note-delivery/NotesSendMenu.tsx b/src/renderer/src/components/editor/review-note-delivery/NotesSendMenu.tsx index 05d8adb700f..7b09978a250 100644 --- a/src/renderer/src/components/editor/review-note-delivery/NotesSendMenu.tsx +++ b/src/renderer/src/components/editor/review-note-delivery/NotesSendMenu.tsx @@ -1,4 +1,4 @@ -import { useAppStore } from '@/store' +import { useAppStore } from '@/store/index' import { useAppStateValue } from '@/store/retained-views/app-state-view' import type { DiffCommentOwner } from '@/store/slices/diff-comments/diff-comment-owner' import React, { useCallback, useEffect, useMemo, useState } from 'react' @@ -14,7 +14,7 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { ReviewNotesSendMenuContent } from './ReviewNotesSendMenuContent' import type { DiffCommentDeliverySnapshot } from '@/store/slices/diff-comments/create-diff-comments-slice' import { diff --git a/src/renderer/src/components/editor/review-note-delivery/ReviewNotesSendMenuContent.tsx b/src/renderer/src/components/editor/review-note-delivery/ReviewNotesSendMenuContent.tsx index d4917f2b16f..40bc1576fec 100644 --- a/src/renderer/src/components/editor/review-note-delivery/ReviewNotesSendMenuContent.tsx +++ b/src/renderer/src/components/editor/review-note-delivery/ReviewNotesSendMenuContent.tsx @@ -41,7 +41,7 @@ import { } from '@/lib/agent-session/messaging/notes-send-agent-targets' import { agentKindForAgentType } from '@/lib/agent-status/aggregation/worktree-agent-status' import { formatAgentTypeLabel } from '../../../../../shared/agent-status/presentation/agent-type-label' -import { track } from '@/lib/telemetry' +import { track } from '@/diagnostics/telemetry/telemetry' import { useNow } from '@/hooks/use-now' import type { DashboardAgentRow as DashboardAgentRowData } from '@/components/dashboard/useDashboardData' import { lastEnteredDoneAt } from '@/components/dashboard/agent-finished-timestamp' diff --git a/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.tsx b/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.tsx index b4b609c2c8e..569e498fc17 100644 --- a/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.tsx +++ b/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownSlashMenu.tsx @@ -1,7 +1,7 @@ import React from 'react' import type { Editor } from '@tiptap/react' import { Search } from 'lucide-react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { runSlashCommand } from './rich-markdown-slash-commands' import type { SlashCommand, diff --git a/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownToolbarButton.tsx b/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownToolbarButton.tsx index 2234e044eb8..ca9ae654ee8 100644 --- a/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownToolbarButton.tsx +++ b/src/renderer/src/components/editor/rich-markdown/commands/RichMarkdownToolbarButton.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' type RichMarkdownToolbarButtonProps = { diff --git a/src/renderer/src/components/editor/rich-markdown/doc-links/RichMarkdownDocLinkMenu.tsx b/src/renderer/src/components/editor/rich-markdown/doc-links/RichMarkdownDocLinkMenu.tsx index 02d5d563c8c..1ad43bfa103 100644 --- a/src/renderer/src/components/editor/rich-markdown/doc-links/RichMarkdownDocLinkMenu.tsx +++ b/src/renderer/src/components/editor/rich-markdown/doc-links/RichMarkdownDocLinkMenu.tsx @@ -1,6 +1,6 @@ import React from 'react' import type { Editor } from '@tiptap/react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/class-names' import { commitRow } from './rich-markdown-commands' import type { DocLinkMenuRow, DocLinkMenuState } from './rich-markdown-commands' import { useTranslate } from '@/i18n/use-translate' diff --git a/src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.lazy-chunk.test.tsx b/src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.lazy-chunk.test.tsx deleted file mode 100644 index bd5303b953c..00000000000 --- a/src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.lazy-chunk.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -// @vitest-environment happy-dom - -import { Suspense, act, type ReactElement, type ReactNode } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -import { lazyWithRetry } from '@/lib/lazy-with-retry' -import { - clearLazyChunkBreadcrumbDedupeForTest, - RichMarkdownErrorBoundary -} from './RichMarkdownErrorBoundary' - -const reportCrashMock = vi.hoisted(() => vi.fn()) -const recordBreadcrumbMock = vi.hoisted(() => vi.fn()) - -vi.mock('@/lib/react-error-boundary-reporting', () => ({ - reportReactErrorBoundaryCrash: reportCrashMock -})) - -vi.mock('@/lib/crash-breadcrumb-recorder', () => ({ - recordRendererCrashBreadcrumb: recordBreadcrumbMock -})) - -const RELOAD_GUARD_KEY = 'orca:lazy-chunk-reload-attempted' -const LANDED_RELOAD_GUARD_VALUE = 'doc-before-the-reload' - -globalThis.IS_REACT_ACT_ENVIRONMENT = true - -function createContainer(): { container: HTMLDivElement; root: Root } { - const container = document.createElement('div') - document.body.appendChild(container) - return { container, root: createRoot(container) } -} - -function BoundaryHarness({ - children, - boundaryKey = 'pane-a' -}: { - children: ReactNode - boundaryKey?: string -}): ReactElement { - return ( - Loading...
}> - - {children} - - - ) -} - -async function flushReactWork(): Promise { - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 0)) - }) -} - -const CORRUPT_CHUNK_PARSE_ERROR = "Unexpected token ':'" - -describe('RichMarkdownErrorBoundary lazy chunk containment', () => { - let root: Root | null = null - let container: HTMLDivElement | null = null - let consoleError: ReturnType - - beforeEach(() => { - reportCrashMock.mockReset() - recordBreadcrumbMock.mockReset() - clearLazyChunkBreadcrumbDedupeForTest() - window.sessionStorage.clear() - consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined) - }) - - afterEach(() => { - if (root) { - act(() => root?.unmount()) - } - container?.remove() - root = null - container = null - window.sessionStorage.clear() - consoleError.mockRestore() - }) - - it('renders the fallback without reporting after guarded dynamic import exhaustion', async () => { - window.sessionStorage.setItem(RELOAD_GUARD_KEY, LANDED_RELOAD_GUARD_VALUE) - const LazyRejectingImport = lazyWithRetry( - () => Promise.reject(new SyntaxError(CORRUPT_CHUNK_PARSE_ERROR)), - { retries: 0, reloadKey: 'rich-markdown-editor' } - ) - ;({ container, root } = createContainer()) - - await act(async () => { - root?.render( - - - - ) - }) - await flushReactWork() - await flushReactWork() - - expect(container?.textContent).toContain('rich markdown editor') - expect(reportCrashMock).not.toHaveBeenCalled() - expect(recordBreadcrumbMock).toHaveBeenCalledWith('lazy_chunk_boundary_degraded', { - boundaryId: 'editor.rich-markdown', - reloadKey: 'rich-markdown-editor', - cause: `SyntaxError: ${CORRUPT_CHUNK_PARSE_ERROR}` - }) - }) -}) diff --git a/src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.tsx b/src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.tsx index 00b0450cccf..74ceadcb2d4 100644 --- a/src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.tsx +++ b/src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { recordRendererCrashBreadcrumb } from '@/lib/crash-breadcrumb-recorder' -import { isLazyChunkLoadError } from '@/lib/lazy-with-retry' -import { reportReactErrorBoundaryCrash } from '@/lib/react-error-boundary-reporting' +import { recordRendererCrashBreadcrumb } from '@/diagnostics/crash-reporting/crash-breadcrumb-recorder' +import { isLazyChunkLoadError } from '@/app-shell/startup/lazy-with-retry' +import { reportReactErrorBoundaryCrash } from '@/diagnostics/crash-reporting/react-error-boundary-reporting' import { translate } from '@/i18n/i18n' type Props = { diff --git a/src/renderer/src/components/editor/rich-markdown/viewport/useEditorScrollRestore.ts b/src/renderer/src/components/editor/rich-markdown/viewport/useEditorScrollRestore.ts index b0b5a759580..2ec457630de 100644 --- a/src/renderer/src/components/editor/rich-markdown/viewport/useEditorScrollRestore.ts +++ b/src/renderer/src/components/editor/rich-markdown/viewport/useEditorScrollRestore.ts @@ -1,6 +1,9 @@ import { useLayoutEffect, type RefObject } from 'react' import type { Editor } from '@tiptap/react' -import { scrollTopCache, setWithLRU } from '@/lib/scroll-cache' +import { + scrollTopCache, + setWithLRU +} from '@/components/editor/model-lifetime/retention/editor-view-state-cache' export function useEditorScrollRestore( scrollContainerRef: RefObject, diff --git a/src/renderer/src/components/editor/use-pdf-viewer-find-shortcut.test.tsx b/src/renderer/src/components/editor/use-pdf-viewer-find-shortcut.test.tsx deleted file mode 100644 index 03b34ea2780..00000000000 --- a/src/renderer/src/components/editor/use-pdf-viewer-find-shortcut.test.tsx +++ /dev/null @@ -1,164 +0,0 @@ -// @vitest-environment happy-dom -import { act, cleanup, render } from '@testing-library/react' -import { useContext, useRef } from 'react' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { EditorCommandOwnerContext } from './editor-command-owner-context' -import { usePdfViewerFindShortcut } from './use-pdf-viewer-find-shortcut' -import { useNativeChatFind } from '../native-chat/viewport/find/use-native-chat-find' - -vi.mock('@/store', () => ({ useAppStore: { getState: () => ({ keybindings: undefined }) } })) -vi.mock('../../store', () => ({ useAppStore: { getState: () => ({ keybindings: undefined }) } })) -vi.mock('@/lib/web-client-location', () => ({ isWebClientLocation: () => false })) - -afterEach(() => { - cleanup() - document.body.replaceChildren() -}) - -function pressFind(target: EventTarget): KeyboardEvent { - const isMac = navigator.userAgent.includes('Mac') - const event = new KeyboardEvent('keydown', { - key: 'f', - code: 'KeyF', - bubbles: true, - cancelable: true, - metaKey: isMac, - ctrlKey: !isMac - }) - target.dispatchEvent(event) - return event -} - -function PdfViewerStandIn({ - name, - openFind -}: { - name: string - openFind: () => void -}): React.JSX.Element { - const rootRef = useRef(null) - const ownsCommands = useContext(EditorCommandOwnerContext) - usePdfViewerFindShortcut({ rootRef, ownsCommands, keybindings: undefined, openFind }) - return ( -
- -
- ) -} - -/** Match split-group strip/body roots with retained terminal/chat/browser overlays outside the bodies. */ -function Workspace({ - focusedGroup, - openFind -}: { - focusedGroup: 'pdf' | 'chat' | 'second-pdf' | null - openFind: Record<'pdf' | 'second-pdf', () => void> -}): React.JSX.Element { - return ( -
-
-
-
-
- -
-
- - - -
-
-
-
-
-
-