diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b73068e25b..2c0e4a254c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,6 +141,20 @@ jobs: if: steps.plan.outputs.asf_source == 'true' run: npm run check:asf-source + # Parsed dependency rules and an exact legacy-debt ledger keep the + # renderer root from absorbing new feature or Desktop ownership while + # the existing AppShell is migrated behind stable boundaries. + - name: Check renderer architecture + if: steps.plan.outputs.code == 'true' + env: + BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} + run: | + if [[ -n "$BASE_SHA" && ! "$BASE_SHA" =~ ^0+$ ]]; then + npm run check:renderer-architecture -- --base "$BASE_SHA" + else + npm run check:renderer-architecture + fi + - name: Lint if: steps.plan.outputs.code == 'true' run: npm run lint diff --git a/apps/desktop/e2e/sidebar-geometry.spec.ts b/apps/desktop/e2e/sidebar-geometry.spec.ts index fd30481673..e30acf885a 100644 --- a/apps/desktop/e2e/sidebar-geometry.spec.ts +++ b/apps/desktop/e2e/sidebar-geometry.spec.ts @@ -44,7 +44,7 @@ import { expect, test } from './fixtures'; import { SESSION_LIST_EXPANDED_MAX_WIDTH, SESSION_LIST_EXPANDED_MIN_WIDTH, -} from '../src/renderer/features/session-navigation/model/session-list-layout'; +} from '../src/renderer/features/session-navigation/testing'; import type { Locator, Page } from '@playwright/test'; async function revealPopulatedSidebar(page: Page): Promise { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 464cd0c2d6..217128e223 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -16,6 +16,7 @@ "storybook": "storybook dev -p 6006 -c .storybook", "build-storybook": "storybook build -c .storybook --output-dir storybook-static", "smoke:storybook": "node ../../scripts/storybook-visual-smoke.mjs", + "check:architecture": "node --test scripts/check-renderer-architecture.test.mjs && node scripts/check-renderer-architecture.mjs", "build": "npm run build:resources && npm run build:main && npm run build:preload && npm run build:overlay && npm run build:renderer", "build:resources": "node scripts/copy-runtime-filesystem-worker.mjs", "build:test": "npm run build:main && npm run build:preload && npm run build:overlay", @@ -24,7 +25,7 @@ "build:main": "tsc -p tsconfig.main.json", "build:preload": "esbuild src/preload/preload.ts --bundle --platform=node --format=cjs --outfile=dist/preload/preload.cjs --external:electron", "build:overlay": "node ../../scripts/build-cursor-overlay.mjs", - "build:renderer": "vite build && node ../../scripts/check-third-party-notices.mjs", + "build:renderer": "vite build && node scripts/check-renderer-entry-output.mjs && node ../../scripts/check-third-party-notices.mjs", "build:workspace-deps": "npm --workspace @maka/core run build && npm --workspace @maka/storage run build && npm --workspace @maka/mcp run build && npm --workspace @maka/runtime run build && npm --workspace @maka/runtime-host run build && npm --workspace @maka/computer-use run build && npm --workspace @maka/ui run build", "package:macos-arm64": "electron-builder --config electron-builder.config.mjs --mac --arm64 --publish never", "package:windows-x64": "electron-builder --config electron-builder.config.mjs --win --x64 --publish never", diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json new file mode 100644 index 0000000000..10f00759ce --- /dev/null +++ b/apps/desktop/renderer-architecture.json @@ -0,0 +1,5596 @@ +{ + "version": 1, + "legacyRendererFiles": [ + "src/renderer/agent-graph-panel-visibility.ts", + "src/renderer/agent-graph-panel.tsx", + "src/renderer/agent-graph-refresh.ts", + "src/renderer/app-shell-app-update.ts", + "src/renderer/app-shell-build-stamp.ts", + "src/renderer/app-shell-chat-actions.ts", + "src/renderer/app-shell-chrome-actions.tsx", + "src/renderer/app-shell-command-actions.ts", + "src/renderer/app-shell-context-compaction.ts", + "src/renderer/app-shell-copy.ts", + "src/renderer/app-shell-detail-panel.tsx", + "src/renderer/app-shell-e2e-fixture.ts", + "src/renderer/app-shell-effects.ts", + "src/renderer/app-shell-overlays.tsx", + "src/renderer/app-shell-project-actions.ts", + "src/renderer/app-shell-revision-actions.ts", + "src/renderer/app-shell-session-events.ts", + "src/renderer/app-shell-session-settings-actions.ts", + "src/renderer/app-shell-session-start-actions.ts", + "src/renderer/app-shell-session-ui-state.ts", + "src/renderer/app-shell-stop-action.ts", + "src/renderer/app-shell-turn-actions.ts", + "src/renderer/app-shell-turn-view-model.ts", + "src/renderer/app-shell.tsx", + "src/renderer/app-update-install.ts", + "src/renderer/app.tsx", + "src/renderer/astryx-theme-mode.ts", + "src/renderer/astryx-theme/maka.js", + "src/renderer/astryx-theme/makaTheme.ts", + "src/renderer/astryx-theme/type-scale.ts", + "src/renderer/attachment-preflight.ts", + "src/renderer/bootstrap-selection-lease.ts", + "src/renderer/browser-storage.ts", + "src/renderer/cached-theme-bootstrap.ts", + "src/renderer/chat-composer-region.tsx", + "src/renderer/chat-message-surface.tsx", + "src/renderer/command-palette-commands.ts", + "src/renderer/command-palette-types.ts", + "src/renderer/command-palette.tsx", + "src/renderer/composer-attachments.ts", + "src/renderer/composer-defaults.ts", + "src/renderer/computer-use-overlay/engine/cursor-candidate-grid.ts", + "src/renderer/computer-use-overlay/engine/cursor-engine.ts", + "src/renderer/computer-use-overlay/engine/palette.ts", + "src/renderer/conversation-markdown.ts", + "src/renderer/custom-pet-companion-model.ts", + "src/renderer/custom-pet-companion.tsx", + "src/renderer/daily-review-actions.ts", + "src/renderer/default-runtime-host-operation.ts", + "src/renderer/derive-turn-lineage-badges.ts", + "src/renderer/desktop-execution-boundary-surface.ts", + "src/renderer/desktop-slash-command.ts", + "src/renderer/desktop-transcript-range-store.ts", + "src/renderer/error-boundary.tsx", + "src/renderer/follow-up-submit-routing.ts", + "src/renderer/interrupted-resume.ts", + "src/renderer/keyboard-help.tsx", + "src/renderer/live-content-seed.ts", + "src/renderer/live-turn-reconciler.tsx", + "src/renderer/live-turn-snapshot.ts", + "src/renderer/local-memory-digest.ts", + "src/renderer/locales/artifact-copy.ts", + "src/renderer/locales/browser-copy.ts", + "src/renderer/locales/conversation-copy.ts", + "src/renderer/locales/external-session-import-copy.ts", + "src/renderer/locales/mcp-copy.ts", + "src/renderer/locales/onboarding-copy.ts", + "src/renderer/locales/permission-center-copy.ts", + "src/renderer/locales/plan-mode-copy.ts", + "src/renderer/locales/settings-bot-copy.ts", + "src/renderer/locales/settings-daily-review-copy.ts", + "src/renderer/locales/settings-data-copy.ts", + "src/renderer/locales/settings-health-copy.ts", + "src/renderer/locales/settings-memory-copy.ts", + "src/renderer/locales/settings-navigation-copy.ts", + "src/renderer/locales/settings-preferences-copy.ts", + "src/renderer/locales/settings-projects-copy.ts", + "src/renderer/locales/settings-provider-copy.ts", + "src/renderer/locales/settings-shared-copy.ts", + "src/renderer/locales/settings-subagents-copy.ts", + "src/renderer/locales/settings-tasks-copy.ts", + "src/renderer/locales/settings-test-result-copy.ts", + "src/renderer/locales/settings-usage-copy.ts", + "src/renderer/locales/settings-web-search-copy.ts", + "src/renderer/locales/shell-copy.ts", + "src/renderer/locales/shell-remaining-copy.ts", + "src/renderer/main.tsx", + "src/renderer/mcp-brand-contrast.ts", + "src/renderer/mcp-brand-marks.tsx", + "src/renderer/mcp-catalog.ts", + "src/renderer/mcp-command-line.ts", + "src/renderer/mcp-editor-validation.ts", + "src/renderer/mcp-page-model.ts", + "src/renderer/mcp-page.tsx", + "src/renderer/model-catalog-choices.ts", + "src/renderer/model-connection-errors.ts", + "src/renderer/model-wait-state.ts", + "src/renderer/nav-selection.ts", + "src/renderer/new-task-reload-intent.ts", + "src/renderer/onboarding-hero-copy.ts", + "src/renderer/onboarding-hero.tsx", + "src/renderer/onboarding-provider-types.ts", + "src/renderer/open-path.ts", + "src/renderer/pending-items.ts", + "src/renderer/pending-session-view.ts", + "src/renderer/plan-mode-panel.tsx", + "src/renderer/project-path-display.ts", + "src/renderer/remote-project-directory-dialog.tsx", + "src/renderer/scroll-motion-policy.ts", + "src/renderer/session-copy-attempt.ts", + "src/renderer/session-error-presentation.ts", + "src/renderer/session-event-health.ts", + "src/renderer/session-health-notice.ts", + "src/renderer/session-message-settlement.ts", + "src/renderer/session-read-state.ts", + "src/renderer/session-status-presentation.ts", + "src/renderer/session-trace-refresh.ts", + "src/renderer/session-workspace-errors.ts", + "src/renderer/settings/about-settings-page.tsx", + "src/renderer/settings/about-update-status.ts", + "src/renderer/settings/action-guard.ts", + "src/renderer/settings/appearance-settings-page.tsx", + "src/renderer/settings/bot-chat-detail.tsx", + "src/renderer/settings/bot-chat-overview.tsx", + "src/renderer/settings/bot-chat-settings-page.tsx", + "src/renderer/settings/bot-chat-shared.tsx", + "src/renderer/settings/bot-onboarding-modal.tsx", + "src/renderer/settings/bot-settings-view-model.ts", + "src/renderer/settings/bot-wechat-login.tsx", + "src/renderer/settings/connection-name-draft.ts", + "src/renderer/settings/custom-pet-library-model.ts", + "src/renderer/settings/custom-pet-settings-section.tsx", + "src/renderer/settings/daily-review-settings-page.tsx", + "src/renderer/settings/data-settings-page.tsx", + "src/renderer/settings/general-settings-page.tsx", + "src/renderer/settings/health-center-page.tsx", + "src/renderer/settings/import-tasks-settings-page.tsx", + "src/renderer/settings/memory-entry-list.tsx", + "src/renderer/settings/memory-settings-labels.ts", + "src/renderer/settings/memory-settings-page.tsx", + "src/renderer/settings/memory-settings-sections.tsx", + "src/renderer/settings/memory-settings-view-model.ts", + "src/renderer/settings/nav-group-summary.ts", + "src/renderer/settings/oauth-login-flow-guard.ts", + "src/renderer/settings/optimistic-settings-draft-controller.ts", + "src/renderer/settings/password-input.tsx", + "src/renderer/settings/permission-center-page.tsx", + "src/renderer/settings/personalization-settings-section.tsx", + "src/renderer/settings/projects-settings-page.tsx", + "src/renderer/settings/provider-add-form.tsx", + "src/renderer/settings/provider-add-model-dialog.tsx", + "src/renderer/settings/provider-add-submission.ts", + "src/renderer/settings/provider-brand-marks.tsx", + "src/renderer/settings/provider-catalog-page.tsx", + "src/renderer/settings/provider-connection-detail.tsx", + "src/renderer/settings/provider-connection-status.ts", + "src/renderer/settings/provider-display-copy.ts", + "src/renderer/settings/provider-display.tsx", + "src/renderer/settings/provider-enabled-model-manager.tsx", + "src/renderer/settings/provider-endpoint-presentation.ts", + "src/renderer/settings/provider-oauth-section.tsx", + "src/renderer/settings/provider-panel-shared.ts", + "src/renderer/settings/providers-panel.tsx", + "src/renderer/settings/relay-profile-draft.ts", + "src/renderer/settings/relay-thinking-bulk.ts", + "src/renderer/settings/request-customization-editor.tsx", + "src/renderer/settings/runtime-host-connection-code-dialog.tsx", + "src/renderer/settings/runtime-host-interaction-boundary.tsx", + "src/renderer/settings/runtime-host-management-dialog.tsx", + "src/renderer/settings/runtime-host-onboarding-dialog.tsx", + "src/renderer/settings/runtime-host-profiles-section.tsx", + "src/renderer/settings/runtime-host-project-directory-editor.tsx", + "src/renderer/settings/runtime-host-settings-bridge.ts", + "src/renderer/settings/runtime-host-settings-target.tsx", + "src/renderer/settings/runtime-host-ssh-terminal-dialog.tsx", + "src/renderer/settings/settings-error-copy.ts", + "src/renderer/settings/settings-expandable-row.tsx", + "src/renderer/settings/settings-metric-card.tsx", + "src/renderer/settings/settings-modal.tsx", + "src/renderer/settings/settings-nav.ts", + "src/renderer/settings/settings-request-authority.ts", + "src/renderer/settings/settings-resource-state.ts", + "src/renderer/settings/settings-route-focus.ts", + "src/renderer/settings/settings-route-header.tsx", + "src/renderer/settings/settings-rows.tsx", + "src/renderer/settings/settings-section.tsx", + "src/renderer/settings/settings-skeleton.tsx", + "src/renderer/settings/settings-snapshot-cache.ts", + "src/renderer/settings/settings-status-badge.ts", + "src/renderer/settings/settings-status-summary-filter.tsx", + "src/renderer/settings/settings-surface.tsx", + "src/renderer/settings/subagent-preset-presentation.ts", + "src/renderer/settings/subagent-settings-page.tsx", + "src/renderer/settings/task-catalog-rows.ts", + "src/renderer/settings/tasks-settings-page.tsx", + "src/renderer/settings/ui-locale-update-gate.ts", + "src/renderer/settings/usage-settings-page.tsx", + "src/renderer/settings/use-action-guard.ts", + "src/renderer/settings/use-connection-detail.ts", + "src/renderer/settings/use-memory-settings-controller.ts", + "src/renderer/settings/use-oauth-login-flow.ts", + "src/renderer/settings/use-optimistic-settings-draft.ts", + "src/renderer/settings/web-search-settings-page.tsx", + "src/renderer/settled-message-merge.ts", + "src/renderer/settled-session-transients.ts", + "src/renderer/shell-chat-model-selection.ts", + "src/renderer/shell-run-update-state.ts", + "src/renderer/side-chat-command.ts", + "src/renderer/skill-invocation-feedback.ts", + "src/renderer/stable-actions.ts", + "src/renderer/stale-sessions.ts", + "src/renderer/task-readiness-notice.ts", + "src/renderer/theme.ts", + "src/renderer/titlebar-dim-color.ts", + "src/renderer/titlebar-modal-sync.ts", + "src/renderer/transient-message-projection.ts", + "src/renderer/turn-footer-actions.ts", + "src/renderer/use-active-execution-boundary.ts", + "src/renderer/use-app-shell-composer-quotes.ts", + "src/renderer/use-app-shell-session-list.ts", + "src/renderer/use-app-shell-session-ui-reads.ts", + "src/renderer/use-app-shell-session-ui-selector.ts", + "src/renderer/use-app-shell-session-workspace.ts", + "src/renderer/use-composer-attachments.ts", + "src/renderer/use-composer-mentions.ts", + "src/renderer/use-deep-research-run.ts", + "src/renderer/use-delayed-flag.ts", + "src/renderer/use-new-task-choice.ts", + "src/renderer/use-onboarding-snapshot.ts", + "src/renderer/use-pending-action-registry.ts", + "src/renderer/use-project-context.ts", + "src/renderer/use-session-setting-intent.ts", + "src/renderer/use-settings-modal.ts", + "src/renderer/use-shell-appearance.ts", + "src/renderer/use-shell-chat-model.ts", + "src/renderer/use-shell-connections.ts", + "src/renderer/use-shell-live-turn.ts", + "src/renderer/use-shell-memory-pill.ts", + "src/renderer/use-shell-resume.ts", + "src/renderer/use-shell-search.ts", + "src/renderer/use-stable-actions.ts", + "src/renderer/use-system-ui-locale.ts", + "src/renderer/use-task-submission-readiness.ts", + "src/renderer/use-work-board.ts", + "src/renderer/work-board-panel.tsx", + "src/renderer/workhub-controller.ts", + "src/renderer/workhub-coordination-host-scope.ts", + "src/renderer/workhub-coordination-lifecycle.ts", + "src/renderer/workhub-coordination-port.ts", + "src/renderer/workhub-route-policy.ts", + "src/renderer/workhub-send-lease.ts", + "src/renderer/workhub-session-port.ts", + "src/renderer/workhub-surface.tsx", + "src/renderer/workspace-readiness-recovery.ts" + ], + "legacyGrowthDirectories": [ + "src/renderer/astryx-theme", + "src/renderer/computer-use-overlay", + "src/renderer/locales", + "src/renderer/settings" + ], + "legacyFeatureImports": [ + "src/renderer/features/goals/controller/use-goal-controller.ts -> src/renderer/locales/shell-copy", + "src/renderer/features/goals/ui/goal-dialog.tsx -> src/renderer/locales/shell-copy", + "src/renderer/features/module-hub/controller/use-daily-review-controller.ts -> src/renderer/daily-review-actions", + "src/renderer/features/module-hub/controller/use-daily-review-controller.ts -> src/renderer/locales/shell-copy", + "src/renderer/features/module-hub/controller/use-daily-review-controller.ts -> src/renderer/locales/shell-remaining-copy", + "src/renderer/features/module-hub/controller/use-scheduled-tasks-controller.ts -> src/renderer/locales/shell-copy", + "src/renderer/features/module-hub/controller/use-scheduled-tasks-controller.ts -> src/renderer/locales/shell-remaining-copy", + "src/renderer/features/module-hub/controller/use-skills-controller.ts -> src/renderer/locales/shell-copy", + "src/renderer/features/module-hub/ui/module-hub-host.tsx -> src/renderer/mcp-page", + "src/renderer/features/session-navigation/controller/session-row-actions.ts -> src/renderer/locales/shell-copy", + "src/renderer/features/session-navigation/controller/use-session-navigation-controller.ts -> src/renderer/browser-storage", + "src/renderer/features/session-navigation/controller/use-session-navigation-controller.ts -> src/renderer/use-stable-actions", + "src/renderer/features/session-navigation/model/session-list-layout.ts -> src/renderer/browser-storage", + "src/renderer/features/session-navigation/model/session-project-grouping.ts -> src/renderer/locales/shell-remaining-copy", + "src/renderer/features/task-entry/controller/use-task-entry-controller.ts -> src/renderer/locales/shell-copy", + "src/renderer/features/task-entry/model/task-entry-selection.ts -> src/renderer/new-task-reload-intent", + "src/renderer/features/task-entry/ui/task-entry-host.tsx -> src/renderer/remote-project-directory-dialog", + "src/renderer/features/workbar/controller/use-workbar-controller.ts -> src/renderer/browser-storage", + "src/renderer/features/workbar/controller/use-workbar-controller.ts -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/controller/use-workbar-controller.ts -> src/renderer/locales/shell-copy", + "src/renderer/features/workbar/controller/use-workbar-controller.ts -> src/renderer/side-chat-command", + "src/renderer/features/workbar/model/workbar-layout.ts -> src/renderer/browser-storage", + "src/renderer/features/workbar/model/workbar-tabs.ts -> src/renderer/browser-storage", + "src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx -> src/renderer/locales/artifact-copy", + "src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx -> src/renderer/open-path", + "src/renderer/features/workbar/tools/artifacts/artifact-preview-registry-shell.tsx -> src/renderer/locales/artifact-copy", + "src/renderer/features/workbar/tools/artifacts/artifact-preview.tsx -> src/renderer/locales/artifact-copy", + "src/renderer/features/workbar/tools/browser/browser-panel.tsx -> src/renderer/locales/browser-copy", + "src/renderer/features/workbar/tools/inspector/session-inspector-panel.tsx -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/tools/inspector/use-session-trace.ts -> src/renderer/session-trace-refresh", + "src/renderer/features/workbar/tools/review/session-review-panel.tsx -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/tools/side-chat/quote-companion-core.ts -> src/renderer/model-connection-errors", + "src/renderer/features/workbar/tools/side-chat/quote-companion-core.ts -> src/renderer/session-copy-attempt", + "src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx -> src/renderer/attachment-preflight", + "src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx -> src/renderer/composer-attachments", + "src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx -> src/renderer/scroll-motion-policy", + "src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx -> src/renderer/turn-footer-actions", + "src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx -> src/renderer/use-composer-attachments", + "src/renderer/features/workbar/tools/side-chat/use-quote-companion.ts -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/tools/side-chat/use-quote-companion.ts -> src/renderer/settled-message-merge", + "src/renderer/features/workbar/tools/tasks/use-session-tasks.ts -> src/renderer/locales/shell-remaining-copy", + "src/renderer/features/workbar/tools/terminal/session-terminal-panel.tsx -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/tools/terminal/session-terminal-panel.tsx -> src/renderer/theme", + "src/renderer/features/workbar/ui/side-chat-close-confirmation.tsx -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/ui/workbar-host.tsx -> src/renderer/locales/shell-copy", + "src/renderer/features/workbar/ui/workbar-surface.tsx -> src/renderer/locales/conversation-copy", + "src/renderer/features/workbar/ui/workbar-surface.tsx -> src/renderer/work-board-panel", + "src/renderer/features/workbar/ui/workbar-toggle.tsx -> src/renderer/locales/shell-copy" + ], + "legacyPlatformImports": [ + "src/renderer/platform/desktop/create-workbar-services.ts -> src/renderer/session-message-settlement" + ], + "legacyAppShell": { + "files": { + "src/renderer/app-shell-app-update.ts": { + "importDeclarations": 2, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "@maka/ui": 1 + }, + "importSpecifiers": 2, + "nonTriviaTokens": 92 + }, + "src/renderer/app-shell-build-stamp.ts": { + "importDeclarations": 2, + "bridgePaths": { + "window.maka.app.info": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 3, + "nonTriviaTokens": 130 + }, + "src/renderer/app-shell-chat-actions.ts": { + "importDeclarations": 24, + "bridgePaths": { + "window.maka.newTasks.create": 1, + "window.maka.sessions.remove": 1, + "window.maka.sessions.respondToSandboxBoundary": 1, + "window.maka.sessions.respondToUserQuestion": 1, + "window.maka.sessions.submitMessage": 1 + }, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellChatActions" + ], + "dependencyPaths": { + "../preload/bridge-contract.js": 2, + "./app-shell-copy.js": 1, + "./attachment-preflight.js": 1, + "./composer-attachments.js": 1, + "./desktop-transcript-range-store.js": 1, + "./locales/shell-copy.js": 1, + "./model-connection-errors.js": 1, + "./session-message-settlement.js": 1, + "./session-workspace-errors.js": 1, + "./skill-invocation-feedback.js": 1, + "@maka/core/collaboration": 1, + "@maka/core/events": 2, + "@maka/core/model-thinking": 1, + "@maka/core/orchestration": 1, + "@maka/core/runtime-inputs": 1, + "@maka/core/sandbox-boundary": 1, + "@maka/core/session": 1, + "@maka/core/session-name": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/core/user-question": 1, + "@maka/runtime/skill-invocation": 1, + "@maka/ui": 1 + }, + "importSpecifiers": 37, + "nonTriviaTokens": 4461 + }, + "src/renderer/app-shell-chrome-actions.tsx": { + "importDeclarations": 5, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy": 1, + "@astryxdesign/core/Icon": 1, + "@astryxdesign/core/Tooltip": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1 + }, + "importSpecifiers": 8, + "nonTriviaTokens": 408 + }, + "src/renderer/app-shell-command-actions.ts": { + "importDeclarations": 16, + "bridgePaths": { + "window.maka.connections.setDefault": 1, + "window.maka.connections.test": 1, + "window.maka.diagnostics.copyReport": 1, + "window.maka.memory.openFile": 1, + "window.maka.sessions.saveConversationToFile": 1, + "window.maka.settings.testNetworkProxy": 1 + }, + "environmentCapabilities": { + "navigator.clipboard.writeText": 1 + }, + "hookCalls": { + "useRef": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/diagnostics-contract.js": 1, + "./app-shell-copy.js": 1, + "./command-palette-commands.js": 1, + "./command-palette-types.js": 1, + "./conversation-markdown.js": 1, + "./default-runtime-host-operation.js": 1, + "./locales/settings-test-result-copy.js": 1, + "./locales/shell-copy.js": 1, + "@maka/core/explore-agent": 1, + "@maka/core/llm-connections": 1, + "@maka/core/permission": 1, + "@maka/core/session": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 22, + "nonTriviaTokens": 2306 + }, + "src/renderer/app-shell-context-compaction.ts": { + "importDeclarations": 4, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy.js": 1, + "@maka/core/events": 1, + "@maka/core/ui-locale": 1, + "@maka/runtime-host/protocol": 1 + }, + "importSpecifiers": 4, + "nonTriviaTokens": 612 + }, + "src/renderer/app-shell-copy.ts": { + "importDeclarations": 5, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/redaction": 1, + "@maka/core/text-file-import": 1, + "@maka/core/ui-locale": 1 + }, + "importSpecifiers": 6, + "nonTriviaTokens": 515 + }, + "src/renderer/app-shell-detail-panel.tsx": { + "importDeclarations": 1, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "react": 1 + }, + "importSpecifiers": 1, + "nonTriviaTokens": 87 + }, + "src/renderer/app-shell-e2e-fixture.ts": { + "importDeclarations": 6, + "bridgePaths": { + "window.maka.e2eFixture.getState": 1 + }, + "environmentCapabilities": { + "document.documentElement.setAttribute": 4 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellE2eFixtureActions" + ], + "dependencyPaths": { + "./features/workbar": 1, + "./theme": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 8, + "nonTriviaTokens": 673 + }, + "src/renderer/app-shell-effects.ts": { + "importDeclarations": 23, + "bridgePaths": { + "window.maka.app.info": 1, + "window.maka.appWindow.subscribeCommand": 1, + "window.maka.connections.subscribeEvents": 1, + "window.maka.runtimeHostProfiles.subscribeChanges": 1, + "window.maka.sessions.subscribeChanges": 1, + "window.maka.sessions.subscribeEvents": 1, + "window.maka.settings.subscribeClientChanged": 1, + "window.maka.settings.subscribeExternalChanged": 1, + "window.maka.shellRuns.list": 1, + "window.maka.shellRuns.subscribeResync": 1, + "window.maka.shellRuns.subscribeUpdates": 1, + "window.maka.transcripts.open": 1 + }, + "environmentCapabilities": { + "cancelAnimationFrame": 1, + "clearTimeout": 1, + "document.addEventListener": 1, + "document.documentElement.setAttribute": 1, + "document.removeEventListener": 1, + "document.visibilityState": 1, + "requestAnimationFrame": 1, + "window": 3, + "window.clearInterval": 1, + "window.clearTimeout": 3, + "window.setInterval": 1, + "window.setTimeout": 2 + }, + "hookCalls": { + "useEffect": 9, + "useHotkeys": 1, + "useLayoutEffect": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "../shared/runtime-host-identity.js": 1, + "./app-shell-copy": 1, + "./browser-storage": 1, + "./desktop-transcript-range-store.js": 1, + "./locales/conversation-copy.js": 1, + "./nav-selection.js": 1, + "./session-event-health": 1, + "./shell-run-update-state.js": 1, + "./theme": 1, + "./titlebar-modal-sync": 1, + "@astryxdesign/core/hooks": 1, + "@maka/core/connections": 1, + "@maka/core/events": 2, + "@maka/core/redaction": 1, + "@maka/core/session": 1, + "@maka/core/session-event-health": 2, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 39, + "nonTriviaTokens": 3924 + }, + "src/renderer/app-shell-overlays.tsx": { + "importDeclarations": 14, + "bridgePaths": {}, + "environmentCapabilities": { + "window": 4, + "window.addEventListener": 1, + "window.removeEventListener": 1 + }, + "hookCalls": { + "useAppShellCommands": 1, + "useHotkeys": 1, + "useLayoutEffect": 2, + "useRef": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./app-shell-command-actions": 1, + "./command-palette": 1, + "./keyboard-help": 1, + "./locales/shell-remaining-copy.js": 1, + "./settings/settings-modal": 1, + "./settings/tasks-settings-page": 1, + "./settings/ui-locale-update-gate": 1, + "@astryxdesign/core/hooks": 1, + "@astryxdesign/core/Spinner": 1, + "@maka/core/llm-connections": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 22, + "nonTriviaTokens": 990 + }, + "src/renderer/app-shell-project-actions.ts": { + "importDeclarations": 9, + "bridgePaths": { + "window.maka.app.openPath": 4, + "window.maka.app.resolveProjectGitInfo": 1, + "window.maka.projects.add": 1, + "window.maka.projects.archive": 1, + "window.maka.projects.getDefaultContext": 1, + "window.maka.projects.relink": 1, + "window.maka.projects.rename": 1, + "window.maka.projects.restore": 1, + "window.maka.projects.select": 3 + }, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellProjectActions" + ], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./app-shell-copy": 1, + "./default-runtime-host-operation.js": 1, + "./locales/shell-copy.js": 1, + "./open-path": 1, + "./session-workspace-errors": 1, + "@maka/core/project": 1, + "@maka/core/ui-locale": 1, + "react": 1 + }, + "importSpecifiers": 15, + "nonTriviaTokens": 2284 + }, + "src/renderer/app-shell-revision-actions.ts": { + "importDeclarations": 10, + "bridgePaths": { + "window.maka.sessions.abandonSessionCopy": 2, + "window.maka.sessions.reviseBeforeTurn": 1 + }, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellRevisionActions" + ], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./locales/conversation-copy.js": 1, + "./locales/shell-copy.js": 1, + "./session-copy-attempt.js": 1, + "./session-message-settlement.js": 1, + "./session-workspace-errors.js": 1, + "@maka/core/session": 2, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + }, + "importSpecifiers": 16, + "nonTriviaTokens": 2336 + }, + "src/renderer/app-shell-session-events.ts": { + "importDeclarations": 8, + "bridgePaths": {}, + "environmentCapabilities": { + "requestAnimationFrame": 1, + "window.setTimeout": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellSessionDisplayBatch", + "createAppShellSessionEventHandlers" + ], + "dependencyPaths": { + "./app-shell-chat-actions.js": 1, + "./app-shell-session-ui-state.js": 1, + "./locales/conversation-copy.js": 1, + "./model-connection-errors.js": 1, + "@maka/core/events": 1, + "@maka/core/session": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + }, + "importSpecifiers": 21, + "nonTriviaTokens": 2777 + }, + "src/renderer/app-shell-session-settings-actions.ts": { + "importDeclarations": 8, + "bridgePaths": { + "window.maka.sessions.setModel": 1, + "window.maka.sessions.setPermissionMode": 1, + "window.maka.sessions.setThinkingLevel": 1 + }, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellSessionSettingsActions" + ], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./locales/shell-copy.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/model-thinking": 1, + "@maka/core/permission": 1, + "@maka/core/session": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1 + }, + "importSpecifiers": 10, + "nonTriviaTokens": 1472 + }, + "src/renderer/app-shell-session-start-actions.ts": { + "importDeclarations": 7, + "bridgePaths": { + "window.maka.newTasks.create": 1, + "window.maka.onboarding.setMilestone": 1 + }, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellSessionStartActions" + ], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./locales/shell-copy.js": 1, + "./model-connection-errors.js": 1, + "./session-workspace-errors.js": 1, + "@maka/core/explore-agent": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + }, + "importSpecifiers": 11, + "nonTriviaTokens": 650 + }, + "src/renderer/app-shell-session-ui-state.ts": { + "importDeclarations": 5, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useRef": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellSessionUiStateController" + ], + "dependencyPaths": { + "./shell-run-update-state.js": 1, + "@maka/core/events": 1, + "@maka/core/session-event-health": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 7, + "nonTriviaTokens": 1234 + }, + "src/renderer/app-shell-stop-action.ts": { + "importDeclarations": 3, + "bridgePaths": { + "window.maka.sessions.stop": 1 + }, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellStopAction" + ], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "./locales/shell-copy.js": 1, + "@maka/core/ui-locale": 1 + }, + "importSpecifiers": 3, + "nonTriviaTokens": 391 + }, + "src/renderer/app-shell-turn-actions.ts": { + "importDeclarations": 8, + "bridgePaths": { + "window.maka.sessions.branchFromTurn": 1, + "window.maka.sessions.regenerateTurn": 1 + }, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellTurnActions" + ], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./locales/conversation-copy.js": 1, + "./locales/shell-copy.js": 1, + "./session-copy-attempt.js": 1, + "./session-workspace-errors.js": 1, + "@maka/core/session": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + }, + "importSpecifiers": 9, + "nonTriviaTokens": 673 + }, + "src/renderer/app-shell-turn-view-model.ts": { + "importDeclarations": 7, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useRef": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [ + "createAppShellTurnPresentationDerivation" + ], + "dependencyPaths": { + "./derive-turn-lineage-badges.js": 1, + "./interrupted-resume.js": 1, + "./session-status-presentation.js": 1, + "./turn-footer-actions.js": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 18, + "nonTriviaTokens": 1425 + }, + "src/renderer/app-shell.tsx": { + "importDeclarations": 103, + "bridgePaths": { + "window.maka.app.installUpdate": 1, + "window.maka.app.retryUpdateDownload": 1, + "window.maka.app.subscribeUpdateStatus": 1, + "window.maka.app.updateStatus": 1, + "window.maka.attachments": 1, + "window.maka.attachments.readBytes": 1, + "window.maka.connections.subscribeEvents": 1, + "window.maka.diagnostics.copyPreviousMainProcessInterruption": 1, + "window.maka.diagnostics.copyReport": 1, + "window.maka.diagnostics.takePreviousMainProcessInterruption": 1, + "window.maka.notifications.runEnded": 1, + "window.maka.onboarding.setMilestone": 1, + "window.maka.runtimeHostProfiles.subscribeChanges": 1, + "window.maka.sessions": 1, + "window.maka.sessions.abandonPlanProposal": 1, + "window.maka.sessions.compact": 1, + "window.maka.sessions.getPlanState": 1, + "window.maka.sessions.listActiveInteractions": 1, + "window.maka.sessions.listTurnLandmarks": 1, + "window.maka.sessions.promoteQueueEntry": 1, + "window.maka.sessions.reorderQueueEntries": 1, + "window.maka.sessions.retractQueueEntry": 1, + "window.maka.sessions.setCollaborationMode": 1, + "window.maka.sessions.setOrchestrationMode": 1, + "window.maka.sessions.subscribeActiveInteractions": 1, + "window.maka.sessions.updateQueueEntry": 1, + "window.maka.settings.getClient": 1, + "window.maka.settings.subscribeClientChanged": 1, + "window.maka.transcripts": 2, + "window.maka.workHub.act": 1, + "window.maka.workHub.answer": 1, + "window.maka.workHub.candidates": 1, + "window.maka.workHub.createSession": 1, + "window.maka.workHub.record": 1, + "window.maka.workHub.resolveCoordinationSession": 1 + }, + "environmentCapabilities": { + "document.querySelector": 1, + "requestAnimationFrame": 1, + "window": 1, + "window.clearTimeout": 4, + "window.requestAnimationFrame": 4, + "window.setTimeout": 2 + }, + "hookCalls": { + "useActiveExecutionBoundary": 1, + "useActiveSessionEvents": 1, + "useAppShellBootstrapSubscriptions": 1, + "useAppShellComposerQuotes": 1, + "useAppShellHostEffects": 1, + "useAppShellNavRefSync": 1, + "useAppShellPersistenceEffects": 1, + "useAppShellProjectContext": 1, + "useAppShellSessionUiReads": 1, + "useAppShellSessionWorkspace": 1, + "useAppShellTurnPresentation": 1, + "useBuildStamp": 1, + "useCommandPalette": 1, + "useComposerAttachments": 1, + "useComposerMentions": 1, + "useEffect": 14, + "useGoalController": 1, + "useKeyboardHelp": 1, + "useKeyedPendingRegistry": 3, + "useLayoutEffect": 2, + "useModuleHubController": 1, + "useNewTaskChoice": 1, + "useOnboardingSnapshot": 1, + "usePlanModeState": 1, + "useRef": 22, + "useSessionEventHealthPolling": 1, + "useSessionNavigationController": 1, + "useSessionSettingIntent": 2, + "useSettingsModal": 1, + "useShellAppearance": 1, + "useShellChatModel": 1, + "useShellConnections": 3, + "useShellLiveTurn": 1, + "useShellMemoryPill": 1, + "useShellResume": 1, + "useShellRunUpdates": 1, + "useShellSearch": 1, + "useStableActions": 7, + "useState": 18, + "useSystemUiLocale": 1, + "useTaskEntryController": 1, + "useTaskSubmissionReadiness": 1, + "useToast": 1, + "useWorkbarController": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "../preload/transcript-contract.js": 1, + "./agent-graph-panel": 1, + "./app-shell-app-update": 1, + "./app-shell-build-stamp": 1, + "./app-shell-chat-actions": 1, + "./app-shell-chrome-actions": 1, + "./app-shell-command-actions": 1, + "./app-shell-context-compaction": 1, + "./app-shell-detail-panel": 1, + "./app-shell-e2e-fixture": 1, + "./app-shell-effects": 1, + "./app-shell-overlays": 1, + "./app-shell-revision-actions": 1, + "./app-shell-session-events": 1, + "./app-shell-session-settings-actions": 1, + "./app-shell-session-start-actions": 1, + "./app-shell-stop-action": 1, + "./app-shell-turn-actions": 1, + "./app-shell-turn-view-model": 1, + "./app-update-install": 1, + "./chat-composer-region": 1, + "./chat-message-surface": 1, + "./command-palette": 1, + "./composer-defaults": 1, + "./custom-pet-companion": 1, + "./custom-pet-companion-model": 1, + "./default-runtime-host-operation.js": 1, + "./desktop-execution-boundary-surface": 1, + "./desktop-slash-command": 1, + "./error-boundary": 1, + "./features/goals": 1, + "./features/module-hub": 1, + "./features/session-navigation": 1, + "./features/task-entry": 1, + "./features/workbar": 1, + "./follow-up-submit-routing": 1, + "./keyboard-help": 1, + "./live-content-seed": 1, + "./live-turn-reconciler": 1, + "./locales/conversation-copy": 1, + "./locales/shell-copy": 1, + "./locales/shell-remaining-copy.js": 1, + "./model-connection-errors": 1, + "./nav-selection": 1, + "./pending-items": 1, + "./pending-session-view": 1, + "./plan-mode-panel": 1, + "./scroll-motion-policy": 1, + "./session-workspace-errors": 1, + "./settings/provider-brand-marks": 1, + "./settings/provider-display": 1, + "./settings/runtime-host-ssh-terminal-dialog.js": 1, + "./settings/tasks-settings-page": 1, + "./stale-sessions": 1, + "./task-readiness-notice": 1, + "./use-active-execution-boundary": 1, + "./use-app-shell-composer-quotes": 1, + "./use-app-shell-session-ui-reads": 1, + "./use-app-shell-session-workspace": 1, + "./use-composer-attachments": 1, + "./use-composer-mentions": 1, + "./use-new-task-choice": 1, + "./use-onboarding-snapshot": 1, + "./use-pending-action-registry": 1, + "./use-project-context": 1, + "./use-session-setting-intent": 1, + "./use-settings-modal": 1, + "./use-shell-appearance": 1, + "./use-shell-chat-model": 1, + "./use-shell-connections": 1, + "./use-shell-live-turn": 1, + "./use-shell-memory-pill": 1, + "./use-shell-resume": 1, + "./use-shell-search": 1, + "./use-stable-actions": 1, + "./use-system-ui-locale": 1, + "./use-task-submission-readiness": 1, + "./workhub-controller.js": 1, + "./workhub-coordination-host-scope.js": 1, + "./workhub-coordination-lifecycle.js": 1, + "./workhub-coordination-port.js": 1, + "./workhub-session-port.js": 1, + "./workhub-surface.js": 1, + "./workspace-readiness-recovery": 1, + "@astryxdesign/core/AppShell": 1, + "@astryxdesign/core/Button": 1, + "@maka/core/connections": 1, + "@maka/core/events": 1, + "@maka/core/onboarding-milestone": 1, + "@maka/core/orchestration": 1, + "@maka/core/project": 1, + "@maka/core/session": 2, + "@maka/core/session-revisions": 1, + "@maka/core/settings": 1, + "@maka/core/slash-command-catalog": 2, + "@maka/core/ui-locale": 2, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + }, + "importSpecifiers": 175, + "nonTriviaTokens": 15581 + }, + "src/renderer/use-app-shell-composer-quotes.ts": { + "importDeclarations": 3, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./pending-items.js": 1, + "@maka/core/events": 1, + "react": 1 + }, + "importSpecifiers": 7, + "nonTriviaTokens": 360 + }, + "src/renderer/use-app-shell-session-list.ts": { + "importDeclarations": 8, + "bridgePaths": { + "window.maka.sessions.list": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useRef": 3, + "useState": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./locales/conversation-copy.js": 1, + "./locales/shell-copy.js": 1, + "./session-read-state": 1, + "./session-status-presentation": 1, + "./settled-session-transients.js": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 12, + "nonTriviaTokens": 558 + }, + "src/renderer/use-app-shell-session-ui-reads.ts": { + "importDeclarations": 4, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useAppShellSessionUiSelector": 9 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./app-shell-session-ui-state.js": 1, + "./live-turn-snapshot.js": 1, + "./use-app-shell-session-ui-selector.js": 1, + "@maka/ui": 1 + }, + "importSpecifiers": 10, + "nonTriviaTokens": 391 + }, + "src/renderer/use-app-shell-session-ui-selector.ts": { + "importDeclarations": 2, + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useSyncExternalStore": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./app-shell-session-ui-state.js": 1, + "react": 1 + }, + "importSpecifiers": 4, + "nonTriviaTokens": 219 + }, + "src/renderer/use-app-shell-session-workspace.ts": { + "importDeclarations": 10, + "bridgePaths": { + "window.maka.sessions.queryCancelledMessages": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useAppShellSessionList": 1, + "useAppShellSessionUiState": 1, + "useRef": 8, + "useState": 4 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./app-shell-session-ui-state": 1, + "./bootstrap-selection-lease": 1, + "./desktop-transcript-range-store.js": 1, + "./new-task-reload-intent": 1, + "./transient-message-projection.js": 1, + "./use-app-shell-session-list": 1, + "@maka/core/session": 1, + "@maka/runtime-host/protocol": 1, + "@maka/ui": 1, + "react": 1 + }, + "importSpecifiers": 15, + "nonTriviaTokens": 1481 + } + }, + "closure": { + "src/preload/diagnostics-contract.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/preload/external-session-catalog.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../shared/runtime-host-identity.js": 1, + "@maka/runtime-host/protocol": 1 + } + }, + "src/preload/external-session-import-result.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session": 1 + } + }, + "src/preload/runtime-host-renderer-operations.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/runtime-host/protocol": 1 + } + }, + "src/preload/transcript-contract.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/agent-graph-panel-visibility.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/agent-graph-panel.tsx": { + "bridgePaths": { + "window.maka.graphs.getSnapshot": 1, + "window.maka.graphs.listCurrentEpochs": 1, + "window.maka.graphs.listEpochs": 2, + "window.maka.graphs.stop": 1, + "window.maka.graphs.subscribe": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useRef": 4, + "useState": 9 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./agent-graph-panel-visibility.js": 1, + "./agent-graph-refresh.js": 1, + "@astryxdesign/core/Banner": 1, + "@astryxdesign/core/Button": 1, + "@astryxdesign/core/EmptyState": 1, + "@astryxdesign/core/Spinner": 1, + "@maka/core/ui-locale": 1, + "@maka/runtime-host/client": 1, + "@maka/runtime-host/protocol": 1, + "@maka/runtime/stream-graph-read-model": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/agent-graph-refresh.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/app-update-install.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1 + } + }, + "src/renderer/astryx-theme/type-scale.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/attachment-preflight.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "@maka/core/attachments": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/bootstrap-selection-lease.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/browser-storage.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "localStorage.getItem": 1, + "localStorage.setItem": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/chat-composer-region.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useLayoutEffect": 1, + "useRef": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./new-task-reload-intent.js": 1, + "@maka/ui": 2, + "react": 1 + } + }, + "src/renderer/chat-message-surface.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useAppShellSessionUiSelector": 2, + "useDeepResearchRun": 1, + "useState": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./app-shell-session-ui-state": 1, + "./locales/conversation-copy": 1, + "./locales/shell-copy": 1, + "./onboarding-hero": 1, + "./task-readiness-notice": 1, + "./use-app-shell-session-ui-reads": 1, + "./use-app-shell-session-ui-selector": 1, + "./use-deep-research-run": 1, + "./use-shell-chat-model": 1, + "./workspace-readiness-recovery": 1, + "@astryxdesign/core": 1, + "@maka/core/explore-agent": 1, + "@maka/core/llm-connections": 1, + "@maka/core/onboarding": 1, + "@maka/core/settings": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/command-palette-commands.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./command-palette-types.js": 1, + "./locales/shell-copy.js": 1, + "./settings/settings-nav.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/permission": 1, + "@maka/core/provider-registry": 1, + "@maka/core/session": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1 + } + }, + "src/renderer/command-palette-types.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/ui/icons": 1 + } + }, + "src/renderer/command-palette.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "window.cancelAnimationFrame": 1, + "window.requestAnimationFrame": 1 + }, + "hookCalls": { + "useEffect": 1, + "useHotkeys": 1, + "useRef": 1, + "useState": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./command-palette-types": 2, + "./locales/shell-copy": 1, + "@astryxdesign/core/EmptyState": 1, + "@astryxdesign/core/hooks": 1, + "@astryxdesign/core/Kbd": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/composer-attachments.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/events": 1 + } + }, + "src/renderer/composer-defaults.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./browser-storage": 1 + } + }, + "src/renderer/conversation-markdown.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-remaining-copy.js": 1, + "@maka/core/session": 2, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/custom-pet-companion-model.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/pet": 1, + "@maka/core/session": 1 + } + }, + "src/renderer/custom-pet-companion.tsx": { + "bridgePaths": { + "window.maka.pets": 1, + "window.maka.pets.subscribeChanges": 1 + }, + "environmentCapabilities": { + "document": 1, + "document.documentElement": 5, + "document.documentElement.dataset.makaE2eFixture": 1, + "document.documentElement.dataset.makaReducedMotion": 1, + "MutationObserver": 1, + "window": 1, + "window.cancelAnimationFrame": 1, + "window.matchMedia": 3, + "window.requestAnimationFrame": 2 + }, + "hookCalls": { + "useEffect": 4, + "useMountedRef": 1, + "useRef": 4, + "useState": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./custom-pet-companion-model.js": 1, + "@maka/core/pet": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/daily-review-actions.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/daily-review": 2, + "@maka/core/redaction": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/default-runtime-host-operation.ts": { + "bridgePaths": { + "window.maka.runtimeHostProfiles.getDefaultHost": 2 + }, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1 + } + }, + "src/renderer/derive-turn-lineage-badges.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/desktop-execution-boundary-surface.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/permission": 1, + "@maka/core/sandbox-boundary": 2 + } + }, + "src/renderer/desktop-slash-command.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./side-chat-command.js": 1, + "@maka/core/graph-command": 1, + "@maka/core/swarm-command": 1 + } + }, + "src/renderer/desktop-transcript-range-store.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "window.clearTimeout": 1, + "window.setTimeout": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/transcript-contract.js": 1, + "../shared/desktop-session-projection.js": 1, + "../shared/runtime-host-identity.js": 1, + "@maka/core/persisted-value": 1, + "@maka/core/session": 1 + } + }, + "src/renderer/error-boundary.tsx": { + "bridgePaths": { + "window.maka.diagnostics": 3, + "window.maka.diagnostics.copyReport": 1 + }, + "environmentCapabilities": { + "location": 3, + "navigator": 1, + "navigator.clipboard.writeText": 1, + "navigator.userAgent": 2, + "window": 1, + "window.location.href": 2, + "window.location.reload": 1 + }, + "hookCalls": {}, + "lifecycleMethods": { + "componentDidCatch": 1, + "componentDidMount": 1, + "componentWillUnmount": 1, + "getDerivedStateFromError": 1, + "setState": 5, + "state": 5 + }, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy.js": 1, + "@maka/core/diagnostic-log": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/follow-up-submit-routing.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/events": 1 + } + }, + "src/renderer/interrupted-resume.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/keyboard-help.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useHotkeys": 1, + "useState": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Heading": 1, + "@astryxdesign/core/hooks": 1, + "@astryxdesign/core/Kbd": 1, + "@astryxdesign/core/Layout": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/live-content-seed.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/live-turn-reconciler.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useAppShellSessionUiSelector": 1, + "useEffect": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./app-shell-session-ui-state": 1, + "./use-app-shell-session-ui-reads": 1, + "./use-app-shell-session-ui-selector": 1, + "@maka/core/session": 1, + "react": 1 + } + }, + "src/renderer/live-turn-snapshot.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./model-wait-state.js": 1, + "./session-event-health.js": 1, + "@maka/ui": 1 + } + }, + "src/renderer/local-memory-digest.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "window.crypto.subtle": 3, + "window.crypto.subtle.digest": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/local-memory": 1 + } + }, + "src/renderer/locales/artifact-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/browser-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/conversation-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/connection-readiness": 1, + "@maka/core/model-call-attempt": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/external-session-import-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/mcp-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/onboarding-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../onboarding-hero-copy.js": 1, + "@maka/core/onboarding": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/permission-center-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/capabilities": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/locales/plan-mode-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/plan": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-bot-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/bot-chat-settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/locales/settings-daily-review-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-data-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "history": 3 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1, + "@maka/storage/config-transfer": 1 + } + }, + "src/renderer/locales/settings-health-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/health": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/locales/settings-memory-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/local-memory": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-navigation-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../settings/nav-group-summary.js": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-preferences-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-projects-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 2, + "@maka/core/ui-locale": 1, + "@maka/runtime-host/operator": 1 + } + }, + "src/renderer/locales/settings-provider-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-shared-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-subagents-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/model-thinking": 1, + "@maka/core/subagent-settings": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-tasks-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-test-result-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "location": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-usage-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/settings-web-search-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1, + "@maka/core/web-search": 1 + } + }, + "src/renderer/locales/shell-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/goal": 1, + "@maka/core/model-thinking": 1, + "@maka/core/permission": 1, + "@maka/core/redaction": 1, + "@maka/core/settings": 1, + "@maka/core/slash-command-catalog": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/locales/shell-remaining-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/mcp-brand-contrast.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/mcp-brand-marks.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./mcp-brand-contrast.js": 1, + "./mcp-catalog": 1, + "@ant-design/icons-svg/es/asn/DingtalkOutlined.js": 1, + "@ant-design/icons-svg/es/types.js": 1, + "react": 1, + "simple-icons": 2 + } + }, + "src/renderer/mcp-catalog.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/mcp": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/mcp-command-line.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/mcp-editor-validation.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./mcp-command-line.js": 1 + } + }, + "src/renderer/mcp-page-model.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/mcp-copy.js": 1, + "./mcp-command-line.js": 1, + "@maka/core/mcp": 2 + } + }, + "src/renderer/mcp-page.tsx": { + "bridgePaths": { + "window.maka.mcp.cancelInstall": 1, + "window.maka.mcp.getConfig": 1, + "window.maka.mcp.importConfig": 1, + "window.maka.mcp.install": 1, + "window.maka.mcp.listStatuses": 1, + "window.maka.mcp.remove": 1, + "window.maka.mcp.subscribeChanges": 1, + "window.maka.mcp.test": 1, + "window.maka.mcp.upsert": 2 + }, + "environmentCapabilities": { + "cancelAnimationFrame": 1, + "requestAnimationFrame": 1, + "window.requestAnimationFrame": 2, + "window.setTimeout": 1 + }, + "hookCalls": { + "useEffect": 3, + "useMountedRef": 1, + "useRef": 4, + "useRovingRowFocus": 1, + "useState": 11, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./default-runtime-host-operation.js": 1, + "./locales/mcp-copy": 1, + "./mcp-brand-marks": 1, + "./mcp-catalog": 1, + "./mcp-command-line": 1, + "./mcp-editor-validation": 1, + "./mcp-page-model": 1, + "./settings/settings-error-copy": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Layout": 1, + "@astryxdesign/core/MetadataList": 1, + "@maka/core/mcp": 2, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/model-catalog-choices.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-remaining-copy.js": 1, + "@maka/core/llm-connections": 2, + "@maka/core/model-catalog": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/model-connection-errors.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "./locales/shell-copy.js": 1, + "./session-error-presentation.js": 1, + "@maka/core/connection-error-copy": 1, + "@maka/core/connection-readiness": 1, + "@maka/core/events": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/model-wait-state.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/nav-selection.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./browser-storage.js": 1, + "@maka/ui": 1 + } + }, + "src/renderer/new-task-reload-intent.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "sessionStorage": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/onboarding-hero-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/onboarding-copy.js": 1, + "@maka/core/onboarding": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/onboarding-hero.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useUiLocale": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/onboarding-copy": 1, + "./onboarding-hero-copy": 1, + "./onboarding-provider-types": 1, + "./settings/provider-display": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 1, + "@maka/core/onboarding": 1, + "@maka/core/settings": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/onboarding-provider-types.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/llm-connections": 1 + } + }, + "src/renderer/open-path.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy.js": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/pending-items.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/pending-session-view.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/permission": 1, + "@maka/core/session": 1 + } + }, + "src/renderer/plan-mode-panel.tsx": { + "bridgePaths": { + "window.maka.sessions.abandonPlanExecution": 1, + "window.maka.sessions.approvePlan": 1, + "window.maka.sessions.getPlanState": 1, + "window.maka.sessions.requestPlanRevision": 1, + "window.maka.sessions.resumePlan": 1, + "window.maka.sessions.subscribeEvents": 1, + "window.maka.sessions.subscribePlanChanges": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useRef": 2, + "useState": 4, + "useToast": 1, + "useUiLocale": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/plan-mode-copy.js": 1, + "@astryxdesign/core/Banner": 1, + "@astryxdesign/core/Collapsible": 1, + "@maka/core/events": 1, + "@maka/core/plan": 1, + "@maka/core/session": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/project-path-display.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/remote-project-directory-dialog.tsx": { + "bridgePaths": { + "window.maka.projects.getDirectoryRoots": 1, + "window.maka.projects.listDirectory": 2, + "window.maka.projects.registerDirectory": 1 + }, + "environmentCapabilities": { + "window.cancelAnimationFrame": 1, + "window.requestAnimationFrame": 1 + }, + "hookCalls": { + "useEffect": 2, + "useRef": 4, + "useState": 8, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./locales/shell-copy.js": 1, + "@astryxdesign/core/Button": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/DropdownMenu": 1, + "@astryxdesign/core/Layout": 1, + "@astryxdesign/core/Stack": 1, + "@astryxdesign/core/Text": 1, + "@maka/core/project": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/scroll-motion-policy.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "document.documentElement": 2, + "document.documentElement.dataset.makaE2eFixture": 1, + "document.documentElement.dataset.makaReducedMotion": 1, + "document.documentElement.dataset.makaScrollMotion": 5, + "window.matchMedia": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/session-copy-attempt.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "sessionStorage": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/session-error-presentation.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/session-event-health.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session": 1, + "@maka/core/session-event-health": 2, + "@maka/core/tool-result-status": 1, + "@maka/ui": 1 + } + }, + "src/renderer/session-health-notice.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/session-send-projection": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/session-message-settlement.ts": { + "bridgePaths": { + "window.maka.transcripts": 1 + }, + "environmentCapabilities": { + "window.clearTimeout": 2, + "window.setTimeout": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./desktop-transcript-range-store.js": 1, + "@maka/core/session": 1 + } + }, + "src/renderer/session-read-state.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session": 1 + } + }, + "src/renderer/session-status-presentation.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "./session-error-presentation.js": 1, + "@maka/core/sandbox-boundary": 1, + "@maka/core/session": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/session-trace-refresh.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/events": 1 + } + }, + "src/renderer/session-workspace-errors.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy.js": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/about-settings-page.tsx": { + "bridgePaths": { + "window.maka.app.checkForUpdates": 1, + "window.maka.app.info": 1, + "window.maka.app.subscribeUpdateStatus": 1, + "window.maka.app.updateStatus": 1, + "window.maka.diagnostics.copyReport": 1 + }, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": { + "useActionGuard": 2, + "useEffect": 2, + "useMountedRef": 1, + "useState": 5, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../default-runtime-host-operation.js": 1, + "../locales/settings-preferences-copy.js": 1, + "../locales/settings-shared-copy.js": 1, + "./about-update-status.js": 1, + "./settings-error-copy.js": 1, + "./settings-rows.js": 1, + "./settings-section.js": 1, + "./settings-skeleton.js": 1, + "./use-action-guard.js": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/Kbd": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/about-update-status.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../locales/settings-preferences-copy.js": 1 + } + }, + "src/renderer/settings/action-guard.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/appearance-settings-page.tsx": { + "bridgePaths": { + "window.maka.app.iconPreviews": 2, + "window.maka.app.importIcon": 1, + "window.maka.app.removeIcon": 1, + "window.maka.app.selectIcon": 1 + }, + "environmentCapabilities": { + "window": 2 + }, + "hookCalls": { + "useEffect": 2, + "useMountedRef": 1, + "useRef": 1, + "useState": 6, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-preferences-copy.js": 1, + "../theme": 1, + "./custom-pet-settings-section.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "@astryxdesign/core": 1, + "@maka/core/settings": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/bot-chat-detail.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "window.cancelAnimationFrame": 4, + "window.requestAnimationFrame": 4 + }, + "hookCalls": { + "useEffect": 5, + "useMountedRef": 1, + "useState": 5, + "useToast": 1, + "useUiLocale": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-bot-copy": 1, + "./bot-chat-shared": 1, + "./bot-onboarding-modal": 1, + "./bot-settings-view-model": 1, + "./bot-wechat-login": 1, + "./password-input": 1, + "./settings-section": 1, + "@astryxdesign/core": 1, + "@maka/core/bot-chat-settings": 1, + "@maka/core/bot-onboarding": 1, + "@maka/core/settings": 1, + "@maka/runtime/bots": 1, + "@maka/ui": 2, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/bot-chat-overview.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-bot-copy": 1, + "./bot-chat-shared": 1, + "./bot-settings-view-model": 1, + "./settings-section": 1, + "@astryxdesign/core": 1, + "@maka/core/bot-chat-settings": 1, + "@maka/core/settings": 1, + "@maka/runtime/bots": 1, + "@maka/ui": 2, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/bot-chat-settings-page.tsx": { + "bridgePaths": { + "window.maka.e2eFixture.getState": 1, + "window.maka.settings.bots.listStatuses": 2, + "window.maka.settings.bots.restart": 1, + "window.maka.settings.bots.subscribeStatusChanges": 1, + "window.maka.settings.testBotChannel": 2 + }, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": { + "useEffect": 3, + "useMountedRef": 1, + "useRef": 1, + "useState": 6, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-bot-copy": 1, + "../locales/settings-test-result-copy": 1, + "./bot-chat-detail": 1, + "./bot-chat-overview": 1, + "./bot-chat-shared": 1, + "./settings-error-copy": 1, + "@maka/core/bot-chat-settings": 1, + "@maka/core/bot-onboarding": 1, + "@maka/core/settings": 1, + "@maka/runtime/bots": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/bot-chat-shared.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-bot-copy": 1, + "@maka/core/bot-chat-settings": 1, + "@maka/core/ui-locale": 1, + "@maka/runtime/bots": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/bot-onboarding-modal.tsx": { + "bridgePaths": { + "window.maka.settings.bots.onboarding.cancel": 1, + "window.maka.settings.bots.onboarding.openInBrowser": 1, + "window.maka.settings.bots.onboarding.poll": 1, + "window.maka.settings.bots.onboarding.start": 1 + }, + "environmentCapabilities": { + "window.clearTimeout": 1, + "window.setTimeout": 1 + }, + "hookCalls": { + "useEffect": 3, + "useMountedRef": 1, + "useRef": 4, + "useState": 3, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-bot-copy": 1, + "./bot-chat-shared": 1, + "./settings-error-copy": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Layout": 1, + "@maka/core/bot-onboarding": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/bot-settings-view-model.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/bot-chat-settings": 1, + "@maka/core/bot-events": 1, + "@maka/runtime/bots": 1 + } + }, + "src/renderer/settings/bot-wechat-login.tsx": { + "bridgePaths": { + "window.maka.settings.bots.wechatQrCode": 1 + }, + "environmentCapabilities": { + "window.clearInterval": 1, + "window.setInterval": 1 + }, + "hookCalls": { + "useEffect": 2, + "useRef": 2, + "useState": 4, + "useUiLocale": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-bot-copy": 1, + "./password-input": 1, + "./settings-error-copy": 1, + "@astryxdesign/core/Collapsible": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Layout": 1, + "@maka/core/bot-chat-settings": 1, + "@maka/runtime/bots": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/connection-name-draft.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/custom-pet-library-model.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/pet": 1 + } + }, + "src/renderer/settings/custom-pet-settings-section.tsx": { + "bridgePaths": { + "window.maka.pets.getSelection": 1, + "window.maka.pets.importLocalDirectory": 1, + "window.maka.pets.list": 1, + "window.maka.pets.remove": 1, + "window.maka.pets.select": 1, + "window.maka.pets.subscribeChanges": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useMountedRef": 1, + "useRef": 1, + "useState": 3, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-preferences-copy.js": 1, + "./custom-pet-library-model.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "@astryxdesign/core": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/daily-review-settings-page.tsx": { + "bridgePaths": { + "window.maka.dailyReview": 2, + "window.maka.dailyReview.getConfig": 3, + "window.maka.dailyReview.setConfig": 3 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useActionGuard": 1, + "useEffect": 2, + "useMountedRef": 1, + "useRuntimeHostSettingsErrorReporter": 1, + "useRuntimeHostSettingsTarget": 1, + "useState": 6, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-daily-review-copy": 1, + "../model-catalog-choices": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "./settings-skeleton": 1, + "./use-action-guard": 1, + "@astryxdesign/core": 1, + "@maka/core/daily-review": 1, + "@maka/core/llm-connections": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/data-settings-page.tsx": { + "bridgePaths": { + "window.maka.app.info": 1, + "window.maka.app.openPath": 1, + "window.maka.config.export": 1, + "window.maka.config.import": 1 + }, + "environmentCapabilities": { + "history": 1, + "navigator.clipboard.writeText": 1, + "window": 2 + }, + "hookCalls": { + "useActionGuard": 1, + "useEffect": 1, + "useMountedRef": 1, + "useOptionalRuntimeHostSettingsTarget": 1, + "useState": 6, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-data-copy": 1, + "../locales/settings-shared-copy.js": 1, + "../open-path": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-rows": 1, + "./settings-section": 1, + "./use-action-guard": 1, + "@maka/storage/config-transfer": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/general-settings-page.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "window": 4 + }, + "hookCalls": { + "useActionGuard": 2, + "useEffect": 1, + "useKeyedActionGuard": 1, + "useMountedRef": 2, + "useOptimisticSettingsDraft": 1, + "useOptionalRuntimeHostSettingsTarget": 2, + "useRuntimeHostSettingsTarget": 2, + "useState": 7, + "useToast": 4, + "useUiLocale": 4 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-preferences-copy.js": 1, + "../locales/settings-shared-copy.js": 1, + "../locales/settings-test-result-copy.js": 1, + "../locales/shell-copy.js": 1, + "./password-input": 1, + "./personalization-settings-section": 1, + "./provider-brand-marks": 1, + "./runtime-host-settings-bridge.js": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-resource-state.js": 1, + "./settings-section": 1, + "./settings-skeleton.js": 1, + "./use-action-guard": 1, + "./use-optimistic-settings-draft": 1, + "@maka/core/chat-model-choice": 1, + "@maka/core/llm-connections": 1, + "@maka/core/model-thinking": 1, + "@maka/core/settings": 3, + "@maka/core/settings/network-settings": 1, + "@maka/ui": 2, + "react": 1 + } + }, + "src/renderer/settings/health-center-page.tsx": { + "bridgePaths": { + "window.maka.health.getSnapshot": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useRuntimeHostSettingsTarget": 1, + "useState": 5, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-health-copy": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "./settings-skeleton": 1, + "./settings-status-summary-filter": 1, + "@astryxdesign/core": 1, + "@maka/core/health": 2, + "@maka/ui": 2, + "react": 1 + } + }, + "src/renderer/settings/import-tasks-settings-page.tsx": { + "bridgePaths": { + "window.maka.externalSessions.import": 1, + "window.maka.externalSessions.list": 2, + "window.maka.externalSessions.listSources": 1 + }, + "environmentCapabilities": { + "clearTimeout": 2, + "setTimeout": 2 + }, + "hookCalls": { + "useEffect": 4, + "useMountedRef": 1, + "useRef": 3, + "useRuntimeHostSettingsTarget": 1, + "useState": 18, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../../preload/external-session-catalog.js": 1, + "../locales/external-session-import-copy.js": 1, + "../locales/shell-copy.js": 1, + "./runtime-host-settings-target.js": 1, + "./settings-section.js": 1, + "@astryxdesign/core/Banner": 1, + "@astryxdesign/core/Button": 1, + "@astryxdesign/core/CheckboxInput": 1, + "@astryxdesign/core/EmptyState": 1, + "@astryxdesign/core/List": 1, + "@astryxdesign/core/SegmentedControl": 1, + "@astryxdesign/core/Stack": 1, + "@astryxdesign/core/TextInput": 1, + "@maka/core/external-session": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/memory-entry-list.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-memory-copy": 1, + "./memory-settings-labels": 1, + "@astryxdesign/core": 1, + "@maka/core/local-memory": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/memory-settings-labels.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-memory-copy": 1, + "@maka/core/local-memory": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/memory-settings-page.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": { + "useMemoryDocumentController": 1, + "useState": 2, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-memory-copy": 1, + "../locales/settings-shared-copy.js": 1, + "./memory-entry-list": 1, + "./memory-settings-labels": 1, + "./memory-settings-sections": 1, + "./settings-section": 1, + "./use-memory-settings-controller": 1, + "@maka/core/settings": 1, + "@maka/ui": 2, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/memory-settings-sections.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-memory-copy": 1, + "./settings-section": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/memory-settings-view-model.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-memory-copy.js": 1, + "./memory-settings-labels.js": 1, + "@maka/core/local-memory": 2, + "@maka/core/settings": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/nav-group-summary.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/oauth-login-flow-guard.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/optimistic-settings-draft-controller.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/password-input.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "navigator.clipboard.writeText": 1, + "window.clearTimeout": 2, + "window.setTimeout": 1 + }, + "hookCalls": { + "useActionGuard": 1, + "useEffect": 1, + "useMountedRef": 1, + "useRef": 1, + "useState": 3, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-preferences-copy.js": 1, + "./use-action-guard": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/permission-center-page.tsx": { + "bridgePaths": { + "window.maka.capabilities.getSnapshot": 1, + "window.maka.permissions.getSnapshot": 1, + "window.maka.permissions.openSystemSettings": 1, + "window.maka.permissions.requestAccess": 1, + "window.maka.permissions.startDragOnboarding": 1 + }, + "environmentCapabilities": { + "document.addEventListener": 1, + "document.removeEventListener": 1, + "document.visibilityState": 1, + "window.addEventListener": 1, + "window.removeEventListener": 1 + }, + "hookCalls": { + "useActionGuard": 1, + "useEffect": 3, + "useMountedRef": 1, + "useRuntimeHostSettingsErrorReporter": 1, + "useRuntimeHostSettingsTarget": 1, + "useState": 7, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/permission-center-copy": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "./settings-skeleton": 1, + "./settings-status-summary-filter": 1, + "./use-action-guard": 1, + "@astryxdesign/core": 1, + "@maka/core/capabilities": 2, + "@maka/core/ui-locale": 1, + "@maka/ui": 3, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/personalization-settings-section.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "clearTimeout": 3, + "setTimeout": 1, + "window": 1 + }, + "hookCalls": { + "useEffect": 2, + "useMountedRef": 1, + "useOptionalRuntimeHostSettingsTarget": 1, + "useRef": 4, + "useState": 4, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-preferences-copy.js": 1, + "../locales/settings-shared-copy": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-expandable-row": 1, + "./settings-section": 1, + "./settings-skeleton.js": 1, + "@astryxdesign/core": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/projects-settings-page.tsx": { + "bridgePaths": { + "window.maka.app.info": 1, + "window.maka.projects.add": 1, + "window.maka.projects.archive": 1, + "window.maka.projects.getSnapshot": 1, + "window.maka.projects.rename": 1, + "window.maka.projects.reveal": 1, + "window.maka.projects.subscribeChanges": 1, + "window.maka.runtimeHostProfiles.subscribeChanges": 1 + }, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": { + "useEffect": 1, + "useKeyedActionGuard": 1, + "useMountedRef": 1, + "useOptionalRuntimeHostSettingsTarget": 1, + "useRef": 2, + "useState": 6, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../locales/settings-projects-copy.js": 1, + "../locales/settings-shared-copy.js": 1, + "../project-path-display.js": 1, + "../remote-project-directory-dialog.js": 1, + "./runtime-host-interaction-boundary.js": 1, + "./runtime-host-profiles-section.js": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "./use-action-guard": 1, + "@astryxdesign/core": 1, + "@maka/core/project": 1, + "@maka/core/settings": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/provider-add-form.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useActionGuard": 1, + "useMountedRef": 1, + "useState": 11, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy": 1, + "../model-catalog-choices": 1, + "./password-input": 1, + "./provider-add-submission": 1, + "./provider-display": 1, + "./provider-panel-shared": 1, + "./request-customization-editor": 1, + "./use-action-guard": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/Collapsible": 1, + "@maka/core/llm-connections": 3, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/provider-add-model-dialog.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useState": 4, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/FormLayout": 1, + "@astryxdesign/core/Layout": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/provider-add-submission.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/llm-connections": 2 + } + }, + "src/renderer/settings/provider-brand-marks.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../assets/provider-brands/alibabacloud.svg": 1, + "../assets/provider-brands/cerebras.svg": 1, + "../assets/provider-brands/cloudflare.svg": 1, + "../assets/provider-brands/cohere.svg": 1, + "../assets/provider-brands/deepinfra.svg": 1, + "../assets/provider-brands/fireworks.svg": 1, + "../assets/provider-brands/groq.svg": 1, + "../assets/provider-brands/huggingface.svg": 1, + "../assets/provider-brands/hunyuan.svg": 1, + "../assets/provider-brands/lmstudio.svg": 1, + "../assets/provider-brands/localai.svg": 1, + "../assets/provider-brands/mistral.svg": 1, + "../assets/provider-brands/nvidia.svg": 1, + "../assets/provider-brands/opencode.svg": 1, + "../assets/provider-brands/openrouter.svg": 1, + "../assets/provider-brands/stepfun.svg": 1, + "../assets/provider-brands/tencentcloud.svg": 1, + "../assets/provider-brands/together.svg": 1, + "../assets/provider-brands/vercel.svg": 1, + "../assets/provider-brands/volcengine.svg": 1, + "../assets/provider-brands/xai.svg": 1, + "../assets/provider-brands/xiaomimimo.svg": 1, + "../assets/provider-brands/zai.svg": 1, + "../assets/provider-brands/zenmux.svg": 1, + "@maka/core/llm-connections": 1, + "react": 1, + "simple-icons": 1 + } + }, + "src/renderer/settings/provider-catalog-page.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useOAuthCards": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy": 1, + "./provider-add-form": 1, + "./provider-display": 1, + "./provider-oauth-section": 1, + "./provider-panel-shared": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 1, + "@maka/core/provider-registry": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1 + } + }, + "src/renderer/settings/provider-connection-detail.tsx": { + "bridgePaths": { + "window.maka.githubCopilotSubscription.connectExistingLogin": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useActionGuard": 1, + "useConnectionDetail": 1, + "useEffect": 2, + "useMountedRef": 3, + "useOAuthLoginFlow": 1, + "useRuntimeHostSettingsErrorReporter": 3, + "useRuntimeHostSettingsTarget": 1, + "useState": 8, + "useToast": 2, + "useUiLocale": 4 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy": 1, + "./password-input": 1, + "./provider-add-model-dialog": 1, + "./provider-display": 1, + "./provider-enabled-model-manager": 1, + "./provider-endpoint-presentation": 1, + "./provider-panel-shared": 1, + "./relay-thinking-bulk": 1, + "./request-customization-editor": 1, + "./runtime-host-settings-target.js": 1, + "./settings-expandable-row": 1, + "./settings-section": 1, + "./use-action-guard": 1, + "./use-connection-detail": 1, + "./use-oauth-login-flow": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 1, + "@maka/core/model-metadata": 1, + "@maka/core/model-thinking": 1, + "@maka/ui": 2, + "react": 1 + } + }, + "src/renderer/settings/provider-connection-status.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/provider-registry": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/provider-display-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/llm-connections": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/settings/provider-display.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./provider-brand-marks": 1, + "./provider-display-copy": 1, + "@maka/core/llm-connections": 1 + } + }, + "src/renderer/settings/provider-enabled-model-manager.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy": 1, + "@astryxdesign/core": 1, + "@maka/core/model-catalog": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/provider-endpoint-presentation.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/display-redaction": 1, + "@maka/core/llm-connections": 1, + "@maka/core/model-metadata": 1 + } + }, + "src/renderer/settings/provider-oauth-section.tsx": { + "bridgePaths": { + "window.maka.githubCopilotSubscription.connectExistingLogin": 1, + "window.maka.githubCopilotSubscription.getAccountState": 2, + "window.maka.githubCopilotSubscription.logout": 1, + "window.maka.githubCopilotSubscription.refreshTokens": 1, + "window.maka.openAiCodex": 1, + "window.maka.openAiCodex.getAccountState": 1, + "window.maka.xaiOAuth": 1, + "window.maka.xaiOAuth.getAccountState": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useMountedRef": 1, + "useOAuthLoginFlow": 2, + "useRef": 1, + "useRuntimeHostSettingsGenerationKey": 1, + "useRuntimeHostSettingsTarget": 3, + "useState": 2, + "useUiLocale": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../locales/settings-provider-copy": 1, + "./runtime-host-settings-bridge.js": 1, + "./runtime-host-settings-target.js": 1, + "./use-oauth-login-flow": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/provider-panel-shared.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../shared/desktop-connection-snapshot.js": 1, + "../locales/settings-provider-copy.js": 1, + "../model-connection-errors.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/redaction": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/settings/providers-panel.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "document.querySelector": 1 + }, + "hookCalls": { + "useEffect": 4, + "useMountedRef": 1, + "useRef": 6, + "useRuntimeHostSettingsErrorReporter": 1, + "useSettingsRouteFocus": 1, + "useState": 6, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy": 1, + "./provider-catalog-page": 1, + "./provider-connection-detail": 1, + "./provider-connection-status": 1, + "./provider-display": 1, + "./provider-oauth-section": 1, + "./provider-panel-shared": 2, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-route-focus": 1, + "./settings-route-header": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 1, + "@maka/core/provider-registry": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/relay-profile-draft.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/model-thinking": 1 + } + }, + "src/renderer/settings/relay-thinking-bulk.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/model-thinking": 1 + } + }, + "src/renderer/settings/request-customization-editor.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./password-input": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 1, + "@maka/core/runtime-policy": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1 + } + }, + "src/renderer/settings/runtime-host-connection-code-dialog.tsx": { + "bridgePaths": { + "window.maka.runtimeHostProfiles.importConnectionCode": 1 + }, + "environmentCapabilities": { + "navigator.clipboard.writeText": 1 + }, + "hookCalls": { + "useState": 2, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-projects-copy.js": 1, + "./settings-error-copy.js": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Layout": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/runtime-host-interaction-boundary.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "react": 1 + } + }, + "src/renderer/settings/runtime-host-management-dialog.tsx": { + "bridgePaths": { + "window.maka.runtimeHostManagement.configureDirectPeer": 1, + "window.maka.runtimeHostManagement.configureProjectDirectories": 1, + "window.maka.runtimeHostManagement.getDirectPeer": 3, + "window.maka.runtimeHostManagement.getUpdatePolicy": 2, + "window.maka.runtimeHostManagement.listCredentials": 1, + "window.maka.runtimeHostManagement.reconcileUpdate": 1, + "window.maka.runtimeHostManagement.revokeCredential": 1, + "window.maka.runtimeHostManagement.rotateCredential": 1, + "window.maka.runtimeHostManagement.run": 2, + "window.maka.runtimeHostManagement.setUpdatePolicy": 1, + "window.maka.runtimeHostManagement.subscribeProgress": 1, + "window.maka.runtimeHostManagement.update": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useLayoutEffect": 1, + "useRef": 2, + "useState": 17, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../../shared/runtime-host-project-directory-policy.js": 1, + "../locales/settings-projects-copy.js": 1, + "./runtime-host-project-directory-editor.js": 1, + "./settings-error-copy.js": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Layout": 1, + "@astryxdesign/core/Text": 1, + "@maka/core/ui-locale": 1, + "@maka/runtime-host/client": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/runtime-host-onboarding-dialog.tsx": { + "bridgePaths": { + "window.maka.runtimeHostOnboarding.cancel": 2, + "window.maka.runtimeHostOnboarding.getSnapshot": 1, + "window.maka.runtimeHostOnboarding.listWslDistributions": 1, + "window.maka.runtimeHostOnboarding.reset": 2, + "window.maka.runtimeHostOnboarding.start": 1, + "window.maka.runtimeHostOnboarding.subscribe": 1 + }, + "environmentCapabilities": { + "navigator.userAgent.includes": 2 + }, + "hookCalls": { + "useEffect": 2, + "useRef": 2, + "useState": 8, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../../shared/runtime-host-project-directory-policy.js": 1, + "../locales/settings-projects-copy.js": 1, + "./runtime-host-project-directory-editor.js": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Layout": 1, + "@astryxdesign/core/Text": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/runtime-host-profiles-section.tsx": { + "bridgePaths": { + "window.maka.localRuntimeHostRemoteAccess.createConnectionCode": 1, + "window.maka.localRuntimeHostRemoteAccess.disable": 1, + "window.maka.localRuntimeHostRemoteAccess.enable": 1, + "window.maka.localRuntimeHostRemoteAccess.getSnapshot": 1, + "window.maka.localRuntimeHostRemoteAccess.revokeSharedAccess": 1, + "window.maka.localRuntimeHostRemoteAccess.uninstall": 1, + "window.maka.runtimeHostProfiles.addAndEnable": 1, + "window.maka.runtimeHostProfiles.getSnapshot": 1, + "window.maka.runtimeHostProfiles.remove": 1, + "window.maka.runtimeHostProfiles.resolvePairingRecovery": 1, + "window.maka.runtimeHostProfiles.setDefault": 1, + "window.maka.runtimeHostProfiles.setEnabled": 1, + "window.maka.runtimeHostProfiles.subscribeChanges": 1 + }, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": { + "useEffect": 1, + "useMountedRef": 1, + "useState": 8, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../locales/settings-projects-copy.js": 1, + "./password-input.js": 1, + "./runtime-host-connection-code-dialog.js": 1, + "./runtime-host-management-dialog.js": 1, + "./runtime-host-onboarding-dialog.js": 1, + "./settings-error-copy.js": 1, + "./settings-section.js": 1, + "@astryxdesign/core": 1, + "@maka/runtime-host/client": 1, + "@maka/runtime-host/protocol": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/runtime-host-project-directory-editor.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-projects-copy.js": 1, + "@maka/runtime-host/protocol": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/runtime-host-settings-bridge.ts": { + "bridgePaths": { + "window.maka.connections.create": 1, + "window.maka.connections.delete": 1, + "window.maka.connections.fetchModels": 1, + "window.maka.connections.getRequestHeaders": 1, + "window.maka.connections.getSnapshot": 1, + "window.maka.connections.hasSecret": 1, + "window.maka.connections.setDefault": 1, + "window.maka.connections.setDefaultModel": 1, + "window.maka.connections.setRequestHeaders": 1, + "window.maka.connections.subscribeEvents": 1, + "window.maka.connections.test": 1, + "window.maka.connections.update": 1 + }, + "environmentCapabilities": { + "window": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "./provider-panel-shared.js": 1, + "./use-oauth-login-flow.js": 1 + } + }, + "src/renderer/settings/runtime-host-settings-target.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useToast": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/runtime-host-ssh-terminal-dialog.tsx": { + "bridgePaths": { + "window.maka.runtimeHostSshTerminal.cancel": 1, + "window.maka.runtimeHostSshTerminal.getSnapshot": 1, + "window.maka.runtimeHostSshTerminal.resize": 1, + "window.maka.runtimeHostSshTerminal.subscribe": 1, + "window.maka.runtimeHostSshTerminal.write": 1 + }, + "environmentCapabilities": { + "requestAnimationFrame": 1, + "ResizeObserver": 1 + }, + "hookCalls": { + "useEffect": 2, + "useRef": 5, + "useState": 2, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../locales/settings-projects-copy.js": 1, + "../theme": 1, + "@astryxdesign/core/Dialog": 1, + "@astryxdesign/core/Layout": 1, + "@maka/ui": 1, + "@xterm/addon-fit": 1, + "@xterm/xterm": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-error-copy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-shared-copy.js": 1, + "@maka/core/redaction": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1 + } + }, + "src/renderer/settings/settings-expandable-row.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useRef": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./settings-section": 1, + "@astryxdesign/core": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-metric-card.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/ui": 1 + } + }, + "src/renderer/settings/settings-modal.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useRef": 2, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../locales/settings-shared-copy": 1, + "./settings-nav": 2, + "./settings-surface": 1, + "./tasks-settings-page": 1, + "./ui-locale-update-gate": 1, + "@maka/core/llm-connections": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-nav.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../browser-storage.js": 1, + "../locales/settings-navigation-copy.js": 1, + "./nav-group-summary.js": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-request-authority.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/settings-resource-state.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/settings-route-focus.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "window.cancelAnimationFrame": 1, + "window.requestAnimationFrame": 1 + }, + "hookCalls": { + "useEffect": 2, + "useRef": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "react": 1 + } + }, + "src/renderer/settings/settings-route-header.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@astryxdesign/core": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-rows.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./settings-section.js": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-section.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@astryxdesign/core": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-skeleton.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-shared-copy.js": 1, + "./settings-section.js": 1, + "@astryxdesign/core": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/settings-snapshot-cache.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/settings": 1 + } + }, + "src/renderer/settings/settings-status-badge.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/settings/settings-status-summary-filter.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@astryxdesign/core": 1 + } + }, + "src/renderer/settings/settings-surface.tsx": { + "bridgePaths": { + "window.maka": 1, + "window.maka.runtimeHostProfiles.getSnapshot": 1, + "window.maka.runtimeHostProfiles.subscribeChanges": 1, + "window.maka.settings.get": 1, + "window.maka.settings.getClient": 1, + "window.maka.settings.subscribeClientChanged": 1, + "window.maka.settings.subscribeExternalChanged": 1, + "window.maka.settings.testNetworkProxy": 1, + "window.maka.settings.update": 1, + "window.maka.settings.updateClient": 1, + "window.maka.settings.usageStats": 1 + }, + "environmentCapabilities": { + "window": 2, + "window.addEventListener": 1, + "window.removeEventListener": 1 + }, + "hookCalls": { + "useEffect": 10, + "useMediaQuery": 1, + "useMountedRef": 1, + "useRef": 10, + "useState": 12, + "useToast": 1, + "useUiLocale": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../../shared/settings-ownership.js": 1, + "../browser-storage": 1, + "../locales/settings-navigation-copy.js": 1, + "../locales/settings-shared-copy.js": 1, + "./about-settings-page": 1, + "./appearance-settings-page": 1, + "./bot-chat-settings-page": 1, + "./daily-review-settings-page": 1, + "./data-settings-page": 1, + "./general-settings-page": 1, + "./health-center-page": 1, + "./import-tasks-settings-page": 1, + "./memory-settings-page": 1, + "./permission-center-page": 1, + "./projects-settings-page": 1, + "./providers-panel": 1, + "./runtime-host-interaction-boundary.js": 1, + "./runtime-host-settings-bridge.js": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-nav": 1, + "./settings-request-authority.js": 1, + "./settings-resource-state.js": 1, + "./settings-rows": 1, + "./settings-section": 1, + "./settings-skeleton": 1, + "./settings-snapshot-cache.js": 1, + "./subagent-settings-page": 1, + "./tasks-settings-page": 1, + "./ui-locale-update-gate": 1, + "./usage-settings-page": 1, + "./web-search-settings-page": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 1, + "@maka/core/settings": 2, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/subagent-preset-presentation.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./settings-status-badge.js": 1, + "@maka/core/llm-connections": 2, + "@maka/core/provider-registry": 1, + "@maka/core/subagent-settings": 1 + } + }, + "src/renderer/settings/subagent-settings-page.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "document.querySelector": 2, + "window": 1 + }, + "hookCalls": { + "useRef": 2, + "useRuntimeHostSettingsErrorReporter": 1, + "useSettingsRouteFocus": 1, + "useState": 5, + "useToast": 1, + "useUiLocale": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-subagents-copy.js": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy.js": 1, + "./settings-route-focus.js": 1, + "./settings-route-header.js": 1, + "./settings-rows.js": 1, + "./settings-section.js": 1, + "./settings-status-badge.js": 1, + "./subagent-preset-presentation.js": 1, + "@astryxdesign/core": 1, + "@maka/core/llm-connections": 2, + "@maka/core/model-thinking": 2, + "@maka/core/settings": 1, + "@maka/core/subagent-settings": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/task-catalog-rows.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../features/session-navigation/index.js": 1, + "@maka/core/session": 1 + } + }, + "src/renderer/settings/tasks-settings-page.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useMountedRef": 1, + "useState": 2, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../features/session-navigation": 1, + "../locales/settings-shared-copy.js": 1, + "../locales/settings-tasks-copy.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "./task-catalog-rows": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/List": 1, + "@astryxdesign/core/TextInput": 1, + "@maka/core/project": 1, + "@maka/core/relative-time": 1, + "@maka/runtime-host/profile-kind": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/ui-locale-update-gate.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/settings/usage-settings-page.tsx": { + "bridgePaths": {}, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": { + "useActionGuard": 1, + "useOptimisticSettingsDraft": 1, + "useState": 1, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../shared/runtime-host-identity.js": 1, + "../locales/settings-usage-copy": 1, + "./settings-error-copy": 1, + "./settings-metric-card": 1, + "./settings-section": 1, + "./use-action-guard": 1, + "./use-optimistic-settings-draft": 1, + "@astryxdesign/core": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/core/usage-ledger-merge": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/settings/use-action-guard.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useRef": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./action-guard.js": 1, + "./oauth-login-flow-guard.js": 1, + "react": 1 + } + }, + "src/renderer/settings/use-connection-detail.ts": { + "bridgePaths": { + "window.maka.openAiCodex": 1, + "window.maka.xaiOAuth": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 6, + "useKeyedActionGuard": 1, + "useMountedRef": 1, + "useRef": 4, + "useRuntimeHostSettingsErrorReporter": 1, + "useRuntimeHostSettingsTarget": 1, + "useState": 14, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../../preload/bridge-contract.js": 1, + "../locales/settings-provider-copy": 1, + "./connection-name-draft.js": 1, + "./provider-connection-status": 1, + "./provider-panel-shared": 1, + "./relay-profile-draft": 1, + "./relay-thinking-bulk": 1, + "./runtime-host-settings-bridge.js": 1, + "./runtime-host-settings-target.js": 1, + "./use-action-guard": 1, + "./use-oauth-login-flow": 1, + "@maka/core/llm-connections": 3, + "@maka/core/model-catalog": 1, + "@maka/core/model-thinking": 1, + "@maka/core/provider-registry": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/use-memory-settings-controller.ts": { + "bridgePaths": { + "window.maka.app.openPath": 1, + "window.maka.memory.getState": 1, + "window.maka.memory.openBackup": 1, + "window.maka.memory.openFile": 1, + "window.maka.memory.openLatestBackup": 1, + "window.maka.memory.reset": 1, + "window.maka.memory.restoreBackup": 1, + "window.maka.memory.restoreLatestBackup": 1, + "window.maka.memory.save": 3, + "window.maka.memory.setAgentReadEnabled": 1, + "window.maka.memory.setEnabled": 1 + }, + "environmentCapabilities": { + "navigator.clipboard.writeText": 4, + "requestAnimationFrame": 1 + }, + "hookCalls": { + "useEffect": 2, + "useKeyedActionGuard": 1, + "useRef": 4, + "useRuntimeHostSettingsErrorReporter": 1, + "useRuntimeHostSettingsTarget": 1, + "useState": 11, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../local-memory-digest": 1, + "../locales/settings-memory-copy": 1, + "../open-path": 1, + "../scroll-motion-policy": 1, + "./memory-settings-labels": 1, + "./memory-settings-view-model": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./use-action-guard": 1, + "@maka/core/local-memory": 2, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/use-oauth-login-flow.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useMountedRef": 1, + "useRef": 2, + "useRuntimeHostSettingsErrorReporter": 1, + "useState": 5, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-provider-copy": 1, + "./oauth-login-flow-guard": 1, + "./runtime-host-settings-target.js": 1, + "@maka/core/redaction": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/settings/use-optimistic-settings-draft.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useMountedRef": 1, + "useRef": 4, + "useState": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./optimistic-settings-draft-controller": 1, + "@maka/ui": 1, + "react": 2 + } + }, + "src/renderer/settings/web-search-settings-page.tsx": { + "bridgePaths": { + "window.maka.webSearch.query": 1, + "window.maka.webSearch.test": 1 + }, + "environmentCapabilities": { + "window": 2 + }, + "hookCalls": { + "useKeyedActionGuard": 1, + "useMountedRef": 1, + "useRef": 1, + "useRuntimeHostSettingsErrorReporter": 1, + "useRuntimeHostSettingsTarget": 1, + "useState": 8, + "useToast": 1, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../locales/settings-shared-copy.js": 1, + "../locales/settings-web-search-copy": 1, + "./password-input": 1, + "./runtime-host-settings-target.js": 1, + "./settings-error-copy": 1, + "./settings-section": 1, + "./use-action-guard": 1, + "@astryxdesign/core": 1, + "@maka/core/search": 1, + "@maka/core/settings": 1, + "@maka/core/web-search": 2, + "@maka/ui": 2, + "react": 1 + } + }, + "src/renderer/settled-message-merge.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session": 1 + } + }, + "src/renderer/settled-session-transients.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session": 1, + "@maka/ui": 1 + } + }, + "src/renderer/shell-chat-model-selection.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/chat-model-choice": 1 + } + }, + "src/renderer/shell-run-update-state.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/events": 1, + "@maka/core/shell-run-result": 1 + } + }, + "src/renderer/side-chat-command.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/skill-invocation-feedback.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy.js": 1, + "@maka/core/ui-locale": 1, + "@maka/runtime/skill-invocation": 1 + } + }, + "src/renderer/stable-actions.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/stale-sessions.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session-send-projection": 1 + } + }, + "src/renderer/task-readiness-notice.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session-send-projection": 1, + "@maka/core/task-submission-readiness": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/theme.ts": { + "bridgePaths": { + "window.maka.appWindow.setThemeSource": 1, + "window.maka.appWindow.setTitlebarControlsVisible": 1, + "window.maka.appWindow.setTitleBarOverlayTheme": 1 + }, + "environmentCapabilities": { + "document.createElement": 1, + "document.documentElement": 9, + "document.documentElement.classList.contains": 1, + "document.documentElement.classList.toggle": 1, + "document.documentElement.removeAttribute": 1, + "document.documentElement.setAttribute": 1, + "document.documentElement.style.colorScheme": 1, + "document.documentElement.style.fontSize": 1, + "document.querySelector": 1, + "window.matchMedia": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./astryx-theme/type-scale.js": 1, + "./browser-storage": 1, + "./titlebar-dim-color.js": 1, + "@maka/core/settings": 1 + } + }, + "src/renderer/titlebar-dim-color.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/titlebar-modal-sync.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "document.documentElement": 1, + "document.querySelector": 1, + "MutationObserver": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./theme": 1 + } + }, + "src/renderer/transient-message-projection.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/session": 1, + "@maka/ui": 1 + } + }, + "src/renderer/turn-footer-actions.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "@maka/core/session": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/renderer/use-active-execution-boundary.ts": { + "bridgePaths": { + "window.maka.sessions.readExecutionBoundary": 1 + }, + "environmentCapabilities": { + "clearTimeout": 1, + "setTimeout": 1 + }, + "hookCalls": { + "useEffect": 2, + "useRef": 1, + "useState": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/sandbox-boundary": 1, + "react": 1 + } + }, + "src/renderer/use-composer-attachments.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useRef": 2, + "useState": 2, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./composer-attachments.js": 1, + "./locales/conversation-copy.js": 1, + "./locales/shell-copy.js": 1, + "./pending-items.js": 1, + "@maka/core/attachments": 1, + "@maka/core/events": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/use-composer-mentions.ts": { + "bridgePaths": { + "window.maka.mcp.subscribeChanges": 1, + "window.maka.newTasks.listInvocableSkills": 1, + "window.maka.newTasks.searchFiles": 1, + "window.maka.newTasks.subscribeChanges": 1, + "window.maka.sessions.subscribeChanges": 1, + "window.maka.skills.listInvocable": 1, + "window.maka.workspace.searchFiles": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "@maka/core/settings": 1, + "@maka/runtime/skill-invocation": 1, + "react": 1 + } + }, + "src/renderer/use-deep-research-run.ts": { + "bridgePaths": { + "window.maka.deepResearch.get": 1, + "window.maka.deepResearch.subscribeChanges": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/deep-research-run": 1, + "react": 1 + } + }, + "src/renderer/use-delayed-flag.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "window.clearTimeout": 1, + "window.setTimeout": 1 + }, + "hookCalls": { + "useEffect": 2, + "useRef": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./model-wait-state.js": 1, + "react": 1 + } + }, + "src/renderer/use-new-task-choice.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./new-task-reload-intent.js": 1, + "react": 1 + } + }, + "src/renderer/use-onboarding-snapshot.ts": { + "bridgePaths": { + "window.maka.connections.subscribeEvents": 1, + "window.maka.onboarding.getSnapshot": 1, + "window.maka.sessions.subscribeChanges": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useRef": 5, + "useState": 2, + "useUiLocale": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./locales/onboarding-copy.js": 1, + "@maka/core/llm-connections": 1, + "@maka/core/onboarding-milestone": 1, + "@maka/core/redaction": 1, + "@maka/core/session": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/use-pending-action-registry.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "clearTimeout": 2, + "setTimeout": 1 + }, + "hookCalls": { + "useRef": 3, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "react": 1 + } + }, + "src/renderer/use-project-context.ts": { + "bridgePaths": { + "window.maka.app.sessionProjectInfo": 1, + "window.maka.projects.getDefaultContext": 1, + "window.maka.projects.getLocalSnapshot": 1, + "window.maka.projects.getSnapshot": 1, + "window.maka.projects.subscribeChanges": 2, + "window.maka.projects.subscribeLocalChanges": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 4, + "useRef": 3, + "useState": 8 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./app-shell-project-actions": 1, + "./default-runtime-host-operation.js": 1, + "@maka/core/project": 1, + "@maka/core/ui-locale": 1, + "@maka/runtime-host/profile-kind": 1, + "react": 1 + } + }, + "src/renderer/use-session-setting-intent.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useRef": 2, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "react": 1 + } + }, + "src/renderer/use-settings-modal.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "document.activeElement": 1, + "document.activeElement.blur": 1 + }, + "hookCalls": { + "useState": 5 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./browser-storage": 1, + "@maka/core/llm-connections": 1, + "@maka/core/settings": 1, + "react": 1 + } + }, + "src/renderer/use-shell-appearance.ts": { + "bridgePaths": { + "window.maka.e2eFixture.getState": 1, + "window.maka.settings.get": 1, + "window.maka.settings.getClient": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useState": 6 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy": 1, + "./settings/ui-locale-update-gate": 1, + "./theme": 1, + "@maka/core/model-thinking": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "react": 1 + } + }, + "src/renderer/use-shell-chat-model.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useNewTaskChoice": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./composer-defaults": 1, + "./locales/conversation-copy.js": 1, + "./session-health-notice": 1, + "./shell-chat-model-selection": 2, + "./use-new-task-choice.js": 1, + "@maka/core/chat-model-choice": 1, + "@maka/core/llm-connections": 1, + "@maka/core/model-thinking": 1, + "@maka/core/session": 1, + "@maka/core/session-send-projection": 1, + "@maka/core/settings": 1, + "@maka/core/ui-locale": 1, + "react": 1 + } + }, + "src/renderer/use-shell-connections.ts": { + "bridgePaths": { + "window.maka.connections.getSnapshot": 2, + "window.maka.newTasks.getConnections": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useLayoutEffect": 2, + "useRef": 2, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "../shared/desktop-connection-snapshot.js": 1, + "../shared/runtime-host-identity.js": 1, + "./default-runtime-host-operation.js": 1, + "./locales/shell-copy.js": 1, + "./locales/shell-remaining-copy.js": 1, + "@maka/core/connections": 1, + "@maka/core/ui-locale": 1, + "react": 1 + } + }, + "src/renderer/use-shell-live-turn.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useDelayedFlag": 3 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./live-turn-snapshot.js": 1, + "./model-wait-state.js": 1, + "./use-delayed-flag.js": 1, + "@maka/core/session": 1 + } + }, + "src/renderer/use-shell-memory-pill.ts": { + "bridgePaths": { + "window.maka.memory.getState": 2 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useRef": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./default-runtime-host-operation.js": 1, + "./locales/shell-copy.js": 1, + "@maka/core/ui-locale": 1, + "react": 1 + } + }, + "src/renderer/use-shell-resume.ts": { + "bridgePaths": { + "window.maka.sessions.resumeLatest": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useState": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/shell-copy.js": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/use-shell-search.ts": { + "bridgePaths": { + "window.maka.search.thread": 1 + }, + "environmentCapabilities": { + "window": 1 + }, + "hookCalls": { + "useState": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "react": 1 + } + }, + "src/renderer/use-stable-actions.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useLayoutEffect": 1, + "useRef": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./stable-actions.js": 1, + "react": 1 + } + }, + "src/renderer/use-system-ui-locale.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "navigator": 1, + "navigator.languages": 1, + "window": 1, + "window.addEventListener": 1, + "window.removeEventListener": 1 + }, + "hookCalls": { + "useEffect": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/ui-locale": 1, + "react": 1 + } + }, + "src/renderer/use-task-submission-readiness.ts": { + "bridgePaths": { + "window.maka.newTasks.getReadiness": 1, + "window.maka.taskReadiness.getSnapshot": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useRef": 1, + "useState": 2 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "@maka/core/task-submission-readiness": 1, + "react": 1 + } + }, + "src/renderer/use-work-board.ts": { + "bridgePaths": { + "window.maka.workBoard.archive": 1, + "window.maka.workBoard.create": 1, + "window.maka.workBoard.list": 2, + "window.maka.workBoard.remove": 1, + "window.maka.workBoard.subscribeChanges": 1, + "window.maka.workBoard.unarchive": 1, + "window.maka.workBoard.update": 1 + }, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useRef": 2, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../shared/work-board-ipc.js": 1, + "@maka/core/work-board": 1, + "@maka/storage/work-board-store": 1, + "react": 1 + } + }, + "src/renderer/work-board-panel.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 1, + "useRef": 1, + "useState": 7, + "useUiLocale": 1, + "useWorkBoard": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./locales/conversation-copy.js": 1, + "./use-work-board.js": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/Button": 1, + "@astryxdesign/core/TextInput": 1, + "@maka/core/work-board": 1, + "@maka/ui": 1, + "@maka/ui/icons": 1, + "react": 1 + } + }, + "src/renderer/workhub-controller.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./workhub-route-policy.js": 1, + "@maka/runtime-host/protocol": 1 + } + }, + "src/renderer/workhub-coordination-host-scope.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../shared/runtime-host-identity.js": 1, + "./workhub-session-port.js": 1 + } + }, + "src/renderer/workhub-coordination-lifecycle.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1 + } + }, + "src/renderer/workhub-coordination-port.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./desktop-transcript-range-store.js": 1, + "./workhub-controller.js": 2, + "./workhub-session-port.js": 1, + "@maka/core/session": 1, + "@maka/runtime-host/protocol": 1 + } + }, + "src/renderer/workhub-route-policy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./workhub-controller.js": 1 + } + }, + "src/renderer/workhub-send-lease.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "document": 1, + "localStorage": 1, + "window": 1, + "window.localStorage": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/renderer/workhub-session-port.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "window.clearTimeout": 1, + "window.setTimeout": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/transcript-contract.js": 1, + "../shared/runtime-host-identity.js": 1, + "./desktop-transcript-range-store.js": 1, + "./workhub-controller.js": 2, + "@maka/core/session": 1 + } + }, + "src/renderer/workhub-surface.tsx": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": { + "useEffect": 2, + "useRef": 3, + "useState": 8 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./workhub-controller.js": 1, + "./workhub-coordination-port.js": 1, + "./workhub-send-lease.js": 1, + "@astryxdesign/core": 1, + "@astryxdesign/core/Button": 1, + "@maka/core/ui-locale": 1, + "@maka/ui": 1, + "react": 1 + } + }, + "src/renderer/workspace-readiness-recovery.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./onboarding-hero-copy.js": 1, + "@maka/core/onboarding": 1, + "@maka/core/ui-locale": 1 + } + }, + "src/shared/desktop-connection-snapshot.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/chat-model-choice": 1, + "@maka/core/llm-connections": 1 + } + }, + "src/shared/desktop-session-projection.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./runtime-host-identity.js": 1, + "@maka/core/daily-review": 1, + "@maka/core/events": 1, + "@maka/core/session": 1, + "@maka/core/settings": 1, + "@maka/runtime-host/profile-kind": 1 + } + }, + "src/shared/goal-arm.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/runtime/goal-state": 1 + } + }, + "src/shared/runtime-host-identity.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": {} + }, + "src/shared/runtime-host-project-directory-policy.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/runtime-host/protocol": 1 + } + }, + "src/shared/settings-ownership.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/core/settings": 1 + } + }, + "src/shared/work-board-ipc.ts": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@maka/storage/work-board-store": 1 + } + } + } + }, + "rootDebt": { + "src/renderer/app.tsx": { + "importDeclarations": 6, + "bridgePaths": { + "window.maka.appWindow.notifyRendererReady": 1 + }, + "environmentCapabilities": { + "cancelAnimationFrame": 2, + "requestAnimationFrame": 2 + }, + "hookCalls": { + "useAstryxThemeMode": 1, + "useEffect": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./app-shell": 1, + "./astryx-theme-mode": 1, + "./astryx-theme/maka": 1, + "@astryxdesign/core/theme": 1, + "react": 1 + }, + "importSpecifiers": 7, + "nonTriviaTokens": 206 + }, + "src/renderer/main.tsx": { + "importDeclarations": 17, + "bridgePaths": { + "window.maka.onboarding.getSnapshot": 2 + }, + "environmentCapabilities": { + "document.getElementById": 1, + "setTimeout": 2 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "../preload/bridge-contract.js": 1, + "./app": 1, + "./cached-theme-bootstrap": 1, + "./features/goals": 1, + "./features/module-hub": 1, + "./features/session-navigation": 1, + "./features/task-entry": 1, + "./features/workbar": 1, + "./platform/desktop/create-goal-services": 1, + "./platform/desktop/create-module-hub-services": 1, + "./platform/desktop/create-session-navigation-services": 1, + "./platform/desktop/create-task-entry-services": 1, + "./platform/desktop/create-workbar-services": 1, + "./styles.css": 1, + "./use-system-ui-locale": 1, + "@maka/ui": 1, + "react-dom/client": 1 + }, + "importSpecifiers": 16, + "nonTriviaTokens": 412 + } + }, + "rootDebtClosure": { + "src/renderer/astryx-theme-mode.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "document.documentElement": 1, + "document.documentElement.classList.contains": 1, + "MutationObserver": 1 + }, + "hookCalls": { + "useEffect": 1, + "useState": 1 + }, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@astryxdesign/core/theme": 1, + "react": 1 + } + }, + "src/renderer/astryx-theme/maka.js": { + "bridgePaths": {}, + "environmentCapabilities": {}, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "@astryxdesign/theme-neutral": 1 + } + }, + "src/renderer/cached-theme-bootstrap.ts": { + "bridgePaths": {}, + "environmentCapabilities": { + "document.documentElement.classList.add": 1, + "document.documentElement.setAttribute": 1, + "document.documentElement.style.colorScheme": 2, + "window.matchMedia": 1 + }, + "hookCalls": {}, + "lifecycleMethods": {}, + "unresolvedDependencies": 0, + "actionFactories": [], + "dependencyPaths": { + "./browser-storage": 1, + "./theme": 1 + } + } + }, + "ownership": [ + { + "capability": "renderer-bootstrap-entry", + "targetZone": "bootstrap", + "legacyPaths": [ + "src/renderer/main.tsx" + ] + }, + { + "capability": "application-mount-lifecycle", + "targetZone": "composition", + "legacyPaths": [ + "src/renderer/app.tsx" + ] + }, + { + "capability": "shell-root-and-frame", + "targetZone": "shell", + "legacyPaths": [ + "src/renderer/app-shell-chrome-actions.tsx", + "src/renderer/app-shell-detail-panel.tsx" + ] + }, + { + "capability": "app-shell-integration-knot", + "targetZone": "split-by-capability", + "legacyPaths": [ + "src/renderer/app-shell.tsx" + ] + }, + { + "capability": "app-lifecycle", + "targetZone": "application/app-lifecycle", + "legacyPaths": [ + "src/renderer/app-shell-app-update.ts", + "src/renderer/app-shell-build-stamp.ts" + ] + }, + { + "capability": "task-submission-and-composer-drafts", + "targetZone": "features/conversation", + "legacyPaths": [ + "src/renderer/app-shell-chat-actions.ts", + "src/renderer/app-shell-session-start-actions.ts", + "src/renderer/use-app-shell-composer-quotes.ts" + ] + }, + { + "capability": "session-controls-and-turn-actions", + "targetZone": "features/conversation", + "legacyPaths": [ + "src/renderer/app-shell-context-compaction.ts", + "src/renderer/app-shell-revision-actions.ts", + "src/renderer/app-shell-session-settings-actions.ts", + "src/renderer/app-shell-stop-action.ts", + "src/renderer/app-shell-turn-actions.ts" + ] + }, + { + "capability": "conversation-runtime-and-presentation", + "targetZone": "features/conversation", + "legacyPaths": [ + "src/renderer/app-shell-session-events.ts", + "src/renderer/app-shell-session-ui-state.ts", + "src/renderer/app-shell-turn-view-model.ts", + "src/renderer/use-app-shell-session-ui-reads.ts", + "src/renderer/use-app-shell-session-ui-selector.ts" + ] + }, + { + "capability": "session-catalog-and-scope", + "targetZone": "application/session", + "legacyPaths": [ + "src/renderer/use-app-shell-session-list.ts", + "src/renderer/use-app-shell-session-workspace.ts" + ] + }, + { + "capability": "workspace-projection", + "targetZone": "application/workspace", + "legacyPaths": [ + "src/renderer/app-shell-project-actions.ts" + ] + }, + { + "capability": "commands-and-overlays", + "targetZone": "application/overlays", + "legacyPaths": [ + "src/renderer/app-shell-command-actions.ts", + "src/renderer/app-shell-overlays.tsx" + ] + }, + { + "capability": "mixed-bootstrap-and-conversation-effects", + "targetZone": "split-by-capability", + "legacyPaths": [ + "src/renderer/app-shell-copy.ts", + "src/renderer/app-shell-effects.ts" + ] + }, + { + "capability": "desktop-e2e-fixture", + "targetZone": "testing", + "legacyPaths": [ + "src/renderer/app-shell-e2e-fixture.ts" + ] + } + ] +} diff --git a/apps/desktop/scripts/check-renderer-architecture.mjs b/apps/desktop/scripts/check-renderer-architecture.mjs new file mode 100644 index 0000000000..c290c10da3 --- /dev/null +++ b/apps/desktop/scripts/check-renderer-architecture.mjs @@ -0,0 +1,2209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { builtinModules } from 'node:module'; +import { dirname, join, relative, resolve, sep } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { parse } from '@babel/parser'; + +const STATEFUL_HOOKS = new Set([ + 'useActionState', + 'useDeferredValue', + 'useEffect', + 'useImperativeHandle', + 'useInsertionEffect', + 'useLayoutEffect', + 'useOptimistic', + 'useReducer', + 'useRef', + 'useState', + 'useSyncExternalStore', + 'useTransition', +]); +const REACT_LIFECYCLE_METHODS = new Set([ + 'UNSAFE_componentWillMount', + 'UNSAFE_componentWillReceiveProps', + 'UNSAFE_componentWillUpdate', + 'componentDidCatch', + 'componentDidMount', + 'componentDidUpdate', + 'componentWillMount', + 'componentWillReceiveProps', + 'componentWillUnmount', + 'componentWillUpdate', + 'getDerivedStateFromError', + 'getDerivedStateFromProps', + 'getSnapshotBeforeUpdate', + 'shouldComponentUpdate', +]); +const ENVIRONMENT_CALLS = new Set([ + 'BroadcastChannel', + 'EventSource', + 'FileReader', + 'IntersectionObserver', + 'MutationObserver', + 'ResizeObserver', + 'SharedWorker', + 'WebSocket', + 'Worker', + 'XMLHttpRequest', + 'addEventListener', + 'cancelAnimationFrame', + 'clearInterval', + 'clearTimeout', + 'fetch', + 'matchMedia', + 'queueMicrotask', + 'requestAnimationFrame', + 'removeEventListener', + 'setInterval', + 'setTimeout', +]); +const ENVIRONMENT_OBJECTS = new Set([ + 'document', + 'history', + 'indexedDB', + 'localStorage', + 'location', + 'navigator', + 'sessionStorage', +]); +const FORBIDDEN_ENVIRONMENT_IMPORTS = new Set([ + 'electron', + ...builtinModules, + ...builtinModules.map((module) => `node:${module.replace(/^node:/u, '')}`), +]); +const SOURCE_EXTENSION = /\.(?:(?:c|m)?(?:js|ts)x?)$/u; +const SOURCE_FILE = SOURCE_EXTENSION; +const DECLARATION_FILE = /\.d\.(?:(?:c|m)?ts)$/u; +const LEGACY_APP_SHELL_FILE = /^(?:app-shell(?:-.+)?|use-app-shell-.+)\.(?:(?:c|m)?(?:js|ts)x?)$/u; +const LEGACY_APP_SHELL_MODULE = 'src/renderer/app-shell'; +const LEGACY_APP_SHELL_ADAPTER = 'src/renderer/composition/legacy-desktop-region.tsx'; +const RENDERER_ENTRY_INDEX = 'src/renderer/index.html'; +const RENDERER_ENTRY_SOURCE = 'src/renderer/main.tsx'; +const RENDERER_ENTRY_SCRIPT_SOURCE = '/main.tsx'; +const RENDERER_LOADER_SOURCE = 'src/main/main-window.ts'; +const MAIN_RENDERER_LOADER_SOURCE = 'src/main/main-renderer-loader.ts'; +const RENDERER_VITE_CONFIG = 'vite.config.ts'; +const RENDERER_BUILD_SCRIPT = + 'vite build && node scripts/check-renderer-entry-output.mjs && node ../../scripts/check-third-party-notices.mjs'; +const DESKTOP_SELF_PREFIX = '@maka/desktop/'; +const CAPABILITY_DEBT_METRICS = [ + 'actionFactories', + 'bridgePaths', + 'dependencyPaths', + 'environmentCapabilities', + 'hookCalls', + 'lifecycleMethods', + 'unresolvedDependencies', +]; +const ROOT_DEBT_METRICS = [ + ...CAPABILITY_DEBT_METRICS, + 'importDeclarations', + 'importSpecifiers', + 'nonTriviaTokens', +]; +const DEFAULT_LEGACY_GROWTH_DIRECTORIES = [ + 'src/renderer/astryx-theme', + 'src/renderer/computer-use-overlay', + 'src/renderer/locales', + 'src/renderer/settings', +]; +const PARSER_PLUGINS = [ + 'explicitResourceManagement', + 'importAttributes', + 'jsx', + 'typescript', +]; + +function normalizePath(value) { + return value.split(sep).join('/'); +} + +function sortedObject(value) { + return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right))); +} + +function sameJson(left, right) { + return JSON.stringify(sortedObject(left)) === JSON.stringify(sortedObject(right)); +} + +function isRecord(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function isSortedUniqueStrings(value) { + return ( + Array.isArray(value) && + value.every((item) => typeof item === 'string') && + JSON.stringify(value) === JSON.stringify([...new Set(value)].sort()) + ); +} + +function validateCountMap(value) { + return ( + isRecord(value) && + Object.entries(value).every( + ([key, count]) => key.length > 0 && Number.isInteger(count) && count >= 0, + ) + ); +} + +function validateArchitectureConfig(config, label, violations) { + if (!isRecord(config)) { + violations.push(`${label} architecture ledger must be an object`); + return false; + } + let valid = true; + const reject = (message) => { + valid = false; + violations.push(`${label} architecture ledger: ${message}`); + }; + if (config.version !== 1) reject('version must be 1'); + if (!isSortedUniqueStrings(config.legacyRendererFiles)) { + reject('legacyRendererFiles must be sorted unique strings'); + } + if (!isSortedUniqueStrings(config.legacyGrowthDirectories)) { + reject('legacyGrowthDirectories must be sorted unique strings'); + } else if ( + config.legacyGrowthDirectories.some( + (path) => !path.startsWith('src/renderer/') || path.includes('..') || path.includes('\\'), + ) + ) { + reject('legacyGrowthDirectories must contain normalized renderer directories'); + } + for (const field of ['legacyFeatureImports', 'legacyPlatformImports']) { + if (!isSortedUniqueStrings(config[field])) reject(`${field} must be sorted unique strings`); + } + if ( + !isRecord(config.legacyAppShell) || + !isRecord(config.legacyAppShell.files) || + !isRecord(config.legacyAppShell.closure) + ) { + reject('legacyAppShell.files and legacyAppShell.closure must be objects'); + } + if (!isRecord(config.rootDebt)) reject('rootDebt must be an object'); + if (!isRecord(config.rootDebtClosure)) reject('rootDebtClosure must be an object'); + if (!Array.isArray(config.ownership)) reject('ownership must be an array'); + if (!valid) return false; + + const debtSections = [ + { entries: Object.entries(config.legacyAppShell.files), full: true }, + { entries: Object.entries(config.legacyAppShell.closure), full: false }, + { entries: Object.entries(config.rootDebt), full: true }, + { entries: Object.entries(config.rootDebtClosure), full: false }, + ]; + for (const section of debtSections) { + for (const [path, debt] of section.entries) { + const requiredPrefix = section.full ? 'src/renderer/' : 'src/'; + if (!path.startsWith(requiredPrefix) || path.includes('..') || path.includes('\\')) { + reject(`${path}: debt path must be a normalized ${section.full ? 'renderer' : 'Desktop'} source path`); + } + if (!isRecord(debt)) { + reject(`${path}: debt entry must be an object`); + continue; + } + if (section.full) { + for (const metric of ['importDeclarations', 'importSpecifiers', 'nonTriviaTokens']) { + if (!Number.isInteger(debt[metric]) || debt[metric] < 0) reject(`${path}: ${metric} must be a non-negative integer`); + } + } + if (!Number.isInteger(debt.unresolvedDependencies) || debt.unresolvedDependencies < 0) { + reject(`${path}: unresolvedDependencies must be a non-negative integer`); + } + for (const metric of ['bridgePaths', 'dependencyPaths', 'environmentCapabilities', 'hookCalls', 'lifecycleMethods']) { + if (!validateCountMap(debt[metric])) reject(`${path}: ${metric} must be a non-negative count map`); + } + if (!isSortedUniqueStrings(debt.actionFactories)) reject(`${path}: actionFactories must be sorted unique strings`); + } + } + + const capabilities = new Set(); + const targetZone = /^(?:application(?:\/[a-z0-9-]+)*|bootstrap|composition|features\/[a-z0-9-]+|shell|split-by-capability|testing)$/u; + for (const owner of config.ownership) { + if (!isRecord(owner)) { + reject('ownership entries must be objects'); + continue; + } + if (typeof owner.capability !== 'string' || owner.capability.length === 0) reject('ownership capability must be non-empty'); + if (capabilities.has(owner.capability)) reject(`duplicate ownership capability ${owner.capability}`); + capabilities.add(owner.capability); + if (typeof owner.targetZone !== 'string' || !targetZone.test(owner.targetZone)) { + reject(`${owner.capability}: unsupported targetZone ${String(owner.targetZone)}`); + } + if (!isSortedUniqueStrings(owner.legacyPaths)) { + reject(`${owner.capability}: legacyPaths must be sorted unique strings`); + } + } + return valid; +} + +function childNodes(node) { + const children = []; + for (const [key, value] of Object.entries(node)) { + if (['comments', 'end', 'errors', 'extra', 'loc', 'start', 'tokens'].includes(key)) continue; + if (Array.isArray(value)) { + for (const item of value) { + if (item && typeof item === 'object' && typeof item.type === 'string') children.push(item); + } + continue; + } + if (value && typeof value === 'object' && typeof value.type === 'string') children.push(value); + } + return children; +} + +function isMemberExpression(node) { + return node?.type === 'MemberExpression' || node?.type === 'OptionalMemberExpression'; +} + +function memberName(node) { + if (node?.type === 'Identifier') return node.name; + if (node?.type === 'StringLiteral') return node.value; + return undefined; +} + +function unwrapExpression(node) { + let current = node; + while ( + current && + ['ParenthesizedExpression', 'TSAsExpression', 'TSNonNullExpression', 'TSSatisfiesExpression', 'TSTypeAssertion', 'TypeCastExpression'].includes( + current.type, + ) + ) { + current = current.expression; + } + return current; +} + +function memberPropertyName(node) { + if (!isMemberExpression(node)) return undefined; + const property = unwrapExpression(node.property); + if (!node.computed && property?.type === 'Identifier') return property.name; + return staticString(property); +} + +function isWindowExpression(node, windowAliases = new Set(['globalThis', 'window'])) { + const expression = unwrapExpression(node); + if (expression?.type === 'Identifier' && windowAliases.has(expression.name)) return true; + return ( + isMemberExpression(expression) && + unwrapExpression(expression.object)?.type === 'Identifier' && + windowAliases.has(unwrapExpression(expression.object).name) && + memberPropertyName(expression) === 'window' + ); +} + +function isWindowMaka(node, windowAliases) { + const expression = unwrapExpression(node); + return ( + isMemberExpression(expression) && + isWindowExpression(expression.object, windowAliases) && + memberPropertyName(expression) === 'maka' + ); +} + +function hookCallName(node, hookAliases, hookNamespaces, reactNamespaces) { + const expression = unwrapExpression(node); + if (expression?.type === 'Identifier') return hookAliases.get(expression.name); + if ( + isMemberExpression(expression) && + unwrapExpression(expression.object)?.type === 'Identifier' + ) { + const name = memberPropertyName(expression); + const namespace = unwrapExpression(expression.object).name; + if (reactNamespaces.has(namespace)) return name && STATEFUL_HOOKS.has(name) ? name : undefined; + if (hookNamespaces.has(namespace)) return name && /^use[A-Z0-9]/u.test(name) ? name : undefined; + } + return undefined; +} + +function staticString(node) { + if (node?.type === 'StringLiteral') return node.value; + if (node?.type === 'BinaryExpression' && node.operator === '+') { + const left = staticString(node.left); + const right = staticString(node.right); + return left !== undefined && right !== undefined ? `${left}${right}` : undefined; + } + if (node?.type === 'TemplateLiteral') { + let value = ''; + for (let index = 0; index < node.quasis.length; index += 1) { + value += node.quasis[index]?.value.cooked ?? node.quasis[index]?.value.raw ?? ''; + if (index < node.expressions.length) { + const expression = staticString(node.expressions[index]); + if (expression === undefined) return undefined; + value += expression; + } + } + return value; + } + return undefined; +} + +function environmentPath( + node, + windowAliases, + environmentAliases = new Map(), + parents = new WeakMap(), +) { + const expression = unwrapExpression(node); + if (expression?.type === 'Identifier') { + if (environmentAliases.has(expression.name)) return environmentAliases.get(expression.name); + if (windowAliases.has(expression.name)) return 'window'; + if (ENVIRONMENT_OBJECTS.has(expression.name) && !environmentIdentifierIsShadowed(expression, expression.name, parents)) { + return expression.name; + } + return undefined; + } + if (!isMemberExpression(expression)) return undefined; + const base = environmentPath( + expression.object, + windowAliases, + environmentAliases, + parents, + ); + const property = memberPropertyName(expression); + if (!base || !property) return undefined; + return `${base}.${property}`; +} + +function bridgePath(node, windowAliases, bridgeAliases = new Map()) { + const expression = unwrapExpression(node); + if (expression?.type === 'Identifier') return bridgeAliases.get(expression.name); + if (isWindowMaka(expression, windowAliases)) return 'window.maka'; + if (!isMemberExpression(expression)) return undefined; + const base = bridgePath(expression.object, windowAliases, bridgeAliases); + const property = memberPropertyName(expression); + if (!base || !property) return undefined; + return `${base}.${property}`; +} + +function sourceDependency(node) { + if ( + (node.type === 'ImportDeclaration' || + node.type === 'ExportAllDeclaration' || + node.type === 'ExportNamedDeclaration') && + staticString(node.source) !== undefined + ) { + return staticString(node.source); + } + if (node.type === 'ImportExpression' && staticString(node.source) !== undefined) { + return staticString(node.source); + } + if ( + node.type === 'TSImportEqualsDeclaration' && + node.moduleReference?.type === 'TSExternalModuleReference' && + staticString(node.moduleReference.expression) !== undefined + ) { + return staticString(node.moduleReference.expression); + } + if (node.type === 'TSImportType' && staticString(node.argument) !== undefined) { + return staticString(node.argument); + } + if ( + (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') && + staticString(node.arguments?.[0]) !== undefined && + (node.callee?.type === 'Import' || + (node.callee?.type === 'Identifier' && node.callee.name === 'require')) + ) { + return staticString(node.arguments[0]); + } + return undefined; +} + +function bindingNames(pattern) { + if (!pattern) return []; + if (pattern.type === 'Identifier') return [pattern.name]; + if (pattern.type === 'AssignmentPattern') return bindingNames(pattern.left); + if (pattern.type === 'RestElement') return bindingNames(pattern.argument); + if (pattern.type === 'TSParameterProperty') return bindingNames(pattern.parameter); + if (pattern.type === 'ArrayPattern') return pattern.elements.flatMap((element) => bindingNames(element)); + if (pattern.type === 'ObjectPattern') { + return pattern.properties.flatMap((property) => + property.type === 'RestElement' ? bindingNames(property.argument) : bindingNames(property.value), + ); + } + return []; +} + +function buildParentMap(root) { + const parents = new WeakMap(); + function visit(node) { + for (const child of childNodes(node)) { + parents.set(child, node); + visit(child); + } + } + visit(root); + return parents; +} + +function statementBindings(statement) { + const declaration = + statement?.type === 'ExportNamedDeclaration' || statement?.type === 'ExportDefaultDeclaration' + ? statement.declaration + : statement; + if (!declaration) return []; + if (declaration.type === 'ImportDeclaration') return declaration.specifiers.map((specifier) => specifier.local.name); + if (declaration.type === 'VariableDeclaration') { + return declaration.declarations.flatMap((item) => bindingNames(item.id)); + } + if (declaration.type === 'FunctionDeclaration' || declaration.type === 'ClassDeclaration') { + return bindingNames(declaration.id); + } + return []; +} + +function environmentIdentifierIsShadowed(node, name, parents) { + let current = parents.get(node); + while (current) { + if ( + current.type === 'FunctionDeclaration' || + current.type === 'FunctionExpression' || + current.type === 'ArrowFunctionExpression' || + current.type === 'ObjectMethod' || + current.type === 'ClassMethod' || + current.type === 'ClassPrivateMethod' + ) { + if ( + current.type !== 'ArrowFunctionExpression' && + !current.type.endsWith('Method') && + bindingNames(current.id).includes(name) + ) { + return true; + } + if (current.params.some((parameter) => bindingNames(parameter).includes(name))) return true; + } + if (current.type === 'CatchClause' && bindingNames(current.param).includes(name)) return true; + if (current.type === 'Program' || current.type === 'BlockStatement') { + if (current.body.some((statement) => statementBindings(statement).includes(name))) return true; + } + if ( + current.type === 'ForStatement' && + current.init?.type === 'VariableDeclaration' && + statementBindings(current.init).includes(name) + ) { + return true; + } + if ( + (current.type === 'ForInStatement' || current.type === 'ForOfStatement') && + current.left?.type === 'VariableDeclaration' && + statementBindings(current.left).includes(name) + ) { + return true; + } + current = parents.get(current); + } + return false; +} + +function collectAliases(program, parents) { + const hookAliases = new Map(); + const hookNamespaces = new Set(); + const reactComponentBases = new Set(); + const reactNamespaces = new Set(); + for (const statement of program.body) { + if (statement.type !== 'ImportDeclaration') continue; + const isReact = statement.source.value === 'react'; + for (const specifier of statement.specifiers) { + if (isReact && (specifier.type === 'ImportDefaultSpecifier' || specifier.type === 'ImportNamespaceSpecifier')) { + reactNamespaces.add(specifier.local.name); + } else if (isReact && specifier.type === 'ImportSpecifier') { + const imported = memberName(specifier.imported); + if (imported && STATEFUL_HOOKS.has(imported)) hookAliases.set(specifier.local.name, imported); + if (imported === 'Component' || imported === 'PureComponent') reactComponentBases.add(specifier.local.name); + } else if (!isReact) { + const imported = specifier.type === 'ImportSpecifier' ? memberName(specifier.imported) : specifier.local.name; + if (imported && /^use[A-Z0-9]/u.test(imported)) hookAliases.set(specifier.local.name, imported); + if (specifier.type === 'ImportNamespaceSpecifier') hookNamespaces.add(specifier.local.name); + } + } + } + + const windowAliases = new Set(['globalThis', 'self', 'window']); + const bridgeAliases = new Map(); + const environmentAliases = new Map(); + let changed = true; + while (changed) { + changed = false; + const addAlias = (aliases, name, path) => { + if (!path || aliases.has(name)) return; + aliases.set(name, path); + changed = true; + }; + function visit(node) { + if ( + node.type === 'ClassDeclaration' && + node.id?.type === 'Identifier' && + isReactComponentClass(node, reactComponentBases, reactNamespaces) && + !reactComponentBases.has(node.id.name) + ) { + reactComponentBases.add(node.id.name); + changed = true; + } + if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier') { + if (node.init?.type === 'Identifier' && reactNamespaces.has(node.init.name) && !reactNamespaces.has(node.id.name)) { + reactNamespaces.add(node.id.name); + changed = true; + } + if (node.init?.type === 'Identifier' && hookNamespaces.has(node.init.name) && !hookNamespaces.has(node.id.name)) { + hookNamespaces.add(node.id.name); + changed = true; + } + if (isWindowExpression(node.init, windowAliases) && !windowAliases.has(node.id.name)) { + windowAliases.add(node.id.name); + changed = true; + } + addAlias(bridgeAliases, node.id.name, bridgePath(node.init, windowAliases, bridgeAliases)); + const environment = environmentPath( + node.init, + windowAliases, + environmentAliases, + parents, + ); + if (environment && !environment.startsWith('window.maka')) { + addAlias(environmentAliases, node.id.name, environment); + } + if ( + node.init?.type === 'Identifier' && + ENVIRONMENT_CALLS.has(node.init.name) && + !environmentIdentifierIsShadowed(node.init, node.init.name, parents) + ) { + addAlias(environmentAliases, node.id.name, node.init.name); + } + const hook = hookCallName(node.init, hookAliases, hookNamespaces, reactNamespaces); + if (hook && !hookAliases.has(node.id.name)) { + hookAliases.set(node.id.name, hook); + changed = true; + } + } + if (node.type === 'VariableDeclarator' && node.id?.type === 'ObjectPattern') { + const bridgeBase = bridgePath(node.init, windowAliases, bridgeAliases); + const environmentBase = environmentPath( + node.init, + windowAliases, + environmentAliases, + parents, + ); + for (const property of node.id.properties) { + if (property.type !== 'ObjectProperty' || property.computed || property.value?.type !== 'Identifier') continue; + const propertyName = memberName(property.key); + if (!propertyName) continue; + if (bridgeBase) addAlias(bridgeAliases, property.value.name, `${bridgeBase}.${propertyName}`); + if (isWindowExpression(node.init, windowAliases) && propertyName === 'maka') { + addAlias(bridgeAliases, property.value.name, 'window.maka'); + } + if (environmentBase && !environmentBase.startsWith('window.maka')) { + addAlias(environmentAliases, property.value.name, `${environmentBase}.${propertyName}`); + } + } + } + if (node.type === 'AssignmentExpression' && node.left?.type === 'Identifier') { + if (node.right?.type === 'Identifier' && reactNamespaces.has(node.right.name) && !reactNamespaces.has(node.left.name)) { + reactNamespaces.add(node.left.name); + changed = true; + } + if (node.right?.type === 'Identifier' && hookNamespaces.has(node.right.name) && !hookNamespaces.has(node.left.name)) { + hookNamespaces.add(node.left.name); + changed = true; + } + addAlias(bridgeAliases, node.left.name, bridgePath(node.right, windowAliases, bridgeAliases)); + const environment = environmentPath( + node.right, + windowAliases, + environmentAliases, + parents, + ); + if (environment && !environment.startsWith('window.maka')) { + addAlias(environmentAliases, node.left.name, environment); + } + if ( + node.right?.type === 'Identifier' && + ENVIRONMENT_CALLS.has(node.right.name) && + !environmentIdentifierIsShadowed(node.right, node.right.name, parents) + ) { + addAlias(environmentAliases, node.left.name, node.right.name); + } + const hook = hookCallName(node.right, hookAliases, hookNamespaces, reactNamespaces); + if (hook && !hookAliases.has(node.left.name)) { + hookAliases.set(node.left.name, hook); + changed = true; + } + } + if ( + node.type === 'VariableDeclarator' && + node.id?.type === 'ObjectPattern' && + node.init?.type === 'Identifier' && + reactNamespaces.has(node.init.name) + ) { + for (const property of node.id.properties) { + if (property.type !== 'ObjectProperty' || property.computed) continue; + const imported = memberName(property.key); + if (imported && STATEFUL_HOOKS.has(imported) && property.value?.type === 'Identifier') { + hookAliases.set(property.value.name, imported); + } + } + } + for (const child of childNodes(node)) visit(child); + } + visit(program); + } + return { + bridgeAliases, + environmentAliases, + hookAliases, + hookNamespaces, + reactComponentBases, + reactNamespaces, + windowAliases, + }; +} + +function isReactComponentClass(node, reactComponentBases, reactNamespaces) { + if (node?.type !== 'ClassDeclaration' && node?.type !== 'ClassExpression') return false; + const superClass = unwrapExpression(node.superClass); + if (superClass?.type === 'Identifier') return reactComponentBases.has(superClass.name); + return ( + isMemberExpression(superClass) && + unwrapExpression(superClass.object)?.type === 'Identifier' && + reactNamespaces.has(unwrapExpression(superClass.object).name) && + ['Component', 'PureComponent'].includes(memberPropertyName(superClass)) + ); +} + +function enclosingClass(node, parents) { + let current = parents.get(node); + while (current) { + if (current.type === 'ClassDeclaration' || current.type === 'ClassExpression') return current; + current = parents.get(current); + } + return undefined; +} + +export function analyzeRendererSource(source, file = 'fixture.ts') { + const typedSource = /\.(?:(?:c|m)?ts|tsx)$/u.test(file); + const jsxSource = /\.(?:jsx|tsx)$/u.test(file); + const ast = parse(source, { + createImportExpressions: true, + errorRecovery: false, + plugins: PARSER_PLUGINS.filter((plugin) => (plugin === 'typescript' ? typedSource : plugin === 'jsx' ? jsxSource : true)), + sourceFilename: file, + sourceType: 'module', + tokens: true, + }); + const parents = buildParentMap(ast.program); + const { + bridgeAliases, + environmentAliases, + hookAliases, + hookNamespaces, + reactComponentBases, + reactNamespaces, + windowAliases, + } = collectAliases(ast.program, parents); + const bridgePaths = {}; + const environmentCapabilities = {}; + const hookCalls = {}; + const lifecycleMethods = {}; + const actionFactories = []; + const dependencies = []; + const dependencyPaths = {}; + let importDeclarations = 0; + let importSpecifiers = 0; + let unresolvedDependencies = 0; + + function recordBridgePath(path) { + bridgePaths[path] = (bridgePaths[path] ?? 0) + 1; + } + + function recordEnvironmentCapability(path) { + environmentCapabilities[path] = (environmentCapabilities[path] ?? 0) + 1; + } + + function visit(node, parent) { + if (node.type === 'ImportDeclaration') { + importDeclarations += 1; + importSpecifiers += node.specifiers.length; + } + if (node.type === 'TSImportEqualsDeclaration') { + importDeclarations += 1; + importSpecifiers += 1; + } + const dependency = sourceDependency(node); + if (dependency !== undefined) { + dependencies.push(dependency); + dependencyPaths[dependency] = (dependencyPaths[dependency] ?? 0) + 1; + } + if ( + (node.type === 'ImportExpression' && staticString(node.source) === undefined) || + ((node.type === 'CallExpression' || node.type === 'OptionalCallExpression') && + (node.callee?.type === 'Import' || + (node.callee?.type === 'Identifier' && node.callee.name === 'require')) && + staticString(node.arguments?.[0]) === undefined) || + ((node.type === 'CallExpression' || node.type === 'OptionalCallExpression') && + isMemberExpression(node.callee) && + unwrapExpression(node.callee.object)?.type === 'MetaProperty' && + unwrapExpression(node.callee.object).meta?.name === 'import' && + unwrapExpression(node.callee.object).property?.name === 'meta' && + ['glob', 'globEager'].includes(memberPropertyName(node.callee))) + ) { + unresolvedDependencies += 1; + } + + const directEnvironmentPath = environmentPath( + node, + windowAliases, + environmentAliases, + parents, + ); + if ( + directEnvironmentPath && + !directEnvironmentPath.startsWith('window.maka') && + !(isMemberExpression(parent) && unwrapExpression(parent.object) === node) + ) { + recordEnvironmentCapability(directEnvironmentPath); + } + + if (isMemberExpression(node) && memberPropertyName(node) === undefined) { + const dynamicBridgeBase = bridgePath(node.object, windowAliases, bridgeAliases); + if (dynamicBridgeBase) recordBridgePath(`${dynamicBridgeBase}.*`); + const dynamicEnvironmentBase = environmentPath( + node.object, + windowAliases, + environmentAliases, + parents, + ); + if (dynamicEnvironmentBase && !dynamicEnvironmentBase.startsWith('window.maka')) { + recordEnvironmentCapability(`${dynamicEnvironmentBase}.*`); + } + } + + const directBridgePath = bridgePath(node, windowAliases, bridgeAliases); + if ( + directBridgePath && + !(isMemberExpression(parent) && unwrapExpression(parent.object) === node) && + !(node.type === 'Identifier' && parent?.type === 'VariableDeclarator' && parent.id === node) + ) { + recordBridgePath(directBridgePath); + } + if ( + node.type === 'VariableDeclarator' && + node.id?.type === 'ObjectPattern' && + isWindowExpression(node.init, windowAliases) + ) { + for (const property of node.id.properties) { + if (property.type === 'ObjectProperty' && !property.computed && memberName(property.key) === 'maka') { + recordBridgePath('window.maka'); + } + } + } + + if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') { + const name = hookCallName(node.callee, hookAliases, hookNamespaces, reactNamespaces); + if (name) hookCalls[name] = (hookCalls[name] ?? 0) + 1; + const callee = unwrapExpression(node.callee); + if ( + callee?.type === 'Identifier' && + ENVIRONMENT_CALLS.has(callee.name) && + !environmentIdentifierIsShadowed(callee, callee.name, parents) + ) { + recordEnvironmentCapability(callee.name); + } + } + if (node.type === 'NewExpression') { + const callee = unwrapExpression(node.callee); + if ( + callee?.type === 'Identifier' && + ENVIRONMENT_CALLS.has(callee.name) && + !environmentIdentifierIsShadowed(callee, callee.name, parents) + ) { + recordEnvironmentCapability(callee.name); + } + } + const ownerClass = enclosingClass(node, parents); + const reactComponentOwner = isReactComponentClass(ownerClass, reactComponentBases, reactNamespaces); + if (reactComponentOwner && (node.type === 'ClassMethod' || node.type === 'ClassPrivateMethod')) { + const name = memberName(node.key); + if (name && REACT_LIFECYCLE_METHODS.has(name)) { + lifecycleMethods[name] = (lifecycleMethods[name] ?? 0) + 1; + } + } + if (reactComponentOwner && ['ClassProperty', 'ClassPrivateProperty', 'PropertyDefinition'].includes(node.type)) { + const name = memberName(node.key); + if (name === 'state' || (name && REACT_LIFECYCLE_METHODS.has(name))) { + lifecycleMethods[name] = (lifecycleMethods[name] ?? 0) + 1; + } + } + if ( + reactComponentOwner && + isMemberExpression(node) && + unwrapExpression(node.object)?.type === 'ThisExpression' && + ['setState', 'state'].includes(memberPropertyName(node)) + ) { + const name = memberPropertyName(node); + lifecycleMethods[name] = (lifecycleMethods[name] ?? 0) + 1; + } + + if ( + (node.type === 'FunctionDeclaration' || node.type === 'VariableDeclarator') && + node.id?.type === 'Identifier' && + node.id.name.startsWith('createAppShell') + ) { + actionFactories.push(node.id.name); + } + + for (const child of childNodes(node)) { + parents.set(child, node); + visit(child, node); + } + } + + visit(ast.program, undefined); + return { + actionFactories: actionFactories.sort(), + bridgePaths: sortedObject(bridgePaths), + dependencies, + dependencyPaths: sortedObject(dependencyPaths), + environmentCapabilities: sortedObject(environmentCapabilities), + hookCalls: sortedObject(hookCalls), + importDeclarations, + importSpecifiers, + lifecycleMethods: sortedObject(lifecycleMethods), + nonTriviaTokens: ast.tokens.length, + unresolvedDependencies, + }; +} + +function sourceFiles(root, { includeDeclarations = false } = {}) { + if (!existsSync(root)) return []; + return readdirSync(root, { withFileTypes: true }).flatMap((entry) => { + const path = join(root, entry.name); + if (entry.isDirectory()) return sourceFiles(path, { includeDeclarations }); + return SOURCE_FILE.test(entry.name) && (includeDeclarations || !DECLARATION_FILE.test(entry.name)) ? [path] : []; + }); +} + +function zoneFor(path) { + const normalized = normalizePath(path).replace(SOURCE_EXTENSION, ''); + if (normalized.startsWith('src/main/')) return { kind: 'main' }; + if (normalized.startsWith('src/preload/')) return { kind: 'preload' }; + if (!normalized.startsWith('src/renderer/')) return { kind: 'external' }; + const feature = normalized.match(/^src\/renderer\/features\/([^/]+)(?:\/(.*))?$/u); + if (feature) return { kind: 'feature', name: feature[1], subpath: feature[2] ?? '' }; + for (const kind of ['application', 'bootstrap', 'composition', 'shell']) { + const prefix = `src/renderer/${kind}/`; + if (normalized.startsWith(prefix)) return { kind, subpath: normalized.slice(prefix.length) }; + } + if (normalized.startsWith('src/renderer/platform/desktop/')) return { kind: 'platform' }; + return { kind: 'legacy' }; +} + +function isRootClosureDebtSource(path) { + return ['external', 'legacy', 'main', 'preload'].includes(zoneFor(path).kind); +} + +function resolveDependency(desktopRoot, importer, dependency) { + let target; + if (dependency.startsWith('.')) target = resolve(dirname(importer), dependency); + else if (dependency.startsWith(DESKTOP_SELF_PREFIX)) target = resolve(desktopRoot, dependency.slice(DESKTOP_SELF_PREFIX.length)); + else return undefined; + const normalized = normalizePath(target); + return normalized.replace(/\.(?:c|m)?(?:js|ts)x?$/u, ''); +} + +function sourceFileIndex(desktopRoot) { + const index = new Map(); + const paths = sourceFiles(resolve(desktopRoot, 'src'), { includeDeclarations: true }).sort(); + for (const path of paths.filter((path) => !DECLARATION_FILE.test(path))) { + const key = normalizePath(path).replace(SOURCE_EXTENSION, ''); + if (!index.has(key)) index.set(key, path); + } + for (const path of paths.filter((path) => DECLARATION_FILE.test(path))) { + const key = normalizePath(path).replace(SOURCE_EXTENSION, ''); + index.set(key, path); + const runtimeAlias = key.replace(/\.d$/u, ''); + if (!index.has(runtimeAlias)) index.set(runtimeAlias, path); + } + return index; +} + +function resolveSourceFile(desktopRoot, importer, dependency, index) { + const target = resolveDependency(desktopRoot, importer, dependency); + if (!target) return undefined; + return index.get(target) ?? index.get(`${target}/index`); +} + +function isSourceLikeDependency(dependency) { + const withoutQuery = dependency.split(/[?#]/u, 1)[0]; + const lastSegment = withoutQuery.split('/').at(-1) ?? ''; + return !lastSegment.includes('.') || SOURCE_EXTENSION.test(lastSegment); +} + +function isAllowedLegacyGrowthPath(config, path) { + return config.legacyGrowthDirectories.some((directory) => path.startsWith(`${directory}/`)); +} + +function capabilityDebtMetrics(analysis) { + return { + bridgePaths: analysis.bridgePaths, + environmentCapabilities: analysis.environmentCapabilities, + hookCalls: analysis.hookCalls, + lifecycleMethods: analysis.lifecycleMethods, + unresolvedDependencies: analysis.unresolvedDependencies, + actionFactories: analysis.actionFactories, + dependencyPaths: analysis.dependencyPaths, + }; +} + +function debtMetrics(analysis) { + return { + importDeclarations: analysis.importDeclarations, + ...capabilityDebtMetrics(analysis), + importSpecifiers: analysis.importSpecifiers, + nonTriviaTokens: analysis.nonTriviaTokens, + }; +} + +function collectRootDependencyClosure(desktopRoot, rootPaths, violations = [], label = 'renderer root') { + const index = sourceFileIndex(desktopRoot); + const sourceRoot = normalizePath(resolve(desktopRoot, 'src')); + const roots = new Set(rootPaths); + const queue = rootPaths.map((path) => resolve(desktopRoot, path)); + const seen = new Set(); + const closure = new Set(); + + while (queue.length > 0) { + const file = queue.shift(); + const fileKey = normalizePath(file).replace(SOURCE_EXTENSION, ''); + if (seen.has(fileKey) || !existsSync(file)) continue; + seen.add(fileKey); + const fileRelative = normalizePath(relative(desktopRoot, file)); + let analysis; + try { + analysis = analyzeRendererSource(readFileSync(file, 'utf8'), fileRelative); + } catch (error) { + violations.push(`${fileRelative}: could not parse ${label} dependency closure source: ${error instanceof Error ? error.message : String(error)}`); + continue; + } + if (analysis.unresolvedDependencies > 0) { + violations.push(`${fileRelative}: ${label} closure contains a non-static import, require, or import.meta glob`); + } + for (const dependency of analysis.dependencies) { + const unresolvedTarget = resolveDependency(desktopRoot, file, dependency); + if (!unresolvedTarget) continue; + const normalizedTarget = normalizePath(unresolvedTarget); + if (normalizedTarget !== sourceRoot && !normalizedTarget.startsWith(`${sourceRoot}/`)) continue; + const target = resolveSourceFile(desktopRoot, file, dependency, index); + if (!target) { + if (isSourceLikeDependency(dependency)) { + violations.push(`${fileRelative}: ${label} closure dependency does not resolve to Desktop source: ${dependency}`); + } + continue; + } + const targetRelative = normalizePath(relative(desktopRoot, target)); + if (!seen.has(normalizePath(target).replace(SOURCE_EXTENSION, ''))) queue.push(target); + if (isRootClosureDebtSource(targetRelative) && !DECLARATION_FILE.test(target) && !roots.has(targetRelative)) { + closure.add(targetRelative); + } + } + } + return [...closure].sort(); +} + +function featureForAbsolutePath(desktopRoot, absolutePath) { + const rendererFeatures = normalizePath(resolve(desktopRoot, 'src/renderer/features')); + if (absolutePath !== rendererFeatures && !absolutePath.startsWith(`${rendererFeatures}/`)) return undefined; + const rest = absolutePath.slice(rendererFeatures.length).replace(/^\//u, ''); + const [name, ...segments] = rest.split('/'); + return { name, subpath: segments.join('/') }; +} + +function isTestConsumer(path) { + const normalized = normalizePath(path); + return ( + normalized.includes('/__tests__/') || + normalized.includes('/e2e/') || + normalized.includes('/stories/') || + /\.(?:spec|stories|test)\.[^/]+$/u.test(normalized) + ); +} + +function isPublicFeaturePath(subpath) { + return subpath === '' || subpath === 'index'; +} + +function isApplicationContractPath(path) { + return path === 'src/renderer/application/contracts' || path.startsWith('src/renderer/application/contracts/'); +} + +function isPublicApplicationPath(path) { + if (isApplicationContractPath(path)) return true; + return /^src\/renderer\/application\/[^/]+(?:\/index)?$/u.test(path); +} + +function isForbiddenEnvironmentDependency(dependency) { + return FORBIDDEN_ENVIRONMENT_IMPORTS.has(dependency) || dependency.startsWith('electron/'); +} + +function validateDependencies({ + allowedLegacyFeatureImports, + allowedLegacyPlatformImports, + analysis, + desktopRoot, + file, + fileRelative, + observedLegacyFeatureImports, + observedLegacyPlatformImports, + violations, +}) { + const sourceZone = zoneFor(fileRelative); + for (const dependency of analysis.dependencies) { + if ( + ['application', 'bootstrap', 'composition', 'feature', 'platform', 'shell'].includes(sourceZone.kind) && + !isTestConsumer(file) && + isForbiddenEnvironmentDependency(dependency) + ) { + violations.push(`${fileRelative}: ${sourceZone.kind} code imports forbidden environment module: ${dependency}`); + continue; + } + const target = resolveDependency(desktopRoot, file, dependency); + if (!target) continue; + const targetRelative = normalizePath(relative(desktopRoot, target)); + const targetZone = zoneFor(targetRelative); + const targetFeature = featureForAbsolutePath(desktopRoot, target); + + if (targetFeature) { + if (sourceZone.kind === 'feature' && sourceZone.name !== targetFeature.name) { + violations.push(`${fileRelative}: feature ${sourceZone.name} imports feature ${targetFeature.name}: ${dependency}`); + continue; + } + if ( + sourceZone.kind === 'feature' && + sourceZone.name === targetFeature.name && + targetFeature.subpath === 'testing' && + sourceZone.subpath !== 'testing' && + !isTestConsumer(file) + ) { + violations.push(`${fileRelative}: feature production code cannot import its testing entry: ${dependency}`); + } + if (sourceZone.kind !== 'feature') { + const testingEntry = targetFeature.subpath === 'testing'; + if (!isPublicFeaturePath(targetFeature.subpath) && !(testingEntry && isTestConsumer(file))) { + violations.push(`${fileRelative}: feature imports must use index${isTestConsumer(file) ? ' or testing' : ''}: ${dependency}`); + } + if (testingEntry && !isTestConsumer(file)) { + violations.push(`${fileRelative}: production code imports a feature testing entry: ${dependency}`); + } + } + } + + if (sourceZone.kind === 'feature') { + if (targetZone.kind === 'legacy') { + const edge = `${fileRelative} -> ${targetRelative}`; + observedLegacyFeatureImports.add(edge); + if (!allowedLegacyFeatureImports.has(edge)) { + violations.push(`${fileRelative}: feature imports unbudgeted renderer legacy code: ${dependency}`); + } + } + if (targetZone.kind === 'application' && !isApplicationContractPath(targetRelative)) { + violations.push(`${fileRelative}: feature imports application implementation instead of a contract: ${dependency}`); + } + if (['bootstrap', 'composition', 'main', 'platform', 'preload', 'shell'].includes(targetZone.kind)) { + violations.push(`${fileRelative}: feature imports a forbidden Desktop/shell module: ${dependency}`); + } + } + + if (sourceZone.kind === 'shell') { + const allowedApplicationContract = isApplicationContractPath(targetRelative); + if ( + targetZone.kind === 'feature' || + targetZone.kind === 'platform' || + targetZone.kind === 'legacy' || + targetZone.kind === 'bootstrap' || + targetZone.kind === 'composition' || + (targetZone.kind === 'application' && !allowedApplicationContract) || + targetZone.kind === 'main' || + targetZone.kind === 'preload' + ) { + violations.push(`${fileRelative}: shell imports forbidden implementation module: ${dependency}`); + } + } + + if (sourceZone.kind === 'application') { + if ( + sourceZone.subpath === 'contracts' || + sourceZone.subpath.startsWith('contracts/') + ) { + if (targetZone.kind === 'application' && !isApplicationContractPath(targetRelative)) { + violations.push(`${fileRelative}: application contracts import application implementation: ${dependency}`); + } + } + if ( + targetZone.kind === 'feature' || + targetZone.kind === 'platform' || + targetZone.kind === 'shell' || + targetZone.kind === 'bootstrap' || + targetZone.kind === 'composition' || + targetZone.kind === 'legacy' || + targetZone.kind === 'main' || + targetZone.kind === 'preload' + ) { + violations.push(`${fileRelative}: application authority imports an outer implementation: ${dependency}`); + } + } + + if (sourceZone.kind === 'composition') { + if (targetZone.kind === 'application' && !isPublicApplicationPath(targetRelative)) { + violations.push(`${fileRelative}: composition imports application implementation instead of a public entry: ${dependency}`); + } + const allowedLegacyAdapter = + fileRelative === LEGACY_APP_SHELL_ADAPTER && targetRelative === LEGACY_APP_SHELL_MODULE; + if ( + targetZone.kind === 'bootstrap' || + targetZone.kind === 'main' || + targetZone.kind === 'preload' || + (targetZone.kind === 'legacy' && !allowedLegacyAdapter) + ) { + violations.push(`${fileRelative}: composition imports a forbidden implementation module: ${dependency}`); + } + } + + if (sourceZone.kind === 'bootstrap') { + if (!['bootstrap', 'composition', 'external', 'platform'].includes(targetZone.kind)) { + violations.push(`${fileRelative}: bootstrap imports outside composition/platform: ${dependency}`); + } + } + + if (sourceZone.kind === 'platform') { + if (targetZone.kind === 'application' && !isPublicApplicationPath(targetRelative)) { + violations.push(`${fileRelative}: Desktop adapter imports application implementation instead of a public entry: ${dependency}`); + } + if (targetZone.kind === 'legacy') { + const edge = `${fileRelative} -> ${targetRelative}`; + observedLegacyPlatformImports.add(edge); + if (!allowedLegacyPlatformImports.has(edge)) { + violations.push(`${fileRelative}: Desktop adapter imports unbudgeted renderer legacy code: ${dependency}`); + } + } + if (['bootstrap', 'composition', 'main', 'shell'].includes(targetZone.kind)) { + violations.push(`${fileRelative}: Desktop adapter imports an outer or legacy implementation: ${dependency}`); + } + } + } +} + +function validateStrictZone({ fileRelative, analysis, violations }) { + const zone = zoneFor(fileRelative); + if (['application', 'bootstrap', 'composition', 'feature', 'shell'].includes(zone.kind)) { + if (Object.keys(analysis.bridgePaths).length > 0) { + violations.push(`${fileRelative}: ${zone.kind} code accesses the Desktop global bridge`); + } + if (Object.keys(analysis.environmentCapabilities).some((path) => path.endsWith('.*'))) { + violations.push(`${fileRelative}: ${zone.kind} code contains non-static global environment access`); + } + } + if (['bootstrap', 'composition', 'platform', 'shell'].includes(zone.kind)) { + if (Object.keys(analysis.hookCalls).length > 0) { + violations.push(`${fileRelative}: ${zone.kind} code owns stateful React hooks`); + } + if (Object.keys(analysis.lifecycleMethods).length > 0) { + violations.push(`${fileRelative}: ${zone.kind} code owns React class lifecycle methods`); + } + const environmentPaths = Object.keys(analysis.environmentCapabilities); + const bootstrapEnvironmentIsOnlyMountLookup = + zone.kind === 'bootstrap' && + environmentPaths.every((path) => ['document.getElementById', 'document.querySelector'].includes(path)); + if (zone.kind !== 'platform' && environmentPaths.length > 0 && !bootstrapEnvironmentIsOnlyMountLookup) { + violations.push(`${fileRelative}: ${zone.kind} code directly accesses browser environment capabilities`); + } + } + if (zone.kind === 'platform' && (/\.(?:(?:c|m)?(?:jsx|tsx))$/u.test(fileRelative) || analysis.dependencies.includes('react'))) { + violations.push(`${fileRelative}: Desktop adapters cannot own React UI`); + } + if (['application', 'bootstrap', 'composition', 'feature', 'platform', 'shell'].includes(zone.kind) && analysis.unresolvedDependencies > 0) { + violations.push(`${fileRelative}: ${zone.kind} code contains a non-static import or require`); + } +} + +function validateMetric(path, metric, actual, expected, violations) { + if (metric === 'actionFactories') { + if (JSON.stringify(actual) !== JSON.stringify(expected)) { + violations.push(`${path}: ${metric} changed; expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}`); + } + return; + } + if (typeof expected === 'number') { + if (actual !== expected) violations.push(`${path}: ${metric} changed; expected ${expected}, received ${actual}`); + return; + } + if (!sameJson(actual, expected)) { + violations.push(`${path}: ${metric} changed; expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}`); + } +} + +function validateDebtFile(desktopRoot, path, expected, violations, metrics = ROOT_DEBT_METRICS) { + const absolutePath = resolve(desktopRoot, path); + if (!existsSync(absolutePath)) { + violations.push(`${path}: debt ledger entry points to a missing file`); + return; + } + const analysis = analyzeRendererSource(readFileSync(absolutePath, 'utf8'), path); + for (const metric of metrics) { + validateMetric(path, metric, analysis[metric], expected[metric], violations); + } +} + +function validateLegacyLedger(desktopRoot, config, violations) { + const rendererRoot = resolve(desktopRoot, 'src/renderer'); + const actualLegacyFiles = sourceFiles(rendererRoot) + .map((path) => normalizePath(relative(desktopRoot, path))) + .filter((path) => zoneFor(path).kind === 'legacy') + .sort(); + const expectedLegacyFiles = [...config.legacyRendererFiles].sort(); + if (JSON.stringify(actualLegacyFiles) !== JSON.stringify(expectedLegacyFiles)) { + violations.push( + `unclassified legacy renderer file set changed; expected ${JSON.stringify(expectedLegacyFiles)}, received ${JSON.stringify(actualLegacyFiles)}`, + ); + } + + const actualFiles = readdirSync(rendererRoot, { withFileTypes: true }) + .filter((entry) => entry.isFile() && LEGACY_APP_SHELL_FILE.test(entry.name)) + .map((entry) => `src/renderer/${entry.name}`) + .sort(); + const expectedFiles = Object.keys(config.legacyAppShell.files).sort(); + if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) { + violations.push(`legacy AppShell file set changed; expected ${JSON.stringify(expectedFiles)}, received ${JSON.stringify(actualFiles)}`); + } + + for (const [path, expected] of Object.entries(config.legacyAppShell.files)) { + validateDebtFile(desktopRoot, path, expected, violations); + } + const actualClosure = collectRootDependencyClosure(desktopRoot, expectedFiles, violations, 'AppShell'); + const expectedClosure = Object.keys(config.legacyAppShell.closure).sort(); + if (JSON.stringify(actualClosure) !== JSON.stringify(expectedClosure)) { + violations.push( + `legacy AppShell transitive renderer closure changed; expected ${JSON.stringify(expectedClosure)}, received ${JSON.stringify(actualClosure)}`, + ); + } + for (const [path, expected] of Object.entries(config.legacyAppShell.closure)) { + if (config.legacyAppShell.files[path]) { + violations.push(`${path}: AppShell root file must not also appear in its transitive closure`); + continue; + } + if (!isRootClosureDebtSource(path) || DECLARATION_FILE.test(path)) { + violations.push(`${path}: AppShell closure debt must point to a non-owner Desktop source`); + } + validateDebtFile(desktopRoot, path, expected, violations, CAPABILITY_DEBT_METRICS); + } + for (const [path, expected] of Object.entries(config.rootDebt)) { + validateDebtFile(desktopRoot, path, expected, violations); + } + const appShellDebtPaths = new Set([...expectedFiles, ...expectedClosure]); + const rootDebtPaths = Object.keys(config.rootDebt).sort(); + const actualRootClosure = collectRootDependencyClosure(desktopRoot, rootDebtPaths, violations, 'renderer root') + .filter((path) => !appShellDebtPaths.has(path)); + const expectedRootClosure = Object.keys(config.rootDebtClosure).sort(); + if (JSON.stringify(actualRootClosure) !== JSON.stringify(expectedRootClosure)) { + violations.push( + `renderer root transitive legacy closure changed; expected ${JSON.stringify(expectedRootClosure)}, received ${JSON.stringify(actualRootClosure)}`, + ); + } + for (const [path, expected] of Object.entries(config.rootDebtClosure)) { + if (config.rootDebt[path] || appShellDebtPaths.has(path)) { + violations.push(`${path}: renderer root closure debt overlaps another root debt section`); + continue; + } + if (!isRootClosureDebtSource(path) || DECLARATION_FILE.test(path)) { + violations.push(`${path}: renderer root closure debt must point to a non-owner Desktop source`); + } + validateDebtFile(desktopRoot, path, expected, violations, CAPABILITY_DEBT_METRICS); + } + + const ownedPaths = new Map(); + for (const owner of config.ownership) { + if (!owner.capability || !owner.targetZone || !Array.isArray(owner.legacyPaths)) { + violations.push('ownership entries require capability, targetZone, and legacyPaths'); + continue; + } + for (const path of owner.legacyPaths) { + if (ownedPaths.has(path)) { + violations.push(`${path}: assigned to both ${ownedPaths.get(path)} and ${owner.capability}`); + } + ownedPaths.set(path, owner.capability); + } + } + const expectedOwnedPaths = [...expectedFiles, ...Object.keys(config.rootDebt)]; + for (const path of expectedOwnedPaths) { + if (!ownedPaths.has(path)) violations.push(`${path}: missing target owner in the architecture ledger`); + } + for (const path of ownedPaths.keys()) { + if (!config.legacyAppShell.files[path] && !config.rootDebt[path]) { + violations.push(`${path}: ownership ledger path is not recorded renderer root debt`); + } + } +} + +function htmlAttribute(attributes, name) { + const match = attributes.match( + new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, 'iu'), + ); + return match?.[1] ?? match?.[2]; +} + +function parseContractSource(desktopRoot, path, violations) { + const absolutePath = resolve(desktopRoot, path); + if (!existsSync(absolutePath)) { + violations.push(`${path}: renderer entry contract source is missing`); + return undefined; + } + try { + return parse(readFileSync(absolutePath, 'utf8'), { + createImportExpressions: true, + errorRecovery: false, + plugins: PARSER_PLUGINS.filter((plugin) => plugin !== 'jsx'), + sourceFilename: path, + sourceType: 'module', + }).program; + } catch (error) { + violations.push( + `${path}: could not parse renderer entry contract: ${error instanceof Error ? error.message : String(error)}`, + ); + return undefined; + } +} + +function nodesIn(program) { + if (!program) return []; + const nodes = []; + function visit(node) { + nodes.push(node); + for (const child of childNodes(node)) visit(child); + } + visit(program); + return nodes; +} + +function objectPropertyValues(object, name) { + if (object?.type !== 'ObjectExpression') return []; + return object.properties + .filter( + (property) => + property.type === 'ObjectProperty' && + !property.computed && + memberName(property.key) === name, + ) + .map((property) => unwrapExpression(property.value)); +} + +function isIdentifier(node, name) { + return unwrapExpression(node)?.type === 'Identifier' && unwrapExpression(node).name === name; +} + +function hasNamedImport(program, source, importedName, localName = importedName) { + return program.body.some( + (node) => + node.type === 'ImportDeclaration' && + staticString(node.source) === source && + node.specifiers.some( + (specifier) => + specifier.type === 'ImportSpecifier' && + memberName(specifier.imported) === importedName && + specifier.local.name === localName, + ), + ); +} + +function hasDefaultImport(program, source, localName) { + return program.body.some( + (node) => + node.type === 'ImportDeclaration' && + staticString(node.source) === source && + node.specifiers.some( + (specifier) => + specifier.type === 'ImportDefaultSpecifier' && specifier.local.name === localName, + ), + ); +} + +function isNamedMember(node, objectName, propertyName) { + const expression = unwrapExpression(node); + return ( + isMemberExpression(expression) && + isIdentifier(expression.object, objectName) && + memberPropertyName(expression) === propertyName + ); +} + +function isMemberCall(node, objectName, methodName, argumentObject, argumentProperty) { + const expression = unwrapExpression(node); + return ( + expression?.type === 'CallExpression' && + isMemberExpression(expression.callee) && + isIdentifier(expression.callee.object, objectName) && + memberPropertyName(expression.callee) === methodName && + expression.arguments.length === 1 && + isNamedMember(expression.arguments[0], argumentObject, argumentProperty) + ); +} + +function isRendererEntryPathInitializer(node) { + const expression = unwrapExpression(node); + return ( + expression?.type === 'CallExpression' && + isIdentifier(expression.callee, 'join') && + expression.arguments.length === 5 && + isIdentifier(expression.arguments[0], 'mainModuleDirectory') && + JSON.stringify(expression.arguments.slice(1).map(staticString)) === + JSON.stringify(['..', '..', 'dist-renderer', 'index.html']) + ); +} + +function isRendererEntryUrlInitializer(node) { + const expression = unwrapExpression(node); + if (expression?.type !== 'LogicalExpression' || expression.operator !== '??') return false; + const left = unwrapExpression(expression.left); + const right = unwrapExpression(expression.right); + const viteDevServerUrl = isIdentifier(left, 'viteDevServerUrl'); + const fileUrl = + isMemberExpression(right) && + memberPropertyName(right) === 'href' && + unwrapExpression(right.object)?.type === 'CallExpression' && + isIdentifier(unwrapExpression(right.object).callee, 'pathToFileURL') && + unwrapExpression(right.object).arguments.length === 1 && + isIdentifier(unwrapExpression(right.object).arguments[0], 'rendererEntryPath'); + return viteDevServerUrl && fileUrl; +} + +function isViteDevServerFlag(node) { + const expression = unwrapExpression(node); + const inner = expression?.type === 'UnaryExpression' && expression.operator === '!' ? unwrapExpression(expression.argument) : undefined; + return ( + inner?.type === 'UnaryExpression' && + inner.operator === '!' && + isIdentifier(inner.argument, 'viteDevServerUrl') + ); +} + +function frozenRendererEntryObject(node) { + const expression = unwrapExpression(node); + if ( + expression?.type !== 'CallExpression' || + expression.arguments.length !== 1 || + !isMemberExpression(expression.callee) || + expression.callee.computed || + !isIdentifier(expression.callee.object, 'Object') || + memberPropertyName(expression.callee) !== 'freeze' + ) { + return undefined; + } + const object = unwrapExpression(expression.arguments[0]); + return object?.type === 'ObjectExpression' ? object : undefined; +} + +function expressionRootIdentifier(node) { + let expression = unwrapExpression(node); + while (isMemberExpression(expression)) expression = unwrapExpression(expression.object); + return expression?.type === 'Identifier' ? expression.name : undefined; +} + +function mutatesIdentifier(node, name) { + if (node.type === 'AssignmentExpression') return expressionRootIdentifier(node.left) === name; + if (node.type === 'UpdateExpression') return expressionRootIdentifier(node.argument) === name; + return ( + node.type === 'UnaryExpression' && + node.operator === 'delete' && + expressionRootIdentifier(node.argument) === name + ); +} + +function navigationApiToken(node) { + if (node?.type === 'Identifier' && ['loadFile', 'loadURL'].includes(node.name)) return node.name; + const value = staticString(node); + if (['loadFile', 'loadURL'].includes(value ?? '')) return value; + return undefined; +} + +function isOnlyAwaitedMemberCall(block, objectName, methodName, argumentObject, argumentProperty) { + if (block?.type !== 'BlockStatement' || block.body.length !== 1) return false; + const [statement] = block.body; + const expression = statement?.type === 'ExpressionStatement' ? unwrapExpression(statement.expression) : undefined; + return ( + expression?.type === 'AwaitExpression' && + isMemberCall(expression.argument, objectName, methodName, argumentObject, argumentProperty) + ); +} + +function isImportMetaDirectory(node) { + const expression = unwrapExpression(node); + return ( + isMemberExpression(expression) && + unwrapExpression(expression.object)?.type === 'MetaProperty' && + unwrapExpression(expression.object).meta?.name === 'import' && + unwrapExpression(expression.object).property?.name === 'meta' && + memberPropertyName(expression) === 'dirname' + ); +} + +function isProcessViteDevServerUrl(node) { + const expression = unwrapExpression(node); + return ( + isMemberExpression(expression) && + isMemberExpression(expression.object) && + isIdentifier(expression.object.object, 'process') && + memberPropertyName(expression.object) === 'env' && + memberPropertyName(expression) === 'VITE_DEV_SERVER_URL' + ); +} + +function validateMainRendererLoader(desktopRoot, violations) { + const program = parseContractSource(desktopRoot, MAIN_RENDERER_LOADER_SOURCE, violations); + if (!program) return; + const nodes = nodesIn(program); + const declarators = (name) => + nodes.filter( + (node) => + node.type === 'VariableDeclarator' && + node.id?.type === 'Identifier' && + node.id.name === name, + ); + const entryPaths = declarators('rendererEntryPath'); + const entryUrls = declarators('rendererEntryUrl'); + const loadCalls = nodes.filter( + (node) => + node.type === 'CallExpression' && + isMemberExpression(node.callee) && + ['loadFile', 'loadURL'].includes(memberPropertyName(node.callee)), + ); + const loaderFunctions = nodes.filter( + (node) => node.type === 'FunctionDeclaration' && node.id?.name === 'loadMainRenderer', + ); + const resolverFunctions = nodes.filter( + (node) => node.type === 'FunctionDeclaration' && node.id?.name === 'resolveMainRendererEntry', + ); + const [loaderFunction] = loaderFunctions; + const [resolverFunction] = resolverFunctions; + const ifStatements = loaderFunction ? nodesIn(loaderFunction.body).filter((node) => node.type === 'IfStatement') : []; + const [loadBranch] = ifStatements; + const resolverReturns = resolverFunction + ? nodesIn(resolverFunction.body).filter((node) => node.type === 'ReturnStatement') + : []; + const [resolverReturn] = resolverReturns; + const frozenEntry = frozenRendererEntryObject(resolverReturn?.argument); + const returnFilePaths = objectPropertyValues(frozenEntry, 'filePath'); + const returnUrls = objectPropertyValues(frozenEntry, 'url'); + const returnDevFlags = objectPropertyValues(frozenEntry, 'useDevServer'); + const navigationTokens = nodes.filter((node) => navigationApiToken(node)); + const valid = + hasNamedImport(program, 'node:path', 'join') && + hasNamedImport(program, 'node:url', 'pathToFileURL') && + !program.body.some((statement) => statementBindings(statement).includes('Object')) && + resolverFunctions.length === 1 && + resolverFunction.async !== true && + JSON.stringify(resolverFunction.params.map((parameter) => parameter.type === 'Identifier' ? parameter.name : undefined)) === + JSON.stringify(['mainModuleDirectory', 'viteDevServerUrl']) && + resolverFunction.body.body.length === 3 && + resolverReturns.length === 1 && + Boolean(frozenEntry) && + returnFilePaths.length === 1 && + isIdentifier(returnFilePaths[0], 'rendererEntryPath') && + returnUrls.length === 1 && + isIdentifier(returnUrls[0], 'rendererEntryUrl') && + returnDevFlags.length === 1 && + isViteDevServerFlag(returnDevFlags[0]) && + loaderFunctions.length === 1 && + loaderFunction.async === true && + JSON.stringify(loaderFunction.params.map((parameter) => parameter.type === 'Identifier' ? parameter.name : undefined)) === + JSON.stringify(['mainWindow', 'rendererEntry']) && + loaderFunction.body.body.length === 1 && + entryPaths.length === 1 && + isRendererEntryPathInitializer(entryPaths[0].init) && + entryUrls.length === 1 && + isRendererEntryUrlInitializer(entryUrls[0].init) && + loadCalls.length === 2 && + loadCalls.filter((node) => isMemberCall(node, 'mainWindow', 'loadFile', 'rendererEntry', 'filePath')).length === 1 && + loadCalls.filter((node) => isMemberCall(node, 'mainWindow', 'loadURL', 'rendererEntry', 'url')).length === 1 && + navigationTokens.length === 4 && + ifStatements.length === 1 && + isNamedMember(loadBranch.test, 'rendererEntry', 'useDevServer') && + isOnlyAwaitedMemberCall(loadBranch.consequent, 'mainWindow', 'loadURL', 'rendererEntry', 'url') && + isOnlyAwaitedMemberCall(loadBranch.alternate, 'mainWindow', 'loadFile', 'rendererEntry', 'filePath'); + if (!valid) { + violations.push( + `${MAIN_RENDERER_LOADER_SOURCE}: renderer loader must load only the pinned dist-renderer/index.html entry`, + ); + } +} + +function validateMainWindowEntryContract(desktopRoot, violations) { + const program = parseContractSource(desktopRoot, RENDERER_LOADER_SOURCE, violations); + if (!program) return; + const nodes = nodesIn(program); + const parents = buildParentMap(program); + const loaderImports = program.body.filter( + (node) => + node.type === 'ImportDeclaration' && + staticString(node.source) === './main-renderer-loader.js' && + ['loadMainRenderer', 'resolveMainRendererEntry'].every((name) => + node.specifiers.some( + (specifier) => + specifier.type === 'ImportSpecifier' && + memberName(specifier.imported) === name && + specifier.local.name === name, + ), + ), + ); + const entryDeclarators = nodes.filter( + (node) => + node.type === 'VariableDeclarator' && + node.id?.type === 'Identifier' && + node.id.name === 'rendererEntry' && + unwrapExpression(node.init)?.type === 'CallExpression' && + isIdentifier(unwrapExpression(node.init).callee, 'resolveMainRendererEntry') && + unwrapExpression(node.init).arguments.length === 2 && + isImportMetaDirectory(unwrapExpression(node.init).arguments[0]) && + isProcessViteDevServerUrl(unwrapExpression(node.init).arguments[1]), + ); + const loaderCalls = nodes.filter( + (node) => + node.type === 'CallExpression' && + isIdentifier(node.callee, 'loadMainRenderer') && + node.arguments.length === 2 && + isIdentifier(node.arguments[0], 'mainWindow') && + isIdentifier(node.arguments[1], 'rendererEntry'), + ); + const entryDeclaration = entryDeclarators.length === 1 ? parents.get(entryDeclarators[0]) : undefined; + const entryMutations = nodes.filter((node) => mutatesIdentifier(node, 'rendererEntry')); + if ( + loaderImports.length !== 1 || + entryDeclarators.length !== 1 || + entryDeclaration?.type !== 'VariableDeclaration' || + entryDeclaration.kind !== 'const' || + loaderCalls.length !== 1 || + entryMutations.length > 0 || + nodes.some((node) => navigationApiToken(node)) + ) { + violations.push( + `${RENDERER_LOADER_SOURCE}: main window must delegate exactly once to the pinned renderer loader`, + ); + } + + const allowedNavigationFiles = new Set([ + 'src/main/browser/controller.ts', + 'src/main/computer-use/cursor-overlay-window.ts', + 'src/main/computer-use/pip-electron.ts', + MAIN_RENDERER_LOADER_SOURCE, + 'src/main/permission-overlay/permission-overlay-main.ts', + ]); + for (const file of sourceFiles(resolve(desktopRoot, 'src/main'))) { + const fileRelative = normalizePath(relative(desktopRoot, file)); + if (isTestConsumer(fileRelative) || allowedNavigationFiles.has(fileRelative)) continue; + const sourceProgram = parseContractSource(desktopRoot, fileRelative, violations); + if (sourceProgram && nodesIn(sourceProgram).some((node) => navigationApiToken(node))) { + violations.push(`${fileRelative}: main-process navigation APIs are restricted to approved loaders`); + } + } + + validateMainRendererLoader(desktopRoot, violations); +} + +function validateViteEntryContract(desktopRoot, violations) { + const program = parseContractSource(desktopRoot, RENDERER_VITE_CONFIG, violations); + if (!program) return; + const defaultExports = program.body.filter((node) => node.type === 'ExportDefaultDeclaration'); + const declaration = unwrapExpression(defaultExports[0]?.declaration); + const config = + defaultExports.length === 1 && + declaration?.type === 'CallExpression' && + isIdentifier(declaration.callee, 'defineConfig') && + declaration.arguments.length === 1 + ? unwrapExpression(declaration.arguments[0]) + : undefined; + const roots = objectPropertyValues(config, 'root'); + const builds = objectPropertyValues(config, 'build'); + const plugins = objectPropertyValues(config, 'plugins'); + const outDirs = objectPropertyValues(builds[0], 'outDir'); + const rollupOptions = objectPropertyValues(builds[0], 'rollupOptions'); + const hasInputOverride = rollupOptions.some( + (options) => options?.type !== 'ObjectExpression' || objectPropertyValues(options, 'input').length > 0, + ); + const hasContractSpread = + config?.type !== 'ObjectExpression' || + config.properties.some((property) => property.type === 'SpreadElement') || + builds[0]?.type !== 'ObjectExpression' || + builds[0].properties.some((property) => property.type === 'SpreadElement'); + const pluginElements = plugins[0]?.type === 'ArrayExpression' ? plugins[0].elements : []; + const pluginCall = (index, name, argumentCount) => { + const expression = unwrapExpression(pluginElements[index]); + return expression?.type === 'CallExpression' && + isIdentifier(expression.callee, name) && + expression.arguments.length === argumentCount + ? expression + : undefined; + }; + const reactCall = pluginCall(0, 'react', 0); + const dependencyPatchesCall = pluginCall(1, 'dependencyPatchesCachePlugin', 1); + const bundledPackagesCall = pluginCall(2, 'bundledNpmPackagesPlugin', 0); + const rendererContractCall = pluginCall(3, 'rendererEntryContractPlugin', 1); + const rendererContractRoot = unwrapExpression(rendererContractCall?.arguments[0]); + const hasPinnedRendererContractRoot = + rendererContractRoot?.type === 'CallExpression' && + isIdentifier(rendererContractRoot.callee, 'resolve') && + rendererContractRoot.arguments.length === 2 && + isImportMetaDirectory(rendererContractRoot.arguments[0]) && + staticString(rendererContractRoot.arguments[1]) === 'src/renderer'; + const hasPinnedImports = + hasNamedImport(program, 'vite', 'defineConfig') && + hasNamedImport(program, 'node:path', 'resolve') && + hasDefaultImport(program, '@vitejs/plugin-react', 'react') && + hasNamedImport(program, './vite-dependency-patches.js', 'dependencyPatchesCachePlugin') && + hasNamedImport(program, './vite-bundled-packages.js', 'bundledNpmPackagesPlugin') && + hasNamedImport( + program, + './scripts/vite-renderer-entry-contract.js', + 'rendererEntryContractPlugin', + ); + const hasPinnedPlugins = + pluginElements.length === 4 && + Boolean(reactCall) && + Boolean(dependencyPatchesCall) && + isIdentifier(dependencyPatchesCall.arguments[0], 'REPO_ROOT') && + Boolean(bundledPackagesCall) && + Boolean(rendererContractCall) && + hasPinnedRendererContractRoot; + if ( + roots.length !== 1 || + staticString(roots[0]) !== 'src/renderer' || + builds.length !== 1 || + outDirs.length !== 1 || + staticString(outDirs[0]) !== '../../dist-renderer' || + plugins.length !== 1 || + !hasPinnedImports || + !hasPinnedPlugins || + hasInputOverride || + hasContractSpread + ) { + violations.push( + `${RENDERER_VITE_CONFIG}: Vite must build src/renderer/index.html into dist-renderer without an input override`, + ); + } + + const packagePath = resolve(desktopRoot, 'package.json'); + let buildScript; + try { + buildScript = JSON.parse(readFileSync(packagePath, 'utf8')).scripts?.['build:renderer']; + } catch { + buildScript = undefined; + } + if (buildScript !== RENDERER_BUILD_SCRIPT) { + violations.push('package.json: build:renderer must retain the pinned Vite renderer entry command'); + } +} + +function validateRendererEntryContract(desktopRoot, config, violations) { + const indexPath = resolve(desktopRoot, RENDERER_ENTRY_INDEX); + const entryPath = resolve(desktopRoot, RENDERER_ENTRY_SOURCE); + if (!existsSync(indexPath)) { + violations.push(`${RENDERER_ENTRY_INDEX}: renderer entry index is missing`); + return; + } + if (!existsSync(entryPath)) { + violations.push(`${RENDERER_ENTRY_SOURCE}: renderer entry source is missing`); + } + if (!Object.hasOwn(config.rootDebt, RENDERER_ENTRY_SOURCE)) { + violations.push(`${RENDERER_ENTRY_SOURCE}: renderer entry must retain its permanent rootDebt guard`); + } + + const html = readFileSync(indexPath, 'utf8'); + const openingScriptCount = html.match(/]*)>([\s\S]*?)<\/script\s*>/giu)]; + const [script] = scripts; + const isPinnedEntry = + openingScriptCount === 1 && + scripts.length === 1 && + htmlAttribute(script?.[1] ?? '', 'type') === 'module' && + htmlAttribute(script?.[1] ?? '', 'src') === RENDERER_ENTRY_SCRIPT_SOURCE && + (script?.[2] ?? '').trim().length === 0; + if (!isPinnedEntry) { + violations.push( + `${RENDERER_ENTRY_INDEX}: renderer entry contract requires exactly one empty external module script for ${RENDERER_ENTRY_SCRIPT_SOURCE}`, + ); + } + validateMainWindowEntryContract(desktopRoot, violations); + validateViteEntryContract(desktopRoot, violations); +} + +function metricTotal(value) { + if (typeof value === 'number') return value; + if (Array.isArray(value)) return value.length; + return Object.values(value).reduce((total, count) => total + count, 0); +} + +function allowsMigrationDependency({ base, current, dependency, desktopRoot, path, section }) { + if (metricTotal(current.dependencyPaths) > metricTotal(base.dependencyPaths)) return false; + const target = resolveDependency(desktopRoot, resolve(desktopRoot, path), dependency); + if (!target) return false; + const targetRelative = normalizePath(relative(desktopRoot, target)); + const targetZone = zoneFor(targetRelative); + if (section === 'legacyAppShell' || section === 'legacyAppShellClosure') { + if (targetZone.kind === 'shell' || isPublicApplicationPath(targetRelative)) return true; + const targetFeature = featureForAbsolutePath(desktopRoot, target); + return Boolean(targetFeature && isPublicFeaturePath(targetFeature.subpath)); + } + if (section === 'rootDebt') return ['bootstrap', 'composition'].includes(targetZone.kind); + return ['application', 'bootstrap', 'composition', 'platform'].includes(targetZone.kind); +} + +function debtForPath(desktopRoot, path) { + return debtMetrics(analyzeRendererSource(readFileSync(resolve(desktopRoot, path), 'utf8'), path)); +} + +function capabilityDebtForPath(desktopRoot, path) { + return capabilityDebtMetrics(analyzeRendererSource(readFileSync(resolve(desktopRoot, path), 'utf8'), path)); +} + +function collectLegacyImportEdges(desktopRoot) { + const feature = new Set(); + const platform = new Set(); + for (const file of sourceFiles(resolve(desktopRoot, 'src'))) { + const fileRelative = normalizePath(relative(desktopRoot, file)); + const sourceZone = zoneFor(fileRelative); + if (sourceZone.kind !== 'feature' && sourceZone.kind !== 'platform') continue; + const analysis = analyzeRendererSource(readFileSync(file, 'utf8'), fileRelative); + for (const dependency of analysis.dependencies) { + const target = resolveDependency(desktopRoot, file, dependency); + if (!target) continue; + const targetRelative = normalizePath(relative(desktopRoot, target)); + if (zoneFor(targetRelative).kind !== 'legacy') continue; + const edge = `${fileRelative} -> ${targetRelative}`; + if (sourceZone.kind === 'feature') feature.add(edge); + else platform.add(edge); + } + } + return { feature: [...feature].sort(), platform: [...platform].sort() }; +} + +export function generateArchitectureConfig(desktopRoot, config) { + const rendererRoot = resolve(desktopRoot, 'src/renderer'); + const legacyRendererFiles = sourceFiles(rendererRoot) + .map((path) => normalizePath(relative(desktopRoot, path))) + .filter((path) => zoneFor(path).kind === 'legacy') + .sort(); + const appShellFiles = readdirSync(rendererRoot, { withFileTypes: true }) + .filter((entry) => entry.isFile() && LEGACY_APP_SHELL_FILE.test(entry.name)) + .map((entry) => `src/renderer/${entry.name}`) + .sort(); + const closureViolations = []; + const closureFiles = collectRootDependencyClosure(desktopRoot, appShellFiles, closureViolations, 'AppShell'); + if (closureViolations.length > 0) { + throw new Error(`cannot generate AppShell closure:\n${closureViolations.join('\n')}`); + } + const imports = collectLegacyImportEdges(desktopRoot); + const rootDebt = {}; + for (const path of Object.keys(config.rootDebt ?? {}).sort()) { + if (existsSync(resolve(desktopRoot, path))) rootDebt[path] = debtForPath(desktopRoot, path); + } + const appShellDebtPaths = new Set([...appShellFiles, ...closureFiles]); + const rootDebtClosureFiles = collectRootDependencyClosure( + desktopRoot, + Object.keys(rootDebt), + closureViolations, + 'renderer root', + ).filter((path) => !appShellDebtPaths.has(path)); + if (closureViolations.length > 0) { + throw new Error(`cannot generate renderer root closure:\n${closureViolations.join('\n')}`); + } + return { + version: 1, + legacyRendererFiles, + legacyGrowthDirectories: config.legacyGrowthDirectories ?? DEFAULT_LEGACY_GROWTH_DIRECTORIES, + legacyFeatureImports: imports.feature, + legacyPlatformImports: imports.platform, + legacyAppShell: { + files: Object.fromEntries(appShellFiles.map((path) => [path, debtForPath(desktopRoot, path)])), + closure: Object.fromEntries(closureFiles.map((path) => [path, capabilityDebtForPath(desktopRoot, path)])), + }, + rootDebt, + rootDebtClosure: Object.fromEntries( + rootDebtClosureFiles.map((path) => [path, capabilityDebtForPath(desktopRoot, path)]), + ), + ownership: config.ownership ?? [], + }; +} + +function validateMonotonicDebt(config, baseConfig, desktopRoot, violations) { + if (!baseConfig) return; + for (const section of ['legacyAppShell', 'legacyAppShellClosure', 'rootDebt', 'rootDebtClosure']) { + const currentFiles = + section === 'legacyAppShell' + ? config.legacyAppShell.files + : section === 'legacyAppShellClosure' + ? config.legacyAppShell.closure + : section === 'rootDebt' + ? config.rootDebt + : config.rootDebtClosure; + const baseFiles = + section === 'legacyAppShell' + ? baseConfig.legacyAppShell.files + : section === 'legacyAppShellClosure' + ? baseConfig.legacyAppShell.closure + : section === 'rootDebt' + ? baseConfig.rootDebt + : baseConfig.rootDebtClosure; + for (const path of Object.keys(currentFiles)) { + const shiftedAppShellBase = + section === 'rootDebtClosure' ? baseConfig.legacyAppShell.closure[path] : undefined; + if (!baseFiles[path] && !shiftedAppShellBase) { + violations.push(`${path}: new ${section} debt entries are forbidden`); + } + } + if (section === 'rootDebt') { + for (const path of Object.keys(baseFiles)) { + if (!currentFiles[path] && existsSync(resolve(desktopRoot, path))) { + violations.push(`${path}: permanent root entry guard cannot be removed while the source exists`); + } + } + } + for (const [path, current] of Object.entries(currentFiles)) { + const base = + baseFiles[path] ?? + (section === 'rootDebtClosure' ? baseConfig.legacyAppShell.closure[path] : undefined); + if (!base) continue; + const metrics = section.endsWith('Closure') ? CAPABILITY_DEBT_METRICS : ROOT_DEBT_METRICS; + for (const metric of metrics) { + if (metricTotal(current[metric]) > metricTotal(base[metric])) { + violations.push(`${path}: ${metric} debt increased from ${metricTotal(base[metric])} to ${metricTotal(current[metric])}`); + } + if (['bridgePaths', 'environmentCapabilities', 'hookCalls', 'lifecycleMethods'].includes(metric)) { + for (const [key, count] of Object.entries(current[metric])) { + if (count > (base[metric][key] ?? 0)) { + violations.push(`${path}: new or increased ${metric} debt ${key}`); + } + } + } + if (metric === 'actionFactories') { + for (const factory of current.actionFactories) { + if (!base.actionFactories.includes(factory)) violations.push(`${path}: new action factory debt ${factory}`); + } + } + if (metric === 'dependencyPaths') { + for (const [dependency, count] of Object.entries(current.dependencyPaths)) { + if ( + count > (base.dependencyPaths[dependency] ?? 0) && + !allowsMigrationDependency({ base, current, dependency, desktopRoot, path, section }) + ) { + violations.push(`${path}: new dependency debt ${dependency}`); + } + } + } + } + } + } + + const baseLegacyFiles = new Set(baseConfig.legacyRendererFiles); + for (const path of config.legacyRendererFiles) { + if (!baseLegacyFiles.has(path) && !isAllowedLegacyGrowthPath(config, path)) { + violations.push(`${path}: new unclassified renderer source files are forbidden outside approved legacy directories`); + } + } + const baseLegacyGrowthDirectories = new Set(baseConfig.legacyGrowthDirectories); + for (const path of config.legacyGrowthDirectories) { + if (!baseLegacyGrowthDirectories.has(path)) { + violations.push(`${path}: new legacy growth directories are forbidden`); + } + } + const baseLegacyFeatureImports = new Set(baseConfig.legacyFeatureImports); + for (const edge of config.legacyFeatureImports) { + if (!baseLegacyFeatureImports.has(edge)) violations.push(`${edge}: new feature-to-legacy imports are forbidden`); + } + const baseLegacyPlatformImports = new Set(baseConfig.legacyPlatformImports); + for (const edge of config.legacyPlatformImports) { + if (!baseLegacyPlatformImports.has(edge)) violations.push(`${edge}: new platform-to-legacy imports are forbidden`); + } +} + +export function checkRendererArchitecture({ + desktopRoot, + config, + baseConfig, + enforceRendererEntryContract = true, +} = {}) { + const resolvedDesktopRoot = resolve(desktopRoot ?? fileURLToPath(new URL('..', import.meta.url))); + const resolvedConfig = config ?? JSON.parse(readFileSync(join(resolvedDesktopRoot, 'renderer-architecture.json'), 'utf8')); + const violations = []; + + const currentConfigValid = validateArchitectureConfig(resolvedConfig, 'current', violations); + const baseConfigValid = baseConfig ? validateArchitectureConfig(baseConfig, 'base', violations) : true; + if (!currentConfigValid || !baseConfigValid) return violations.sort(); + if (enforceRendererEntryContract) { + validateRendererEntryContract(resolvedDesktopRoot, resolvedConfig, violations); + } + validateLegacyLedger(resolvedDesktopRoot, resolvedConfig, violations); + validateMonotonicDebt(resolvedConfig, baseConfig, resolvedDesktopRoot, violations); + const allowedLegacyFeatureImports = new Set(resolvedConfig.legacyFeatureImports); + const allowedLegacyPlatformImports = new Set(resolvedConfig.legacyPlatformImports); + const observedLegacyFeatureImports = new Set(); + const observedLegacyPlatformImports = new Set(); + + for (const scanRoot of ['src', 'stories', 'e2e']) { + for (const file of sourceFiles(resolve(resolvedDesktopRoot, scanRoot))) { + const fileRelative = normalizePath(relative(resolvedDesktopRoot, file)); + let analysis; + try { + analysis = analyzeRendererSource(readFileSync(file, 'utf8'), fileRelative); + } catch (error) { + violations.push(`${fileRelative}: could not parse source: ${error instanceof Error ? error.message : String(error)}`); + continue; + } + validateStrictZone({ fileRelative, analysis, violations }); + validateDependencies({ + allowedLegacyFeatureImports, + allowedLegacyPlatformImports, + analysis, + desktopRoot: resolvedDesktopRoot, + file, + fileRelative, + observedLegacyFeatureImports, + observedLegacyPlatformImports, + violations, + }); + } + } + + for (const edge of allowedLegacyFeatureImports) { + if (!observedLegacyFeatureImports.has(edge)) violations.push(`${edge}: stale feature-to-legacy import budget`); + } + for (const edge of allowedLegacyPlatformImports) { + if (!observedLegacyPlatformImports.has(edge)) violations.push(`${edge}: stale platform-to-legacy import budget`); + } + + return violations.sort(); +} + +function loadBaseConfig(repoRoot, desktopRoot, base) { + if (!base) return { baseConfig: undefined, introducedLedger: false }; + const relativeConfig = normalizePath(relative(repoRoot, join(desktopRoot, 'renderer-architecture.json'))); + try { + execFileSync('git', ['rev-parse', '--verify', `${base}^{commit}`], { + cwd: repoRoot, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + } catch { + throw new Error(`base ref does not resolve to a commit: ${base}`); + } + + let source; + try { + source = execFileSync('git', ['show', `${base}:${relativeConfig}`], { + cwd: repoRoot, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + } catch { + const diffStatus = execFileSync('git', ['diff', '--name-status', base, '--', relativeConfig], { + cwd: repoRoot, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + const worktreeStatus = execFileSync( + 'git', + ['status', '--porcelain=v1', '--untracked-files=all', '--', relativeConfig], + { + cwd: repoRoot, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }, + ).trim(); + if (diffStatus === `A\t${relativeConfig}` || worktreeStatus === `?? ${relativeConfig}`) { + return { baseConfig: undefined, introducedLedger: true }; + } + throw new Error(`base ledger is missing at ${base}:${relativeConfig}`); + } + + try { + return { baseConfig: JSON.parse(source), introducedLedger: false }; + } catch (error) { + throw new Error( + `base ledger is invalid JSON at ${base}:${relativeConfig}: ${error instanceof Error ? error.message : String(error)}`, + ); + } +} + +function parseCliArguments(args) { + let base; + let write = false; + for (let index = 0; index < args.length; index += 1) { + const argument = args[index]; + if (argument === '--write' && !write) { + write = true; + continue; + } + if (argument === '--base' && base === undefined) { + const value = args[index + 1]; + if (!value || value.startsWith('--')) { + throw new Error('usage: check-renderer-architecture.mjs [--write] [--base ]'); + } + base = value; + index += 1; + continue; + } + throw new Error('usage: check-renderer-architecture.mjs [--write] [--base ]'); + } + return { base, write }; +} + +function runCli() { + const desktopRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); + const repoRoot = resolve(desktopRoot, '../..'); + let base; + let config; + let loadedBase; + let write; + try { + ({ base, write } = parseCliArguments(process.argv.slice(2))); + config = JSON.parse(readFileSync(join(desktopRoot, 'renderer-architecture.json'), 'utf8')); + loadedBase = loadBaseConfig(repoRoot, desktopRoot, base); + if (write) { + config = generateArchitectureConfig(desktopRoot, config); + writeFileSync(join(desktopRoot, 'renderer-architecture.json'), `${JSON.stringify(config, null, 2)}\n`); + console.log('Renderer architecture ledger updated.'); + } + } catch (error) { + console.error(`Renderer architecture check could not start: ${error instanceof Error ? error.message : String(error)}`); + process.exitCode = 1; + return; + } + const { baseConfig, introducedLedger } = loadedBase; + const violations = checkRendererArchitecture({ baseConfig, config, desktopRoot }); + if (violations.length > 0) { + console.error('Renderer architecture check failed:'); + for (const violation of violations) console.error(`- ${violation}`); + process.exitCode = 1; + return; + } + if (introducedLedger) { + console.log(`Renderer architecture check passed; ${base} has no ledger and this change introduces it.`); + return; + } + console.log(`Renderer architecture check passed${baseConfig ? ` against ${base}` : ''}.`); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) runCli(); diff --git a/apps/desktop/scripts/check-renderer-architecture.test.mjs b/apps/desktop/scripts/check-renderer-architecture.test.mjs new file mode 100644 index 0000000000..2d21041d4c --- /dev/null +++ b/apps/desktop/scripts/check-renderer-architecture.test.mjs @@ -0,0 +1,2122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { strict as assert } from 'node:assert'; +import { spawnSync } from 'node:child_process'; +import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { + analyzeRendererSource, + checkRendererArchitecture, + generateArchitectureConfig, +} from './check-renderer-architecture.mjs'; +import { + assertRendererEntryHtml, + rendererEntryContractPlugin, +} from './vite-renderer-entry-contract.ts'; + +const TRANSITIVE_APP_SHELL_PATH = 'src/renderer/app-shell.ts'; +const TRANSITIVE_LEGACY_HELPER_PATH = 'src/renderer/legacy-session-helper.ts'; +const RENDERER_ENTRY_PATH = 'src/renderer/main.tsx'; + +function emptyDebt(overrides = {}) { + return { + importDeclarations: 0, + importSpecifiers: 0, + nonTriviaTokens: 0, + dependencyPaths: {}, + bridgePaths: {}, + environmentCapabilities: {}, + hookCalls: {}, + lifecycleMethods: {}, + unresolvedDependencies: 0, + actionFactories: [], + ...overrides, + }; +} + +function architectureConfig({ + legacyAppShellClosureDebt, + legacyFeatureImports = [], + legacyFiles = {}, + legacyGrowthDirectories = [], + legacyPlatformImports = [], + rootDebt = {}, + rootDebtClosure = {}, + legacyRendererFiles = Object.keys(rootDebt), + ownership = [], +} = {}) { + return { + version: 1, + legacyRendererFiles: [...legacyRendererFiles].sort(), + legacyGrowthDirectories: [...legacyGrowthDirectories].sort(), + legacyFeatureImports: [...legacyFeatureImports].sort(), + legacyPlatformImports: [...legacyPlatformImports].sort(), + legacyAppShell: { + files: legacyFiles, + closure: legacyAppShellClosureDebt ?? {}, + }, + rootDebt, + rootDebtClosure, + ownership, + }; +} + +function debtForSource(source, path) { + const analysis = analyzeRendererSource(source, path); + return { + importDeclarations: analysis.importDeclarations, + importSpecifiers: analysis.importSpecifiers, + nonTriviaTokens: analysis.nonTriviaTokens, + dependencyPaths: analysis.dependencyPaths, + bridgePaths: analysis.bridgePaths, + environmentCapabilities: analysis.environmentCapabilities, + hookCalls: analysis.hookCalls, + lifecycleMethods: analysis.lifecycleMethods, + unresolvedDependencies: analysis.unresolvedDependencies, + actionFactories: analysis.actionFactories, + }; +} + +function capabilityDebtForSource(source, path) { + const debt = debtForSource(source, path); + return { + actionFactories: debt.actionFactories, + bridgePaths: debt.bridgePaths, + dependencyPaths: debt.dependencyPaths, + environmentCapabilities: debt.environmentCapabilities, + hookCalls: debt.hookCalls, + lifecycleMethods: debt.lifecycleMethods, + unresolvedDependencies: debt.unresolvedDependencies, + }; +} + +async function withDesktopFixture(files, run) { + const desktopRoot = await mkdtemp(join(tmpdir(), 'maka-renderer-architecture-')); + try { + for (const [path, source] of Object.entries(files)) { + const absolutePath = join(desktopRoot, path); + await mkdir(dirname(absolutePath), { recursive: true }); + await writeFile(absolutePath, source, 'utf8'); + } + return await run(desktopRoot); + } finally { + await rm(desktopRoot, { force: true, recursive: true }); + } +} + +function violationsFor(desktopRoot, config = architectureConfig(), baseConfig) { + return checkRendererArchitecture({ + baseConfig, + config, + desktopRoot, + enforceRendererEntryContract: false, + }); +} + +function assertHasViolation(violations, pattern) { + assert.ok( + violations.some((violation) => pattern.test(violation)), + `Expected a violation matching ${pattern}, received:\n${violations.join('\n')}`, + ); +} + +function transitiveAppShellFiles(helperSource, extraFiles = {}) { + return { + [TRANSITIVE_APP_SHELL_PATH]: ` + import { legacySessionHelper } from './legacy-session-helper.js'; + export const AppShell = legacySessionHelper; + `, + [TRANSITIVE_LEGACY_HELPER_PATH]: helperSource, + ...extraFiles, + }; +} + +function transitiveAppShellSeedConfig() { + return architectureConfig({ + ownership: [ + { + capability: 'fixture-app-shell', + targetZone: 'shell', + legacyPaths: [TRANSITIVE_APP_SHELL_PATH], + }, + ], + }); +} + +function rendererEntrySeedConfig() { + return architectureConfig({ + rootDebt: { [RENDERER_ENTRY_PATH]: emptyDebt() }, + ownership: [ + { + capability: 'fixture-root', + targetZone: 'bootstrap', + legacyPaths: [RENDERER_ENTRY_PATH], + }, + ], + }); +} + +function rendererEntryContractFiles(overrides = {}) { + return { + 'src/renderer/index.html': ` + + + `, + [RENDERER_ENTRY_PATH]: `export const main = true;`, + 'src/main/main-window.ts': ` + import { loadMainRenderer, resolveMainRendererEntry } from './main-renderer-loader.js'; + async function createWindow() { + const rendererEntry = resolveMainRendererEntry(import.meta.dirname, process.env.VITE_DEV_SERVER_URL); + await loadMainRenderer(mainWindow, rendererEntry); + } + `, + 'src/main/main-renderer-loader.ts': ` + import { join } from 'node:path'; + import { pathToFileURL } from 'node:url'; + interface MainRendererWindow { + loadFile(path: string): Promise; + loadURL(url: string): Promise; + } + interface MainRendererEntry { + readonly filePath: string; + readonly url: string; + readonly useDevServer: boolean; + } + export function resolveMainRendererEntry( + mainModuleDirectory: string, + viteDevServerUrl: string | undefined, + ): MainRendererEntry { + const rendererEntryPath = join(mainModuleDirectory, '..', '..', 'dist-renderer', 'index.html'); + const rendererEntryUrl = viteDevServerUrl ?? pathToFileURL(rendererEntryPath).href; + return Object.freeze({ + filePath: rendererEntryPath, + url: rendererEntryUrl, + useDevServer: !!viteDevServerUrl, + }); + } + export async function loadMainRenderer( + mainWindow: MainRendererWindow, + rendererEntry: MainRendererEntry, + ): Promise { + if (rendererEntry.useDevServer) { + await mainWindow.loadURL(rendererEntry.url); + } else { + await mainWindow.loadFile(rendererEntry.filePath); + } + } + `, + 'vite.config.ts': ` + import react from '@vitejs/plugin-react'; + import { resolve } from 'node:path'; + import { defineConfig } from 'vite'; + import { rendererEntryContractPlugin } from './scripts/vite-renderer-entry-contract.js'; + import { bundledNpmPackagesPlugin } from './vite-bundled-packages.js'; + import { dependencyPatchesCachePlugin } from './vite-dependency-patches.js'; + const REPO_ROOT = '/fixture'; + export default defineConfig({ + root: 'src/renderer', + plugins: [ + react(), + dependencyPatchesCachePlugin(REPO_ROOT), + bundledNpmPackagesPlugin(), + rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer')), + ], + build: { outDir: '../../dist-renderer' }, + }); + `, + 'package.json': JSON.stringify({ + scripts: { + 'build:renderer': + 'vite build && node scripts/check-renderer-entry-output.mjs && node ../../scripts/check-third-party-notices.mjs', + }, + }), + ...overrides, + }; +} + +function runRendererEntryBundleContract(directImports) { + const plugin = rendererEntryContractPlugin('/fixture/src/renderer'); + plugin.configResolved({ root: '/fixture/src/renderer' }); + assert.equal(typeof plugin.generateBundle, 'function'); + plugin.generateBundle.call( + { + emitFile() {}, + error(message) { + throw new Error(message); + }, + getModuleInfo(id) { + return id === '/fixture/src/renderer/index.html' ? { importedIds: directImports } : null; + }, + }, + {}, + { + 'assets/index.js': { + type: 'chunk', + isEntry: true, + facadeModuleId: '/fixture/src/renderer/index.html', + }, + }, + ); +} + +function canonicalRendererEntryHtml(extraBody = '', policy = "script-src 'self'") { + return ` + + + + + + + Maka + + + +
+ ${extraBody} + + + + `; +} + +describe('renderer architecture checker fixtures', () => { + it('accepts the legal inward dependency graph and testing entry boundary', async () => { + await withDesktopFixture( + { + 'src/renderer/application/contracts/regions.ts': ` + export interface RegionModel { readonly id: string } + export const fetch = () => 'injected-contract'; + `, + 'src/renderer/application/session/session-scope.ts': ` + import type { RegionModel } from '../contracts/regions.js'; + export type SessionScope = RegionModel; + `, + 'src/renderer/shell/shell-frame.tsx': ` + import type { RegionModel } from '../application/contracts/regions.js'; + import { fetch } from '../application/contracts/regions.js'; + export function ShellFrame(props: { readonly region: RegionModel }) { + return
; + } + `, + 'src/renderer/features/alpha/index.ts': ` + export { AlphaHost } from './ui/alpha-host.js'; + export type { AlphaServices } from './ports.js'; + `, + 'src/renderer/features/alpha/ports.ts': ` + export interface AlphaServices { readonly read: () => Promise } + `, + 'src/renderer/features/alpha/testing.ts': ` + export function createFakeAlphaServices() { + return { read: async () => 'fixture' }; + } + `, + 'src/renderer/features/alpha/ui/alpha-host.tsx': ` + import type { RegionModel } from '../../../application/contracts/regions.js'; + export function AlphaHost(props: RegionModel) { + return
{props.id}
; + } + `, + 'src/renderer/platform/desktop/create-alpha-services.ts': ` + import type { AlphaServices } from '../../features/alpha/index.js'; + export function createAlphaServices(): AlphaServices { + return { read: async () => 'desktop' }; + } + `, + 'src/renderer/composition/desktop-application.tsx': ` + import { AlphaHost } from '../features/alpha/index.js'; + import { createAlphaServices } from '../platform/desktop/create-alpha-services.js'; + import { ShellFrame } from '../shell/shell-frame.js'; + void createAlphaServices; + export function DesktopApplication() { + return <>; + } + `, + 'src/main/__tests__/alpha-boundary.test.ts': ` + import { createFakeAlphaServices } from '../../renderer/features/alpha/testing.js'; + void createFakeAlphaServices; + `, + 'stories/alpha.stories.tsx': ` + import { createFakeAlphaServices } from '../src/renderer/features/alpha/testing.js'; + export const services = createFakeAlphaServices(); + `, + }, + (desktopRoot) => { + assert.deepEqual(violationsFor(desktopRoot), []); + }, + ); + }); + + it('resolves @maka/desktop self-imports before enforcing zone boundaries', async () => { + await withDesktopFixture( + { + 'src/renderer/features/alpha/controller.ts': ` + import { desktopOnly } from '@maka/desktop/src/renderer/platform/desktop/private.js'; + export const alpha = desktopOnly; + `, + 'src/renderer/shell/shell-frame.ts': ` + import { BetaHost } from '@maka/desktop/src/renderer/features/beta/index.js'; + export const frame = BetaHost; + `, + 'src/renderer/application/session/session-scope.ts': ` + import { BetaHost } from '@maka/desktop/src/renderer/features/beta/index.js'; + export const scope = BetaHost; + `, + 'src/renderer/features/beta/index.ts': 'export const BetaHost = true;', + 'src/renderer/platform/desktop/private.ts': 'export const desktopOnly = true;', + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /features\/alpha\/controller\.ts: feature imports a forbidden Desktop\/shell module/u, + ); + assertHasViolation( + violations, + /shell\/shell-frame\.ts: shell imports forbidden implementation module/u, + ); + assertHasViolation( + violations, + /application\/session\/session-scope\.ts: application authority imports an outer implementation/u, + ); + }, + ); + }); + + it('rejects direct browser environment access in shell and composition files', async () => { + await withDesktopFixture( + { + 'src/renderer/shell/local-storage.ts': ` + export const persisted = localStorage.getItem('shell-layout'); + `, + 'src/renderer/shell/window-listener.ts': ` + window.addEventListener('resize', () => undefined); + `, + 'src/renderer/composition/fetch.ts': ` + export const request = fetch('/renderer-bootstrap'); + `, + 'src/renderer/composition/timers.ts': ` + setTimeout(() => undefined, 1); + requestAnimationFrame(() => undefined); + `, + 'src/renderer/shell/aliased-environment.ts': ` + const later = setTimeout; + const retrieve = fetch; + later(() => undefined, 1); + void retrieve('/shell-data'); + `, + 'src/renderer/composition/browser-globals.ts': ` + void indexedDB.open('maka'); + history.pushState({}, '', '/'); + void new FileReader(); + addEventListener('online', () => undefined); + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + for (const path of [ + 'src/renderer/shell/local-storage.ts', + 'src/renderer/shell/window-listener.ts', + 'src/renderer/composition/fetch.ts', + 'src/renderer/composition/timers.ts', + 'src/renderer/shell/aliased-environment.ts', + 'src/renderer/composition/browser-globals.ts', + ]) { + assertHasViolation( + violations, + new RegExp( + `^${path.replaceAll('/', '\\/').replaceAll('.', '\\.')}.*directly accesses browser environment capabilities$`, + 'u', + ), + ); + } + }, + ); + }); + + it('resolves browser-global shadowing lexically instead of masking the whole file', () => { + const analysis = analyzeRendererSource( + ` + function useInjected(fetch, history, setTimeout) { + fetch('/injected'); + history.pushState({}, '', '/injected'); + setTimeout(() => undefined, 1); + } + void useInjected; + fetch('/global'); + history.pushState({}, '', '/global'); + setTimeout(() => undefined, 1); + `, + 'src/renderer/shell/lexical-browser-globals.ts', + ); + + assert.equal(analysis.environmentCapabilities.fetch, 1); + assert.equal(analysis.environmentCapabilities['history.pushState'], 1); + assert.equal(analysis.environmentCapabilities.setTimeout, 1); + }); + + it('rejects computed and optional access to the Desktop bridge in strict zones', async () => { + await withDesktopFixture( + { + 'src/renderer/application/computed-bridge.ts': ` + void window['maka'].sessions.list(); + `, + 'src/renderer/application/optional-bridge.ts': ` + void window?.maka?.sessions?.subscribeEvents?.(() => undefined); + `, + 'src/renderer/application/dynamic-global.ts': ` + const bridgeName = 'maka'; + void window[bridgeName]; + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /^src\/renderer\/application\/computed-bridge\.ts: application code accesses the Desktop global bridge$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/application\/optional-bridge\.ts: application code accesses the Desktop global bridge$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/application\/dynamic-global\.ts: application code contains non-static global environment access$/u, + ); + }, + ); + }); + + it('rejects cross-feature, deep feature, and production testing imports', async () => { + await withDesktopFixture( + { + 'src/renderer/features/alpha/controller.ts': ` + export async function loadBeta() { + return import('../beta/index.js'); + } + `, + 'src/renderer/features/beta/index.ts': ` + export const beta = true; + `, + 'src/renderer/composition/deep-feature.ts': ` + const alphaController = require('../features/alpha/controller/use-alpha.js'); + void alphaController; + `, + 'src/renderer/composition/testing-in-production.ts': ` + import { createFakeAlphaServices } from '../features/alpha/testing.js'; + void createFakeAlphaServices; + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /^src\/renderer\/features\/alpha\/controller\.ts: feature alpha imports feature beta:/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/composition\/deep-feature\.ts: feature imports must use index:/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/composition\/testing-in-production\.ts: production code imports a feature testing entry:/u, + ); + }, + ); + }); + + it('rejects a feature production module importing its own testing entry', async () => { + await withDesktopFixture( + { + 'src/renderer/features/alpha/controller.ts': ` + import { fakeAlpha } from './testing.js'; + export const alpha = fakeAlpha; + `, + 'src/renderer/features/alpha/testing.ts': 'export const fakeAlpha = true;', + }, + (desktopRoot) => { + assertHasViolation( + violationsFor(desktopRoot), + /features\/alpha\/controller\.ts:.*testing entry/u, + ); + }, + ); + }); + + it('rejects an application contract re-exporting application implementation', async () => { + await withDesktopFixture( + { + 'src/renderer/application/contracts/index.ts': ` + export { sessionStore } from '../session/session-store.js'; + `, + 'src/renderer/application/session/session-store.ts': ` + export const sessionStore = true; + `, + }, + (desktopRoot) => { + assertHasViolation( + violationsFor(desktopRoot), + /application\/contracts\/index\.ts:.*(?:contract|contracts).*implementation/iu, + ); + }, + ); + }); + + it('rejects composition and Desktop adapters importing deep application implementation', async () => { + await withDesktopFixture( + { + 'src/renderer/application/session/session-store.ts': `export const sessionStore = true;`, + 'src/renderer/composition/deep-application.ts': ` + import { sessionStore } from '../application/session/session-store.js'; + export const composed = sessionStore; + `, + 'src/renderer/platform/desktop/deep-application.ts': ` + import { sessionStore } from '../../application/session/session-store.js'; + export const adapted = sessionStore; + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /composition\/deep-application\.ts: composition imports application implementation instead of a public entry/u, + ); + assertHasViolation( + violations, + /platform\/desktop\/deep-application\.ts: Desktop adapter imports application implementation instead of a public entry/u, + ); + }, + ); + }); + + it('rejects Electron and node built-in imports from strict feature code', async () => { + await withDesktopFixture( + { + 'src/renderer/features/alpha/electron-import.ts': ` + import { ipcRenderer } from 'electron'; + export const ipc = ipcRenderer; + `, + 'src/renderer/features/alpha/node-import.ts': ` + import { readFile } from 'node:fs/promises'; + export const read = readFile; + `, + 'src/renderer/features/alpha/controller.test.ts': ` + import { strict as assert } from 'node:assert'; + assert.equal(1, 1); + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /features\/alpha\/electron-import\.ts:.*feature.*electron/iu, + ); + assertHasViolation( + violations, + /features\/alpha\/node-import\.ts:.*feature.*node:fs/iu, + ); + assert.ok( + !violations.some((violation) => violation.includes('controller.test.ts')), + `Feature tests may use the Node test environment:\n${violations.join('\n')}`, + ); + }, + ); + }); + + it('keeps Desktop platform adapters free of UI lifecycle and runtime module loading', async () => { + await withDesktopFixture( + { + 'src/renderer/platform/desktop/stateful-view.tsx': ` + import * as React from 'react'; + import { useState } from 'react'; + export function StatefulView() { + const [open] = useState(false); + return
{String(open)}
; + } + export class StatefulAdapter extends React.Component { + componentDidMount() {} + render() { return null; } + } + `, + 'src/renderer/platform/desktop/dynamic-adapter.ts': ` + export function loadAdapter(path: string) { return import(path); } + `, + 'src/renderer/platform/desktop/electron-adapter.ts': ` + import { ipcRenderer } from 'electron'; + export const ipc = ipcRenderer; + `, + 'src/renderer/platform/desktop/node-adapter.ts': ` + import { readFile } from 'node:fs/promises'; + export const read = readFile; + `, + 'src/renderer/platform/desktop/allowed-capabilities.ts': ` + export function createAllowedCapabilities() { + localStorage.getItem('desktop-adapter'); + return window.maka.sessions.list(); + } + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /platform\/desktop\/stateful-view\.tsx: platform code owns stateful React hooks/u, + ); + assertHasViolation( + violations, + /platform\/desktop\/stateful-view\.tsx: platform code owns React class lifecycle methods/u, + ); + assertHasViolation( + violations, + /platform\/desktop\/stateful-view\.tsx: Desktop adapters cannot own React UI/u, + ); + assertHasViolation( + violations, + /platform\/desktop\/dynamic-adapter\.ts: platform code contains a non-static import or require/u, + ); + assertHasViolation( + violations, + /platform\/desktop\/electron-adapter\.ts: platform code imports forbidden environment module: electron/u, + ); + assertHasViolation( + violations, + /platform\/desktop\/node-adapter\.ts: platform code imports forbidden environment module: node:fs\/promises/u, + ); + assert.ok( + !violations.some((violation) => violation.includes('allowed-capabilities.ts')), + `Desktop adapters may own the bridge and browser environment:\n${violations.join('\n')}`, + ); + }, + ); + }); + + it('rejects new AppShell-family helper files outside the debt ledger', async () => { + await withDesktopFixture( + { + 'src/renderer/app-shell-fresh-owner.ts': 'export const owner = true;', + 'src/renderer/use-app-shell-fresh-owner.ts': 'export const owner = true;', + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /^legacy AppShell file set changed;.*app-shell-fresh-owner\.ts.*use-app-shell-fresh-owner\.ts/u, + ); + }, + ); + }); + + it('generates the transitive legacy closure owned directly by AppShell', async () => { + const helperSource = ` + export const legacySessionHelper = 'legacy-session'; + `; + + await withDesktopFixture( + transitiveAppShellFiles(helperSource), + (desktopRoot) => { + const generated = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + + assert.deepEqual( + Object.keys(generated.legacyAppShell.closure), + [TRANSITIVE_LEGACY_HELPER_PATH], + ); + assert.deepEqual( + generated.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH], + capabilityDebtForSource(helperSource, TRANSITIVE_LEGACY_HELPER_PATH), + ); + }, + ); + }); + + it('prefers runtime source over a same-stem declaration in the AppShell closure', async () => { + const runtimePath = 'src/renderer/legacy-session-helper.js'; + const runtimeSource = ` + export function legacySessionHelper() { + return window.maka.sessions.list(); + } + `; + + await withDesktopFixture( + { + [TRANSITIVE_APP_SHELL_PATH]: ` + import { legacySessionHelper } from './legacy-session-helper.js'; + export const AppShell = legacySessionHelper; + `, + 'src/renderer/legacy-session-helper.d.ts': ` + export declare function legacySessionHelper(): Promise; + `, + [runtimePath]: runtimeSource, + }, + (desktopRoot) => { + const generated = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + + assert.deepEqual(Object.keys(generated.legacyAppShell.closure), [runtimePath]); + assert.deepEqual( + generated.legacyAppShell.closure[runtimePath], + capabilityDebtForSource(runtimeSource, runtimePath), + ); + }, + ); + }); + + it('ratchets legacy helpers reachable through a feature public ownership boundary', async () => { + await withDesktopFixture( + { + [TRANSITIVE_APP_SHELL_PATH]: ` + import { AlphaHost } from './features/alpha/index.js'; + export const AppShell = AlphaHost; + `, + 'src/renderer/features/alpha/index.ts': ` + import { legacySessionHelper } from '../../legacy-session-helper.js'; + export const AlphaHost = legacySessionHelper; + `, + [TRANSITIVE_LEGACY_HELPER_PATH]: ` + import { useState } from 'react'; + export function legacySessionHelper() { + void window.maka.sessions.list(); + localStorage.getItem('feature-owned'); + return useState(false); + } + `, + }, + (desktopRoot) => { + const currentConfig = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + assert.deepEqual( + Object.keys(currentConfig.legacyAppShell.closure), + [TRANSITIVE_LEGACY_HELPER_PATH], + ); + + const baseConfig = structuredClone(currentConfig); + const baseHelperDebt = baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH]; + baseHelperDebt.hookCalls = {}; + baseHelperDebt.bridgePaths = {}; + baseHelperDebt.environmentCapabilities = {}; + + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: new or increased hookCalls debt useState$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u, + ); + }, + ); + }); + + it('ratchets legacy helpers reachable through a shared Desktop boundary', async () => { + await withDesktopFixture( + { + [TRANSITIVE_APP_SHELL_PATH]: ` + import { rootBridge } from '../shared/root-bridge.js'; + export const AppShell = rootBridge; + `, + 'src/shared/root-bridge.ts': ` + import { legacySessionHelper } from '../renderer/legacy-session-helper.js'; + export const rootBridge = legacySessionHelper; + `, + [TRANSITIVE_LEGACY_HELPER_PATH]: ` + export function legacySessionHelper() { + return window.maka.sessions.list(); + } + `, + }, + (desktopRoot) => { + const currentConfig = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + assert.deepEqual( + Object.keys(currentConfig.legacyAppShell.closure), + [TRANSITIVE_LEGACY_HELPER_PATH, 'src/shared/root-bridge.ts'], + ); + + const baseConfig = structuredClone(currentConfig); + baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].bridgePaths = {}; + + assertHasViolation( + violationsFor(desktopRoot, currentConfig, baseConfig), + /^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, + ); + }, + ); + }); + + it('ratchets capability debt owned directly by a shared Desktop intermediary', async () => { + const sharedPath = 'src/shared/root-bridge.ts'; + await withDesktopFixture( + { + [TRANSITIVE_APP_SHELL_PATH]: ` + import { rootBridge } from '../shared/root-bridge.js'; + export const AppShell = rootBridge; + `, + [sharedPath]: ` + import { useState } from 'react'; + export function rootBridge() { + void window.maka.sessions.list(); + localStorage.getItem('shared-root-state'); + return useState(false); + } + `, + }, + (desktopRoot) => { + const currentConfig = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + assert.deepEqual(Object.keys(currentConfig.legacyAppShell.closure), [sharedPath]); + + const baseConfig = structuredClone(currentConfig); + const baseSharedDebt = baseConfig.legacyAppShell.closure[sharedPath]; + baseSharedDebt.hookCalls = {}; + baseSharedDebt.bridgePaths = {}; + baseSharedDebt.environmentCapabilities = {}; + + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/shared\/root-bridge\.ts: new or increased hookCalls debt useState$/u, + ); + assertHasViolation( + violations, + /^src\/shared\/root-bridge\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, + ); + assertHasViolation( + violations, + /^src\/shared\/root-bridge\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u, + ); + }, + ); + }); + + it('allows support debt to leave the AppShell closure but rejects the reverse ownership regression', async () => { + const mainPath = 'src/renderer/main.tsx'; + const compositionPath = 'src/renderer/composition/desktop-application.ts'; + const sharedPath = 'src/shared/root-bridge.ts'; + const appShellOwnsSupport = ` + import { rootBridge } from '../shared/root-bridge.js'; + export const AppShell = rootBridge; + `; + const compositionUsesAppShell = ` + import { AppShell } from '../app-shell.js'; + export const DesktopApplication = AppShell; + `; + const compositionOwnsSupport = ` + import { rootBridge } from '../../shared/root-bridge.js'; + export const DesktopApplication = rootBridge; + `; + + await withDesktopFixture( + { + [mainPath]: ` + import { DesktopApplication } from './composition/desktop-application.js'; + export const main = DesktopApplication; + `, + [compositionPath]: compositionUsesAppShell, + [TRANSITIVE_APP_SHELL_PATH]: appShellOwnsSupport, + [sharedPath]: `export const rootBridge = window.maka.sessions.list();`, + }, + async (desktopRoot) => { + const seedConfig = architectureConfig({ + rootDebt: { [mainPath]: emptyDebt() }, + ownership: [ + { + capability: 'fixture-app-shell', + targetZone: 'shell', + legacyPaths: [TRANSITIVE_APP_SHELL_PATH], + }, + { + capability: 'fixture-root', + targetZone: 'bootstrap', + legacyPaths: [mainPath], + }, + ], + }); + const appShellOwnedConfig = generateArchitectureConfig(desktopRoot, seedConfig); + assert.deepEqual(Object.keys(appShellOwnedConfig.legacyAppShell.closure), [sharedPath]); + assert.deepEqual(Object.keys(appShellOwnedConfig.rootDebtClosure), []); + + await writeFile(join(desktopRoot, compositionPath), compositionOwnsSupport, 'utf8'); + await writeFile( + join(desktopRoot, TRANSITIVE_APP_SHELL_PATH), + `export const AppShell = true;`, + 'utf8', + ); + const rootOwnedConfig = generateArchitectureConfig(desktopRoot, appShellOwnedConfig); + assert.deepEqual(Object.keys(rootOwnedConfig.legacyAppShell.closure), []); + assert.deepEqual(Object.keys(rootOwnedConfig.rootDebtClosure), [sharedPath]); + assert.deepEqual( + violationsFor(desktopRoot, rootOwnedConfig, appShellOwnedConfig), + [], + ); + + await writeFile(join(desktopRoot, compositionPath), compositionUsesAppShell, 'utf8'); + await writeFile( + join(desktopRoot, TRANSITIVE_APP_SHELL_PATH), + appShellOwnsSupport, + 'utf8', + ); + const regressedConfig = generateArchitectureConfig(desktopRoot, rootOwnedConfig); + assertHasViolation( + violationsFor(desktopRoot, regressedConfig, rootOwnedConfig), + /^src\/shared\/root-bridge\.ts: new legacyAppShellClosure debt entries are forbidden$/u, + ); + }, + ); + }); + + it('rejects stateful hook growth inside a transitive legacy AppShell helper', async () => { + await withDesktopFixture( + transitiveAppShellFiles(` + import { useState } from 'react'; + export function legacySessionHelper() { + const [session] = useState('legacy-session'); + return session; + } + `), + (desktopRoot) => { + const currentConfig = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + const baseConfig = structuredClone(currentConfig); + baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].hookCalls = {}; + + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: hookCalls debt increased from 0 to 1$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: new or increased hookCalls debt useState$/u, + ); + }, + ); + }); + + it('rejects bridge and environment capability growth inside a transitive legacy AppShell helper', async () => { + await withDesktopFixture( + transitiveAppShellFiles(` + export function legacySessionHelper() { + void window.maka.sessions.list(); + return localStorage.getItem('legacy-session'); + } + `), + (desktopRoot) => { + const currentConfig = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + const baseConfig = structuredClone(currentConfig); + const baseHelperDebt = baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH]; + baseHelperDebt.bridgePaths = {}; + baseHelperDebt.environmentCapabilities = {}; + + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u, + ); + }, + ); + }); + + it('rejects dependency growth inside a transitive legacy AppShell helper', async () => { + const dependencyPath = 'src/renderer/legacy-session-store.ts'; + await withDesktopFixture( + transitiveAppShellFiles( + ` + import { legacySessionStore } from './legacy-session-store.js'; + export const legacySessionHelper = legacySessionStore; + `, + { + [dependencyPath]: `export const legacySessionStore = 'legacy-session';`, + }, + ), + (desktopRoot) => { + const currentConfig = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + const baseConfig = structuredClone(currentConfig); + baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].dependencyPaths = {}; + + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: dependencyPaths debt increased from 0 to 1$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/legacy-session-helper\.ts: new dependency debt \.\/legacy-session-store\.js$/u, + ); + }, + ); + }); + + it('rejects a stale AppShell closure ledger when another legacy file becomes reachable', async () => { + const newlyReachablePath = 'src/renderer/legacy-session-store.ts'; + await withDesktopFixture( + transitiveAppShellFiles(` + export const legacySessionHelper = 'legacy-session'; + `), + async (desktopRoot) => { + const staleConfig = generateArchitectureConfig( + desktopRoot, + transitiveAppShellSeedConfig(), + ); + const expandedHelperSource = ` + import { legacySessionStore } from './legacy-session-store.js'; + export const legacySessionHelper = legacySessionStore; + `; + await writeFile( + join(desktopRoot, TRANSITIVE_LEGACY_HELPER_PATH), + expandedHelperSource, + 'utf8', + ); + await writeFile( + join(desktopRoot, newlyReachablePath), + `export const legacySessionStore = 'legacy-session';`, + 'utf8', + ); + staleConfig.legacyRendererFiles = [ + ...staleConfig.legacyRendererFiles, + newlyReachablePath, + ].sort(); + staleConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH] = capabilityDebtForSource( + expandedHelperSource, + TRANSITIVE_LEGACY_HELPER_PATH, + ); + + assertHasViolation( + violationsFor(desktopRoot, staleConfig), + /legacy AppShell transitive renderer closure changed;.*legacy-session-store\.ts/u, + ); + }, + ); + }); + + it('rejects non-static dependency escape hatches inside the AppShell legacy closure', async () => { + await withDesktopFixture( + transitiveAppShellFiles( + ` + export const hiddenLegacyOwners = import.meta.glob('./legacy-session-store.ts'); + `, + { + 'src/renderer/legacy-session-store.ts': ` + void window.maka.sessions.list(); + `, + }, + ), + (desktopRoot) => { + assert.throws( + () => generateArchitectureConfig(desktopRoot, transitiveAppShellSeedConfig()), + /legacy-session-helper\.ts: AppShell closure contains a non-static import, require, or import\.meta glob/u, + ); + }, + ); + }); + + it('rejects stateful shell hooks and imports from legacy implementation', async () => { + await withDesktopFixture( + { + 'src/renderer/legacy-session-owner.ts': 'export const legacySessionOwner = true;', + 'src/renderer/shell/shell-frame.tsx': ` + import * as React from 'react'; + import { legacySessionOwner } from '../legacy-session-owner.js'; + export function ShellFrame() { + const [open] = React.useState(legacySessionOwner); + return
; + } + `, + 'src/renderer/shell/custom-hook.ts': ` + import { useExternalSession } from '@example/session-hooks'; + export const session = useExternalSession(); + `, + 'src/renderer/shell/namespace-hook.ts': ` + import * as hooks from '@example/session-hooks'; + export const session = hooks.useExternalSession(); + `, + 'src/renderer/shell/react-namespace-alias.ts': ` + import * as React from 'react'; + const R = React; + export const state = R.useState(false); + `, + 'src/renderer/shell/react-19-hooks.tsx': ` + import { useActionState, useInsertionEffect, useOptimistic, useTransition } from 'react'; + export function StatefulFrame() { + useActionState(async (state: number) => state, 0); + useInsertionEffect(() => undefined, []); + useOptimistic(0); + useTransition(); + return null; + } + `, + 'src/renderer/composition/class-lifecycle.tsx': ` + import * as React from 'react'; + export class DesktopComposition extends React.Component { + componentDidMount() {} + render() { return null; } + } + `, + 'src/renderer/composition/class-field-lifecycle.tsx': ` + import * as React from 'react'; + export class DesktopComposition extends React.Component { + componentDidMount = () => {}; + render() { return null; } + } + `, + 'src/renderer/composition/class-state.tsx': ` + import * as React from 'react'; + export class StatefulComposition extends React.Component { + constructor() { + super({}); + this.state = { mounted: false }; + } + render() { return null; } + } + `, + 'src/renderer/composition/feature-namespace-hook.ts': ` + import * as session from '../features/alpha/index.js'; + export const controller = session.useSessionController(); + `, + 'src/renderer/features/alpha/index.ts': ` + export function useSessionController() { return {}; } + `, + 'src/renderer/composition/plain-class.ts': ` + export class DomainLifecycleName { + componentDidMount() { return 'not React'; } + } + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation( + violations, + /^src\/renderer\/shell\/shell-frame\.tsx: shell code owns stateful React hooks$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/shell\/shell-frame\.tsx: shell imports forbidden implementation module:/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/shell\/custom-hook\.ts: shell code owns stateful React hooks$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/shell\/namespace-hook\.ts: shell code owns stateful React hooks$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/shell\/react-namespace-alias\.ts: shell code owns stateful React hooks$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/shell\/react-19-hooks\.tsx: shell code owns stateful React hooks$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/composition\/class-lifecycle\.tsx: composition code owns React class lifecycle methods$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/composition\/class-field-lifecycle\.tsx: composition code owns React class lifecycle methods$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/composition\/class-state\.tsx: composition code owns React class lifecycle methods$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/composition\/feature-namespace-hook\.ts: composition code owns stateful React hooks$/u, + ); + assert.ok( + !violations.some((violation) => violation.includes('plain-class.ts')), + `Non-React classes must not be treated as React lifecycle owners:\n${violations.join('\n')}`, + ); + }, + ); + }); + + it('rejects bridge debt that grows relative to the base ledger', async () => { + const debtPath = 'src/renderer/legacy-root.ts'; + const currentConfig = architectureConfig({ + rootDebt: { + [debtPath]: emptyDebt({ bridgePaths: { 'window.maka.sessions.list': 1 } }), + }, + }); + const baseConfig = architectureConfig({ + rootDebt: { + [debtPath]: emptyDebt(), + }, + }); + + await withDesktopFixture( + { + [debtPath]: 'void window.maka.sessions.list();', + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/legacy-root\.ts: bridgePaths debt increased from 0 to 1$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/legacy-root\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, + ); + }, + ); + }); + + it('accepts the single pinned renderer module entry', async () => { + await withDesktopFixture( + rendererEntryContractFiles(), + (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assert.deepEqual(checkRendererArchitecture({ config, desktopRoot }), []); + }, + ); + }); + + it('rejects replacing the pinned renderer module entry with an alternate source', async () => { + await withDesktopFixture( + rendererEntryContractFiles({ + 'src/renderer/index.html': ` + + + `, + 'src/renderer/settings/alternate-entry.tsx': ` + void window.maka.sessions.list(); + export const alternateEntry = true; + `, + }), + (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/renderer\/index\.html: renderer entry contract requires exactly one empty external module script for \/main\.tsx$/u, + ); + }, + ); + }); + + it('rejects adding another renderer module entry beside the pinned source', async () => { + await withDesktopFixture( + rendererEntryContractFiles({ + 'src/renderer/index.html': ` + + + + + + `, + 'src/renderer/settings/alternate-entry.tsx': `export const alternateEntry = true;`, + }), + (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/renderer\/index\.html: renderer entry contract requires exactly one empty external module script for \/main\.tsx$/u, + ); + }, + ); + }); + + it('rejects changing the packaged main-window loader to an alternate renderer document', async () => { + await withDesktopFixture( + rendererEntryContractFiles({ + 'src/main/main-renderer-loader.ts': ` + import { join } from 'node:path'; + import { pathToFileURL } from 'node:url'; + interface MainRendererWindow { + loadFile(path: string): Promise; + loadURL(url: string): Promise; + } + interface MainRendererEntry { + readonly filePath: string; + readonly url: string; + readonly useDevServer: boolean; + } + export function resolveMainRendererEntry( + mainModuleDirectory: string, + viteDevServerUrl: string | undefined, + ): MainRendererEntry { + const rendererEntryPath = join(mainModuleDirectory, '..', '..', 'dist-renderer', 'alternate.html'); + const rendererEntryUrl = viteDevServerUrl ?? pathToFileURL(rendererEntryPath).href; + return Object.freeze({ + filePath: rendererEntryPath, + url: rendererEntryUrl, + useDevServer: !!viteDevServerUrl, + }); + } + export async function loadMainRenderer( + mainWindow: MainRendererWindow, + rendererEntry: MainRendererEntry, + ): Promise { + if (rendererEntry.useDevServer) { + await mainWindow.loadURL(rendererEntry.url); + } else { + await mainWindow.loadFile(rendererEntry.filePath); + } + } + `, + }), + (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u, + ); + }, + ); + }); + + it('rejects shadowing the pinned renderer path helpers behind local bindings', async () => { + const files = rendererEntryContractFiles(); + files['src/main/main-renderer-loader.ts'] = files['src/main/main-renderer-loader.ts'] + .replace("import { join } from 'node:path';", "import { join as pathJoin } from 'node:path';") + .replace( + "import { pathToFileURL } from 'node:url';", + ` + import { pathToFileURL as nodePathToFileURL } from 'node:url'; + function join(...parts) { + return pathJoin(...parts.slice(0, -1), 'alternate.html'); + } + function pathToFileURL(path) { + return nodePathToFileURL(path); + } + `, + ); + await withDesktopFixture(files, (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u, + ); + }); + }); + + it('rejects reassigning the resolved renderer entry before loading it', async () => { + const files = rendererEntryContractFiles(); + files['src/main/main-window.ts'] = files['src/main/main-window.ts'] + .replace('const rendererEntry =', 'let rendererEntry =') + .replace( + 'await loadMainRenderer(mainWindow, rendererEntry);', + ` + rendererEntry = { + filePath: '/alternate.html', + url: 'file:///alternate.html', + useDevServer: false, + }; + await loadMainRenderer(mainWindow, rendererEntry); + `, + ); + await withDesktopFixture(files, (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u, + ); + }); + }); + + it('rejects mutating the frozen renderer entry before loading it', async () => { + const files = rendererEntryContractFiles(); + files['src/main/main-window.ts'] = files['src/main/main-window.ts'].replace( + 'await loadMainRenderer(mainWindow, rendererEntry);', + ` + rendererEntry.filePath = '/alternate.html'; + await loadMainRenderer(mainWindow, rendererEntry); + `, + ); + await withDesktopFixture(files, (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u, + ); + }); + }); + + it('rejects aliasing the main window navigation API around the dedicated loader', async () => { + await withDesktopFixture( + rendererEntryContractFiles({ + 'src/main/main-window.ts': ` + import { loadMainRenderer, resolveMainRendererEntry } from './main-renderer-loader.js'; + async function createWindow() { + const rendererEntry = resolveMainRendererEntry(import.meta.dirname, process.env.VITE_DEV_SERVER_URL); + await loadMainRenderer(mainWindow, rendererEntry); + const alternateEntryPath = join(import.meta.dirname, '..', '..', 'dist-renderer', 'alternate.html'); + const loadAlternateEntry = mainWindow.loadFile.bind(mainWindow); + await loadAlternateEntry(alternateEntryPath); + } + `, + }), + (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u, + ); + }, + ); + }); + + it('rejects a computed second navigation inside the dedicated loader branch', async () => { + const files = rendererEntryContractFiles(); + files['src/main/main-renderer-loader.ts'] = files['src/main/main-renderer-loader.ts'].replace( + 'await mainWindow.loadFile(rendererEntry.filePath);', + ` + await mainWindow.loadFile(rendererEntry.filePath); + await mainWindow['load' + 'File']( + rendererEntry.filePath.replace('index.html', 'alternate.html'), + ); + `, + ); + await withDesktopFixture(files, (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u, + ); + }); + }); + + it('rejects remapping the Vite renderer root to an alternate source tree', async () => { + await withDesktopFixture( + rendererEntryContractFiles({ + 'vite.config.ts': ` + export default defineConfig({ + root: 'src/renderer/settings/alternate-root', + build: { outDir: '../../../../dist-renderer' }, + }); + `, + }), + (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u, + ); + }, + ); + }); + + it('rejects shadowing the pinned Vite config factory behind a local binding', async () => { + const files = rendererEntryContractFiles(); + files['vite.config.ts'] = files['vite.config.ts'] + .replace("import { defineConfig } from 'vite';", "import { defineConfig as viteDefineConfig } from 'vite';") + .replace( + "const REPO_ROOT = '/fixture';", + ` + const REPO_ROOT = '/fixture'; + function defineConfig(config) { + return viteDefineConfig(config); + } + `, + ); + await withDesktopFixture(files, (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u, + ); + }); + }); + + it('rejects pointing the Vite entry guard at a different renderer root', async () => { + const files = rendererEntryContractFiles(); + files['vite.config.ts'] = files['vite.config.ts'].replace( + "rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer'))", + "rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer/settings/alternate-root'))", + ); + await withDesktopFixture(files, (desktopRoot) => { + const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); + assertHasViolation( + checkRendererArchitecture({ config, desktopRoot }), + /^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u, + ); + }); + }); + + it('attests the canonical main source in the final Vite entry graph', () => { + assert.doesNotThrow(() => + runRendererEntryBundleContract([ + '\0vite/modulepreload-polyfill.js', + '/fixture/src/renderer/index.html?html-proxy&inline-css&index=0.css', + '/fixture/src/renderer/main.tsx', + ]), + ); + }); + + it('does not apply the renderer entry contract to Storybook builds', () => { + const plugin = rendererEntryContractPlugin('/fixture/src/renderer'); + plugin.configResolved({ root: '/fixture/storybook' }); + assert.doesNotThrow(() => + plugin.generateBundle.call( + { + emitFile() { + assert.fail('a non-renderer build must not emit a renderer attestation'); + }, + }, + {}, + {}, + ), + ); + }); + + it('rejects a Vite HTML transform that swaps the final entry graph', () => { + assert.throws( + () => + runRendererEntryBundleContract([ + '\0vite/modulepreload-polyfill.js', + '/fixture/src/renderer/settings/alternate-entry.tsx', + ]), + /renderer entry contract requires src\/renderer\/index\.html to import only src\/renderer\/main\.tsx/u, + ); + }); + + it('rejects executable HTML injected around the canonical module entry', () => { + assert.doesNotThrow(() => assertRendererEntryHtml(canonicalRendererEntryHtml())); + const emittedHtml = canonicalRendererEntryHtml().replace( + '/main.tsx', + './assets/index-canonical.js', + ); + assert.doesNotThrow(() => + assertRendererEntryHtml(emittedHtml, './assets/index-canonical.js'), + ); + assert.throws( + () => assertRendererEntryHtml(emittedHtml, './assets/index-other.js'), + /renderer entry HTML contract forbids transformed executable or navigation surfaces/u, + ); + assert.throws( + () => + assertRendererEntryHtml( + canonicalRendererEntryHtml( + '', + "script-src 'self' 'unsafe-inline'", + ), + ), + /renderer entry HTML contract forbids transformed executable or navigation surfaces/u, + ); + assert.throws( + () => + assertRendererEntryHtml( + canonicalRendererEntryHtml(''), + ), + /renderer entry HTML contract forbids transformed executable or navigation surfaces/u, + ); + }); + + it('rejects a newly allowlisted root debt file relative to the base ledger', async () => { + const debtPath = 'src/renderer/new-root-debt.ts'; + const currentConfig = architectureConfig({ + rootDebt: { [debtPath]: emptyDebt() }, + }); + const baseConfig = architectureConfig(); + + await withDesktopFixture( + { + [debtPath]: 'export const newRootDebt = true;', + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/new-root-debt\.ts: new rootDebt debt entries are forbidden$/u, + ); + }, + ); + }); + + it('rejects deleting root debt while the existing root entry is still dirty', async () => { + const debtPath = 'src/renderer/main.tsx'; + const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] }); + const baseConfig = architectureConfig({ + legacyRendererFiles: [debtPath], + rootDebt: { + [debtPath]: emptyDebt({ + bridgePaths: { 'window.maka.sessions.list': 1 }, + hookCalls: { useEffect: 1 }, + }), + }, + }); + + await withDesktopFixture( + { + [debtPath]: ` + import { useEffect } from 'react'; + void window.maka.sessions.list(); + export function RendererEntry() { useEffect(() => undefined, []); return null; } + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u, + ); + }, + ); + }); + + it('rejects removing the permanent root guard while a clean thin entry still exists', async () => { + const debtPath = 'src/renderer/main.tsx'; + const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] }); + const baseConfig = architectureConfig({ + legacyRendererFiles: [debtPath], + rootDebt: { + [debtPath]: emptyDebt({ + bridgePaths: { 'window.maka.onboarding.getSnapshot': 1 }, + hookCalls: { useEffect: 1 }, + }), + }, + }); + + await withDesktopFixture( + { + [debtPath]: ` + import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js'; + mountDesktopApplication(); + `, + 'src/renderer/bootstrap/mount-desktop-application.ts': ` + export function mountDesktopApplication() {} + `, + }, + (desktopRoot) => { + assertHasViolation( + violationsFor(desktopRoot, currentConfig, baseConfig), + /^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u, + ); + }, + ); + }); + + it('rejects bridge debt returning to a retained clean root entry', async () => { + const debtPath = 'src/renderer/main.tsx'; + const cleanSource = ` + import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js'; + mountDesktopApplication(); + `; + const pollutedSource = ` + import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js'; + void window.maka.sessions.list(); + mountDesktopApplication(); + `; + const baseConfig = architectureConfig({ + legacyRendererFiles: [debtPath], + rootDebt: { [debtPath]: debtForSource(cleanSource, debtPath) }, + }); + const currentConfig = architectureConfig({ + legacyRendererFiles: [debtPath], + rootDebt: { [debtPath]: debtForSource(pollutedSource, debtPath) }, + }); + + await withDesktopFixture( + { + [debtPath]: pollutedSource, + 'src/renderer/bootstrap/mount-desktop-application.ts': ` + export function mountDesktopApplication() {} + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/main\.tsx: bridgePaths debt increased from 0 to 1$/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/main\.tsx: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, + ); + }, + ); + }); + + it('allows removing the permanent root guard after its source is deleted', async () => { + const debtPath = 'src/renderer/main.tsx'; + const currentConfig = architectureConfig(); + const baseConfig = architectureConfig({ + legacyRendererFiles: [debtPath], + rootDebt: { [debtPath]: emptyDebt() }, + }); + + await withDesktopFixture( + { + 'src/renderer/bootstrap/mount-desktop-application.ts': ` + export function mountDesktopApplication() {} + `, + }, + (desktopRoot) => { + assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []); + }, + ); + }); + + it('rejects retiring root debt by moving state and storage ownership into bootstrap', async () => { + const debtPath = 'src/renderer/main.tsx'; + const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] }); + const baseConfig = architectureConfig({ + legacyRendererFiles: [debtPath], + rootDebt: { [debtPath]: emptyDebt() }, + }); + + await withDesktopFixture( + { + [debtPath]: ` + import { PollutedBootstrap } from './bootstrap/polluted-bootstrap.js'; + void PollutedBootstrap; + `, + 'src/renderer/bootstrap/polluted-bootstrap.tsx': ` + import { useState } from 'react'; + export function PollutedBootstrap() { + const [value] = useState(() => localStorage.getItem('root-state')); + return
{value}
; + } + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /bootstrap\/polluted-bootstrap\.tsx: bootstrap code owns stateful React hooks/u, + ); + assertHasViolation( + violations, + /bootstrap\/polluted-bootstrap\.tsx: bootstrap code directly accesses browser environment capabilities/u, + ); + assertHasViolation( + violations, + /^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u, + ); + }, + ); + }); + + it('allows new legacy files inside an explicitly approved growth directory', async () => { + const growthDirectory = 'src/renderer/settings'; + const path = `${growthDirectory}/new-preference.ts`; + const currentConfig = architectureConfig({ + legacyGrowthDirectories: [growthDirectory], + legacyRendererFiles: [path], + }); + const baseConfig = architectureConfig({ + legacyGrowthDirectories: [growthDirectory], + }); + + await withDesktopFixture( + { + [path]: 'export const newPreference = true;', + }, + (desktopRoot) => { + assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []); + }, + ); + }); + + it('rejects arbitrary new unclassified renderer source names relative to the base ledger', async () => { + const path = 'src/renderer/session-coordinator.mts'; + const currentConfig = architectureConfig({ legacyRendererFiles: [path] }); + const baseConfig = architectureConfig(); + + await withDesktopFixture( + { + [path]: 'export const coordinator = true;', + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot, currentConfig, baseConfig); + assertHasViolation( + violations, + /^src\/renderer\/session-coordinator\.mts: new unclassified renderer source files are forbidden outside approved legacy directories$/u, + ); + }, + ); + }); + + it('rejects unbudgeted legacy imports from every strict ownership zone', async () => { + await withDesktopFixture( + { + 'src/renderer/legacy-helper.ts': 'export const legacy = true;', + 'src/renderer/features/alpha/controller.ts': ` + import { legacy } from '../../legacy-helper.js'; + export const featureValue = legacy; + `, + 'src/renderer/application/session/session-scope.ts': ` + import { legacy } from '../../legacy-helper.js'; + export const applicationValue = legacy; + `, + 'src/renderer/composition/desktop-application.ts': ` + import { legacy } from '../legacy-helper.js'; + export const compositionValue = legacy; + `, + 'src/renderer/platform/desktop/create-services.ts': ` + import { legacy } from '../../legacy-helper.js'; + export const platformValue = legacy; + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation(violations, /feature imports unbudgeted renderer legacy code/u); + assertHasViolation(violations, /application authority imports an outer implementation/u); + assertHasViolation(violations, /composition imports a forbidden implementation module/u); + assertHasViolation(violations, /Desktop adapter imports unbudgeted renderer legacy code/u); + }, + ); + }); + + it('detects bridge aliases, TypeScript wrappers, and aliased React hooks', async () => { + await withDesktopFixture( + { + 'src/renderer/application/bridge-aliases.ts': ` + const desktopWindow = window; + void desktopWindow.maka.sessions.list(); + void globalThis.maka.sessions.list(); + void self['maka'].sessions.list(); + void (window as Window).maka.sessions.list(); + void window!.maka.sessions.list(); + const { maka } = window; + void maka.sessions.list(); + `, + 'src/renderer/shell/aliased-hook.ts': ` + import { useState as state } from 'react'; + export function useFrameState() { return state(false); } + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation(violations, /bridge-aliases\.ts: application code accesses the Desktop global bridge/u); + assertHasViolation(violations, /aliased-hook\.ts: shell code owns stateful React hooks/u); + }, + ); + }); + + it('retains full bridge method paths after the Desktop bridge is aliased', () => { + const analysis = analyzeRendererSource( + ` + const bridge = window.maka; + void bridge.sessions.list(); + const { maka: desktop } = window; + void desktop.transcripts.open('session-a'); + `, + 'src/renderer/application/bridge-method-aliases.ts', + ); + + assert.equal(analysis.bridgePaths['window.maka.sessions.list'], 1); + assert.equal(analysis.bridgePaths['window.maka.transcripts.open'], 1); + }); + + it('retains stateful hook ownership through local aliases', () => { + const analysis = analyzeRendererSource( + ` + import { useState as state } from 'react'; + const localState = state; + localState(false); + `, + 'src/renderer/shell/hook-method-aliases.ts', + ); + + assert.equal(analysis.hookCalls.useState, 1); + }); + + it('parses TypeScript and static-template dependencies and fails closed on dynamic paths', async () => { + await withDesktopFixture( + { + 'src/renderer/features/alpha/import-equals.ts': ` + import beta = require('../beta/private.js'); + export const value = beta; + `, + 'src/renderer/features/alpha/dynamic.ts': ` + export function load(path: string) { return import(path); } + `, + 'src/renderer/features/beta/private.ts': 'export const beta = true;', + 'src/renderer/application/contracts/deep-type.ts': ` + export type Deep = import('../../features/alpha/private.js').Deep; + `, + 'src/renderer/composition/static-template.ts': ` + export const deep = import(\`../features/alpha/private.js\`); + `, + 'src/renderer/composition/import-meta-glob.ts': ` + export const privateFeatures = import.meta.glob('../features/alpha/private.ts'); + `, + }, + (desktopRoot) => { + const violations = violationsFor(desktopRoot); + assertHasViolation(violations, /import-equals\.ts: feature alpha imports feature beta/u); + assertHasViolation(violations, /dynamic\.ts: feature code contains a non-static import or require/u); + assertHasViolation(violations, /deep-type\.ts: feature imports must use index/u); + assertHasViolation(violations, /static-template\.ts: feature imports must use index/u); + assertHasViolation(violations, /import-meta-glob\.ts: composition code contains a non-static import or require/u); + }, + ); + }); + + it('forbids exposing a feature testing entry through its public index', async () => { + await withDesktopFixture( + { + 'src/renderer/features/alpha/index.ts': `export * from './testing.js';`, + 'src/renderer/features/alpha/testing.ts': 'export const fake = true;', + }, + (desktopRoot) => { + assertHasViolation( + violationsFor(desktopRoot), + /feature .*testing entry/u, + ); + }, + ); + }); + + it('allows legacy AppShell dependency replacement with feature public APIs and application contracts', async () => { + const appShellPath = 'src/renderer/app-shell.tsx'; + const appShellSource = ` + import { AlphaHost } from './features/alpha/index.js'; + import type { SessionScope } from './application/contracts/session-scope.js'; + export function AppShell(props: { readonly scope: SessionScope }) { + return ; + } + `; + const currentDebt = debtForSource(appShellSource, appShellPath); + const baseDebt = { + ...currentDebt, + dependencyPaths: { + './legacy-alpha-owner.js': 1, + './legacy-session-owner.js': 1, + }, + }; + const ownership = [ + { + capability: 'fixture-app-shell', + targetZone: 'shell', + legacyPaths: [appShellPath], + }, + ]; + const currentConfig = architectureConfig({ + legacyFiles: { [appShellPath]: currentDebt }, + legacyRendererFiles: [appShellPath], + ownership, + }); + const baseConfig = architectureConfig({ + legacyFiles: { [appShellPath]: baseDebt }, + legacyRendererFiles: [appShellPath], + ownership, + }); + + await withDesktopFixture( + { + [appShellPath]: appShellSource, + 'src/renderer/features/alpha/index.ts': ` + export function AlphaHost(_props: unknown) { return null; } + `, + 'src/renderer/application/contracts/session-scope.ts': ` + export interface SessionScope { readonly sessionId?: string } + `, + }, + (desktopRoot) => { + assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []); + }, + ); + }); + + it('rejects replacing legacy AppShell debt with a feature private import', async () => { + const appShellPath = 'src/renderer/app-shell.tsx'; + const appShellSource = ` + import { alphaController } from './features/alpha/controller.js'; + export const AppShell = alphaController; + `; + const currentDebt = debtForSource(appShellSource, appShellPath); + const baseDebt = { + ...currentDebt, + dependencyPaths: { './legacy-alpha-owner.js': 1 }, + }; + const ownership = [ + { + capability: 'fixture-app-shell', + targetZone: 'shell', + legacyPaths: [appShellPath], + }, + ]; + const currentConfig = architectureConfig({ + legacyFiles: { [appShellPath]: currentDebt }, + legacyRendererFiles: [appShellPath], + ownership, + }); + const baseConfig = architectureConfig({ + legacyFiles: { [appShellPath]: baseDebt }, + legacyRendererFiles: [appShellPath], + ownership, + }); + + await withDesktopFixture( + { + [appShellPath]: appShellSource, + 'src/renderer/features/alpha/controller.ts': 'export const alphaController = true;', + }, + (desktopRoot) => { + assertHasViolation( + violationsFor(desktopRoot, currentConfig, baseConfig), + /^src\/renderer\/app-shell\.tsx: new dependency debt \.\/features\/alpha\/controller\.js$/u, + ); + }, + ); + }); + + it('fails closed when the CLI base argument is missing or invalid', () => { + const checker = fileURLToPath(new URL('./check-renderer-architecture.mjs', import.meta.url)); + const repoRoot = fileURLToPath(new URL('../../..', import.meta.url)); + const missing = spawnSync(process.execPath, [checker, '--base'], { + cwd: repoRoot, + encoding: 'utf8', + }); + const invalid = spawnSync(process.execPath, [checker, '--base', 'definitely-not-a-renderer-architecture-ref'], { + cwd: repoRoot, + encoding: 'utf8', + }); + + assert.notEqual(missing.status, 0); + assert.match(missing.stderr, /usage: check-renderer-architecture/u); + assert.notEqual(invalid.status, 0); + assert.match(invalid.stderr, /base ref does not resolve to a commit/u); + }); +}); diff --git a/apps/desktop/scripts/check-renderer-entry-output.mjs b/apps/desktop/scripts/check-renderer-entry-output.mjs new file mode 100644 index 0000000000..0163de93eb --- /dev/null +++ b/apps/desktop/scripts/check-renderer-entry-output.mjs @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { assertRendererEntryHtml } from './vite-renderer-entry-contract.ts'; + +export function checkRendererEntryOutput(desktopRoot = resolve(import.meta.dirname, '..')) { + const outputRoot = resolve(desktopRoot, 'dist-renderer'); + const attestation = JSON.parse( + readFileSync(resolve(outputRoot, 'renderer-entry-attestation.json'), 'utf8'), + ); + if ( + attestation?.htmlFile !== 'index.html' || + attestation?.sourceFile !== 'src/renderer/main.tsx' || + typeof attestation?.entryFile !== 'string' || + !/^assets\/[a-z0-9._-]+\.js$/iu.test(attestation.entryFile) + ) { + throw new Error('renderer entry build attestation is invalid'); + } + const html = readFileSync(resolve(outputRoot, attestation.htmlFile), 'utf8'); + assertRendererEntryHtml(html, `./${attestation.entryFile}`); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + try { + checkRendererEntryOutput(); + console.log('Renderer entry output check passed.'); + } catch (error) { + console.error( + `Renderer entry output check failed: ${error instanceof Error ? error.message : String(error)}`, + ); + process.exitCode = 1; + } +} diff --git a/apps/desktop/scripts/vite-renderer-entry-contract.ts b/apps/desktop/scripts/vite-renderer-entry-contract.ts new file mode 100644 index 0000000000..ca696b0809 --- /dev/null +++ b/apps/desktop/scripts/vite-renderer-entry-contract.ts @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve, sep } from 'node:path'; +import type { Plugin } from 'vite'; + +const SOURCE_FILE = /\.(?:(?:c|m)?(?:js|ts)x?)$/u; +const ALLOWED_HTML_TAGS = new Set([ + 'body', + 'div', + 'head', + 'html', + 'link', + 'meta', + 'script', + 'style', + 'title', +]); +const CONTENT_SECURITY_POLICY = + "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'"; + +function normalizePath(path: string): string { + return path.split(sep).join('/'); +} + +function htmlAttribute(attributes: string, name: string): string | undefined { + const match = attributes.match( + new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, 'iu'), + ); + return match?.[1] ?? match?.[2]; +} + +export function assertRendererEntryHtml(html: string, expectedScriptSource?: string): void { + const tagNames = [...html.matchAll(/<\/?([a-z][a-z0-9-]*)\b/giu)].map((match) => + match[1].toLowerCase(), + ); + const hasForbiddenTag = tagNames.some((tag) => !ALLOWED_HTML_TAGS.has(tag)); + const hasEventHandler = /\son[a-z0-9-]+\s*=/iu.test(html); + + const openingScriptCount = html.match(/]*)>([\s\S]*?)<\/script\s*>/giu)]; + const [script] = scripts; + const scriptSource = htmlAttribute(script?.[1] ?? '', 'src'); + const validScriptSource = expectedScriptSource + ? scriptSource === expectedScriptSource + : scriptSource === '/main.tsx' || /^\.\/assets\/[a-z0-9._-]+\.js$/iu.test(scriptSource ?? ''); + const validScript = + openingScriptCount === 1 && + scripts.length === 1 && + htmlAttribute(script?.[1] ?? '', 'type') === 'module' && + validScriptSource && + (script?.[2] ?? '').trim().length === 0; + + const metas = [...html.matchAll(/]*)>/giu)].map((match) => match[1]); + const charsetMetas = metas.filter( + (attributes) => htmlAttribute(attributes, 'charset')?.toLowerCase() === 'utf-8', + ); + const viewportMetas = metas.filter( + (attributes) => + htmlAttribute(attributes, 'name')?.toLowerCase() === 'viewport' && + htmlAttribute(attributes, 'content') === 'width=device-width, initial-scale=1.0', + ); + const policyMetas = metas.filter( + (attributes) => + htmlAttribute(attributes, 'http-equiv')?.toLowerCase() === 'content-security-policy' && + htmlAttribute(attributes, 'content')?.trim().replace(/\s+/gu, ' ') === CONTENT_SECURITY_POLICY, + ); + const validMetas = + metas.length === 3 && + charsetMetas.length === 1 && + viewportMetas.length === 1 && + policyMetas.length === 1; + + const links = [...html.matchAll(/]*)>/giu)].map((match) => match[1]); + const validLinks = links.every((attributes) => { + const relation = htmlAttribute(attributes, 'rel')?.toLowerCase(); + const href = htmlAttribute(attributes, 'href'); + return ( + ['modulepreload', 'stylesheet'].includes(relation ?? '') && + /^\.\/assets\/[a-z0-9._-]+\.(?:css|js)$/iu.test(href ?? '') + ); + }); + + if ( + hasForbiddenTag || + hasEventHandler || + !validScript || + !validMetas || + !validLinks + ) { + throw new Error( + 'renderer entry HTML contract forbids transformed executable or navigation surfaces', + ); + } +} + +/** + * Attests the final Vite module graph rather than trusting the source HTML. + * A transformIndexHtml hook may rewrite a valid index.html before bundling; + * the canonical main.tsx must therefore remain the sole executable source + * imported directly by the emitted HTML entry. + */ +export function rendererEntryContractPlugin(rendererRoot: string): Plugin { + const canonicalIndex = normalizePath(resolve(rendererRoot, 'index.html')); + const canonicalMain = normalizePath(resolve(rendererRoot, 'main.tsx')); + let isRendererBuild = false; + + return { + name: 'maka-renderer-entry-contract', + apply: 'build', + configResolved(config) { + isRendererBuild = normalizePath(resolve(config.root)) === normalizePath(resolve(rendererRoot)); + }, + transformIndexHtml: { + order: 'post', + handler(html) { + if (!isRendererBuild) return html; + assertRendererEntryHtml(html); + return html; + }, + }, + generateBundle(_options, bundle) { + if (!isRendererBuild) return; + const entryChunks = Object.values(bundle).filter( + (item) => item.type === 'chunk' && item.isEntry, + ); + const [entry] = entryChunks; + const facade = entry?.facadeModuleId ? normalizePath(entry.facadeModuleId) : undefined; + const moduleInfo = facade ? this.getModuleInfo(entry.facadeModuleId) : undefined; + const directSourceImports = (moduleInfo?.importedIds ?? []) + .filter((id) => !id.startsWith('\0')) + .map((id) => normalizePath(id.split(/[?#]/u, 1)[0])) + .filter((id) => SOURCE_FILE.test(id)); + + if ( + entryChunks.length !== 1 || + facade !== canonicalIndex || + directSourceImports.length !== 1 || + directSourceImports[0] !== canonicalMain + ) { + this.error( + 'renderer entry contract requires src/renderer/index.html to import only src/renderer/main.tsx', + ); + } + this.emitFile({ + type: 'asset', + fileName: 'renderer-entry-attestation.json', + source: `${JSON.stringify( + { + entryFile: entry.fileName, + htmlFile: 'index.html', + sourceFile: 'src/renderer/main.tsx', + }, + null, + 2, + )}\n`, + }); + }, + }; +} diff --git a/apps/desktop/src/main/main-renderer-loader.ts b/apps/desktop/src/main/main-renderer-loader.ts new file mode 100644 index 0000000000..5b97a43564 --- /dev/null +++ b/apps/desktop/src/main/main-renderer-loader.ts @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export interface MainRendererWindow { + loadFile(path: string): Promise; + loadURL(url: string): Promise; +} + +export interface MainRendererEntry { + readonly filePath: string; + readonly url: string; + readonly useDevServer: boolean; +} + +export function resolveMainRendererEntry( + mainModuleDirectory: string, + viteDevServerUrl: string | undefined, +): MainRendererEntry { + const rendererEntryPath = join( + mainModuleDirectory, + '..', + '..', + 'dist-renderer', + 'index.html', + ); + const rendererEntryUrl = viteDevServerUrl ?? pathToFileURL(rendererEntryPath).href; + return Object.freeze({ + filePath: rendererEntryPath, + url: rendererEntryUrl, + useDevServer: !!viteDevServerUrl, + }); +} + +export async function loadMainRenderer( + mainWindow: MainRendererWindow, + rendererEntry: MainRendererEntry, +): Promise { + if (rendererEntry.useDevServer) { + await mainWindow.loadURL(rendererEntry.url); + } else { + await mainWindow.loadFile(rendererEntry.filePath); + } +} diff --git a/apps/desktop/src/main/main-window.ts b/apps/desktop/src/main/main-window.ts index 4b2feea00d..730b7fb1b5 100644 --- a/apps/desktop/src/main/main-window.ts +++ b/apps/desktop/src/main/main-window.ts @@ -20,7 +20,6 @@ import { app, BrowserWindow, dialog, nativeTheme, screen, shell } from 'electron'; import { mkdir } from 'node:fs/promises'; import { join } from 'node:path'; -import { pathToFileURL } from 'node:url'; import { appIconForTheme, type AppSettings } from '@maka/core/settings'; import { readableAppIconPath } from './app-icon-surface.js'; import { isExternalUrl } from './external-link-guard.js'; @@ -29,6 +28,7 @@ import { BrowserViewController } from './browser/controller.js'; import { BrowserViewManager } from './browser/view-manager.js'; import type { E2eFixture } from './e2e-fixture.js'; import { installMainWindowPermissionPolicy } from './main-window-permission-policy.js'; +import { loadMainRenderer, resolveMainRendererEntry } from './main-renderer-loader.js'; import { observeMainRendererProcessGone } from './main-renderer-process-gone.js'; import { isDarkAppearance, isThemePreference, toNativeThemeSource } from './theme-source.js'; import { createWindowRevealGate } from './window-reveal.js'; @@ -265,15 +265,10 @@ export function createMainWindowController(deps: MainWindowControllerDeps): Main // disagrees with the persisted in-app preference. nativeTheme.themeSource = toNativeThemeSource(themePref); - const rendererEntryPath = join( + const rendererEntry = resolveMainRendererEntry( import.meta.dirname, - '..', - '..', - 'dist-renderer', - 'index.html', + process.env.VITE_DEV_SERVER_URL, ); - const rendererEntryUrl = process.env.VITE_DEV_SERVER_URL - ?? pathToFileURL(rendererEntryPath).href; // Re-arm the reveal gate for this window's lifecycle (macOS keeps the app // alive after close-all; the next createWindow starts hidden again and a @@ -394,7 +389,7 @@ export function createMainWindowController(deps: MainWindowControllerDeps): Main }); }, }); - installMainWindowPermissionPolicy(mainWindow.webContents, rendererEntryUrl); + installMainWindowPermissionPolicy(mainWindow.webContents, rendererEntry.url); // Two-layer external-link hygiene: assistant markdown often emits `` // links to docs / GitHub / provider sign-up pages. Without these guards @@ -496,11 +491,7 @@ export function createMainWindowController(deps: MainWindowControllerDeps): Main void writeSavedBounds(workspaceRoot, final); }); - if (process.env.VITE_DEV_SERVER_URL) { - await mainWindow.loadURL(rendererEntryUrl); - } else { - await mainWindow.loadFile(rendererEntryPath); - } + await loadMainRenderer(mainWindow, rendererEntry); // PR-SHOW-AFTER-FIRST-COMMIT: reveal fallback. Start this budget only once // the renderer document has loaded. Starting it before loadURL/loadFile diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index f95082b7db..f2f9afef76 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -29,21 +29,127 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ `styles.css` is the **only** bundled style entry: it imports Astryx, fonts, `maka-tokens.css`, `reference-shell.css`, and every `styles/*.css`. It contains only top-level orchestration; real selector rules go in `styles/*.css`. One contract-pinned exception: `index.html` carries an inline `.maka-preload` skeleton with hardcoded colors (no CSS variables — `maka-tokens.css` hasn't loaded yet) so there's no blank window during the CSS + JS load gap; `createRoot` replaces it on mount. -## AppShell + the action modules - -`app-shell.tsx` is the shell component: it owns session state and wires the -`@maka/ui` conversation surfaces. The right/bottom Workbar is the vertical -feature under `features/workbar`; its own README defines the state and lifecycle -boundary for Review, Terminal, Tasks, Browser, Files, Inspector and Side Chat. -The shell is supported by a set of `app-shell-*` modules, each a narrow slice of -shell logic split by one concern (e.g. `app-shell-session-events.ts`, -`app-shell-chat-actions.ts`, `app-shell-effects.ts`, and -`app-shell-overlays.tsx`). Keep a slice to one concern; if it grows, split along -the same seam. - -Autonomous Goal state, controls, dialog lifecycle, and Desktop I/O live under -`features/goals`; AppShell supplies the active Session boundary and consumes -the feature's host, commands, and selectors. +## Renderer ownership boundary + +`app-shell.tsx`, `app-shell-*`, and `use-app-shell-*` are a **frozen legacy +boundary**, not a pattern for new renderer code. They temporarily retain +ownership that predates the composition-root migration. Do not add another +file to this family or move new state, effects, subscriptions, bridge calls, or +feature view-model construction into it. Its recorded debt may only decrease as +each capability moves to its target owner. + +The target dependency direction is: + +```text +bootstrap -> composition -> shell + application contracts + feature public entries +platform/desktop -> injected feature/application ports +features -> own internals + shared contracts/core/UI +application -> shared contracts + injected ports +``` + +- `shell/` owns only the fixed frame, regions, and mount/visibility policy. It + has no Desktop bridge access, feature implementation imports, or business + state/effects. Direct storage, timers, fetches, and DOM/global subscriptions + are environment ownership too and are forbidden here. +- `bootstrap/` owns one-shot startup and React mount sequencing. Apart from + locating the DOM mount point, it owns no React state/class lifecycle, + storage, timers, subscriptions, or network access. +- `composition/` assembles providers, adapters, and public feature hosts. It is + wiring, not another lifecycle or browser-environment owner. +- `application/` owns explicitly shared renderer authorities. It must not + depend on feature, shell, Desktop adapter, preload, or main-process + implementations. +- `features//` owns one vertical capability. A feature cannot access + `window.maka`, import another feature's internals, or depend on AppShell, + preload, main, or `platform/desktop`. Consumers use its public `index` entry; + `testing` is test/Storybook-only. +- `platform/desktop/` is the outer adapter zone for the preload bridge. It + implements narrow inward-facing ports rather than exporting the whole bridge; + composition and adapters consume application public entries, not deep + implementation modules. Adapters may own bridge and browser-environment + access, but never React UI/hooks/class lifecycle, Electron/Node imports, or + non-static dependency loading. + +The right/bottom Workbar and the other extracted features define their detailed +state and lifecycle boundaries in their own READMEs. Cross-feature behavior +uses explicit contracts and intents, not private imports or a service locator. + +### Architecture guardrail and migration ledger + +`apps/desktop/scripts/check-renderer-architecture.mjs` parses renderer imports, +bridge aliases, browser-environment access, and stateful-hook ownership and +enforces the zones above, including imported/local hook aliases, React 19 state +hooks, React component class state/lifecycle, and non-static global access. It +also rejects Electron/Node imports from inward zones, deep/cross-feature +imports, `import.meta.glob` escape hatches, production use of feature testing +entries, and application contracts that re-export application implementation. +`apps/desktop/renderer-architecture.json` records the exact legacy/root debt and +maps every AppShell/root path to its intended owner. It freezes every +unclassified legacy renderer source and every non-owner Desktop source +transitively reachable from AppShell. The graph crosses explicit feature, +application, and platform owners while recording legacy renderer, shared, +preload, and other non-owner intermediaries in the debt closure. The ledger +traverses declarations for dependency resolution without treating them as +runtime debt; explicit owner nodes remain governed by their zone rules. It also +freezes the separate root-entry closure and each transitional +feature/platform import of legacy code. The AppShell-family and root-entry +files are full ratchets: dependency paths, imported bindings, +bridge/hooks/browser capabilities, action factories, and non-trivia tokens may +not grow. Their transitive support closures ratchet only architectural +capabilities and dependencies, so ordinary implementation can evolve without +token-count ledger noise. A support entry may move one way from the AppShell +closure to the root closure without resetting its budget; the reverse move is +rejected. Legacy import allowlists may only shrink relative to the base branch. Same-count +dependency replacement is allowed only when it moves ownership behind a shell, +feature public, or application public/contract boundary. + +New flat renderer modules are forbidden; new code belongs in an explicit zone. +The existing `settings`, `locales`, `astryx-theme`, and +`computer-use-overlay` directories may still add scoped legacy files, but those +files cannot become newly reachable from AppShell/root or a feature/Desktop +adapter without passing the corresponding ratchet. + +Run the current-tree check locally with: + +```sh +npm run check:renderer-architecture +``` + +Before opening a PR, also verify that debt did not grow relative to main: + +```sh +npm run check:renderer-architecture -- --base upstream/main +``` + +After a legitimate debt-reducing move, regenerate the mechanical counts and +then run the base comparison; regeneration cannot hide growth from CI: + +```sh +npm run check:renderer-architecture -- --write --base upstream/main +``` + +`main.tsx` and `app.tsx` are permanent guarded root entries while those files +exist. Their recorded debt can fall to zero as they become thin mounts, but the +ledger entries remain so that a later PR cannot add bridge, hook, +browser-environment, dynamic-import, or legacy dependency ownership back into +them. A root entry guard may be removed only when the guarded source file is +deleted. + +The production entry chain is part of the same root contract. The main process +delegates its one renderer navigation to `main-renderer-loader.ts`, which loads +only `dist-renderer/index.html`; Vite must build that document from +`src/renderer`; and the source HTML must keep a single external module entry at +`/main.tsx`. A build-time Vite attestation also inspects the final module graph, +and a post-build verifier binds the emitted HTML's sole script to that exact +entry chunk while preserving the fixed CSP and rejecting extra executable or +navigation surfaces. An HTML-transform plugin therefore cannot silently +replace or augment the canonical entry after the source check. `main.tsx` +remains under the permanent root guard. Moving any part of this chain requires +an explicit architecture change instead of routing around the ledger. + +This initial guardrail is source-policy and migration metadata only. It does not +change runtime behavior, provider order, IPC/storage contracts, bootstrap, +Composer mount semantics, Session switching, or Workbar resource lifecycles. `settings/` holds the settings pages and the `SettingsModal` shell — one page per `SettingsSection` (defined in `@maka/core`); the models/providers page is `ProvidersPanel`. Plus the `provider-*` files and the shared `settings-rows` / `settings-skeleton` / `settings-surface` helpers. diff --git a/apps/desktop/src/renderer/features/session-navigation/testing.ts b/apps/desktop/src/renderer/features/session-navigation/testing.ts index f39ec85855..208d3deb05 100644 --- a/apps/desktop/src/renderer/features/session-navigation/testing.ts +++ b/apps/desktop/src/renderer/features/session-navigation/testing.ts @@ -39,6 +39,8 @@ export { deriveSessionRail } from './model/session-rail.js'; export { deriveSessionRevisionNavigation } from './model/session-revisions.js'; export { readSessionListViewMode, + SESSION_LIST_EXPANDED_MAX_WIDTH, + SESSION_LIST_EXPANDED_MIN_WIDTH, writeSessionListViewMode, } from './model/session-list-layout.js'; diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 72878356f7..23b8539fba 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -23,6 +23,7 @@ import { dirname, resolve } from 'node:path'; import react from '@vitejs/plugin-react'; import { dependencyPatchesCachePlugin } from './vite-dependency-patches.js'; import { bundledNpmPackagesPlugin } from './vite-bundled-packages.js'; +import { rendererEntryContractPlugin } from './scripts/vite-renderer-entry-contract.js'; /** * PR-ICONS-FULL-REPLACE-0 (WAWQAQ msg `60064e2d` 2026-06-24): point the @@ -42,7 +43,12 @@ export default defineConfig({ // Vite hashes plugin names into its dependency-cache key. patch-package does // not change package-lock.json, so carry the patch contents in that key while // keeping every Astryx entry in one optimized module graph. - plugins: [react(), dependencyPatchesCachePlugin(REPO_ROOT), bundledNpmPackagesPlugin()], + plugins: [ + react(), + dependencyPatchesCachePlugin(REPO_ROOT), + bundledNpmPackagesPlugin(), + rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer')), + ], resolve: { dedupe: ['react', 'react-dom'], alias: [ diff --git a/package-lock.json b/package-lock.json index 7348cd358a..05ad3b5ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@ai-sdk/provider-utils": "5.0.28", "@astryxdesign/cli": "0.4.5", "@astryxdesign/core": "0.5.0", + "@babel/parser": "7.29.7", "@biomejs/biome": "2.5.9", "@electron/asar": "4.3.0", "@types/node": "^26.2.0", diff --git a/package.json b/package.json index 3d960adda0..81825556d7 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "lint": "biome lint .", "format": "biome format --write .", "format:check": "biome format .", + "check:renderer-architecture": "npm --workspace @maka/desktop run check:architecture --", "typecheck": "npm run typecheck --workspaces --if-present", "test": "npm run build:test && node scripts/run-workspace-tests-parallel.mjs --concurrency=3", "test:dist": "node scripts/run-workspace-tests-parallel.mjs --concurrency=3", @@ -94,6 +95,7 @@ "smoke:windows:dist": "node scripts/windows-smoke.mjs" }, "devDependencies": { + "@babel/parser": "7.29.7", "@ai-sdk/provider-utils": "5.0.28", "@electron/asar": "4.3.0", "@astryxdesign/cli": "0.4.5",