From 96fab90c37a6e04539f35e0f13dd3609cd43f503 Mon Sep 17 00:00:00 2001 From: matt Date: Wed, 11 Feb 2026 15:52:00 +0900 Subject: [PATCH] Initialize project structure with essential configuration files including .editorconfig, .gitattributes, .gitignore, and TypeScript settings. Add build and linting configurations, along with README, LICENSE, and contribution guidelines. Set up Tailwind CSS and ESLint for styling and code quality. Include initial package.json and pnpm workspace configuration for dependency management. --- .claude/agents/claude-md-auditor.md | 198 + .claude/agents/quality-fixer.md | 90 + .../commands/ccc/chatgroup-architecture.md | 389 + .claude/commands/ccc/design-system.md | 356 + .../commands/ccc/explain-visible-context.md | 104 + .claude/commands/ccc/markdown-search-logic.md | 179 + .claude/commands/ccc/navigation-scroll.md | 232 + .claude/rules/react.md | 50 + .claude/rules/tailwind.md | 61 + .claude/rules/testing.md | 76 + .claude/settings.json | 38 + .editorconfig | 12 + .gitattributes | 1 + .gitignore | 47 + .nvmrc | 1 + .prettierignore | 25 + .prettierrc.json | 28 + CHANGELOG.md | 27 + CLAUDE.md | 158 + CODE_OF_CONDUCT.md | 19 + CONTRIBUTING.md | 41 + LICENSE | 21 + README.md | 70 + SECURITY.md | 20 + electron-builder.yml | 41 + electron.vite.config.ts | 62 + eslint.config.js | 591 ++ knip.json | 17 + package.json | 98 + pnpm-lock.yaml | 7228 +++++++++++++++++ pnpm-workspace.yaml | 3 + postcss.config.cjs | 6 + resources/icon.png | Bin 0 -> 482226 bytes resources/icons/mac/icon.icns | Bin 0 -> 1262237 bytes resources/icons/png/1024x1024.png | Bin 0 -> 678224 bytes resources/icons/png/128x128.png | Bin 0 -> 15770 bytes resources/icons/png/16x16.png | Bin 0 -> 710 bytes resources/icons/png/24x24.png | Bin 0 -> 1316 bytes resources/icons/png/256x256.png | Bin 0 -> 56635 bytes resources/icons/png/32x32.png | Bin 0 -> 1838 bytes resources/icons/png/48x48.png | Bin 0 -> 3364 bytes resources/icons/png/512x512.png | Bin 0 -> 221734 bytes resources/icons/png/64x64.png | Bin 0 -> 5197 bytes resources/icons/win/icon.ico | Bin 0 -> 361102 bytes src/CLAUDE.md | 31 + src/main/CLAUDE.md | 32 + src/main/constants/messageTags.ts | 46 + src/main/constants/worktreePatterns.ts | 44 + src/main/index.ts | 295 + src/main/ipc/CLAUDE.md | 57 + src/main/ipc/config.ts | 628 ++ src/main/ipc/configValidation.ts | 292 + src/main/ipc/guards.ts | 148 + src/main/ipc/handlers.ts | 93 + src/main/ipc/notifications.ts | 186 + src/main/ipc/projects.ts | 109 + src/main/ipc/search.ts | 82 + src/main/ipc/sessions.ts | 305 + src/main/ipc/subagents.ts | 124 + src/main/ipc/utility.ts | 230 + src/main/ipc/validation.ts | 145 + src/main/services/CLAUDE.md | 59 + src/main/services/analysis/ChunkBuilder.ts | 443 + src/main/services/analysis/ChunkFactory.ts | 203 + .../analysis/ConversationGroupBuilder.ts | 213 + src/main/services/analysis/ProcessLinker.ts | 61 + .../analysis/SemanticStepExtractor.ts | 210 + .../services/analysis/SemanticStepGrouper.ts | 114 + .../analysis/SubagentDetailBuilder.ts | 135 + .../services/analysis/ToolExecutionBuilder.ts | 82 + .../services/analysis/ToolResultExtractor.ts | 224 + .../services/analysis/ToolSummaryFormatter.ts | 113 + src/main/services/analysis/index.ts | 26 + .../services/discovery/ProjectPathResolver.ts | 118 + src/main/services/discovery/ProjectScanner.ts | 819 ++ .../discovery/SessionContentFilter.ts | 249 + .../services/discovery/SessionSearcher.ts | 312 + .../services/discovery/SubagentLocator.ts | 203 + .../services/discovery/SubagentResolver.ts | 547 ++ .../services/discovery/SubprojectRegistry.ts | 98 + .../services/discovery/WorktreeGrouper.ts | 200 + src/main/services/discovery/index.ts | 20 + src/main/services/error/ErrorDetector.ts | 222 + .../services/error/ErrorMessageBuilder.ts | 185 + .../services/error/ErrorTriggerChecker.ts | 461 ++ src/main/services/error/ErrorTriggerTester.ts | 329 + src/main/services/error/TriggerMatcher.ts | 82 + src/main/services/error/index.ts | 16 + src/main/services/index.ts | 16 + .../services/infrastructure/ConfigManager.ts | 681 ++ src/main/services/infrastructure/DataCache.ts | 356 + .../services/infrastructure/FileWatcher.ts | 697 ++ .../infrastructure/NotificationManager.ts | 657 ++ .../services/infrastructure/TriggerManager.ts | 318 + src/main/services/infrastructure/index.ts | 16 + src/main/services/parsing/ClaudeMdReader.ts | 293 + .../services/parsing/GitIdentityResolver.ts | 674 ++ .../services/parsing/MessageClassifier.ts | 65 + src/main/services/parsing/SessionParser.ts | 378 + src/main/services/parsing/index.ts | 14 + src/main/types/chunks.ts | 503 ++ src/main/types/domain.ts | 283 + src/main/types/index.ts | 24 + src/main/types/jsonl.ts | 326 + src/main/types/messages.ts | 376 + src/main/utils/contextAccumulator.ts | 34 + src/main/utils/jsonl.ts | 473 ++ src/main/utils/metadataExtraction.ts | 44 + src/main/utils/pathDecoder.ts | 228 + src/main/utils/pathValidation.ts | 265 + src/main/utils/regexValidation.ts | 182 + src/main/utils/sessionStateDetection.ts | 151 + src/main/utils/timelineGapFilling.ts | 53 + src/main/utils/tokenizer.ts | 46 + src/main/utils/toolExtraction.ts | 63 + src/preload/CLAUDE.md | 61 + src/preload/constants/ipcChannels.ts | 60 + src/preload/index.ts | 291 + src/renderer/App.tsx | 32 + src/renderer/CLAUDE.md | 82 + src/renderer/components/CLAUDE.md | 81 + src/renderer/components/chat/AIChatGroup.tsx | 529 ++ src/renderer/components/chat/ChatHistory.tsx | 745 ++ .../components/chat/ChatHistoryEmptyState.tsx | 14 + .../components/chat/ChatHistoryItem.tsx | 133 + .../chat/ChatHistoryLoadingState.tsx | 24 + .../components/chat/CompactBoundary.tsx | 171 + src/renderer/components/chat/ContextBadge.tsx | 571 ++ .../components/chat/DisplayItemList.tsx | 232 + .../components/chat/LastOutputDisplay.tsx | 244 + .../DirectoryTree/DirectoryTreeNode.tsx | 125 + .../DirectoryTree/buildDirectoryTree.ts | 47 + .../DirectoryTree/types.ts | 12 + .../components/ClaudeMdFilesSection.tsx | 90 + .../components/ClaudeMdSection.tsx | 86 + .../components/CollapsibleSection.tsx | 77 + .../components/MentionedFilesSection.tsx | 50 + .../components/SessionContextHeader.tsx | 155 + .../components/SessionContextHelpTooltip.tsx | 184 + .../components/TaskCoordinationSection.tsx | 47 + .../components/ThinkingTextSection.tsx | 47 + .../components/ToolOutputsSection.tsx | 47 + .../components/UserMessagesSection.tsx | 47 + .../chat/SessionContextPanel/index.tsx | 250 + .../items/ClaudeMdItem.tsx | 76 + .../items/MentionedFileItem.tsx | 91 + .../items/TaskCoordinationItem.tsx | 116 + .../items/ThinkingTextItem.tsx | 96 + .../items/ToolBreakdownItem.tsx | 38 + .../items/ToolOutputItem.tsx | 113 + .../items/UserMessageItem.tsx | 69 + .../chat/SessionContextPanel/types.ts | 79 + .../SessionContextPanel/utils/formatting.ts | 6 + .../SessionContextPanel/utils/pathParsing.ts | 23 + .../components/chat/SystemChatGroup.tsx | 60 + .../components/chat/UserChatGroup.tsx | 471 ++ .../components/chat/items/BaseItem.tsx | 192 + .../components/chat/items/ExecutionTrace.tsx | 151 + .../components/chat/items/LinkedToolItem.tsx | 207 + .../components/chat/items/MetricsPill.tsx | 179 + .../components/chat/items/SlashItem.tsx | 71 + .../components/chat/items/SubagentItem.tsx | 538 ++ .../chat/items/TeammateMessageItem.tsx | 263 + .../components/chat/items/TextItem.tsx | 56 + .../components/chat/items/ThinkingItem.tsx | 56 + .../components/chat/items/baseItemHelpers.ts | 42 + .../items/linkedTool/DefaultToolViewer.tsx | 67 + .../chat/items/linkedTool/EditToolViewer.tsx | 73 + .../chat/items/linkedTool/ReadToolViewer.tsx | 65 + .../chat/items/linkedTool/SkillToolViewer.tsx | 67 + .../items/linkedTool/ToolErrorDisplay.tsx | 43 + .../chat/items/linkedTool/WriteToolViewer.tsx | 32 + .../components/chat/items/linkedTool/index.ts | 12 + .../chat/items/linkedTool/renderHelpers.tsx | 116 + .../components/chat/markdownComponents.tsx | 228 + .../components/chat/searchHighlightUtils.ts | 147 + .../chat/viewers/CodeBlockViewer.tsx | 244 + .../components/chat/viewers/DiffViewer.tsx | 372 + .../chat/viewers/MarkdownViewer.tsx | 339 + src/renderer/components/chat/viewers/index.ts | 3 + .../chat/viewers/syntaxHighlighter.ts | 373 + src/renderer/components/common/CopyButton.tsx | 78 + .../components/common/CopyablePath.tsx | 68 + .../components/common/ErrorBoundary.tsx | 109 + .../components/common/OngoingIndicator.tsx | 67 + .../components/common/RepositoryDropdown.tsx | 230 + .../components/common/TokenUsageDisplay.tsx | 572 ++ .../components/common/WorktreeBadge.tsx | 118 + .../components/dashboard/DashboardView.tsx | 404 + .../components/layout/MiddlePanel.tsx | 18 + .../components/layout/PaneContainer.tsx | 152 + .../components/layout/PaneContent.tsx | 55 + .../components/layout/PaneResizeHandle.tsx | 84 + .../components/layout/PaneSplitDropZone.tsx | 54 + src/renderer/components/layout/PaneView.tsx | 88 + .../components/layout/SessionTabContent.tsx | 102 + src/renderer/components/layout/Sidebar.tsx | 120 + .../components/layout/SidebarHeader.tsx | 541 ++ .../components/layout/SortableTab.tsx | 160 + src/renderer/components/layout/TabBar.tsx | 430 + .../components/layout/TabContextMenu.tsx | 149 + .../components/layout/TabbedLayout.tsx | 41 + .../notifications/NotificationRow.tsx | 213 + .../notifications/NotificationsView.tsx | 351 + .../components/search/CommandPalette.tsx | 497 ++ src/renderer/components/search/SearchBar.tsx | 122 + .../components/AddTriggerForm.tsx | 233 + .../components/ColorPaletteSelector.tsx | 144 + .../components/DynamicConfigSection.tsx | 191 + .../components/GeneralInfoSection.tsx | 68 + .../components/IgnorePatternsSection.tsx | 73 + .../components/ModeSelector.tsx | 45 + .../components/RepositoryScopeSection.tsx | 67 + .../components/SectionHeader.tsx | 15 + .../components/TriggerCard.tsx | 150 + .../components/TriggerCardHeader.tsx | 125 + .../components/TriggerConfiguration.tsx | 342 + .../components/TriggerPreview.tsx | 103 + .../hooks/useAddTriggerFormHandlers.ts | 218 + .../hooks/useAddTriggerFormState.ts | 135 + .../hooks/useRepositoryLookup.ts | 47 + .../hooks/useTriggerCardState.ts | 281 + .../hooks/useTriggerForm.ts | 184 + .../NotificationTriggerSettings/index.tsx | 88 + .../NotificationTriggerSettings/types.ts | 39 + .../utils/constants.ts | 50 + .../utils/trigger.ts | 112 + .../components/settings/SettingsTabs.tsx | 64 + .../components/settings/SettingsView.tsx | 146 + .../settings/components/SettingRow.tsx | 35 + .../components/SettingsSectionHeader.tsx | 19 + .../settings/components/SettingsSelect.tsx | 97 + .../settings/components/SettingsToggle.tsx | 45 + .../components/settings/components/index.ts | 8 + .../components/settings/hooks/index.ts | 6 + .../settings/hooks/useSettingsConfig.ts | 228 + .../settings/hooks/useSettingsHandlers.ts | 391 + .../settings/sections/AdvancedSection.tsx | 104 + .../settings/sections/GeneralSection.tsx | 60 + .../sections/NotificationsSection.tsx | 166 + .../components/settings/sections/index.ts | 7 + .../sidebar/DateGroupedSessions.tsx | 355 + .../components/sidebar/SessionContextMenu.tsx | 130 + .../components/sidebar/SessionItem.tsx | 200 + src/renderer/constants/cssVariables.ts | 223 + src/renderer/constants/layout.ts | 9 + src/renderer/constants/teamColors.ts | 51 + src/renderer/contexts/TabUIContext.tsx | 51 + src/renderer/contexts/useTabUIContext.ts | 18 + src/renderer/favicon.png | Bin 0 -> 15770 bytes src/renderer/hooks/navigation/utils.ts | 263 + src/renderer/hooks/useAutoScrollBottom.ts | 263 + src/renderer/hooks/useKeyboardShortcuts.ts | 284 + .../hooks/useTabNavigationController.ts | 524 ++ src/renderer/hooks/useTabUI.ts | 252 + src/renderer/hooks/useTheme.ts | 102 + src/renderer/hooks/useVisibleAIGroup.ts | 122 + src/renderer/hooks/useZoomFactor.ts | 34 + src/renderer/index.css | 505 ++ src/renderer/index.html | 63 + src/renderer/main.tsx | 12 + src/renderer/store/CLAUDE.md | 52 + src/renderer/store/index.ts | 241 + src/renderer/store/slices/configSlice.ts | 89 + .../store/slices/conversationSlice.ts | 476 ++ .../store/slices/notificationSlice.ts | 223 + src/renderer/store/slices/paneSlice.ts | 356 + src/renderer/store/slices/projectSlice.ts | 66 + src/renderer/store/slices/repositorySlice.ts | 133 + .../store/slices/sessionDetailSlice.ts | 658 ++ src/renderer/store/slices/sessionSlice.ts | 274 + src/renderer/store/slices/subagentSlice.ts | 143 + src/renderer/store/slices/tabSlice.ts | 740 ++ src/renderer/store/slices/tabUISlice.ts | 319 + src/renderer/store/slices/uiSlice.ts | 45 + src/renderer/store/types.ts | 87 + src/renderer/store/utils/paneHelpers.ts | 134 + src/renderer/store/utils/pathResolution.ts | 121 + src/renderer/store/utils/stateResetHelpers.ts | 43 + src/renderer/types/api.ts | 8 + src/renderer/types/claudeMd.ts | 74 + src/renderer/types/contextInjection.ts | 307 + src/renderer/types/data.ts | 136 + src/renderer/types/groups.ts | 398 + src/renderer/types/notifications.ts | 18 + src/renderer/types/panes.ts | 35 + src/renderer/types/tabs.ts | 236 + src/renderer/utils/aiGroupEnhancer.ts | 78 + src/renderer/utils/aiGroupHelpers.ts | 100 + src/renderer/utils/claudeMdTracker.ts | 656 ++ src/renderer/utils/contextTracker.ts | 1099 +++ src/renderer/utils/dateGrouping.ts | 91 + src/renderer/utils/displayItemBuilder.ts | 499 ++ src/renderer/utils/displaySummary.ts | 67 + src/renderer/utils/formatters.ts | 22 + src/renderer/utils/groupTransformer.ts | 700 ++ src/renderer/utils/lastOutputDetector.ts | 150 + src/renderer/utils/modelExtractor.ts | 90 + src/renderer/utils/pathDisplay.ts | 94 + src/renderer/utils/pathUtils.ts | 47 + src/renderer/utils/slashCommandExtractor.ts | 154 + src/renderer/utils/stringUtils.ts | 29 + src/renderer/utils/toolLinkingEngine.ts | 117 + src/renderer/utils/toolRendering/index.ts | 14 + .../utils/toolRendering/toolContentChecks.ts | 58 + .../utils/toolRendering/toolSummaryHelpers.ts | 271 + .../utils/toolRendering/toolTokens.ts | 57 + src/renderer/vite-env.d.ts | 19 + src/shared/CLAUDE.md | 35 + src/shared/constants/cache.ts | 12 + src/shared/constants/index.ts | 8 + src/shared/constants/trafficLights.ts | 60 + src/shared/constants/triggerColors.ts | 126 + src/shared/constants/window.ts | 12 + src/shared/types/api.ts | 210 + src/shared/types/index.ts | 22 + src/shared/types/notifications.ts | 278 + src/shared/types/visualization.ts | 60 + src/shared/utils/contentSanitizer.ts | 151 + src/shared/utils/errorHandling.ts | 26 + src/shared/utils/logger.ts | 69 + src/shared/utils/markdownTextSearch.ts | 204 + src/shared/utils/modelParser.ts | 155 + src/shared/utils/teammateMessageParser.ts | 52 + src/shared/utils/tokenFormatting.ts | 91 + tailwind.config.js | 53 + test/main/ipc/configValidation.test.ts | 87 + test/main/ipc/guards.test.ts | 48 + .../services/analysis/ChunkBuilder.test.ts | 449 + .../discovery/ProjectPathResolver.test.ts | 88 + .../discovery/SessionSearcher.test.ts | 122 + .../infrastructure/FileWatcher.test.ts | 571 ++ .../parsing/MessageClassifier.test.ts | 303 + .../services/parsing/SessionParser.test.ts | 415 + test/main/utils/jsonl.test.ts | 175 + test/main/utils/pathDecoder.test.ts | 201 + test/main/utils/pathValidation.test.ts | 292 + test/main/utils/regexValidation.test.ts | 146 + test/main/utils/tokenizer.test.ts | 63 + test/mocks/electronAPI.ts | 174 + test/renderer/hooks/navigationUtils.test.ts | 68 + .../hooks/useAutoScrollBottom.test.ts | 13 + .../hooks/useSearchContextNavigation.test.ts | 48 + test/renderer/hooks/useVisibleAIGroup.test.ts | 54 + test/renderer/store/notificationSlice.test.ts | 450 + test/renderer/store/paneSlice.test.ts | 487 ++ test/renderer/store/pathResolution.test.ts | 51 + test/renderer/store/sessionSlice.test.ts | 314 + test/renderer/store/storeTestUtils.ts | 43 + test/renderer/store/tabSlice.test.ts | 592 ++ test/renderer/store/tabUISlice.test.ts | 375 + test/renderer/utils/claudeMdTracker.test.ts | 95 + test/renderer/utils/dateGrouping.test.ts | 164 + test/renderer/utils/formatters.test.ts | 85 + test/renderer/utils/pathUtils.test.ts | 137 + test/setup.ts | 50 + .../markdownSearchRendererAlignment.test.ts | 56 + test/shared/utils/markdownTextSearch.test.ts | 304 + test/shared/utils/modelParser.test.ts | 141 + test/shared/utils/tokenFormatting.test.ts | 123 + tsconfig.json | 25 + tsconfig.node.json | 22 + tsconfig.test.json | 7 + vitest.config.ts | 24 + vitest.critical.config.ts | 34 + 365 files changed, 66518 insertions(+) create mode 100644 .claude/agents/claude-md-auditor.md create mode 100644 .claude/agents/quality-fixer.md create mode 100644 .claude/commands/ccc/chatgroup-architecture.md create mode 100644 .claude/commands/ccc/design-system.md create mode 100644 .claude/commands/ccc/explain-visible-context.md create mode 100644 .claude/commands/ccc/markdown-search-logic.md create mode 100644 .claude/commands/ccc/navigation-scroll.md create mode 100644 .claude/rules/react.md create mode 100644 .claude/rules/tailwind.md create mode 100644 .claude/rules/testing.md create mode 100644 .claude/settings.json create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 electron-builder.yml create mode 100644 electron.vite.config.ts create mode 100644 eslint.config.js create mode 100644 knip.json create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 postcss.config.cjs create mode 100644 resources/icon.png create mode 100644 resources/icons/mac/icon.icns create mode 100644 resources/icons/png/1024x1024.png create mode 100644 resources/icons/png/128x128.png create mode 100644 resources/icons/png/16x16.png create mode 100644 resources/icons/png/24x24.png create mode 100644 resources/icons/png/256x256.png create mode 100644 resources/icons/png/32x32.png create mode 100644 resources/icons/png/48x48.png create mode 100644 resources/icons/png/512x512.png create mode 100644 resources/icons/png/64x64.png create mode 100644 resources/icons/win/icon.ico create mode 100644 src/CLAUDE.md create mode 100644 src/main/CLAUDE.md create mode 100644 src/main/constants/messageTags.ts create mode 100644 src/main/constants/worktreePatterns.ts create mode 100644 src/main/index.ts create mode 100644 src/main/ipc/CLAUDE.md create mode 100644 src/main/ipc/config.ts create mode 100644 src/main/ipc/configValidation.ts create mode 100644 src/main/ipc/guards.ts create mode 100644 src/main/ipc/handlers.ts create mode 100644 src/main/ipc/notifications.ts create mode 100644 src/main/ipc/projects.ts create mode 100644 src/main/ipc/search.ts create mode 100644 src/main/ipc/sessions.ts create mode 100644 src/main/ipc/subagents.ts create mode 100644 src/main/ipc/utility.ts create mode 100644 src/main/ipc/validation.ts create mode 100644 src/main/services/CLAUDE.md create mode 100644 src/main/services/analysis/ChunkBuilder.ts create mode 100644 src/main/services/analysis/ChunkFactory.ts create mode 100644 src/main/services/analysis/ConversationGroupBuilder.ts create mode 100644 src/main/services/analysis/ProcessLinker.ts create mode 100644 src/main/services/analysis/SemanticStepExtractor.ts create mode 100644 src/main/services/analysis/SemanticStepGrouper.ts create mode 100644 src/main/services/analysis/SubagentDetailBuilder.ts create mode 100644 src/main/services/analysis/ToolExecutionBuilder.ts create mode 100644 src/main/services/analysis/ToolResultExtractor.ts create mode 100644 src/main/services/analysis/ToolSummaryFormatter.ts create mode 100644 src/main/services/analysis/index.ts create mode 100644 src/main/services/discovery/ProjectPathResolver.ts create mode 100644 src/main/services/discovery/ProjectScanner.ts create mode 100644 src/main/services/discovery/SessionContentFilter.ts create mode 100644 src/main/services/discovery/SessionSearcher.ts create mode 100644 src/main/services/discovery/SubagentLocator.ts create mode 100644 src/main/services/discovery/SubagentResolver.ts create mode 100644 src/main/services/discovery/SubprojectRegistry.ts create mode 100644 src/main/services/discovery/WorktreeGrouper.ts create mode 100644 src/main/services/discovery/index.ts create mode 100644 src/main/services/error/ErrorDetector.ts create mode 100644 src/main/services/error/ErrorMessageBuilder.ts create mode 100644 src/main/services/error/ErrorTriggerChecker.ts create mode 100644 src/main/services/error/ErrorTriggerTester.ts create mode 100644 src/main/services/error/TriggerMatcher.ts create mode 100644 src/main/services/error/index.ts create mode 100644 src/main/services/index.ts create mode 100644 src/main/services/infrastructure/ConfigManager.ts create mode 100644 src/main/services/infrastructure/DataCache.ts create mode 100644 src/main/services/infrastructure/FileWatcher.ts create mode 100644 src/main/services/infrastructure/NotificationManager.ts create mode 100644 src/main/services/infrastructure/TriggerManager.ts create mode 100644 src/main/services/infrastructure/index.ts create mode 100644 src/main/services/parsing/ClaudeMdReader.ts create mode 100644 src/main/services/parsing/GitIdentityResolver.ts create mode 100644 src/main/services/parsing/MessageClassifier.ts create mode 100644 src/main/services/parsing/SessionParser.ts create mode 100644 src/main/services/parsing/index.ts create mode 100644 src/main/types/chunks.ts create mode 100644 src/main/types/domain.ts create mode 100644 src/main/types/index.ts create mode 100644 src/main/types/jsonl.ts create mode 100644 src/main/types/messages.ts create mode 100644 src/main/utils/contextAccumulator.ts create mode 100644 src/main/utils/jsonl.ts create mode 100644 src/main/utils/metadataExtraction.ts create mode 100644 src/main/utils/pathDecoder.ts create mode 100644 src/main/utils/pathValidation.ts create mode 100644 src/main/utils/regexValidation.ts create mode 100644 src/main/utils/sessionStateDetection.ts create mode 100644 src/main/utils/timelineGapFilling.ts create mode 100644 src/main/utils/tokenizer.ts create mode 100644 src/main/utils/toolExtraction.ts create mode 100644 src/preload/CLAUDE.md create mode 100644 src/preload/constants/ipcChannels.ts create mode 100644 src/preload/index.ts create mode 100644 src/renderer/App.tsx create mode 100644 src/renderer/CLAUDE.md create mode 100644 src/renderer/components/CLAUDE.md create mode 100644 src/renderer/components/chat/AIChatGroup.tsx create mode 100644 src/renderer/components/chat/ChatHistory.tsx create mode 100644 src/renderer/components/chat/ChatHistoryEmptyState.tsx create mode 100644 src/renderer/components/chat/ChatHistoryItem.tsx create mode 100644 src/renderer/components/chat/ChatHistoryLoadingState.tsx create mode 100644 src/renderer/components/chat/CompactBoundary.tsx create mode 100644 src/renderer/components/chat/ContextBadge.tsx create mode 100644 src/renderer/components/chat/DisplayItemList.tsx create mode 100644 src/renderer/components/chat/LastOutputDisplay.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/DirectoryTree/DirectoryTreeNode.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts create mode 100644 src/renderer/components/chat/SessionContextPanel/DirectoryTree/types.ts create mode 100644 src/renderer/components/chat/SessionContextPanel/components/ClaudeMdFilesSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/ClaudeMdSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/CollapsibleSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/MentionedFilesSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/SessionContextHelpTooltip.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/TaskCoordinationSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/ThinkingTextSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/ToolOutputsSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/components/UserMessagesSection.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/index.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/items/ClaudeMdItem.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/items/MentionedFileItem.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/items/TaskCoordinationItem.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/items/ThinkingTextItem.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/items/ToolBreakdownItem.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/items/ToolOutputItem.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/items/UserMessageItem.tsx create mode 100644 src/renderer/components/chat/SessionContextPanel/types.ts create mode 100644 src/renderer/components/chat/SessionContextPanel/utils/formatting.ts create mode 100644 src/renderer/components/chat/SessionContextPanel/utils/pathParsing.ts create mode 100644 src/renderer/components/chat/SystemChatGroup.tsx create mode 100644 src/renderer/components/chat/UserChatGroup.tsx create mode 100644 src/renderer/components/chat/items/BaseItem.tsx create mode 100644 src/renderer/components/chat/items/ExecutionTrace.tsx create mode 100644 src/renderer/components/chat/items/LinkedToolItem.tsx create mode 100644 src/renderer/components/chat/items/MetricsPill.tsx create mode 100644 src/renderer/components/chat/items/SlashItem.tsx create mode 100644 src/renderer/components/chat/items/SubagentItem.tsx create mode 100644 src/renderer/components/chat/items/TeammateMessageItem.tsx create mode 100644 src/renderer/components/chat/items/TextItem.tsx create mode 100644 src/renderer/components/chat/items/ThinkingItem.tsx create mode 100644 src/renderer/components/chat/items/baseItemHelpers.ts create mode 100644 src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx create mode 100644 src/renderer/components/chat/items/linkedTool/EditToolViewer.tsx create mode 100644 src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx create mode 100644 src/renderer/components/chat/items/linkedTool/SkillToolViewer.tsx create mode 100644 src/renderer/components/chat/items/linkedTool/ToolErrorDisplay.tsx create mode 100644 src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx create mode 100644 src/renderer/components/chat/items/linkedTool/index.ts create mode 100644 src/renderer/components/chat/items/linkedTool/renderHelpers.tsx create mode 100644 src/renderer/components/chat/markdownComponents.tsx create mode 100644 src/renderer/components/chat/searchHighlightUtils.ts create mode 100644 src/renderer/components/chat/viewers/CodeBlockViewer.tsx create mode 100644 src/renderer/components/chat/viewers/DiffViewer.tsx create mode 100644 src/renderer/components/chat/viewers/MarkdownViewer.tsx create mode 100644 src/renderer/components/chat/viewers/index.ts create mode 100644 src/renderer/components/chat/viewers/syntaxHighlighter.ts create mode 100644 src/renderer/components/common/CopyButton.tsx create mode 100644 src/renderer/components/common/CopyablePath.tsx create mode 100644 src/renderer/components/common/ErrorBoundary.tsx create mode 100644 src/renderer/components/common/OngoingIndicator.tsx create mode 100644 src/renderer/components/common/RepositoryDropdown.tsx create mode 100644 src/renderer/components/common/TokenUsageDisplay.tsx create mode 100644 src/renderer/components/common/WorktreeBadge.tsx create mode 100644 src/renderer/components/dashboard/DashboardView.tsx create mode 100644 src/renderer/components/layout/MiddlePanel.tsx create mode 100644 src/renderer/components/layout/PaneContainer.tsx create mode 100644 src/renderer/components/layout/PaneContent.tsx create mode 100644 src/renderer/components/layout/PaneResizeHandle.tsx create mode 100644 src/renderer/components/layout/PaneSplitDropZone.tsx create mode 100644 src/renderer/components/layout/PaneView.tsx create mode 100644 src/renderer/components/layout/SessionTabContent.tsx create mode 100644 src/renderer/components/layout/Sidebar.tsx create mode 100644 src/renderer/components/layout/SidebarHeader.tsx create mode 100644 src/renderer/components/layout/SortableTab.tsx create mode 100644 src/renderer/components/layout/TabBar.tsx create mode 100644 src/renderer/components/layout/TabContextMenu.tsx create mode 100644 src/renderer/components/layout/TabbedLayout.tsx create mode 100644 src/renderer/components/notifications/NotificationRow.tsx create mode 100644 src/renderer/components/notifications/NotificationsView.tsx create mode 100644 src/renderer/components/search/CommandPalette.tsx create mode 100644 src/renderer/components/search/SearchBar.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/AddTriggerForm.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/ColorPaletteSelector.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/DynamicConfigSection.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/GeneralInfoSection.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/IgnorePatternsSection.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/ModeSelector.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/RepositoryScopeSection.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/SectionHeader.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCard.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCardHeader.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/TriggerConfiguration.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/components/TriggerPreview.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormHandlers.ts create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormState.ts create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/hooks/useRepositoryLookup.ts create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerCardState.ts create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerForm.ts create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/index.tsx create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/types.ts create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/utils/constants.ts create mode 100644 src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts create mode 100644 src/renderer/components/settings/SettingsTabs.tsx create mode 100644 src/renderer/components/settings/SettingsView.tsx create mode 100644 src/renderer/components/settings/components/SettingRow.tsx create mode 100644 src/renderer/components/settings/components/SettingsSectionHeader.tsx create mode 100644 src/renderer/components/settings/components/SettingsSelect.tsx create mode 100644 src/renderer/components/settings/components/SettingsToggle.tsx create mode 100644 src/renderer/components/settings/components/index.ts create mode 100644 src/renderer/components/settings/hooks/index.ts create mode 100644 src/renderer/components/settings/hooks/useSettingsConfig.ts create mode 100644 src/renderer/components/settings/hooks/useSettingsHandlers.ts create mode 100644 src/renderer/components/settings/sections/AdvancedSection.tsx create mode 100644 src/renderer/components/settings/sections/GeneralSection.tsx create mode 100644 src/renderer/components/settings/sections/NotificationsSection.tsx create mode 100644 src/renderer/components/settings/sections/index.ts create mode 100644 src/renderer/components/sidebar/DateGroupedSessions.tsx create mode 100644 src/renderer/components/sidebar/SessionContextMenu.tsx create mode 100644 src/renderer/components/sidebar/SessionItem.tsx create mode 100644 src/renderer/constants/cssVariables.ts create mode 100644 src/renderer/constants/layout.ts create mode 100644 src/renderer/constants/teamColors.ts create mode 100644 src/renderer/contexts/TabUIContext.tsx create mode 100644 src/renderer/contexts/useTabUIContext.ts create mode 100644 src/renderer/favicon.png create mode 100644 src/renderer/hooks/navigation/utils.ts create mode 100644 src/renderer/hooks/useAutoScrollBottom.ts create mode 100644 src/renderer/hooks/useKeyboardShortcuts.ts create mode 100644 src/renderer/hooks/useTabNavigationController.ts create mode 100644 src/renderer/hooks/useTabUI.ts create mode 100644 src/renderer/hooks/useTheme.ts create mode 100644 src/renderer/hooks/useVisibleAIGroup.ts create mode 100644 src/renderer/hooks/useZoomFactor.ts create mode 100644 src/renderer/index.css create mode 100644 src/renderer/index.html create mode 100644 src/renderer/main.tsx create mode 100644 src/renderer/store/CLAUDE.md create mode 100644 src/renderer/store/index.ts create mode 100644 src/renderer/store/slices/configSlice.ts create mode 100644 src/renderer/store/slices/conversationSlice.ts create mode 100644 src/renderer/store/slices/notificationSlice.ts create mode 100644 src/renderer/store/slices/paneSlice.ts create mode 100644 src/renderer/store/slices/projectSlice.ts create mode 100644 src/renderer/store/slices/repositorySlice.ts create mode 100644 src/renderer/store/slices/sessionDetailSlice.ts create mode 100644 src/renderer/store/slices/sessionSlice.ts create mode 100644 src/renderer/store/slices/subagentSlice.ts create mode 100644 src/renderer/store/slices/tabSlice.ts create mode 100644 src/renderer/store/slices/tabUISlice.ts create mode 100644 src/renderer/store/slices/uiSlice.ts create mode 100644 src/renderer/store/types.ts create mode 100644 src/renderer/store/utils/paneHelpers.ts create mode 100644 src/renderer/store/utils/pathResolution.ts create mode 100644 src/renderer/store/utils/stateResetHelpers.ts create mode 100644 src/renderer/types/api.ts create mode 100644 src/renderer/types/claudeMd.ts create mode 100644 src/renderer/types/contextInjection.ts create mode 100644 src/renderer/types/data.ts create mode 100644 src/renderer/types/groups.ts create mode 100644 src/renderer/types/notifications.ts create mode 100644 src/renderer/types/panes.ts create mode 100644 src/renderer/types/tabs.ts create mode 100644 src/renderer/utils/aiGroupEnhancer.ts create mode 100644 src/renderer/utils/aiGroupHelpers.ts create mode 100644 src/renderer/utils/claudeMdTracker.ts create mode 100644 src/renderer/utils/contextTracker.ts create mode 100644 src/renderer/utils/dateGrouping.ts create mode 100644 src/renderer/utils/displayItemBuilder.ts create mode 100644 src/renderer/utils/displaySummary.ts create mode 100644 src/renderer/utils/formatters.ts create mode 100644 src/renderer/utils/groupTransformer.ts create mode 100644 src/renderer/utils/lastOutputDetector.ts create mode 100644 src/renderer/utils/modelExtractor.ts create mode 100644 src/renderer/utils/pathDisplay.ts create mode 100644 src/renderer/utils/pathUtils.ts create mode 100644 src/renderer/utils/slashCommandExtractor.ts create mode 100644 src/renderer/utils/stringUtils.ts create mode 100644 src/renderer/utils/toolLinkingEngine.ts create mode 100644 src/renderer/utils/toolRendering/index.ts create mode 100644 src/renderer/utils/toolRendering/toolContentChecks.ts create mode 100644 src/renderer/utils/toolRendering/toolSummaryHelpers.ts create mode 100644 src/renderer/utils/toolRendering/toolTokens.ts create mode 100644 src/renderer/vite-env.d.ts create mode 100644 src/shared/CLAUDE.md create mode 100644 src/shared/constants/cache.ts create mode 100644 src/shared/constants/index.ts create mode 100644 src/shared/constants/trafficLights.ts create mode 100644 src/shared/constants/triggerColors.ts create mode 100644 src/shared/constants/window.ts create mode 100644 src/shared/types/api.ts create mode 100644 src/shared/types/index.ts create mode 100644 src/shared/types/notifications.ts create mode 100644 src/shared/types/visualization.ts create mode 100644 src/shared/utils/contentSanitizer.ts create mode 100644 src/shared/utils/errorHandling.ts create mode 100644 src/shared/utils/logger.ts create mode 100644 src/shared/utils/markdownTextSearch.ts create mode 100644 src/shared/utils/modelParser.ts create mode 100644 src/shared/utils/teammateMessageParser.ts create mode 100644 src/shared/utils/tokenFormatting.ts create mode 100644 tailwind.config.js create mode 100644 test/main/ipc/configValidation.test.ts create mode 100644 test/main/ipc/guards.test.ts create mode 100644 test/main/services/analysis/ChunkBuilder.test.ts create mode 100644 test/main/services/discovery/ProjectPathResolver.test.ts create mode 100644 test/main/services/discovery/SessionSearcher.test.ts create mode 100644 test/main/services/infrastructure/FileWatcher.test.ts create mode 100644 test/main/services/parsing/MessageClassifier.test.ts create mode 100644 test/main/services/parsing/SessionParser.test.ts create mode 100644 test/main/utils/jsonl.test.ts create mode 100644 test/main/utils/pathDecoder.test.ts create mode 100644 test/main/utils/pathValidation.test.ts create mode 100644 test/main/utils/regexValidation.test.ts create mode 100644 test/main/utils/tokenizer.test.ts create mode 100644 test/mocks/electronAPI.ts create mode 100644 test/renderer/hooks/navigationUtils.test.ts create mode 100644 test/renderer/hooks/useAutoScrollBottom.test.ts create mode 100644 test/renderer/hooks/useSearchContextNavigation.test.ts create mode 100644 test/renderer/hooks/useVisibleAIGroup.test.ts create mode 100644 test/renderer/store/notificationSlice.test.ts create mode 100644 test/renderer/store/paneSlice.test.ts create mode 100644 test/renderer/store/pathResolution.test.ts create mode 100644 test/renderer/store/sessionSlice.test.ts create mode 100644 test/renderer/store/storeTestUtils.ts create mode 100644 test/renderer/store/tabSlice.test.ts create mode 100644 test/renderer/store/tabUISlice.test.ts create mode 100644 test/renderer/utils/claudeMdTracker.test.ts create mode 100644 test/renderer/utils/dateGrouping.test.ts create mode 100644 test/renderer/utils/formatters.test.ts create mode 100644 test/renderer/utils/pathUtils.test.ts create mode 100644 test/setup.ts create mode 100644 test/shared/utils/markdownSearchRendererAlignment.test.ts create mode 100644 test/shared/utils/markdownTextSearch.test.ts create mode 100644 test/shared/utils/modelParser.test.ts create mode 100644 test/shared/utils/tokenFormatting.test.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 tsconfig.test.json create mode 100644 vitest.config.ts create mode 100644 vitest.critical.config.ts diff --git a/.claude/agents/claude-md-auditor.md b/.claude/agents/claude-md-auditor.md new file mode 100644 index 00000000..dd8a6273 --- /dev/null +++ b/.claude/agents/claude-md-auditor.md @@ -0,0 +1,198 @@ +--- +name: claude-md-auditor +description: "Use this agent when CLAUDE.md files or .claude/rules/ files may have drifted from the actual codebase state. This includes after significant refactors, feature additions, file renames, directory restructuring, or when you suspect documentation no longer matches reality. Use proactively after any large code change.\\n\\nExamples:\\n\\n- Example 1:\\n user: \"Refactor the services directory to split analysis into separate modules\"\\n assistant: \"I've completed the refactoring of the services directory. Let me now launch the claude-md-auditor agent to ensure all CLAUDE.md files reflect these structural changes.\"\\n \\n Since a significant refactor was performed that likely changed file paths, exports, and directory structure documented in CLAUDE.md files, use the Task tool to launch the claude-md-auditor agent.\\n \\n\\n- Example 2:\\n user: \"Add a new notifications feature with hooks, components, and a store slice\"\\n assistant: \"The notifications feature is now implemented. I'll use the claude-md-auditor agent to update the documentation to include the new files and patterns.\"\\n \\n A new feature was added with multiple new files across components, hooks, and store slices. The CLAUDE.md files likely need updating to document these additions. Use the Task tool to launch the claude-md-auditor agent.\\n \\n\\n- Example 3:\\n user: \"Rename isRealUserMessage to isParsedRealUserMessage across the codebase\"\\n assistant: \"The rename is complete across all source and test files. Now I'll launch the claude-md-auditor agent to update any documentation references to the old function name.\"\\n \\n A function was renamed which is likely documented in CLAUDE.md type guard tables and conventions sections. Use the Task tool to launch the claude-md-auditor agent to fix stale references.\\n \\n\\n- Example 4:\\n user: \"Can you audit the CLAUDE.md files to make sure they're up to date?\"\\n assistant: \"I'll launch the claude-md-auditor agent to systematically verify all documentation against the actual codebase.\"\\n \\n The user explicitly requested a documentation audit. Use the Task tool to launch the claude-md-auditor agent.\\n " +model: opus +color: green +memory: project +--- + +You are an elite CLAUDE.md auditor and documentation integrity specialist. Your sole purpose is to ensure every `CLAUDE.md` file and `.claude/rules/*.md` file in the project accurately reflects the current codebase state. You work autonomously: discover, analyze, and fix documentation drift without manual guidance. + +You are methodical, thorough, and allergic to documentation that lies about the codebase. + +## Core Principles + +1. **Truth from codebase, not docs** — The filesystem is the source of truth. If a CLAUDE.md says a file exists but `Glob` can't find it, the doc is wrong. + +2. **Max 200 lines per file** — Keep files concise. Split if over limit. + +3. **Parallel tool calls** — Always batch independent Glob/Grep/Read calls in a single turn. Never sequentially read files that can be read in parallel. This is critical for performance. + +4. **Surgical edits** — Use Edit (not Write) for existing files. Change only what's wrong. Don't rewrite entire files when a few lines need fixing. + +5. **No invention** — Only document what actually exists. Never add aspirational content. + +6. **Preserve voice and style** — Match the existing writing style of each file. Don't introduce new formatting patterns unless the file has none. + +7. **Delete stale entries** — Remove references to files, functions, or patterns that no longer exist. Don't comment them out. + +8. **Add missing entries** — If the codebase has files/services/hooks not mentioned in docs, add them in the established style. + +## Process + +### Phase 1: Discovery (parallel) + +**Check your agent memory first.** Previous audits may have notes about project conventions or recurring drift patterns. + +Make ALL of these calls in a single turn: + +- `Glob: **/CLAUDE.md` +- `Glob: .claude/rules/*.md` +- `Glob: src/**/*.ts` (to understand actual structure) +- `Glob: src/**/*.tsx` +- `Glob: test/**/*.test.ts` + +Then, in the next turn, read every discovered CLAUDE.md and rules file in a single parallel batch. + +### Phase 2: Cross-Reference Analysis + +For each CLAUDE.md file, verify every claim against the actual codebase: + +| Documented Item | Verification Method | +|----------------|-------------------| +| File/directory exists | `Glob` for the path | +| Export name is correct | `Grep` for the export | +| Function/hook name | `Grep` for the definition | +| Service/class name | `Grep` for `class X` or `export.*X` | +| Method count (e.g., "9 methods") | Count actual methods | +| Test file listing | `Glob` for test directory | +| CSS variable names | `Grep` in index.css | +| Command names (pnpm scripts) | Read package.json `scripts` | + +**Batch verification calls**: Group all Grep/Glob checks for a single CLAUDE.md file into one parallel turn. Then move to the next file. + +### Common Drift Patterns to Catch + +- **Renamed exports**: Function/type names changed but docs still reference old names +- **Missing new files**: New services/hooks/utils added but not documented +- **Deleted files**: Old entries referencing removed code +- **Wrong counts**: "11 slices" when there are now 12 +- **Wrong descriptions**: File purpose changed but doc wasn't updated +- **Missing subdirectories**: New `utils/` or `hooks/` folders not listed +- **Stale commands**: Build/test commands that changed in package.json +- **Moved files**: Files relocated to different directories +- **Changed import paths**: Path aliases or barrel exports changed + +### Phase 3: Parallel Updates + +Group all edits by file. For each CLAUDE.md that needs changes: + +1. **Use Edit tool** with precise `old_string` → `new_string` replacements +2. **Make multiple Edit calls per turn** for independent files +3. **Only use Write** if creating a new CLAUDE.md file that doesn't exist yet + +Decision matrix: + +| Situation | Action | +|-----------|--------| +| Entry references non-existent file | Delete the entry | +| New file exists but undocumented | Add entry in alphabetical order | +| Name/path changed | Update to current name/path | +| Count is wrong | Update the number | +| Sections accurate | Leave untouched | +| Entire file is obsolete | Delete the file | +| Directory needs docs but has none | Create new CLAUDE.md | + +### Phase 4: Verification + +After all edits, do a final pass: +1. Re-read each modified file to confirm edits applied correctly +2. Check line counts (warn if any file exceeds 200 lines) +3. Cross-check: spot-verify 3-5 entries from each file against codebase + +## Output Format + +When finished, return a concise summary: + +``` +## CLAUDE.md Audit Complete + +### Files Modified +- `path/CLAUDE.md` — [what changed: added X, removed Y, fixed Z] +- ... + +### Files Created +- `path/CLAUDE.md` — [why it was needed] + +### Files Deleted +- `path/CLAUDE.md` — [why it was obsolete] + +### No Changes Needed +- `path/CLAUDE.md` — accurate as-is + +### Stats +- Files audited: N +- Files modified: N +- Entries added: N +- Entries removed: N +- Entries corrected: N +``` + +## Critical Rules + +**ALWAYS verify before editing.** Never assume a documented entry is wrong without checking the actual codebase first. + +**PARALLEL, PARALLEL, PARALLEL.** Every turn should have multiple tool calls unless there's a data dependency. Reading 10 files? One turn, 10 Read calls. Checking 15 exports? One turn, 15 Grep calls. + +**Don't touch non-documentation files.** You modify ONLY `**/CLAUDE.md` and `.claude/rules/*.md` files. Never edit source code, tests, or config files. + +**Respect .claude/rules/ glob patterns.** Rules files may have YAML frontmatter with `globs:` that control when they're loaded. Don't change the globs unless the file patterns genuinely changed. + +**No commits.** Return results only. The caller decides whether to commit. + +**Update your agent memory** as you discover project conventions, recurring drift patterns, file organization quirks, naming conventions, and areas of the codebase that frequently change. This builds up institutional knowledge across audits. Write concise notes about what you found and where. + +Examples of what to record: +- Directories or files that are frequently renamed or restructured +- Naming conventions for exports, hooks, utilities, and services +- Common patterns of documentation drift (e.g., counts going stale, renamed type guards) +- Which CLAUDE.md files cover which parts of the codebase +- Project-specific conventions that affect how documentation should be written +- Files or sections that were accurate and rarely drift (low-priority for future audits) + +# Persistent Agent Memory + +You have a persistent Persistent Agent Memory directory at `.claude/agent-memory/claude-md-auditor/`. Its contents persist across conversations. + +As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned. + +Guidelines: +- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise +- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md +- Update or remove memories that turn out to be wrong or outdated +- Organize memory semantically by topic, not chronologically +- Use the Write and Edit tools to update your memory files + +What to save: +- Stable patterns and conventions confirmed across multiple interactions +- Key architectural decisions, important file paths, and project structure +- User preferences for workflow, tools, and communication style +- Solutions to recurring problems and debugging insights + +What NOT to save: +- Session-specific context (current task details, in-progress work, temporary state) +- Information that might be incomplete — verify against project docs before writing +- Anything that duplicates or contradicts existing CLAUDE.md instructions +- Speculative or unverified conclusions from reading a single file + +Explicit user requests: +- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions +- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files +- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project + +## Searching past context + +When looking for past context: +1. Search topic files in your memory directory: +``` +Grep with pattern="" path="/.claude/agent-memory/claude-md-auditor/" glob="*.md" +``` +2. Session transcript logs (last resort — large files, slow): +``` +Grep with pattern="" path="~/.claude/projects//" glob="*.jsonl" +``` +Use narrow search terms (error messages, file paths, function names) rather than broad keywords. + +## MEMORY.md + +Your MEMORY.md is currently empty. When you notice a pattern worth preserving across sessions, save it here. Anything in MEMORY.md will be included in your system prompt next time. diff --git a/.claude/agents/quality-fixer.md b/.claude/agents/quality-fixer.md new file mode 100644 index 00000000..650060e7 --- /dev/null +++ b/.claude/agents/quality-fixer.md @@ -0,0 +1,90 @@ +--- +name: quality-fixer +description: "Use this agent when the user wants to fix all code quality issues in the project, including linting, formatting, and unused code detection. This agent runs `pnpm fix` followed by `pnpm quality` in a loop, delegating each iteration to a subagent, until all issues are resolved.\\n\\nExamples:\\n\\n- User: \"Fix all the quality issues\"\\n Assistant: \"I'll launch the quality-fixer agent to iteratively fix all linting, formatting, and quality issues.\"\\n (Uses Task tool to launch quality-fixer agent)\\n\\n- User: \"Run quality checks and fix everything\"\\n Assistant: \"Let me use the quality-fixer agent to handle that.\"\\n (Uses Task tool to launch quality-fixer agent)\\n\\n- User: \"Make sure the code passes all checks\"\\n Assistant: \"I'll use the quality-fixer agent to ensure all quality checks pass.\"\\n (Uses Task tool to launch quality-fixer agent)\\n\\n- After completing a large refactor or feature implementation:\\n Assistant: \"Now that the changes are complete, let me launch the quality-fixer agent to ensure everything passes quality checks.\"\\n (Uses Task tool to launch quality-fixer agent)" +model: opus +color: red +--- + +You are an elite code quality engineer specializing in automated code quality remediation. Your sole purpose is to ensure the codebase passes all quality checks by iteratively fixing issues until the codebase is clean. + +## Project Context +- This is an Electron + React + TypeScript project using pnpm +- Quality commands: + - `pnpm fix` = runs `pnpm lint:fix && pnpm format` (auto-fixes lint and formatting) + - `pnpm quality` = runs `pnpm check && pnpm format:check && npx knip` (type checking, format verification, unused code detection) +- Path aliases: `@main/*`, `@renderer/*`, `@shared/*`, `@preload/*` + +## Core Process + +You operate in a **loop** where each iteration is delegated to a **subagent** via the Task tool. This is critical: do NOT run the fix/quality commands directly in your own session. Every iteration MUST be dispatched as a subagent. + +### Loop Structure + +**Iteration N (each via a Task tool subagent):** + +1. **Subagent prompt must instruct the subagent to:** + a. Run `pnpm fix` and capture the full output + b. Run `pnpm quality` and capture the full output + c. If `pnpm quality` succeeds (exit code 0, no errors), report SUCCESS + d. If `pnpm quality` fails, analyze the error output carefully and fix all reported issues: + - **TypeScript errors** (`pnpm check`): Fix type errors, missing imports, incorrect types + - **Format issues** (`pnpm format:check`): These should be auto-fixed by `pnpm fix`, but if persistent, manually fix formatting + - **Knip issues** (`npx knip`): Remove unused exports, unused dependencies, unused files, unused types + e. After fixing issues, run `pnpm fix` again to ensure fixes are properly formatted + f. Report back: what was fixed, what errors remain (if any), and whether quality passed + +2. **After receiving the subagent's report:** + - If the subagent reports SUCCESS (all quality checks pass), you are DONE. Report the final status. + - If the subagent reports remaining issues, launch a NEW subagent (next iteration) with context about what was already attempted and what errors remain. + +### Subagent Prompt Template + +When launching each subagent via the Task tool, provide a detailed prompt like this: + +``` +You are fixing code quality issues in this project. This is iteration {N} of the quality fix loop. + +{If iteration > 1: "Previous iteration found these remaining issues: {paste remaining errors}"} + +Steps: +1. Run `pnpm fix` to auto-fix lint and formatting issues. Show the output. +2. Run `pnpm quality` to check for remaining issues. Show the full output. +3. If quality passes with no errors, report "SUCCESS: All quality checks pass." +4. If quality fails, carefully analyze EVERY error and fix them: + - For TypeScript errors: fix the type issues in the relevant files + - For knip (unused code) errors: remove unused exports, imports, dependencies, or files + - For format errors: fix formatting manually if pnpm fix didn't catch it +5. After making fixes, run `pnpm fix` one more time to ensure your changes are properly formatted. +6. Report what you fixed and any remaining errors you could not resolve. + +IMPORTANT: +- Fix ALL issues, not just some of them +- When removing unused exports, check if they're used elsewhere before removing +- For knip unused dependency warnings, remove them from package.json +- For knip unused file warnings, verify the file is truly unused before deleting +- Use path aliases (@main/*, @renderer/*, @shared/*, @preload/*) for any new imports +``` + +### Safety Rules + +1. **Maximum 5 iterations**. If after 5 loops quality still doesn't pass, stop and report the remaining issues to the user with a clear summary of what was fixed and what remains. +2. **Never delete files without verification** — when knip reports unused files, the subagent should verify they're truly unused. +3. **Never remove exports that are used** — when knip reports unused exports, verify they're not imported elsewhere. +4. **Preserve functionality** — fixes should only address quality issues, never change application behavior. +5. **Each subagent gets full context** — always pass remaining errors from the previous iteration to the next subagent so it doesn't repeat failed approaches. + +### Reporting + +After the loop completes (either success or max iterations), provide a summary: +- Total iterations run +- Issues found and fixed (categorized by type: lint, format, types, unused code) +- Final status: PASS or FAIL with remaining issues +- Files modified + +**Update your agent memory** as you discover common quality issues, recurring lint violations, frequently flagged unused exports, and knip patterns in this codebase. This builds up institutional knowledge across conversations. Write concise notes about what you found and where. + +Examples of what to record: +- Common TypeScript errors that recur (e.g., specific type mismatches) +- Files or exports frequently flagged by knip +- Lint rules that frequently need fixing +- Patterns that tend to cause quality check failures diff --git a/.claude/commands/ccc/chatgroup-architecture.md b/.claude/commands/ccc/chatgroup-architecture.md new file mode 100644 index 00000000..3a26b0d4 --- /dev/null +++ b/.claude/commands/ccc/chatgroup-architecture.md @@ -0,0 +1,389 @@ +--- +name: ccc:chatgroup-architecture +description: ChatGroup architecture — how conversation data flows from raw JSONL to rendered chat groups. Use when working on UserGroup, AIGroup, SystemGroup, display items, tool linking, chunks, or the rendering hierarchy. +--- + +# ChatGroup Architecture + +How conversation data flows from raw JSONL messages to rendered chat groups. + +## Core Design Principle + +Chat groups are **independent items in a flat chronological list**, not paired turns. +There is no UserTurn/AITurn pairing — each group stands alone. + +```typescript +// src/renderer/types/groups.ts +export type ChatItem = + | { type: 'user'; group: UserGroup } + | { type: 'system'; group: SystemGroup } + | { type: 'ai'; group: AIGroup } + | { type: 'compact'; group: CompactGroup }; + +export interface SessionConversation { + sessionId: string; + items: ChatItem[]; // Flat chronological list + totalUserGroups: number; + totalSystemGroups: number; + totalAIGroups: number; + totalCompactGroups: number; +} +``` + +## Pipeline Overview + +``` +Raw JSONL messages + → MessageClassifier (classify into user/system/ai/hardNoise) + → ChunkBuilder (buffer AI messages, flush on user/system boundary) + → ChunkFactory (build EnhancedAIChunk with SemanticSteps) + → groupTransformer (chunks → flat ChatItem[] conversation) + → aiGroupEnhancer (AIGroup → EnhancedAIGroup with displayItems, linkedTools, lastOutput) + → React components render +``` + +Primary source files: + +- `src/main/services/parsing/MessageClassifier.ts` +- `src/main/services/analysis/ChunkBuilder.ts` +- `src/main/services/analysis/ChunkFactory.ts` +- `src/renderer/utils/groupTransformer.ts` +- `src/renderer/utils/aiGroupEnhancer.ts` +- `src/renderer/utils/displayItemBuilder.ts` +- `src/renderer/types/groups.ts` +- `src/main/types/chunks.ts` + +## Data Models + +### UserGroup + +```typescript +// src/renderer/types/groups.ts +interface UserGroup { + id: string; + message: ParsedMessage; + timestamp: Date; + content: UserGroupContent; + index: number; // Ordering index within session +} + +interface UserGroupContent { + text?: string; // Plain text (commands removed) + rawText?: string; // Original text + commands: CommandInfo[]; // Extracted /commands + images: ImageData[]; // Attached images + fileReferences: FileReference[]; // @file.ts mentions +} +``` + +Renders right-aligned blue bubble. Contains markdown text, slash commands, images, and file references. + +### AIGroup + +```typescript +interface AIGroup { + id: string; + turnIndex: number; // 0-based (for turn navigation) + startTime: Date; + endTime: Date; + durationMs: number; + steps: SemanticStep[]; // Core semantic steps + tokens: AIGroupTokens; + summary: AIGroupSummary; // For collapsed view + status: AIGroupStatus; // 'complete' | 'interrupted' | 'error' | 'in_progress' + processes: Process[]; // Subagent processes + chunkId: string; + metrics: SessionMetrics; + responses: ParsedMessage[]; // All assistant + internal messages + isOngoing?: boolean; // True for last group in ongoing session +} +``` + +### EnhancedAIGroup + +The renderer enhances `AIGroup` before rendering: + +```typescript +interface EnhancedAIGroup extends AIGroup { + lastOutput: AIGroupLastOutput | null; // Always-visible output + displayItems: AIGroupDisplayItem[]; // Flattened chronological items + linkedTools: Map; // Tool call/result pairs + itemsSummary: string; // "2 thinking, 4 tool calls, 3 subagents" + mainModel: ModelInfo | null; + subagentModels: ModelInfo[]; + claudeMdStats: ClaudeMdStats | null; +} +``` + +Enhancement happens in `src/renderer/utils/aiGroupEnhancer.ts`: + +1. `findLastOutput` — extracts the final visible output (text, tool result, interruption, plan exit, ongoing) +2. `linkToolCallsToResults` — pairs tool calls with their results into `LinkedToolItem` +3. `buildDisplayItems` — flattens steps into chronological `AIGroupDisplayItem[]` +4. `buildSummary` — generates human-readable summary string +5. `extractMainModel` / `extractSubagentModels` — extracts model info + +### AIGroupDisplayItem + +```typescript +type AIGroupDisplayItem = + | { type: 'thinking'; content: string; timestamp: Date; tokenCount?: number } + | { type: 'tool'; tool: LinkedToolItem } + | { type: 'subagent'; subagent: Process } + | { type: 'output'; content: string; timestamp: Date; tokenCount?: number } + | { type: 'slash'; slash: SlashItem } + | { type: 'teammate_message'; teammateMessage: TeammateMessage }; +``` + +Display items are sorted chronologically in `src/renderer/utils/displayItemBuilder.ts`. + +### LinkedToolItem + +```typescript +interface LinkedToolItem { + id: string; + name: string; + input: Record; + callTokens?: number; + result?: { + content: string | unknown[]; + isError: boolean; + toolUseResult?: ToolUseResultData; + tokenCount?: number; + }; + inputPreview: string; // First 100 chars + outputPreview?: string; // First 200 chars + startTime: Date; + endTime?: Date; + durationMs?: number; + isOrphaned: boolean; // No result received + sourceModel?: string; + skillInstructions?: string; // For Skill tool calls + skillInstructionsTokenCount?: number; +} +``` + +### AIGroupLastOutput + +Always-visible output below the AI group header: + +```typescript +interface AIGroupLastOutput { + type: 'text' | 'tool_result' | 'interruption' | 'ongoing' | 'plan_exit'; + text?: string; + toolName?: string; + toolResult?: string; + isError?: boolean; + interruptionMessage?: string; + planContent?: string; + planPreamble?: string; + timestamp: Date; +} +``` + +### SystemGroup + +```typescript +interface SystemGroup { + id: string; + message: ParsedMessage; + timestamp: Date; + commandOutput: string; + commandName?: string; +} +``` + +Renders left-aligned with neutral gray styling. Monospace pre with ANSI escape codes cleaned. + +### CompactGroup + +```typescript +interface CompactGroup { + id: string; + timestamp: Date; + message: ParsedMessage; + tokenDelta?: CompactionTokenDelta; + startingPhaseNumber?: number; +} +``` + +Visual boundary for context compaction events. + +## Backend: Chunk Building + +### Message Classification + +`src/main/services/parsing/MessageClassifier.ts` classifies raw messages into 4 categories: + +| Category | Description | Result | +|----------|-------------|--------| +| `user` | Genuine user input | Creates UserChunk, renders right | +| `system` | Command output | Creates SystemChunk, renders left | +| `ai` | Assistant responses | Buffered into AIChunk, renders left | +| `hardNoise` | System metadata, caveats, reminders | Filtered out entirely | + +### Chunk Building + +`src/main/services/analysis/ChunkBuilder.ts` buffers AI messages and flushes on boundaries: + +``` +for each classified message: + hardNoise → skip + compact → flush AI buffer, emit CompactChunk + user → flush AI buffer, emit UserChunk + system → flush AI buffer, emit SystemChunk + ai → add to AI buffer +``` + +AI buffer is flushed when a non-AI message arrives, producing one `AIChunk` per contiguous run of assistant messages. + +### Semantic Step Extraction + +`src/main/services/analysis/ChunkFactory.ts` enriches AI chunks: + +1. Build tool executions from message content blocks +2. Collect sidechain messages within the time range +3. Link subagent processes to the chunk +4. Extract semantic steps (`SemanticStep[]`) +5. Fill timeline gaps +6. Calculate step context accumulation +7. Build step groups + +```typescript +type SemanticStepType = 'thinking' | 'tool_call' | 'tool_result' | 'subagent' | 'output' | 'interruption'; + +interface SemanticStep { + id: string; + type: SemanticStepType; + startTime: Date; + endTime?: Date; + durationMs: number; + content: { /* type-specific fields */ }; + tokens?: { input: number; output: number; cached?: number }; + context: 'main' | 'subagent'; +} +``` + +## Rendering + +### Component Hierarchy + +``` +ChatHistory + └─ ChatHistoryItem (router) + ├─ UserChatGroup → right-aligned blue bubble + ├─ SystemChatGroup → left-aligned gray block + ├─ AIChatGroup → left-aligned, collapsible + │ ├─ Header (model, summary, tokens, duration, timestamp) + │ ├─ DisplayItemList (when expanded) + │ │ ├─ ThinkingItem + │ │ ├─ LinkedToolItem (Read, Edit, Write, Skill, etc.) + │ │ ├─ SubagentItem (with nested trace) + │ │ ├─ TextItem + │ │ ├─ SlashItem + │ │ └─ TeammateMessageItem + │ └─ LastOutputDisplay (always visible) + └─ CompactBoundary +``` + +Primary render files: + +- `src/renderer/components/chat/ChatHistory.tsx` +- `src/renderer/components/chat/ChatHistoryItem.tsx` +- `src/renderer/components/chat/UserChatGroup.tsx` +- `src/renderer/components/chat/SystemChatGroup.tsx` +- `src/renderer/components/chat/AIChatGroup.tsx` +- `src/renderer/components/chat/DisplayItemList.tsx` +- `src/renderer/components/chat/LastOutputDisplay.tsx` + +### AI Group: Collapsed vs Expanded + +**Collapsed** (default): +- Header: Bot icon, "Claude", model badge, items summary, chevron +- Right side: context badge, token usage, duration, timestamp +- Last output (always visible below header) + +**Expanded**: +- All collapsed content, plus: +- `DisplayItemList` with chronologically ordered items +- Each display item can be individually expanded (nested expansion) + +### Last Output Rendering + +Always visible regardless of expansion state. Renders based on `type`: + +| Type | Rendering | +|------|-----------| +| `text` | Markdown in code-bg rounded block | +| `tool_result` | Tool name + pre-formatted result | +| `interruption` | Warning banner with AlertTriangle | +| `plan_exit` | Plan preamble + plan content in special block | +| `ongoing` | Ongoing session banner (last AI group only) | + +## Per-Tab UI State Isolation + +Each tab maintains **completely independent** expansion state via `tabUISlice.ts`: + +```typescript +interface TabUIState { + expandedAIGroupIds: Set; + expandedDisplayItemIds: Map>; + expandedSubagentTraceIds: Set; + showContextPanel: boolean; + selectedContextPhase: number | null; + savedScrollTop?: number; +} +``` + +Accessed via `useTabUI()` hook which reads `tabId` from `TabUIContext`: + +```typescript +// src/renderer/hooks/useTabUI.tsx +const { isAIGroupExpanded, toggleAIGroupExpansion, expandAIGroup, + getExpandedDisplayItemIds, toggleDisplayItemExpansion, + isSubagentTraceExpanded, toggleSubagentTraceExpansion } = useTabUI(); +``` + +Auto-expansion triggers: +- Error deep linking (contains highlighted error tool) +- Search results (contains search match) + +## Store Integration + +### Session Detail Slice + +`src/renderer/store/slices/sessionDetailSlice.ts` manages the fetch pipeline: + +``` +fetchSessionDetail(projectId, sessionId, tabId?) + → IPC: getSessionDetail (returns chunks + processes) + → transformChunksToConversation (chunks → ChatItem[]) + → processSessionClaudeMd (compute CLAUDE.md stats) + → processSessionContextWithPhases (compute context stats) + → store in global state + per-tab tabSessionData +``` + +Key state: + +| Field | Purpose | +|-------|---------| +| `sessionDetail` | Raw session data from main process | +| `conversation` | Transformed `SessionConversation` | +| `conversationLoading` | True during fetch (causes ChatHistory unmount) | +| `tabSessionData` | Per-tab copies of session data | +| `sessionClaudeMdStats` | CLAUDE.md injection stats per AI group | +| `sessionContextStats` | Context stats per AI group | +| `sessionPhaseInfo` | Phase boundary info | + +### Real-Time Updates + +`refreshSessionInPlace` re-fetches and transforms without setting `conversationLoading: true`, avoiding ChatHistory unmount/remount flicker. + +## Invariants + +1. Chat items are always flat and chronological — no nesting at the conversation level. +2. AI groups are self-contained — all semantic steps, tool links, and display items are computed per group. +3. Display items within an AI group are chronologically sorted. +4. Per-tab UI state is fully isolated — expanding a group in one tab doesn't affect another. +5. Last output is always visible regardless of AI group expansion state. +6. `conversationLoading: true` unmounts ChatHistory — avoid setting it unnecessarily for existing tabs. diff --git a/.claude/commands/ccc/design-system.md b/.claude/commands/ccc/design-system.md new file mode 100644 index 00000000..0f326def --- /dev/null +++ b/.claude/commands/ccc/design-system.md @@ -0,0 +1,356 @@ +--- +name: ccc:design-system +description: Design system and visual language — theming, CSS variables, Tailwind config, component styling patterns, icon usage, animations, and z-index layers. Use when creating or modifying UI components, working with the dark/light theme, or debugging visual issues. +--- + +# Design System & Visual Language + +How the theming, color palette, component patterns, and styling conventions work. + +## Theme Architecture + +Two themes (dark/light) driven by CSS custom properties in `src/renderer/index.css`. +Toggled via `useTheme()` hook which adds/removes `light` class on `document.documentElement`. + +Flash prevention: a script in `index.html` applies the cached theme before React loads. + +### Theme Hook + +```typescript +// src/renderer/hooks/useTheme.ts +const { theme, resolvedTheme, isDark, isLight } = useTheme(); +// theme: 'dark' | 'light' | 'system' +// resolvedTheme: 'dark' | 'light' (after system resolution) +``` + +## Styling Convention + +**Colors**: Always via CSS variables (theme-aware). Use inline `style` or Tailwind classes mapped to variables. +**Layout/spacing**: Tailwind utility classes. +**Icons**: `lucide-react` with `size-*` Tailwind classes. + +```tsx +// Preferred: inline style for theme-aware colors, Tailwind for layout +
+ +
+ +// Also valid: Tailwind classes that reference CSS variables +
+
+``` + +### TypeScript Constants + +`src/renderer/constants/cssVariables.ts` centralizes CSS variable strings: + +```typescript +import { COLOR_TEXT_MUTED, CARD_BG, CARD_BORDER_STYLE } from '@renderer/constants/cssVariables'; + +Muted text +
Card
+``` + +Constants cover: text colors, surfaces, borders, code blocks, diff, cards, tags, prose. + +## CSS Variable Reference + +All defined in `src/renderer/index.css` under `:root` (dark) and `:root.light`. + +### Surfaces + +| Variable | Dark | Light | Usage | +|----------|------|-------|-------| +| `--color-surface` | `#141416` | `#f9f9f7` | Main background | +| `--color-surface-raised` | `#27272a` | `#f0efed` | Elevated surfaces | +| `--color-surface-overlay` | `#27272a` | `#e8e7e4` | Overlays/modals | +| `--color-surface-sidebar` | `#0f0f11` | `#f1f0ee` | Sidebar background | + +### Text + +| Variable | Dark | Light | Usage | +|----------|------|-------|-------| +| `--color-text` | `#fafafa` | `#1c1b19` | Primary text | +| `--color-text-secondary` | `#a1a1aa` | `#4d4b46` | Secondary text | +| `--color-text-muted` | `#71717a` | `#6d6b65` | Muted text | + +### Borders + +| Variable | Dark | Light | +|----------|------|-------| +| `--color-border` | `rgba(255,255,255,0.05)` | `#d5d3cf` | +| `--color-border-subtle` | `rgba(255,255,255,0.05)` | `#e3e1dd` | +| `--color-border-emphasis` | `rgba(255,255,255,0.1)` | `#a8a5a0` | + +### Chat Bubbles + +**User bubble** (right-aligned): +| Variable | Dark | Light | +|----------|------|-------| +| `--chat-user-bg` | `#27272a` | `#eae9e6` | +| `--chat-user-text` | `#a1a1aa` | `#5a5955` | +| `--chat-user-border` | `rgba(255,255,255,0.08)` | `#d5d3cf` | +| `--chat-user-shadow` | `0 1px 0 0 rgba(255,255,255,0.03)` | `0 1px 2px 0 rgba(0,0,0,0.04)` | +| `--chat-user-tag-bg` | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.05)` | +| `--chat-user-tag-text` | `#e4e4e7` | `#3a3935` | +| `--chat-user-tag-border` | `rgba(255,255,255,0.12)` | `rgba(0,0,0,0.08)` | + +**AI message**: +| Variable | Dark | Light | +|----------|------|-------| +| `--chat-ai-border` | `rgba(255,255,255,0.05)` | `#d5d3cf` | +| `--chat-ai-icon` | `#71717a` | `#6d6b65` | + +**System bubble**: +| Variable | Dark | Light | +|----------|------|-------| +| `--chat-system-bg` | `rgba(39,39,42,0.5)` | `#eae9e6` | +| `--chat-system-text` | `#d4d4d8` | `#3a3935` | + +### Code & Syntax + +| Variable | Dark | Light | +|----------|------|-------| +| `--code-bg` | `#1c1c1e` | `#f0efed` | +| `--code-header-bg` | `#1c1c1e` | `#eae9e6` | +| `--code-border` | `rgba(255,255,255,0.1)` | `#d5d3cf` | +| `--code-line-number` | `#52525b` | `#a8a5a0` | +| `--code-filename` | `#60a5fa` | `#2563eb` | +| `--inline-code-bg` | `rgba(255,255,255,0.08)` | `rgba(0,0,0,0.05)` | +| `--inline-code-text` | `#e4e4e7` | `#3a3935` | + +Syntax highlighting: `--syntax-string`, `--syntax-comment`, `--syntax-number`, `--syntax-keyword`, `--syntax-type`, `--syntax-operator`, `--syntax-function`. Dark uses vibrant colors; light uses GitHub-inspired palette. + +### Semantic Blocks + +**Thinking**: Purple tones (`--thinking-bg`, `--thinking-border`, `--thinking-text`) +**Tool call**: Amber tones (`--tool-call-bg`, `--tool-call-border`, `--tool-call-text`) +**Tool result success**: Green tones (`--tool-result-success-bg/border/text`) +**Tool result error**: Red tones (`--tool-result-error-bg/border/text`) +**Output**: Gray tones (`--output-bg`, `--output-border`, `--output-text`) +**Interruption**: Red (`--interruption-bg/border/text`) +**Warning**: Amber (`--warning-bg/border/text`) +**Plan exit**: Green (`--plan-exit-bg/header-bg/border/text`) + +### Diff Viewer + +| Variable | Dark | Light | +|----------|------|-------| +| `--diff-added-bg` | `rgba(34,197,94,0.15)` | `rgba(34,197,94,0.1)` | +| `--diff-added-text` | `#4ade80` | `#166534` | +| `--diff-removed-bg` | `rgba(239,68,68,0.15)` | `rgba(239,68,68,0.1)` | +| `--diff-removed-text` | `#f87171` | `#991b1b` | + +### Cards (Subagents) + +| Variable | Dark | Light | +|----------|------|-------| +| `--card-bg` | `#121212` | `#f9f9f7` | +| `--card-border` | `#27272a` | `#d5d3cf` | +| `--card-header-bg` | `#18181b` | `#f0efed` | +| `--card-header-hover` | `#1f1f23` | `#eae9e6` | +| `--card-icon-muted` | `#52525b` | `#a8a5a0` | +| `--card-separator` | `#3f3f46` | `#d5d3cf` | + +### Badges + +Status badges: `--badge-error-bg/text`, `--badge-warning-bg/text`, `--badge-success-bg/text`, `--badge-info-bg/text`, `--badge-neutral-bg/text`. +Tags: `--tag-bg`, `--tag-text`, `--tag-border`. + +### Search Highlights + +| Variable | Dark | Light | +|----------|------|-------| +| `--highlight-bg` | `rgba(202,138,4,0.7)` | `#facc15` | +| `--highlight-bg-inactive` | `rgba(113,63,18,0.5)` | `#fef08a` | +| `--highlight-ring` | `#facc15` | `#ca8a04` | + +### Scrollbar + +Custom scrollbar styling via `--scrollbar-thumb`, `--scrollbar-thumb-hover`, `--scrollbar-thumb-active`. + +## Tailwind Config + +`tailwind.config.js` maps CSS variables to Tailwind classes: + +```javascript +colors: { + surface: { + DEFAULT: 'var(--color-surface)', + raised: 'var(--color-surface-raised)', + overlay: 'var(--color-surface-overlay)', + sidebar: 'var(--color-surface-sidebar)', + code: 'var(--code-bg)', + }, + border: { + DEFAULT: 'var(--color-border)', + subtle: 'var(--color-border-subtle)', + emphasis: 'var(--color-border-emphasis)', + }, + text: { + DEFAULT: 'var(--color-text)', + secondary: 'var(--color-text-secondary)', + muted: 'var(--color-text-muted)', + }, + semantic: { + success: '#22c55e', + error: '#ef4444', + warning: '#f59e0b', + info: '#3b82f6', + }, + // Backward-compatible alias + 'claude-dark': { bg: 'var(--color-surface)', surface: 'var(--color-surface-raised)', ... }, +} +``` + +Plugin: `@tailwindcss/typography` for markdown prose. + +## Icon Library + +`lucide-react` across 55+ components. + +**Sizes**: `size-3` (tiny), `size-3.5` (small), `size-4` (standard), `size-5` (medium), `size-10` (empty states) + +**Pattern**: `className` for size, `style` for color: +```tsx + + +``` + +**Common icons**: `Bot` (AI), `User` (user), `ChevronRight`/`ChevronDown` (expansion), `Check`/`Copy` (clipboard), `Info` (tooltips), `Loader2` (loading), `Clock` (duration), `Terminal` (traces), `CheckCircle2` (completed). + +## Team Colors + +`src/renderer/constants/teamColors.ts` defines 8 color sets for teammate visualization: + +```typescript +import { getTeamColorSet, TeamColorSet } from '@renderer/constants/teamColors'; + +const colors = getTeamColorSet('blue'); +// colors.border: '#3b82f6' — border accent +// colors.badge: 'rgba(59, 130, 246, 0.15)' — badge background +// colors.text: '#60a5fa' — text color +``` + +Available colors: blue, green, red, yellow, purple, cyan, orange, pink. + +## Component Patterns + +### User Chat Bubble + +Right-aligned with rounded corners, subtle shadow, copy overlay on hover: + +```tsx +
+
+
+ {text} +
+
+
+``` + +### AI Group Header + +Collapsible with model badge, summary, metrics: + +```tsx +
+ + Claude + {model.name} + {itemsSummary} + +
+``` + +### Subagent Card + +Linear-style card with nested expansion: + +```tsx +
+
+ + {/* colored dot + badge + description + metrics */} +
+ {isExpanded &&
{/* content */}
} +
+``` + +### Copy Button (Overlay) + +Gradient-fade overlay that appears on group hover: + +```tsx +
+
+ +
+``` + +### Popover via Portal + +Token usage and context badges use portaled popovers to escape stacking context: + +```tsx +{showPopover && createPortal( +
+ {/* content */} +
, + document.body +)} +``` + +## Animations + +### CSS Keyframes (in `index.css`) + +- `shimmer` — skeleton loading shimmer effect +- `skeleton-fade-in` — staggered fade-in for skeleton cards +- `splash-slide` — splash screen loading bar + +### Tailwind Utilities + +| Class | Usage | +|-------|-------| +| `animate-spin` | Loading spinners (`Loader2`) | +| `animate-ping` | Pulsing dots (ongoing state) | +| `transition-transform` | Chevron rotation | +| `transition-colors` | Hover color changes | +| `transition-opacity` | Copy button fade-in | +| `transition-all duration-300` | Card highlight transitions | +| `duration-[3000ms]` | Highlight ring fade-out | + +## Z-Index Layers + +| Z-Index | Usage | +|---------|-------| +| `z-10` | Copy button overlays, dropdown backdrops | +| `z-20` | Dropdown menus, search bar | +| `z-30` | Pane split drop zones | +| `z-40` | Pane view overlays | +| `z-50` | Context menus, command palette, settings selects | +| `99999` | Portaled popovers (token usage, context badge, metrics pill) | + +## Light Theme Notes + +The light theme uses **warm neutrals** (not pure white/gray): +- Backgrounds: `#f9f9f7`, `#f0efed`, `#eae9e6` (warm off-white) +- Borders: `#d5d3cf`, `#e3e1dd` (warm gray) +- Text: `#1c1b19`, `#4d4b46`, `#6d6b65` (warm dark) +- Syntax highlighting: GitHub-inspired palette + +Body transition: `background-color 0.2s ease, color 0.2s ease` for smooth theme switching. diff --git a/.claude/commands/ccc/explain-visible-context.md b/.claude/commands/ccc/explain-visible-context.md new file mode 100644 index 00000000..35df5c4b --- /dev/null +++ b/.claude/commands/ccc/explain-visible-context.md @@ -0,0 +1,104 @@ +--- +name: ccc:explain-visible-context +description: Explains what "Visible Context" is — the 6 trackable token categories, what falls outside tracking, how it's displayed, and why it matters. Use when someone asks about visible context, token attribution, or context window usage. +--- + +Present the following explanation directly to the user. Output the full content below as your response — do not summarize, ask follow-up questions, or treat this as background context. + +# Visible Context + +## What It Is + +"Visible Context" is the portion of Claude's context window that we can identify and measure. Every time Claude processes a turn, its context window fills with various pieces of information — your messages, file contents, tool outputs, thinking, and more. Visible Context tracks what we **can** attribute to a known source, so you can see where your tokens are going. + +## What We Track (6 Categories) + +### CLAUDE.md Files + +Memory files that Claude loads automatically at the start of every session and after each compaction. These include: + +- **Global** CLAUDE.md (`~/.claude/CLAUDE.md`) — your personal instructions across all projects +- **Project** CLAUDE.md (`.claude/CLAUDE.md` or `CLAUDE.md` at project root) — project-specific instructions +- **Directory** CLAUDE.md — instructions scoped to subdirectories (e.g., `src/renderer/CLAUDE.md`) + +These are injected repeatedly (once per compaction phase), so their token cost accumulates. A 500-token CLAUDE.md file injected across 3 compaction phases costs ~1,500 tokens total. + +### @-Mentioned Files + +Files you reference with `@path/to/file` in your messages. When you mention a file, Claude Code injects the full file contents into the context. Large files consume significant tokens — a 1,000-line source file could use 5,000+ tokens per mention. + +### Tool Outputs + +Results returned from tool executions: file reads (`Read`), command output (`Bash`), search results (`Grep`, `Glob`), and others. Every tool result stays in the context window until compaction. A `Bash` command that prints 500 lines of output or a `Read` of a large file both count here. + +### Thinking + Text Output + +Claude's own output that consumes context: + +- **Extended thinking** — Claude's internal reasoning (when thinking mode is active). This can be substantial for complex tasks. +- **Text output** — Claude's visible responses to you. Longer explanations and code blocks use more tokens. + +### Task Coordination + +Messages and operations from Claude Code's team/orchestration features: + +- `SendMessage` — messages between teammates +- `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` — task management +- `TeamCreate`, `TeamDelete` — team lifecycle + +Each coordination message adds to the context window of the receiving agent. + +### User Messages + +Your actual prompt text for each turn. This includes the raw text you type, but not the system-injected metadata around it. + +## What We Don't Track + +Visible Context does **not** cover everything in Claude's context window. The following are present but not attributable by our tracking: + +- **Claude Code's system prompt** — the base instructions that tell Claude how to behave, use tools, format output, etc. +- **Tool descriptions** — the schema and documentation for each built-in tool (Read, Write, Edit, Bash, Grep, Glob, etc.) +- **MCP tool descriptions** — schemas for any MCP (Model Context Protocol) servers you have connected +- **Custom agent definitions** — instructions from `.claude/agents/` configurations +- **Skill descriptions** — the short descriptions of available skills that Claude sees so it knows what's available (visible via `/context` in Claude Code) +- **Internal system reminders** — `` injections that Claude Code adds for session state, git status, available skills, etc. +- **Conversation structure overhead** — the message formatting, role markers, and protocol framing around each message + +These untracked items form a "base cost" that's always present. You can see what Claude Code injects via the `/context` command in Claude Code itself. + +## How It's Displayed + +### Per-Turn Popover (Context Badge) + +Each AI group in the chat shows a small badge. Hovering reveals what was injected at that specific turn — which CLAUDE.md files, which @-mentioned files, which tool outputs contributed tokens. + +### Token Usage Popover + +The token count next to each AI group has an info icon. Hovering shows the standard input/output/cache breakdown, plus an expandable "Visible Context" section showing the percentage of total tokens attributable to each tracked category. + +### Session Context Panel + +A dedicated panel (toggle via the context badge or header button) that shows the full session-wide view: + +- All tracked injections grouped by category +- Token estimates per injection +- Phase filtering (if compaction events split the session into phases) +- Total visible context as a percentage of total session tokens + +## Compaction Phases + +When Claude's context window fills up, Claude Code compacts the conversation — summarizing older messages to free space. Each compaction creates a new "phase." Visible Context tracks injections per phase because: + +- CLAUDE.md files are re-injected after each compaction +- Previous tool outputs and file contents are summarized away +- The phase selector lets you see what's in context **right now** (current phase) vs. what was present earlier + +## Why Visible Context Matters + +Understanding where tokens go helps you: + +- **Spot expensive injections** — a massive CLAUDE.md file or a frequently-mentioned large file could be using 20%+ of your context +- **Optimize CLAUDE.md** — keep memory files concise; every token is repeated across phases +- **Be strategic with @-mentions** — mentioning a 2,000-line file costs real context space +- **Understand compaction impact** — see how much context resets after compaction +- **Debug unexpected behavior** — if Claude seems to "forget" something, check whether it was compacted away diff --git a/.claude/commands/ccc/markdown-search-logic.md b/.claude/commands/ccc/markdown-search-logic.md new file mode 100644 index 00000000..f0d959a4 --- /dev/null +++ b/.claude/commands/ccc/markdown-search-logic.md @@ -0,0 +1,179 @@ +--- +name: ccc:markdown-search +description: Markdown search logic — how in-session and cross-session search works. Use when working on SearchBar, search highlighting, searchHighlightUtils, markdownTextSearch, or SessionSearcher. +--- + +# Markdown Search Logic + +How in-session and cross-session markdown search works end-to-end. + +## Scope + +Current in-session search intentionally covers: + +- User message markdown text +- AI `lastOutput` text markdown + +Current in-session search intentionally excludes: + +- System items +- Tool result text blocks +- Thinking/subagent/internal display items + +Primary source files: + +- `src/renderer/components/search/SearchBar.tsx` +- `src/renderer/store/slices/conversationSlice.ts` +- `src/renderer/components/chat/ChatHistory.tsx` +- `src/renderer/components/chat/searchHighlightUtils.ts` +- `src/shared/utils/markdownTextSearch.ts` +- `src/main/services/discovery/SessionSearcher.ts` + +## Core Data Model + +`SearchMatch` (renderer store) in `src/renderer/store/types.ts`: + +- `itemId`: chat group id (`user-*`, `ai-*`) +- `itemType`: `user | ai` +- `matchIndexInItem`: 0-based index inside one searchable item +- `globalIndex`: 0-based index across all matches +- `displayItemId`: optional (`lastOutput` for AI output) + +Important distinction: + +- `matchIndexInItem` is local to one item. +- `currentSearchIndex` is global position in the search result list. + +## Pipeline Overview + +### 1) Query input and initial match generation + +`SearchBar` updates the query with tab-scoped conversation data: + +- `setSearchQuery(query, conversation)` in `src/renderer/components/search/SearchBar.tsx` + +`setSearchQuery` in `src/renderer/store/slices/conversationSlice.ts`: + +- Scans conversation items +- Uses `findMarkdownSearchMatches` (shared parser logic) per searchable item +- Builds initial `searchMatches`, `searchResultCount`, `currentSearchIndex` + +### 2) Rendering highlights + +Search highlighting is rendered in markdown component trees through: + +- `createSearchContext(...)` in `src/renderer/components/chat/searchHighlightUtils.ts` +- `highlightSearchInChildren(...)` in `src/renderer/components/chat/searchHighlightUtils.ts` + +Each rendered highlight mark includes: + +- `data-search-item-id` +- `data-search-match-index` +- `data-search-result` (`current` or `match`) + +### 3) Canonicalization to rendered DOM (critical) + +`ChatHistory` collects rendered `` elements in DOM order and calls: + +- `syncSearchMatchesWithRendered(renderedMatches)` in `src/renderer/store/slices/conversationSlice.ts` + +Why this exists: + +- Real UI navigation must match visible marks exactly. +- Parser results can temporarily differ during render timing. +- DOM order is the final source of truth for nth navigation. + +Safety guard: + +- `ChatHistory` delays syncing when a transient empty mark snapshot appears, to avoid wiping results mid-render. + +### 4) Next/prev navigation and scrolling + +`nextSearchResult` / `previousSearchResult` in `src/renderer/store/slices/conversationSlice.ts`: + +- Move `currentSearchIndex` with wrap-around + +`ChatHistory` scroll effect: + +- First tries exact selector: + - `mark[data-search-item-id="..."][data-search-match-index="..."]` +- If missing, falls back to the global nth rendered mark (same `currentSearchIndex`) +- Final fallback walks text nodes under `[data-search-content]` roots + +## Shared Markdown Search Engine + +`src/shared/utils/markdownTextSearch.ts` is used by both renderer and main process: + +- `findMarkdownSearchMatches` +- `countMarkdownSearchMatches` +- `extractMarkdownPlainText` + +Design principle: + +- Search parser mirrors markdown render behavior (remark + gfm + HAST traversal) +- Matching is segment-based (no cross-node match) + +## Cross-Session Search (Command Palette / IPC) + +Main process search path: + +- IPC handler: `src/main/ipc/search.ts` +- Engine: `src/main/services/discovery/SessionSearcher.ts` + +`SessionSearcher` also uses shared markdown search utils, and returns: + +- `groupId` +- `itemType` +- `matchIndexInItem` +- `matchStartOffset` + +These are passed into tab navigation context so opening a search result can jump to the exact in-session match. + +## Invariants to Keep + +When changing markdown/search code, keep these invariants: + +1. Parser and renderer must agree on searchable text boundaries. +2. `matchIndexInItem` semantics must stay stable per item. +3. `currentSearchIndex` must represent the global nth visible match. +4. `searchResultCount` must reflect actual rendered match count after canonicalization. +5. Search source scope must be explicit (no accidental inclusion of hidden/internal text). + +## If You Add New Searchable Markdown Surfaces + +If you make a new markdown surface searchable: + +1. Ensure it uses search context + `highlightSearchInChildren`. +2. Ensure emitted marks include `data-search-item-id` and `data-search-match-index`. +3. Ensure the content is included in `setSearchQuery` source scanning. +4. Ensure parser collection logic in `src/shared/utils/markdownTextSearch.ts` still mirrors render behavior. +5. Add/adjust alignment tests. + +## Debug Playbook + +Enable debug logs: + +- `localStorage.setItem('search-debug', '1')` + +Useful logs: + +- `[search] query` / `[search] sample` from `setSearchQuery` +- `[search] sync-rendered` from DOM canonicalization +- `[search] next` / `[search] prev` navigation logs + +Quick checks when behavior is off: + +1. Compare `searchResultCount` vs number of rendered marks. +2. Verify `currentSearchIndex` increments exactly once per click. +3. Check whether exact mark selector exists for current match. +4. Confirm the active tab conversation is the same one used for `setSearchQuery`. +5. Confirm virtualization is disabled during active search. + +## Tests + +Main tests relevant to this logic: + +- `test/shared/utils/markdownTextSearch.test.ts` +- `test/shared/utils/markdownSearchRendererAlignment.test.ts` + +The alignment test ensures parser match indexes and rendered mark indexes stay identical across representative markdown cases. diff --git a/.claude/commands/ccc/navigation-scroll.md b/.claude/commands/ccc/navigation-scroll.md new file mode 100644 index 00000000..5eb3563b --- /dev/null +++ b/.claude/commands/ccc/navigation-scroll.md @@ -0,0 +1,232 @@ +--- +name: ccc:navigation-scroll +description: Navigation and scroll orchestration — tab navigation, error highlights, search scrolling, auto-scroll coordination, and common bug patterns. Use when working on useTabNavigationController, scroll restore, or navigation requests. +--- + +# Navigation & Scroll Orchestration + +How tab navigation (error highlights, search scrolling, auto-scroll) works end-to-end. + +## Architecture + +### Navigation Request Model (Nonce-Based) + +```typescript +// src/renderer/types/tabs.ts +interface TabNavigationRequest { + id: string; // crypto.randomUUID() — fresh nonce per click + kind: 'error' | 'search' | 'autoBottom'; + highlight: 'red' | 'yellow' | 'none'; + payload: ErrorNavigationPayload | SearchNavigationPayload | {}; + source: 'notification' | 'triggerPreview' | 'commandPalette' | 'sessionOpen'; +} + +// Stored on Tab: +interface Tab { + pendingNavigation?: TabNavigationRequest; // Set by enqueue, cleared by consume + lastConsumedNavigationId?: string; // Tracks last processed request +} +``` + +### Store Actions (tabSlice.ts) + +| Action | Purpose | +|--------|---------| +| `enqueueTabNavigation(tabId, request)` | Set `pendingNavigation` on a tab | +| `consumeTabNavigation(tabId, requestId)` | Clear `pendingNavigation`, record `lastConsumedNavigationId` | + +### Navigation Sources + +| Source | Slice | Creates | +|--------|-------|---------| +| Notification click / test trigger | `notificationSlice.navigateToError()` | `ErrorNavigationRequest` (red) | +| CommandPalette search result | `tabSlice.navigateToSession()` | `SearchNavigationRequest` (yellow) | + +### Controller Hook: `useTabNavigationController` + +**Location:** `src/renderer/hooks/useTabNavigationController.ts` + +Phase state machine: +``` +idle → pending → expanding → scrolling → highlighting → complete → idle +``` + +Key behaviors: +- **Active-tab-only:** Ignores `!isActiveTab` to prevent cross-tab races +- **Nonce dedup:** `activeRequestIdRef.current === pendingNavigation.id` prevents reprocessing +- **Failure debounce:** 500ms cooldown after failed navigation (`lastFailureAtRef`) +- **Abort support:** New navigation aborts in-progress one via `AbortController` +- **Highlight-first:** Highlight is set BEFORE scroll (best-effort scroll, guaranteed highlight) + +### Scroll Precedence (ChatHistory.tsx) + +Three scroll systems compete — navigation wins: + +| System | Guard | Priority | +|--------|-------|----------| +| Navigation scroll | Controller's `executeNavigation` | Highest | +| Scroll restore (tab switch) | `!shouldDisableAutoScroll` | Medium | +| Auto-scroll to bottom | `disabled: shouldDisableAutoScroll` | Lowest | + +`shouldDisableAutoScroll` is `true` during ANY navigation phase or when `pendingNavigation` exists. + +## Key Files + +| File | Role | +|------|------| +| `src/renderer/hooks/useTabNavigationController.ts` | Unified navigation controller | +| `src/renderer/hooks/navigation/utils.ts` | Shared helpers (scroll calc, element lookup, visibility) | +| `src/renderer/components/chat/ChatHistory.tsx` | Scroll restore + auto-scroll coordination | +| `src/renderer/store/slices/tabSlice.ts` | `enqueueTabNavigation`, `consumeTabNavigation`, `navigateToSession` | +| `src/renderer/store/slices/notificationSlice.ts` | `navigateToError` | +| `src/renderer/store/slices/sessionDetailSlice.ts` | `fetchSessionDetail` (sets `conversationLoading`) | +| `src/renderer/types/tabs.ts` | `TabNavigationRequest` types + factory helpers | + +## Common Bug Patterns + +### 1. Scroll Restore Overrides Navigation + +**Symptom:** Scrolls to target, then snaps back to top/previous position. + +**Root cause:** The scroll restore effect fires after `consumeTabNavigation` clears `pendingNavigation`. If the guard only checks `!pendingNavigation`, it triggers while navigation highlight is still active. + +**Fix pattern:** Guard scroll restore with `!shouldDisableAutoScroll` instead of `!pendingNavigation`. The controller's `shouldDisableAutoScroll` covers the FULL lifecycle (pending → complete), not just while `pendingNavigation` exists. + +**Additional:** Save scroll position when `shouldDisableAutoScroll` transitions true→false (navigation completed) to prevent stale `savedScrollTop` from being restored later. + +```typescript +// ChatHistory.tsx — scroll restore effect +useEffect(() => { + const wasDisabled = prevShouldDisableRef.current; + prevShouldDisableRef.current = shouldDisableAutoScroll; + // Navigation just completed — save current position, skip restore + if (wasDisabled && !shouldDisableAutoScroll && scrollContainerRef.current) { + saveScrollPosition(scrollContainerRef.current.scrollTop); + return; + } + if (isThisTabActive && savedScrollTop !== undefined && !conversationLoading && !shouldDisableAutoScroll) { + // ... restore logic + } +}, [isThisTabActive, savedScrollTop, conversationLoading, shouldDisableAutoScroll, saveScrollPosition]); +``` + +### 2. Redundant `fetchSessionDetail` Unmounts ChatHistory + +**Symptom:** Navigation doesn't scroll at all, or session "reloads" unnecessarily. + +**Root cause:** `navigateToSession` or `navigateToError` calls `fetchSessionDetail` even when the session is already loaded in an existing tab. This sets `conversationLoading: true`, causing ChatHistory to unmount (show loading spinner) and remount — losing scroll container and controller state. + +**Fix pattern:** Only call `fetchSessionDetail` for NEW tabs. For existing tabs, `setActiveTab` already handles the fetch when `sessionChanged` is true. + +```typescript +// tabSlice.ts — navigateToSession +if (existingTab) { + state.setActiveTab(existingTab.id); + // NO fetchSessionDetail — setActiveTab handles it +} else { + state.openTab({ ... }); + void state.fetchSessionDetail(projectId, sessionId); // Only for new tabs +} +``` + +### 3. Highlight Not Showing (Strict Post-Scroll Gates) + +**Symptom:** Scrolls to correct location but no red/yellow highlight ring appears. + +**Root cause:** `executeErrorNavigation` / `executeSearchNavigation` returns `false` after scroll due to strict gates: +- `userInterrupted` — any accidental wheel/touch event during smooth scroll +- `isElementVisibleInContainer` — element partially off-screen after centering (tall elements) +- Element not found within 600ms timeout + +When `success = false`, `executeNavigation` clears all highlight state (`setHighlightedGroupId(null)`). + +**Fix pattern:** Set highlight BEFORE scroll attempt. Make scroll best-effort. Always return `true` once target group is found. + +```typescript +// In executeErrorNavigation: +// 1. Find target group +// 2. Expand group +// 3. SET HIGHLIGHT HERE (before scroll) +setHighlightedGroupId(targetGroupId); +setIsSearchHighlight(false); +if (toolUseId) setCurrentToolUseId(toolUseId); +// 4. Best-effort scroll (don't gate highlight on scroll outcome) +// 5. Return true (highlight already visible) +``` + +### 4. Test Trigger Shows No Highlight + +**Symptom:** "Test trigger" creates an error with a timestamp that doesn't match any AI group. Navigation scrolls but nothing is highlighted. + +**Root cause:** Same as #3. The error timestamp doesn't match, so `findAIGroupByTimestamp` falls back to closest/last group. But post-scroll gates prevent the highlight from being applied. + +**Fix:** Same as #3 — highlight-first pattern. + +### 5. `conversationLoading` Race During Tab Switch + +**Symptom:** Navigation queued on tab, but when switching to that tab, loading state causes ChatHistory unmount. Navigation controller state is lost. + +**Root cause:** `fetchSessionDetail` immediately sets `conversationLoading: true`. ChatHistory returns ``, unmounting the controller hook. + +**Recovery:** The controller is designed to survive remount — when ChatHistory remounts with `pendingNavigation` still set and `conversationLoading: false`, the detection effect starts fresh navigation. BUT scroll restore can race with it (see #1). + +## Debugging Checklist + +When navigation isn't working: + +1. **Check `pendingNavigation` exists on the tab** — is `enqueueTabNavigation` called? +2. **Check `isActiveTab` is true** — controller ignores inactive tabs +3. **Check `conversationLoading`** — if true, controller waits in `pending` phase +4. **Check `conversation` exists** — if null, controller waits +5. **Check timestamp matching** — does `findAIGroupByTimestamp` find the right group? +6. **Check element refs** — are `aiGroupRefs` / `chatItemRefs` populated? +7. **Check `shouldDisableAutoScroll`** — is scroll restore racing with navigation? +8. **Check for double `fetchSessionDetail`** — is ChatHistory unmounting unnecessarily? +9. **Check `phase` progression** — is it stuck in `pending` or failing at `scrolling`? + +## Navigation Helper Functions (navigation/utils.ts) + +| Function | Purpose | +|----------|---------| +| `findAIGroupByTimestamp(items, timestamp)` | Find AI group containing/closest to timestamp | +| `findChatItemByTimestamp(items, timestamp)` | Find any chat item by timestamp | +| `findAIGroupBySubagentId(items, subagentId)` | Find AI group by subagent ID | +| `calculateCenteredScrollTop(element, container, offset)` | Calculate scroll position to center element | +| `waitForElementStability(element, timeout, stableFrames)` | Wait for element size to stop changing | +| `waitForScrollEnd(container, timeout)` | Wait for smooth scroll to finish | +| `isElementVisibleInContainer(element, container, offset)` | Check if element is in viewport | +| `findCurrentSearchResultInContainer(container)` | Find `[data-search-result="current"]` element | + +## Factory Helpers (tabs.ts) + +```typescript +createErrorNavigationRequest({ errorId, errorTimestamp, toolUseId, lineNumber }) +createSearchNavigationRequest({ query, messageTimestamp, matchedText }) +isErrorPayload(request) // type guard +isSearchPayload(request) // type guard +``` + +## Tests + +Related test files: +- `test/renderer/store/tabSlice.test.ts` — `enqueueTabNavigation` / `consumeTabNavigation` +- `test/renderer/store/notificationSlice.test.ts` — `navigateToError` behavior +- `test/renderer/hooks/navigationUtils.test.ts` — Navigation utility functions +- `test/renderer/hooks/useSearchContextNavigation.test.ts` — Search result finding + +Test patterns: +```typescript +// Mock crypto for predictable nonces +vi.stubGlobal('crypto', { randomUUID: () => `test-uuid-${++counter}` }); + +// Verify navigation request shape +expect(tab.pendingNavigation?.kind).toBe('error'); +expect(tab.pendingNavigation?.highlight).toBe('red'); + +// Verify nonce uniqueness on repeated clicks +store.getState().navigateToError(error); +const firstId = store.getState().openTabs[0].pendingNavigation?.id; +store.getState().navigateToError(error); +const secondId = store.getState().openTabs[0].pendingNavigation?.id; +expect(firstId).not.toBe(secondId); +``` diff --git a/.claude/rules/react.md b/.claude/rules/react.md new file mode 100644 index 00000000..ba893c65 --- /dev/null +++ b/.claude/rules/react.md @@ -0,0 +1,50 @@ +--- +globs: ["src/renderer/**/*.tsx"] +--- + +# React Conventions + +## Component Structure +- Components in `src/renderer/components/` organized by feature +- One component per file, PascalCase naming +- Colocate related hooks and utilities + +## State Management (Zustand) +```typescript +// Slices pattern +projects: Project[] +selectedProjectId: string | null +projectsLoading: boolean +projectsError: string | null +``` + +Each domain slice includes: +- Data array or object +- Selected/active item ID +- Loading state +- Error state + +## Hooks +- Custom hooks in `src/renderer/hooks/` +- Prefix with `use`: `useAutoScrollBottom`, `useTheme` +- Keep hooks focused and composable + +## Component Organization +``` +components/ +├── chat/ # Chat display, items, viewers, SessionContextPanel +├── common/ # Shared components (badges, token display) +├── dashboard/ # Dashboard views +├── layout/ # Layout components (headers, shells) +├── notifications/ # Notification panels and badges +├── search/ # Search UI and results +├── settings/ # Settings pages and controls +│ ├── components/ # Reusable setting controls +│ ├── hooks/ # Settings-specific hooks +│ ├── sections/ # Setting sections +│ └── NotificationTriggerSettings/ # Trigger config UI +└── sidebar/ # Sidebar navigation +``` + +## Contexts +- `contexts/TabUIContext.tsx` - Per-tab UI state isolation diff --git a/.claude/rules/tailwind.md b/.claude/rules/tailwind.md new file mode 100644 index 00000000..387a353c --- /dev/null +++ b/.claude/rules/tailwind.md @@ -0,0 +1,61 @@ +--- +globs: ["**/*.css", "src/renderer/**/*.tsx"] +--- + +# Tailwind CSS Conventions + +## Theme Architecture +Uses CSS custom properties for theme-aware colors defined in `src/renderer/index.css`. + +### Core Surface Colors +```css +--color-surface: #141416 /* Main background */ +--color-surface-raised: #27272a /* Elevated surfaces */ +--color-surface-overlay: #27272a /* Overlays/modals */ +--color-surface-sidebar: #0f0f11 /* Sidebar background */ +``` + +### Border Colors +```css +--color-border: rgba(255, 255, 255, 0.05) +--color-border-subtle: rgba(255, 255, 255, 0.05) +--color-border-emphasis: rgba(255, 255, 255, 0.1) +``` + +### Text Colors +```css +--color-text: #fafafa /* Primary text */ +--color-text-secondary: #a1a1aa /* Secondary text */ +--color-text-muted: #71717a /* Muted text */ +``` + +## Tailwind Usage +Use theme-aware classes that reference CSS variables: +```tsx +// Preferred - uses CSS variables for theme support +
+
+ +// Also available via claude-dark namespace +
+``` + +## Additional CSS Variable Categories +- Chat bubbles: `--chat-user-*`, `--chat-ai-*`, `--chat-system-*` +- Code blocks: `--code-*`, `--syntax-*`, `--inline-code-*` +- Diff viewer: `--diff-added-*`, `--diff-removed-*` +- Tool blocks: `--tool-call-*`, `--tool-result-*` +- Tool items: `--tool-item-name`, `--tool-item-summary`, `--tool-item-muted`, `--tool-item-hover-bg` +- Badges: `--badge-*`, `--tag-*` +- Search: `--highlight-*` +- Scrollbar: `--scrollbar-thumb`, `--scrollbar-thumb-hover`, `--scrollbar-thumb-active` +- Prose/Markdown: `--prose-heading`, `--prose-body`, `--prose-link`, `--prose-code-*`, `--prose-pre-*` +- Thinking blocks: `--thinking-bg`, `--thinking-border`, `--thinking-text`, `--thinking-content-*` +- Output blocks: `--output-bg`, `--output-border`, `--output-text`, `--output-content-border` +- Cards/Subagents: `--card-bg`, `--card-border`, `--card-header-*`, `--card-icon-muted`, `--card-separator` +- Highlights: `--skill-highlight-*`, `--path-highlight-*` +- UI elements: `--interruption-*`, `--warning-*`, `--plan-exit-*`, `--error-highlight-*`, `--kbd-*`, `--context-btn-*` + +## Dark/Light Theme +Both themes supported via `:root` and `:root.light` in index.css. +Toggle via `useTheme` hook which adds/removes `light` class on root. diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 00000000..ed92f71b --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,76 @@ +--- +globs: ["test/**/*", "**/*.test.ts", "**/*.spec.ts"] +--- + +# Testing Conventions + +## Test Framework +Uses Vitest with `happy-dom` environment. Config in `vitest.config.ts`. + +## Test Commands +```bash +pnpm test # Run all vitest tests +pnpm test:watch # Watch mode +pnpm test:coverage # Coverage report +pnpm test:coverage:critical # Critical path coverage +pnpm test:chunks # Chunk building tests +pnpm test:semantic # Semantic step extraction +pnpm test:noise # Noise filtering tests +pnpm test:task-filtering # Task tool filtering +``` + +## Test Structure +``` +test/ +├── main/ +│ ├── ipc/ # IPC handler tests +│ │ ├── configValidation.test.ts +│ │ └── guards.test.ts +│ ├── services/ # Service tests +│ │ ├── analysis/ (ChunkBuilder) +│ │ ├── discovery/ (ProjectPathResolver, SessionSearcher) +│ │ ├── infrastructure/ (FileWatcher) +│ │ └── parsing/ (MessageClassifier, SessionParser) +│ └── utils/ # Main process utilities +│ ├── jsonl.test.ts +│ ├── pathDecoder.test.ts +│ ├── pathValidation.test.ts +│ ├── regexValidation.test.ts +│ └── tokenizer.test.ts +├── renderer/ +│ ├── hooks/ # Hook tests +│ │ ├── navigationUtils.test.ts +│ │ ├── useAutoScrollBottom.test.ts +│ │ ├── useSearchContextNavigation.test.ts +│ │ └── useVisibleAIGroup.test.ts +│ ├── store/ # Zustand store slices +│ │ ├── notificationSlice.test.ts +│ │ ├── paneSlice.test.ts +│ │ ├── pathResolution.test.ts +│ │ ├── sessionSlice.test.ts +│ │ ├── tabSlice.test.ts +│ │ └── tabUISlice.test.ts +│ └── utils/ # Renderer utilities +│ ├── claudeMdTracker.test.ts +│ ├── dateGrouping.test.ts +│ ├── formatters.test.ts +│ └── pathUtils.test.ts +├── shared/ +│ └── utils/ # Shared utilities +│ ├── markdownSearchRendererAlignment.test.ts +│ ├── markdownTextSearch.test.ts +│ ├── modelParser.test.ts +│ └── tokenFormatting.test.ts +├── mocks/ # Test fixtures and mocks +└── setup.ts # Test setup/config +``` + +## Files to Test After Changes +- `services/analysis/ChunkBuilder.ts` - Chunk building logic +- `services/parsing/SessionParser.ts` - JSONL parsing +- `services/parsing/MessageClassifier.ts` - Message classification +- Store slices in `src/renderer/store/slices/` +- Utility functions in `*/utils/` + +## Test Data +Test fixtures use real JSONL session data from `~/.claude/projects/`. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..bf6eae03 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,38 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "jq -r '.tool_input.file_path // empty' | { read f; for p in pnpm-lock.yaml .env dist/ dist-electron/ node_modules/; do if [ -n \"$f\" ] && echo \"$f\" | grep -q \"$p\"; then echo \"Blocked: $f matches protected pattern '$p'\" >&2; exit 2; fi; done; exit 0; }" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Edit|Write|NotebookEdit", + "hooks": [ + { + "type": "command", + "command": "jq -r '.tool_input.file_path // .tool_input.notebook_path // empty' | { read file_path; if [ -n \"$file_path\" ] && echo \"$file_path\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then pnpm eslint --fix \"$file_path\" 2>/dev/null || true; fi; if [ -n \"$file_path\" ] && echo \"$file_path\" | grep -qE '\\.(ts|tsx|js|jsx|json|css)$'; then pnpm prettier --write \"$file_path\" 2>/dev/null || true; fi; }", + "timeout": 30 + } + ] + } + ], + "SessionStart": [ + { + "matcher": "compact", + "hooks": [ + { + "type": "command", + "command": "echo '[Post-compaction context reminder]\n- Package manager: pnpm only (not npm/yarn)\n- Path aliases: @main/*, @renderer/*, @shared/*, @preload/*\n- Electron 3-process: main/ (Node), preload/ (bridge), renderer/ (React), shared/ (cross-process)\n- isMeta: false = real user message, true = internal/system message\n- Chunk types: UserChunk, AIChunk, SystemChunk, CompactChunk\n- State: Zustand slices pattern (data, selectedId, loading, error)\n- Styling: Tailwind with CSS variables (bg-surface, text-text, border-border)\n- Naming: PascalCase services/components, camelCase utils, UPPER_SNAKE constants\n- Barrel exports: import from domain index.ts\n- New IPC: channel in preload/constants → handler in main/ipc → method in preload/index.ts\n- PostToolUse hook auto-runs eslint+prettier on edited files\n- PreToolUse hook blocks edits to pnpm-lock.yaml, .env, dist/, node_modules/'" + } + ] + } + ] + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..d77093c2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.{ts,tsx,js,jsx,json,md,yml,yaml}] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5cc3daba --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +dist-electron/ +out/ +release/ +coverage/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# TypeScript +*.tsbuildinfo + +.pnpm-store/ +package-lock.json +notification_example/ +temp/ +.claude/*.local.json +.claude/agent-memory/* + + +eslint-fix/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..209e3ef4 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..316035f4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,25 @@ +# Build outputs +dist/ +dist-electron/ +build/ +out/ + +# Dependencies +node_modules/ +pnpm-lock.yaml + +# Generated files +*.min.js +*.min.css + +# Config files that shouldn't be formatted +*.config.js +*.config.ts + +# IDE +.idea/ +.vscode/ + +# Misc +.DS_Store +*.log diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..c96ec48e --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100, + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf", + "jsxSingleQuote": false, + "bracketSameLine": false, + "plugins": ["prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": ["*.json", "*.jsonc"], + "options": { + "trailingComma": "none" + } + }, + { + "files": "*.md", + "options": { + "proseWrap": "preserve" + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..0e741060 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on Keep a Changelog and this project follows Semantic Versioning. + +## [Unreleased] + +### Added +- Strict IPC input validation guards for project/session/subagent/search limits. +- `get-waterfall-data` IPC endpoint implementation. +- Cross-platform path normalization in renderer path resolvers. +- `onTodoChange` preload API event bridge. +- CI workflow for macOS/Windows (typecheck, lint, test, build). +- Release workflow for signed package builds. +- Open-source governance docs (`LICENSE`, `CONTRIBUTING`, `CODE_OF_CONDUCT`, `SECURITY`). + +### Changed +- `readMentionedFile` preload API signature now requires `projectRoot`. +- Notification update event contract standardized to `{ total, unreadCount }`. +- Session pagination uses cached displayable-content detection for performance. +- File watcher error detection optimized for append-only updates. + +### Fixed +- Lint violations in navigation and markdown/subagent UI components. +- Test mock drift causing runtime errors in test output. +- Multiple Windows path handling edge cases. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..ba799cf4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,158 @@ +# Claude Code Context + +Electron app that visualizes Claude Code session execution + +## Tech Stack +Electron 28.x, React 18.x, TypeScript 5.x, Tailwind CSS 3.x, Zustand 4.x + +## Commands +Always use pnpm (not npm/yarn) for this project. + +- `pnpm install` - Install dependencies +- `pnpm dev` - Dev server with hot reload +- `pnpm build` - Production build +- `pnpm typecheck` - Type checking +- `pnpm lint:fix` - Lint and auto-fix +- `pnpm format` - Format code +- `pnpm test` - Run all vitest tests +- `pnpm test:watch` - Watch mode +- `pnpm test:coverage` - Coverage report +- `pnpm test:coverage:critical` - Critical path coverage +- `pnpm test:chunks` - Chunk building tests +- `pnpm test:semantic` - Semantic step extraction tests +- `pnpm test:noise` - Noise filtering tests +- `pnpm test:task-filtering` - Task tool filtering tests + +## Path Aliases +Use path aliases for imports: +- `@main/*` → `src/main/*` +- `@renderer/*` → `src/renderer/*` +- `@shared/*` → `src/shared/*` +- `@preload/*` → `src/preload/*` + +## Data Sources +~/.claude/projects/{encoded-path}/*.jsonl - Session files +~/.claude/todos/{sessionId}.json - Todo data + +Path encoding: `/Users/name/project` → `-Users-name-project` + +## Critical Concepts + +### isMeta Flag +- `isMeta: false` = Real user message (creates new chunks) +- `isMeta: true` = Internal message (tool results, system-generated) + +### Chunk Structure +Independent chunk types for timeline visualization: +- **UserChunk**: Single user message with metrics +- **AIChunk**: All assistant responses with tool executions and spawned subagents +- **SystemChunk**: Command output/system messages +- **CompactChunk**: System metadata/structural messages + +Each chunk has: timestamp, duration, metrics (tokens, cost, tools) + +### Task/Subagent Filtering +Task tool_use blocks are filtered when subagent exists +Keep orphaned Task calls (no matching subagent) for visibility. + +### Agent Teams +Claude Code's "Orchestrate Teams" feature: multiple sessions coordinate as a team. +- **Process.team?** `{ teamName, memberName, memberColor }` — enriched by SubagentResolver from Task call inputs and `teammate_spawned` tool results +- **Teammate messages** arrive as `content` in user messages (isMeta: false). Detected by `isParsedTeammateMessage()` — excluded from UserChunks, rendered as `TeammateMessageItem` cards +- **Session ongoing detection** treats `SendMessage` shutdown_response (approve: true) and its tool_result as ending events, not ongoing activity +- **Display summary** counts distinct teammates (by name) separately from regular subagents +- **Team tools**: TeamCreate, TaskCreate, TaskUpdate, TaskList, TaskGet, SendMessage, TeamDelete — have readable summaries in `toolSummaryHelpers.ts` + +### Visible Context Tracking +Tracks what consumes tokens in Claude's context window across 6 categories (discriminated union on `category` field): + +| Category | Type | Source | +|----------|------|--------| +| `claude-md` | `ClaudeMdContextInjection` | CLAUDE.md files (global, project, directory) | +| `mentioned-file` | `MentionedFileInjection` | User @-mentioned files | +| `tool-output` | `ToolOutputInjection` | Tool execution results (Read, Bash, etc.) | +| `thinking-text` | `ThinkingTextInjection` | Extended thinking + text output tokens | +| `team-coordination` | `TeamCoordinationInjection` | Team tools (SendMessage, TaskCreate, etc.) | +| `user-message` | `UserMessageInjection` | User prompt text per turn | + +- **Types**: `src/renderer/types/contextInjection.ts` — `ContextInjection` union, `ContextStats`, `TokensByCategory` +- **Tracker**: `src/renderer/utils/contextTracker.ts` — `computeContextStats()`, `processSessionContextWithPhases()` +- **Context Phases**: Compaction events reset accumulated injections, tracked via `ContextPhaseInfo` +- **Display surfaces**: `ContextBadge` (per-turn popover), `TokenUsageDisplay` (hover breakdown), `SessionContextPanel` (full panel) + +## Error Handling +- Main: try/catch, console.error, return safe defaults +- Renderer: error state in Zustand store +- IPC: parameter validation, graceful degradation + +## Performance +- LRU Cache: Avoid re-parsing large JSONL files +- Streaming JSONL: Line-by-line processing +- Virtual Scrolling: For large session/message lists +- Debounced File Watching: 100ms debounce + +## Troubleshooting + +### Build Issues +```bash +rm -rf dist dist-electron node_modules +pnpm install +pnpm build +``` + +### Type Errors +```bash +pnpm typecheck +``` + +### Test Failures +Check for changes in message parsing or chunk building logic. + +## TypeScript Conventions + +### Naming +| Category | Convention | Example | +|----------|------------|---------| +| Services/Components | PascalCase | `ProjectScanner.ts` | +| Utilities | camelCase | `pathDecoder.ts` | +| Constants | UPPER_SNAKE_CASE | `PARALLEL_WINDOW_MS` | +| Type Guards | isXxx | `isRealUserMessage()` | +| Builders | buildXxx | `buildChunks()` | +| Getters | getXxx | `getResponses()` | + +### Type Guards +```typescript +// Message type guards (src/main/types/messages.ts) +isParsedRealUserMessage(msg) // isMeta: false, string content +isParsedInternalUserMessage(msg) // isMeta: true, array content +isAssistantMessage(msg) // type: "assistant" + +// Chunk type guards +isUserChunk(chunk) // type: "user" +isAIChunk(chunk) // type: "ai" +isSystemChunk(chunk) // type: "system" +isCompactChunk(chunk) // type: "compact" + +// Context injection type guards (component-scoped in ContextBadge.tsx, not exported) +isClaudeMdInjection(inj) // category: "claude-md" +isMentionedFileInjection(inj) // category: "mentioned-file" +isToolOutputInjection(inj) // category: "tool-output" +isThinkingTextInjection(inj) // category: "thinking-text" +isTeamCoordinationInjection(inj) // category: "team-coordination" +isUserMessageInjection(inj) // category: "user-message" +``` + +### Barrel Exports +`src/main/services/` and its domain subdirectories have barrel exports via index.ts: +```typescript +// Preferred +import { ChunkBuilder, ProjectScanner } from './services'; +// Also valid +import { ChunkBuilder } from './services/analysis'; +``` +Note: renderer utils/hooks/types do NOT have barrel exports — import directly from files. + +### Import Order +1. External packages +2. Path aliases (@main, @renderer, @shared) +3. Relative imports diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..81ecb971 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,19 @@ +# Code of Conduct + +This project follows the Contributor Covenant Code of Conduct. + +## Our Standards +- Be respectful and constructive. +- Assume good intent and discuss ideas, not people. +- Give actionable feedback and accept feedback gracefully. + +## Unacceptable Behavior +- Harassment, discrimination, or personal attacks. +- Trolling, insulting language, or sustained disruption. +- Publishing private information without explicit permission. + +## Enforcement +Project maintainers are responsible for clarifying and enforcing this code of conduct and may take corrective action for unacceptable behavior. + +## Reporting +Please report incidents privately to the maintainers through the security/contact channel listed in `SECURITY.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..cb132152 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing + +Thanks for contributing to Claude Code Context. + +## Prerequisites +- Node.js 20+ +- pnpm 10+ +- macOS or Windows + +## Setup +```bash +pnpm install +pnpm dev +``` + +## Quality Gates +Before opening a PR, run: +```bash +pnpm typecheck +pnpm lint +pnpm test +pnpm build +``` + +## Pull Request Guidelines +- Keep changes focused and small. +- Add/adjust tests for behavior changes. +- Update docs when changing public behavior or setup. +- Use clear PR titles and include a short validation checklist. + +## Commit Style +- Prefer conventional commits (`feat:`, `fix:`, `chore:`, `docs:`). +- Include rationale in commit body for non-trivial changes. + +## Reporting Bugs +Please include: +- OS version +- app version / commit hash +- repro steps +- expected vs actual behavior +- logs/screenshots when possible diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..578aca4e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Claude Code Context contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2f67f067 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# Claude Code Context + +Desktop app for exploring Claude Code session context usage. + +It helps you inspect session timelines, search across sessions, debug context injections (`CLAUDE.md`, mentioned files, tool outputs), and configure notification triggers. + +## Features +- Repository/worktree-aware project grouping +- Session search with context snippets +- Structured conversation/chunk parsing from Claude JSONL logs +- Context usage inspection (CLAUDE.md + mentioned files + tool output) +- Native notifications with configurable trigger rules +- Real-time updates from Claude session/todo file changes + +## Tech Stack +- Electron + electron-vite +- React + TypeScript + Zustand +- Tailwind CSS +- Vitest + ESLint + +## Requirements +- Node.js 20+ +- pnpm 10+ +- macOS or Windows + +## Getting Started +```bash +pnpm install +pnpm dev +``` + +## Data Source +The app reads Claude local data from: +- `~/.claude/projects/` +- `~/.claude/todos/` + +## Scripts +```bash +pnpm dev # Run app in development +pnpm typecheck # TypeScript checks +pnpm lint # ESLint (no auto-fix) +pnpm test # Unit tests +pnpm build # Electron/Vite production build +pnpm check # Full local quality gate +pnpm dist:mac # Package macOS app (electron-builder) +pnpm dist:win # Package Windows app (electron-builder) +pnpm dist # Package both targets +``` + +## Packaging and Release +- Packaging is configured with `electron-builder.yml`. +- CI workflow (`.github/workflows/ci.yml`) runs typecheck/lint/test/build on macOS + Windows. +- Release workflow (`.github/workflows/release.yml`) builds distributables on tags (`v*`). +- Code signing/notarization uses GitHub secrets: + - `CSC_LINK`, `CSC_KEY_PASSWORD` + - `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID` (macOS notarization) + +## Security Notes +- IPC handlers validate IDs/inputs and apply strict path containment checks. +- File reads for context injection are constrained to project root and `~/.claude`. +- Sensitive credential path patterns are blocked. + +## Contributing +See: +- `CONTRIBUTING.md` +- `CODE_OF_CONDUCT.md` +- `SECURITY.md` + +## License +MIT (`LICENSE`) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..ce3dc57e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +## Supported Versions +Only the latest release is supported with security fixes. + +## Reporting a Vulnerability +Please report vulnerabilities privately and do not open public issues for undisclosed security problems. + +Include: +- affected version/commit +- vulnerability description +- impact assessment +- reproduction steps or proof of concept + +If you do not have a private contact path yet, open a minimal GitHub issue asking for a secure reporting channel without disclosing technical details. + +## Disclosure Process +- We will acknowledge reports as quickly as possible. +- We will validate, triage severity, and prepare a fix. +- We will coordinate a release and publish advisories when appropriate. diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 00000000..cf2aae91 --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,41 @@ +appId: com.claudecode.context +productName: Claude Code Context + +directories: + output: release + +files: + - out/renderer/** + - dist-electron/** + - package.json + +asar: true + +extraMetadata: + main: dist-electron/main/index.js + +mac: + category: public.app-category.developer-tools + target: + - dmg + - zip + hardenedRuntime: true + gatekeeperAssess: false + icon: resources/icons/mac/icon.icns + +dmg: + sign: false + +win: + target: + - nsis + icon: resources/icons/win/icon.ico + +nsis: + oneClick: false + perMachine: false + allowToChangeInstallationDirectory: true + +publish: + - provider: github + releaseType: draft diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 00000000..990ddc3a --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,62 @@ +import { defineConfig, externalizeDepsPlugin } from 'electron-vite' +import react from '@vitejs/plugin-react' +import { resolve } from 'path' + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + resolve: { + alias: { + '@main': resolve(__dirname, 'src/main'), + '@shared': resolve(__dirname, 'src/shared') + } + }, + build: { + outDir: 'dist-electron/main', + rollupOptions: { + input: { + index: resolve(__dirname, 'src/main/index.ts') + } + } + } + }, + preload: { + plugins: [externalizeDepsPlugin()], + resolve: { + alias: { + '@preload': resolve(__dirname, 'src/preload'), + '@shared': resolve(__dirname, 'src/shared'), + '@main': resolve(__dirname, 'src/main') + } + }, + build: { + outDir: 'dist-electron/preload', + rollupOptions: { + input: { + index: resolve(__dirname, 'src/preload/index.ts') + }, + output: { + format: 'cjs', + entryFileNames: '[name].js' + } + } + } + }, + renderer: { + resolve: { + alias: { + '@renderer': resolve(__dirname, 'src/renderer'), + '@shared': resolve(__dirname, 'src/shared'), + '@main': resolve(__dirname, 'src/main') + } + }, + plugins: [react()], + build: { + rollupOptions: { + input: { + index: resolve(__dirname, 'src/renderer/index.html') + } + } + } + } +}) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..d5938fba --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,591 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import jsxA11y from 'eslint-plugin-jsx-a11y'; +import tailwindcss from 'eslint-plugin-tailwindcss'; +import sonarjs from 'eslint-plugin-sonarjs'; +import simpleImportSort from 'eslint-plugin-simple-import-sort'; +import importPlugin from 'eslint-plugin-import'; +import security from 'eslint-plugin-security'; +import boundaries from 'eslint-plugin-boundaries'; +import eslintComments from '@eslint-community/eslint-plugin-eslint-comments'; +import eslintConfigPrettier from 'eslint-config-prettier/flat'; +import globals from 'globals'; + +export default defineConfig([ + // Global ignores + globalIgnores([ + 'dist/**', + 'dist-electron/**', + 'build/**', + 'node_modules/**', + '*.config.js', + '*.config.cjs', + '*.config.ts', + 'out/**', + ]), + + // Base ESLint recommended rules + js.configs.recommended, + + // TypeScript-ESLint recommended with type checking + stylistic + // Using recommended (not strict) for a balanced approach + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + + // SonarJS - Code quality and bug detection rules + sonarjs.configs.recommended, + + // Security - Catch common security mistakes in AI-generated code + security.configs.recommended, + + // TypeScript parser options for type-aware linting + { + name: 'typescript-parser-options', + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + + // Import plugin configuration - Main/Preload (uses tsconfig.node.json) + { + name: 'import-plugin-main', + files: ['src/main/**/*.ts', 'src/preload/**/*.ts'], + plugins: { + import: importPlugin, + }, + settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.node.json', + }, + }, + }, + rules: { + 'import/no-cycle': ['error', { maxDepth: 3, ignoreExternal: true }], + 'import/no-unresolved': 'error', + 'import/no-default-export': 'warn', + }, + }, + + // Import plugin configuration - Renderer (uses tsconfig.json) + { + name: 'import-plugin-renderer', + files: ['src/renderer/**/*.{ts,tsx}'], + plugins: { + import: importPlugin, + }, + settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json', + }, + }, + }, + rules: { + 'import/no-cycle': ['error', { maxDepth: 3, ignoreExternal: true }], + 'import/no-unresolved': 'error', + 'import/no-default-export': 'warn', + }, + }, + + // Module boundaries - Enforce Electron three-process architecture + { + name: 'module-boundaries', + files: ['src/**/*.{js,jsx,ts,tsx}'], + plugins: { + boundaries: boundaries, + }, + settings: { + 'boundaries/elements': [ + { type: 'main', pattern: 'src/main/**', mode: 'folder' }, + { type: 'preload', pattern: 'src/preload/**', mode: 'folder' }, + { type: 'renderer', pattern: 'src/renderer/**', mode: 'folder' }, + { type: 'shared', pattern: 'src/shared/**', mode: 'folder' }, + ], + 'boundaries/ignore': ['**/*.test.ts', '**/*.spec.ts'], + }, + rules: { + // Enforce strict module boundaries for Electron architecture + 'boundaries/element-types': [ + 'error', + { + default: 'disallow', + rules: [ + // Renderer can only import from renderer and shared + { from: 'renderer', allow: ['renderer', 'shared'] }, + // Main process can only import from main and shared + { from: 'main', allow: ['main', 'shared'] }, + // Preload can only import from preload and shared + { from: 'preload', allow: ['preload', 'shared'] }, + // Shared can import from shared and main (for type re-exports) + { from: 'shared', allow: ['shared', 'main'] }, + ], + }, + ], + // Prevent importing private modules + 'boundaries/no-private': 'error', + }, + }, + + // ESLint Comments + { + name: 'eslint-comments', + files: ['src/**/*.{js,jsx,ts,tsx}'], + plugins: { + '@eslint-community/eslint-comments': eslintComments, + }, + rules: { + // Prevents blanket-disabling rules + '@eslint-community/eslint-comments/no-unlimited-disable': 'error', + // Require description for disable comments + '@eslint-community/eslint-comments/require-description': [ + 'error', + { ignore: [] }, + ], + // Re-enable rules after disabling + '@eslint-community/eslint-comments/disable-enable-pair': 'error', + // No duplicate disable comments + '@eslint-community/eslint-comments/no-duplicate-disable': 'error', + // Unused disable comments + '@eslint-community/eslint-comments/no-unused-disable': 'error', + }, + }, + + // Import sorting for all JS/TS files + { + name: 'import-sorting', + files: ['src/**/*.{js,jsx,ts,tsx}'], + plugins: { + 'simple-import-sort': simpleImportSort, + }, + rules: { + 'simple-import-sort/imports': [ + 'error', + { + groups: [ + // Side effect imports (e.g., import './styles.css') + ['^\\u0000'], + // Node.js builtins (fs, path, etc.) + ['^node:'], + // React and related packages + ['^react', '^react-dom'], + // External packages from node_modules + ['^@?\\w'], + // Internal aliases (@/ paths) + ['^@/'], + // Parent imports (../) + ['^\\.\\.(?!/?$)', '^\\.\\./?$'], + // Same-folder imports (./) + ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], + // Type imports + ['^.+\\u0000$'], + ], + }, + ], + 'simple-import-sort/exports': 'error', + }, + }, + + // Main process (Electron Node.js) + { + name: 'electron-main', + files: ['src/main/**/*.ts'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + // Allow console in main process for logging + 'no-console': 'off', + }, + }, + + // Preload script (Electron bridge) + { + name: 'electron-preload', + files: ['src/preload/**/*.ts'], + languageOptions: { + globals: { + ...globals.node, + ...globals.browser, + }, + }, + }, + + // Renderer process (React + A11y + Tailwind) + { + name: 'renderer-react', + files: ['src/renderer/**/*.{ts,tsx}'], + languageOptions: { + globals: { + ...globals.browser, + }, + }, + plugins: { + react: reactPlugin, + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + 'jsx-a11y': jsxA11y, + tailwindcss: tailwindcss, + }, + settings: { + react: { + version: 'detect', + }, + tailwindcss: { + // Tailwind config path (relative to cwd) + config: 'tailwind.config.js', + // Allow custom classnames (e.g., from CSS modules) + callees: ['classnames', 'clsx', 'cn'], + }, + }, + rules: { + // React recommended rules + ...reactPlugin.configs.recommended.rules, + // JSX runtime (React 17+) - no need to import React + ...reactPlugin.configs['jsx-runtime'].rules, + // React Hooks rules + ...reactHooks.configs.recommended.rules, + // Accessibility rules (recommended) + ...jsxA11y.configs.recommended.rules, + // Tailwind CSS rules + ...tailwindcss.configs.recommended.rules, + + // React Refresh for HMR + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + // Disable prop-types since we use TypeScript + 'react/prop-types': 'off', + + // A11y rule adjustments for this project + // Allow click handlers on divs when keyboard handlers also present + 'jsx-a11y/click-events-have-key-events': 'warn', + 'jsx-a11y/no-static-element-interactions': 'warn', + // Allow autofocus for search inputs in desktop apps + 'jsx-a11y/no-autofocus': 'off', + + // Tailwind CSS rule adjustments + // Warn on class order (Prettier plugin handles sorting) + 'tailwindcss/classnames-order': 'off', // Prettier plugin handles this + // Warn on conflicting classes + 'tailwindcss/no-contradicting-classname': 'error', + // Warn on custom classnames that don't exist + 'tailwindcss/no-custom-classname': 'warn', + + // === React-Specific Rules === + // Consistent component definition + 'react/function-component-definition': [ + 'error', + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function', + }, + ], + + // Strengthen exhaustive-deps + 'react-hooks/exhaustive-deps': 'error', + + // Prevent prop spreading + 'react/jsx-props-no-spreading': [ + 'warn', + { + exceptions: ['input', 'button', 'Input', 'Button', 'textarea', 'select'], + }, + ], + + // Ensure key props + 'react/jsx-key': [ + 'error', + { + checkFragmentShorthand: true, + checkKeyMustBeforeSpread: true, + }, + ], + + // Prevent unnecessary fragments + 'react/jsx-no-useless-fragment': 'warn', + + // Self-closing components for consistency + 'react/self-closing-comp': [ + 'error', + { + component: true, + html: true, + }, + ], + }, + }, + + // Test files + { + name: 'test-files', + files: ['test/**/*.ts', '**/*.test.ts', '**/*.spec.ts'], + languageOptions: { + globals: { + ...globals.node, + }, + parserOptions: { + projectService: false, + project: './tsconfig.test.json', + }, + }, + rules: { + // Relax TypeScript strict rules for tests + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/unbound-method': 'off', + + // Relax function/export rules for tests + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + + // Relax naming conventions for tests (allow describe, it, expect patterns) + '@typescript-eslint/naming-convention': 'off', + + // Allow magic numbers in tests + 'sonarjs/no-hardcoded-ip': 'off', + + // Allow floating promises in tests (common with async test helpers) + '@typescript-eslint/no-floating-promises': 'off', + }, + }, + + // Custom rule overrides for all TypeScript files + { + name: 'custom-rules', + files: ['src/**/*.{ts,tsx}'], + rules: { + // === Core JavaScript rules === + 'prefer-const': 'error', + 'no-var': 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], + + // === TypeScript Import/Export rules === + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + fixStyle: 'inline-type-imports', + }, + ], + '@typescript-eslint/consistent-type-exports': [ + 'error', + { fixMixedExportsWithInlineTypeSpecifier: true }, + ], + + // === Unused variables === + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + + // === Relaxed strict rules for practical use === + // Allow empty functions (useful for callbacks and stubs) + '@typescript-eslint/no-empty-function': 'off', + + // Allow numbers/booleans in template literals (common pattern) + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowNumber: true, + allowBoolean: true, + allowNullish: false, + }, + ], + + // Allow async functions without await (IPC handlers often need this) + '@typescript-eslint/require-await': 'off', + + // Allow floating promises in event handlers (common in Electron) + '@typescript-eslint/no-floating-promises': [ + 'error', + { + ignoreVoid: true, + ignoreIIFE: true, + }, + ], + + // Allow promises in places that don't expect them (event handlers) + '@typescript-eslint/no-misused-promises': [ + 'error', + { + checksVoidReturn: { + attributes: false, + arguments: false, + }, + }, + ], + + // Allow void expression in arrow functions shorthand + '@typescript-eslint/no-confusing-void-expression': [ + 'error', + { + ignoreArrowShorthand: true, + ignoreVoidOperator: true, + }, + ], + + // Prefer nullish coalescing but don't error on logical or + '@typescript-eslint/prefer-nullish-coalescing': 'off', + + // Allow inferrable types (style preference) + '@typescript-eslint/no-inferrable-types': 'off', + + // === Anti-Hallucination Rules === + // Explicit return types + '@typescript-eslint/explicit-function-return-type': [ + 'warn', + { + allowExpressions: true, + allowTypedFunctionExpressions: true, + allowHigherOrderFunctions: true, + allowDirectConstAssertionInArrowFunctions: true, + }, + ], + + // Explicit types for exported functions (minimum requirement) + '@typescript-eslint/explicit-module-boundary-types': 'warn', + + // Prevent variable shadowing + '@typescript-eslint/no-shadow': 'error', + + // === Naming Conventions === + '@typescript-eslint/naming-convention': [ + 'warn', + // Imports can be camelCase or PascalCase (React, ReactDOM, App, etc.) + { + selector: 'import', + format: ['camelCase', 'PascalCase'], + }, + // Default: variables and parameters in camelCase + { + selector: 'default', + format: ['camelCase'], + leadingUnderscore: 'allow', + }, + // Static readonly class properties can be UPPER_CASE + { + selector: 'classProperty', + modifiers: ['static', 'readonly'], + format: ['camelCase', 'UPPER_CASE'], + }, + // Variables: camelCase or UPPER_CASE for constants + { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE', 'PascalCase'], + leadingUnderscore: 'allow', + }, + // Functions: camelCase (includes type guards like isXxx, builders like buildXxx) + { + selector: 'function', + format: ['camelCase', 'PascalCase'], + }, + // Parameters: camelCase, allow leading underscore for unused + { + selector: 'parameter', + format: ['camelCase'], + leadingUnderscore: 'allow', + }, + // Types and interfaces in PascalCase + { + selector: 'typeLike', + format: ['PascalCase'], + }, + // Interfaces should NOT start with I (modern convention) + { + selector: 'interface', + format: ['PascalCase'], + custom: { regex: '^I[A-Z]', match: false }, + }, + // Enum members in PascalCase or UPPER_CASE + { + selector: 'enumMember', + format: ['PascalCase', 'UPPER_CASE'], + }, + // Object literal properties: allow any format (for API compatibility) + { + selector: 'objectLiteralProperty', + format: null, + }, + // Type properties: allow any format (for type definitions matching APIs) + { + selector: 'typeProperty', + format: null, + }, + ], + + // === Import Restrictions === + // Note: boundaries/element-types handles main/renderer separation + 'no-restricted-imports': [ + 'error', + { + patterns: [ + // Prevent deep relative imports - use @/ aliases + { + group: ['../**/..'], + message: 'Avoid deep relative imports, use @/ aliases', + }, + ], + }, + ], + + // === Mutation Prevention === + 'no-param-reassign': [ + 'error', + { + props: true, + ignorePropertyModificationsFor: ['draft', 'acc', 'ctx', 'state', 'req', 'res'], + }, + ], + + // === SonarJS rule adjustments === + // Cognitive complexity - warn instead of error for gradual adoption + 'sonarjs/cognitive-complexity': 'off', + // Allow some duplication in similar but not identical code + 'sonarjs/no-duplicate-string': 'off', + // Relax for Electron IPC patterns (many similar switch cases) + 'sonarjs/no-small-switch': 'off', + // Allow nested ternaries in JSX (common React pattern) + 'sonarjs/no-nested-conditional': 'off', + + // === Security rule adjustments (Code Protection) === + // These catch common security mistakes + 'security/detect-eval-with-expression': 'error', + // Disabled: This is a desktop file reader app - file system access is expected + 'security/detect-non-literal-fs-filename': 'off', + // Disabled: Dynamic patterns are intentional in this app + 'security/detect-non-literal-regexp': 'off', + // Disabled: Often false positives with typed code + 'security/detect-object-injection': 'off', + 'security/detect-child-process': 'warn', + 'security/detect-non-literal-require': 'warn', + 'security/detect-possible-timing-attacks': 'warn', + }, + }, + + // === IMPORTANT: eslint-config-prettier MUST be LAST === + // This disables all ESLint rules that conflict with Prettier + // Prettier handles formatting, ESLint handles code quality + eslintConfigPrettier, +]); diff --git a/knip.json b/knip.json new file mode 100644 index 00000000..1f16c2dd --- /dev/null +++ b/knip.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://unpkg.com/knip@next/schema.json", + "entry": [ + "src/main/index.ts", + "src/preload/index.ts", + "src/renderer/main.tsx", + "electron.vite.config.ts" + ], + "project": ["src/**/*.{ts,tsx}!"], + "ignore": ["tsconfig*.json"], + "paths": { + "@main/*": ["./src/main/*"], + "@renderer/*": ["./src/renderer/*"], + "@preload/*": ["./src/preload/*"], + "@shared/*": ["./src/shared/*"] + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..8aa7377f --- /dev/null +++ b/package.json @@ -0,0 +1,98 @@ +{ + "name": "claude-code-context", + "type": "module", + "version": "0.1.0", + "description": "Desktop app that visualizes Claude Code session execution — explore conversations, track context usage, and analyze tool calls", + "license": "MIT", + "author": "Claude Code Context contributors", + "homepage": "https://github.com/matt1398/claude-code-context", + "repository": { + "type": "git", + "url": "https://github.com/matt1398/claude-code-context.git" + }, + "bugs": { + "url": "https://github.com/matt1398/claude-code-context/issues" + }, + "main": "dist-electron/main/index.js", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "dist": "pnpm dlx electron-builder@24.13.3 --config electron-builder.yml --mac --win", + "dist:mac": "pnpm dlx electron-builder@24.13.3 --config electron-builder.yml --mac", + "dist:win": "pnpm dlx electron-builder@24.13.3 --config electron-builder.yml --win", + "preview": "electron-vite preview", + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css}\"", + "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build", + "fix": "pnpm lint:fix && pnpm format", + "quality": "pnpm check && pnpm format:check && npx knip", + "test:chunks": "tsx test/test-chunk-building.ts", + "test:semantic": "tsx test/test-semantic-steps.ts", + "test:noise": "tsx test/test-noise-filtering.ts", + "test:task-filtering": "tsx test/test-task-filtering.ts", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:coverage:critical": "vitest run --coverage --config vitest.critical.config.ts" + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@tanstack/react-virtual": "^3.10.8", + "date-fns": "^3.6.0", + "lucide-react": "^0.562.0", + "mdast-util-to-hast": "^13.2.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "unified": "^11.0.5", + "zustand": "^4.5.0" + }, + "devDependencies": { + "@eslint-community/eslint-plugin-eslint-comments": "^4.6.0", + "@eslint/js": "^9.39.2", + "@tailwindcss/typography": "^0.5.19", + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "@types/node": "^25.0.7", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "^3.1.4", + "autoprefixer": "^10.4.17", + "electron": "^40.3.0", + "electron-vite": "^2.3.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-boundaries": "^5.3.1", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.26", + "eslint-plugin-security": "^3.0.1", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sonarjs": "^3.0.6", + "eslint-plugin-tailwindcss": "^3.18.2", + "globals": "^17.2.0", + "happy-dom": "^17.4.6", + "knip": "^5.82.1", + "postcss": "^8.4.35", + "prettier": "^3.8.1", + "prettier-plugin-tailwindcss": "^0.7.2", + "tailwindcss": "^3.4.1", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.54.0", + "vite": "^5.4.2", + "vitest": "^3.1.4" + }, + "packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..64b0bb6d --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,7228 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@18.3.1) + '@tanstack/react-virtual': + specifier: ^3.10.8 + version: 3.13.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + date-fns: + specifier: ^3.6.0 + version: 3.6.0 + lucide-react: + specifier: ^0.562.0 + version: 0.562.0(react@18.3.1) + mdast-util-to-hast: + specifier: ^13.2.1 + version: 13.2.1 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@18.3.27)(react@18.3.1) + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + unified: + specifier: ^11.0.5 + version: 11.0.5 + zustand: + specifier: ^4.5.0 + version: 4.5.7(@types/react@18.3.27)(react@18.3.1) + devDependencies: + '@eslint-community/eslint-plugin-eslint-comments': + specifier: ^4.6.0 + version: 4.6.0(eslint@9.39.2(jiti@1.21.7)) + '@eslint/js': + specifier: ^9.39.2 + version: 9.39.2 + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)) + '@types/hast': + specifier: ^3.0.4 + version: 3.0.4 + '@types/mdast': + specifier: ^4.0.4 + version: 4.0.4 + '@types/node': + specifier: ^25.0.7 + version: 25.0.7 + '@types/react': + specifier: ^18.3.3 + version: 18.3.27 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.27) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.7.0(vite@5.4.21(@types/node@25.0.7)) + '@vitest/coverage-v8': + specifier: ^3.1.4 + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(happy-dom@17.6.3)) + autoprefixer: + specifier: ^10.4.17 + version: 10.4.23(postcss@8.5.6) + electron: + specifier: ^40.3.0 + version: 40.3.0 + electron-vite: + specifier: ^2.3.0 + version: 2.3.0(vite@5.4.21(@types/node@25.0.7)) + eslint: + specifier: ^9.39.2 + version: 9.39.2(jiti@1.21.7) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.39.2(jiti@1.21.7)) + eslint-import-resolver-typescript: + specifier: ^4.4.4 + version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-boundaries: + specifier: ^5.3.1 + version: 5.3.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import: + specifier: ^2.32.0 + version: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-jsx-a11y: + specifier: ^6.10.2 + version: 6.10.2(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-refresh: + specifier: ^0.4.26 + version: 0.4.26(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-security: + specifier: ^3.0.1 + version: 3.0.1 + eslint-plugin-simple-import-sort: + specifier: ^12.1.1 + version: 12.1.1(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-sonarjs: + specifier: ^3.0.6 + version: 3.0.6(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-tailwindcss: + specifier: ^3.18.2 + version: 3.18.2(tailwindcss@3.4.19(tsx@4.21.0)) + globals: + specifier: ^17.2.0 + version: 17.2.0 + happy-dom: + specifier: ^17.4.6 + version: 17.6.3 + knip: + specifier: ^5.82.1 + version: 5.82.1(@types/node@25.0.7)(typescript@5.9.3) + postcss: + specifier: ^8.4.35 + version: 8.5.6 + prettier: + specifier: ^3.8.1 + version: 3.8.1 + prettier-plugin-tailwindcss: + specifier: ^0.7.2 + version: 0.7.2(prettier@3.8.1) + tailwindcss: + specifier: ^3.4.1 + version: 3.4.19(tsx@4.21.0) + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.54.0 + version: 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + vite: + specifier: ^5.4.2 + version: 5.4.21(@types/node@25.0.7) + vitest: + specifier: ^3.1.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(happy-dom@17.6.3) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@boundaries/elements@1.1.2': + resolution: {integrity: sha512-DnGHL+v36YVMoWhWZqyJYVZ9dapNm7h4N3/P0lDPirJj0CHVPkjChMCCotj74cg6LW7iPJZFGrdEfh0X0g2bmQ==} + engines: {node: '>=18.18'} + + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + + '@electron/get@2.0.3': + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-plugin-eslint-comments@4.6.0': + resolution: {integrity: sha512-2EX2bBQq1ez++xz2o9tEeEQkyvfieWgUFMH4rtJJri2q0Azvhja3hZGXsjPXs31R4fQkZDtWzNDDK2zQn5UE5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-resolver/binding-android-arm-eabi@11.16.4': + resolution: {integrity: sha512-6XUHilmj8D6Ggus+sTBp64x/DUQ7LgC/dvTDdUOt4iMQnDdSep6N1mnvVLIiG+qM5tRnNHravNzBJnUlYwRQoA==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.16.4': + resolution: {integrity: sha512-5ODwd1F5mdkm6JIg1CNny9yxIrCzrkKpxmqas7Alw23vE0Ot8D4ykqNBW5Z/nIZkXVEo5VDmnm0sMBBIANcpeQ==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.16.4': + resolution: {integrity: sha512-egwvDK9DMU4Q8F4BG74/n4E22pQ0lT5ukOVB6VXkTj0iG2fnyoStHoFaBnmDseLNRA4r61Mxxz8k940CIaJMDg==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.16.4': + resolution: {integrity: sha512-HMkODYrAG4HaFNCpaYzSQFkxeiz2wzl+smXwxeORIQVEo1WAgUrWbvYT/0RNJg/A8z2aGMGK5KWTUr2nX5GiMw==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.16.4': + resolution: {integrity: sha512-mkcKhIdSlUqnndD928WAVVFMEr1D5EwHOBGHadypW0PkM0h4pn89ZacQvU7Qs/Z2qquzvbyw8m4Mq3jOYI+4Dw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.16.4': + resolution: {integrity: sha512-ZJvzbmXI/cILQVcJL9S2Fp7GLAIY4Yr6mpGb+k6LKLUSEq85yhG+rJ9eWCqgULVIf2BFps/NlmPTa7B7oj8jhQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.16.4': + resolution: {integrity: sha512-iZUB0W52uB10gBUDAi79eTnzqp1ralikCAjfq7CdokItwZUVJXclNYANnzXmtc0Xr0ox+YsDsG2jGcj875SatA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.16.4': + resolution: {integrity: sha512-qNQk0H6q1CnwS9cnvyjk9a+JN8BTbxK7K15Bb5hYfJcKTG1hfloQf6egndKauYOO0wu9ldCMPBrEP1FNIQEhaA==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-musl@11.16.4': + resolution: {integrity: sha512-wEXSaEaYxGGoVSbw0i2etjDDWcqErKr8xSkTdwATP798efsZmodUAcLYJhN0Nd4W35Oq6qAvFGHpKwFrrhpTrA==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.16.4': + resolution: {integrity: sha512-CUFOlpb07DVOFLoYiaTfbSBRPIhNgwc/MtlYeg3p6GJJw+kEm/vzc9lohPSjzF2MLPB5hzsJdk+L/GjrTT3UPw==} + cpu: [ppc64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.16.4': + resolution: {integrity: sha512-d8It4AH8cN9ReK1hW6ZO4x3rMT0hB2LYH0RNidGogV9xtnjLRU+Y3MrCeClLyOSGCibmweJJAjnwB7AQ31GEhg==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-musl@11.16.4': + resolution: {integrity: sha512-d09dOww9iKyEHSxuOQ/Iu2aYswl0j7ExBcyy14D6lJ5ijQSP9FXcJYJsJ3yvzboO/PDEFjvRuF41f8O1skiPVg==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-s390x-gnu@11.16.4': + resolution: {integrity: sha512-lhjyGmUzTWHduZF3MkdUSEPMRIdExnhsqv8u1upX3A15epVn6YVwv4msFQPJl1x1wszkACPeDHGOtzHsITXGdw==} + cpu: [s390x] + os: [linux] + + '@oxc-resolver/binding-linux-x64-gnu@11.16.4': + resolution: {integrity: sha512-ZtqqiI5rzlrYBm/IMMDIg3zvvVj4WO/90Dg/zX+iA8lWaLN7K5nroXb17MQ4WhI5RqlEAgrnYDXW+hok1D9Kaw==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-musl@11.16.4': + resolution: {integrity: sha512-LM424h7aaKcMlqHnQWgTzO+GRNLyjcNnMpqm8SygEtFRVW693XS+XGXYvjORlmJtsyjo84ej1FMb3U2HE5eyjg==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-openharmony-arm64@11.16.4': + resolution: {integrity: sha512-8w8U6A5DDWTBv3OUxSD9fNk37liZuEC5jnAc9wQRv9DeYKAXvuUtBfT09aIZ58swaci0q1WS48/CoMVEO6jdCA==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.16.4': + resolution: {integrity: sha512-hnjb0mDVQOon6NdfNJ1EmNquonJUjoYkp7UyasjxVa4iiMcApziHP4czzzme6WZbp+vzakhVv2Yi5ACTon3Zlw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.16.4': + resolution: {integrity: sha512-+i0XtNfSP7cfnh1T8FMrMm4HxTeh0jxKP/VQCLWbjdUxaAQ4damho4gN9lF5dl0tZahtdszXLUboBFNloSJNOQ==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-ia32-msvc@11.16.4': + resolution: {integrity: sha512-ePW1islJrv3lPnef/iWwrjrSpRH8kLlftdKf2auQNWvYLx6F0xvcnv9d+r/upnVuttoQY9amLnWJf+JnCRksTw==} + cpu: [ia32] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.16.4': + resolution: {integrity: sha512-qnjQhjHI4TDL3hkidZyEmQRK43w2NHl6TP5Rnt/0XxYuLdEgx/1yzShhYidyqWzdnhGhSPTM/WVP2mK66XLegA==} + cpu: [x64] + os: [win32] + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + cpu: [x64] + os: [win32] + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tanstack/react-virtual@3.13.18': + resolution: {integrity: sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.18': + resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@24.10.12': + resolution: {integrity: sha512-68e+T28EbdmLSTkPgs3+UacC6rzmqrcWFPQs1C8mwJhI/r5Uxr0yEuQotczNRROd1gq30NGxee+fo0rSIxpyAw==} + + '@types/node@25.0.7': + resolution: {integrity: sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@typescript-eslint/eslint-plugin@8.54.0': + resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.54.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.54.0': + resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.54.0': + resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.54.0': + resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.54.0': + resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.54.0': + resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.54.0': + resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.54.0': + resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.54.0': + resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.54.0': + resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + ast-v8-to-istanbul@0.3.10: + resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.1: + resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.9.14: + resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001764: + resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + electron-vite@2.3.0: + resolution: {integrity: sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@swc/core': ^1.0.0 + vite: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + '@swc/core': + optional: true + + electron@40.3.0: + resolution: {integrity: sha512-ZaDkTZpNHr863tyZHieoqbaiLI0e3RVCXoEC5y1Ld70/Q5H1mPV9d5TK0h1dWtaSFVOW0w8iDvtdLwAXtasXpg==} + engines: {node: '>= 12.20.55'} + hasBin: true + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.2: + resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-context@0.1.9: + resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@4.4.4: + resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} + engines: {node: ^16.17.0 || >=18.6.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-boundaries@5.3.1: + resolution: {integrity: sha512-91StsOYtDyrna1fyRJ+1Ps5CnrfyFLbdCouPZ3E/o2cllLxJke3OoScdqjpBSl7pNEYbojhpNlurQAr30sf9Bg==} + engines: {node: '>=18.18'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-plugin-security@3.0.1: + resolution: {integrity: sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-plugin-simple-import-sort@12.1.1: + resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==} + peerDependencies: + eslint: '>=5.0.0' + + eslint-plugin-sonarjs@3.0.6: + resolution: {integrity: sha512-3mVUqsAUSylGfkJMj2v0aC2Cu/eUunDLm+XMjLf0uLjAZao205NWF3g6EXxcCAFO+rCZiQ6Or1WQkUcU9/sKFQ==} + peerDependencies: + eslint: ^8.0.0 || ^9.0.0 + + eslint-plugin-tailwindcss@3.18.2: + resolution: {integrity: sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==} + engines: {node: '>=18.12.0'} + peerDependencies: + tailwindcss: ^3.4.0 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@17.2.0: + resolution: {integrity: sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + happy-dom@17.6.3: + resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==} + engines: {node: '>=20.0.0'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsx-ast-utils-x@0.1.0: + resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + knip@5.82.1: + resolution: {integrity: sha512-1nQk+5AcnkqL40kGQXfouzAEXkTR+eSrgo/8m1d0BMei4eAzFwghoXC4gOKbACgBiCof7hE8wkBVDsEvznf85w==} + engines: {node: '>=18.18.0'} + hasBin: true + peerDependencies: + '@types/node': '>=18' + typescript: '>=5.0.4 <7' + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.562.0: + resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + oxc-resolver@11.16.4: + resolution: {integrity: sha512-nvJr3orFz1wNaBA4neRw7CAn0SsjgVaEw1UHpgO/lzVW12w+nsFnvU/S6vVX3kYyFaZdxZheTExi/fa8R8PrZA==} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-tailwindcss@0.7.2: + resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + engines: {node: '>=14'} + hasBin: true + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-regex@2.1.1: + resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + stable-hash-x@0.2.0: + resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} + engines: {node: '>=12.0.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.54.0: + resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@boundaries/elements@1.1.2(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7))': + dependencies: + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)) + handlebars: 4.7.8 + is-core-module: 2.16.1 + micromatch: 4.0.8 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + '@dnd-kit/accessibility@3.1.1(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@eslint-community/eslint-plugin-eslint-comments@4.6.0(eslint@9.39.2(jiti@1.21.7))': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 9.39.2(jiti@1.21.7) + ignore: 7.0.5 + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@1.21.7))': + dependencies: + eslint: 9.39.2(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-resolver/binding-android-arm-eabi@11.16.4': + optional: true + + '@oxc-resolver/binding-android-arm64@11.16.4': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.16.4': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.16.4': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.16.4': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.16.4': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.16.4': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.16.4': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.16.4': + optional: true + + '@oxc-resolver/binding-win32-ia32-msvc@11.16.4': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.16.4': + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-x64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.55.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.55.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.55.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@sindresorhus/is@4.6.0': {} + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(tsx@4.21.0))': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.19(tsx@4.21.0) + + '@tanstack/react-virtual@3.13.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.13.18 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/virtual-core@3.13.18': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.0.4 + '@types/keyv': 3.1.4 + '@types/node': 25.0.7 + '@types/responselike': 1.0.3 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/http-cache-semantics@4.0.4': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 25.0.7 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/node@24.10.12': + dependencies: + undici-types: 7.16.0 + + '@types/node@25.0.7': + dependencies: + undici-types: 7.16.0 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.27)': + dependencies: + '@types/react': 18.3.27 + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 25.0.7 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 25.0.7 + optional: true + + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + eslint: 9.39.2(jiti@1.21.7) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.54.0': {} + + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + eslint-visitor-keys: 4.2.1 + + '@ungap/structured-clone@1.3.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@25.0.7))': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@25.0.7) + transitivePeerDependencies: + - supports-color + + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(happy-dom@17.6.3))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.10 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(happy-dom@17.6.3) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@5.4.21(@types/node@25.0.7))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@25.0.7) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + ast-types-flow@0.0.8: {} + + ast-v8-to-istanbul@0.3.10: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + + async-function@1.0.0: {} + + autoprefixer@10.4.23(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001764 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.1: {} + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.9.14: {} + + binary-extensions@2.3.0: {} + + boolean@3.2.0: + optional: true + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.14 + caniuse-lite: 1.0.30001764 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + buffer-crc32@0.2.13: {} + + builtin-modules@3.3.0: {} + + bytes@3.1.2: {} + + cac@6.7.14: {} + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001764: {} + + ccount@2.0.1: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + check-error@2.1.3: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-fns@3.6.0: {} + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + detect-node@2.1.0: + optional: true + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.267: {} + + electron-vite@2.3.0(vite@5.4.21(@types/node@25.0.7)): + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) + cac: 6.7.14 + esbuild: 0.21.5 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@25.0.7) + transitivePeerDependencies: + - supports-color + + electron@40.3.0: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 24.10.12 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: {} + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.2: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es6-error@4.1.1: + optional: true + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@1.21.7)): + dependencies: + eslint: 9.39.2(jiti@1.21.7) + + eslint-import-context@0.1.9(unrs-resolver@1.11.1): + dependencies: + get-tsconfig: 4.13.0 + stable-hash-x: 0.2.0 + optionalDependencies: + unrs-resolver: 1.11.1 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + get-tsconfig: 4.13.0 + is-bun-module: 2.0.0 + stable-hash-x: 0.2.0 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-boundaries@5.3.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@boundaries/elements': 1.1.2(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)) + chalk: 4.1.2 + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)) + micromatch: 4.0.8 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@1.21.7)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@1.21.7)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.1 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.2(jiti@1.21.7) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + eslint: 9.39.2(jiti@1.21.7) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@1.21.7)): + dependencies: + eslint: 9.39.2(jiti@1.21.7) + + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@1.21.7)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.2 + eslint: 9.39.2(jiti@1.21.7) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-plugin-security@3.0.1: + dependencies: + safe-regex: 2.1.1 + + eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.2(jiti@1.21.7)): + dependencies: + eslint: 9.39.2(jiti@1.21.7) + + eslint-plugin-sonarjs@3.0.6(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@eslint-community/regexpp': 4.12.2 + builtin-modules: 3.3.0 + bytes: 3.1.2 + eslint: 9.39.2(jiti@1.21.7) + functional-red-black-tree: 1.0.1 + jsx-ast-utils-x: 0.1.0 + lodash.merge: 4.6.2 + minimatch: 10.1.1 + scslre: 0.3.0 + semver: 7.7.3 + typescript: 5.9.3 + + eslint-plugin-tailwindcss@3.18.2(tailwindcss@3.4.19(tsx@4.21.0)): + dependencies: + fast-glob: 3.3.3 + postcss: 8.5.6 + tailwindcss: 3.4.19(tsx@4.21.0) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-util-is-identifier-name@3.0.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + expect-type@1.3.0: {} + + extend@3.0.2: {} + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + + fraction.js@5.3.4: {} + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functional-red-black-tree@1.0.1: {} + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.7.3 + serialize-error: 7.0.1 + optional: true + + globals@14.0.0: {} + + globals@17.2.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + graceful-fs@4.2.11: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + happy-dom@17.6.3: + dependencies: + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + html-escaper@2.0.2: {} + + html-url-attributes@3.0.1: {} + + http-cache-semantics@4.2.0: {} + + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inline-style-parser@0.2.7: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.3 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-decimal@2.0.1: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stringify-safe@5.0.1: + optional: true + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsx-ast-utils-x@0.1.0: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + knip@5.82.1(@types/node@25.0.7)(typescript@5.9.3): + dependencies: + '@nodelib/fs.walk': 1.2.8 + '@types/node': 25.0.7 + fast-glob: 3.3.3 + formatly: 0.3.0 + jiti: 2.6.1 + js-yaml: 4.1.1 + minimist: 1.2.8 + oxc-resolver: 11.16.4 + picocolors: 1.1.1 + picomatch: 4.0.3 + smol-toml: 1.6.0 + strip-json-comments: 5.0.3 + typescript: 5.9.3 + zod: 4.3.6 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + + lowercase-keys@2.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.562.0(react@18.3.1): + dependencies: + react: 18.3.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + markdown-table@3.0.4: {} + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + + math-intrinsics@1.1.0: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + neo-async@2.6.2: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + oxc-resolver@11.16.4: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.16.4 + '@oxc-resolver/binding-android-arm64': 11.16.4 + '@oxc-resolver/binding-darwin-arm64': 11.16.4 + '@oxc-resolver/binding-darwin-x64': 11.16.4 + '@oxc-resolver/binding-freebsd-x64': 11.16.4 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.16.4 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.16.4 + '@oxc-resolver/binding-linux-arm64-gnu': 11.16.4 + '@oxc-resolver/binding-linux-arm64-musl': 11.16.4 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.16.4 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.16.4 + '@oxc-resolver/binding-linux-riscv64-musl': 11.16.4 + '@oxc-resolver/binding-linux-s390x-gnu': 11.16.4 + '@oxc-resolver/binding-linux-x64-gnu': 11.16.4 + '@oxc-resolver/binding-linux-x64-musl': 11.16.4 + '@oxc-resolver/binding-openharmony-arm64': 11.16.4 + '@oxc-resolver/binding-wasm32-wasi': 11.16.4 + '@oxc-resolver/binding-win32-arm64-msvc': 11.16.4 + '@oxc-resolver/binding-win32-ia32-msvc': 11.16.4 + '@oxc-resolver/binding-win32-x64-msvc': 11.16.4 + + p-cancelable@2.1.1: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + possible-typed-array-names@1.1.0: {} + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + tsx: 4.21.0 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-plugin-tailwindcss@0.7.2(prettier@3.8.1): + dependencies: + prettier: 3.8.1 + + prettier@3.8.1: {} + + progress@2.0.3: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.1.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + quick-lru@5.1.1: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@16.13.1: {} + + react-markdown@10.1.0(@types/react@18.3.27)(react@18.3.1): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 18.3.27 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 18.3.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-refresh@0.17.0: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + resolve-alpn@1.2.1: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + reusify@1.1.0: {} + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + + rollup@4.55.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex@2.1.1: + dependencies: + regexp-tree: 0.1.27 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + + semver-compare@1.0.0: + optional: true + + semver@6.3.1: {} + + semver@7.7.3: {} + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + smol-toml@1.6.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + space-separated-tokens@2.0.2: {} + + sprintf-js@1.1.3: + optional: true + + stable-hash-x@0.2.0: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + strip-json-comments@5.0.3: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + sumchecker@3.0.1: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.19(tsx@4.21.0): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.5.0 + minimatch: 9.0.5 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.13.1: + optional: true + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + uglify-js@3.19.3: + optional: true + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.16.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universalify@0.1.2: {} + + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-node@3.2.4(@types/node@25.0.7): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 5.4.21(@types/node@25.0.7) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@25.0.7): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.55.1 + optionalDependencies: + '@types/node': 25.0.7 + fsevents: 2.3.3 + + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(happy-dom@17.6.3): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@25.0.7)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 5.4.21(@types/node@25.0.7) + vite-node: 3.2.4(@types/node@25.0.7) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 25.0.7 + happy-dom: 17.6.3 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + walk-up-path@4.0.0: {} + + webidl-conversions@7.0.0: {} + + whatwg-mimetype@3.0.0: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + yallist@3.1.1: {} + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + + zustand@4.5.7(@types/react@18.3.27)(react@18.3.1): + dependencies: + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + react: 18.3.1 + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..6b58ca0b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +ignoredBuiltDependencies: + - electron + - esbuild diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 00000000..85f717cc --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6ad9c2093eee169593c30ed374e59602ad86764b GIT binary patch literal 482226 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

HtSa?ip_QF~Bt0d83r1atzZQbKu(b6CVrBaP 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∨T|9 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

XC>zwxJa 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

K*QiKoy?3yEG@F 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%0D-SSZ)uCm+yT;$4F_>Yjbl=#$!8`tzWpDH*Zn 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%0Dpk?1xGvZ6j@Mk`S9S z6Vgc;Z=E;~4EvnHa9|FmuXq_BpnN<}ZVs*x11;ZylJl}*QXp9F-_IAg_AQ$Xi;pFxr}ounVtUJYz%g&geaie174lIT zZD$cUyv|uH0}m0G>XW?bnTipQJQe*BA41L`GVaj%FwFC~gPjUEP$g11`!O0bn8(h8 z&ljc@_Ml{2BWI^MnO{1)1VfYW`7tNHbBc;LTCd*>VE)z^?3h~fLacclmJ;DEcD=vBl{>bFwyVLbUs%77)(_k?%)57e`hy|$ zX?J_H|9Y|Nr@V}&k!^njwtH#D=BUP1m`vvFty@5a`;TGl(1^oa(kS?71jm@`H!$f^ z%)tLO#={T#<`zpplURdxN5sg#X;SY$9%hB<^7bp}#3V7^pLYytPp|2<-s6oyt@JZS zj6n|to0%hWco94!HT6IBu4KTNs->|@}V>(3q)b3iu zF66{TqPD$rN`x4niUp&WEdH32P~oHVhKB?taSh-_>^R(B8&BJJ2{VeU2QlCP7Gnb- z{6HKOYmgw@0&s@cWH=n+H1Xt~5BOdYZh58j3W}No>9W*<`?h2nHCsXr&su?Xl}{TC zzMGd@AQBIMM4y%E*!5+0q5qgIp^7b^DA*4gWklMLt)(idCBL!+=g6h)MgTZA zdH$}EkTkfh?v}d?u;NoAE#i!x6W$}#R9q9*S41~*pC~H6WwCng8 zR-D0SP)?w)Si3{gAA$pf6P%O&Z0HoCrugYpMrQh<^T)Lrf_D)-4L=ibcYEzc zXT^u{hxFp?2^K^Owrit%C^{sM{R6??jwZY$+=9f~TajrY!q_UpkvBfIKiCNY0{Jup zYK|vhXEV0fnlCihWNef`WA>Ai%oemJq2fc{Kd}MBMi4GK#s`GbL~yVf(O7`Oa{(Xx z5()9u>=KJ!{*)rEX8Me(#z0kbAY}krHjg~O`LKhi;sH||_=kctwwsuPM;#9~J1B=f zJVbuxnLwH+1^k3tK|zb>@Rh}$t&Zmaw5SpkP^i|M9E}jiKnm*&A7p>pi^VBH=Ixn- zBjJa~TgO{$6TTFuJRTi@use&+yAc(kfa}3Lxk}RM4O7C+aA1>c1V< z8#hk*#OglCMV^DB7Pv>HAI>h(wkb6eUO(t~%n~FpX8-Ar?K{1+hHl%qGJF^38-M&e z-LNkw==1YadiI)Ea(aW{rr78?5btoSg7ZHC;YiuLFkd{3$`|Yu(_5#WY{iKZ!I4M1 z!W{x_PmO+hdu_;uP85_^$vWM zVp#c*X|v9*@sJb2Ia*VnG}@xO7r1=3s&fYJyZ&F-dwC;n>9R-1ROhv|K3&;zHMI4= zQ>%X3ot%CuudR8(Pg>23&gu=FAc=RR{j)ZyNQ^XcgY==DV48jL6&k_K0$4X=E(li9 zIE9Q%s0@U@3Vc`KnauBJ)aO_kX!7@BjthhKc3L)YEKrx z!tpIhNSm;&7ia;HjVu$O3P)$Sn6xrS(>O5zLST0dYv1NB zpi4oS*lM>0O^eE?vuA_g5~I~bVf&}~9LTmL(h_4h0Ajw#6C=qfEHkK3Sc6GsUMIWFz zqB4aNi>?7{*ygwC-|Nvu>uvaT4`Bp2lHP#2fbhxCwi*^ zkD0b;qsEzE?c2*%?Ym8_-ZsA(_RH2YjTv>N=?u&@0n^*z3u?HW9Bi>{3-zkS9hyD+ zT+gFmqI?rU4h)?`iikmV^5{ifvDt>)(vp+`-oD=dwhH>)PL`9&azq!#BjL5CA&uGx z#%f*yM*T23qVdJ94@t0;(1u1B5F;$`5hTAjfZad~?Cbc3QxsRG=BI6}k8JRZsCzoy z{fVt|1m0}%W5&R^l4%B%Okf1i8ru$XYu=(*+QGSb+^Vg@#~%E3Q&dZ~XMetH0B6Vd z8*96<4tjbB*3aTiu7K%RkhBO3j#UymTq2q_U3?+=Sc&Sf>11daAyGdEkK(J%l*J73S#Grcb1uapfDCAq6(zreJVy0`1iiD?BGYRPBKnSbGJc67gm`4G*k z2wfg`f+`_G1&h=|e7~S2`-tUEd#JZsLMiXaCw|oUNb<(ezfc$KN+9Q!uXmJjkEe2xA+1KI2?Q`2gbva zlj69_#Nj!o{bSnmmI^JJ0$T|h6504yFIUh6|H zmz+`9x&`<;(6)qj%7>Z_oUK@O&YOHu!U?tcR)3wr9Tm58?Q=xk>z4QY2lubB9eip_ zXpFa)SBR1H@45VC;VE;U^EtF>jt?eBU#fL9o;F*jdmoCf>|MGk$oFj+zb(b<9ocmk z@07ORi*36e*uD!+u_{+!`!6e7VcnkN%CYE^)$3CRkV9Ksvr-8l6dQKPrX6=P=P;Bq zWC6vLTCP=-Lg}V^H#A=NO{eu*77L+j>r~OSDwAj-RY>qDa>f7B-vKOx)DXZjdR#sG zQ+(=s!(65_=y<4@t=2~yaxg=(r*wd!k$`Y74trwDeuv{vGIZ>Z8QFG6SkUrr1IK3s zB;DaTaevz&S_eD;4-T-^=KzX&AgY*5Lb2<`TvbD7Y&wsW@QI`smIu?rHuU1~^$A|_ z=qNGklnPcyfmQ{FSX&a)ULugiUr4A){KK>gG5mi>Zk;E3AUT3$9+VxJNQopcka@m} zwhb&O9sz?zN`iO@UB?$IS_!V_NlI`@BG&zsF8+;UKNYI7;iq{rf6kg|zHx`Rg|cji z{`_^ldmo=V2*K2=eT?I&Lt^d3i3$#hkJOA!)I2gF@$p%66&!`iNg{7dPCvTYaibD4 z$HWw@&GwfJ8}ZS9NG8g)7aj)S&VEWaXT5v>dabB8k+(k8-G1rq%TJbn^y|lk%Rk!C z{4g*0(3!xk@7liKv?%6Kbbj6Yzg+#-=vR++iaXfa@Tis!LEmqNYBgrPk3mNlkZO_s zonZrJeCxYmjCc3Y$Ja)7jv9J1mt>gmx`j%^W00=&`Nyu=^rcacP!{c`?dspJXRk+m zvaaSAH^<3rh2-d!&3+1@k95!xm@lUGuxOjspvv~9oUp(gLNACkI4LX&O6!;?++e3> zVcZ5g22Ez8>GYr>LXu|0T2lHI&R`fvrSC{6{i$Ur<1MnWkqUmx*1`%$r`TzB>*;(` zV$G4PVT^Ee49_2`9??DM|GIeR)E>I{%z#Bk)9C}9axzt_m@anLxuN<}^-YL1XLpL7 zU^1(-1739H!?WhHOMM6ZA$)YLS3op;~K1Qp=p%GBfP~-Uegi-V`V+mr77xw{7aO z1I^=6ASM4q(@`UpGJ=+3?pp^`o2F%w%e^bld>HCCep=9~!JEqNh>#8Og3j zEhQDnY`TQZQRbs$%QfVOg|xaDY6i(b0hdO#s%0F$gee{12^$eiiL_yw%lJ951%_I< z#@WH<5M~r=h%%4Eehw?|mI@LmTEh1iWr~5(aIAp9avELGa9RTxTHe zLjq2j&AMGE_@5dhjE8tM85^St=HY-LqM{9(rj>voB+G|f&t|j9rdDCsj6s@bfp1zb zZ$q-mF(4;f6&bm=hl-I$yDPyo!dNIo!@>;hSn;>DqoZ$5%xqob87JGCxaBW>a-UDU z*G=CAe^P|wb2h(D8C1F?NPLr4;SXt58-D28Z%9Fa!T5s7Mz5lDrO~U!Fh{{NwUhK} zjZD{yQ|6>ah~PC5TG#-J;Aijr^6#ff(@<(AXC`~gz>+a64ax_6@r0CT#WSITIlCXz z4d@WBb-w;u=uN31EWP~Zu1Rm#?so0#yvEx-W^ZO(pedyKvotbh~vCZmpAv5z?f!A1d$;ao(|<` zv$+yRKAzHq)F%zKeH&tH`wD=X1iBUORWzk#Y(ptucEy&ysWzBoyl31jbNZiW(p*zn z34Hq=R)P{lsPN|^ck;M|PnDkrQcjZs1olTP7HwI`I_EMOP9C-jCp)Gto$U3^rH!K- zR`I%P>pkOecgp6A+7N@m&RCD|fzeYV+Y{)=R(jRw%oej{V+U2o-Y=zwE^F7Mn^vAa z>MDZ3(;>Nof}oGmhN$co;pgldP{t7$!4~+y$ISTX^Gh@Dedc5tg=2K;WUm6TtXKGr zpof34z|H@MdEMYRmI<04y#~kj)#wfC%UGQy(J&A$TX;sKJ7jcq->kjcshCFbGp<*f zQIVEK(&@yI4=aH>%lrNDQV2d5 z#VzT=nz(6tjX7wp@%5saM_(^eDf_#`Vw*i5NdrK@`WbrL6}3$#SoA1;nx?ih;X6}5 z?${Ky4?B-+m&uvXI-%GKnzPu?i)zajO>1I|TiLlT96<8aK8KC!CA>+51?dA9f!M?;7J40&P`rUytMP%z}g z;>@#S*N+jaC|zdoVKGC60ix@K4Q0aRgl1cJ`J~)w{$p0Z6MuSk(FFj5tqBX%da5z~ zRB{Gw=ur849<;Z<=x*ZN4!s%aqu4O>NSa+S2r0lUo1lhhO-BmC-a8NS`VEQO$44-^q^Av4TH9 z$EaiQ9OUwF8*B!l%%IQktP@8CxnWedVqyz%~Oq)Z5`Ara)Wf1JwRH!^I=71_!@d{^?eq zxDDJiogPbf`)sBCILhK3-SVFEQiH4;4N#hQjH*c2Y3V)`fvMlCrxLGrj-Z*sRy}e- zzU9*P;NT>g?F4=|imKrUgh~?Up!8<{wcCcrCB;680BMrL{t6ur#+Edi!m71H*}W#2 za2iFFi3l-cRIL^MWJ!6`va6B*=H^3Az{ts;5%bku`UKIPBO7|Jkm1U?dLc6K=T4Xr zPS4yptg$Oo9^D_#x^&sI3K-7gRKIdbOSaxQ(Mvie-V7eI zWu@T=7l190TKU-}noq~mb%a0|YIfrq%g_9Mrt!BUw!OxTAnl1k1#^!(P0k#DqfTSh zMW2kERm#>};eHnkh!f(53o72tCb!d3nWSm3uvwS~WUNr^DAf8uR8R`vDH!Oe{DQ@~ za7?lRSnxnpgWYJ2Ecy8}>-Vl7Hn`56eVeJ0*^KJ9uyTx@sP|rZ-gw$_4gW*el6WT9 z^ok#Hd<>7yqBqHU)4pr=d|ioi$(qNFxC3RNQhbk$G;6bap5CoitFzUqemY&2WlM!4 z`|3QY#>l}l3E>?=qc@%EEG1JqHV|B8l38gqmDO1HC~>fqf+$q!-$bh*d*;|=4bM7A zYlV#+)dlh^%^GxviaW*pD5?a(`5~qk$J?ZtN>mSGYNEvbwf=$bh zuFs^d%V{oiT8;0hRFSN3R!DR>zLxEpSFEpG`Zaszn1LqK_uB*0o4<-^KU3vdDU;17 zA|9?xZ^~#3zIf!*3##76*ck6>jC4hBmX)&(iKO)cbEx56yXGA*i1Y%yErT9DD8NY!W+yh?5`NL4(X`^ z%Ai})kq@#Fe5b^d9Ycos{9Unt$dV|Y%qL6C()@wrvc%QW?#;^C|IOQc0HX?VI2tUZ z52&()P60IsF6H>|^uPB?Tl1+159KR!!0hP!!u&p4X4I5-;tKKsaY7f)W7p1$Z=K^9PpClU9%_2tKe6jz^;u2V9Ph7g$Mk*Krc1_gBA)mmyQfHLO-0q@VwC8J zB$D(JEGBW+7UJBD1m@cOqg?%pJY7~)Dzjg*R=LhLM* z?#PXp8?X*m1lgKVvuJ)Z-Sk2U$B%GC$_+O(pmEB*bljucB}7nnvSL?Q2} zQ^^8TQG5<4ydNkPU`HUT?A0sq?O*5{oBz(?i%<_LOGcCupqNXkgoCVy6|nNCyUKht zCG~i7Q+so-Hg{m#`l<)zRkxDb8j{2Ubs5O^A9#>O_8b~ud9#OJHiu)R2Bd8v#gr%8IuP8EW zGDU%$tn-Wmff3yJ#d_0iL}Ua~qauPJN@J+PPmb0|Q!rI(qAsqn2-`73_J2uWwj2FG z!4%LTh?dL7euCZ=x>?~w0}irEF)sfO=Ns5|=Mg$9vaW9UDa)(2f4nYMA7M_EhJ?-X z?1>lq7%?k2D*Lb#eNf;bM=Z{>_OIUgZLP=5hkQK9cqCxi;KyRiE8e?IU_WPN%RnVX zB(;RpeX6B;a3<1*89*)s#XKYVeo0781Y`yn%J4Dhj*Mx;V zUZP*GfaCY(G(pgj`k#}x>hfAzPb?t(R;i+ZfBzd&E}Utz+7`RV8L3ef2NiA{;o2DQ z6BoHPXjlld9P1?}mqaE0++afVC7hH7dE3XrxFJzw$MRp+rWAU9Lye^mgTv*jFj3e9 z%ozhVJP2G4l_Mpzqc9Fe1$|3iL2Q~bbAp&5lw=Qp^U5wTK02Ssk8Wh?wN^eC7+4qt z_7p>jn+AqRr-|egiZCCc+1l#AL$j_Gpss)r1WytaDqodq^Q63;tOs3Dax8IZdV5Cu zd-bjNGVUa`-%D=!u)cL|``riab4%y-_R(+Es%~ziVk5@a7Ok<%lMrp8hjwy-j~5ESbZz`4RM z%!f7JoFR?EI05o2GWbB9T=8He17y5_&J1M>jp945h?N1YNDn*$Y!vV^x?ua&3(QKO zrl4C8s@HL;9aa(^{*~+yDn5`Mi3~Dz(hM&=>sI9RDm{j17Dmx5+Z=F*C?>l+K#jsL z!9yWQA&{_0#$Uw3lXF;%VPt-SQ(HGf_KasVp9pNezK#_-4gcRRKU`f2J3L^p%gz>Y#NT3e(o&!sD>%|R^njUqV>GeuPLq`)hDJ? zOtjWp`tMm51b_!nNP)J$Zep1jW$5(dhT)1N3^BSa5BaKAfK zGe2I%P!_w+tzL)w;(D&1xmEY-s<;Xmk0hfDiP6=Di9dGNGu^{Xp=bWe5)+hT*@fGm zH@^`!ILQ3b=VP%>;IGYCdkG4y!+^LvGMMYB9Ze6A-bd+}ov9boBXmqmia@lWB+Bse zj4coz0b@Fhz(iqaKf3q()W@f+IREE|1JaHyt7>09@QTm73qKh+=FDD8Xk@^kruYAu zl>7Xhkm;G4nC70Z+5dld&+ai&xp@a%&oGdv5t$upUM162kheOZ~U;q zU*QIl1d$9`Y@#wOnIZWhIUVv7z$XIg`Ag{AjGDJanI{+2$;|HUWql)OH>n!tC3ZUg zj~td~Cs#gX?ZCsvmIoQlu{f^wkq~^xCME?(*))2MCa9+Yic?uLQq)Nq6;gulnp@v8 zJS7Pe+o-Pg(W$*_-fIps#71U1dcN>uJli)uz&XNJYi$XT#TW!pl3SR-aBE50CPcHN zITlMJiMnN`yo7=Zf2>o)yQ+pFSdTZh4snDMH7B&@$+jYE+YnFO7lW$q&r1}g^Xz*& zFP|EINNybf&rHgY95A0B=UxsFs3VwQQ{(SLabUnr4#FbCp5E+yFh$U=E_rU|e)=+h zcxQrlVM=c@dm^opHfE@DGQC!ydP%DzoRVkc3M2_CpqHC#du_C?}9W2Q$OK!X;1@2&{{&inXsIMARdB?5yivP=1u0U zo(pZgS0s5E#+wQizE+<#L*6nf2mQYXUNbYcR6x9jS1qMVgz+8C*^-35*u+d_o-^dao2Nq&K zDlnDevuTv}(wwJQS|ETMTc0J35xiJTyArH$Dxoy0NiJkp zL4I>L&zP1tZY|DBI7a9S0>_{;*{yvnf8tRDo6-SvCL@!N_wufDsniRHO{QDAdDI}#7twLL*yGRH{ zL}Aj-%bHT+P(cP1X2kdLhu45BxKyQQk@yfi0UD7LVh6fHjln27iX)CD6v9Xn?btv5 z$T~)0D*L!xpBxo4yL3au>XxV5nrr;h9$YPJ-O=vJ)0KEf^=yfY{xoi>soiW$*B|+9 zt)uoGo8?>Iug1u-r{Uvjmq7RsO94t(8!HzsIhlPj&J@_y+}kc0Ahfw;#2d$lf`9fc z)Bk+9?~yCAf>;uAKuwH;Y-SR!1+rhtLlk#f$f7dpPz6EHY`*>(;nJu3gi9RI}2qWjigqkj)M#S{&QG-xW1ko7HdL@@ku62NQC}l z`3UutU8J(H9w~H=NK8snM$mXUUvP=_K?;87@6&5YfnS;_F#*qftT%GQ6A&EApJw(D1|Zp-dkAD)Aypm5exj8<-Kj#jrgfkap>rAvtG;0o}u`PPc{ zK%Iv7OxBhs=n{Bko8^nc-L&opB)u`&(i)k^+PGTM3;8Pd z2h!x`+NclwI%#rA^9-KJhn^2=NlPckxx-L;nNN~nXOem?piVLiB|o3LeQ&KJFs5sC z4TT$&1FjoBTY2F+c`J!M?5DM551N6~;qdv=JiF&96nR%<>HvcdCBp!&cm6EX{N~!e z1Ji{Iz@3P5MwBFz@JtA>61A5+q4J`RRZ4=y#rJSUWD%AeXvUie=#$A`k-iTkO(-h@ z1?njA6NoQ6+ke;Aix->!o!3?^d$23-|M$(d()RmF{sqP;xgMUXL8Dgxj?rVv@pd67 zolT);S0C;z@{Gz{_R0!BJEZ}M5pcs&8bM1Vy`%R^K3HNvZvp{~gbvsT5}?^h{ECR# zYciJUYz`2nSMht|k>wKzEik_UA@Qt17V07x9gH9MHJJ5=@yFh^7NqIpynNkPSJvPC zy{z7I{~wV{_4?N{Mf||^oBdA~$X*Jw&epSGqY2Y@dUxTR&=osK#qOG&{p+8EOH#%G z(vWGPN4MN&L6vzzf64HwAtTRVHn3HqMzj#38*vCa=}v{8m@*MXtjn?mnf~>aN&&94 zv{c#ML86^nG@h%YR3kws0x7rKk*k!Xfyg{QQH~PBMNaYXkaAMqN?xC#mD+#JVoRu$ zZAJMo`+QANVIOG+FC!b1diL#8GK`s1yJ#ib7k3oW()I4Ie?|qKeeKfB%C`G`(;rNd ziK?GMThCOb!RM1PDW~TP7F&R}I>~9hcA9RK5kVH0Jx_*?ZqfgE+>k64&ZV}@--hbS z&#W>;Xtzv?R(nPEENT85uc_ZJ$OIEC#~X^UqRtZAHPT)usb2ve61J4axL?X{^`OuHzM zR|%6if1D`$v8m^QnP-7}o5O^eUaGcc$_C0z8`IEyq9n5MPc-a+18t-S$+@xOR6NdK z(b2wmN|j|1?Jad_?%U~T%-fqJ8;|2+J(O=8_PrvlKZg`uzmV!!^^oY29u{l{GhE|seJAy(5C5Z?g6CZnHxbct2cwAJhQ&+Dhre&NNQzmN8 zu#b|oz}37yfCwIZ!;#9Z4I{phxLqL%?I=Ru4qEoJG3xNto1UvH{n}fr+Pm?1}~SiS6i z%Cc>et9Q#FL)j}zWD6>bl~J7t@{032Zl4cHC@_ zGBrZ|R4OjT9rb4d!KME;F>7C{df)%y=Jut{ck^1l3%>JJY5G=4uCd6_3;QDa!*L1n z_0TFKc~R+q$CyajUve*kOFm$pgb*uCHokiE_*%zq3q5>hw?MwX>{qON%(l>YuS4A) z!)YRU;RiA=T$i-qK*i`H0_2PMBr#GXt$9kMfTe6NQBAlkYv&p|dU3 zES1u3cVBaK+bgZ6kKSbL2=-0}&Zu4CvLsi&%5XX8tIVE?^)i={UCPPUYI;1*6)rIQ z1a@5%5F8wgLKWdh@7vgNZfE{Dw=c@!2v20jx4YN_p*c+|t3ry}knPR!H*k%$W>h z;@@^alXyIV?@`<*GU|b{+61h-ratmpm*|T1p;>UO8aYfJ$QARbtyDk|W1OR8LqQbO zXe7J2n|z*9rUF%`HQ&j?S7Ez|0Ev|ppQ?x+nN8Ffr$sK3zeuS>Mt-oG*FmOI-vhNS zn$J*8z(s-_g+=3R_q&hYgLRir)EryUUmo!&Uk$Q(wc#j>(BBb*Myegpi>VLZ>}UGu zbEOc-v?-$y*_nQi-sq#dU|j;e$j*#zpodw2?g zPz6qF?#fl4eBp`qR^QwuKB04L2i_Rwt zx4*(=S(YuL2Kx#40^hV=qmCwbQ22jcvgjsd0r~y=SyvnS6r|aPQTLUN#AF9Wb17wP zm~=G%(Z6npd#B*3R!3(kJZWSw90Rll@2+xn2rE(3sI(s?@#r95is4=)6e|!he8iL+ zR>WWk?5gkJ2cx&|n=yo4D8m5LBC*wLG$&%s^{bnPB&~jEsLyDBamde|JQF}jQo%SH zBa9~{!z|9`CuV{6)d68<(mQINu^8%!}g9ak}Z#SIjIRw^1ljsgz^^(fTQ;`i`v3P3btI{1w=EKyxo zl5tqt-{=7npQy9~cI0u;Tg84TDLKg>Dq$5x%N0B9lnMAIo-3F*_MQ)=X+|4tPPO{g zAweN;TfVrR?Xj9iy}Mbj%)JWP$KQw=?|sIbFiEJSWDP{Z78}mE#tprAvdd#otfravw#E@oeU_evclX^M}VPBz&l%qh`42W-UGZ`+wJq1 z-mHSNaEfAkrP+?0!*l`k$#HGD_)VP9?iHn@(wcQu_hU%VRmDC+{n5NFkTTQD9H}yc zgiB=i`Z$T$#O+N^P438DDp^=cqvGHGs`(PCH=Y~Y9|;`a8pqB__4p2y8#?m(iM?T=j5mOI^Faw!1-Re{JRPZxatq{jV*1Y4~7Hwsnm1MffiqPk_LF%dW@m(|N^*-FJkn~6|0zG5q zVOOwU*b$rEB9J%@!RttVaG@Q1nIgN=T!$ayQ7T?h1X;RAG3W$g=unV~Y^>IHGRyS? zy#$Z49TCI+YiZR_my;{!m2QZOcLa(%B(0NmDqQNE&i_Z#xxhtTrtM!4g3uIlxqP&s!_P@cTW_eYo!HKGi^WRo}z$fWJZ0_k`5X$DQnnsGiV& z&j-CIHq!b$kQ!)1v*wfumeU|*N5MO<{^8rtEct5$-L+)bc|#d>*EoN%i%ibV$uW6k zBpNu#0g*r#gD1d=`}0u{OosvUX>2g2ooYQ*r)p@0NNo+vlBI#!#~oE40g7q>{EIW> z6rB_OJM!_H(FMe^JX1=vb*);e&DzDQ*`@atHH<6RH=M^O!Z1UxHLL%^g}aYkxcd+| zml)ctXeUZZO$}`R;ltm`{bzm`f8~(3%j;g^Xj|fGV(7bjWr8QarOvyJy4b*aZ{xt4 zKU`S6ZRLyqyxP%c(wiAOw!Hn1*&eTB?1@#e8Cy1$KfY<0`_KMSgZj^TD6aG5%3kg7 zytrWd-^tPdXA7_Iel8@bmv>{8Ces$ zVSD>$m;)(!%LJc}j%nUX$b>*p5CmRGZa)5b8QZKIDP`-^JRvNLpSIaE6`zx>8fojE zgJ-P$b2#o0|3lEy@m1feYa_d&;%;VZn=LWXmsPc|fs%03F$UF0PW|F=*yQG6#Z{(z zR2QY<8XlTY47WNf)7jyPtZ}Q#jnE(WQz`vNXgRF11x7K^;MEZfRKW9@yf6+_0^^#c zJGT@sX*oqX;H^r?$%lf4^#6q3nx$;~lK!Bk!A8|m{w4c$SU~W&tp>N%tW1)x8cMUGRLb)Rda_Q{wiI6z6>OO2_#U zDmy)!8WX(#=rQNkr&mEDNykAQa4!6__EkQ8T3|~3<^PN=EGTkb&6AHn7nHG}G!sZg zKxs^Zi3r7l%NVLUZ*TwKLHG*|Dfm>lX=aoGW)+TJuaK4fl7_+CsAfU|sZ><74s;&X zsx7CuRwcYoh$-QZ+>iOQ@mWeU2;OWCdHK*R1-#CX_36GzXraMYwOunyt!t_6)F(?< z9>!Q5?H$eD&gFd|>3s$Eu*TDuE`%0f$w#?sSaIKiyn%PHtg+-uOLteM+u_~n*xy8N zbSo<=7S0=K1f*;c*4akQgqBY)os`3T3=p+g0Mhs}>?TZQyEs>3|5JUCAD7HpZaAPS zYqw41B1Ro_!Kuv?aXB2e%1yI&@!iuH%+s`4jA0e0!3+(W@@>YIdpWt;li7JsH=YvMRRZ1%679nu?qF%j1LSn+gRwfoU#Q5R7#qtr8klNc6 zOlU^u|MjTLGR~cB`$LqfEonz2ab#Q5Q=SD!BU0TAMh{D>d2>fLV~B90FGC&j7sKDC zG!G+C%QWua~sVj!z(KrzuyD4F0{UAVMQ;_M=3LkJ?WCz~T^)x{#x^3XK@k zyqpV7_ZvEsqAgxcI8pV#KH?TfLaypPGm)K%ATuJuqN{F8uYH5Z*WhHkxXX^waBUUwAl<1_djgexyVE-2L4G15!YQWa@5ucE_F{5|6h$?W zz-%jPeNZ< z#WKx8FH`Ys&!m9DCq|^C`CFwQXfy5uOp9^Fn5;9%NPF*$ePL%C*283`W>ps58kjR^ zkMTShl(v?pN$K|v_S+N6W+Wu~hqkD|IbMEVKlioqRw=dYsP6eQTx< zn|3Qk`R;dW9>;Gn{B=WWZrA5?Jg#GP&0i;YZqA&(a2TW1!s)_Q7Bi%9YD&naq6BMv zXmMX=XR8=uW5kp9=J$yRJF@q6_GkM$i=3ar_bH`hJ;jh@T+L%9aaJpcAZ@Sjg%IjVJF_GrRVtmE1TYzLqG%i}*SyZC!Q|7p`(V*imAmUHyM;IzNJ9u!si z)Tj`KDp0>w@y&Zn%W8^F_E65tbe<_ytvcJZ{J^K$%FitiIu^*l>bpGu&h!p*Tpi(! z#AeR%tZR$t|8jU~5$9PM9=N7&fU9SE2SsTX>#eQH3ye{*%~$%+FTJ!T_?7LOoLgI0 zFdJj7Yx1hr6daZ`Vd~nDz_97jQHj?0BC5HhF&e`_oMLG@M)fswIsRE;-~8&Boam^~ zsFkf_2MlT0%<*<4&ip7ZQ3%b~3d8}KT@DVwwSD|zER)ij8)4Hjl%5)l@u;T1A4F!T+IYtzY zCJ|tuYT&Kb@d4I88y2>Et~Pe8bhHQM{`P)}JHp|P@SHp8{bI_?dD(l0g%$9TFBW4rTkTOPR8EcB5~aw=gguDe;+UeFAS+w8Am11yw%&)T*RseoQLd zx8~vJfzE+(TDn3W@wX~__eAeMFHH5vs|y*}thH~Hih9RwGbg&NJu0z}Uvp|rKU4^>%^9_ zg5;nL3qSmjc?rXTg#)jax+eQ2*ZgipSjcmu7L$CQajMSIs4;2G&VOZU;@YAIqE*HD zq`YCN;(bJ4+4>k3viuw^j zwF4nq>Uq$drOKO&vohSp6zmNR*WcDBOd^$Blyz5TL=!!p(=6=QirwW>6^T{?ZBID) zVD2wt>R)rNQ-%i853`TE*~KQ6CKeB?_Rj|rsY``WV03d-Vs}hbpZp$}|G475Z4YQOp%(8*p~PLU0RA0wDt$y^_?=6Wp9|II*aitIV<;;!&gpZ-g$O z7pmKZ&k+vYOQp6#`gzz`)#GBeBxF@v29Yo(YE{o1*}!CX^mBfrW(W9_yq&e- z+S%!|e`krkA)=+}=Cq{J49zb`L*(=Nh+tscMPi~D>Eg77F-?cVUIS=6R%U-hEmBHF zt9O3Bg;-Z_Xgq@A#2XKYP60a8&a0^{{B7ojsZoha(q)E(DhoIJhJp*gdIU+NL&b-u zGM}Ho+o*KRSGdVBt5FQHU&S=4%P6W^y!kj#l}{#bSv7Z7+e4;&QO?HjwK!7t;LQWd z`fxhfW6w9`t+x*Ps9^4ytBz}Tu_CxD%ah>!t;tPt{;v40U*epxKQE|=Nt1ZRtIWY? zlhlg&XwGE&F2#<0iYpUg_Wq_;D9vV3bd3n^(!FUSyG7(|BvL`gJ zL_`5UHh|ffoE?Z%>o^tYA-726xca{7?pPvPU{yvr!b1u$>q1v#RN0bRhV5k8WE zA-$9^E2834F|R{w|HT+tzoc_m<=hJ8GdaGjNmM9s=Ji!*3r)6g7o@FU5ja-tQBIqm z=pTkInF4O^NnV7zlEKBsE7GTkg20IAly}AQVYtbiF`|U!#vuiVdbhN>meh5=oau3G z_H@7B^uG5{;kuv$t6A`pzN_>Ky9BC;bWS#JIXq}(3Yn>GlerL~Hr$p}26V)PN-h^8 zNK~T#;Y;Q#1&RseKC6#RgO*bLuh2Yz^IwZnP*1J5Loa+hlj0nrNi#4WQpH&PZIfd% zf4FHXOL8hwpB(Y_j0NocXw3BvRW`YUMa$XV?uVw;gn$5yBR07@;9`F34k`dx^7 zl}dDQ4EPVoagv!w7f5so>c{AcIBI<2*{;quQEx({8uX2vA*m_@!MjrI10jXUR#Z*? z1Ko*E;hxWimioEJCo*Fl#+4%eofDsI4pEf|n1ygk zxc+2jYLBhI^uy|@&k@i9aHW%~w%9nua_H=!X_VXg?lC6jn~c?YpN<^)-W0pLerc{7 z%p2*rHmUo63EdkrJ+0=e8OiaX48tz8jlMb92sR@G6wk_3#)lRu6dr~RMRfcC*9?1N z84{wi!^J%*lf$z3E#uV;0R9%&MbS1C<6r6zRn!m~ySr{Y9EUt2XOc2vBD_c48_t$; zbOcxG!#jQdP_xdj9^Ci?JZvIl0D zf()!#-(1oD0Mv!@iE@x6u9Qqv_;2#rDk?@gpe;nE!VfZc1zs(#C*mjt0vr&>Qg{H^ zQ;VZPQ~WAgnL&3&MR_}(9`W3$P2W8g;EX+0cRs;;vPtA0jIY~H;D3#2NiKkmmeKN*p(RsM70`_D?HA>$U z%b2P)To2ZvWp^T51WX(6xYU$h@i27X-4a?@Fg3&)6}@)ey%jsaAp^%G9}^kQQ79IY z2`90FK1aS^4f`{0*bs^luE0fwghm%7U>pcfSbIpx+TzXq0t;Z!wbXZ4_6|NmJ%9Q8 zE+?1vJP3{semFEBn5r5$vHUH5&opJ)A)`EXj?NOcQ(qhEy@C&3-4-!ubzWXfY)YVO zf}ekCqMgCHm=qB03EQa*M%1BcR`zY)YYB}EJ|LVz$JWIJjg#V4p}TkSs;Pxrxaul%8Up#@xB-^H4ldzmpU*tDXQ8E~a&=bMrK|d8IXOSsZdxru!w&5YuY^NOe9rJ=16!Cn5jrIq!<#CK7Scqqw5sN+ zbZ*N(4J#^=`JbKF&W4ZhznL}!FsQ=*C)Q7?FH25OKhygb>fP{RtV6{G9}Y5|RRf{} z2AATo-j@Tnw?%g6S31sawRb+)bY@P1chsPvQG+7GL+f`(72R{ssvp0P`w2iQVO9GAneN4I5 zGy>YgGj_v5I^A7hm zXL&bK-_&(ix2Lgt&61XZc~NOvVyzq*rd*d7fLlus&#Vxfqzj>6zLa*nC}qe-vqs)JbLPy~Z-cfaWj^12M~E+5szMY_2WD1_(+nOLo1}NA zr7k$;;=x~A_w9`dw_QBAC!&8%jia%xw(X%@&ub-DzCW_m-~R`Ej_4XO2ou%PZMsMv z&K(x7#|hvtcp%VVfU8&{P3DmD@!iNAPpDiuovIK&d|AbWT-;lDF&T4{@s}D%t>R@M z_^Jqws3INo;N*aE7RC807v{!(_{5!1$qXXFCMpn~%}~J4F1J$3LrM!RV3S!H8+249 zr7)u)Bqeag;NNri$On2k%{n$LfBl#h>lUB=AN*mOUw-Jb%Wqu2eMZJ_Q~&o=_?-5K z16&LaYs6)iIIi@p?KtMxpXs~>b#2?!cC-#0ie_tTrPNHOsw zme?A(gAYOLRnJCNox#^CP?0aw_z+zph0fOSdX5QZUEi4SE2#<`ZIPnkF+hHcQ&2w)s<(d#E za6IZ2;H!;hKn-5cG&~a~;cfUJjI~Y~cSu~MQgnAZ4N~ZuL5$OY3fK>g<6ybZq2-1X z6BRhm+&jlXEP_GASsNL|9Jy$Q=1Y)FPON!#n5;$XTXkKP_Om1a+iJay_TLt5 z$S5uE84&EAX4__|Om3cpua?JF`T=3!$ZeuR`A1*1syc&u_P@CA_A9IDR|K|gO;_$M zCN1|Zf?)A$2(`o*{ZP)%F!_;hk_>3g!N<}Yv&)WTV?FtkIs~VorOt*3q6m}cm}P#P zTd#IqRX_=zJ$rZN7Z299?zrguW37hP52&xaK6nsmDp7FEAdSbU4fX+X1<`1dZUMJV zD=NP@cQAP}1H1SIC}!I(d>G?D#Afmvd=s;BR4MouxAhP4M2BN+|mwLwjMC!-DND+DZhqesd0Cg*PZ>b zi}hQka!CRz$C5JDPbxqOg^Z9+3;nVJ7TT}=@~h=z4zt0Wi}aja(sAur z-DgW`uP*UiY|8!YhCxZ+713oS0%EykWWHaAr;dWm0t`@=q+ zXf7VW49*f|%!blTU!Qe&8mAd2Y5W)VmgQue7F@X8jwMW zMasF6*N5o*3A}6VX1pmClhc$4%=~w6N9$dVeM(Zc7AKsL3Qa#25^ra{gmYTp;dFN@ z4lGv12J%XLCZ?)H@fBCf1>seywA_stgb>6YPM;?b02C)L1DAst&!W5%6RpZy_1a+s zsa_ijXrWiiWcxG%3`irY_c6LuRTRb!4@1H-rA7k8-)Sg@a{H<)Ha-mnCM^$Tfr|#j zE6V0G%2dZYGzK!L&EC@KJ?iNCVo7@){IbAW(2wpXDg$$dgbJ%yFz%%wQN#AuIva_U z%wRH}M0daxCOv@?U8V+z5mC$><;#FM(^`jk2;V~rU6rs%!$kD!zohI*gRb6su9y_|<4;ZV?>^p`d(hii zQujqd*VVr5yx8f|f>Qr-CQj=Y7{tko%Ub2-^-!{7zWdG4QMQ3`F&bla-An`l@uQ*P zRA*^83WKDNUeQc2ekHQ4lp6|wCz0LKbEkn?RWe5Um9bt7h^VN@`>vTWM#auy)-*~c zGDwc#*5Ihj4$tPfnVk$%;#VXE{t_%(q2MkkPR+_Q!QvKz?g5ac=q4f}>`XG_o z>TCFBUy*08DIBe5vpOPX*b0v5FPktue8Afm0!h{N)F9G3m?tbsqJy~8T* zu6lgbvfA!nv#v~WTpgC%9-HuE^VFx>ZyK6ACNw?mGnR1`WtuV*rI-*|1(|9;V#Wt~ z!L)^w^8L!Q_b9K7uGP?V`HE^DlldbAA+{QzY>Ln!zVrvi)DFd#kE`EVVZV+4Y}TH@ z(Ub6OlYAH}9!mBIqi|-C9KlN{-3<-ivFMb$ICjTliJv;@p?(Zj&3HMENdwRLHL(~e z1I*QYAa8@!$NEh}gBloC$l!mRnlO#z3!i7k{b^J`93G9SRQyn(Q`Y)C#fa=i;Eq7y z(kyuzPzw##D6ewmWVVIGLlmAl@wJ=@)=iaEpane{KZ{QJT9`%XU}9sRQ$P|`+n7s2eKXY6LYWKOrw3> zAyu!vJ=Z-b+8PytSg83GW(Ap_G_)cQI8Q}q2=6Q~CV|xK+b#J|&_i#?N0q5yyR+3D zjf>|cNfw$)dqWYQ^sVzY{ouNX_F_hp;+3*7xT;gVWsI! zQWGkYxCNNWH1vK&7hWoRB`js{>c;q`jnzwNZ(2B`*VWURxvg-N=*#VjhTv z01bRP`@gN~{|gHZc8-~OV+{TRQzC{Ei6>Mb-NS)ATGn2w{Qbso>UO!0_R+t?s!u+l zG={Hng^6_pAxB`&0a8i?*z5zT134TC)21p)rfqx`g9W-yh;`Byqfv&Qd#&BxH$VKV zoe5S`KEXuSwl>#*@SH!~4c@lIMEX1l0FoadK&3E?*brBNCJ|g|b^*Q*4dQ&6C-WEp zv_FLpeyErz26-i2c$LC`4Ih|*TZ{+eVV-?fO-;VzviW6zRl-L zJek-7xN{;v5OzKUgOwqhV8R&{bz;V}&xh|QIGMI!MTyrHVQ;H;G*;Dh-QL|&=b6xb zsIL75{M+%%9CXdATk=oRS2ZtyUuSV)F(zu z#hYSP$t0v=K^t!lbtkc)98hYi&E6e$_S4=MlVQaq-_p+O)FHJX%D)Iz7-TB7;qMKP zPw>T}1KpNRTeiCTN_HZ_l>krrDixUnZ<(e-YK7M}dBPfpcN=4YthK-Gsnn`c3wzqC z-TzPFClsYX7vNo}; zY~iSn`)m%cWLo=pGc{;N#m2UfpnquK=&`}sVUJsH`S5|6ssw9aEPQ?LWlT;U03QfF=o|cJe7ue@gIeh0MreB$Vn)gv3$vt;*H$ z@sui-CztuF$qZTK!37TC7R=NGb?q;d#=>R`PRipE$?xRj83RM;tA;N~1=j3|rQ`Di zC$6Iy^)3KsICCNZ@dS7PBEAkem zQ+dqs#HokA`y??f{<}}ohQ`0{SD2nQKO_!^BCATxK0u0BGW45)nh)1$ ztg1pxNxoJcD_w31)nFau?eS>7Hd(_Pn-W8*@3a~=W1)fKgiL*;#gb#O#n@~`e;a09 zmxDfBWUAu+B=Us4%KxXuk*XF&yg%nnfwC(Cfg;t z!}_~=MvKvl^TTQyA7`z0@dzf1I_N(K_m=XFDv6|bkj0^5EX)*FG@hym3@A6s*>kQq zjg_X3fGdQm;@_(0Kl<=Zq3@tl3TekJBvz42!#MI1XsOD{fru1b-m@+!y!u_YHHwiw zREud+f;8&6?{C@Eq}Y3uHJr)Z1LMM(dG-xZByT|6eOD=|S+&mBpbM1MzpK4HI5RGxduZ&jey!so8k;+djX?!w!=4Q)CRA~KfKa{3i}=0^ZGH(gNMc-K8F7@6!=aK6vB^U;$7`1 z7A7EVLX^=&bop&3=;D;UFcY`Wt zY~3;C$$y@oUj5W-Pd*ql=7xo%|Iz+hm)ASK_bpM8BmeNn9tWddo|v)D*p-)JkVTa%$V7&N#dKY{`|AEz3%>9G%BKm)6DKb;k`M=U#)u zrS;(q?-s zPAW`gD}YE=z{Uz@g@x=iCg8ffDEyoEs_jG<+yW&C{U0BYZjwa@&-{J$tR+K3Kl<_Z zJv9zTTwG1{se5N8hJJ9SD7YR_j^|{yQ_EKDC+rQ`By|cevgM7`WEWf$n?*EmjC{x5 z^)b)RYWq>tjvzkfK=B*dSAH7nso0yV4+#z}-hR5a=_)zf#aX}qt@f`s+|GLDh7w_Eoot2X8Y$8n~)T(R$>@18{C3A#s0{3O&@)e(>01>ey$*(38VE)A&WAUnxv9uHEoLf;8o=Bx~JLVRStsh zRq83rkm{x@3{WiTdXpAxa!nPZwKD-DGtjb5w1SFyRN90r4FgRQbj!u*?vYLmHhb?y zU9;~Uue+8<8plAdZ(hx~vORLy7jsIM9dvXzIu6%$jqt8;T$|W^qjlbw?+pvJMp~A} zTFef5JfMM4@=^@ez)}pfGJ(-)=Dn%L9Um>t6;j;q@dWRmGPmE`=d~xOL*i%=# zTD-S8uKly6wZ5~hv?y=n=__%hEk?u2T^}u>d;nVEUp&Qf$=?-xc%0K2$M7J+7v&1a z70|VVs_7G@P+}5Q`vT6>sAHXzu7-nKX9|+l=O(o_=?#94{p#;6?FnK|%lRf_;Va}o z+E^#y>|=@2eAGovM^53iUMNdTpU~e={f`t4^67k_FAIxTv8@GPa{nO{@)H zI&SGC`ybe0H<|}`vf4h8x1pXvfQbPaESvq9@9eC_IQA)KF5Y%6vEMxR2InVPzkU4S z+xbtdDu}(jweH%5$ks^j*O9Fc)^!f+{%KnoUUbq|;kd&O5m^jEafmvgr&u^^A zdmi8{P(}5gz=@J##qb7Zd#h!C^ z$L<>f`*LNJh{ z(lc|HxT@U-KAgfVAc-^0*sgUH14(_Ym58p$ar(6l{1Z_*Ye^2Vz40oT-M#|7hMPvP z#Y;jWur!jrXG?OBhm{RVnvg@l==`n6u9%qkqS)H@q3Xma!~s=%njKx0p8sZb-Ra$5 zz2tG5?Ne88e?Qy?mR%JN#^#vxF9IMO3ghf!81RAyN0BQSuKq zI>*sW=4|aFD^+#zhG04>L)4ui#{ut+b8&XojEdU(1f2Yl}ES;hD<6&v3M!+lGzyJP- z!_;HnhozE}$}mkYza!9^5z%%Nu)vIWXj?{Tq-GB+hD-^!6_p12r80=rljvf(XL{ga zd?Xe|-ukT&OOfj0R4dT&DshP|czT?j@B24sZ|HZWg-g!{kwJ9)a>#L@#q&jr)Wz{* zrT53IcGt`WUoJSCHtO8+yhPXb_9$1&?xNQ>80&Ar{>#;H2iwcrw`5N)<=1j%Z>rZr zIa5IyEJod$hG|xab=g9?BvYH;a%TR^bqwbMEl)X_t`|6jXR5G2!UxK6iw?k$OZ+Am zHh7QQx*weWzaFixR5st<)V@d3J$hr`bNm(+^rEf?S+DbTgK}9aMr(bOaEOn>hlxE$ zSNa0WF!T&8RfD66c)2Q(u_$FZxyLEjogAOK4)TCMDsASAn#6)huW%n`%&wDfkgZo$ znGCs&{wb`Z)P#(k7(e+|03sHx*`PE!rqbP$ZO?s$H1B2FH9VEqbI9IO-E?+hcl*Tl zCP!ygN2YhP_qHYZrD=a1llrvEv1_ye%k}w-K?v-~-h@ z4mfuI8WSA~iV^i&Eb~$l2Mh_Xo{%^!q04iZr@q8H%JExQ%hmdb%rw9I{6k)*Qd1(J zNDW9xQaLrS2nkIu^B?3NUG8Ey_B)C;HNh7jQWO(__O>+DoyR*$%T6|9!zsI%!oBJh zxk*JN(nAEIK9+A&Vq*KI&RS3uFN^d6xYwiSSBx87+4FD^NIb4*lAFmg4ZIIh$dFW2 zS$qla&PS-PfghiD8$~>5X7KF;aYPz&exhxu_3gP8U$P1MhwR3iXjqde+hn8<*`J3mkN1D9a4m1|MJ z;TG3G?=px$5{WF!8e`MrkuNkz@MEo0Jh@Q`2cwJ$1Y;17zQkp;W9tf3tDsVc3hF4t z#7%ZH3AsHe`X2k)x%i#jc@|6E!-?T|vYGI%nBwMV;U;)S6X*(Zi3>--noa+&UgLL#c&`Foh7aV2{bbh0=?|haCtD4zMI# z`|7-hwSHmV%S*a%_qsAY4|vaJd1iHgRo%<7A=v-ioO7?=7O;0ma%$p^r&g!iV&uM4 z>#?*VjDJh>JwY~QpF*|One8cjIHfMV0ADk-HRHB`&< z#Lq&6!oPmc5~GNXK0~^8-bZaep4vLKHvHIhBtYHI6-A{V9NGIpdcN`aHD1?4Aur6h zxNOWleI7j;kWa)RxqkDh5wq1~H~Wq9ThiNu)u!_>OEgKHKH(+hv#eJ6- z-Bse~o>kk{m2keT`}A@9g*HN>uJ21|H^@y2>=ko3HY8;4klHs|H`r~1!a|es!47Qw zW1Wa>$4t%4pm$50*{l%*Ep;(bh%fhhcd@gwyp`XLWz1CcUaZI@1Pes`pW)X!B)xVo z@sk`Fz0Ist$ruo*;n;VD0HJ_-Q>3P~&IWIU)s{OM&@%V_+;^(&XD{Rk$w?0YY0@XY zcQ*zL^}&i8*M&m$EgmPIKHBG>Vgpl#MOibQM&^V?xvJ8?Xul)8bmW+}eyj7wC#LpF=yL7!e2)vL>ujm3_jXivTk1NddHZ`k zBfO)$Kb?HS{?36xQSYpYPJUu}O6m$0^ZdTFbv!~NV$0O$U%_uIP}pP242f(s-fet6 zpjA*F2eny2IVM;EwchEv;NepxEi15iT2gnkci24YjXyh8R$9Qv5qcS%MCS6d`K z-m=v3*~Gfw%3fnbZQ=Lr{cynSfTwY?8v3f*P^EAg=-;f7riQ}VcS~SR+Age^Ffvvw zN6e4%!cZA%mew={w_aY<^0%C-?^X`Ic#~z1?VClML!S`lXT7{)1>o~fKczvx47fiL zk8qfaF;)44zPp89irYmB3e*^94m$0u{R`a(w?4W3l?VD1Pqi+t-*Dk%YW+t?Q{h4as=r)b*7!+MYxmlI19GqBb{vWH3~TD%pYWr%8zXg>TA_8HT!oGn+g*1g{#p-qaWE-g1T`#%Hl*6sL$Y zM23B^AHD>0ZfSNp@;P6V& zu!uvcHQsm+sFL^^s}13yvWS6%5xg!RXXHi{7`_-^iVkvSH5nZO9jUM?zF{u{C?I)& za(!52@bP{B4k{~+{?G6LYXIhQaD3;*xZJjBO)l@XW8S|zerxO=ddxHHsSOlc4fw~C zE%d}PfpK=?xxuNmHt7-JJ-7oIRl#6-jy#;;Q!8YOsiIYsE3tDrF$>0!U?B`k*(^eX z_XZejcM+HKh>CKoe;jFmhw$aQ&)-AHnEr16zin5Xoy=*&lT7&RewgBvl+ega2m9t; z>B%Ec?C5T*Yn+(ddh@M`IDXf1*LRXjL4bZFMd5fC9o)N4H#lYaLTa2@TlK~TG~FT zsf(ENrQf-(&aIJMKfO?U^<>MHAjg#;&*yVW+>r2t+B)0bV0b#xbT`kW(4V4|9O|)j z1seSdA6&!A6nZssllWaiOiRiWiA3Dv#YRXektqWR5KLeW_klm(O_Vm!S{GA$huFP= zif9Z91Bae$o&xnezIlphUa;F};V}$cg`IuhW15S{*H>xRF#d;fvx*+!Boyb1&jljNa)_3}=Sih}M3$wJOWSe=JZC6* zDnI1R8FBo`r^~zv7eD*!`#XOA%M<7KT>fWpdDxx%=6$?j@PK)vySxeid9tT}{@6eC zeYVGqcTE2F`?mkt@B4Mr-nD=JZ`2aU?h7ApA9PQj2n$<0cJ6%8du_F&xqp-AuG)@E zS?-)@Vmq^k66s z%yqBe1;-{WXnJsE+y_T?f3&b=t*VU<9*laS|NN@5r<^xFP|^NvrQ`Fq+$+m!yZ+g7 zr82=A*mldDm{Ws77H_@{B#5g(Im}da)no}1fF_pKy-%BZ2vpy@f~1ng&!Z|nG)Jq} zc{Q~2VTi+We-huTk-gtz_nS6J~) zu04K%&#Vp!EFM!&*m?ZX+2@we*mn6y*TBlzx2{M%wY24!y(OsW`~>fnzV=^tXExuJ z&~fj?NPCYy(UOz6{ff9AsmHJoeTfz0gVFs7^;{YDdb)LZ;P2{JitZo|d%>!{#6=**?ao8%Y3cRE-%{uQdE{YgDYhVY+RbnM?rtyRN*oX;MAzpottSn5=ub*%NJ==t976>4Y2_%Do~fjYMAD4tQfw_T?Y1#d40n; z>pG$}Cx-qbKqrmHX&-S1`s76M|Q7|b#xr@L^^)0159`v?w#H<>tF>^-k9-mQ^)#6nio=2HhQQ0AOK9* zNe?cWK{HV8ufM*T+$ck`)7}4Q*x?#g?4%;RY>})XqP2MQ5zR)?8B=JqxLP8qDv$C- zyf7DbB-ul98O=NM*75TaPyfL1n89mWV@}WXTszj{Ry0;=Z@$}eIw;}v&p+Q-F!dk( zAps9h^GoeNAtuW7`5fiat ze0^&35bHYnU2n9Yag~?mSGu=MJZ~xKSk7-5JtA)O6drA7#=`WRMhSJ}br;6k^BcVcyz~oXo}So!Ze4!a6A=%*{?VD>z!f(< zTpt#d=6+QTH7JJg8l|9A0>Cp$an300VKiLVh_m%nrs2U6(DR?Lk$;NZyWe zf2?wOa!vYIxI&-+KJ>)=0(ec;_obKh8;2`enWvmL>H{iZw<-Q2q?qAemN~EAw(-~- zof&2KXyMAyFK;+-IXP|7(qBh?7~3}El*7A;i$V;Sy#JUSN{xf3VEFPK^0+7I(=R0GU> z;pd-+#$Vl4=`5@i{;V7 zD|X$W&PBdt*ms;T3RN*(56bIpCe#1Lv_&y^km~fBJFQV{{tB@Ssh{g+#vc-{WEkhc zy|2?sSEFFWWg-~LD8v+q(J3_=a2MGx?cDvTFXvIPP+kzau<60NYu{G3|F^1^*+ld-#wBJ1QUM*p3V){K@G%`$rh7CX^a zWc%RVwg=;u-)yCZB7=#zo@<}9b$r%0_rQqmpSOBWk4reWxh{9z$-F0T26axZ2=aDIP+HOkkh2f3s!^_gO z*Cz_sp`HmL9petv#f+~3GgI*Vt0xQ# zGk45MvL{m9Aq5kIiz?dJLo4brl6UCGDMz8o9JXeB+&f9hNq%Dj4k%A-U6c61y8({~ zgVl9=BJ9n(GtXw#wYS;n_WzNd`XxMVlpmL|HZTRAu_De66bpFHsjI;q{@Euw-gQ85w469O8(8h+@X?F<$A>{#t@ z_jk9(dS8vTGiInH-aDt}r5CqHr8KkYx~M5edex|LN|Jt+QUE$ke1j6G2eGa5c9ZVq zm$KYQV`^4#}LJA|0mol)< zxtUP4Pb>-hZs%RUVn_6826nfYF>^v~ zM5}`>x1+I86oi3&cw4T))F^PURmX4QUa>(qnCX&nR&A2);WXm*Us`;Y*LZ$)ku$_E zd3wszV-tt#+Y$C{+b*v`0QpC83Z5Pu$<&q~v{s1aWBk%_^&{^|d-AF1(8|E=dEwt= zxL3TImKWOZy^|eP4)2gA&wrP+|7UY|zT?`DRqe+zJq3NYZHq}_cHhmfy6#_9nuyq# z2mwLj{bJK|-bM#)nCJ>BezzwgoxHUTD-1LBhHC?+zT{;Y;noUQdc83GDCauLbdYdZ zAikH#d7Nx~hFrKo!1d#HwMXu9mA5`pVb8EcG-2nzxEq6C;Th{0TaO_3F=GB^`$!pS z)Aq2|d7ejDv@83KjIxY0KYUe&`&9(dh<;h?!_6kiaeYFqZ1U)xL=maYCI}2EZWg`P@%F&Gi(*<0$1KY%dbT4gmjzg`PY7=~zsU8EYo5P(> zmTZ(nde7Zuzce@Vtha;qw(i!1t~v2-haCIntr%PiPdO|~9)UzQztV;e!hP-(SBNB$ z?w>euA_c3*Ul9t*enjz^Y6^%OEYo@~-?#Tf+uEY>2bfZlGO{uhXi>iD>;wDLC#0qn z76ms@ol+xp(d!BkxDSH70q1^*K6Ns^-(#&tMp{_kjUTOQ_`2(hM(?Ev`__5Q+-3qnnFc5U8XrxNX$yI7SF^Ye*`VW>RsgP4=4v|PAqPZ* z$Ps{kP;hLiDv+qiQ)R^BY7_z`7?au-YLdfUYZL3s{P)X=E#8dJ_+OuZ!`r!($X7?O zgR+p&+^`Nf{LM(LsLX|qm;6(=t?E0pGIbcg2E97Fry}NYrojDgS#8H#Ev;3Vo~5;| z-wFwK_iFj1Exvlg@`h>28A;LZ6|I0D%+fBYuQ-~IY@;)B{=5U}W>{Y0=qcAlU8WQ3 zjj#F$zf49>!it?;Co%nVb4>2Kh8K(^zKPUj+uRG$8BZCx!3;pAG4g`a+g-*F)Xybp zlNf~(e1bMOe-|3LJ$o1^hL{&MTiq~0I(f3Wymg`EQ;sI9FgdBPfnyLGl!}e#-0?*6 z`NBZP4oRwoeb&h?X)z^|R?nh`F_e9XVlbfUr2je7WyYUGuxsS$|J7_W>Jel$9Vepo zHoPh<&Km$WgRo3s?q140H2xLgzBoLl&eN9JdMV4Xwa&|yO6^G7G&FfM>6KNS%idl$BTzJF!%(}L@wch=a?r$q||CzPye6`0DQFr?1+S5J!7pCXuZ)ze?8nv;n z51W~rtqSmGSWCqliIfnYU62lKBz3k8;E+*6l@V*w`RF%|pX9zc8t4mL^Cl%h0Mi0t zC$ULLIdBDcp~!c;iyxA>8yDy!fw5EzRY8X6_9z~+vIsg#&SmzBg+@;2ATU1~?3>cX=p5p2;9`;n9r5iDgymxb8CaWMv-fCk>N((LglYxk#2F4?0hMtp zDueRjUu)v*H?n!rj}#FbbUVMQzqA35txPhe83D)W$t)XrR`4cRa&b7v3R9>*((^=u zoc3G_tJ1^L7uv7<;+N3vp4je+s5|WNlz0a_y3ael$n5$y)BRDx#~b>+SEwOnX*Dz_ z@=}Co3Xw%%3Q5Zh$eto+U|#|RpJm-fKckXwMKcyWO%d-wCC~_IAj*36I<5j5vYR`g zR^o_ev@kNg+ZNV zqI*?I_b+qoS3bMBe`H2d+NiO?ITI*Y`&*B6T`^kcnY5nl?29JEU|Beo+a<3A12go6xc$pol;AoWy0EV4qox*B@rgMMFuan%0Errg z=lg#Iy%alwJQ1Lyawu6Eocg20Kl?OH3TP7aK~G+-&haj z9Uv!h5r%C6HzC20Fle?!U4IGv59FKTe$~~cClwU^ zjDF+FHx&q>U(U>bHJElKbyRdj|$u;zoF~ zl&`4l_{-zuE4-W1diynu$yht-nVr*%$JZv-dLLZw?(xVU%5M1nhL?xTI#OEp?Xurq zsJz(p*Mz6$c0T*;pC)_CD<@Eahb_bkG2DzKTm5QLNQ^)ct4{TAS!v%VzW)RhjQEMq z(#kGi7vvDqCe+8s9WyY+R10X5jap1lO;c*E4&)I89?f-k27e$kP;z6DYiCGuz~VD! z*U=B(`C3AAW>E38$-aFI2Uccz4z{9_{IWqyPD&V{u_4)21H_Z1{S##BTxAE?R)BeaOr}NuaX%LLxh9!I*evj^EK@->z2MZ&L4sAWZ_+IHg6swC zQc7&5h#=-Ep~!Gl*pYqvUVLcMm0$jF@zsInMc-VR%84iqqPBNiqOA6~r}R+m^Qq2}$}=;M7kj5JyMzRp4)k^Ip74b2MB5}qcy`+W@U6U}iq*6WUO8HD zgTiMllcp?i;wpL721wn2E@(OB9~tk~*H%+GefQg+gpRUBh03R^6ELSTYRe%m$1F0c zNV64H0C*Q(eDU2?+N0dq*a%e6jZ*ukyBF=y)E`%OevNvYlA(8`&a(&sy$dK8IL4~B1@?+9w1|KHtuq97vkn9P{ z2x)nRg*MLm`9}})WI`n>e#Kxicts3igwn;%^XKPa%6w@vy4P@o z^GN`u9&2UTEK-1Q(eg_Q-B*G_`3Uos=z)dgWLgyghb)vTT}(LvQ%+bsHqgCw%K$VZB!ajMs|~T-Zd+SG`)hOP`iO>ihcP;&6P7S-l&V94L~}K=%aAFi z%@E>?s(1V^Yf9+dhl32k;&o7pc)3zn`(Kqy)}=6SMzHUs21e&VzF(OMYkAo%(i<=jhd|apq$NXmnCP zcEsW)X@OViE2^xe*acJNM@arYvt-cgx1~fY+xp2S4gCnt9&TOmY4f;9$I-}K25gga6A&dr^j?;qIDvc5-t$ogZ1De);gTzhz3n-Kw^b`DvKb|MDMq&wTnAGK>g@NuzCRV=T+QWlsfH}*q9!NC7YZBZRv%eeVP_iwWdl1h+(-1gG13Y~ zRev`ZSMwp>P5u276d0<3@C2I3+*z|QoXM%rj0IED|5tho$(Rwd69J{F6E)v!UgjtN z36JC>ChP}_S{$L-Oh?mL_>OnwzRYYV1!nWPVNRTbhy~Oe7q3evM$q9*GuaXDgLd9N z&4njyTJ5^U&lQ*MpTGS5&;zRrm$VG8WogpUagoaojC1@F=4~(W%&KiIuFQLHY1yFI zfDLUgZ^=KmNBf-V!6eH_nvhmqmh^zztXgbA{V23J1f%K>czNwOA?0KlH*j6q>5h&q zSQGW6{ zn2_oE`02{kp{kAf@PVf~)-`$Kp{OETf6cPL=DiZ(-GJDRUt6#xDTrg-qcmZUp`s#B z5$Ixc|B_yVB*jFZD?0hz+??FpA76T@l(AJZ-Zm;;`ib8mJEMf8z$48C)OnfXRhX*< z&_F3$Ol&|3Grd5&*HuAL1CXG~Y$2PewPrOmlS?>wKMns;ZK}dOUT=8=+H&aYv#W9r z$p5Ta^iU=%Huoo5jO3OB$jMXmgY_-;g{{E}sCHB@E{rnjt%7 z*zMtO&n4wwnw+!}(*FGU)e?^7w|&JY1k}@-FYInbciDwBI58+-amSf;xksyNe+~0` z+Pa4rgL?96Vf?cZMg0nUtj;bQ&~Ifct7~sn6ZTv9uWZ@oG1UJv)?AYe*sx%~)7gek z?#swiGp16G3y~LF#V|l17I3A;42$zzDr$TfID;qRE3Jbpb!(`ZR~szKmN^-8;~k~} zKaQKh;a2Y+qmutB2R|Q(h%dRT_R2@t?%Ik?G@m3)z)l0-8JdOqFgsU%$mn$QW(CDq zvXUX8q1aEw?fkp?$Xr1?D$CPZrtX^+q!6mSyhbaX#T{&Im>UxKyFXUg51R@GZh!WQ zWBBQYr6EJ!vER8df?1^-AOG zoDt>zXhKY^Yfk;3*a5E8&_ObjOX}Jp+Z}b+UhH-!blm0nF{^u>=UP)o$n#&m2NbkY zXwuim{^FWc+hdwh#b&$z4q(T!AZBV#5^YcI5fEd&bN`0ehaS3j9Rz{wKj{<_jtoY_ z9An$fY5*BTRC1oig}Bf~2EA+3kEzo;yn2H?e_B%7Z8yxp;0C>XHR`Dm;mNO``FLAL zcV_pTTKCFGx0hNG&*vrGw?%r}?+U+Ty(s6S3l6M~o*5ojT3))mPt+cMiHcNwo#b2s zS?|_uJoKZOne>I1qNypT>j!a;vW4>&*h#rjUlk#+2j$Urq$jAg5c7=>*2ce!E5^cd z%A9Jd7`+Qr)KFlc@1_`tA@;t^k1(Q{#Gtujx)=Q2i!;>52Nnb^;uU(w&VK#S^7Q2F zPcN76U&?0rc_?;Kn@@e+NF92L{EV}Y-SJfAYF6vfGp|Lu>{KrZ32ffEF~Yw*=-owZ!qsb0lKaG0VI=%<`~F}j-x$Je3ev>KJGW2-kFneS=n#Vfj(+uNybOyMcs4j zew*8^@{V40A4|6`&hIRbs7bHc>9LV37eZt3mqyYsWtA{Z3az{V@D;Ewal@yau!d+j zJzpWjHkbl0i`!)H2A9^6!?SGhJBOyo{IitNXFxAZo&AOx)vZs*CyfiF4wfnTgQB#H zK#6+QY@0SPR2Gh1Sp*ekQjP#b)yyskIBb!KA6z31S^!Kyt>{TbZEIaSZO({vF!WMq zVqz~#$gzW&U8jTWze2Q=q8ZUa(M`e?M|Y(6!OV_^%|nY4*T=*xc$q<>njhg?vU>!B z%rJRI(fIJJD!3JAz@zKs@YW_n#GPm$Jl{-OOta<;ePf2~fI8K(&OxD9d;{SO@dg)a z>^{Ik)AlS@e|BdwHlX*bkI!CViSPL3=2}m4WnRI%)d$|G@9X$waBUY5QRR?EglLkU zGCDKjo{?az*MqEw=Y)iZ6#Z^?;*Rp+i6NL}?FHa6h!Pb|19wl5*|dgYvVtz9vbNPU z9CEy2D{IoO&ngk<1xIQW&Gkr$fl!Jk1zNaiGGq2TpYzlB3{ioEyNU@I04mPIh@{3- zYLb!}JTACz=wq8IMYo7p|8Vj5n`Tw69=15bzt}mqr*Bl?+9i#R&%1xgcK!VGD(vsH z`lW>hqG>J;|AJku@{DXHpUKgg*1i+Z4XtQjGhoj<`@i_l3#03n9-0aB`*PsEvp@SS z_3L-#a>wln?Pu1tZ%?>3E4L-nGohB$xqGvtV+t|inxmsfE@q}u>lU`2#Z#1MUTck4{g{X}1=K`Kbi zO-Ki^r!hk{8s!SN@F+Nu5nBdFqJ0_)C0dlVoaW2QB7*?Xe&%#JrG<)#`5Z;%>EwBg zewrp8rD93mUgcUXd$c4#M24xI;o=#5!ahKi1XWpa?`re_o5_8t|A$G=*qjero>q~r}G4So|@8^^ac;-e(l zkib8f`HdP@3X{qtjj7@j5E$BUq-bVB9DFX9JZ4B~0f*@C_wMhP4tw`#Z({w6@jd;Q zuPZBz>}o$milO_qOz#gP+JD-u|M7`1ghTrcNF%{VrHOkMI(AV@*Zy0^UdY^eC|2qevG)% zHiSX2azS&_@?Q@Nw*B^2wz!DHH%h`FZvPSMqMQNu}*qwZJ}(}L8SnrKb9I)>1( z9gA?C)qFDYV)$q725?e+`j+c{Ikh-+30wseslNb-g8WE|kPl;E;ckgQ6D1It_(Xp< zlgH;;1d%h_?c3V-oiA^Vo>}3!zhey|3>>Hu-s2;1x#sa0f2!gEF)7mUe;?MEa&#JV z8VQH0I0hrt@NR!FWmLmbgjHMFHPlTYGOe_x$TbJkFaM|^p(Z74Rsr(6v?#??oRj=O zn>9xgH>LepK4sgdo^!o|@qbvy> z=O%Uk(pbCSG%@$Db6@Zr%<}xvdnCcLvcmQ_8;7HV=P4>t-p7_^_%rNGv|yM9r%SW0 z$Kem=7ilTM8X{7RCVLB(!kj#Q2VlEx8P6IlHy#Tz^OVO4DjoJ+UNgOHSxJ z!}fgODzeX>HhujVY)>c-<45-Y&K0F?yeM`L7WDtq^fmBM=H2_5D2>V}!zyLcR?}># zRbDpB5UErK)5{`CrCLR5H$2H}qETo|YPET(7~5LCtPoK#OH#I{eWFcdkoLE2%xFW# z%>O#K{r7yHHjJ73zQ5md&ULPHo%4k=#>CQ`Zx#q1ICOZ)yd^jU3-^Mw4uJ}c1d=;jcJKs} z@}azQ%mn>_*tb#p7+VGLh|y>OEr+*gpO8;BEQ`Zw9gsxA!-PjxTaKkwSuf|wMGrV$ zXoeeFOTrlRUg=p3aA*i(rT^HOT*OfV`&zGv({4QRmRo7e`vS&y{a$*EYDw&eADntI z?(&O3&9AmNV4$XcL0|im`|}0bhb`=?xX#WQ*JjS(K#NP#n}3lJRV@f9j%}N4?9;>X zjiG#CY9T|P0tR3Wzi3LK?7SHC2x1l!z$iNc!OUbugytwP(s2_t5{bUoFcPDhg=iC` z&ms07oeCQ0_CRUIEQv=zqZ#G*dk2u5WwAM!i;ff!iXIyKU4$Vk7->Y7iSt-80L7Uz zCaCO3RtFvn2_e5YOC8PsjCG6;59Ob!*%0O*4StgC8p~hKCZm3@Xm+jS>v~`QN;E(;6^G`}vXgTNR zCxn<78)oY9BONXdy?<%#es4ie%zY%1#xz;GuBAp{&@nvFr%-r0CLJ?5e=2pcupy<{ zm6t2!^ZNeG&9BOUyG2ro=YhgmC|kgu(Rg?&N6c%`)5<&h^2%A8&w&lg)lMH?74I2P zE&6G|cktDY(Hrn* zV43>AwI1n9=iU|n`1Z5Twhj-b2m>RhvIXE&ZFy`aa^dV2Q zf)2edYX_ej2{r7)8uAXOF`8yPn??MB_l7i9)hJ+q~1n6oWckODS-Hk z9T`PN8QB{nT2&WqkXD^+j9o22MI12bnDC507F^&sn0@$OucP%&L#<&53Eg2KStwrP zV^HD($OL17JPj(FEApF&)g-D%g*{t>%ZArf*if#atjK5o)6#?ZJ@&gp!k`V*^@f&Jokh2}DK62vh^Ya0Oj42pIy{)CNUS z7?KnG7}b04a6TPbOWF>cag;DgxklwJ;y29EseFjGIiL0;gD+Icn*Lbj^fU2#pkIk*6Ov6xMt_9kOgS)yUY0+kN z_quk&@BG^#VE6gdk_Vq@X({~ali_bZ5v7QQ8CWz%e@avGB9NPgm)IHja1#RbDy_E%03WP!27vQ&^Bfz+1nIw=XqlzM5`s;0TSRe1@+_sq=& z+Y0SU$_dK!`XXQo)MQR)=25RcqaDB|LRoAL1Tnqne*pBS6q~93Pu0oP?I}%pxg7o# zo04BLS@sz#-?lviwacD|$mm{DTG!DB0eNBqb}j`?phaxXeD5a{?lmn&@W;Nah`N+N zg>@=C@=^KA+NGkb_QS5&$B2p3-wpEPohM-Ko2XV36=!-ip=O zBsE1?nbU?&3y2hM9(^6qUlHpKEo$_4aIHvk%MsYni%_IOn5|h42|#=tWctKoKpjCD zG#LU+7T-X&g?m9&9=jYy!2BHkinJ+Ix36IQ1d5-tQKlnRI?%~ju>&KQk&Td=3bsNd zBxGWsOlWEbj8#J7Gpe=0s_-$8S2A-=80COhIFKd1c~QSj%3DkA%6@buowGXf=fkGS zRyh}OwC4*=W6j4fO&pGDj~JJ?)0WTC7MK><$+>waNle}(RAHu+%!njwG#>%Lw&GFl z_(e6)q+?_aoiHNOWfUCXXkPY^Fda!Q94g@Ii!I!33OMGwuv^^S|O z2sjRJMnz({E=IdX5rBA30Zk#tb1Hv3p)I`o+^H>jaUzaL>(pFJb2QSVv9*|q@OT=u zyhMk9wjyy))GySpw8Xk9Qjy?EE*T{Wk%w%jt)vBxN%AoC?XQ@CEV}j?bhWeozZAWb za#Jsr3-K>w+GnP~VJEGkL4HUi7={aWhyy(9=5ldt2FY(A&rfUo`_Lu%C&?y7{FJ#< zXK9-s%F40|MEy!NYwn`O2L^N&IlSz9qbF2r~?|D0N{fGjSvn!cW*G zWwWO^z@Yz7rJ|zwwNB26d!BdI23ZLysgbnP(4^Ja#|T@+FrO^=el$x-NtJ@Vf;SkW z776TGEQUmh{2r-jv0Fv)wAUfj0Q1J!I&%~~7ML0a5F?2n1f76P^Az0zqz;TG18YjH zn3!Qme`u5f6SbJLH^`MSwK?=tL5^%nnl-eS9FX#*oD%!-sMP#mrg~K}EH~DiE6lR0 zaJtj28S2nvXx;^CK8pG1&B`=Zfa@Xix0(Lj)sdlf}c?O^z&H}Eh z9@}s? z8^Ezb-l6fdB?p}Q$xMHMA*opg~S}TyBg1bFvU@p*|p=(H;N|8Uy=_vu~AQ5MHco;(_!8X|oYQH3> zeKKxn1KAQ#j}TA?o|Riugq4LYka*UVt2u}54Gq?CvNv?^Pa%jN{t3<*Jq&uPp?as+ z2Xor>90$`^>U5*~XjRWs5aqpa(G)4Du?YQAs7V8LVa;B^Gh&21oDwr%7uEr97N)J; zz%+bPG{c3sGom2kd#Tn9^6Zj?cu1!2GD-~K#mJaw&Iu!}#P2}ERu6qFc}n~g<#_1C z#;TZ%i>BO&@~37>y0L+z)7V(GN8PJY zjdt`Nn%O-iYA3Z}ZXFMJ)Vk%5%|!XKC2>=}rfw2+=fkZ&KY2Tm0j`XbZr$?QhU!2- zSX2!_%tCy`)R_>N^&OJRkvst5k=P0_)WDhhrL9NghYsJ{cg*HcpNKCN{pNlBcx?z< zz@51{8fBX!otM%Bl&5oI>LPGP8M7ZAg~g$K7Uqg-OP?bZdjOPyL z55XlVEyzZ_G03-vPJNa6}D$AnEv6+##2x8Z&JHO}cb~>L74V0 z58t`G0-+JN`d;W8k@v$R+ifofv=85#82hNQ??^TUd;+6N^@v%&qTXuZZ=F(p%!9~v z?LW#l!D}&%aB#6>om8jcc?I;4IHzp$&C~GQsNEQH%d%`55<*RUXr~~B95r-d{2xhp zH&FzE^86nEL7?H$Wb1y8MGRD z*)h7MeK@D16HIwv?&r!izv#`aeE8&8+ykS;Mb4sdUHBJ_5Lfvdk}0AVgW^^uZQ86h&I_;!G0A%r1ds zga)8O6pBrZeXVm884P^LJEE+Pj}bRZpgC@c1@;~(J;6>rp5=ye?c1O z1Gy+N03ra9VlZqsWRQ4Pj1-q5i~pEla3^Oa935R*De3#eN!e8UFw3eaZqAO=8HN&x zh4!LD#%yl9v#YCk&AZ86aW`gG_AA^yqx(DcqxxQS^q~J+09DCh)&Bs!>dk5{^L7~S z+MVd4!x~R5qD&Ji@C)^P&_pSkOohcqHy+w0@G#8tS>2}ruRy#h);RnaiD5KAV=t(e z2ZjOcf&U`gUfe-NJb)qAsoS0Z3MUXex-H4CKd9m3d*TX@PNC{vb zla}DJ;oJ!8kZ~~ah4Kc6ax8TzORpW~VQv&q?X7X)zxDwX9kO)RB?yq&~;unwrj$o_n!) zjYWFZHTR7C#NuD{sCUfGt&Y#~qjCGL(ze&XqFIjuAng{k zslx3t+JBU7#u{2lipfk8sWRXsH@*OfqfGG~Gp19zTMU@5T*)udMPF)_0aIswiNz*^ zz^j0)~!4D>bVJJP#crN~kr4T~P|tJ0`Ps-lON@D}SH za|D%!0LA1q0ChBZ0ehXAIw+qZ^$;B3dE{Rtk3B`u z9507`z_H@3f$*4`JE?WB{?u!MCzdPvMdW2rVrb4WDv9dqdDvs6k!yO=)o)UXHUX}T zeONP^qqcxf0PV6>0|}G4hl(P%KM(Rl-FfOMU}@yU)Hw=NMZJpfzVOeC6= zg1Q4}w@TRdM1qO=DVtmZCTX+rpQ%fBZ$A#-d;C07|0CxOHs8vyWqn<=bMP@5UJzJ4 zXAYj^5W9seG$l+^uSwR7cvd&HR1dFn9C|h0F(zu)c6xB zw4^ycXa_~+nSlJ-S)s>v#mOjcItpJIPS1o7YdJ$)C59o9bPsZZF+UHNON=^lJ2(vN z7dWg_NTIucod9qFB?(I+-@D`U&Z6>CmG&MZT!i*(kZWIGBEaR;uW&DWuuoJ?5 z?#%N6cS+S&Xevp3@+KGM1SaW$H^W<}Ll@?&y@$;eQ2p6b^EmK`=7{6RH}jS@weg=LCyJWKr(rm-n6{{ zl`+`_<>tt45Y`A;QT_Z1mX6#HW&1#BY&1~e_hHf)XaIDM zel_dbxzyEvzLN|K$PdlYOeZc1sok6u$i_>9Y=u1jySttMEwO6j*n3Y$kJIrcIGPuh zF%qHC)6~XlN}?1=#@MkuofqB)?r{yDp=|lPH{1|Rm!cX?vq5EmB8%X?X1o65|14YA zT>C}Ix3O1TZ%vLA#_=W`9XG-J)9?%97pOJ57rH-PympP8cieDcNN!Wqo6rx}Iu2jC zD5&m!w(;B3`<^RT2VznciIz~zgofhW#HnG(nknFe!jsSl78+N;_SL?C?ELJdT$Xtq zhQGB=$;5mcEht`51hQqH0T31Kuk$#RWf45#Oh{H?NYrilpC8XrK7-RFnuj6+KOUh* zR3!+!6n&?U=Q2=@+r<+a?8hiTM%YgkU(_5Gdq}ehEeVDLtb+~e8E6Rve;-%{HCc+S zHU@y8XId8?098^f02SC!QN$rCm@b5QayYI7kB-#tOXj*mN`$I5T6Lj_qlQF8J9NZI zRr|D14foP(fNU=!9tj})VP^#@uuMvc9Dhta4}9!~MJEPijM4$Q1J8=rBVC%Toryyw zAap1RxI@Ee@U);4k%b`Xk&5=lAE+z&<;kiC6Hwc)OOJiOy?XeOQ;$cW`UpvWlx8)9 zU&7j^9~yt~O9k8sW$00I=X6t4c%vjY_B4)FFe+8SRICG)VFJBb>Ba|?`dCF|*kSFArU!fYUsSroc$khCC zCeS--9Yl3^GpdCLEu zFxA07Jg6>swxZBQ^X`YT1&=n*}UfLO=*esD?lA*YD9uLb}{^#PkEtdQIkKQ-(^n7^O zEV^zmwjalH;tau(*wK(^oFdtKxMO5?j)U72#rFvb;}t89uGDR?1E!!u-UtE%YM`P) z86EE1rKcDkPBLy6NcBtbG{0^O{>|B?Cun#n*Bl`WqtU3ppD+oI8>2JE;KmrO4-vY! z1BST(M!tCQ+^PEQ)Nn$On50~kpFop9+Z|3G*qNEKO1UI_JBky4dVgEu-3j9XfQ=nz zMu*B@WDXNWza``hp;JlpB@m?8j`7v_bvQA$@0k`17e16_Yx;W0n{m*_ePfZmm#LGf zyV1$TSjvZgoa7}nBVVG-z5UR5EPs^ zu!uP)i@v7VaFAO%b~-bi+jWqrTg7+yZ!;Q=h!p|##ptsGyrWk4JT)YLLSX^>`yfeT z@F>LSOQ#MS%vzw?cR2-e?ByE0%;rn*Ilb6()U*MLjCmatbA8;vA%F4jTHTmGER6BN zFW_WXl85563rYLzfMq2Add3#dQDgNnl2qoG%guoTfoM=m1&a zHW7i6oq_>el)b|r;}15vE%PLpr;Ji1=6D0H6A8r#HgQZLqoyXHwuYLu&=3g*h#VO+ z_TmNl)u;my?vxq_L89V-N9+i?F!E(2y&%$6xlx+J3zJzD@i^bezO~?vs#d5!R(AAs zc{;v^5y@+FV(+?Z{x48jSu;|px)l399kOMIm8Q9^C($mIrz?tT8lO^lakp^>YOPdd zqdp~qcK9a_oCCZ9cENLfvU0j1ZfHAcSeSfpqcbW*H1>f5<*kl(T_{#X2iIo(`CJ4X zbPLMS24|(CEwI-Ac?;Y8;?G0RN1S>~st2~HyJvR1O4oGH#31A&**KzSBwG1kL0w`7 zTPxXCZeib@QG-4}45j&&+RG?j!))lLrzWvENvvUR=9vVX0CawvsrZk1E|Be$V{x5r zEv*vcTH)MK#se5ejAz2+7@C&fnrWRnRlv2{@b7eCrVkn$2_B541#1F0>-rx~G)KOB zD9ev@N^KO(E69BSx-&q52sM(O(Zts6#=%0gp2$n%@G~6Ji&hm3?pjmF2;dcqc@WnK z<)uN!!P;)~b_~jE(;aErMVDX4M;ndWBk(mU%vaID75gaJEXMUg-ZY-A*TQy@mvm?; zBlOB0@`I>?aq~#WAYMsQdg#bzHqOM6%N&7@pbr@4sx%HB7CX%f1wCXV9{9~(FEcbJJ-4f!x$fS88gE*`ofX+flU@mY0$ zR1{9j%(P?Ek2awuN~sP>wjnG*P@}Fp2+!P-*bROxd|`-_Oyc8uEa=A7n3&9KGnbgihrkKf7PI)R0w zWqIa5hwL$)#l(JKZSti|JhduvD67K%mSMf-gc8wl1XbMvKXGQUF0lmZ@tfBTA|=U zCCO-K?FXZcXw3}ahqM!LfLRw5_aW(|A!3LQ)YH&9WivZCavc+|{~dAy3`JQGGR$wc zw22R0vef>1zw5*k7~N6+Apx`JaTQ49nHJE0;}N_70vWs=Eh&>UlG{S?MW00|E&@th zm&<2|7$F>jq~Pq%!JxsQ;Qg+zZsYyVYxVq5?z`r-LJ}8OxTRW?Xx9J8Y1AlixVEGp z6EaRRIHA8bozEBP&Jlty4Dc>O$;BR_0E2sZ?O+2*_C;lZ{74H!iLC(5cKm?KaSIm` zzARF$5ys&_7n}mil=A=8;MW|N5WfVnT^xY988~yBism%ChoTYGW=x%3;OK#KbTPaH z#Fn6x@Hzl;y~v_`qKq?ZK3m4iw2F2tfu;?0%I~W-f}tq6Hgu}~#`__MT%+i0a*$8eii zK42(VQyC*X$Hivh>1L6Ogk4hHMd4X~6SNy2;yFh-ZL`^r>yLugzTGmHd! ze>_GSsz!AtJbdPuTB|KBjiPNdi@#jzHgLso~3IrjNNq^m`AAUmBZ zK~VQ$|B&^~FLgC6uA^W%0TA8ZjxG?W%p>VTWn(Q6pN&;EXruQ~V@l zsQdQNJCMnns+G&o^x^bsX2;Nk4vkuWsJ~Vp)y_3(k;S5b={>e+1ON^8*&HujDYZ-j zg+MS3c){MB#!A8xWZ?$T5eq7W$S6cwc@}ocCHYfqH-c%E;^?!K|6hT(V`xyASXsBU z=O&d!Dj#}q`yi?YL_>}v--!JlI|bc$_z|F=YJxWuoKSg6tfDOinN=&C6~1u)wwc*$ zWMMe-4o5^(f*)gk4LZ^)>=KYI6e!?Ino-eFmOaS3J`>qhkybyGYdp$-5gG3u1=RG{mZ zy1%wk!bm84>W+TQW6zF}xE$5nU8*q6 zUz+<)nqJL#O@CSJO#2-x5l1F`atqP~h(v(R0jHrLp&0`JzV6NzFz6y0RGgRaImJ9m zv2dBVSX|2#J^;l%vi3MWo)o@^3Oc@G_6$N?L)Vh3kHr&f{`o)dVGCHsw_k>DSu*hI ze^>9B*tDKZzi3Glbq4+p!x48wnI)Jss)3mEs-|J2FZS;Lybti-EVj0*_0r0Vt1>EM z^z|r-V8%EIdpvX+Ux5BmcoPz31KDB10}GKnE^cmDu8YuFD**@ph}*sy_uR+`@tNdh zlq*mtuDE@y%828Fs6P%j%B^P_ab?tkCqlM{JCU>)W&GtUu|;x*936iHdP;l#yxn?d zT(~x{5?#s)#WUC~{bXf{t!<-^{MFLC9-7ux)vi#}TfN5`!qy&I>%0mKA;gSoy6&n#W z=&U9(Ys#$1f(n%0G%Wzs21%6H0K@?9!&xBdKm8q&xu^6>G4in%5G_?=w6p}rjA$1E*U&l|$cW5IK*N5oN-^FE z?uibfA=aV{EJGgDIDT~Y&{qQ9*0{*e81n|?L~P@$shvKm2yf?yPXx07lc(AsNlIXM z@ZN-*F{^-#1y9=>B5HezG&t;nCjOCCVWW~zgxV*GAff&*gcQPMKxOpA@E>L_Gae7W zX_ewvBdyPo)*x#UG|v1&+h6vNqFz;xq0yk9j>M===k(vqQJ>Lt1!_Xn^{8>i^qQ&1 z&nAAtdc4CM3A?9s_x99%MXe`oDDlEDt_UsrpE1QC*cG)I8b*LUS^j7hDjY=5z(@U! z1{Z>d1{}Jlrg69)Lb6aUVCp;G7OFj0%p-1`Nf+hmY220Yd3r}s>L9RUpX#58;V!g- zm&E)N65ZvgInx*0H&LZZOpp8{b*|7&Tc_J1p2Nw2XZ`wl5((>wfe0m@2cJSiU+^#~ zjRORRR;VG5H7$3mYMq5HgPE4-i!)lZQD7~S@7LaD`v>%A4AV|CEnr5tWID@+Dm7qh zNG3<*0G4k>&u?0m(0gH^2E;Nu2jvu~(Y=7RMAQnn^B=fIxQttGcwyY#O(|0u5t*0A zWG-9=(Y{E3s67=48yX8xLcmapfBh3W?h%HOgfeHaP%2f`xmY+RVa-iJF^9nt?9F2H zTNz~(!e92Vk>hpW7B-}sY2Ft`|M@WXE;|2JH7Mi6{)#fNW2G@>b8`F4c_k+1CUsUt zn9g%>o_5-a@d=hKvJMzE5mPvi3mg||Mv~cR^QO7bIymG3;#z0qA(X$Nq(Re>foX_> zb42kT-7~xkf*n7w_Z6k4LT<=T@#lx`o2)rUGIbvOSa~;W_U0rJ@$jAX3~sOQ$MH_uOCJ+ zEe@7o$Y=H&_AVT#P+}|Q@j_|in&OQ5hkny2xhBv;IRVX#mTeQk|GLVW`v32LpDi5& z|3F+orr3#I7+Qe*I>6z`wDeO~Bmap+EXWO7X?UyW&p+=c4aTbH#C|Mx>UtQ92Iqg$ z)#qwdS4sXpybx$}Ql&7i_Qadq8#0zrz40JG4Osu==g!$*`69+yq^$M?pTn-~*O?JQ?+fcGYojusgwHIlKzK~;LxUANd`teb8U z@Z~WDJVxomQiH@sJBNMIeIqzyp++-N6{z{pp~Tr3>)H=%dfz4}zkh@CrJ;($I9i>_ zECY^?fM=T-!KTrln2yD;vaku{7eK8z)eB2QLKC2_{H?ZAx!e?je~cE4Mxgb=00^Cf zG@$&5pc-D>LL*pVb40Qt=s;r#5e@pMhZj-*at94x^;Qgm<)#k2m5-$>P=ieblK*&KT2PQ7yNMX z^12^ZYcz@LKYqTiE359;)>T`DRy^Uf?=n6FH21#y`17N=qnr1xPO*DBeT%WNPG+eQ z51I%;h(%i0*5%EoL9t%7Yo4CDZYTO$y`BqfG9|`xQoM!Z3xVKPsZodpzPT^BoRMF7 zT3hc8v3TYSJK?@)x`w(3nKV;pLu(P@sVSw^_!Vr2XTAtTG}8R5kZ`0YtbWt4S8Hl-g8FmFxyilI`@mOki+{o5{qc?ArhrR{jlQX+Mz_mroV30Q@j`KU!`?@W zF_}L&Mb`_T445HdYbOjiJb&hET7NIxRJ`~)rt}RC4vJ0d<#f@4vd05Eg5z4nx2W4~ z^Zj8}U1_9abKZW7XJ2goEcn>2=8K9C%lni+Y^tdb9Q`M@+v&((epQODGPK3+YI2=) zR1y-Oe`A9bVk8t~r*YUJ58QTMC72R?Y+cXU#scr_ML(5NS1Mpjk@TlhQM^`u0@_UT zgRX9MKI)(=5IyrP-?4m`UGr%)d_R<3H>wZcPY*Mjdhu>-*h@ktsA4we-FOxhj92~r z?%mWazc04li$M?tc@9688>VALnV;BA#a-kh zH&th%RJ_-^_K@ks5R2errA9BJF5;156c3IrCF0&!4-3v*NvRR`3j9$xx-5B=pJcCb z;NOyrj2y=E^07}#EHx6m)L%GF%R13D!P1a*Y+szrFzn7h)72d{st`3MBKHj^_f_3@ zH~TVKJHJ!M+4zB#tUz%*57k~RFMbTFe|iae2Hjq6+KR2PVsJsswqs|mbJKA_{S?v~ zsdO(FZPk)>I@&=#_-W{)$@mgi=2^6thMQvRJ{R0V*|UHilb?(cPN7d9?%V@p%}(TX z-cdio)^OyA!j0Z7yU`+3qN%}h32tFlQqQqyFMQAVit=+xRRTZ$-KMx=1#zAHY~upg zs3N2i0>oIFtbibXo%Gr1gkPCUYaCx;m>!HO0RTG??_PnQ3~!G3jh5Tvk>@4o@!ff~ z6<3Xmofl#u6n-roau9Iweg!v*NGge@4R!-@%K!QZ!s}L+7Jts+V5JsVjZ8AMm;#TP#4Vg1- z@nl_d4mT%dU$Kt!Ttl9qxvwv(>!fYeU~ixDSKRx zy?$5Lv|8m|#kp|sz{T1xjM5X>-Oj(~rVdo?VrN=x4L;}MrhQrN{lvcMu5W5_!qypb zXB6BVI1+Zk{IB_(^2+6{j|VOeR85@v?2B?k@$dUDF6gy4l)jN~_Il!aFY4mn_E{H> z-W%v`wok-yDuIhl9hT<|P=Cv>*-o!tX!>WVyE@bsCDk}h?!)8d4wX(?Pm)ptr*%6U zN~Mc*qCU;fb(nK|T-2H#yQxU1Lo870CtU6)!}!0v10luO#;L|e%lPSM&2Kz|q1n&b z9pvL!V1D57gVE71d!uelGE6_V&A|Zf2@ej@9yB6lI5aZG5AJvhlA5FhB@Z_tDebn~ z`ug&*QdfN(Vqilfk)`9F!?b5y{Bk4(JMo2Ys)T*!f4@yuK&ZFan*Yhz$!uJF|yfv;UwMyjWpqrA~=pzvLO#yFa z9OQ$CSwtK5N+dPE%w7d|bDeCAN#Dn&2$vdpZm2rZxbgZ1e4k{a*tgiq@O{|%G%&Tt zTlF2uF0vaRk~NgUqc(fP5FsG&xI*?TR@)avFHv5PbsBKm{eDC75X=+ssOJo zWaE#-YSif6(0`3#LVsP(oq$37ZZce^=LRt%hyWAEFq{Ey8%3?>wCZFW`S~m_k(RfX zS8kOU$r^Kg*c7+e2zUYtNS6N@2es7K*cMa__D9eHV8GyIuNCkck0$p<;q9cY;(!7< z+HX-<;0bP{9Ofm(*wl^45OL!2&SDvqxMD;@tT-cN^h1*u^nF*$uiW^dceO0sbT8Hy z<^N0?^3sZ+L(dZbqVm==->M65f*y?bGFX)%+bNmPIh%jOZK+VX`_&>#kdJdZqW;-{ zjsBwQqi08N>}Z50Eq26Mqt~H&ZCk$Fh!?f>p0-Yc$GLMRLF3{#V^ZSdFPEigyB=Mn zzu#cV^pFR>`9Zi5?ny*DEWRKOX3I@Z^2X-<;|`I9Ll80;53;53#VxG_g;du_|LC z%R4s}FBCr>xSXF5$>SONj9n!~E(VzZ1~8ZVn~rXZ<+B+DFutH-tRTK`RJ(TR^=IZn z=Q;8VhhLjMt|*+4P&)OSIeu%su0IPZ9-ksPxITD&!L}I!qkDDUtb$f{!MX0qP*&KN z)F|d_I;}L{gEUjeM*f2ny)W%*J|(*}LCaWLXA=h%PXMO}2m8hW7>UT#7tVG~*z4>7 zGlZnhZD+CDlz9-4*#!Ed{o|&q!eDnDM=JIH!6lN5B)dn&orwebGZ7^cQJuX zjBrPaqn*$v!2#?lLd^4nsaCB;%8KJ$l%EU0KDzZRWDF7WN-!GJN9(yEAo2#73o$Dj z6@Tr3SQch-OM(IrJPLL)04OfoCKH1wlEy8>ffhgE$ynEtgQ4?%7(pJ{6);>UT3qMQY~f?$kRpS`H4Q|)k1#%qpI81$)Bg{r6eF%3@9BWA-NoK$l$&1YuS z07Bwn^<+cs??{`3-63Fy9EUHrYADEMo;g;c99U&9?G!v>FRdGA_j0+P+!)&IHRsi| zErw$4-$f1?U3PY9<0TUNWOkIZ_RblvW9-rr@x=4t5yvzZO}TmbqVS7QrxI}jHyGrD zWf26dbTf{KKoj8xig4oDItDB}Qv}tbCi@H4Kh3+HE8QG&{S0DSEAR=ePschh_l7^woPYuK;jgB-+hna>u`6ZKm3$V$xsbi%`WcC2z5qv%e=RzG?%4VG zpiiDn%QQ9$nN>dZU0-r<^TmO8I4katkT7MA`a|E)!Vc*BI(Jem65D03ma--jUnmj`~=yJZaXnwg;#<-vyn9YqMNT6^TXqmut2@28( zrKF351!1xvq5fOXf}W16xZ(};VvJ{dI}TP5`6H6I{2Rrkh(3j4#Cb9Wm^p5Vjtopd z!T<{pl#xmj?^FQmr0wyLvN*a!xR+OFg7Qe-E1gMMOl9q~C#MmFm z5%8LIkO=V?$&lZ*3cfyPa_h?4dy~r}#k%En--=v>2MjWHA2WOw^!jqptkiX{?2MNF zWc0I+#N*kV+rK5AKV8-llf0>VE&Ct_{ZN?oz)=nEoGJ%FqN zYwiOnpd9(cd6TcD&SV!Gu=nBmu~mrp*y&RtZbM^qIsV42K&9#ruk8m=7Jk)bm@62g z?Qw^N*sZ85Kg44#6Tpv4isd_LC&%tVn+1J>qIor0%|oa)yrYiCHHxd>%P8-|C< z)?-MO$)baNs7Hh12dzHf1(U$wEBryaFyKJxFB~9}Q-I-&1W9p%xr>{Qc2r)gAuIUU zfP=wTNPu?k!#$@Bna`6Kg>qi4AMYA zDyS4TD55Bz0hf>O;gujC#7Wr1*sE(4A!3^?LcXVNLjiK59$Pr^4GvdsY-szj70xFc zigo%nm<#nm*nen|Samx$rDJVUW0}P} zo6M}Dp?m-=m>6TFH^Wy@hl8^&gl zBBWvc5M!~O2-{nRSX{(?j^$BQ0>VTz30jtc_y}csx~&3UH`M=h2YdX+pGqUus*B{o z2HOv(m$m;V75g}`CE)N6E$aW}4D5>iSV-X{1K}h@^{!8CSu=PzaA)!Oc>BcT11EYC z=JEMfs~q@2QXY zz7n};5fcCOi%z}IZ|(xpIuX$kmD~aw+7vKs8CIp3?-cdd0@eG!%Z7&mM4sF{{J1lo zqrbz4=2+|{#7p}ESc3E^$X^*w4Q_%~8NdQHq560bd)F88kqhM7Et$^EPO-ct-_GSu z`&qVTeXh9+=peVd_A6wD*xL-PYF)b#TO0_=f9%O|(0(3%?FaC7d5m$M-^Lh{2i6wp z^oHxe02H{u)sWM3+3Dx81Dr0!lI=>q`GnIcsi-#~7k-m}?7WCSTYHskgexr_I`7Q4}{0j{)QyTq)rSbTkq5MZlEEM@-$qNzSv6 zA~~VN1zk3`Kq5IFQ&`+{t3I?Qdxph`a5*MALYgn?4oJ2&4f?TOi<6K$kW{<}ey=t|+;P!7iE>m=M zwRDjCRE@MB2DWnQ+7(vw=tY?3gl$LkY0QcEHU=!C^6_$~UT8)sx4JN0YaUMr_wb1g zJubfxE6z71C0sz4IE_y)QX1`e9Uu|Gjz*~%yk7eNu@_wQQ{|cUkN!F zRMM4;T^}j_4P5!Ne1HY|4b&V9l=oRImlfogKpUf^OE?@+jc^4@A7FH_=i0tDX9b;3 zYS^d+VhMQLlC6uwXl;N4DM2xvs1LS5oK`Be+m&06xh>)gy^~}w!|TD4qil(lykIR7 zyQaAEr6N`A+k1cBzPKxD_jCK4u&?e+VK7A4Yv3H5U|Jt+s?YVjJbEPTEE!TvmcRuM_03!+;dP2vdsQ;=_B*tpy z;H;aHDCK*T`hM>VMm(9HcEhsFbv>88!*Oy&k+dp}L>Nd`yvR94gIw+}X}?&{4_)fH*< zSAxJZjTo4GhMhSA=FVHZo*TA+6XuhrwBd%$7lbG>HSsg6l~=@Vr)))FDA#ibNfR%< zfO2#735gE~#0@IXBLA!92y}sr;fpVzZw3(qAD@3CQ|J#efbijIL`e|W@D`^rLL4iD zTSEl}2|aGZiixwd4}iwtH`*_w1_D^h6n?-op>hCos{jp>e}KM3I0A`>+00mNq7u9- znEe2pwS6YVICk>Eti}1VKGRH3$o8{gLrYRp&*;RXshV?>rZ?W z7g?@ZQ-7D8rF*pDGp;t*D7)d-vwS1#+O@*9@y6b1U9T|owFl>3+iQBA)TmPQYx;9A z53EMrzv7}p)USU}p7Wn=3s=OUmgU=a$_K0w*e%?2xHoMnq`qZ@>9J>V8Mz&K+gcSF81!v*jfunyowDBRdO{eNpiZe2(Q?vm@f#s z2{;5DzXv>pO{UYPq{b{`9f2m6`Laga&FwcyNwmFfbkDR-9RY zlIRm?&#wlK8rPtcRXyPDBwyY!JhfxAJ!0rZ&9I+Z-KS1N8{UiP(K``QeJ@KC|AbZ5 zAMnb(apnr)^Cr<8OcYf7H$gH3pb#S+mfFePZsD2#Z5k&PuT8w)bEEtxy`S}N@3-(b z{{Gz7;PN|PMk-V~={XLsNfLAk|A%U!r&d4qk!Q?DpO_B=f&G|!mFAxA*fl)RTNSxv zt+}Y}s}$WcZG~+Hun>&eMVJ6Z?S&i1!(EmcLir4#Tx@Rmr`4pNmD&|ag>W<+uDM6b za+jnkJo7o(YYj@IhFdR)MSB(tpMNf}o3L5v)z#Dn_Q-L2;`s?_j0K4cEGaj}K* z6hgQqK3?$FIH1`_hErVW!?14fW`)4DAxMDOtSZ+4v5WuqOf{ED!P$2OFEC>Gv2SRR*d?!<~7gf-QFdvJBMLI-Z zQ9PwL#pi~5gCL_-r79o+zgF?jk0#?I8-I}I8F6%}DuHTxnqR?YOCd+sg&!O=BQ(9x z;mC&*aOh>|!bOV&shdaNMGRHKY^q0I_Vxc=^U{98lA!tB$=pUKXi>F+{yRa$=> zR<_pzTlE$@g`Ih2X)T|rmTMVvwUvc}TPTHvR`^@Sy}DnzTmI+MUC|$(Z;w%@2KJBk z;rz1qqkRMSCf0O6=qhY$pBnD|M31)&xaQeT=h|hNo4UWp#u|i00H{mw&lXC@aZkBB z=2Nr{u|QP87Qq2n;L4HA5avKEhNlNdKq0kAo+=vNejrem&4>o5nZiv>k*De;ZP~!1 zcoDslqJ|IXg#d{D(jNe=B1Q}!gp$fn68NFM}nf7c!5SJBSo$FWm+%pXeUI7b-{5pQQV~`O11% z0%Jwx1xlNc6Iij&q%A*n6w2;P*~VhU;GNx)f?Tpzm! zevVW@rW5PKMKC6cQlyTd+9B0SLSR}jAnqaQk{yPT9i z^hGBp>zj=osH<*|i0pfr>~CponWdNY&?OIjK07P;c4>L9^6P%kHZE_Kr{#Ma?aDgtpil1ucXv zq?3$H0q1tJF>Y}^aoX!4y${dD766vOIuacRBJRmVNGl() zhT<17P)KN&VJEM10~<*BITNVhwMhj9g5d(iC7@H}I(rDh67)x3F2%C-WQ(v-%(T^E zlVQ>?`%(gU0Vz=1rf`@9pwW+Zcr6U%d?${ZSjN~1q8B#4cX$r0_WMTTd|jA!jyqGZ zy;@!(ua=C^m4zTE`M6oF3wf}q`T~;^!2!fB3iwJ$9t`_88zX0!Jjfe`guDYez#O0m z-+?(0;95Y0;9~L*phSQx2F!ade&@sN_NK|a+vOX>e=H|xhr_gDU3t(+-P;l=jdK0O(O%TGvCK67mD0b;awM{F%vlS2G#4lrcHx`tdyREhg`VmtPGC zL&`aZ|`FE-eR6muDt2=BQ>yf}-suYce#U#?ehKPKX|bA8TjB}+2?}L^u$k$QY&ULCsxR>%%_ppsm4|)R(5?7(AKVe z``r4XW7Pew1)jq%!Qz76Kn68>hHzjRS|4jh-^`uu6w`FG&@)Q;)}*X#qVHXnF8<(ZMyXoegHnuu~7}wMCuW+l5Rpn4qnbqIKzs+ zBIP%^mX+nMZ96_gcO{j}zSHtb1jx`?#8?)H8TGLvNwnvO7TJm&y*ljSDpYJ_908dZ1rh{)A{SsEv}wh+&J!BL zwt}n1{~4*M7=DFG562##9o6WVo6!u0K+QxM4rzO(lD-d1OqZ9wjkv&8{sktQ39JFBWR;Tp1HxW2C$P)>;#Dp zc3uV8SZxdd8E#5dkU|M)NkPe_e1vQe;x5JX3xP?g?l+atS!087MRq=$pi6Zn9g?fZR-RRdG?%^Y5J)+RIJfymxjvmK)w;PF}Ylj}`$e|qS0`PhrDS9YQH zq55ei-&3xo9GXN)#sbfa{fvbjZhX4HbLj2O`sfcslX7B|DV}Cu%KyB%tF3*u!{HA^ z2Ol(@sKuRL7qqbaHywp{oZe>h`;)m=3t1Qx7AH~C*&tLjD}}rhL{#?q&dUe9iSY~( zya49*8B;;i0&*ea_2M``nao{K;Uik>m78#5o!1Jt@CEYqUb%W%x=U9)SPKm?+jWXH z!aqQQ?{sS~NRvqPiuZ%m)!z~ePz>gjP|A7=2n8fghN|}PVG!}p#`IkH7D#l4Dklet z!4zG&_vdLZnXLSd_ut9uv=_PUTqy9r2s2_LwoYJ}9Br*s$ zJlN)a-JkbHK7m0nUe62o<^D#t0PAv@g;l7&BTgfUQFWOO)KqJ%qBVi)z!>#CtEl&H zd=M4c`{yJ;wZ>v83SIMt<{~mtEz>tO1ZD(R04SF4jmT6jS$BoJFThb*5hkG0cZi&j zizzBGqz()PXW$mfJ&(I64r7Y}Y9Y^$bmQ@O>dh)=5N+|T>@%AAEqEHp<`6pyVP)+r zVNW#YD|&V&JJYsk=%83NZ$tE?N52j9{{IvWUJ3m&0*S^5n6qaeJxzQjgdmtHL5zwI z=}T~t-%=O^XfLuWYTLv_Qi^xViC(HnTwPtRZqNGNXYPt}^-|zjfeTxM!GBZ$TuA*f zx`UA_Ai5*12GZ>Oo(fN}HT?Jjkr{LWH1-5JBB+dF2KNzPNM3tvpRpX5kZ|MWdei2> zt#SSDhxHskc29q{GaS*VcA|ddP-%$**3=%=I|~L|Ug00v|4NJO4#}CaG@|C$mg*0d zwzkH%&TpIHpW0&{l-Bv7yr=xj@-L^Rb)B_8)@v0S^|5-ep70`w)>9cprT4#4<;p-sdVj1DQvxZ41{|p^XbeQ9^V=%@Ev0R3ZPy-62FL(s zz{KcjRf1AvsZFmFCO@u)ATYN3e+bX#N#wpwsfD+G7 z#K3)GWuo%t>D2E-QhWi5#|Tw@tU4w3quOZz5h@JuAy5I%^Y?KqR^E+FaLY)O%-LK4 zM;3x@oK0L6<@RLf$Y8J$2+qJNzHdZ!bwM!Z?MdA^h5;pr0>l*`pz~WwVKwl$H~7G4cmYob*wA z*JjYQ%-qJA7qHOg6p@a6QM?@F#+1Ne@8E0bf2#)D>_X%d5rD+T5bR!$^}-egD~eR0 zMeOKFp#x+-_Vjv&;a~_l%FAGIW(bpCG-kFg6-bsRHh!j+m)~|g!6e{&6T}nnR}A%! zC`Ls99oH!fGB;@-8xj0mj37jeBhoe8@fZt>OaKJ|{GnJQ`wNUGKAvPQ1dbX0-XGx| zdeyYXo&z+Mn7LSL=U@^E%I5eYSDRDw>dc*m#!D!dgKjP(OAz3}=W|3}@#k3GcE^nC z<2|B3B-DH6TnDS*{Y@V48kU?^y}k2??3tnMQ4^D9@R3%zEC%eU-`@l}5P&81gmx+K)L6e>L^9{~u3p z0}y4I_5aHVNoWGfWG2aonK-_nORiGLn;<6nFyJ7X38kgA7WlF$ti*td;FzK&nrZSD z3TlzIg(X5IB#kDuTR&}8$PuEp5@$4l9GL&-T)2OpXLoU!x#zyG>%4r=xz72XE2%H< zNp@Sictzd9PSeY&lQ++r{kLyE__XBZ&{>Pz-+kl5z1O#VSNV_eyBC%Z`SF_H)aAKm z_n|(&4>6Q%fI5TO8y=QaSNDLQf1FL9<%vOO0^B|HLD~K{SJF16ra;pYBt1dWtVHXW zuih^-lgUdmZ-ERP^(<7}oq@#S3jLjQR>i+Oitf`(4=&pg#|zhGr2F|n{`3XVOC&ws z@a8N#xpv&j7kmfFSZ>;e6c2f7{p!VA2j9BZ|Dx7;H_7=$ps71H>&}Now}v_YsDt;p z_l-Jdiu1FytXqrLmOVGi2#y!)I!SRw7Dt1yQW{xr3bX`Zf$cpK>VsC zlS-p@V+bjXkNfcr`r0^>9G1Nmj#0*Dswp5n;tRBG`QbR{chJh;VI@2&OL=~uf>)SkUQg)ZVLqo0T}>b|3K`j{r_ z#h+XnzaQv5N~hB@}re$}RT_ek7eo~Ym6&!uqb^=`>Q*B2oo_k?kaFlV-5Tic8kV*jl-Zl@{SqDDfxqoz? z=F9kO`r5o9$dl<}((cyB!b}G+dJuG`k7Y>)G+$Y^`SSUdr{3QxolY*JHdvQ-?TO6k zdK=0dyX`syu?M8Q{zu=OmbvBrYP{OM&%GZshGuqscH>0gFz=ui0;QgKf5q zJ$_L>w=&(Q4%R!)&zzgXy59`DR+V*UO;-0g=LI64uv?c4{C;rrVVFbfh}8jUK0dSk zb+!9zpe?XJElXyI&xlj4xa{e zG7tK+q#QNcT(fnX{Ym>%nOl(a_pF@_bGs*ivtSStt^bk5d0Q!2YcX-%j1&^E@+ z;y1h2+toYeL9fY8X3dw0ggXH$K9e5CF8jO*7 zsWL-F_>Z>3N!>=W8bPFueiFv7nto=g*t*|FrfpAW%#-pQtp&E_<*;E}I;HqPjp>}( z_GUmpvyleTOs473mWZo_-H5hnWJ1Ufg3d655Q=z@!pk?L$&_;ia+Na6hy(PBE6fjo z#)u|a>FV9isz~oIkxw&6FX?>^!P45UXzm7UU3a3JqGCKle*O5F4LR0CpQ^}+=r%0p znVq})MMp)q z?W1GU>K<|5#7C#*8yd&A8IEW7i_cFp7H9f3AZEE-Lr^KHkd=wtgxB~KTwA38Ky z57p~WOyM$#dlH_rL&wbL>vm*^%rIl1fW zw3M#B4Bjx^B7tpU__VDZu1`mksVgR{=hu{5wzksT{Oq$b`^Yv&x81sJ!xuXDMaKGS z_~;%tmoY|%KvOPh3uen%v8QFM0=Y-{;rLgwbindLC@1;! z%MK~*hO3`Z3y%e5#`Qw_^TzxR{UOeKdx9xq9da zdwb=_X3?%xn z9BA^|V(1JoL#zY41D!UZt5DruT0H0PLbJ*I4WFINUD$Wc_K?rAk*}OPySJdR_=8ir z^MlT0>kQfcE(zxz^wNFs*@^X|P81BV9A%8EVMm&eYt6Igs(RZlI_z~$PfmNCU#2+P z;<|ola=vYH95zvp@%pv@yx8Bm&0}_i?W0}Gz1@bVjQ0-@_n`$XSj61S7#|_gDznZI zf>w-hMRultQ5lA1xn6OH;rlYm><7h}V^x{?i$LG~%5)z_T}mKm0q+>s65G~E+Yln% zMB=OWA#Ky{B#n#sXrZ1jKwXYmTVQ_MXYr#2d|PH7iLS6e%QAnA3CkanHt0UDhjiv7 z+biob;)JY8FLN39TjsW{K_me!>Pz_?<_P)}%E&V0#9qw1ATSSWVX>}p6#l+rgc)LI zBpMWgkq3fuh)Kd5T8=zaYyK)MuA1vaa^_z z03vXYJppSX4oEeORmw1POSl`MiIc;Yh}i7GI3N9B$velj8lWFj;tl@*5jvy+Yxk~Q z_+s`ZbH73``CxRw+so^@nq4y)mVj`S*dZ5e@ZR}qrB?-ZaCUNTbd5RA$GqS2h7UTV zf5I5@?~>B+Xtz?=$S5I?%w^ zML}fJ^cBF0z>(9NmYWl{{z850-0k%<( ziRRqwx6_=iEq=~xcJ}!}ek0FBzwxil4Iv{38E!rl{GivR=eDlcYx>ia)zH-Y=P<`! zr~R;_JH;`^c|Fa!C#C0H)eqaYP4nMg>@M6p;kw#U-ls~u^4u#vE3O%5Nd8LbMTFj~ z-1>v4XHAmg7o8WMG2gbu*K*OEv@vqxM?yfsZu3NAZL^8m6!8?l9sxPKyP{4uxk9X~~akC1b>7;zRb1 z68i%#pCpFN>)wS3tjBZMdRpJ?dcCB$=F{wZD8K&}&{Si*;~?)K{K23pz6Kl*@domL zSV&fo&bc@W(V@he!=(bhvE$Psa^_?vL}dDt;qr%?go2Y2IqFxyS5kuzh9Nh~gJ6Ua zU}2dd9;@u~ZCm%kELyb0g$TDZmR&fjsu48ak8dEBNR*e9`4^`TcLO)qw=`TEP(jU3 zM8=2FBZfaAQGtXS)3nR`c~=6$P%dW23O7=GfZ?Z6F%2IAh^Fh!NvnMV(meVcO+raA zO6@YL_{2mHjb*HDi@{rZRUj-0+W5t!?-Ghji&u}YJya8){M8#i0WGBl;h)a61i`@f z$fzx2BiuKI1BzM=%>e=ULm4(It^l+s#M{)v2t$`}ArV&c85qkT6o=Pw^FEi>OWWdF zS9nB)3rUSjZoX|!s@BKHMPGjr_Q+xIfVh~Y?TVD4i13aO!mXhv8Fqw9ye3xI#W|!&*ij!-dou3<^cPvFs$WR-qR(!1q{ls z_<5xr0>s80k10*OYz6KWOs!d2Lzw!07op8~h zLXugcTJfS!LVz}3-}J*Z5}_;B!jt2Ge5}2`{WlHCXWynTyU&d=Mh!#jVplA~%YCs=Zj3+97o5hUvhMn^RQuR7-?LgOx^GlK9px@@?yIA(8 zV=tB)ULLl4-!;Xw6Bt-O9`5b%zhz`}H=dt7I!5@4E&HF*dl(Vv=@O-$XE>a&k7M@5 zLvZ%|8oovV7kq9Us4+hkwjre=czOOvu(a&Hwl|Bd3voTgb`9&{i(d(EyMDGnd26?L)$$B z+iCXedCsrvThB*zEit$lHMUt1qzih_;5r8dk%LPKGUf9saRq8iCEx}nk&A<4NjS%E zlrq&Q-Y{^J2h>{$w!4vSBjPbs2$T@{2yqtY9Yn~&St-dQ9&uhkEdeAQa)NW4ST7Pb za)K-PA5Tzs3y43)n=FeOFlYg0mKFxoEB9)}j%=B` zJX5hgA{asqsA%ehBT8Z^7_>;ylh-C_kZ(trr%95&ll`HbiVPDNGII-GB9vl;3MaqL zwjW}9LN;&EY;bpr*}g&3R>)36^p$H~4mp}U7qrCCu!+mSNRq^2O_{EFYD!~lJqexhxx1ipf>Gs zGq3AYPK@w+0#_q&35TKBs1s~sNa7%;BjRlj5F+AyS^|3weX{DASnP-w$l+M7OuzFL zt)-xM=<5oyKR9>8zH9RdWxHo( z4nwUaDXq*kMgvF`uL2OX1)PHeXAS^9GhL!|v;(su>L)kgtF1p8ko*t7o3bR_=!W~4#*tHqk$TDo&N?dRisOTi!&2R+`#83!Mp@FEGOwm#eZMn3|znR z(`CP8JkmJT`t`qhTK4;$T2!hV{Pn_wHM`$-)i(`pztB3s*LRuArt=^7^rvWWLg8x{ z;D8cpArCjLmshU2e09jvpG9rX-z;5SmB-99wq23loINy96`zpFEIbe282zFc$zzPt z16s*juYgoD{_zj%+EY^+65<>CN9DSYeB4kwM630Vh03}@mrOD3gu=38AhQKQXP%Ji zl8WNTZAbczn(krodAvSngTEMkz$4=m6IWF(&>!Vu$Ax)Y)}mZUPyoa2+y!mI<~4)c zef4`R!FlcN%ae!0engcddn%kx!GyQwb5tqQ)}vkV@TU)W>Dzrty}nxU&6!)TdZquF z=Xqeuj!&+3-5TcCZ7pzqRh8AVqW7}N8Pi+fyydi}WZhnxb?b7At>Nqlw*zCt4^98V zW&03N_|HlvX^BWQ}jK|HxN>*NopevhJ*M)krG zop=pocl++D;YgQHVH_5nZ=%(;;us^fxB*}ufr`GlY;_#_d1YRBe9rUzt|!6*II^&O zb^OA1>Oo|!%U4$|sXVYoELY+xDPoX6Y8$p@_vZ~d-Ta2P?=OU#0b=|7Pgn7mUk_}w z(D-$J<%Q=3u}MwOX|^PmOtQ=lUH~_ryvj&>PBCV)h9HB~>JgpB`OyvkIGA(aj$5~L z)(qz67zM0#bMZjtVv)CF2*C;7$Mw$~saSv@vfmpm3GoYq&Ln)2blj+flwDD>00oZ( zN7mBS7fP(FFFc=kj2Wp)5Gat92Zr40_6Pb&-u2_#q7h|YEj^*<4h98AUORwUm2y}1 z>;s}A#J=5klJ=;z6yehw_n1Y-xSH-CZw==YS5k`)jOHfYv_r;6zVLPZs|OjV_TA8b z7?Puq2k(lvBHIEW`@XzQ9tyEdZJMT!G6o#Hpc0_tymr3ipQ@{PD6_Y_eL~^k!($;5 zG-PCD{j~N}iT&D{0mzVoZ=JFi{JF2v3hneLoGN*3?v0K82OroPSR4NNXt`It>;aRc zmmotNm9QPl?lXTs=&JvQ{`3_dKJ58(#HR3^vD5pT{`_}QH{@jx(0>!x{dt%(sHo?h zCsYEah7M;}OlVf-p-11{Voi)Tx9h0;m#1fCEHku)!a8>08Znv?VGqtrJTA+`mWc_i zoy)|iM3IHPYX%=(q6j7s_w`y!N@JOMJ+~nGyikUBt{32y_HKAaB4j5SMlz1*!+M0D z(S_-w^+&t5E+7%r{a1aEA)z;VAun_-U3-=cKkwc$c#sb8+yH3|=18WK zCJ?G|O`1JXbeZ_JUhfeUA7RL5$BcU{Z3oD*?>8j7yXJ3D^30A@E)b#|(RU^3s>PEg zf7E??%#U&`tJMdf_7ws7pD%gL-OGPj>Xn0OygD+OI+Xn4#;t3n2OZ1K7HlG>`SR~y z!k@5!uO|$l2k4zO;mJ~NdxbR2-*G$@@^$;T`L~&UJ3*f#39k*!#sII&Fh^d!apUm2C!)v_s;TM8y68Of3Hf*z4^XC&M6t2PXYPHyo zN_0+4$OQ4j>lqMlRH8u-?{PjN?vIU=rMf`#3#OztwZf2`Z#mMno7a#57yDfO9-`!O zp)zD_S>kcdipp46wurb6?9wr=cBf^Xhzh2>B0SsZa4v3WIl`axPHv%lQP=I;t>oug z*SCCy7_XCgghJVYODMO_V^I0C`WuxK1peZFGn3fjqmU+zE?lqgz|wo=53?OKmlvOS ze7>9OCJRL?d@f-j=11A|eb;tk72pn*BQC0RX;FS`KH6cNpnylL?FiRmz)jMs3(wO{ zdU>131SW++Y-}3(l8EL|CBHzwLV6IjLK-FpiKWuV){l1Ksv@q!ACPF%8@|~XPPsKK z7Q7{~4w!}o`v)x1STe7FJmgIKu5$35tjSM4APxn zuDP!**C((veKgDyDP`D>ziHfgW#XMrVYyhW8;SDiPW9kx1_vD%8*cQV zM8|&vV(i))z3S8hJmS7o;*r}PW%?(zO+v7y!j#N=|f6PUZuzFh8|2M7rZ z3qm8LxS^OYh zxI;JwxV!>e+-H^R!qwX=e$27{mD}@IRNQS4j9lt&43hlc%Wi|hKn zuJ=ikqrmy&^|K?iZywu{jlBb0i5`Vh41g(cSxvj=B=-41EV8V~TURuW@W;W=Gvmz?D-8 z;=lI|zoA4*AfA#jG;Z1E812!>Hbx6jr@Xk*cwkMR(;ud7%RQzfctd24eVD=o3X-A` z(pQgyKBIzR(;v&TDQ|%@*t2k^#G|(b)&qOpn?Amo=YVJ3u{t zpy9vafUt=r06|uS5EI5mSU|5sZU>V~LPykeN7irh6kpYX&oN z5mbb;C(aRV7xSljlHalp8E*`Un|#yZIB#zpR%H4-E$i+giV#w6mH9b0(H1|z(KQ;yH`j+mC;ocQta6z@l?aa4ip&q!eQ8saX@!ytjr=`$~j0^ zXnFxO35hsM^ogCSIb1ATwns3VsUyoMouQ(RSruG$gsU?zBvU_T^bx!HX(RHg@)|`}4ET-bo~K z#169s0CT~hsP^`spMMOb3S1COiIErBBL?3nY98W^#En&Y^}gaiWBD> zOHnp7tE6E#U7w0$3C=|TGyO28!kGOT2 z)CrE2R!nWaIXgd_hi6~s17h1ZCgS1*H_hz)pRtiI=%w5SX$0nfd1`++q95+uBK;aajli$=VTY%Rz|9i?JF z!D zG>%Cu<}m@o`v1pK;Ho%48xX`;L$3hjFJ9M`wz<* zX=A!G9Ln*)uB#ncO+~%8$EHTlwnc`|xA{-949r=e4bbQdL@cY628m?G6JIpfms|I{ z)gLj$kE`hVG<~%AT--}ybZ+Dp`+K-%Wfra+J$d*9AFeqvz5V7~eRCr+#I6~c>lNoZ;x!Q-_I>4NAYyp zlr7f4C^wD9I{t)lcU}H8ml8wGkr78*#}jBZM@4V39R6>^P-8U(2A?D`8X1cv@{KluAqmQW$p_Y;zbdIEBGYFyDaA&^kAP{2 zR$)OWkX@kHHH_Cn-T(r@)<_7BtI#0D_-!_UCy>n4DuL5t*ChrN(jJfqc2UWjpmeR) z$9tI8CuiU1>E(9tI8xQ_8$N&NTtXnXjGq`S+q{LS9BZ1zpBnhjjI%69}6O<{SN`rh6sr>J~e`3J+ z=EI(G6^`y(P2HW&?h!D>rNB(;+|VRM0KFjK%XK|ZI=kz7hoxlNc4`VYccT*u6yEc8zszxUpu7rf{0Zg`W})I~8AhDBU|{OJYqz zvNnLUl=}`3t$QvfrZ)c@+CWXKZ}RtsRq;9LLv=?>(-T)!rI%;Y7%?|M`ElYsy?Gb~ zI^u9JPl8t!&{s%b{&D5Wb^~VBV|S8X=Y@zNbzHN11)m{o6@)D)1c{EtEK3pz0X~Ll z6OB{h*d$;M21_g!X@>nqgT!e-5|SEYAN3J#I8ykSQwajZ9^zF#RC#jU_UsBDG9oVt2qB0*a`I{_i5&B^<35}?&L>xn z`a#G^T!(W7jP+^ZQLZbgVKfwcGeb{WyQp-@b3bfw}|`}_5RjQr)ZsZovEvTN@nIE|Kxz` zQkSL@=l&V#i%(2AJ^0rr+MjUCeXrB>mptEHFYP+H!(V6cf5>M`?7ACM-$~J~z4*%u z^HUlPm%{!rC^UX&lk4WBNEdZTDFrHGYB>zS1{|_Kjec=(V6URqtE6joiS=@131#3? z1Q1!GX$afc1-eaxovbnO@~B1TROIpbm8 zmehu^;xP?lOYN^}Gy^x$mBtiR3N@uBLy9w08G7F&E7>qPbmWw3e`L-EyA%?8d}REN zobyx0a1MwVV=eWPuQCpnleAOk=3AEOd(WW`TM_Ik3dsNLc!R_%-`Wm zQVMMCJ*^8H)=8+zk&Y8U;x#5LO8Pfg(w4x;);x~iG8bk(nje0}H`$G9-mU$$DNM{A z^4i}WKQx8jeABPJe^~dovR+KNopDOS<{(KW0)LU@MaTftp!KPe(X`0yOaeuzW<GOTv|?MJ4$QV?tOEjBO=^3ZLr6?ZQ?Fv zD%)ZL&wGdJQdx3J25nLDPiu(Orb*euc6E$QiAB(v50L_f_n^-enKQlae~DzW6g$Xp z+O`M3{qlxey;@O@ScUyZik_y)ZnNJ~93iE|a-&bUxl8L}sb`+c)*MR97%PsqGZB%aDc#0ZlkRa_L( zP?&sv_E-FmBw}oHygq3-TDth-=%}_SXO`Ya?0eN~c;2f&toUTDxBI2V0R~ss0}(G$ zpv-ams(dPot#2~>TCF z4y4iNr}6x>cn_7PQroUo1K6OsylJ;bHZUtKh?S!NOv)u0OLbiqQo*1s5d^?BHhx2we{l_gjl(n5; zY))W;a^E>Ndy6@mmr-;++dikSX(>GxB{js`IGSmAZ^^n~p-@CMnd*R)a~h7VXn9OW zeXBV@N~4s$Q-x|`P~01?S+%y zj$5TvOFE#$N_Jo{`+7a3(2y!geEEmC43V2)<6sEnHpo!VITT{?>U;cWAA;gZDUpix zftd1$)QR7qmFu3kQKYLxh>{{CUy3D}+l8{I8W*f4ol>fKONu9?&eujG3?Q6J2FriLFhO2))b%&;+S4~ z)l@YLLLJkvT)YPLo7ezUz3#z=7^(-pc#YF895xM87NB5k5Zr#g!<2Amk?(^Caw`8Q}PiG9PPS>pTb}eH% zT6FYh_vVg%?d>+{(xkt8Z8`bsz}&$)t+!V1zWr&kDau64{Si1zVZvEesl_ic)3zdn z3a!o5?MRKWC_=UlUQp_Q5P79RQu9K2*!e+$fq|KSe4j+=|JjXdHrsELeqa5X?QU3a zPKvX$t{v($H&b_#sUgMb80PrtV%@cM&aOCzEzY!Ln&$ERf?4;!G}BKy|D;R8*lZrl zkg#LeWGoeCZYfBH#c1YGu)u7eBa0%!jAY|$m`2XRO2d!@706LeHsciAs8MxzVK1p& z$9&Q~)Iyt(V>ZHb%fNb zcZKS`VjR=89r3m0B$#KrW0Gd-wLZdE8u)-pJ*eqoj=^+vcr_A@DDoep@ zqK16~C8^PslMdR!UXqEDfZr%9uI|nq&|jXJ#Tf;;8u_?uxBbrP%^<@#?@SGhPV!^O!K=mC2(g%TQ3c3s&;C zNxBc@DtzlSnbkuj8L!EdLYdjlLI$1Tl!ssTT|1jq%}b~0Qsw?s5sn}?W{^sXkd{%|Wi>k&Tlxj<6;v%!SVM)2=)T-K zc%QMCOj`~JV1D=PV54vF+LF@6d!8%V7WZyxu}0Hod+5i{myVQM3eZQ9RYZF6Mn5dR z^Ni7V-*BHxmlluo4E~!-gdu$2*~tY=`^yb~zSDY$Vq=-4Ne3!r)2dDr|0-2mZlaWu zW>oN#YPNf&yxb|O0p^mNO~0IPaMay5ul2uIE}z-+$3;;H2T4OyDdQr~);W4RIjrgS zblB5OUqd6@)ZHDJ^;JvHpVJ($HC-nHOikFm7b{0Lw?N)ujD!y-!8 zjkITIG`LihY`Zy-_A6oZV6?iFiCbrAi|%h!1&&COD9=7brrPWb1l=2JV;fGnYNY;( ze+(`(&DFVD?x zTU<3SBUVZaD@l{!6IA6T--fkYK5({8pV~l(r!8`Ici*(Z966CXExZi_7BNsC z5HE5?^jMl-5I#=e=`U7wyo@4$MUaf7^u*Ou7OPs|!2Ad4o)z)(C{#su%EuF9U`l)e zY=j%EtvFbqbmDKba*<-y6(O$CIyyt^55O1Kg4M*Ri-3dmMK`JXq|u+G4hbqs&=qQ< zSaz}Om2}Vv)|dVP0aC0UKg~qy8HXnKr-YyP$(adVQtzrn@!RK?E-u<@U+EFeXa8wSr$UF5W<#Z}w#4x5mksv#V$HuMd85ZN^_G&ev!aMGLei#O zP?>dfTiKGWX9rDx^cC+apJm}C?o?s_kmp%bQ}g4bHw*!$2S57fxr>1ULZuwe7~qZ# zxUg~8iZ~rY359 zzfN`CL{s$WTmrebp0C1g&6Lgu7p;Acx1qZP$6u`(X4sy;%VR^nm@1(2!VoFzRP~`$ zn+}byWgqd-wEjlteCa7w;!(T9-Ym1`Nj<7Zp*1nnUs`+$%QUXmZ#dGv*W)4KBR$Og z7`mgx!s6;P{3R9?kSwM01g!|y>j)j_J@c`Sq3p`*k&cD=nXz8Q^nc@}<82coUF$yt zQ5b9Y`xLp4dAK7!qab{??Wyd{*ik=UFYya~G^{&b;)Uq8L@zH(%tv^PA~KnCY=0Z~ zO?F8aK^2yrZz>T`fX$YPsSE`Ja!9)}cJV`Q@Z(Evx#p(UHx1)R>e;@%k#H`a`by$R zRX~XM5nQcm-{scPVP277nIdNNoVwI%)(1>NVww0)8rMKM(p=tGkEvh>=3koSah1|* zg$*Zqm3mx)cW{C%F|RM(sQK$M8sL=o!Rxwdfp2&UiX>P}Vq>B~(6b(Fq=H!fDO3S{ zlR{IatDItERFhldA{5ES0U($0|yvH z(kMCw9o(@0aWRFlzfi77%*x|o75GBw2%`h#(&BI*4_;P|uKF49zsDny?w>}MJlgu$ zkAr;dbK;Yqns9N$VxOqnL(V=pLORoTMs5wt?D}EA+Px1g+x#lQMEB1)3-n_e)xEz~ z)y>KiN1^lIKv_FoP{bF6&xQ=_Tri2zeb-oMN`JYTS*tehzMA{M*#BJ8y}0g|Pj?i1 z?TlO2I^|y<{nMqS#OoUw&~5)?U|>q!CFiy}XHnhtl-`>$l;q(r=F3rYYDRfLy!RnVf6D&UZ|Ufw5Vwg6rUQ{-bxs8DL8rv$?bs1(`=4o>=j zXB)iR6saSCupV5a$6{{V$EW&C5qWs}k4~Jz6f&%w&{(i6$bs@+{x2isrM*i@HAwVw zlD=%c-u4w~07`vmtOJl?^QD4Prsax0($>tvu9pRt=v(EMFw=c4cz6<4kzvi!mn|JD zcpZM1T&O&Xl!tR2U(0wJP*jL04UK{+KfrBy;c7dWzP=gFCXaP5u9eOuyvi}_Lc*W( zE9V6hqs(-3wUQ~q_3HI$l@;0bW8GX`-Q4W^2J4>)s9LgaxV7xZU-u5vj2!J38ggmH z%X|CLbS+T=H=s(^R9qyE?`-OGSXj`eCv`T$79yqC;^Bq6I9OV12=z-#H^^b&dYk== zqTKw^OvnDF)P6I^vRvb0+evC~<6xc6?czt@4ZYWhB>p=HXijQzy2h9vw?Q)~ zxD^nbx&O0-%-DuSeWTTv$J@356*wd3|1uvdn(gLVKNp0gpuF-C6=VxUAn5=85F1JE z0)-L?g%ep%yjYrGa9K@PNk&pb9W@6`>+5}wV4cd5wsWnQTZ6i1Cj&GVl8}mYvj)-l zRwu!Q7m86(lfbkupUL6I?{dzbcla?8y!*4b-UoIUv{GMQQnRZmDr4$PKPM#ANP+%B zmA{k`>qPaqELmMc2B%X>qFNqn;xJ%!fax&{PQ{$$Qkp3{LEX)K8+kN&lHVrb>@d=j z|IHx#FAzOdFP;otP*97ic!`50)uUt>yby9U{ix*KNN@^r`Iy3)H@U}eOTKuNG>wtg0@0>uwn(x|0MJ~i;jez>V#Xu?`Ka^wb4kt4 z?{_`h`rT7cKjS>Agmz`@Xvn%=q&+$#4 z{>B%xA2=J}bMah5YLxGvpSk$t&-^^&X8O=4cK`O}kOG$tVT9e8w@$U#E<3v(NqN0z zD#Y40ll}Xo6x-#r-i={B?M3Ms8P`5t@}`%ztqR@+kn1HkFm1VYs!%{60pd?f>{BEK zVzyQpjs83!p%9t0gNcrb@y!TRvSegvkkjeICBx4i8y$Q5bqB7R_erG+jir}}g4aWm zQNbh3L|o?EW@2&WU;8a*MY9-0RRI3JqOYaUGv3xI5efD-jUj> z${GlLAlI?yWHW7s?#7z33rl~RlGYs38k5rcK-iy8el?(UHR+3*p?>c5z^VqN4608a z;ht}MhBQ;%UQj)ZIFmE0&gc@PApi{d8bS z=wP<25GX?`SMFA&J+Ub_5OLNF(65LJRO29CMoLqu2O?3Sn?b%BcmnwC%x< z*@HX3FY1fX&}2DQDF=kCDkJjLzL1cR0)ETIk5%uO<}tsd9qen`+x4;f7aUPrzz4ZTKb53AJJvu zM8sT;lOzciOzJk?OCRg;xC9K?QIUVao?`Rq*YGO5DB&jd5Jzz@junATusxIO;Z9c| zB|4dJ0K)b`QqwIbJaO^e%!W&u5Z>KU8lDj|I9TJ_+TS}rFv%8d?46=nHLu-vbION~ zpU>4?i*fdo)|1^%N^16zB4zOa_-NGIpdZ@O@dI;VasvN@X6~h-d>fur31NZ3$|>iO z;+!dDI>LYgM7(#_#qz7LT4Dg{oE|L6CLT^C?P}XnHbwO?(Iv%duof!FARz$`lFSp^ zCs`3hM;zjINvmThWF?VBj@|_Qkc1GUATW#ktQckK&jv;RuvjIKl$(&q7rIAW={05aly0uJd-$BjtlsgDXw z(!ZQ(aWqYuLeA6frKWIioT*`Bc-(-F#sL}<#~ggYdV=EZ8r?h&rd)T8{?g=2Ip&#B z1BS0nkpn$rKGjTezWGrgc+B?G*CUW$37iDnpaiP~=h^M6oaQug)ir7b&LHLwG)iQ8|!FtcAIWF9p{o9cfx9~o$Ba4e|hh~!t4h;jDHWPpk8`E z^^@#flbjG`K8;<RY_E&E8O^!HZp#}%w{F_f8w+Y1M`YdZ%&(KQ z4X4Z7%@7Wfjge5L`&l~Mg$2s5j8e5c9#%50F<4;kVE=F&7Vkhk^kjv~L`1pz6~IoP za^y+>wVc_?fd&^AbW$!y30yIzedRLrlq<;@_lE!<2_^Vm$&wdfCwRN?Kd2>6hkH~&73%=n!MKYvl5?2lq&a8< zeb1V9fBmG631g(QPMZ5+*GjL_Xj<#W`8N;r*T28wljKP+z3esN!Z)M#bTlVBY@Vjpb#-?)r#S3;GyhO|*Ncw- zQe!dA*}JH==Z@#Ir+lW*baQ!Nbt9{_oZExxfb|x27W=4Bgu<@mOnrAol<#>4-%EDB zB6H5?sR8igNR~5{ynaRuYyy%%6uW@q%~)=zz^~$X<7kh@cE{VM=&PugCHpAOn6rKW zCgZ(~q0G-Aihjm6j5I&rf02z?)Nt0AiIC9eEr@0*&>q^rBhx|mTRT18sN1n9w=v%4 z99!5iRA*{?^43pZ@9&1~YI)Y}7n^UE1q#5pw3t3wWE2r^`v~Py6jv8j$ zSy|4DLOFs*MUuflP3B@FF zN^S^B4OCgKhf@N=5^&2&^EAoqL-D{y$XPAI?oprmou*WyJ}0*&dWb@}=^LJOiEtE} zH{{Qr$VpdC&9fE%qy>I(;Atoie&j83;!bL+kqN7UU@-0ku99}2&adQ+ck#s}i#WHe zseyavKj&{g4Rn`oO5hLMgNe_@zWm5sq?cwsKTGrX%It_2M|!%yKi(Inn|1ZW1Kuw! zUD4eX_WD_h=x5aZS#7$WROc8I)`fwhfDq=OzhP+2Z#!XiD==1c2Lw zYscKl@|Ov(&O9leC-4hquOve9W7#r`M5Y9G$dLm<023bOz`a2{h@}*QkhjcDiQLc{ z%qAPqgapl$Uhf)weiS~ZrObNQ7!Wp_lA>SEU$?z=_PXhMOv+E7cYkh`@DL0lbO+QN z$>otYjGUC$;jjc^kmrz%jf2808T>6MTHqEuQ8t)pK*BJvKpGOPC|d3%6%lpN2!Ktv za#i=YFt^BQ5GVWwnhDU7g$;7301ur$G>>HxT_!1_P(AkK)H!;*DU<2*3w%nP2bUBn zN6#?etP%mJgtPb$1Ye+vBxU4GPuK*NkH>>x5pVDia%v!{4?G2$BuQDRQ36595v|;r zti0$L3WHKfk2o@pG;tn#5|SZFdP*(|qbqq0L|g_bs3Z{bTfuS*Y!Y-Nj#+(L#Sij% zL&kg0P1E_z*-Kt!H!nvD#4A!OvtbMS#~&2AJ# zrF&&-WUgQyH=HPUz;>Qs+$?Uwkj)#!=kzkl4Da*^3{vgaaWD7`JX9Fz4;%Se?!jBV?#EPhV7(!^E}pu7zWp74j% z9I`(Hrcf1fDxoAOM@1J1%~JW`{yAUq8rhO7eGqBB?GyGf`Di)^e3F=r^5cK+v@vb1{v?h@JbDwqxx1%jw6nx#xE0VofP zd*e^I3%-blN9OtpE97=T8M0BrS3#y2|2&0jfjbca5_HswNtzII>IYoz&YpUQ!{#F%~Ojf#B<_Ki+SY3pB4qcPNKn}cwX@?uZMN9UqRJc-kqlD(_C}JbLd)NRLMD+C3 zxm1Q&`%-F}S5xL?jN&Sax~AZ4zg5q!7ae-T-w$)=M+q4p0qO{lyhJgQcryVHW!q-r z4alvEbHtOQ%l95YeE9KGSNdpYQa$tmK^t#nzfk4&;HC`|M@D{jW5|yom-cS04ovI5 zb2;qhev@;eB7>Fo*Sg-;lJ@P$U zB_8Z^{OOwDUiHAutk!i|!4>2gSek5&K1d@^> zQV0n$%pfB)oSHwO_ zUPQ8FgjVqwbYQtETq7?+uv#S8xYYg(TPjRBQnw^pgRn}i4LBGJFC}M^vBZfg5X~`e zxyr!E@dv?=uqaDT97P~UNF*$)BJvq}h~NVSA*U_ITJ8nXOuUzJ18~#T)PN!A&f_mC zXrdIN?FTGfO~x$(QH7*fEP@UM5}zU*w%n(jFQYwX1XPmzWThp=AZj8>SyEYYOe5Z= z&o6KY7az)FBz;7xg{0iumf_6!9m^E`=|Me1wd`k1Qok;3jJ~VE%*Fi+m=i_ zFyQ`0E2A4PpT0D(xwdC?OV-`GuFjU;Jw?vnhEacU(eZ0s))!T#o)NX(KaI$8{@wwG za&EaA5Z)W<{?~6@a??Bh6aM_8;o+@)`ysGbB#ZS?XY+E-!m21RsWQb&aB%fcoNZ!G0xKujShRrHop1u5~ByiKjWlKN15q!I^-SM`J$ z2uIg*0SJ`R3Bp(OPyl)8UQ4?Ia(R}{%C3hBJzb+h**ak!hVfI8!YE-cvwrC6J*_aX z#2!)M$j$!VVJmQM^?QBaG{?_=3ASekF+z{0 zwo@&YB%;I`{8icFzKj`9EoGCUkGxsztK=QneNiZ-mV}{VE-2}d`ccy76y(Ut3RIHp z1&C0dkgTc%tjbKY=)3~FlI(>fot5vEq=USlY=_`q?v4^va(j}!6UWi#BKl}}2Wf#X=65qmL8)@&Ie1WTrjL&nFPwhM)Xv@9jHJ z*518U)cxfR;dMsG4i9s7H#t-8{J8eq1^b*2etV`VzS-H6==}NN zvY>NcJ~#S}?(H8Ytr&05o#{2C;@Lk6g1dkFuqos2o$Ru4jG&%rL6WmIVs{JY`>G2hDv$mtd zwmIvMsq~GznR%q%Ys|>3=o!_QgC92i`FL8_zsEk2&4k^`^rzb58xGS4 zrT3#x61^Bxh^F`nK#{{rJAL6@I}kD2{cNN3w1*;F=!M)XFhlwWR|xt-YT0)BMENZl zVd0q=X1<7Rzwke1XhM1p|s-IU%#^i7c(i+s7?#1Bto4aUA z1Z}AnMHaZ6qpGaBHK(plSDk5uWyfj!rZE-}BUZy>_qx_jxsiu;^T@jgbWDw(ncKMD ztv=}fw}$^s+}E6Xd6dT$!xq9QEm%*`D36BWlMsBcQ(r%{w8+CNAngRwl6o`y$DOx+ zdZ0OV_tTTZhkpG|hp8(kt*&P?g15b@IjbS1_s^u>I_IB(p?7~vtLgb~k>l1;lf9{` z?x$Z(SzmXCIU1jSe9BOry~Wj9?X&QNv2|f~$h;k68bGWlJqB~Hq6JV|s9M;9^E|Qt z^O+fn+@aGM@M;-Jc~I1B6!xPBwG9CAGU1f53K@}6Sz=+bt zww-B>=TswWbc7+ag1@fN_bx_mXu~pJBwDn-=3>wL>Pc^%{b0@50@u>6Y8dsN)*bvx z=n!hw4c#2}`rFeSzqBA%2{l8HlF!{~-+s(oAYG8O;}NGCdjn{(8-{(QXf z#EA#a2)gWqZ-XD9(ZD(i>l$k;H+Y18yuBiB6sJH^zCxw5mpIxew`r&#Ie;Mv$}=k^)vnC^#_87vDMU8P-U|-_szJ(J*;sOZDALRZs8PsV3O7d$ zgIT$890YjpFg%z{q`(gwyiVh~WvgYh79tKD zNFP@LjnHfdu0VEC8uig(ao9*HW*;ITm>lG{_>4;8BK4`|Cp^fRN4;2?otclkhR$M3 zt@ALe)YkSnS^+~CuY}4jBO5^D>_C@{xbSdqX^;0%-+|kn)wYv9%z2#cTW5IISCJMu z-}P3%2X<@?cf1`HGSDkO-KVMbk^WH|b$h&=fA!wv(fz<5>8IXVb83|7&qu|s29Gt~ z?pIn$opX)9KAfi9%=|eGs+H3|Me8>Y{3>AlC?Z{|Nhq#KGN22^)`a3>&57MbiJ7y7 z%0bA?VP23XSkY^YEB>4%IIf351rH~?4myJ9qC~I~M@~qF>{_|lxxTmX9qMo7t&#m2 z95J{7*}2jAWYC$>{}W+hHvlMr43Gu9w1OE(C(?uACJ3n)-hY1C(>Fqtfmh3MnovTfb5+}_eU!_BR*U*S|{?oKEiLntdZ__g$s`0CUF zFL@yJVlT=L$J3G?qA@}do0|;)i~c>6CE-y zf1Rf7k&HO6oy$c-AGOA3V}6iPdW^;AVLrq?LS>}TevH0~6ClGvImr{5T%i5c_Y)t9 zcz{2Jw35^K-;e+mk-?B@zz`h?s0e8OHyVK@61R*<`;`I&Io}5=Xn7=|-Eh9oC#zib z-eCiG6k+girJ&mezj&NC%Lsyf+q~S)@9yqCe)Aa5F|5ifpf^8#tf6^pY__AQ)V{T1 zf#s8{7oMb;;pOI=hvQ1qzUlAIPh+f`<;eltmTPPi_E_=&uoz(hd+^TT#}Qkx83p68 zy#F|Pev=q>4==AB*19bo^+(d5;&8iO7bN?2j&hEvVw;4DG&q`1%1kZnw*@gHm;Sq$89 zx{*G5K@VK(=4>Gog|np3g7dx75pFIA^Ga9->cyg#;12LPLiflw<6l5zxO4F|LPvC5 zp^vvoaR>|7-Jbtz7_HKO6djbA;UekDY9FIuw_;Y1C~gHhy=E{ari0axVF_ET&`1OWBB5y!OpP>LupXG{Mp)}1V9jj)q@{BD$lbz4N$kmQa=k)^c^Z4G2&OhI9{;{|2WyzZO`*FwPyKw=&g@Df8+C=o0n_M6G|57JG6D)9j*kg4|;jkJyu1HDFy=4HpEKO zPQ*+CEf4@i0ZS;cb;;^+&08>K2r;W5)U$*|cx|^qO|3JLadsZrd7Hcou1P#xA|T}m z`zMB59C{TFz(bfIG2>a|&Ffwuy?}U(s;|=C@1yk!tQ1kRaY{K)eIHUO8i@~$1Hw_R>yZwj2IP=<*hRs|C`}CtM~R(V$mNDH-6Tye^i8u z3rdPhBfB3U%Ykn~O*w=V8+Vo?i>Kl6YLo!JX0!zuq{17-{~)etf$$=V&E|<=-#XCW z_ixz?g5paDCI`vR%W6oDq7TZ0&Vuuh zK%`M&7sP28e@8Bwx%*-IO?e4lT=Z-B?- zfXOSr+40iB9p7wheyg-JXV>N5S3a4CPmiODiIruK03_n`BtLbJ@>)2?6w_L4LmnQdKyosmMqytB#HnS?c&dBh z11lE@(&j`KjyV1|Az`NN|Iin(YwW{bS~s%3bNZaT09TGXh5l1wt*O{eTuF85=H;aZ zv<~R3v#bZfyyl-%GpxhaeMrYRVFyG>=QjA2!;v?Yo9K0Cxz|6=q1&!?B#p@Q0abv| zBHeo|F%3^fIwpi)ABF^s)OJMK{;y_vqsO*2PgL)(9^U=$!x?9aLbq5FMnbtn-l%y? zd7qaj*QU}kV*98tM=phDQc!~kg!MFhIVklo0VM6dS#vL3dq)ynl9#}0V(EX5@EijI zoayPIMZ1LTP?3oXkf5h5iJi1>ZujVY@?6`kaT=-W<4qiBLq0i;x$ZgH78GzWVnK z-~RH*N@ItPJQiYmAD?K*%WdDk^74w+_KP1bLgVc!ywdqoa?{mQAx&40^bNZjMxVYT z-JMws!s&4IlJ}5t?No?yimz3tw0~bCPodCf?u2-c{zWTB`Nj8pVuz*WMEd39DNoe1 z3M`oja%=K@bA53YO*I(Lw;WYNaeN>3&Q>_lNRT@~JyC%+AU2=If~(ILI68ECv1411e~UQe3UUCX zJo`C=p2`gjNOZO!%WM)(N&6;w%K-!67W5DFaAB142@h{*XnAMpip|+={uXQ40DJQ0 zrM{Mi$D(6y`w_&JNu3NTftpQoYVWt~5BOs~6Vhf8eZ=+=jnB{iyM|W`H5ryxu$%| zez@l(DoH%1l2;`SG?y&>a{rUrrXEVU54Mjgd>SRFRRL0lQ>-LU#w^h>l$=q>hOCJ3 z!=SVg?pLl-rR_Ce{vry%c>ou)6G)oy8i;Dx5R(6D!oY`Nf7O-24HcFvC!^US%F2?U z`j&s1RFp8fy7d&fs5ysOWGeSmA9Zd6DXBk?-Sd-}T7QTOU3Z3)r3@Dc%@8*s)r(pQ zQ=VkGvVmBm|Hq{!Z{^Mz!?H0n92kR!$fn{iz97UYu?krT7N$Fa^}mOq<`?CwUJ7>@ z3XQ5o#I$xNNZ}hO&-+)nQ1Y$}KVr*y+u=>VUY3H@V4CMv4*sIwXDOIQ;$ElhJkMFZT))YG}X4>y-@Y_{jL7}zq{l9&;gHze_md;=zGcxS6w)}DCBdG zXTQJuxAX*9cyjx*n}2F%4eX}}`iwgGT18u%>#xa&J2r-OIrDX_IxlWdX(PULtnl!y zcl!nhcb@6KT<7|>scU0EW8LNNu5Zo{NItMz8Yx z@7W+?AUFFjP=BYeF(O8V4crB#Y~W>*=T2qvOzmHi`5x5tqX7AwTmi$a>2phRq~F$1YpOtW~ZQYi5O=L{@vprLAJ~Sa+Pr_HZRR zt2>|XGb{6YUhT!Cx*3J#&O|sQVxMSUVO85G$Q6aEdx!1=bcK{s=<9WK6*NMk(FG6L<~6!pfb#PQ+HaU04FbFLvH;rmkA zqRhIUp|mYT>=RTBXUhYLBB32_^OQ*fS#VKmW!6On=gLXYScUXCt1O z&P=c6?;ZQg*kQ-EAG@!y^ZSrLIbAC*;?nz13itA#bmTjac|YIT@LcAR?@pym%1HC@ zzVrU?hmQOCkIql`1_UqMGYIl=Z0D8zb2!$=aHI#)Prc7VMQA@1P}4t)Ape4sz=Tk3VL4VVUSJ*lRFEjo;7oIIUkXCZCK6j z?85e_s*NR`S5Nlb&D+v>V&<)u7KXCudVwN2633PLvUbh1g^Q7hMomRYLxIzjCSZid zajG6}BGDikh2T1|km|8=UY)i$YUH;@S;L~5^B8U3r+ENE++fz!sr-w&$Y70 z;$&*SRQk1yGH(&DA4VX<_kc+Qm*p7Bsn;qi+jC;aYln(JO$lYDFV zyRn4M`{ZyJAGv1Rx^_TwQAK{Ns#<(`9A)U2TB=7C9{`nKewpb@b2NICjQutt-0y8y&X5gp z&v?yuu3JAX>Tk7!Gp}uLx|+E)q^yN`c)Y*3f$!pi@lwAzk~cQqbL zKh~1feOFg|UH8h&4oVewg>`RR+0y9jx|JSU+x?fdTl4aR2c<7~=^tzQocUY7!k|Zv zj%22ZPp)g-@wy}!bGO?dwnzRUZk2Z=2EY_&&m^WtAaR>k8LHpobf-XxNR80V1~Y?7 zgC;Gg&SsvrQMjn?1*+I?+gtwL=52SefnXH(=rB=z&WN36((8e_~KcoF|n00kZC@Jk*M{oM%^{ut9kgv*HroyYIy}8OD0CgxBE>|E#Av115GMlgq=G`xRb5Fd zU=Bm}R3ULO=fe|{P}}>S&CynS)&#Wby-;4{4kGV5C9)=nwp$#L>6jooq=PfAop{V|2~rU4-Zc&3`il&-{r#7xf% zH>n;GmT()|n&2bK5aebCTf#hc_qh{o+Fhtdkd`B)G9K0gTdI;hQhJVM(uyL(Nm$B* zG9#mk!|zyB6UZ6rm1CdqL!f6u#zWCwB7f?s?^)~fbf5cQ+X?`B=7CF}+~Q;2-JPG7{m5UQ>t2^19MFE%>A29hNpgKStK;34!|7jkw{7kIA|SK-?yUAJle;=E zhdI}VWgH2pyY(({f*pMW8n4}|S=rfMv%j{Z>;8bS%;)D>eP22IOz?Zh-dz9to(FS| zhC6c}S)CC6WsvJRC|$A&)I{9n=i{gP2;|@npQFW}-lz5<@UD3r{AP?VTF@XZc=_&P z)9nqkGqa$sm1d2PCz!5+CB*aC%i;coZ{Z)q-ok!>Q0Tc?OBq*!^Hmk2ysY1_%_*pB zO&X{1N5-t2hCwd64XJyIl@|Ek-VJr7P`tWd;)}nw z4|jc-Tb)<>u5Zw7F!fatWlwyV6neCGWc_JFp`&FM#V94N7=S`_eba2_f-=YghCEQ~ z*w{iZh|&`*emRbJ+9K?yY5M_ujG1bhDIpGqoB7cGWQMQ1YR25-} zYun4+gA=E~cZ(sbnmQE@x5^XGE|7ZA$LoglJtQ|OCxNhTyKIP(;ueat`5#c9yp{^_ zJt>&s6DbaBA7z?a;1tm?1tte1UUu(xPe+|+wSGh7#ffE8uB`Z>2z`Jt@s0G3nwn8u z5|{1B?On6?KHm|4dSQrlSm;}A556}1@Yw~!LKpUGAocOyhOIwEtUWd*x0fUD(Ah(* zLw>;6{|rAfwq{Uzy=F~+M=@eY)2)qRofq>v+k>;dztFcOt(8pmFLm7$>N>VIxo&i{ zv|n7?THAefhdhVZ{%`BD;62OEm6Wtags#iZc6>%lpY|s`D%zfm_m~>muV30je)S_= zIsQR~X#VhQO;BQd9@`KU+7sM^p2X8r4;{1*Y`O~I2MP(4B5t7UHe~?27lk0PlwYH~ z&-=etJn%pen_%QZNdr|+BlQ+FlMz82<7x2GN1{fH14vyXmZMFEp4Yhcm-~0m?9=?2 zXLHz-yg7gnjeu((U`yskdts>q=lxX-diUmGxpY7I%wn6eIr-MG6tGX?-f&v7d3ha= z?Ujr4g0fB7jG@hwSmo#lLCwd&U?MQf3(eH* zZ%OUAH7~R6;$2KtyZGI)L3%A1Cq1(xxURWxQ|IRWDNQaXwZV;)hsAS3bnMVPr|hu;?)W7lD+L z1dkPTJ2st}=$}Xc!;}WHgAj8?alP$uiSjRhf!K=_puY3-Gl~zG)aTxsPs}24x2W$eyp>K9u0Ljqh3q?^Pa8wHDhvJb8)XlC0?qEO01G9IMkZK zc>Z!I@J-GL@Y*`(`0H;_n4}ed1Sz*??TVc0y|XPT;mbi-29=}HGEP_GI_d`rdI1E3 z%}rlVJEi20Oi-PW+S>xy<$tPW8{}X_MWU{0(Wc1=p0pN?Hw)Q=$&Kw0b6xc+Dp^o< zw<+gVS&TFdcc9_loL3AL@+UKeSuGhqoS?hTfmm1^WSO04o(j_ek6Dhe>W6g{utZos zfH^A;wp1q)(+iXx`dzy1dOWpQ1=kTUCTHg%_ zrDfl(j#dJzg{iGaCVN^#Guw`L^lfQxyEwe1sqXSG-?p@WS=g;`QLmP))YHed=KZvo z4vCL9)o~nFwzOTnI6NTi@GHNdAItwzwBn^tz8G7ye%R{pBTLt0l>8~rKHS@y{1#?s zh3%7GtHI2(V5lx$IV#R0zI|A@%0i} z|I&noez{J-@yH(+1o+!e`y*%PU;7RP)%5@t3e_g^xx7NEGVseysXAw7X+3ctI@h=+ z>YO7gfK?+j7Hc52DbjLMq{8E-EE732fi4{%nGK%Mbl6EzHOXtiB$&cV;!vV`GE?Lz z|HZT#1O})yk46t1UpX?yD%j$?~L{^Pyrxg|X+I0Ws(d|n*- zOk`;M!aaFSU0r=U3uzW|Dy!pi*7r0<{Q1jXH8FQK_;}N4fgqEK6ohqNy@P3jh4AO^ z4?fe2meH4fX-!f4&waM}st>Mx<(CzQ=F2j)hGj6HEq{KEzs(=5VM%(nUqsP=a%W7f z@h^hz5ScycnO?8JD!pKCQT|AI+9kF-Qwc<2_4= zQJU()1q4#nZc~C{J4M1mcvP!%3Va z#@AXG9)&JqSxNl>U&^!0RTh!iBCzjGMpq1e)WHYC;f$SeLwU^VfBg z$*Ngaa()qLQL*^W)MixzV^U+M)-T`;{QX&re~aOn&gFb$!7nA(NNfk<>Pj7ba*U>h zqf(I;rniYnMbhrFMrOM6P>@5(gQnZA7-G6IcpB$bwqh3xV~=d(V-!-=eHW_$CX%v;wdOyGgHEq(Y=UpoEyE-C40*%acM?(c}I^$iMa^l~n76g*)2 z#>?vCirAYyqv(;iG>h$c|Gcox;tWAx*=i5*kUP28-#!TPv>!@~ubAdNa7Hyv2ct#y zDWAv5Y535c3VoOnT}kA(-OO8y#cvR)urf#AaRV3}tGC6}e<+$c42`igXC|-4Kqs!l zTo#sL7lyH)s2c2GD=D*wbNc7JJ-HwNvQ*r`D(DohBocSfs%=+zyLF|t`-1+ia{LbD z?io>=&^{X8hb3#^ASN7tQZlIX*XKf=U-xZiI!&kR%i!+o&0lU!{-=%EM+?;?Jz7kK z!&`|7QYLfIcAoTbOnuXU=EV9+)n4pHIbVS!Cr#qm=)ZV@grYJFSBh%1|NQS!}VzM*L64UMZ zibBiS%{;jKSKvmQA(AR5*~uJV=!T>UJ%(OAaROGL@~bx+ng-;OZ|&m;5zo@itduXZx-NGYEn4DNk^1hS6+JfFeeAxT zZ>=6>y~iilXVTyLS$+I$;SVi5-aj|YhcTc<;g=qVkx<3NGGF_;hVcuJjt^fr_^7X! zr`OzmF`??gok&Ox$y<>GBP3cC4DiCp-9zjo=EfFt)LP+|5DWS8wqBu(0#7(ZCodZO0_NvVDjY~Qp{vG zu}IAa8QezMh&><8FWL~5Iu94n<(LN}Lz~ zjL!Thy!}HR8eoY@u49;_l|?&(B>^{7B?*u~c{+R$9t*2?MQn*O$MD+>UUrY)ktbKq zzT!X?Q?Jfr*hIS3Fi^(rNgpuF2QmZ99-@aI+;J4Hb&}MP^>D_zdzY+9dSFLd{G86Q z3y-4yy&l{#a!Po=y^G#kojRgq?H@P#S372{nsK};b;MoGeIv5#f4g>2gdd8wUtGVr z1D(Ud{no`5%^Bu*w9w5_S}qL}7ypkVPa^L&f2KFdbcjO^yLAk3EI=xeof(T^QGJ$h z5aKA_(|mi9)sHTW_yiR}L0yKooysJ5`isnHPUrb;5Ine$$k^iJFzlBo@;qTJQ;->gmEE6YY z-h8&O?Z>qlU7ZI96|M?K?WR1R$j;jx+r>0S+@*GSK1~{IWtD5hC#tRNAyX!j5mgxI zj2h8~xp+n-{8T`zp*iqU6}hmtl06iWp;m~?h+ALgm{q$j1VJD;Q!%HZ7n&{3BW))t z+%-}<~s(Y7V2<4(Rc za%1fCo5KC#vLiqC^!4$)RN$FY>oe2;*Q?XtjX*vcBmJtU6b*}u4`20)zrV%4I<>SY z&}Cg0p4RMf>6FKve$54TTatukn)j$c19w>-Gjb%eNA&|z zC)lH5p@!dtA>vgZ3~%VgQBp|?_wLQ~M{A&@mQe;wQ{+4wCYQ>RWH96FDLXI>dknZ4 zg(?-87Ms&xUFdZj{1hYO#!VzMV)Ae_%#|7SA2!g_5dsZ^z&a_-kFMyvcC>JHEAOb46AXjBi#HY)Mp@UhtT0TwvZ4|P zp%#?Y$=(yUsFY9@2P&@1H?Fq+qrd^iIO%N#&10xy&;;LY)D9BO_UteF%_oTMb5CFnMlV6=ujpWyXUD}CjN6OXc|e0>Pr-2M9QTP zMh1ra<~I36`gkAjzbe~fP5;M-41G87kvCUA@YKhtWe*?hwJNu)w^|TBZ3_YR@L}!H~+jm1Nden z(6H#xOB`=l;{@u_Yb)7QM=a}Z)C|K<)mT%!DDcc4gza~1?Mjp=g>fY0lBC8Fs*fBA z%SRdV)D_-(SP8leTBVh^$^ll81v_(5NDG85`9oUI=wfTWwd+~eu)}$^v6%yorj&#? z|M+9iKMoAL^$9e!BmbQJ_^IAlH48#BiL4XN1(E`NSd)5bzX;kQg4UQso-_ zJD7vqDmTW;V|VyWjk^}|=fMR8Eiq}!5n_hJ^74DFjgi=o{ zj}pRQD&MqvAv(rZs_H}f2b)WUqg&{CKTa+cLiDZ>Gq`GzB@6S$#7PbV1r(fBA_4FO zu>rgt@fA_jPPXeqmiGV#M10=I)-vjxzj-zOi?$1GV2USrvmyx$MByxmh|N}+nayq` zqN89a$5M!tvkvzYU9^G^DBE*y?}SD#FE6Hd_8HO8 zE3K>u8TYrR=y5n^v$#RePaTQ1m2+MXZw!KF>*)~{xX{a4Vsy4)H~$MZGUH0yxcw+q zsfs5oU}tEV%xmT&p;A_y$9QRT5Ebf55gi>T}e} zB&N$dGd*^EA&ctRMWN7r$t2qt#VEbvs&u3?cq@{cP~K4HN}^74>Xs%@fZPX_xggdQ zZfFp@>X0;v^O0*$MmZ}TBE9qT_j@+uYO+%aNWsK%@DVfKAt0i$m4DjEEh}H26z%(BP>{TEpPbd(F8=tV#_dkKdji(k6P)kS-*5FJ z0|r{}tqQ#J;Ee3rN4$m&i@elhe7#ryQU3nUyV8o!dmr6y?%20E>f_bExk)|W@~ICT zIy9#F?O~on)Iw`iVw`32?kHp2RJs=zMb`3ORl4! z0r&3m2cr(3^b5PS30*d=>&E^%C$kFf+*v|>COVSAM{*I3r3{EyKL&M45UX@F%dcI- zfN{*tgz-=`iM9p7`Vd1iJP-v;q{8Yq0p|*tzyT}sq&@r$)ilStr`4tnedJ?qgw4+i z_TSR%&?#Ex~I^Pyr;+ihCIQYGu1tEKFfm@QSDUo+PUy=R6@CkA8 zN4z|((F-erY=MEc+WwK7lB}Lzhx--n-MG3aV%4y$hl011CG_*7=9kkNXm2_i6`7Xk zvAVW@{6e3j+p~K-vG7DM1{F`8Dxe9NFL$)`^f~_j^$>az*uq61z{8h`5~y~(90Wo9 zW(KBPnXk$?oU<}d&FtZEEJ6OkmuC6P_>{1P+RpWK&sDMxrdy@Pf%X zo~#g-)0M=Uh^6d`nE9wLAI8V=KPV=E_RTnt8Tpva?lJVKBs^HEQ)}ip%Ya$KN*uA5 zqT&MU)_FFs^ls?YzL02%x3w_zjsO1mt4OYn9S z76Q~%-X*sdlV%*UC2NR960bwn1c&8%G7ML)Z2~^5yY?4Rltx0yc`jPRQA)XU)2lmE zQQA^rMgu&y7H;t?Npff0AHmh6xG~&9Iap7HvnVr@#lE0&#@mhv30OW}41A2dESK?_ z9yY;-j7tn7e@OYbWVJ&wqHR@qH_!%AHzWJ`{FZq5mS*>J)A_?BkUcHh5`Y!orFDs7zbXm=9VTe;%xdbgcOC>s^0;tgzs8_s#0={+D=c5Kcyl@l!-nbz%~dFftnP7X-x>-Y3ghjfkWn7JHvkd;vb(5 z`R0#-=T0uz^zmJ5k92o$zJI`X?>@2l;}74wghc@Ydl^u-U_A{9UBBpSACNtYDk2&-E!{kTWP5U}JR`A?am7Bl>Z~>`^B-dw*PK*e#sGkIzPYRWkh}F* zJ*0Sr&&U6mPPdqC5&f2z2NnAJDey!NHQq_O#A23OH6xkUs6^0e>;gH^n? z5kh?F0ZsU{E==qkSdipvsdHQ7r26hhF}wB{X5Y~jwXj%e7~a-u%UEaTg*4Jn#U5+z zvmrSzxff&Lsc3WT584twpLG@6hHTV?Yu{bDcI-lcqi@qC_{R?^w7NI!)~{dZld@`i zcFFsL|L~P)=&|+UYFWn?1jip_92v7anwg2XhbE)NZOkmORO8OW7c~2ie!?y%!rHcO z)ndqEX>?-nr3oIZvfG~Y=Q0aWn;vOM2d|UbWc?aWgB-a1B(ki1CVV>j^krrWWjypr zc6r7?ZWxB2Bq^Fg4(Ws;)@p^1NdAEyiH)HXz_ENH)~;*?2`Euc)NW2kn5(+5 znVxMVigSQBnm-2pnp7k(nh|!|bF1}54$;P^Nwy_ZFr?xQr7~?lJTkn-Q!VF#y38vj zreM#vH!;T4u<>n~v#g;5av>m!97d+D%CQV7*>+K3d6Tg(FkE1$0Bai(Aq4|%+{FDM zD40nm5qdEOfn%?$!#WCt@~;#`91l5N({^)r}YlzE6 z&*r=J6mD*sSU--0_NXmEVT^~#Z%A||7rS;#B&IcIJ*_}uS2t%1?vu6a;Lf(WImhRX zXo&W3&Fx$S4O~!RzfMm6vElzY768lT*o6&Y8SnI^KBV({b=EJt3NvqBjLo|Fb>IAB zrN_Qo>=cq$&Z# zx`9zxy{=)Ye(38hmC^ZexS@n1z-;bVOF^>FaniwN$0c`?vbZ8=wyq5W9 zd`BR+SQ=3^2leK}p!+P<+F;!y1s6tlNbf#@&Xtno@_;Vxop0%0j0gZHEJkG+O=$Gv zl~3<8%mgM9j-kp7`hRI87v!*Rw6DI9y?6<8O??ZN(Oq;8X&^?~928j%7{rFtXp;gC z>%w1fAW+7WtSBqowPsYAl+W!^JjQ#aHt7KG3$8ez#5oEVMm*88-Bi3&X_9Ejm$Yu6 ze=qb1g(ld=YTiIUN2HM>#bM=vRdBIt^%@uYYIO5}9GRW73l57o8wJWjp^DK~_mH1u ztLq6SQZjn$DW1336McdyqK#bVN<8g>8~`E$?O9chIyxi8wLcj+c5{#BrS)OCb-gyl z+9;?l^v-PvyOTgh1O!_>_IAQw!gVry(VXpmKIZq2nGARfI_v69yhXQy0&Zuh3ug z@;h9WU^=>j)2fNls(<3gKq8Hol;u|O07_kk{tT23W80BIa|Gbc(Gqe#)j1?;#P>i) zqm-q#TV!&PZK5ISx;l^lTl_?H;^w%|zEk%tG$u93ZMds1q! zlUXmI znWwrmz9{2Czd&bv#)H+DO519hm-aj!_Cj1J;~PKg;nDs!!Ou-G3=Vyig`Icnm-dV< z{-f99wN+@-x~}Wh9n1_5Zn{Miy!L$Nmj2A3v!$>@utgE=Ga5?;n=;mk*h;TlgQL{G zS(OS5I#zVl*ef1KLn+y?Bui3M{pbRqYI8#S!>_dm@#r#!>hGX}#?*amAmXaSA}OK0 zzP(|z;yf%7yJ9Co%Y`65dq2N~&G~t3SXbL{fj^X<6=sR+G2dWmzSnzrQQtTJ~r_1f0&ek!Z%Ou858RfXB?&Z3p= z{SD`mczg4|SWme!2e~ZEE&DGkf}J5hAfZ;^7;|h+fP_!`^Ek&%Tz!-=C0jVCvfLOV zO(0dPcQ~@>du|qgB>CsDTe*@NwQ?|zoxJ*#p~n%WaqU2xPb=QJh)G7EAAK--Q{+fK zW*V6zFb?K0A-;WL1#~(4H0ID@8j|W00mOCD^;wY6WK6pUNEJ3$wmlj8P7}{C0TW`8 zH&UN6Ah$ZcJ*qt0_80WHwW*}-{m`4=#)3NU@B@Mdw4sujNOS%>XIowOt)}iEwC!We*g1Io+w>DH6zA&Z7NCKFt?yDnX8b~H`A_&3Ie@Y$yQstMlGhR~7mpl$4BY11{v%6B0I~TDX zWn(HHQI?}fF<$_%5q{}0VdJ<_=k8u$T_`1yEczfOsLP^qelLq#CV@yk#3wxVOqOna#W zV!hG}teDZix<&AujyA{fiX!03@U}l0j&`E3)?*dFnMA6k2pLTk@<64JF4!+iyZ;Yy zL5+T!_CK0Av@puKdC0wmCs0knh)q{4>l2F$*7=$6^UV4x-_1=bxW#K*Ix6&D|2X01 z9Iu^-@4a2|84uM*yV8ja2i?Z7*E=p>YsWcVe;dPUX2C6jSKoGbF-z=B_jMXK-D;!j z?z(Mvk#9^%#D`(WSCFaTW;rmDIuQMSo;13C9{!*8a3V5k6d^+EoQcm^R`SSj-`^r3 z(e?XkYQe112525(IRP{5qyt|-%C*e#{^0O%S=ePON0sY49lipV(2WbKrK;FjnPD9j zAwM&z#h^g85&fVG%9WzvYLzO}S?(%y0S(59z#(h69-{eMcbp#S_Baq1Q~UK8_E6&{ zR0^f(Ji_O=;LYtB1Sa_WwGI2Y7%WZ!Iw@eL2to1l5W`r*fK@Xu4rkl@c9uk#2GtYQ zEK{!2@#VM)Xio++-sb8jiQp>P5Fo`zP?B$~+MU z0clBLRfWXt>OuC=;Ght=3r;jI7p={o})glZr4u!a#hGV2*L- zB)~#d(n(43?hM5gz3RSA6AE}tmqO4mYMe>qq;kTkvAAQW^u0Kz!aaI;)bl~iCT9;c zlUP*bO1BO1w)n|buYgV+anDd0_NmdgIp%SGtl{Bp z2IM=>N6m5LxYZLU5NjgY?0OeI=200$jP-o0@4bw_>7Id=2Q)}I+A@P#`oyTI&Xvfs zIm|2<2H#m}5=YIT10(_EDSZe;cfAPA9&+XRKZ%GXWjH(sUSL9A%)W^cIVU&hv87tuaa zMmqE0*pjvaUv@!A&a-)`(b!Qr5$L3hgjx|LXt#!|Z`I6%(>vROw8Wz5CVD8Q18;4n zB%v`Ot;`k1a2t$&1)EIW2~IEjK)j*8jDJ?fjp~ZAWyaJeIy+{pLaG$Hv4-U(AFsB} zmrX^eN+a6E>b;RV<#6G)x?9Mqb8U)Cc$ad=!#HtQb}_cMyR)e&tNT{+%GRD4|G8f| zC!U1>Q^p1z7-2gZL!3vJWrP)-ymlsaE4WACiAn}gL}ds0n@ABKYMzDYit!#4`9a?* zYKQvVXy4C{@8`zE62~)6OH>pSkxc@2_C{IAkHQnSyH`*1&9KFYCA!;$g`{LIG|!Wu zgHd@C?^pVYFB0uiyb=r>P4-kfq9ZV09wPftD&HB8U1E@V^T&~_zmW%F5zOU03}p$Y zQu4&Jt`!Vtcg(~e?ithuWMorRd!k^K5kYBD4UIN*666r2?+(SP_dUP_r#YtiGpSRc zhWGte6J(2yg3GM?Hkxp4gQUwmu6mp(TpZIp1_OXKd5kyA$ChL6XM%gQxxfIKe!gaE zwy9Q+Xk02XjT5ck^VnM%B;sFKUb_#(EuX+ZxUvlY`jLoG_mKzAUz+p!;=A6x&=ETP zLl2)9eg6A@L*DP6@y~?MvY!kZ-Zo&^FIQeawtj0@d#?$*YPQTh9`wp?>b9a58=g%< zaVJ<|X4xX!d~-XcB}Hu;8s^!;pP68txuxbnLi}J$eI5|j)A^Xi_%K%6nwj-+X(Iyb z^YDte#UxAN?Bem(={c-B#swnQ)3GuTbTwj)XH?UxJNo3|$4=({qkyYpIX z$DX#g8h|E2);(<-HYSJ~+UyImvSz$;{&MTkab7X!yeVs(Y-Z z2lDLw3+%+v#)woZ>){PQ9C6`T}IYq9GAsSPf-zkGh;`h5ef zR_@YfC=>OFD_Gu<`!VMcnTYOM1#TPp^mA!Dr92?Xliy|d-&ngz!nY^1mS%H z(^CW#ILPP|o9%B;=N$_1%lF@s7Z*6(w8cvp>^&l%+JgC(l_kB-@~0$;$=!kMExX&uUhJ_=cW!q?)fY5u zt<+5r+!Q!08!xTXeP1~`joBSpcGgK`7T74uA_G@Wb^RudQ(w>kfU@qkA2!}*<9Wlw=U0RW(c$Ie5te=D=7n06L8_WH zW=l5aZ3m>ZmXqkb5nWkn85I^(b}{J zDuy)V?Hj13;*>C9gZF6AU|1$OrD|rX&FZ{ZHO0BM+Vx$27|q#tbkoeRv;AAT=jZSH zCV2n15#Czag6=*AG(c7~#4vPrbJyCrNM%@PdI#NJEl$g8I0Py zK%fVypm*x<0%@4t`*TW)+H^c z5)q!g-#984wamguj_1gtV9{c`71WYev4S~~RYaU`jy2v_P=zr(6rwG{=TDy1#qt(=$wMOaHMMrtl?|+u6*W;H5t&FFWOi-{b)b2oRko*LvHT3P#mJL{ge7gRl z4_1_MIotG}M-wrGv$Okq4h>u#Uldncp1QqvL3G213}UC$3tVNX#wU$y2lG+w3(L2w zdwO$SC3+yIzqcc5eUCKD@eea9di2b3jExHnUlXw^!eg~F;-FvdP`~Jm2a_W%!67Lu zEpgGZaDP+gm#D96U-FNRt-^U>SvP;&bz6BY-mQ)SH=V} z7X=Y+i)e=Yl#Z-6mZCS%u%n6AhK`7?rwQu^zJ5MZHl5N;Sddy`!j|V+HZ+v3J~Rz4 z7-&!t?$419)Wj=7UzKs^(kDpqk3&W78_02p*4|kHyl4FWMx**Le)BZ?$RfMQVmExa z{!oZ9cGy3p6ziv0Yogiy{omegloLXP{y;|}n#KZQgV+zX65cPZb11}UBu$bWXZ;)H zKWLUcvJ_HsXbkSVoX_G#oMhCJ&^(9YP)I&E%FgMOppO*^0tYzeEJNdrzec@V!&!+I=wi$t;Ls#|>TrdMEGm&GQG$C4z;MZW5GameD4nBq^dv54ZK5TkL#a;B)%Eh1d6cR@AH;{_Cno62G+e8}TQ zf6-W6G~OEOyNM4cw$bq6Up@lfvQpELG@)y$-I)jhQbyMAiT6={z8nIRlDSpyc zE4wXLj%DDWRr4dW&!32j^sDQ)>K^Cl_Qc5S&cEHW;GaFUNN$8X6T5) zc<7u7d1J-c$paT&?r3kP@5!y7lDp0P>y7{z8SQS7`(-QK?LZSCbeWD$cR-nLy4dm=7JBn6fo2NXz3@4-Y z`*_LUT0sr#vN7Bkf;67fXC4uS%8FK!yp#&qH&7lojt)o4;A1)b=EmI55|Jji3!kI0 zM8K9Oj2|ao1V@X-;`5oEQ-MC?DvsaB(>Ov;R2nfb$g5wqr*NrXt{seC?x-Xz@G3_o z9h$4XNwJq5T!g2zW!zm*Hwbu4?lG>th5uujBQtpU7)v#zq}u*In_}yGt!hhz03G^v z)9fO8{yb1HZR%nW`i_<^7u|9*x|L7tNbbI#{68@~MicheYHnRsh&%W{mtpa-ZMGo) za{m`F9#EfTKr?z`z=&>?sgj1qTICrJlJRYbYo8|?}O~3=!(0K^J30)aoqVRCmXp~ zN>iWo8UAG}PAlghfk5B!W2_VV4m88`8cTc%fuFmKT|~YHYu%A@EUmmvqu%ACs1v?E zSdk9CT8v4ZwwV12S~=cdwmqDi!sA3TgRVp_9`ld-#`{33gq3k^5omKDfb|VV0WSje z{e0*#-p7jHhJFYy)(0)l2^$CKk&i_5*zHIt=)tX_3rb4Dr?%f`E&Qx?Iwuupiu)C2 z(~2gU@(pYa*FaNdEL0m-gfYIjm*46GqcexcX(R`eQp4pInt6Fx^;M~sX$Rk-dAtL) zmX>E6Aktm+P49q3(3W`Xy{%} zb56vG(T{C8twWAYg_1X3=WMn|UbGd>RYfZk@C{Zcu{>MSXEmaTE#rK0j|3UU`Ri!s zN0t_a(XS4+E9!LcBW%G(WOJsS7=ls zGI~l>RK#MCqhc6Dt}G}SWF5sv+^2CnwLS72;MUqFuR7Z?xBv&izpkQ3+6X`I^tCT< z+&2*2CrWgxO%5av&*S0y7+H)5*$Jg`IK%}2^h&tE%c(n&K+KZ-6~b{KUmn8kZ4>Xi z2QA^b$X~t{UYW2eltLQu`Un4dXQcPhIfT;$MTaBgK$^kWdubI;3NGBZM`u2&5Zynr zPkj=eIbisfntK{P)H0S9d3r`J_Sc%CBMyqoYMbtTH0Z0KpimM$SSRj>+EGDlp2HqI zsBmOA{X#fPqxm0N6z_(67dL_c90Bc;75*!GIfl2-^0*U`y)^t_0$?F8w?})z@_pYp z3}Afg=9ky5UF&WG9Cl}%sq5~jyLMYznJjy9CB1RZ)O25FNKseee}k9pP8k}A{a8M- zw5_04Vt@mrJLh1VfLRpK7EnwR;J?k!uhHLVc~Y{cxw3mqSD`P!0YZYHR*+o=6ZYs61{?%*!DX)6VHp z&;79IpG#UcG?e!c)&T(${{)lc#G05IcrNWvghmwUG1?J72+vgqjS6b?>%mI2G4;Up z3&Mkft-ik2K*-Q@V(gPEg08UU0Jb&wIXE=a7UMa|C?ol=qN2G|fT9UR8fTYfNKZl7 zbPLn*bxq_))*C~}5t5C^Xf8MFfCL-uVpqKI<9&GGa>dI*RG3p))aIzD--n0KbO&AJ zH2v%8kcCemQN+vip)#dH7_1fOISK>95)!DC`kzD?1>B6zRfzL;=ua6Jm_P`iEX-uwNqBBr@gjf!y0^_1+mQwxYGON}u1ZGzwN#{9 z>S9BIr7@fl&0a#FGRA1=fdbV;n+Ps2(4JF>z&&_82}|pm8M!Ihd-Y5&ubkCmIZiBi zv;wORrmx^(_f?1d?%?!SK6~lMKMp+m`uc;P_kW_#yei@`NO2)58ra1p` zS36^2j&(4B`KRC;jolpt!hhPeYnO~?)7zzv&91wottcI4Wm5yFCbXt_=aP*S#B9MR z+CylYV_#~HFlD0qvb`gn(d?1*2iP^b{yrTaeC#O+2xi*OIKrp~7o(-**(!6QcpHJ` z#N|P#M`5pcXMdK;Ks*J>weJWC=+Fd}>rv`+g}jZDGRO^MJySs)1Vy2sxFS!Fk_2Wj z_%*lgT=YXp>gN@+oY-tZRf;i)H3@zomf|_c`PTlM919o1Qzz7&$VoC}9iCGT_Z|g3 zd*j&amBbSb|nd*3{S6Y#z-{ti%Mhv(vW5W^R9b1+X{z0ILXYkzU zWeL8VFm7djg`x@E8)#S2eEpI)M_c&}^NSWxn`r0$POE2_W%Ad%uYQobjos z*ECm4+r`Dk9=w)ao_epi5NP}a;4_n4v z>T9R-rF~E7kG!mo{aGES3vd0r^cLjL=0f`Pwf)eM?kMf+svnf> z)md0~v#Rc9^DnP;+@vCjwG2ip3btkhG4wUDwz$?f02nq@Wc+%rYDDUxQ-zYggZ>+8(`ErcbRj{W46YqvZ6({pwp2O4iin6yT*sLY?!G< zF2i6pZs!(^=YbG>yieMS^ouNcFP3&RR~FDEmVlzgI3!_dNx7gR(d6ELo|CuK#FSm{ z{(S1QIis1)<%^XilP&%)YKe@Yw3uXutt2$XR!(bvk$raWG(bp!t_tB{0tUq8xf>uA zRBKcZ3Q@PWYz)AJ(S4jhymeWH+ZwMmzH0`rVDFG${Qdy8mZ2!tV0o9$x8)nQpu}dH z0E#>bh4`cERjPumM@EqjrE1)fSRm{I_8(leLWjZ8>Ls^JhYDZ{npHqabdZ9EdB_uq z5*$oEn2TcPh|dabl{14gp`FvCJfC!i6g`QrQpF;az!P)<9VwQGU$r}k;Gr_Bt?~@H zc2*8{gOBfB&^OV!)$OCvq<{tk>}~!uP;>3j6b6(zI)Zj!=YDzZyB~k7K)WTsK_IiP z?NmzUwPu1xSvLqS_5Ghd{BKot>WwPF-K~Wgt`i~r@x=abPc7xSgkX^SuKBEi%y>d) zPfkA!hZ}von_EGqz(3zDOUK(oP<X@mnb%ZCJd{roNNyg zps>4Kad*;AF-!zqaFBV_<{>Wg9wu=-iWhR*EE>Ti*zJp582M%eX=Uve3= z83?0sI}uX$+4bZf_P!gQ&ENzAznFlK>3l@4%&Bw~+v<8lc@No5q>cwP@B)==a*aqD z23NDzm^ZT?l{H#nVH0)0^`cE94yPOmu7kEKaqU|4@rXc+Rw;Fzj2FcxJ4-ru`13mZ zexM$Uh?El|uEz{H$D(9X2XRdfB%-UV6_(3&nMtUjY^`k}5d(#DMp(Q5MTmWJg_Per z0J-wi(TDRLef?2BUBmicnwyd0K^4W?gdOV|z=N5u)-`q4r5tYm{&ID4UDt)vSzWJo zT~F>V$j^LlWa!O6(T6Jzs|NGi;FtG*dj`SliGOjpZ}T={pW)-3L_%I?X3Xdy&LYd@`dglcn0F%pxcm*}+b zcDFqh{?9wTJROx0QG=4SkAGvc(RXqYcrs3}Ob2+1wP621QrKoLv7ERbi_D-~3^>13 z^u%l(uuCBf9$5()P#JggNd=-HuqcWZRsiDym{UTPFu2|(8;=dc0it+#86Rb`kF_Xc zlmsx+=nXtADM0e}`*6dI6_pDG2bg9DmF5G#pGmoe0tehOAXNt=&ZDA-iJ;lEmvsC= z)SB;BswO=J1t|2aAO_J=MG+;dY|K}1{NQ^7d8SFW2NG+s@!SHV+S=a*3^cTym{-!diqFAUKtW@GjZzWpimU{`6e0p05C>00L}HX~Wd%?* zoOmS^6htgFR!KfIYGOMg1KyH9XkyaZ96?B=GCjqx1CT^_az%IG28sZ~M8dq#7IAtA zSi2L6838g;h`SwM0rwH?7%$03vC7tqY9VvVLn%Ea$yNMEJ`)2fGwUW8b*IOfUF9ADua%AqXaU3m82<1^JM?x*ucZE9rGcFi zROQ04JBpXGqK9K-W)*Cw!v+tqwjoByy#)e+V*$5dTbSV78dp5!oo-)i_lMTQ^J*V% ze7lOw($8Hq z+=auUL~o+=VS5;AJpk)xL`}I_Y$0EATEIvMmvU(67^Y_tCUn2`6!$J4qv34&wek{F zI*x3!z_&u)N?L%!a7VGfXgCoG*eE0tP$-;+K})@Lb0X7a6?oR`5LpSG;>Hk5U#l4> zh=o-}A&Ep`w*!6LK_V-UptPTML4qs0$9|$2jC%o;5{M&gu1o@l03W<3j7Cqm6Tg1<}GaoCWC5#PnDQc3c7v4G+TsPMtyu$l|XA9Dc$A?&lV-O>sQuxua*4Hg6-V=}3tvVbcv$v`NUak*3q z(gyQ~J2E~9d%>3Lw6nePB#Xxz=`;`wK<*Cv+2ktTzBMc~=YP50ZAo$ic;Q0h&~gp5 zDn`geHj!?Qti`g)aW^Vo*1FW>Of*)RyA+dy_eBFGQT%cGmWBjaFZMs}7!x1gP93J1DUgvXW!{lMo4Xa5 z;n_^I1S-Ekr|({+7m;Tt#dvf8(1 zwTCSm6MFNJ*o~dY(_PX}%-7>t%Z`?SywEJ{Y@*B;_YeifTS(W0F zI3*I4U?D(4^w|LYB?8lfI z++?C2`0`IlqmQyC`iyJH|uf$3|1IkEwQ=aS`A=L;6e%!6hU5@KNoiVEo>q7J|4i|<-fxReAdp3!0BaS*35^{-Q zZRV}_$#yCz&OULeZ=N;*d^>_heo?dw+!28XxhR6S{EK_i4aq0B6o5{VJ|0HOiEk3! zTAP8GGVuHm8_GjTzSC1o#GIvmLXZISBaN=l)<{l{8kCUT1Q|Hyiq!=EIY@_bOn9doBP#7mN2dZs#=xF$Bc)aZNR9@!CjaXhYxC7WA^^^8YD3|aS zoe3B({7uADbWn0bw_`riDjzApBtI3Q1FfY!p#ZVa8i52+#nyWkv`SMcCdl2E<0#p| z83$-K>l(_H(WbKD8|^J99e6TnDi7v-@tKZocpPC{=zug}@apl4?%*WvK?<;|WX=hM zDEpz4L%E?jIX@{ZRM#ecM|I}SuM1uOSSvWOqOPg^=1Qra zZGWrj)|Hh6$T^>s>^nC2Hs_FkNM2jzl^K>54i|LW`!)_@q(z;~IsAXe7RtT%H1gIx%C=RaMzyp}% z*a*B59Yefv^jl8pJWE4Lwva}11D=AL;dL8Tb!4N-lR@3)c^1uI}3?urz^2WVm~LC^v}+=V~|tBjC9VQSoN z?lBr_dIi4n2*G$0QfomSVRIF|G9dz_SYGIDb;L8H%n9_Y_azSpqY^WtBd&yR{M?vu zd7dDn-&p(*OLk7o8vZqt(;HQPb~2BJ>mbLUlaBq6FJLgb4j56%4rC-6M`zA5d_ICW z(zOH)W;CM-TSkJ^jcJzU#8-=VKF5zyV#RF>*+g6*ksBI!5$q}Nq??|i=5JUVV~KrJ z?vO{CJ=VW&P|9%{6jCt)b;i2J_qZ|>x4u8uEfPtl^X7GsN}HpX+ctb*rmz8@yZ&2E zOPe|X=Jk}ioByp+;FNx0q+9Pc(*&ZueU9%Pc-qVJHz_B|dyLw#&UKeB0t{J)m_b36 zL37wP=>gcBf9_F15`&-y7{8@dYpG0LJYE7K%6)MFZ8tVn`EEM&+!p^IJUi=t{$lE< z&1V+8@Z-K4C!Q>OvfrPq))yjbAJ}lO&v(!GuFw9*SG!{0oa*;gV3FhE<)t5we|Glg zf4RK>zgKGi-B7pj<(Z>S1TTzkik|9xaLdT?11#GcX;>Wi2NGW@GMHDNz8uv<)SE`s z^Jt_=@@G+1`B^rO;R2P5W(yxgEZ$CA(^-qF&RT!NrsK(i8LHYg8FVcR)2m>&UaO2N z)&9FRTKRK+j!2-PiQj^)5jixyfCdAp2)BXjKi~H3?)u+oE-R4oTNx%$mgyFtfB-dP zyz(UkmJ0fpR9N&mKt<7AuCGJ7Z7_g3G*WjZo&S=>vYoOeM(I$W$&*kA>Uwg=y49}V z(JUUVeGa|%5*+AR7*Zkz+Xr))=hc^*46Zm;?5TGoof@9p5}auJp~t5cQ+jRi-DGDR zFNfImHZ_5Zt4s>p`nlfox%8WDO5X19se>%uzROO0R$gB0I2RF>*57a9OMlLeo0~T> zfl{X<6FAcs=A5T%0>)K=X%=br}Ix%XW^4uz$YwH_ho)sSj=E5;6Yv;KbVJ-pWN7k}VC)s2td` zuHhIwPpdc>wXa{dgW}_FeXnfQ=_TI}Qk3@ZO`WHju9mi3wuIem?D{UO>rE26ogovh zecf@ZD~yO)R>!4xV!N)A1+HO;m2b{AB#NgR(Q;zK!GyzftOfLyFPeSMcDTy^`z2?s zFKU6Tzrg@TYZu|1&04InEb#3T&fz%i;oYszvvPFOiu8`ep<}L6k8}K#`AIKXCYA@q z?kq_iUv7JefHT_VsXJ!Wvsmyiq+SUPNrw~h;+u191cWwv7E7EEQP3x~qrU-T;B->4 z#&|zPHWvXF)COTME%h|}2uo0r`sjr6mzItB4I)@IiQDCj&?iK|{aa-Uq#VVB-cG3- zhw3FG)CemZj-Ec*yS3wfO94{^9hr5XRj8a#4#w>tkCs>y-$7b9MZzH*1^W8zmY*i) zcTp9Xn9#xdgE@ozls*1)4`O2Hte;E8Mm5XUOaS+QMN}Thw1-dOlQE-2leb^1xp3Bh z$(1R25`WJ{5B0GupwU&VV3merZM4tQ-(>J~^mrI~&a^PWIAjOy=zTh3kgqq3+)@tl z{w3dKPk4KIaaM{v6QxgJ8|KE=<^4!D=?GZFtH# zZIpk&P-?JpTQ7A`xX6NK3{dU;>hKI&j6Oe`PW1f5;&bVVP2kh=Df7Z#vii03JPz)` zdn)_n8=+q!BO~A7zwY>3ttRc2(~y6-E~)c}^bRr+N0M*-EBQkAjg;;iyQ&u+x_Oxp zNmgCjweP!$e!6mKE~TAk7)mIjq*P6bp354jhb(pcM3F+wr67kk8?<7&IvzQ;Y{D|%GC*Ry#YvV7@Kl=H#BnfxZTzeJUsEf=vW zG9e681%(8lky&@G9Nu;rUcJ$AXVr^>pAy}c+IGCS>#RX$RiR{5?)Y@uwGBRw8)<+E3@PXUxap8l1<2%+gTC712cGl~UWzD|CE6 zwHyDbf&wq(ls&~Sb#muDfg<}9 zb}Sf%qakhJ&aW^vjuTmdhPND3c5mCWH5Ue|m60%i-~)*-%pVmS8MSyekEAX{+}2a1 zWhFBIKUZ%97j>ESaSKTrno=>D>l<#FIHgo>nni*lYLdagpr$5ww(8z%NoobG2&gGG zzE+}{rUIdZ=8|@|N+Rs8QaWnXy_LBs=*UE`;*2Svz&zjIHSYa9pJ#R}g_-~S|JQZS z@BGfW&WpLwH&Y|&A3a^4mnE7IGG+<)y_Zs1-FVl*@*$tGGTi zB*c@Tt3q0obtH`>JzLUF@OeLZWE2}qjvpx4#Fr}3%Xt0#84Dv5lC*7%7b91P`P+%( z8dqv>O(JO?G=EY5DfI2*(-R>;lR%>_#~Y_rPd-ksu&jYQ1d?`ZZ0Fx^80dQt%{j2@ zS5Wo)Q@wJ0Z*TvtQ|{$Xe*U&4w@uumVzPE%yv!uCe?_B9{ zl<|8-08HeCB+oh&TmPi8bMRjS$u3q!bo6J;e11C}t}o1dSZ_XQ`g2+e%OMF<|= zH8ew9g&>tYPe5Ok{oFj^C%~wPxM98Qog@Ogipe+Qz}QX`dKf@A2AGDEUw$TWMwS06 ztN=q541!902m`|QKo}=LIk*=hm|8h>3JwGR;G0aurQjtL@k7q&;rI}l(9SE>2Mq+s zN|Z6>@gzk1_I^y?j6DvG7G$|!GJf()gWq`r36QP8KJo0PYYW+Pz~Z=}M|J`mNTYZI zX)W*c{HtT&zEX51^b=9FJ)1AQV9LXQdq8p@aM7~fF_G;QcJfJC1B%oMrbX{={^Z-x z4P}^;Z|x)B=iqKab$6S0ad_P{au7BQ1ekr#IiVq+-PL*h9N$YO0DXUK<>;i+248>9rLX-cB63nR$HeKaq^swmmWD>bTtB#M9p6%Dwv2*R0gjIx*!u?R`ueR=(02D+yF16p=cTemJ{H-VuGr z%5>=ozH5Ptnh@3~Gm3Q}>=K95s!(b56Rx|?KutsVC5D6TlWit~Cu`$~D$V=hp(9VY zo?t7DVW-^mjG$DVLxvTMgUhjd#qKQ}<8!clZXPX3$PH5XU2ue43_bES9Q5StjtT#G zn!kovETO-f`;>RB)^vYq#UI5KUM^vWL_d&HfL@{@+7BgAx+jn=W*Z=zvsY-#8PzUB zz?TpTm@oJP97J&m5^^b|5Aafs(iT@fC`z_QviCuZD$+-&7JN_D>Tz@QRYn;^siDLG z+Qj(6U{M)Fy};oc!wBEqW)LfwK2afb5lk zF%gIA8Zl&A3`0n3)DLD#LIiriy|8X<9^^_?mBWW&1>H-V!{iMJTE2x>?46m?GR&wl zr}TnnP5c(JTzxqRikxMPV6%Kz3c}I>Y_@Q zG&|gHM6%45dqRGv`iRNaehD}cges=YT|A?0=Ir(cwa%+M*)`^R``t0uN9nZf3O#gM z{fn-Af2sy*8db}X&Fx6;^Lv+vuGHF^Bvb5wYtcTSJRdlt1L>?ioJ;#=MAnTD2`*~p zx6nh_l7aot2vRtrjunHO=KTLN_4sgqHo&Bm;yFknmiN=%aF6&(|}xbDg#j*PpB zam4blzS!#B_#sUB1WZi7w(Uu2sV z(TF1~D54FXVK+;WT_f=Z&3RuxmYAT;!(y?b2gousUzvvF=LmUc@H=%;A=)JqjgMv$ zn~`DZ1*i$`{*7Rr1t*6dC1zK~dB=Q%^^y5u*++^ez@+3kO3X!l*@qXz8zkc9@Pj`U zxR6Sjd*8!$3f=T65LLv9*nhZ`!_l2!gSB!Uq5*%cg|jwKu|`b8W;S9^1Xz<9?%A52 z0?n32F&O=m$XY(rH#Iw&G$*3WU!~=?w>0>#6Egd$A=ld@_sg1`YeRE>Ya5*N+p<>L z=k&;J%E)axL`%K{Rn0VZsPXUn=K59U2mj^jPZq0dlWt=jq4@0n;Tr~0x@dop& zd>CO6Pm{K&jXT^xk#k8bK#65eG2it7Mj8I3@PLYABoVyuK}3IX!2o;<;Do4DKC{-{ zB{_M1cw#*f?-KWyBEWRInL%$S*qG@{Wo~nlEhvcp^D3-J!S6OtkT#(D3Gotj-g3^o z>siN;@u9Ei2&FpTZjZ&;fGWbzEjT7SbeDJTeo zP2khnyjY=K#>T-?9eW@(c>N~7N{^yDBSu&3C=(ZNc^t=K+wpr1;S8H>3G?rw%Wu{^P{8wLg^CwcpG*}5^F zulGy_7IzXH_y3er%n(|VhNZ%Qmmx>FyC1~&toU=5<%Brb5cwjAt!gSYJ_5&djzeI;*lecHBq6qv38pgY`$g1@ zH>GS{;U$T#K<@>DpwqxwwV9l{3S`@(3@9uyaWUPLT%Zp#?~kCL>yzc`uEz;3k84N_ zhSpF^48Fnxg39POaJp7HCKm2sxruAwd09jM3>PI-JW(JAIJo;*&b9DBouqj3Xu_h%P|HPJKdYtzZl|rn+{S%!9m^`oM%VbTKY5f zJH!{~j9%3ge0SZt?9|x2?2o?PuqrYL!Zz~X9emr0vWNTbb3`4zJHq>PEiIcTdiJ%LnQ!EL8U7=VknphbTbivXD@A61;u9Mk+ zZJwJUze(GU?o3h#Q$>x$%fXD5Xq9Z@}+EZi@f8hmLBaJaPjbC8`AHX z=DiR2s$3Q3?i1o@&a_VJ=(D(bEUhPfK;xWjEB zgB8OyjF*NC_xzzHsKFIi}Gn z$sU^jrSro}>ibU+f&IF)z-Du1W=~fGK9BHNSEUD&+J&mApruI;w&I(v6g##-{c#85 z{i$W2`ZUq?kKd(p!HPtqZ{`kd-f+f%a&vqQ4aqKFj&SutuzLXP00Bs7*ag3*-IRW) z;bruHW(IMDzAm(&=jJ>3;e_go7w_c)B}E%Kt(+$0L4zk%Um6`jZ@IOPRp2r9(9bb5 z!3%Kbv~S!bE%-cxHKkPDPDgvVRmU_kh`4pQh9X`Z2k<~FmR7_Cj5rT6u}(KdxX`na zC!_|soL%;9Q2)0nmp$#~wgvevUtZlKYR3ER|58Kqx9eKJAMU?A zC->T%8^4?xnsfPF*O=zdb6QTMB`>~?{4f5Eq{!?2UTaEYV%za64K=nbo`=&Ycq3y@ zm{^RMa2UXumGf*$%l&*S`^b7|O0|}S2TtYj0X&^KqV;MsojuvgP;8H`!47%Wu(9S9 zx;{Z|yG&pJHXz)0?H|yP40c?aI&ZL@RMHTE;TeG@eJm)VYqg(&zRq9_{QN-mMON zLn!0X(W_>}4q;nueaA9&MEhmPx^?S*8N$Oe>4s2gK-KAurXQG#A>xfNF10UP zP?pJ;>q8u$p<4=ao!#v`IGLrKe@0TIndR0?P!h|yQGrfo+<|WWRs}71mdF{airox% zQU^gj|K63I!}P15Lbu>yK?0rs-rS`u5X;S=uJo8k3u=G#J7rYCr|@l_AsTeCf;p?X!18<#I%US(v{ z;BxM7tI}GRk_1$Bp;O(dTM*G1h$G{k(|q#LzP{9+@pu1aT9WzOsr~*1xgD#_TLJQd zMAAHFta5iTXS$p?u@Hf(CcfBJSRs+s>j0c#n&p7ziya_Uv-E882=+~zzsYfeRZjG< z+ThhRe$5Xb=PJ_$0Rn7f8_A1$BlH-d00cjvqooH+D(-*$hYr4x7PEAM9(NJ5G6Aw_ zV5xDI1SzyJx0O$pWWr3;GE%)1&pa1uBu6fgFafIz=naSLVUz>q_rPFY`1SCznqCSWGayFs$`3hG3ZT~w6aF81>`$2^(a-kRG&LDn9>GpAi*+b?@~P4ho8uF(kS z3exIO-HgAjIOckB)uO9Ed7=pXUmb|t_v`b@2mppU_+;+}_ZlpTHhZrQr?>H+Mb-mU zR@7oE+=HaQ*u6^u1SL>~DKY&V5yuh;<&5PRQ7b%)!g3+=|BfnAsO(&P$!HwwjV|rC z<1`c8Vqcj3MbMZRZnZ{8%Jz)$Fguk%046;kkk)OOYEe}+_U4gI?|rUY?8X?;e|);Z z(zSrD>a=FBpl|-A1wUW7utaU_O`^yMP+6U8&pb!#X?2<;Z&)3BrSJC)X z?KT;#m&i?2=Uns?g(JS8H{JW81ZpGUf(8XseUQomxTRyHm8-5a|-9;qIt4%c3qofB{1il%e%@&Ox82NyB^ z2_>^-JrEkYVfcpAV|XevGgZLZvNT}Y0}UfxvrH~I&2bUVK%Z?|dCPF1F>LEz=97^b zK=(=Mo$)YXLf%4f9!gN4Uvirz3(DB;c+Z~E`SREEA}e>y*JJ=t8!utg7|wpTO{ivf z=P}ICTG=NnA)<0~_EWKydqdr0v(uDjvpDp=_1!1Uw`EP;5b2+dIf`C%ZS|(8G)({H zsIUEx4vlI1p?KZMGtXh}pS;+qZWT!aTa`Z0{@p*|YsY5#!&>Y#>e7`l3NR&*8;Y@L4Oc;MBU$q7Hvy8Ck z6S2sJ-BI^eTzn@B{;oV~lEE&W>S`>~;4>k&uRk!RFmo`{vI?Le!C9qaR`p?qJ}T80 zVxb_Y+N7)f{mpegV84_~dw#{Im=y3|L7*Tc78l2aC_jVe5xly1G1$h2D+xw7j%1lO zwiA>fxEDlMtApp5WNRzmr5?fvg<8#v(HQbd2Qf&A=|0NWo=wh@%z}%SCLT)50$Ltq z*AI2+xY7j5p*d@B$}u1c@K3j6abX9jRu3rPwh6}(vO&%~+I*lSyn~FX4a37@?@{;bzuU@HvM07~N#Q!t z(b1P%TNZAM$*uf4YGix!#j11X*0ukZ=-*J){&Us$o$Ag(LJv2SrPBN(Ck8)O-N^7? ztN}4Us!H+y=sB^;|1~VMnViON*GSK4><)cSwgnYtleYs{A!#&=1;daNbgf&#E+-K# zvbmzA#^uP2HvKa}nW&JSSUiwHubmLLDTsV z&a|`Hnw3x)PK(|NgI&D>uAR|}S_N7IS2V;I+A9=dL7=6qB=cag$8_sIDBiZpu~gnd zp|81lyyBBsNE+;NvJ#%AdTuLx6Sv0r5>OKzd7}*)U)I~2C@f}AxnFgAmVFdAH!>yK zCfiE8-32g9pqA$P>=OPNR@XZ+z&cF#diEOfplh@9n0qlo1^+~q%S}stwGNj+v&Tai zO$|G8oR)s5Fq;`8C+6kRv%}D77?M0bB4L`Efr@Z#``VsKGrFuS>#`nZAd;;v|H(Uy zx=QyzMc4}SjZKSUBpFJ@h**`XV@`%1F*iwR-(JQcn&}XtN~PEes7eAmOtZ0ZU`HIY zURmarFt`>=gEKJ1TKB#?fmsXLqeiui76zS70bT8_-`%m2j?>Fs@rBI{vdJIc`RJzl zE%u64y_e1}b1&X-y1t8VYWCZl9RNBll{7T(Rxqb#o7Y|4WkyA2OLJ?>N9%T1wW%jS z+Q{7Yt?Nc!U)e7^0~6o$eVzY?zcj7g7vpcJyK%T*+pizZ;pCvWUt4YN6`yBMKEBlO zuNsJca>pbWYykG$s*Mo&mzBVMFn0L;SecgjiToCEJSk%yJ2{XNs?a{I;h|bLsF$lI!!hixk+$}8NBFh1jgyARkhW47;ngrEQ3mZ5A zm!GCv0zGs>jU$AR$o&W|a%0~^<)TV9lM=lDez}Yg0F2A{Vlo8t5qG&tSS;0xX)F(d zZ!D{FmsBy&ld3_vbQG*I!%QG5K(?eD+5#j)8N@Vbum;Am0)}$Rkr6(Yml)>?2ZyKu zZV(!D49#;3ugUpX>%JXAIC@n6W<&&9+1o;=L>KHF!L59%8gO{0Cy$;z2qM_c^YORK z&&?V>TPFdT4%g({R{qnBYSB;#GyUacSF!5w%et&DZKP3Vr?U?vFEZ`jj40v5H_qXhB2G_A5Vq{Q&Nf_c+3r zySsgR%#AZOIad{hZ9RX(ySiU?lu}eK7xz2tCP$#?3}{4H}M9+B}R6Z<6s_q}G$Dx~vFPhsWP0D{Zrh zI3R{h0IBZ9ddNruOoU>Lk0o@-%4QDr431WCj4f$BK@{r3d18zvaH|UPPhR=VLk@1A%gLO6#+9~ zsu2+ZYpUoG?G7Z^nv^BW$*#{+dzu4kNFZAS;sArnKsC_^1Q#$C!GmJ3&w7t&ZyYmd zHByou=GaQDkfw@B&#+#$&2OFq>TWH7Lr28UUh$TL+oP;db-#kfO}YvKuQL-vGQ!fU z=bhc1*TGgfI|IC~oDlnmd2*XHwn~98Iekh}-Q(USK|~cXf=U_0?6ZojUq4CAHkGWS znrsa*H04-|(Fh@kS9wh|%BfPp1NBq}DDaV38kzHexNEhXFB}mHfQ(V7j3QMEPBOwu zKZ!;35l=rpoUHpnl^+XY;70MD?^cC+7L_Dr7rK+@kB_K>Wfd;4;lG~O@z!6i7SHaR z)6)EH-ILKL$N4YBw9@RNeqipY?v?!vs(w^6vY{cy-!d@QH_+b}6OD(}4=DEjOakoT z%=Xq1oQVz6(4-=|e^~OD`rhR{91|`k=OK$8UDL=h^t3c-#@kr$42kYEV=>k`ThoRMTZ98`OHugqE+3*)6AFpkiX6X?UBHtG| zlLtPHxjHyV#2*OY9?Cnpa>A%;TkFO=J7FtU@Zv?^r0EQBki2U8$7s+<;zxHYW|Qy` z190z@=E@13fYcfS)aIiufhZ_06E_Pc$UaNi1l9aBbT+RzM&08 zqn{u9+mfkkr%w2D(2~a-!*P@9LRTIDK15?Rl)|${IKG$r|3cIEWJ?gJu!roCWg*8T zfmm7|jf>&P0+IT1^oRw5iU-IPm%>=#CCRuz0pu!TTtJF()rXm4$MqG*z+ie0IWv*3 z;057y`)wjGYH4tv2rHCjFs>-<%(%+wMS!QZ@(tH;qBOD>^YFpeE=7DJTut{43AG&H z8NyfYauS7)x{a|Z`L3!G$28x&3raJmW)EWH9TzV=pDF+U`j>m&+q{nRt>OL~6jxqL zq#L3CO+@qZvc}IuW?PoUG~d$hj%s&bQA<{T?#PzQr?Gf{v{^(f$RnW}=Tigg2QNp!&~rKi}uc2o)#Gp;?~ zIqyI(=2zWo1XDPIdC@2zxmMVy$Y01eK@lIGL3>XhRGAm>Ao8SE!VvS16d1kOR zZ6zgN6FtclcakWAdaiX$H5Sm+>`PJgw~Vl|4}|kFqp;p` ze3!sM@Hl=V2LbbDKCCw=&TiDz*f*(>77a&J*!YY$#q~f@QLrt^8**m3V^z1`S*jmk z-Pcl(Q0#t*-W>xd+W9@77-C7A819%%zpI6N4t73zmv2J$v(e+SHumN$`At_8SlNv9C9@BdMO81e?=l#UZ=stJ2aj}1U-J-_D zKZVpa|E1!5ue2?7&1>q;lUb8tt7`gkT)(y(kn11HYIFT<{!6bNI(&X0C2%y#r6y-W zG>llgs0G9VdsrSik}xrK4E9D##jYl0k+z=sKRCfUG{E@D8%7KG9UpY&{VA`vHDc&3 zEHn$F zJRL^x{Zs8~ePvx2?l^6a4dq69pv2sVMi@_bY+s3}iu@zXdA+OWq6_9Ij=SQiozc5E zG%F>Hhh*xAg3La}Iuy4>)wUH&IZt-gSTJx+A!)$K{gP}4o5jkJ>ElM68}E-`di*V= zGI8)_4zRyODcF1D8v#)9Mr(!Lnq#q4tu$W-Wo8}r@SEDP{oQYl_NKk=DrtTzxPA_D zP(?^!m21RHryxH*wAYPoI8(ii`n(*A2z zTUyJfr!OMO|8BjUA5-6aDWmE}TYBz|@BGTdZE5L(aq+XpzD96^h0g9DF@en{d3hN@FHJAgZwbaK>dt=4={ChT!VS{Dnr>*ukDv$X zK%j+U%SIp9=qLqv9W~sXh$l7aJbti*(F_iRl2A0r zpE)WckwPI})KOj*8Y_zX>ub<6a?s>F=+QAgo?q#thJ%XPQHBR zIpjEmaVN45h*+{M!*M$N734?;DgUv}#;8dI8xLSH*P=@CB=IUZpECDyHklilpw%w|p)X})>JF_bG z+MU5;^0{-C!30)8z0k0gWy#no!=zxhp=&Zqn^WO5+eoZHs5L7L-KTSdZ+Y`9d-kZl zsSz=lj!ic2s@=_-cF;i zMd>^W<62|oNZgAhQ%G3nLSolLj}*H<#*#WS=_f4jN1Lgsk0zUxBCh~WV$=CGK_U-C zNp?HY0;9#LQA-|#T8j%T zaTk)q0zM?q0bXR7fQrK4ML+?|1K1f=R+$>)Cn~M-{h6F)aMeR6+Hxo+E>*~pE9v)u zDW_@#w7V1@>6(UnQjz)a$+MAMfGajQ!|nDzAu{U}UlOmTcK!uZYt24=C{z$Lb2=l- zEErj`naF8#+q>BD2FB{2rfZvm8zDb=uOOHk1 zF!pG#qKda&QG47CcU4crM6I*N=Jd6u4d?9%-U-?Lx502VLlGo_Uo0NkdO1$Ke3O#b z{4GcNwO{s|wiAtQ|1N9$9Sd6DD{Xs^?2B`+-k@P_>zVCsJymI1Rdph_wfSOVpva;i z5Gk?+_~V$dY1#cH%%Nd?mIiN*mDPB_Tn4_t0gUUn60VLaJm=krVdI zM6qP`$%c~24+L%496g*se+2G2lF{M{oOm88M@S)UBYbB(KtxG8M+P=;+J+#n zGdQ@u)N$bLU94Mls<(GR?z32d8gjrhTFx))-pn~)Qq{D!uRHb2zP#NpAM~@TtJA8g zbh^ebZCQU0&1u9x}ipf%I{|IKhWT1~_>@5;SCE~oj3 zAcQu5AC0+aPFl3n;(Ow;E&*!uBes^&0An;KB^WK)Lp{DR$LcPlNQGPxD}1=d#E5x= zWs^H$!{p%lq^v*BPFm98%L}4?V9h!^M#3EBqzGk2FeK7C|?AKe*vO!sYCc36wLc4SX168^Sv~88)KkNWYMZ@6z;|^uasO-8t)UrGn zbRcZ^rHGCsSd(Sdz>$|iLx?orBCUj^axWqgL=}++^J7T*C_an3KpGMDT#g2C1z$ju zcmf~{x-N~utk-s4PlSgD{o$cosvqE-i3&MB0OA1}<`4&2R%Fr~ndo{ZD!})l3@2o& zkj%|N?wqu>=Qt@bHEYtO(0l$|Drq;auR`GQg&RXVuRrH}=YplwRLQ{EzDlmxDX&AO zR~bmetG!=ge)xL69a9YL+|5dpC0&j3-TZD8cGm33BzZw1rtm398jFtbIz%^NX?C1* z9!rKDS4U8c5D=&F_4a&xXNbHV0fhL-uJBN;!8p{qwx3 z@p=4LNnmR$xn5Is<%y~zYMbj@Q+9eydiLISM&+!}Z7cInYj1D#SE^{fo=Fm|=^J#! zSmWQ*xVUk9YwMMMDM1utDY`b@zTvb<+am*55mFf^F=b6M8fVR4x^(oC6^?E^mPi&a z>?05m(>;*q7l;EWtLvSfv{%7o(FJ4u@G&7B$FN9pvY>~aUBW?8o8ruPQ)&oW7`ktk zxpoQ`3vvkh2l+w>jD&|GiRfOwF9q*xj7d3!b1I%7Kqf~XQL;oBU^2V~s>IEU7B8KO z=b*X}Cd_aks>)2MljsxYrS`QD`w|=udWS0hE+cGcvZjC=Q-rguD1`E{mByF^kqJ0< zh>{eIdX%y;-U;E{PWZ`VCm%Yp46P4%EJjjMNxgJ4E969&^GuWB2Y6u;-su?|A-Np= zk9oKTfuk!WX9jl^`IljZP8GXP@V6yjymJS)%!N>`$6mYm#RURIMl^+ zh~rO#v4(*gwU*K8t12a}LQxS`$pkw^J;V$CFD;e3=KRmFO@$jwEFD=_8;Bm1+EZ>{ zFJmtIx(ega*<&epMmi(Yw6ZF;w^v5+dPq!B$$!x$?2EOA7*RHo;kkXpD;;bz-b{1k z#a7(CPO<=C@uIS2(2U!Vgp9jFC~+>}?Iq2(1qF@D*_fx-j=TsgZdQCOFJn4Jdk#0% zUesLgmd`UPKf*_?^_z60bvc)p^=tilUCwXCn@ocUvO4fn=QMwM`ch8r@w)b3>#nsl z#I)C6{D zC9fx=T$GZbk@AqRacBhrI96EYGrTh6xK+pGdSl|?jx#7^G~N`}VxCa*|7OVh( z;${S36lU|3ax(WCD)CBQ(EB?wRUM9mOYU0U_~MJm$}!k#lNV_cj`Vq~2mD_pO(91Y z>UhvZ{1K2*SudG1qz0jsjyTOYa3kr$JXnsfOezbcvv*or<3BFoqUFx$kGZZw(jcPD z5Rb)v$rC@>)p!B)kkr9n;6momzdZ8fN>{I-LOf$0FqHqhJ~EOoDKl<24`{2#&4Bmv zC7XM`q6m}FL3Hijf~YQ?J7ZPikubq1v7@C+U4^)7>_@V91&)Hu4@S9%rvUygh|u}E z90Y}cz7yv#7$UF&3Me`2iah?k153 z5T;$nbwaAuT07RWsU$c!*mvT# z&MWV&jhvB^Ga=qH=eDko=T+RX$x(K9=gyt)NW-Gv5yYFO`YI&dkA<(F!_W0P1`}$* z5zX?qHDz%EA9L8>e#3u(1W|v}x-|9W9|vi_3vcuj@0zl6F|9{pE|Z1#PE5<&F+U2? zTN&@zj)QhB#{a{(nCm}qlyrq8l8XKj>BSHQ5}DK?kq1(QOnGUU6Fe{3`wnvcu;WMy zMEGS?RV+u8nB#f{PZ%hOA;%7YLF!kMVyy{ekHFF5M#UFOKtYUM04;AIgy6F)1|}j* ze$4Bjhl4z*-k}gg5Urkfi;vnwPo!M-Lf=>aK|FKtauK@+ z;kckLX20*+KkHvfpLBliTKDDAug7kWzn0beOnrIp&Kr^=?_2fOM8_fP z`{Bc1Yk%;??K^K9_h^-Nl%ta>igsk*7VHaC734&F-(uf8j+!@)&Gk952U%hCg{@PUyPjyQI6`RfnEv_K zuEgA~t%)?>Y|D>ny)aP2S!!sJcvt1stDjt)mUf5$dfxWd`hl&#%!&EADW|b%uHWKUnrPep$Gu5GS#Q$;AulOwH65PR-7p_goTUtzD2=B6;%1ks4HcIN|d7m+-BlYy(wOr_eth=aIaS(BHqEM(zDz}_2T#HDWE{#Z^FCGHsXq~4rmOHrHNP+& zKqvqkhBe`rx|P=Zf!HYzM2s9*oe+$jxtDAvm=aRR1beEwzD06@e~JtP8CA5>VCpgZ zRz&Tbl^ZkF14t!}ue94|OqyeNzE$>u@~BDGTjV;ZT?f^mJcd3>iVX`A#>om8P)0Q` z%`;vkjo_~=h|ca%|E$%zVKstKu1iT##eI`i{;sXih3Os@mCzDg5T(|TNm(mhlc^AD z9mcIyS_{hFcExQ-+mqDt`yje9Nre!$37&_oX=_(zFUfwYDEpbdK+XKQvvpV=)BKO_ z3hp(|`93O*Q}hN|{2KrH?(OH>5xM?V*S{`n{^s=0YttA1$|?IlR`rN#CRzF3oYsr| z%H~8px%m2TRhid+`!TnLgyxtZZP<=tvwwPq&koI3rY1fu6y1u~Bmstx}xBABp^_XgfuI059WZVHK=> zIq_u-iD@Tt04EaAkzi^`Bs9AZmYC(k!U%!Lh0DB48{ePNDj}`}-xigh!VFfcy+g$t z{M%>|Y6Kb-f=H?Xo=64Sywa0h1MSSVIJ2LLsHKmWhm5o2tX>6W1dtAZZO~P2c6bzZ zr4v1?I#RoV-sU@_$9hiO@n+3!zPDtz_3FK#^RYNti;I^ot!=7Zv<@5mVPkf~6aK$s zWHiMfZtmO`f(g6QvMH}S)wQa*I!eNJTix}s6}-Ro>$EnD1~s2iX*DNv zn`#phw^uG~Iy0y0kArzG_HY=!`rb^A;??2Ealok1Brn}nX6?yi+fLUn^i9p9fG91A zvP#ySrWw@!n_g>lgQUp?RhSrM!rfa0AC!Go+@~Ft(;U?Om) zNHh?b8b;wspoI**obVa~>6n3jA2%EEsL@&AWZOUq<=3*hg}&5NUHPQKXC?0bbC>%< zLM(Gbj?G=xEldqcWZbz!o*Mn)A-0%2UT(7b1?$>7J9`w8o>*eqiU?`Id~R^EY8KV{ z1c~OZ)qyOf6q%;zloLp+NHLNFvXO$$+OTfqhydxOnd2!G?RVR;BV%r<=l4=Mps3jW zuvy5Rcs^;V)fa>7caQcw%neNj*+veWR(+)7AG!VrS^|(%G}e;CKPP)99iMzh_lIi9V(=V)dmwp1TQh328YV}zZU1RqUatRN z+uL7R-0(!*1(JtG)Lp-R&bv4LC~$W0&>;f0)Ol)-MB0;CzWSE7wl-R?H|V%R?Q%AJ z48I4cn1Z{ZZij@0ENDzSrL)j#mURm$0?9Lk4G}7rf9YE^6VxGz_3HNT?2d z!NM4881x7Q|7rJ?-L`V6e&xFd!z1f6&Go=sog?iG=p{spMqDe@pbEg@76bzbD^y3f%Nr^QlebuScK1`;Wq22uN(a0P_%{n4KO*0gPT;%>Bquz8Ut zeW8`9H@vVG`~ndt@0NKr00%t@eHuE5eIH@3oX2}6ThnNPI@z=`8KV=czwrg>$rZmZ zE6rY9Z^`1U|LuW^nN8<1TH2ec>Y6H>4zF9{_fl1HeY^U4dF$Hv_k6XqKCV)C*xR9t z)9T(4dlRK=Xr#OKH#Zuq8o5k6iJIHmhU?_v1`^0blfJ|cXxuaU4M*tFEuuGiUKNeF zUNAPI7WpSh-Qk*Rw!%)6(17Vx*cnk#Z7qu-IW#tmb91x4)LZvNbFn!0yplOl$^E+# zwKIkT6--oPCb3>MRAA8~DCuVxfqKRFsB_ZLiZvO_rT~$0^bsU%uX+>c@XZixzIgVE&(LiXVS+J;{>Vh~WM9*-i<1EHLY2>h zgExIB&ahQ+O?|_%Zs-G_;uA*`?%aR;qS7-fs3zsZOgfUo3$-;0NU|6wi8~n8%1ISR zR?tOo)0AJEomC32;!Y|^pP7O{C{Yz^iL_4Rgt&t7R!{(#rJ-b!4?}SEhca3MHN0=~ z64djGUm%dQdSE%QQkX(2+{FZ;YQ2MOS$Bbm69JZ-6y%Nj=S-LEWUOm?oM$5ME}P)_ znVUy6vE0yyz605Zo%;bGl;{PS63h0x?kFgGuXTmH1dP~~xZQWIF=vwwX@6dZd{qH~ z@8w?6*!lF_E5mcIxOc_04YI=W9tHz%ue<(fD@kZW$)~Joq4n3l@^YJ6Tdq{)T>Irr zOjT2DuJ6p1hQAWt1$TzA{~T;gfZbPfo3Oh6+^`Ox8SpoWj2D%tr^8kV zo6C1l^JDgmK~VgOb{}l8;or7>gQ7&UI5NkY1q)Jnfii5nuv5JRx=gXh@&Vd>u|blf zVU|AFrGU9~_qUXVjFIizhs1+=N(zu37_c(Es^sMAQa3$;Q1$~IAecf=K-1EWCZOzX zG|kP_>hR`E2dN6ba9V_-!0?FETQ4;>mRD8Hq2GbVt_Y@h$q7bf0?$ZvPzk|^!Q*s% z4Uf9*m=@#3JMQS%F~|s*z!Jo%-+PtUk zYFTkCohl|fGT2AQs${zyPGlYZ*NME`_)ghh#fs9@W3O9JRJC@gyFR-s+z%uCdd!WF zVqTa%(!_a06vs7I-e~nNZal1_JOv9dYlOX=dEY9AtrS4e0~4uqe9%UYn@27En52Lv z8&hZq%@!Q^z)Q9%VCohio;t_S*-R?f1(vO4&vhjbAamAUt)2NApz6?}Lpod=%TyfBSYbXml}H@;qoSs zHPgJ)1Ox+ss;+t`Uq%T5I3XEmqgGE6H@(bHkbgWc(ayjRrkS420H)rK9XNNc4*&>V zV8;L^weEyunF9a6^h^%m^*2+`*43p1%s7uR6)C`CD}EU2N*z@VOVWa7Pr*4kc$0Mr(H!kiuZTV=~E*3^Nt|h{SlY**!p0#fZg*# zuaO+JiN5(70dpIs;b3NISTFNJ327nZaFG^6Z@hKbLSw`7r2yHf*d=z)tNZYDF*Z{= zXTl>w1g&DA^`64Ryn?bgs@r0e91f@=%%-~BDvj5Tar%FzP`x!>N3t{9=m`3Nsx+Fv z8Qy@~U96bqwvmfn3fn7yfA+~m*M5mvOq?gTO-&h^y`M$3eqENM{%l%A7Ha|2%Ip=l z*gDml%nd7t7xSv!P@GRjlN`QMSeyC?YyU}z{@dmY1cU^^h75rmBTz0lse0dnCFU&V z&tP;i?krtm;DN>vGlQ=f=`S-#E{bcU17DQy_(5X^1GZa9+Je3_g2oL|GsMX0w-pX0Wg_^;pD)lgMRLSaSnPIsi6<-td}@$5I9VP&h`$7o%LW*YV(I-ffI z>!Mf_>mMZ!y>AfmWZZx(HJXm)C^RP!-%>jt#Xpq8u9lTF@WcNRU;+J{iVKD%7?y%4 zg+vtp!rd_rteS@6g<}vVh9IIW`7Ge~42r-?aoecz{rln!AoO&_&y$agO{`=C(D7oF=x@=&p4#gCPBXkIM0Osl0lsXXVv3H#l$o zp#+fh16WNjoNq|nK5~(_wPon+j=`g)0@ywout*NI?!4!}z8KZ14`(^I(zC>}o~dQh z6l33TF2%^ebU+^C<=8X^o-qyi0e#g*6i;7LD=(~qxyab$7cj#DEh#nij>ibM@=7w3 zbBwu{sE4kc`c={0(pn4O8H%1FPQq4c4U|r(f`6>u4>#kJ8b9_9U!^FFI3=ni>_-d0 zd(%<;Chnms%H5l}hgcFq!24t<=YjhC#WZo$L)~mJ`~TuOjKN$*-z`IM6EzK(p=kR` z^T?yz5FT~>QTNc?{c3|qh4HCH|G3M<0(RO%DCvtoAa7+JVN z2pp#ugpY!Pi*^LrR0K`_Lz(m#Hx0u*H0rMhw)ah4U-AA-y*8X@V zu(6rrX$QDh-7LS6<;0WgR`3~WUR_3Cq#?%iXu%YD#Y>Yjr+}FnYVu`bZVJK}o5J^| z-Dj+aZpYqq8s(Umn*E+gH&GyAwz|@DF)06OfZu&$H^r=}Ht!*JN9?V<MrOFFrw8OdaFnRDM<-SM(bv16WWRk9AK~1GQogKbLORmX zB5=k)`oeyu#tO;W-4wEAE3&Lb`>OfsJK~u zN}LZ;At%Da3SfI4(y!1s-w1=u$h-ZDO(;U13<{QM2~+6Vj0<`}-g{vZ&` zI+W%m%}60|((v-X*Ey8tgQ0c*wXWx0#v9Ozo;OstVw{KlYtDb#k`JS~>AH~^R*=SB^ zn`k|tTi{-K01oRMEIJTg#tlqI&^O}j#j;k}?-6|-VNIA-%F?p~yRg2AWTdl|CS{82 z@SAdHc7d+8hzuDm{UTklBs-qxJ4jQcz!p~CM&3bH6z6W+ii>=&+AEbcpjc>+f^;lR zff9g;74(>>Kgc4MTg@qDro! z{hyCuOA8QjmS@)N%w9@4x%q~~K>=1=eJ@w1Tz2-Y%R@Ua@2TZHx1#3~YU9dqt?>7? z`)5<0WnUXO?WJ+G&D-13nFK~GNogjD4Ws!g^2h}pTq>mDs1CF)v14``86?{(9~%0B z7z)%td5NYs$gI={S$3SP;V2fzMyb)`JTt190-+w&3FJ$mh2o$8`Jnj&5!KJ1FXed} zBFzykyv2$?z1um(l2F*q5;`;(MMh(qtUR`z%ktc#M`cY6naKg#;hvj&!qX`JvXweJ z*!n)@jl@uLNp4y0Zv3p$yy@1BsBl~#m1!e;l7eT12g(93W(5&<8poI$&ypws0FZKF zqDkJc?jeS(l`etxLduq?E5nCWo!N{&{OeD}vodj{ETUyceH6Q8e4OSY%TH&KXJdKPvw1*^*% zZKOd~fum$UIQU>ePejua_Bh*5H9+{fZtT>AcyGeKaI%aJPNs=%#ZKCB;0ozn>;v-M z&3{=3SxJChAw za`4=W?_T->6EyafDJr{3o!`jRs-f%{rF)78-;6#dAY6c}rMh^ERi=TMA6F>>lQxV) zN-5dghs17vUIW$m1=88FQyQ=6&B>R6KgyF2hjP306-bbIH<*n+D7$o*xy8fGdnh2g zkaPx8F;Ls&eaNGI2p{kWJFR*bl4`BIy$^{DwPR0}zNq%t{e0G-@?F9ELL zBdg68SFUj&>Ync?IUx1@!Ghe#2qEc!mSOhF!I%^}>ue0|`01g@b?#q5B=(n}ZH>-E zKTf;xaXytnt%nwVp4+DCh!)aoquT2hcB3rW%`t*!QPtGpl}+!NZYE@nFKTCW$*_p? zZw#>o(~>!kEiz`fM9o2%}laC!H^T}5)>=6_yT)Q!bVRdu&7 z#Pep}2Us*DCX&=* z!vo^M(}6ammCzSs?1NHWq{V0hZFfiHlh;m_kOTq9@hwaMkYAG1rv7YoJzNBu2>#!qtd~9Vzr2jt9 zCiK|q?B)ykt<9gsTvDo&(nHSYCi|zIPs~wu+Li6?zwK)MvR~UBnDIsUbOY6GceUmK zstsA|H|cHP)K%(KHkYKq_Id@7PjO~@xj3!W-DB%QIO&ee@J)^q6&F2;=)7u;sKlzE zZ^^WWEyQFX1csWTQ8d`l-&qp;n%O`i7&Cu!PZYGFx1D!UQ^xaONM981Pyogs?!thJ%{MwbjL7F-=Z*pPiH04%t)!Qt2QPmmgpdKWh21uZ+ieoI zc0x21$^d+c+V^Xt^Oa@2hkgiJ@N{Lu`^=@-PP%$^*>*!jy-)bzp48KMO zNTWB&5;>#C;U%o21w_0zQp>7!MTCLTQgLmNlGO!F;P!^i!-GViED0gO@4Ja)rFu&u zok?{29OAl{+q9-1>y=ER-mWAb9`tH{Tx$QE+OA~x5}3;lH1ea#pnSD|ySfMhHq^!~ zf>4^WN@B%r{s!oy{{)eRRsJjL_YjrSer0>H?{MR%YZ@2NQOC!s)=mBp(gV^SU3hV8 z`w?(-`{eBv*)9J|BL#E;q}vaBiU!e3X7a5lF3K0LT>Ofi6%0`zn;b~Pv;Yt2t>Dd= znb|AkaR`c(jt6B45NWwgS@+hiEGb&HRF>skehHrqTH>5Sbf!acQc_S+AcilFP>`ym z`atNn*M6#$xTKOgrSuyeOMO$9FAc?MIr^l>7EV$^`7Q}kxX#IjSm*M^Sni*<##NUD z%cU}9J*l>!`OhFFp;iS;6v1G`34J}NHw2tYQ7||GF2+905*XWrK&0*in^Wf}=BGYX z`^5z?{}yckW-xriY0<((H=mlk)YW|b2ktkKc4xT#tHPq@0nj^LUn)j=4H-k9Uofrw z5Y38-)lvqSMX0s9nBIBM>Ku;6g~1X)fb?;DX$X%>I_dBxct`#M78#D|JP222)#*Jb zLRx86+r2U&sxrdFxg4q(g3jPf8H|k{M})0!y!Tz-_=pWr0uIFrWLvmA=)Q4+sDu1Q z{0z7UV(h~TqO~a@p{{!eRq_Ddu^;rbCDlIVx!ndOqN0<;w1`l4rR8r(r)XI;6ji>{ zer>EKXN&#tI=csF8(QHT5Y&=CCO+%Fh%^$4XQj5j;H=n^mpv+}wiKm#X~Vc+TePER z%Rjp3{GEXA$(S3INB*K?-~&J9UZMTD{~~!+-A8f^($vg6z85N+&K>j~ZagU#v1@z# z?K&Yo1DzP@ztBB5^9FfYxp8eAmHhHilZIIyIdV&Nao$0~Fc2+C2a0GMTG{=R9rlNt zEK@;;Za56TTSG-nLZ^8S-0PX|EyQb(@5M@c!0q|z>>`FmP=`mY@K=+Jp%l3oBe+pn z!kt&jEu(KlwcMdeq$VUkTRr#jlV`seRoFb>%Tb*y_fYXYd)^O8(Rbdnam7+!0(KXw z7i8NP{)gCz&)*0|3d@3nWi9Q0`r+k6x4$~Y8rBeJ_dYqd_)$hGc&V!Ykq#sh=!z2ojtab$96}OiAZuIuFsKpg##!XCWGn} z4=ah%gfz~z0Y!BEtLIOx#IJmp=MUwPgXcd(?N408%YEr$l@w95gGoW3j}WkvD5OQg z7SOYas0EP@Pl6Pvp|@jPas{W{4_f-Xa*!HpA}~TQao<<-ZaLO<9RUMlibRjTQ3OMB zuIMeP{CXM{*TaQ|9+9(!$zli3spRwdR6CUtN-R{&p&IDcE@A-`u9uIbJrrwFpdc$Q zHuRN)=WgvYsSkjG)q}U-XUKx=@8V~nU=|R=Cx!bpjTGtJ|Qg`ZPZtkIT?LQr0AYhFetzJ3Y__++i=ObFr z)wT8Ue_z)cRCVJ})s4#(*!Mmk^X}%{b~;^Nq~qmvRUPMtT1boPL(xb9hCw$i^zcOr zO3-K%g9*xtGdnh4&xw-*xh7VIOp3#RED#QBB2GyKFlDR+W)tL5<1rBiXGMPu!sTJW zC%ts{j49kfjI1=f0NI~DdrVfiC4EfDG3$&yLQh^1yh|qoGx~sNv;R02zP zgcpA;7(O~=bf-QNY8=Re8=@20M1IV2vKU`DlLEHL^snPRAF{l}6{f%e-zlGe=yv2D z2K0Y~#jT54e}XLBWZ-ykC;*F0aFSyqR*-lD?RC*$OOx>#5t8Nswl6QN$y|$OQ;M-m zJ#kYIYFU600zT-D@s@yj;%BIr?vHQ6I1-ZN`N4NWYP|?^7-GL88U%%)7ZHXTVoiIi z3@1NK-ePLS8iH4oM|mbsG(o!5)|6z_;CeJj?4SyKoQDo?ue?+K@X)_^Ff|GQ;`-2)QjemdxZ@-8OO|yTfQ5Lc=Ok}J5s|lGZ3%?% z-8i^Lmyify0RNLV@>*k^2pooQA=3%0urJ~2c%Q@McNJ#IG!D~ zEDNQ{c>;b2ZJK>kjCKky#qG z1gt2&uPgrRuCx!N%ls$-@`t}#jZ&!NxKNyPX zrVxT@@AD(KrS!DMd3G#-FxwgVWeMeBirr$}3|p_J{>ziZ7!=KNo4)%VYldxv)45e^ z&|5RiF}IBN=JUzb11ts0T>PzE#k=lgxk@FPKQzZ`9X@>cS&nbN|9%C;Nf%q~a6=ny zJ?Q}w5n=u0AVb|}F&S`F)-#ZTS{Pu}I-?Cug!ev946u+5NP7QF?Otwetj4tQf zA4}0halJz3l6qa_-qB?}F_`_|+|JS3Cprtey|{TbQ|@^TtMzluo7bH#N?)OS%38K>+^MJVV5_kEXcZ3!6uZip&N{KEvPGRpT6C-@r;rPhm=u!W{OgcXt&KhMF z16o&LV>rE(DeU7jix%oD(Fz2>*|lJ(CNJscIu-E5wjjjl8&zZyeUBmeT-5B^)*Jr_S)=U>p+wo1ie7nFY__E6UL)7i`aE;}po-mmS;eqPS! zCZDUy%`N-5Hs{Kz;r<_|{(QP$OiuQd`r{@iqqbMUp5=4PQx|rB?7tH!D`_D|Iat0Js-H=a=7Ta!*k*KA)`}Qp~t*g#0{ta z;QNn+ZzP@Mkh@3A^~>QUkEN8anXuf&n9BJJ7apOo`C+U5)2XWpACU}( zKFA-Dj7}7r97FMvVv}M=?#g~c!`p^>608L9(V_%?zl^)K?dXEEfIUFcjrFtcB^m! zM<*WB6Yen<<(I=el(VZR!9Ihig5|mC#jJlCL&t^|e5Bsxh;2`+%!Ah3afHo3T%7q# zK$YhP&_!{0R*u(cz31@o)2QNVhcQU29qh0BckBd4+u|AVoDQs4A08eDym)$!h#cr$ zeh<4QKxOCMA{BG2>T-MU%xG@5%I7M5{5#m^q8#XkkaY9S(JRoCe(R4TJAhJu^O3K;Tq*ZpaZ5C$!YK3 zGjxEfLE;v~=Qs1e6V8fIq(y!z&v%-pqNw9zs`@Df^ zVV=3Rtp3)Bn^<<`^Cw#WUAEZ&<9F#*SNgr$mheL-W#p)-Z|=9IwZXs3YHu%F{J^EU zs+#V(&2c$DAIO;-gNPmPm0sb!cIwor7%c0LO$N%Ey)Rs=wddtQ5TIL|WIcPo62uqB zj$PvLtTS=HBM8jLa#OH6>?WD7G1TaF3OFb23*3gK=?+7xdDDp)9?@L+4{y zNUbu#`=>lldqT2C#de!Jc*C>@$AnqgWqtY`~^;k&*9+;V~T|UxiVUf6RS}ZbjB?U7+HgR&US+V?`<6vzWt4WD?KVc zYpgYb{2AaP8Kk8t*%JPBEHbH_GiA1xy$Q1jEbUuUk%B@@)1-${CAK~?@~fSr3x`!N z)SDz=yw&Pj$kG`8g*ZH7<*5Y~XaSsmdp4=0YjplKY z`$vjKY-g3bK&E(!_{-^9G$xNy}YQZX;CYOR@ZLLxy~pP zx4%Ps+cZG#UobW=y}06BMaC*7>YJL*GhUoIbFk9~!DZe9z%eS$mDDyE(V zf}S?wrn??nJ&He<&1^g+JGLm@*vbI3?Q8m;N)+3CsIqV)u)BBE;`V;JL2V3X}j-GQ}yef8<*2SDb5)ZkbLEHZGETISjQY z>!3T}bD;nLuqHI*Z$mka)M9UsyIVTF1~oQb~2NN>QEtX>BoO zrjTljR&+=y{qO!guTML_dykR3`~G}B@AvzS{dgGo3;`7g%yPNcuJ^S^~bj|m29_*_q zP*G(_3B_EKqs&mMtCB$2EC}=r0o1?Nku}97_CD|J<=BGz+)4c)En}2%cPIX^l zGr$nn+@)X_vgH^&animLf56XSoLHYRSV})Zbd*;V&BT(pp%ZfYg($0Y(QMUzjF{ZJ z2##wYOt}KTOR4Jyig9S>MYY`dE$zOR_e_CISqkmKT}D67W^^h_Y<&@_@n2XZMBi47 z>ub@7^en|}^N?5rp^Ciw-720E>D+caazW8-^@5~r8677hOBfK6eDO=#e_EaJf#0;vS_KC)B!3TsTDKiH=Llqzm zVt|x65*CoFBx_*77>lv0Lc7{y?j%bRqb!LN$dqpqzcB&GxqtS9l(3`1NXVK>Xjz)J zjlK5$CDsWYpM@=>LWlMw44*Mx*6Ak|qn;#@7-#St;ISC!Ikq>GV;3H#7BdfrY&< z0`0?lIw~gEhyRzRcFyzOZ+e{x$1%i~GwAuafBtGLMuND&=nTg1HDL5~Ek%+@wZ&fq zt|Nu~6v@$~6u}qH)?UUlPI=vL1`r0LLZ)1Cq{MvTjuMxn3>7CpD<+*5!xMktvk?a% zT6IGb=Xzl}gnHp|^U`uPsj-7UMFQlug#jF66K^QqH7HdvS)YkBrWTIO>L9W}Lhe~$ zXpI_@FRs%E*pfRt=?$&y{B>xq(zb$jtv?PKDDo{QZBk(CsjvI}w95Lw`FFQJ-wned zVw22yzWzlKFv@WoL+a+feI?tvv>#tm3@3bEa3w>r*7?>D+P4vc=nGp-K(8af zfit6W#Kl+@qa{y|^j4Xk3twWMJej6hGEPPV1eaJ5*K1#o=@Ye-H{BHumwK6YEMl(3 zRS-I5tinS?{FXWurMbr*SwdDb*8!6NI85~Eml?2pnEJG=jKnv>Zn3G#gkzF3QZFy8 zYh>7R9r+?pW)MwR$j?fwo$#){imZI~N7pgKP0Tj{DGT5g_!^hDHch~6jcNkLl541u z(--&Hz$JEWXEUW0eC<0qiL%6}`=7$m{|M~+Bd~*3pgY1JScmWFZf|O@>8q^itpx)E zaSZiw=d;3lAKgk0L_DX^=5{2zTGg|ECxooZP z(F$#6z{1cKWP*i=3Pz)|&PBLrXd>PuLy)M0qC<3}7r9%Ul(=o#{Lc3TCKdHw5}P)3 z(-f5*^yq;frbo;){>y^*v3%~XV`Wnk=Wxs}3%g0c{405PwhtOG z>sYBOZg_vd13y9pmvsjP_rWeLL?yV|_CR4NIKjg5`l(XlEOxH{_pX3R^@)Med%j&Y zq;7LR;GVAi3%85m+2CF>wJ0M`I3G`%EqvaS*_0=Oc+D!WU4o+bYt8HYRt$#(6>t)A z{)Auzxx7EZkyP6VmY&&kO1u>ekkCPu$(qk<9rx{yoT>FWoFI*vP3p#o#}xygxf&VC z@z6hCAmMS5;e91wyTOf* z7wV#*RI=aI%>O%{5e+NQOJ=1DzG0swpcxj5!~PDHLdd!Nk6+CpDB3x;84t++a;!ut zEnkE{X`mikL^ zF4JRaIEH!erw^X);OyXRWaR7+oG|3@2oqYti|EeB25^_t#0q* z?hx;Eor+8RI?Esvj8*^{(R-iBVyf&cJq!BpyTh( zJXrxnV&En6vix!KfI3^`LG(}cfCwsh`Mlv~Wmr3v8y5}&`i1_fEpxVR<q-Cwv41CoGKN)2I^SYgE;ic0~EPyrje z4;mz4+;Ari1YwzdRAaR}A!k7OMYSkN5LA&1!Us-Hm1s{weC>Nm^p{{7^aqNFrw`6b zUu~iovqYDQj+e|<8Pt*ZNgx4A#tZxY-m3{@#D=P$H^V`r^wyRCyH(%0{oIwjYjxp4vwnE_^MywTPhY6nZTX)sCy$6bFxfl%{GA&!M}BJZ z)Y)opQ|#Pa?TFA166d!rxOo zFQnYZAB0F;;Mm-fA{XzX#EU6RRLa%&B1&8|b;U0?Kk~d!x=Zs9^CryQ_8$QE))mg7 z{+v@2-t8yJ8l7gl=v&ZT(}X|#MrQBxE6?uLf7{es-PEjzww&jK*|rz9TiHLi^37X4 zQ(s?IRn;xKdUZEXFOqCM?dqAMzl==!K$PvSq+2LSFnGsK>QS5?119CvCkf^k*kMyu zz|S^H;6ctl!e${^oQV|AsiOZZryq-Vq9lvW|MxIRnsuo*hxW)xDLIXw-M?FtMg(-jA~UhrI<_OByj+y*GwCkY=& zuGpam@wK`0lZ?M)Th6u9#w~WL-7G7P)lpqyy6jAnpMP@a&I5Z7@6`VK%|FLs&DH9I zMlbKxaAX6Dg*$WorJ!*JMpkoG=9X;kVq@5^q}tdxQ|qZbiyRq>F#0}m^$|kB^@+xh z<27}ga^o+i@{}?*o|~I#r_1p=1yh05b*~&t9*8~TPs$wLbd9~aLUQZgf9FujWz4vf zWH$7d1fwK~C=DJ%|2e_spn2U_^3ZQE^kPn=FbyYDui`F(iWQhI5=vEZoXl!j$>id= z$vv6N+_NnoG2d4Yh;zw$D~r0MXi7rM;uNR_Z=g=I7+0FIDrT>Ka3qR|rK}Z8S-tJS zP{i{DhChb5%`bYLzOUDwvwBbUeScUNHZO`9Qam>Bem(7G)%2XJ2ze4$5m8}hEdl13 z!k%>n&rdb={05_qni+Gg+v<7dq0{>z8ehRdm|HnDj0m6=a(0y0iIRikiod~mrUuA; zr1B(_iErqPEXwPmoIzCtlvo<4!&EFidtGusS575~s>!x2zr>YXEp{tq1&}QVB9Fh1 z_^p)iS;|JE65^jG2U~`-Z$I!Mz!Ko*Dpu~$9z|DH8bxkc??|)^bhPzYsg6DBr-+y& zJD@8GW$20yshmH#ISY!r)TH}goiCi}e zX$JET?l$`Idc^Vfi39A36p2}>_K^}Bio&i$FYj`G6xn2uD4Z;a6m8+zXHa*hBx>a0 zq}ofQU{F0lfX|7!bn86BabM2uO~Kr-dL;*0-Gczl(z;}dRdk-w+6|3%ajqK`4b0xr zhTmg_qRpn5Zzx&9VVEncXI^b9_hz?au;gk!Ac)wlxZZ_&siLFnF82=9FZiaS;6>cF zC(nQX)-P_FZ)b^DM~PR#FBtQxCJ=RO^_8tn(;j=p&duH4ee-m&#DO@U_W{6i2iCuS zpUb&g*3OH6yRKB+0Fe=)n~LTxml8M|Lny^p}RtVUcSoL@y)Kn?ZeB9DvOW5@(cr;^&cYT12|Ddro9khzhn$O8#% zVV$oJj(A|Rb5HGYk?D|Su_GBDnadRa`Vup6DUwQ`0=SHu#b+7FVxjc?0a?TKuN*KE zAB1I>@+m9j=%rB|l1aXfN!{^*a$;L?fJ;_J>~1+ic6{mz);&aK5WuF?k3g4=Qr-fZ zF0psg`8V)zHHGRv^5CgP9F98T z0)tq;jeXXTXTJ>~N77D8*KTd9Db)K?kwN#Ivt~c*H|4HAl)2-Hee=EgVytb6vfnYJ z@Y#@_-_81dZ;$P|b*-)?&W!lBH8JY%rVuOoZBy?K$psR8Gt0vGEm#`nM7zd*7sEFXX>(2p`cen}po^9UA(r2+P&+@NA|JKsFELGaLpH5NQo zt9=mgOYR|KqD#+#-H&5r?!qz0%ZIYA~$2@3c}&zwP($w#Fg8H|F_t-|wvR?OPZA zC?@<s|lQb^tqJuF8@4Lk}FBI^S*9{yM&RcyUnt z+{u#stza~HP>DShHH&;=W1NxtA6El}`$WqzZc5=0NCP+fhXYT<=LZ)LFxujp(6Soc zoUS&<+W{Vhn(@fXK0axgU$qohz^vGOp1x1;+3mBM6jnL`i$ zjvC2n`1IPRhoS^-lwr8OWtDPreDSW5ED>)InBp~Xa>o$$sJhgS%c5&)pR=F`_#+&9 z0In2^3XI!OPYRcYAVgBFCt99F5DVxZ1=ijWBTp0% zi*c0V1H8rW{Q#aBQo<1z2LiQ1>*G{+>s-g152p@Tn|ipCoJOO%{SQC4e%c&iITv1N zIW91Ni~fYw^Wy(_7524d($}YBv~TUHtpz{&#aWIP0j5x+XE;6MV{1B3)r7PN39xsq zCim8JM`!bcMS*j}d;ay{@`Cqi69(UT`}0eYb5;bi$sy2*JwRiy6@sTK>={EM*dT8) zNX#2v4CaZ?@f+rwk^tf@$d~y&b!}IOLwEf5Rd@r-6Ixc1wL03wFEOCIYmf7Nj*2laPJ0hkAqgXH({WhrNpVv0)&aX5js*dtuT zd~lHm+o_4WU7IJ7SIH5SL^%;)^&Q+Bt>2*JsZstzrc*K3ij$FxqboIEki-gcL>vUf zoC1!$<&6Ic!bqVc82F_55h=OV@Diz8vM|9DtaUS z;$5coZ5Y7@{^KDPpXwsCR)Ca+%jKTJk7P8->{(q*4O`yt3UT}UJaNz0(1#6b{W$I6 zqprEK0yk6kj0x#}k|PE6s9v_!Zd+q|PM!lVbLVwl~RU0CV@kp3ESieva)bO(gnh^+9}zkvgQDMSCFObY0War+rlfCw;?uC0Z#}={qH7%cnamx7aVvn==cA zKUf!b{yeO(`SZY@cB(#J)BjF_n7)f+=lU4j>+p6708usJ*ZDu;b^&^Kw4(-p&e1YI zP=w=YD5@%laFiGf@Q8o`aUFgOa?`DuYDP+k^SJCq!$QGD2sWfe_$c}a#*0;FB{vW^ zI4kwMxj-H;4>GU)n?t`H z!PmehF;xH>HC03xB9k1}y6sBP*bX-tJl@6b47EpkhbTR;c+T>n^_e-}$r@ zpoR)ivj_3jHno4 z@Iz!uP__j>6n{v6Oj#05-6c*bFxe?gH{6TZ2Z3Sx!ARb^)Q-iT)7zh}A(LW&?1dvC zHBd*r*|Xb9x5(c5@Tc`0gy}yn=F{Di3upW#u;&LQ%enyBdbUCLVnY=F>t5Vso2c`* zpRH?p?0?M^$s$sI;DO;`B`%Oi%2XIBq|EP1@eu(rbRq-wA#Nr*uP7I_iqK-U0>kh32gA0xI0qfok7+>iTA<|dFq ziiHU-sMRIn<&-G8YOciI{zQ_Mh*lC`SI|*#Y-MD!vAKh?Wc_>#n;Ww1ZJwS2r1E=F zTY0hK1Vuhv|F7YP#m$hS8~i%_T|_N|&=t3Z@uK?caIV+y>xXSFlca$=3#}5LTZCGC zXgYIX8vgDeNn~6Ek&+rmR!YDPj22wRHM)a9a&zd3FcuL(88bM~gWW~U6uPFMh#Wo< z?+{uC3y~3mH#gLF%fuCiq=_LBuUck?xZ$hsMldxzYf|l3QXo)rmcTijK`;(i9J%r6 z{92rBkz^3=5>}<|l6&Vz?m#{?3a%re9Ra!7JoH^{-&EWJ0$f~FiRj!U_rk8JP;ckk z^-GLh7`9gbUzr;{JH zP_wr0sR7~erw^=a!k*Lfu+G=4xJnL<@0{Xd_p)QiLbk=$Q6!@-SBh(jh{)moJ4^#J zAVf}?ivFKu?qnWqJaE97S?DuNl?kappNWY`4?GE!icg>jUL}XEE~4F>Vv)IK{+J9g z<8{Z`4sr@n8yzz{E#|@gjZ8pYtQs;P$3(GZZ(ChIP;!@;ufk5`%%Rn2?#K$tQwo-{ z!vEh+R~no02K*c8(U0E#$(@UxA!h5l=4NhKc>2iPShwB(t$lOZ-v{l|d=;@H&hqfq zPP^W5J5FdczQ^94KK;X2ul)CyrR7_`nsIl;CwI0j_{MGH$ahcwWy$F=^V)Nt-|D#7 zm$%uz^s^7wP5TCPRLd?Z`;6@Pct@1js-jP%jAOSQS<5EDajHd%sXYbH_yq4bIK0hf z5vFk#C6Bck85uT`uuAPkAf{xIY&hART6S{T6y+^;56K*hHe!}YujHrGvYm?vxqMOg z!I~N6wUdghf=IsQdT$Y{+$fuxZZW_5+T0Cn~B}Q^R zFFlS>n?TdqO=ntalmtK9WWJjIL3+_1gJIi{_>k6yo5Px_uC(v}kG{3HNvFReg<&?k za~_~`q)+Dud9~4&U*nIvH4DO45MU~NcDeTP&$rYR@k`mZP_nO< zDqDU>NIP>!{V|R5k{qmsMK>j>USv8uax+7d!0i|&5Z;nqu53uA*D@5z*pda1WxyV5 z`TJry4?G#6(5$mCLvvz}X1zR$J)N>>8iG5GxtH)6* z4zD}eCKpqWjZ5X-$Xk#0QowUr@<069)~UFTGQ%RGCrT#scuH0uS!*Iz){?M+?8;uceDe&!lTVSM z0EEmvXc-bO4H@Kbl4`%!2Dk?uH3C7E&oBhLv`8FG8Xzo}V=ZkJP0i)rQFteD+x2Y~ zzIyxHIu3>$)ps=9Jl+1ZI{#WuVXwbWXRRVijvz4>D43-F#6nrG0Ba4%z6qr_YQ3kf4 zPD&G{BmaM9Y9NErQY{a(AlpVJ!*9k9kAf3xY1r-I=1TXaby|xCP0}{D{;A(D>Iz+} z)Q+QbyL(UuNF+I3f2QQnhf2~J4~hL_4;xy*zizO*hvRxekcOlyw%NCAk%UOu3g;ud zD@rapEm_!0>!^6}a^f-vdH6}n`4F|*Zn5M=9YLJr$_S1vzgQqqZ~_H#T1Y_@N*RfR z0-UVb=>VqfD_Ie_#FZmP9KU?C#Hx9<1*s<@nuz>q=A~Y)#Q_$oN4_iMK(z%$K@o>T zT)~+<7y%l8#7W*+SY!*d(ssht)(aK>RA!y;?JL>vKNUQro{1&HmMtz?$}gxFzLSFCcWs?J6k&*dzn3ZQR%Dr8bf}ov12b!rYLsSNr-}0R;;YR@`gT|eMVz>lQxV7+*(je=MbTi(duOSB7{KeH%3KgDHEWtlA8Tp2=}kP^i%>}W)55}ng1+)w$#gX) zpC&ml{P_tbZju>xVNEjA^h6yX7$!1=HChI0i|x99w)U^; zMea~Y3B^y~L zaaP#NSSEH%(rJXEU<=5(1yRKKuFzATnUxXV z%9&qDC-CjP?TZLisxrf-hCLWtqa5>M7EOgDJ^YC5w*Jfe4oUGb_SE$KmBbEVEzq>fohSOh-lQF zEJ;pcSr|%c$8@dTFlBa6VDco>l~hk)C#-Io@9*_=9nID0KW|#D|D*XqvA&T*-_^tV zI->L)T}r=FT+cxeChw~)NzZP))AUJGb>nAd`i@0WF3FAhMZIxN_j9|=bWiK6Lz`}% z-cJH{Tw2;R00p_wHh(0FN&0BL@H*wV;v}rQOIrWWr^TdTQ9Nvu1z6CbG-08N8OOV4 zSN2THMTNq<1qq%lL442>=|bVwEaZLR;P0_|a)3lk)T7|q;5ly1Z@E!Km6!m>d1^rr z#dU*MiD%+QUME_Gcsyd-6cWbGZeVD|CxZis^k6;g{%R@7$jFu*5t{h`1Yby8_74kr zalV6V%dHTOjx&;4D6qmyTq;*gR3;|g zViREu>%_iCj&|!@AxDZF9R5R(Pb0$v^#f~jJI+g{qgbcSszp?P(-JNhr{)9gRW#si zONd)8?pvBo@}{G>N0ORdN7$7t=r}ob_S-jm+I_Y^sb)*`sg&%jchR@H#Pwa$y=a`f zqwAPg!MKcVbxR&J`s$}}$h)HBUPbjE6o#o9hC|%fxT-oAicR$!VsmLHLf1MH#QFcbmi2P^OH+a zo?L3sD^^Pyz_Dbk7}w?-l5QqTC`4m9cB33{!1!Q-c7IWijas>6Ve?2h)v-9kx=AIA zjF@i8Y-_&Q&*~N82E!Jyhf4o83b3U)uJ4xq=kNCW)#eu77{^XZg zHD8j5Ngzf;@Ms{t3jU^yBdHd~7vt#`l!5;9B69%0Tm>$Z`Hu%HHbxFxNv559N970?@!47=MZlf2(@KW@HJGTP4;FXgf$F21O@ThWo- zyCdoMtr8a7Pr`&l@D1@%z#4*pqS>J0Qm6U3@_5|M!9j_<)EyT(B5#03;>h#J^=kEF zIxve$)JaHF3=A$~`6`-cErKt5#C~c-j!C*OB^iqP26h27Dm@5GRW^rEh|7-LQV@uG|y}14IJkV`Ew(4Mo&vrI!wh{B0 z5Vj3#qVFUFUH^daP5YB6E)xp7tGDZubX_ib1F|3se~Y$^n#-dWvVP+U>iw}sO` zef#7vMPXllO?Q4f2dZ^>0w^iIR|*OW+9?B(k!k-h7{4W9(+Rfw5qr2Uo+#KXu7Kpg zum(IB^IOT$79~t+dk#^6m>_kmoZ~wdcYq=!c$ABq z-;=}+g)WOPDjQ>-8;y*VP5?yTW~oUcJ1kr>aZ>;cnPtImX}{h{*czmo(04< znQO&sM-{YMpJhaVc%irr10Rl1qQ&BuOZ^9Xg?#cwSv+O+EC=xcJ}c6H|H{@VBHTy! z4>NmF?rq=h3k!K}P*G8#@7gnLg1)e#Vyj&b1%RjY^m5)FFMagYmb###X})vpyKY8x zB>8qt4sYM8i;d74eXDFK zWFwbA(^&=^_uT0ckHipT$dvrQ=^aB|4B-HuwPeHS-va0`EH_!(z<@S23vp*s!;Wg)5P&rFA0+ zy`c|NOAozHRWrW$L_gJm(ZfWU|?Ms``7imVw<{c+upx&zpBsh zjlVlhzw>RR{hVLoZ=QdD)bPk-p4V+Axr{io?eDuZAN=syk>*8Sw?mpvCYJ=ZSK2T0 z%(?mD7uj8TluM@Savk2`CTuN5OwoPnb+FMfh0?(vt#_q}{o&mM$7?A`wwCH&>s_Z> zSlAqT$<0>!Rnkvw3Kb}&uBu&>_F!N5IGX!Z@s zvs4EbiP5md$9Ja`o|iiCC+P{e7(0EE>1Tf2I;Xoy_vn>0MxmxRJ-%fX>zRR`ziJt_ z49sVfPtIT8D%4x+|FG=pc(A>%#j>!}t03%YM@h`RQeEF>=~*>S z_soF$=2UUKFO^4z{i=V_*VlKq*sSnjUtjE;ln(9A6pLYa0jW$J8>cjXl>K)WiUCW7 zc*1QSoF1UhF-zmcWKQE_(^SzOPDQeUB$3E)C0|OdUdHyj3`BPLr$--^cO|n#t|Lwp z7f<_OD(M8+7o}5F5%mDxnqv`&X*ourx1#7D$F?k#%Q8O{iXP^;AJhBuYf}(`Ad3DP ziHuzTx8%U4hE0g5{hUO>y7{JDjYqWzG#%y`VqQEzHAN~drld4{G7c2iIvjIB3@JBA z3sXulk}1>3hVb$tBo`*YFA^R4+XAvNGtkk13|vokZrle9Oq?sN%yM3S7{m_##m6tb z?AYnJtHnVZfCg~IG}-^5>tCeQIK3%mUmi;4hWU^=av_`MK#O0?m**u%&RJP@`m51? zWeNhCN^N0gl~GkY)g9zfnD48T;}+FvgVe4eOSBnk)Ab*!7*?FC+l22GIi{Zg4@{{* zYW_20k=FD>rxN~dBh(&glu`03IjyaZM5uuyPkIh*at|@%VZ5bFI!&k&<0Z3)#vdb1 z>PGe#;`Ksm_m*^%ytlBVk{C3Vu~guYK2P``R{d`^>PGhd;Hi z|Kke1@?4~b^tBkX&Ewh~9DW_}vLlJzq-Te`S0qYB7?$NpWdOBjq&J4rCz7Mek&#E8 zi&Du_rc*5tuw|TqoI4Q(JV#GP0F&f}jDA4mEy=GIgjhx-l0~ki4C8cU7Ib#sm69S| zdyfw|gB(e63zz2QIiNYU>|aNAN2VG5nRxpWewyy;XL1MHf= z|M>fV7>^inc*QK^2ftBryRfj{Cb45S8`VnvT#li^Nu`9Z%1cRJ|KDUSPWmwQd}PvG zy0nB$GdE@Uv`|D(up)Mo&C|A-n-5kg?FHX;Dndy4D6^erIO0X)C3l2|!H3;(R)RXE7Qwy?V-cS#)i1 z*#JkUB!|JTm_&Rr+X_TDDQK~{?o3Mvji{;@iL(9;Tv*K)eUI^r9Xy`9v~LG@wFT7A zZ29s>a}8;4J*(!Pr4+8ps-^YONV@U7Lhj?D9UPr4q(FaBJGE50Y6``k?JK$149Uu%Wr^R zhm*=eE~9)b#to#LLmN^~dXi_fpH3LLlm#Wd#v{Ubj;a1LN$o(1&r{t74S^`kw)!&} ze2G#R`Wz|N(t^@Um3%w_z3!L#eoKtfuHWa;?4VxW?C@pez@i|>qI2fet|oG#fY+4I zz2&K$)+y%?#uby^O9^ETyYg%EWTfFc?`E=Bh^@&PKjcXIAxn#_bt)}!RES|!RS<3F zTEq>a2ox&M$EGQ&B{i$X`XU=_dl0nA=r0_NY>6ooXt7D%Y1%|5zrimfUYbD<(|0O@ zcr>!*(?~4bxy$FaeOBT<5V(~`vJo?Cc)+M`KI%TbZr9a=yOm|*c!uOw0ao6K+cH^S zknX-rE9StPdmWTw_R%H3P-NOY*R%Qc=%f})5%hk`Ag7qfWpk%tN*d{Z*hjFrZ!$VN zX~K`ukrdj-tNkhgB%KtvpwXM}f^tC>S(zV%N4Q*?gLgVY2cfvcb z`L;YT)Bh_oEM(sA(|kj%zJpD8#M0t&L`P#qrmwz}cG%(dl+x|_4aU$8W`~I2+Vyp; zC36r$7)D!ylj>zEOv_4=>y#R{$A8ye&}Lx&$(>|ci`;EJPh!~DiY}g80o{SFs{0#E z@f&FoM`hU=(ezJH+;vZ||I3o-KPe|{GgW(@BQ7h{*ldv!0bhE@z))J2!=?rfZ(g`* z_SQj;bOnZ7xLZ_H{)?{|Mc)bUY%&k}eM&_lq za&_C37T<6AXq8;g4KFiq$j4hMhz%c?BgHTfRK59Mk<;MDVH&3pb1zjKA-us<8I4M% z8yu&P&nwbt!$(hQt7*f)%+k4?ehBae>A&E4-so^r{M>W31VEb0|BdL22~cA~vA*eY z<%E`*swgrwFN^ZheM-x>sb-$b^I{cS>{Q8-x>xBT$fG1|a!&<`qMYHfg=b#M{Xg}~ z+_d&@R&#ZICnYV7|9`vvyuMWbptrX-uBxU+4_xTmdj~~J^e?RL+LKp(JJX-FKJu{Z zdG;OEGvaz`IVGj*z6a^3m$S3_cYZrfXO)gUUYS14Y+FPj%W|0G+|EI-BSYztmWqi# zDwaqVk3JV)3SZDRhiG{tInvYZg4)hSD(H)vhE0f;3F_^g2fL&_O4251oHESiMad#2 zNC}dPYq9M;Y_CvR4#B8cA`@(m`Y(zDqw*>lG!>bX!x17an?ycgn!&8)WIP3-vFlTI zQbcRKsk5AeQ8R&9aqujyHvMwaCc32tGNs5JfdAzv_Wi!hVH5}#Cb@oeLnczX20gtowyy^urKce z589J5Zn0x7tQH;`qvvx95Uf?^#N9Ua{<&>2aDc_2bTe{CQv)OpUrZI{)~P#qHECdw z>6Vvhphb)6qU+E8d)LF~>NZ@aux6yAOXnQJM(_WR-Z~Rf}Acw!W6xT){b}t%Hc6VOK)KjbxK`hv=)9D}580Bhs?^m4tgWYj9 zPjQ*#e8KeSb~n*(=>P)#7j_TmD9l1g(w{H11Js9x3JVqU1t;f4M{6b&DZGV@c?Q94 zgk{Muz_fEO?ZVcHtIVf_!}AO1<$`sdrqV<>fujgCg5p7&JR zizW5b)|2j)9N60x`R!*8(^appe@F!rEpmr510i~s6*NT#75+Tov^<1ZE4hqVwEwXR zF~`y(8t8!AO3TP?=2N0`&cshLHvYebZl5Z|v;SZueT`G653L&-owO-UNx%&dzLpG1 z&*D6p^zZu20fZ`{BC28ug`3lO;haUaLKL=qBHjmTtvCeo+$D-7kzj;cj6peCq>f6p znDD*g>5J#wDLITsjovNt7Rcs7&j3mtZ*VL8EL2TA>rL*t>gs;K&kOG-ZczwD1T`G0 zIWx;@CqM}gQQTT+1ptV1f$hTV3Iel0aB6HCJ*=if9{`WFXlm2xOON>5Vz!qB$I8zgSo zC8oQpqVR@ffwV+h=DbtT;{Cfch%D^7o!LQ`YCps~sORxF*Lxmdr2pV5h1OOz;cbs^ zS6z#-4C(Eft(u~GT?Vd{jbK>=$ix%NUUa@mM5w!W^Z85iC}SdCoEGmk)ty3OP-N?+a6v*Pm6~iIp6a zO7N@!X+EwNb_?n*L99duBrGs)(e&}4H2?#`^f&feuAr|X#ftPRtn+9jXD~b?M0`ZN zQ3_j6a0a+kVW?(IouC@-HgVX?Xkb4mqJt&gXx2)JGYO+!0e7Y^uf$xW`=j>&x8|AL zR7M3ARGNyeR1N0e^8CxNyx3_`Pt+E5B(%kR^v|X;l8$2pF(~A!@Hw@*Xsnuv;7*IF z8tNQk$w!@wuApg<4%*hp3H6e(GDNJ-ilRLobCJcS(_xyrGx6?WobQ!bk>cUX{Fk0) zjdB6V^Oz}a(9hpRx>u?TKSk!-8Gq*+@|=oCfXK@P3S+=Z`|jWAU}1jXq!<9YuSlxM z!O=CpRo3*qUDNw?tF9TC?6I_Zp4{Hv6IC441I9%~aZt7r_d$t-w$yDc_6qk4Yua8T zhk|>4uIU*Hw<+F=6vvG(Zaz9?n2^Tq|18S%EiBmH_E?3#Lrr^UVp-s^@C9bMdXey5 z{2Zo8Au`hh9nl}=0KZ4^CjW#8Y%v?C|H8`?wdy~G)gQ=fQUYgOwZ5O@U^|Za9`&57 z9!UFY0+SA5YWzGRD0u*+2H2&nJS(~MYw<7#2w+#E< z6RsMKGpo2{(ot6t21oLr74Hy*J1)-RGJdddax>n1|w)6ZFI>R3cS-bFUuP-C`N{sTN)^1t`tNwpIn~`I?C|bxo2&2L z?cC-bNW%OBob0F(KuP z>#QX*gdZoK##WR`T=@Q8?5VCqZ`?aACt`pu@<{Z9!+Rbt#I5s1PT_Y=JvVgEA9ITE zg}#qOUFrPU7S~&^?-{1AFAaa;l8IOS?a?VN9kgHi(JTC&`_0ice^EM|)07DhX0VQZ zoZ`5ipW^NphWBc5KU1Ifh)X|FI@FLsScPrnoALJ!!(T>p!kr8 zmD+@7B-F}sVKzJv3)mO&LRJ=W5R-fowvLSxaH9Uf)r9!DCKRfmP=qvJLKN3gW!XH^ zKnATQNC?EyRl}C0vZW7)@D;8sdPmd10z>)vA~R!Zq+ZX7FnP#miD2?RGz;xjqEb)+ z99=~!`AwVt{b7Njy3%3Yc1>G$)L%b*(Pq*ii(6X{5B=Nx{LgOg|84r$Z3`OSDE(%L z}7yJ3KT)+gHH8E+=%?QuHt#)cbL>Ngt~epl1e zX}@9L7vtl9UF~JKBi1C*ORJ7{_KutHGI4L&g&E(gLibvfsgtgw* zdnZf`UQnDJP&vnBPIgv$vI%1wG;yzStKpgom8M~MsL}WahbWWKuhSPX-g~3uV?HZo z;0;rQ}T>uzC3v^VVOpjrW#iXK#sL>s`FuJJWwi zf_3?Y{Egm~p`Dwm%A(G%`bT3H6FaH(2Q?44WldFfguGiRPrx%MSIx^fK_@bTq3R?QA$t6UEHsFvxj{6-;PK@Pg9$IY@+o2C*~IM3wC_$@6cFH@n1q4UaGm zUf|;2h>XSF`#w*mCSgSW8?FEFc45+862@wLf57t9P{g)K%gV)y^NM=!@&ySP}lDqKkd- zr0_bg@T8iq^qQ`RHTrIyUf;{f*_!UValLm7JNmktblr4D+R==t#nYAaOpK*{`d*%2 zhHs*2n2OTP$&N7oTGnb!c7*Gr=h{5~9qYXjF{ZI>DC>mcWv34suZUree-*NMA^kyK zhDqp+Z24ZoBEy6|NSUT|elaui50nbYy@@=|DaA7B=Uve7Ax|T&8_^r<8rcN{2LwQ60B?5@=xtHFu?3B zNX{;FgR||8d2&nUVBcl6+<_ZsA#Ih22M@W|Iq>{iZ-Y76Z2^t@(2=nMCH{^6CilvR z@)Z+}b#VM526?Z%JZ3i{7r4iw>#a(Sb*NLro#p9^Oy`*Js1ap6%y`3uWTYgbY!X^K z4y`M@vaxX}GLKYQI5x%qawUF|cjk12r@^5z^kZ)%HDOU@*BgdAj`;5wjy%~mWY1`Q zn@?8{o)-a31{n6b38*Fb=U)>QX5R9DM*04pukY)Y*!GS_@0!Qbx;8&k*W0MmOY*t? z#rB%+YWv#_Of&|eF^zewYCLeX#%pzSSrkhK;3QO*PfK$| zrgoViiDkPEr2{Yiw-X!omuFYMGQPpKQj^2V=Vb&SPiwr2@wBc8+P3~GBBTP0RJBI_ z{MEMivL?y8e0k-yY@Y@FpJmE{Ta_2Gku}3H^ZY6UWH=kC9Ii1OFHZ#)`vVGV(F*&E z0gNg%*4ZFGaYXOyJaLY{0ux}M1L7)37SI56{;382jZ-3=b*6^n4f7MW&Y!GB{Pt!0 zoc3zn=R7$B;PPwSk0u-TzS(ar_>6Vy+@Lm7O_YU}`v<19J{aX>|7PgA{0qjR{*BR2 zfeae~95kLs1EH{*!(Qzz9Z;3sW;P^rY^&K2Ac5xeDvJMw=y}sN8DJp%F^Ab(uJF9^{sOiUn3iaH z-0_v?5X%Pt?6$cR_qu-SbF(tOl24k0uh?*wvH_(CUR`k;K67tW=+`qCFhnSHs9+Zg zby%pCJkA^9`kf#BtQ$s4+G$o=eCZcQ?%8(@j z^#QvTi5#RF+xx9$k46qW7wUiJn2c{Yv`BxYM&Dbb57ae})-~ry)uzuTE%TmU{&1y-d6mu<+-y1nI**rZja2N4S3B0%_* z|9DH^g4Y;>pSR%B@Hu6t(<|U{4g0E>W0`TsLf7Vp0wC3y*EE3*zkyHE!3X~>Rhb#y>$WVUCcvgRyN9yWOcGI&U# zdH?X{kuh&fB;3tksOjy{wSo-05WTO{(UI1Z>0aeSs~W8S;cdUnU``7C6tmk(z@6{U zc?u0rSR|Sg@DF%f+q#X7sa&M0EE^sdc&W{{GIYU->J`?Z{uu1=KQ@G)m7`X@J*D`Y!m9N3Th{3J zOA{7-v98&&uSj1{ui(dCkfbh{R8>LCtta^fEmOX&>Aj-M(bpDs__iZV`V%e%y-x!5 z)z@OWdmHoY@JsUp*Hmn`Z~tjZ+-l0r4!h^`&y7Q>;F*h{dtw~WsNp+afh+D2GCFj8 z!&G43T&o6o(1Z!%awry}!CxrmwL7EW2n$+m4ds` zQzs+?b@dyw+DZ`DRg?P{>)*zq>v|6;mo!8sChv7k_xE?2`1Op|1BogAw^4MtXcX)n z?+9WCbRYzk*|GOPu`-}9_7b>%Ahfe(T~@ZB+2_@f6V4;dXeLm_x+7#7$Wi@9B%pD& zBi0$lB13e*T13Zc0?)&4fCfbvmBAGl!mSlagF_0Hhs!0tT_wT+g6$QDCL4$>^+;a@ zP*l4<8V)Ju8#DF>nC2(uV5v;O=fS|+h4W{x%W;=cr zj{Orqv^I*{Rnv);(fjsx0Vnz=8$^blS?cQw{|h6+U_EN*kCDG~uqvjz<3UvSL8PdS zEF8 zW&mMiu$SS}0bQ?2(AqfxI4e`%kn1Exn!XR&AYlvTCbk?gZ8!`yc zz^Q;PAWbo<_WNE!h)9Cj&7lx(5gOPd;3@t#u&xj$^!D^p>%GpP4@+$wY~E@Z z9qt&H!=pDiSVKprqM`~@lO=}6h#XGf_Lx;T7Ykf4J$M0x1h)yrFobb9iPa6=JAu(z zBj%O3Q&8PT_eV#LeZ1qwUq=CL6I*90#I#h5YLGj+6%?-m&s!kaD#->Tkx8R zJfyfwQ!!Ev{@6?A0;4$rAP8NHIVNmY@X9K}c{2afu#u4X$dM2h}A3(nd#@VnhR-6^qcDM#-cb_wV;~@Ltq#|5! zYmO+=zT56HXIbA+^7Zo{tEWxeOFZ+e~c15Dv>^95l~xZaAXt z3L0{AP;}XbvtRhQVZa%`4a~J6$_Nr|jPn`pZioxcMC~mzQ^A1DTaThCv3vT~I^Z-Z zzF}T#U$S!~<%ToQI!f$J&g(BFVaLs-F(i;}&J=+iIj@WS!J)Zl&#w{o`r6 zSywB(`W!pWuq3y1eR2BCuBN^#zEyFZRy92s3QJu~V@)BgzI=5g55*NA1JV$?^_Gh) zS#pDwDRd&zN2GOp!*kr95fNo$8*N)7CnPKqw^AHvSrFnH<{qT&3R26bgzE=+ z;$IW^;il)dtd8NT6LBO!e#jAjR>|!7dp8VqgZIIcgrM*jYjy}GT&Z?z{fi(@*;wHZ z));7XuQD4yEE}>c-pt-`M+42T+1ZJu@o_Knuaf}?{yO-@@Vx*s`f4o>kfv!jW(kj9 z1f$9%v9xSj*BdEXVF`#mPd7FKH4zSsD2wZlFB4qZ^_uv}mo>HA5TMc?oCJ^Odmxf~Km zu%UE`E<@jH-)q&IrfYkrJ2$TT-d5j&`%VAW;SL`R413~d)^iu(Xc}wj8-5R{x%cAY zsx0Tq4~Rs;81I!wrJV6f88xS2c4QH5%<}Z~3JWbZANkKe;k-JUIM)bblP>vTofnDl z$*N*U%O-sd?VBZGJhUB4FWds2F8)dRE}1xCtJ%a|lu*N;G~EQx!yBc}GIor+l)mcx zp0<4%ODZO}Lik%3s3&$^2aH&I!)SN~@*vzUJHmN&kmtS7bsz&_uL(r>Btn>RSu=UB za}+n@6B5QSR?zJ7Oktm&`?!6ILt$C@aibMVEM7;Y!kk6-5tTV6=eERSVT`-R2_4oX zxu>=gqO*S^)M;|)Py)ZzgA?r4@~b&WDa^nFBYDU_eop5&*;CUYvErD*j)R4FYC7N1 zC1>{h)FLdSUe{q3o?rNEbod2(eZBqTxV~G!OYfz^XZfL>?{MXI5y-5--s`1dy_UcE zVXqd)g|#fMyoOV;rEG(^1ty{Xnk?hJ)hh^;3zxzbXxwMv7`F$-DI4pRMCe1UxU5i( zhN;*E2{H(^kN{J|9f=SV$`T^#kd@umQg=-(H-KOuR$CB+*e7c{Nj@(jvKa^<(v_`g zy_anq`opz=*1tGSM2~~kc)=mC?J3`TzKS#vB{Z4SV5I;FmSDI#!Tlp4FCGp11a2@J zU;}KgVoQ?Xahz_Df&8VyDa4zW$&2!zMKSaABx6_5SjA z2RqGks5QKi7CE}i`rEO;MVkjX44yGgTjqSg`N^yE40mhNErxj>bDa9=fyD>bj@!QV z5B-z-@8sqe_IBG}x)SiKK}d#Q{*CPH@NZX5A^z}%bHjXfetGsX!&4@C<{x`c>GC*u zFZBIWCUcC1#+_=2Xj3=rUWS!DmEAU{;p=qohTSt8_LjxX#wSlS(Zm_7VZ=K=$d6AS zt^L9|>DF2ISEE;1KAPWfF+FC~LAz!)?0~4Mf)`oI(igm2+Ux2+?z?hj!&d#0#`c0= zykhNpyKcqWZ?6vQEe~w_q4di|`OQ|3a&+A}-4VVW|D@|ixR_5bLekwex(*7a^!zRj zESu@0+H00ii)dX&Xe)U)Y$cQ0v4#!iQ)NNpPGxVIb7<0D=Z3wjbk2`PHcYI{3!D}@ zKPjg53!7SGj@T~mg)UBdU@|A+R`#;WykX;uZ+_%G`s?%|dFH`pdBY|K&mSE;4J8DzblP>ydSK{3syj(;O7KP6aWSjFXrXp*sj z`N7)6{sABpU#E`@J@vpw(*SiUN5;F3t(d~|M@(QNi^W0aXY4h}i|{dE%$g~(Y;x^} z0{ zvHjvCZ^JcZgA+zdIB;fzXITs}Qtrb)zfP~1ALEa|waYFpAi?F)s5x$rA|jqH;E5w5 zmT~gx@&9A$ZQ!CTv;Y6Es{~33rHGY+*18y_thuGBJPi`7lz^h9C~WyP@&QQ$O>h`f zh(wb%<4X^#!=MDHh)+O5Fk7aqS}}m4LP&1s9S;)uG}Z{+x;hb?T!GMPI!n*FW6(pDV1L{ihM;Mk2bik_2a>Vrz<$OT=IC@ zY~nf+#)L?W*!`^Khp%hG8O~q4)7HfuY6v~JAM>70SjAVxphw7qkXzZM!yHGmWIEqQ0CpOsNu-AaerFI5k2?lcR z4t#C7VaKzP-s2Ngc~68mWzEg2Ywzql!kVfvgt+CdNp1;O8&(R^t&9R|nA78v7;yFa z1S%NYI^kAg#{7EdUM$Ud1_zTTs<85M%x(sYnlzSI8yiY7yz!B|oqq*C2RN64pwyMurdiT`m*eHy<;ZRYL zyG+yWR$lKZ28*3reqe}iV%7O^zW*z?=s*Iiv?fCDWQ$tU$-6Ci~sf^pS27tC(f))LYjjSJ}?!``s(=ooJ7U z?|-dN_7GQbg0{KL-qX_A)xTGNg01H*y)JTMla({Us(HlF1MR}D78=WHwEi43dt?M{ zBx~r`Plbm$jBh_VDvdcUS*`xR>9}~5aXhgOztU+BJi(yHGe~D<6~Avz2VFgm1G5*V z@7`a}?NVah4JX4s;X~`k09A%XrlG0gc*?5r8f5Is^GttEAD2|t2e8PO-%d(33~f$l zVLFb3YHK^jHvv=2dF2RI@wUlWV4xF{ZJ<}eRWPA)m>`h>vq=<>Hk7h)1I0;YoW2|U zY8L2`H#Mir2QsyQBCiiGTIwzaHGMZx0uzGsPfDHqPE@`C)_B3QVm=#d)G;0MLOyAH zQXsFV4!1tR8DP*l#yP7!OsjLba8#3bob|a{F9T&RL9x6i1ca0|UJZciK+ffc{77O? z^Bd;44rS$^F|;jcUuXzhN;NUmySWTIV=R4!l^xXvwbNdM1GvgfIjdL4so%n{$6JP| zKJpt}IeZ{|5zdD4@pg^(azl`7+T0R8h_`%{3)Wm*Oy8W4b55d2GE2Wxr^KF3bh5W< z?lzOEm)UNF*=}52KL$VR_OV~m-~L#;JTs{^_L^t^cb=v)TSJTVt}K$H2#!(l~u%y)N8uWPV>>7-P@JaiPM8w4jd@xz#5#k6ydprmTuruKTt3ftsa zVqG59_crP6R-zKY$@Wux|E*%%t!jyTQq9VyN`Vkh8Fn?MFGH~bah;s*uv1bj(ol=?fyhojr%DR zDna~_P$dfk8n^(N+y>SowgtH0dJ^#=Ny1QcBgAoJl~_OsPnm0DQXs`K%M0U8-h2f7 zLsiBKD!9R0aXRJ&kgAGnPA5sf3I!n^!Xy+-3v@SqA~^Ld3_(EJJ)xzvv+0(EIf9Wa zyqHCq5D&-*s)7UxB{((-n#94$lz<{8%4cO7*_VNB4^7p z2KSO8=1u0DKen|y_Ty`QK|Y5UF+jyV4hD~{-p^t-gEX>np(Iwq%d|1}UxAnX_TMQw zxoB?6c}poMPF=F%^k<|LaZbiXo)gw9PhBkSh8_FqD=PY~$$kkVr!~dMS^O!sqOxmA zo{D|Y^sD%$w&Z2sA>hWC5HE-WP?{R;FrN8b6*VC#5Ok3HffFwW0YIL2up8i@GYIn$ z6zU8zN@x&U1x`vqOa)Sc{s;}iX-`6Y<0^CJ7yj^g$QT`tYq=-}ah*>AWmCwpiK4h3!J4l6GR$Z%Zvdn?VmB2pN#aV}>8iEXm3L}3r3i5^* z?uIxMGt#2dQ`QoTuZ#kVB$Noz&bd%pH8GV-W8W}vuGcY}m_7=F1t%nU9ts}`C6c&x z2sl)D*i!A%{Gu@E(TUkEjSblt5w*dq4QgXP9E~{SJH&eRpYWyd!%Oi*e9*6?fCsIc zSkRvD9Ai4>!4yLD_YdY`erXG#7N)`+=Gg22||kvYo+D~2CR(A7Eh-xITU^m zS%H7>dnStsgPo471a0z*j8n-rnBbBk|Ins#;VCd${uU049ki4hkO4+Ws4*^tQY4Zm zuo^6}a6K^xf*V^aX7Cxza$58NYhjp{Sy&hI2Lr}X$EL%-2yE#&1exlt+Ziom!_u6<#(CT|c52g}AVSqIE#b|?AqeH%kxBi%>p9JP zT@1UOEWd7d*+*WJtlzG&ut&hU-P3B^ud(tVqAo{GYkYs371QzGWT6snC90STrd|8y z-t+qV&&zR3Zz&BJoS{?JI^FM}?N=UjpfQ0mW%3YLt){>C-eQ=ohcx{7YHZ&^NK-z= zg$d^qFc4@4Qx$~Y3Tagl6a%>rNr_!e!GO7Ndb#UBm%ssLC~;cW-#LvE%zq$kLPqz7 zw{M#Z;cw)6q_eWBL@gZ|CI*3RlyPZy$qAt*5<+#*@E5HIclKC9$R9b|aqMdZUW4bAcn7_?eJdk81y3G`iP@G^81 zErq~XKJy^+!=98F0YXaGBqQ{FZ30P8Clz&W(wJp9&Fx77{v!jn$Dc!1w^iz z|C_4~)qa3&`xGrT(sEzf*IA*Ir>q>c)+>9Mx@IMrj9S0s9@=Gih_9uM)8jeJug3n+ z{yrx@>gz$3Mcs~-_s-soi2rtdjr8hE<$BYY5DZ0sTskZ~>D^E7y|zCj^O{Xk=m)}O zZiQN;Sk5Q|btDTVarg}BZjT&TqsmHc(62#d)ALUN>u5II568ph=zf?b$a-3bk ziu^dN6BZ-cW|}J)Bw>1<@y*oNOBDxULNdom^iH@Pm;Du#4}`* z?;w&Wusj)Zf;H&;^XevB%p)Nd_lKiNK8O93>e22KH^Am8IMzzo+DI3kwIoTr5sp6uQchvKFh zBmLq5gaHyGL#QH{?B4J1>;&(-#8M7bMkM_+N>W?19lsnZ3J6?I{MPj8JaEtX(p#5u z-2nsj<0HlG)l-zW@=w%|D6Blmjr2juSfWfLxOZ12#0n#FEf0?><|{E^yx0X7+iMU` zH9}Rb8<7SCxF)SquRgXq4x!~@=^G`{OZh)T27Ee$iu3D2ljb0O$OR>ldMS$mI*7dI zgH)O1l(ZKhg6YQ*ox;NMYVzQ6&;D~R zDeZtot>>NeioUx~du{{`c*aBn%+o9;kw*QY<+gM=`~rShB`LA~%7ZCt#UPs3kI{=M zG)u#%M3b_8*P5GsId>)#7+Qjk0J73GmgW~~9+*qPdGLaT$`cBqxG!w*70DQ|cw|SY z1PN&%f&>!<9Eeunq+5!0Y@6#e9?Hq}8ZAS}^ZzD4Hv4!DuH8t2}9(%7xLeW>tfheu5#y z7T5_b2EYhbO^_OA3Il-P;0%B;&yH5}rGRi-DoxN(Ad zweXbGXaWR+k)fl|^xXK2+=3#4h{#1F%5$^_VE8^F4Nqc1w=3$?@gnUb1k!ruvGP-o zqZ2b!|5ecFDesViW&@x2bct;2eT_B&~RNrqRXn7&^>rlpd!r4lu*Z9n$7#X5a=aa?l0 z=2*IZ=2u=$(OUDV&LamqAJ*6Vo;We;PeYs=x4!UjKY!H9 zm;2vGe01i>$`x;Z>NYO)?2>Kgj*s|u=9-sZ`2NE;-;RBxtMhNZzTI1v>kmFmY*w2e zL@us*wCh$mIoB6Yk3V=9^jaIgfK30-e92X!{NeAclv#37_=9Y(OtOt zL^669J7UZwp`IWJ3r4yY08?J9Sb&`w8MP2w5g|qJ1S6funW{kerutt}O z8iI+AeZw3$viS|4!|t==)N7+069SA!i`E!Vx=t~ki z!t@We^VH(=@f<&W!+uXn>gT)c9WC~=bePZ7+-=Y%t+C(!%6_<_xf_M{RSrP^q%5iR zDt!W%`0bG1l0AjRZmzoG5#tj`a#U%k2E;dVqCjTO@5pI#lNO}WU=D=MO* zYMgYZ8>Z&g<>xFd!9(Jr(&Lr~xddtt)O7?D6HWqL0xx3;Og)+X5hitcQ-d>A0r{aH z4tZ53#A$huaqAIYOeYN!7ws-7F#gZA=;~t9ku??vI^yf^?g;ftc#L4T3KP@*B@&Am zkO0S$aLumuTHCSJ@Me@(#=a%Mi26}FUlb8XWezlRwY`KiHEzui(XoIe>apInQ_F*E z4u>sh@8&ZulgB5yf>KQfyX%yQS(!mkO!`*VX9qn3 z>zNXK?A7_4LaepXlk=i2BrYaQUi}E-zv{ad(cy{xG)&+$^nFt*d(} z=~ZsAG%?q+{)5H_?)SpYx>e67y_$C#H+RM_QB?Sgdx~}-A(3Cd8HF23MdB$YfVMKa zGo_<;FbfiwCFv>)B@+jw7z&>S8+3zbG?r)wL0KY6_hWQ;iGIT1)^?R-reg93l7HzEyy24QgXkW9LE1-xUE*NyZ&hhkqp`Id1v> zSl%@j>w;&+bsU|P$|Q82C>pHNzMhwUv=m4ggw3!L>QC7`bc)$1<4W?!|2g4tmywv)^*p+O})%x`=WT-tqCY=nGKO&2MX zr@ghMd|AKk=7!Dpd++Tr%XQCecvzdcugBCbW$4PBuLsXHlYd?**SVkU-Zi)2hY2zh z4c64Tmr3-cL>QDG2fSZ~rb-OpHqpUq-%AK`On3~m5T>vd!i(<>0uI2EL1X!s)a~<% z49-b&mZ^M5~qkkeBm-8_!?iC?HQy!0P8VsMaO!Bl*==+{6j%yu0&VIo0tx7 zgh`lwv>XVPpR!hip#+T$gDTGx@08Iz1sPIckHr8=V#C{yPBN|T*h*9buo65IJWZd@ zEgo$Bn`=@@Vu0}}E>W%>PDvEy3oDZ$A^kXyGum0ApUL_P|op6edLO zOJ17f>+L(F_C0KZ)n3@Kb@(!s_Hsi$7m5Yi2o3@t5(kZ2Q=V!{%Ih9#JPF8&scb+7)xrmAkK$dvbG+I{Tn?X~uY{JUb4bFG|1NmD{40Yhl7h_p}UH9iod~3WtetQRWf8`=g?c&GoXR7L^7sPAz52KqMT3y+8u+AXhmfSOv~REc2Et z&M$hs#Q5Y`x`a)eE^})oZod1o8&*A! zn$_f8o1Zhdv4JzSjtqtnp=c4IUUy7-l{TR74<=r*Ov#FV!;z%;R8efh#>mK}1sn!$p7+=R~yNw4O|F`7hek!O8upm8gRN4wWYiH|WwmzhAU zix|4rdHLjQCUx!d2xjI`s__e!qtx)yCYKiZP+3|+=TfIyo!--O3 zt@E*V_52iXYj6QjOvT@9kw=b7t-6aZd(5Z4l_C-gbmxfam!Jmz?plyv`VHW>^G z#Uj{3PMxzik|)uJz9#(Bo9zO`X)qR&YH~fZvqEUClcH1nK}mHK@+IXh+T-pSeE#Q(ENp^v)q$b+HeKU?Qch|Hdwmz@xhxFa-g zDXfVla1}ZWeJVxU#Ls8wirNYYJk^UP4O>3h_e81R*%?R0c=x^$d0o=9wPdX+Gc9v(2bQD$SlL=<*Le1#zYc zbgYQcQI!f+lW9pcfpLzOLsG5&LNwgg+58$X@1lM2>!aw$}kE9JqSL3o1` zQWRH!XZiVcP5eoQrjJG z8A+BYZ9;&ou>_z18%TeJi4rz_hba7jA|3*~BCELO#){h|y@bH06qR5tN}gF}$ZsF- z#QMV$Pc35{LbPEX(KXUIqQ@qFvy3DU7LLZ+Osi5P$u(XrZ{jjiD&@OqN0q;` zD7tKW44o0TGx3yhL+u`~%sw)ji{k52-*vgBHaE9y;enPda&k$pR%2&la|q&y-sXt% zx=FNJjk(L#jGO1*TSP9qK=#Krqq|&TkAJuANA1I@BBbc;cS(QkSk2A*$zl7xwO)6x zy8ClB$D;IA@}nD_oj(usMWSyg(JyOh4c?L0CZuy@<;zL0N_V6SA#Xfl0okBLk^uY{ z_(#pa8I6ijD0Gp9;0I7wz=7o>A9SVYL9B7EJj%<3OfV0Kq@pLK8oY+G!8E|=2Z|tC z02_b{|HtujYfiFNi+8dmS+ZvQOLD9L<(KHMz-$hkaoeL`zj!lu@qiG42bu%5Uq|7* zUPJ%iqJyubMCn6GyhD=$y^s@t2pRWd>S$wZ9A)908d;n1@Lzm%IC0An3%v{_4L|@D z<68r9#gXR;266vF#zn9xPn%2bJC@-u6jV@q7*kW0QkgStCh7aFZhuHTPp8>aMUd~t zqnp*;R9LL?#DHnU?tZQPE`1t4Rnp>Nmy4k?7wc(LeTvbO>o| z6k#|cxusKfG+R$=?GKAJU3bc~{TC3?`eOVy-|v|z&mgx$2)fo*@F1O&N?X`)<13Z_ zSOtl>D5RbEKf*U%{Dzn;XoS&|ZgT5tK_@RfXOIA$P<5EJFsMO_3ra$Y3ko9O*%Ikq zl2Q||VIf&FWoKdzd6N3Sk`i!g6YNhC-DZeBF9#lgyA%)<%<7daE-Kq=q zRtS!QZW5}B2a||NPm<`TS_ft-mGX%r0i64n7c?cYW7NxZ0`&$v<_7X4d~?RXsk0(R zo^qs5HaBn5KS=Z0a{aP|@Cyj$nYIf&rM6GsEvlz~FCEQk(pDDFGfH~SKJD*J$Me(v zL2Or_*7vODMy83wVYHp}vo7{e;&Hcnv#Gt={-2v>&fa42X)~0fTh%vrYL&Euo`~<2 z?Cs_=(5vzfjVq|1_!N@({jorl9|(m)J4mSoA}ufAomdD9Ckch<7mo?ol9mkFa|M`x z2=GbuSqP$NRG**w|(BOfE| zovH@Ad;^rkU6ci>2=f7Ad|-_dx3L6lMz1OegiH#uZ$b;jJN9!w}fv zwZJe)Qtz>)s`R2Z*X+`L6|lW*wyM86A!)cSeJus;5>(KqFj^oAY?QVlq$};H-=4^Z z^Ka$PQ;1?noRj6|X-72&I$8h5RC-}6V!8v|Zi^1Ra;{Qj!%VttI}x}0FCz|Ul%{5l zoWgRwE9DLv#oaq)Eu9-qnP~7jL8xD{S(x3EeQoxBtvS|yTYIO)y2-vwYP84ow#)WL z&lG!~{eiuAzh|>`C!ZEm)qBS0TQd)hjA=|VaU802PNQn$uObS2F}uw6-qM9IxS?ZR zRi-H-Yf&DO>|9_`r)iSB6`&KZq3MqW^1=_mY3Vu+Ksto|v6PB<>1O7scuu+lI-bS5=Yv1X8=J=9?CVKvPL=r zh6}Tjm-vR%PVJbj4Ol$NH^eM7Q??zzHGB#aRNAzC5k(vkgawWw5??0CL7b(FOVL(^ zgw4oV>Qji3p-7i@1{c%gM8^C^8DPk%4u_Cta;c-wFl54P#Wn;GEiVvX5b(AF1XvB2 zfQY8}1%chCT^dsel1LG>7Fm`AP^@ycz93o|Lm=N}32B><#pt1`*8QHpxx|y_tykDh zQf0k_=i2wkJ|4YI62eP9WPHBXv(u_-@$Y(e`g|-ApLAxDq`5e|lIn@B1+X41I!!P_+y)V(Z!WGwgboTRipyeg*o+v4QBKH#GD?eCI;bRw zAQ-Hun35kzi3;1q?3BBa;FI*`CB)~Z+=&^vrEF}o`rN)A-_@1=W=mV;k(yW1XZ<;N z{u6}`iSCd7^p76{)BiMhpw|1jQ{u1nW58$1Wh?`=jiV(pE{iT zhl4{#!}<~VN82nh2MUh=@406lCURgA-F*K5EyiYPC+jZOs7W%q zJ-@Zv7TaI36?6K4*j_Q6nqup!Eyw84B zd-Y^{t;VEhhlaYRmQLqQwx;y-%oIEAM|O8E)nK=s+Y{d(Pi4*Gujcs;PotP^LWt8` zkq*_^>!3DNIIw3yF-IOmqWc2>{&YFcf9Ot~eG|>gYoBQp=>6Ciy zD5=t39aWw_xv88(6M~c$M$w3*e-Zd^N_zi&tUgY9JIXn*0%=a9GkVNqg9?>3pB-{u z#PLf#Y!posZJd=V!@#5~)1^|jBuK+MYe;lanrOwYNm<1F_(bX|Nb6<;dpQIRWM>6K z!=$|=JrVA8yl9|jk{?gTq*q26A0SwG1o^zv5kX&Cb!{cpn+yO`w(~0ucG9zk(tASM z9lNJW$EwmUeL+A{I2k|v9$_D5OhYe?rn+RK2gQTsMu#|;<%UXP^DAs2s60?q5XX$M z@V<{+@)q;q z{(M@>x~RJKpS5@GiGE)s+J>^KOFYxBh=)VAqvH!i zjfwWP-{k?u=jbo$TnxV5G2b)o3G-{`k4%Q*Ywt)Vu%5P!;%y!r;_+4%6$&M#HA|bH z!Iw5I+0R0FfieDfX9UeSL@Md#VOl^tX$V(Z$_z#6>FFLG%xWo~M>iRVVzE*yK}9K~ z2_9a*uH!W{8Au~ED=pc>!G$}~!K7(L0D{Zjl$KboGecLy0XS?7+#nwzQ^xOC)3ujR z)Rp#BrTLy@3-{JK&>PKGG>l$pb71`;-;6=b0y^x)Fj+H}YXxSS%$ubxqWkV$1Fj@U z!Hh`Pim!B1(pHptwV@K;B-duQI}oEVJO^KfD~-2u^oq!X$`=Xn7#M8}YcYMgTbQ!R z?0H--J|I2Tp*wA(m0RQ;bnnSlmC6H@tHnJ#u9dn{xGC-a91E1@yr40EZ1+M9E-Lc> z(BQC~(Me}LgBD+qC?G-(zs8WH`qioABcs^JAl^opcXEMh zNfs}ZcFG^#M4sv*gl{>^L38X$bdG#_TGvPs(QZ_v760U(v;J2s>=$!vvDfh6$dtPk zn(kuTCFMC@!IEez)!?4SHTKEA?Io8}=E;5!qGu*Cu5G_3=i*hjcO;)}$Vx(-PvCPTk06@+#5f%!aR7t=9LAYNDm*}>` z1iQ*N2Z6~T(|KXG7?r@Av*pF zAn)D`qKSnt6bwWxmJlmwn@kvp>+lfFCCm{WV74c_pEG^xv7O7+Y)@HajHT}B%u!QQ zz^;_WxAvCiPNxT8T9Vyr?)!Cnao2XvXQ-_Jx;mt9(TBY}d+ZmgJ-a+p?w_l&7R6g1 zq-*Z^q@0jyUk3)^vCq`F#@|aGjbV|N6%w;Ps2kUe^~AhPGso!rZeKm&F93Hfg`aj+ z-8*|?TBds`XyVs?{raAH1r78Bh!3#v2WY17uHBDWIA=*fAw&JUI@Du(NDM_5?4u)_Kd_sP$x4GXH>QYu(Tc+3qRI6 z>iGR+*|A*NcrcMxH-d2b-yaOt3jZlS zDh*3yok>5kyxG#mMdNVIVdwI6)^^*xsJLJTggudfH<4vYl<(LEuo2q5C^ZYIQhwxW znZ;*G*v@gDQTrI%8E`08UV_^~IkA-pU~LNch;9dIrcLfSiIQ@Nr}-UH_g;5&p@Hc; zQEQs*U$**N<;-f0y*AVD;qRw|T4=TZIKAwe})wi_|_4o$N( z#@aZy#XZSlTBSFI+8-eF`1jwqe*L=UaO)=2xBGp)H$I-WPkobyH+`Qe?T#)^IE`=Q zq11UMW$Wx>-C`BSKn#}lQug5mN;}s!kL?okJC!ZNvP(yhTF@D@7f6I9Y_B>jTvHYa zgJ4r$5L1JP%w=8UI;IR1{RvjWfE1g1f^H5|HB$vSkPUir6x(q(-VT^UAf>E>@b7OM)?ooCZt!I#Uk_!RlkB| z=7s@coFOr^dc5VLGCcNPN#GUKcp#l_Sc9bLN{H)Z-w2~DpeXI{Y+4Cn8Q56w1eQ%< zq@Tz6zTJ`5T}RMHUo}2_FikcA*NL7gEdiM#%(sa2V)r+dNf59-kS9La`}b#hRGF<^ znv?lqnW!;!G|$?3q+?370uHhE)AqYf)|>kNP&McM?3BZLYQ3bjT3_bdKRo47PkPt4 zciE(_C^;Xxvk*nDNptWi7Hu9#rE=fR4H=4P4}BCLl9^We+6UU$nSW^_XZ*qBq{*_= zKsNQ2rwft6Vy3_@ok0k|8ZQ>g%x<@408>uQYIz;eRaZ#XI-Yn?hO)Mp# zkTT5N{p_vjEMy4)egT7!9bx8ybYI+(_<$9_8Q{-AlK_Da#~i5GLu@p8!GQsu= z`s;vDy3RNAMgw(@z!=%pCOEqjv6?t8#Z{4R?>tS*nC#3`(sg~(m}>h7O~-yRX}e82 zoWJ6Eh%FDV$Jj2|(^+NxwNlht%|&veSwb9akM?uq*P#^aW&1+=cbdEYoGw1iQh|ie zbE&Ui)`X|%d!l4hR}W3G+RD3~5W;KnXuJC8_uqg2osKk4mF*rOM9i?mFU^=KxBpH@ zb%Kk~AM0U*_7U+yg@;MDISRk#C9H~aP=G>pX?2ENiC9BYB>9Y_pl~=2sR(3^gt(K) zgtspY7g8ykaB-n=2`*r;a6!d_NGu6dmOTn{A)*MCE>l>iSdDBjRvdJaDPA#AC97u$ zC?wDvcs7h1?nc`#P=krHm23s!sk(jJMAk+%ts&G~qVy=iXlw=`k!=86Rfr)!%qFkH z1=8n59mCWmBPyc<;P3^`uFX^e5ZD0nnI*PBNd{J4T7>}x*=>OX#1kh7eWc4!eWAmq zmD2nFC>AS>7&4venkzm`eo@PI-b?uWWvRQ}6ZIutBPxDYoV_g0o|(y0gS1wW@f-YY z)o5*+u9m(uL>Sv4PUA|a5$z|3yS|9nPAKus^(L%bMhZQJhg=TA}jFpu;>bF%Z{<2||IsJiy^GL|9o~7M36> zxga9cEGNkn@dYKs6T|^=;;pQ#IJO7zQORnRw=rdkI>DrI+1!b#2H&JWvw$SwcvrOO zO{sHz+9&9qP_OO)ca*~#Sb$h1{bJ`n^B+b&jSeiE9F?3w+7cO*Y#Np91wOd=YJ(UE zJK-A}kfVgi@ycwkVa-8i?nV9TNO?1ZS4C-aSv~3U5NMgS+mME}cdz!=(Xj+bKt`a9 zXh6U`LAzOHHY(ryas+kUPFSvBjqqj#)yl_3Vx1U1#77cYMQ>R_+K)hk?UF*{_lkia zlb{?dG*-`!_$V5Ct>L6^pl*iJ{O+o>2+_G=Jc;F zwH&lHa3JITFr}_1aiKC3;p2{>xc~gLJ_@w;;K7olI=;SgkGA8P)^z)I`*B50>+0w5 z2o0L(M#lGel z^VTB?u|o{1S;K7!F%X4V6Tchsf(Nfag>bdpZ)m$ZsxoIOrwyN+XL=6yjO&37CpghzE`}1wpYJjUD*LRe9gx;P zl=DUOy<#bAG+kY6$FzQ|zc;OCC+BsEE~~ee**6wf_OyLKa#5pWA9u`c&}5_LW^sR( zt(LvJ)js{-`)s)*`?GeoPPdiywjfG)**m1NsJU*|*v)-(M7NmIc@~+v|6aBHR^UMq zk%ZhSOyQQuIS#r-QND*8RTI@2B8rNlDr+j+XhzX_#i`NmqCtwLi9d^GFVrvk=Jo3= zI`b|)$dZpMS(k|Q@O&YUu zcqcF=;dNCMn%Yo?F*i@*(+Cln-{^KtBWelLl7q2?S}Oz|bmDX^CRk8zfK`z_sh zSa_-E)m$SAHx?~cd1)JWu$;YUMB?l?>yiMrsA8vaSP(;PB>BGnW^F!3N`g-$`LFm2 zF1@K}uTmj?zR7kW&i<=x;fvQuzrjrMQoZS}R212dSFAkNfrth55p+_v@bTJjG?p;? zD!S&ac~+~Q)}p)At?VAQzSx?5*5AKElC9)ll@Elj2u#iK5q(GYO`+7$-od?qB8c>1 zOH6agMF>7%@ivGFdEojn)>Q%cpzOWJS0r@D_wAz+~Wz7F zwFUfuw6E+~V4QAwPEZ1_kGW3Z(m)eZk#v;f#b)$Jgh@X2u07VJi?h>QE9P~**L-Q= zrAzfLt!MZBbM2dd`lrL+C%*DOxwAJr`UU>${)?YXzxmR*pQi`?-!uPwC;OA-;s3XN z`S>$`Irr3%=Zl~H@TIdWrlyQO`{4HG+ctImCi5@#`iEf)%|9l&%(}*zZGgc(M>(Qk zzo#4Wi{w(C5_u&hk)Agp&G~Uze&Zl4mO)1Dkvr~?_mEhj)r|NeCQyP7s)n*vr=m~XUC zrOE53{n1J7VFgWh8rr2PTY_qzoKmsLe$~H^eWw@m{koH@*KZl6>7`yOhV}8{7Wf`OwqanDbzRZm(wU#=^>E-!mBYOZl8MzB$g(n6wloC+tKFZFf2!p8az95MF zEy}TS$Lz-(u%3BNzRb{g(+32z(vnLXanwka4+1?;cxdEZ4wbSOz19UShTVpyEu3M* z`lt%P6hjd1IwFZSW~z>z0y1 z*5c@P$$xE@4(r7*Yoyk4F~0|LYX1)UVT<9BZrM#XPr6Qi)?0pz^?UG8LRHU94Mz!l zEXS0z5PtN^<~~!Mc6Me`d!GMw45KE^@4*)}f-cD#QQEt zAtg3RQ9J+xV8F|C+BCj4*|a@1oTF6WzE0M0S)DqUNw6No|8(p|tQ1>dB|}JOxX)zP zD&>>8oC-SB*ClPWuM2^U;?69bQz$maY54M%AY}TaOb>pCT@f+@%~(}4d(o4r5nz@~ z0ava5Kya5KNntXFQ;T!lF^iPX_c^LRgWPDw@L2|XovYL!pD+GaGLSG;(MqX9h(*Kp_#N`-GrBK~D`tB%wcW0?TI%#_sZE?NRL~?D>Si3Ll`)HqQJ!c-% z)&2ozM@g#5Mp%)KrN?Blx5-c15a<99A0Ts>q01Z)IHcaD%akgduf!pNQ+}QFIO_l~ z2pO!DDkwH^brCCSNN@PMe$hw!3EY#CGH(@}y!18iAK=o7fyTi?lCL&|1j^Ge#DNvL zI8J!+bdQBI;wnRkx)D(2>Ax9IUTQEGM5+o*gVX_@h6YUG-w}mmUH-!oR5jaiN-}bt z%TRSdUfqssa$I1iE?p_}5C#T|c#DQEoG972UruFn#{%MEq~zfO7nomjkpFAc^jX=O zpq3L9zIe9;1&Mpdi`oL+rS$CocXuM-Nw+jtO9UPA zZ>2m_iArud>$!tdWaQK$Ly*b$`AP`{19F(0LnLvNi6Rf?JT(x4Nf>BFVKF8&y z-4UExHbxX}ECo!#%h+|JOOA3uW3haq6t;-Yprmr3iQoAKGmEG6TDSlqoC3wUnR1$) z;&qNxt$o=VN)+?I47) zLdT0Tg?FRlD(687Rj0y_UnBCHY9l*>#9$5P2nx7BVSz~R^>714nDMoE)Fulhq7q=iuoy>a!i%}A;%GYRmStf zVs42&#q{sg)N1-O?Z>jCEpu}CGSsBBwS~r5=F6^J!~Z)lk8?)uKOaV?Yk#5OkWaR~ zwbXu^g3BGfiS89^z4X)Vqs|*MlY*|T07Gn&lTHK(6vc1Cu&mN+vU;G##nq5{nR4ho zKT&!eCD6-@(sPrND0fNGy|<75O)a{^aL=W%2psUrSfq&p}dgNzvvVw zFM&o~ky(e7LO5a+icMh6=&|0?i+`if-CA2R2V)xzMnyr`Z5=*C!?113bOJcZ;(-i?v-qKu8yr2f%8BlMssr0t6c*``OR3#lN%^y?9Rn%$vpac8-)k`cja%gDdSlduA;fTdEIg;OgXkQ>$m|iMdVfG z*f+Y64VltbB(oth80XW0zp{;1iAzH801#2oA*1M;d_U0rF>uT>=SI=5gK0>NHY_5K zJzgZGy(Hz0;xlM+RZ6Q6$K(pLq$5Y?d0>QPqPVRG0)zy%y`_n|E*id}01r4LlT;LR zlE$3zvk6K{i^n{yW_5=nuFh2m6SG^&isB?Mh*fBd6!T=iEP3zCBGi94#ljM zNSZqDVuP6?wu{v1iEqA1N4R?zCEH-Ng`G+NPAg^)r-1BAP7B=;4VF5P&>M)2sJ`x| zXCOjyvmR1pNN`aHXg)=T+LxZ|%1NKKtZFhWM7m5Zg<4A?8=8uwtKw-*FV?Z;_e+ao ztXH)-1FlC6+^G}l`Ck~PKpejb);|JAd9yX^ZvD$ys8 zFAIJzwwCA{+GW=qQpvRbQ~u_*YrR;?y*AmeX&-BAl#ct$dU%@SUWD#9U{*a#UX#zONi zL3er8a4ed;rW9zJC5(zN#@U(t1`y+p0U0nz-Xb%}EqC%mW9oF(I~9~L(99G&t!S3l zNMqw4RB#^$$g#SYWs3JQppm>-j$Pn79uoYhfGC@l;^>(J<|O07Ux8%4HVJM^NS4oc zl6Si(xfn^AI6FC~bf)5caUDcMGCxMwy*)DNaURdnQ{>i+hUV)rX{UPVCKt#AZBvLh103%mgp}uNf=MWkBvjkkPv0gb%SBzHzi!cLbPHaFpvvj01$@9p(1as+Q zW0K>>?=D?9VAs-PsL=auWMl)VOk^UWiO?Z7%Os0kV=C|tB(e2ET2M|)8;1c_YhikA z)HNl83TBzsVJ8Fhd4S?Ga0XD?UCR+QoIS`3la7^4valyEr~Yfg+l=AO>$)~JHh5Nx zVfD!2ptc)!6YfAFDbM5{VdWv2_;d3h{5}bN7O`_1p+iY zj@dTNl2;DUOPpBgbS_OCz>ieF1OyO8CxZt(AOjHJQDipZwnLY{!ByGD_6u5&(mn?k zuM=}Elf7*f!udTVNIxIb`@MV0%?mNM`?0d^eajszQD1rg{PTO218OnS`rjiE1oJPl z)y1=iGdPYBgIl^JDcD$1*(Yg&rkBpjEx%9StzpMgeM>Tdxhz`UeaWjBfzeC;wU}*F zH+;bad=ZOOS`;5|Ttqrg{6*~&l|V{kiR66U;ns9EkCl~1 z<%eW)3N}w>kempWkqTOB^bmrj9EC1&lTuuVeTzYXkOXPz0H5kXrBk)6)YWurBfm-VR2)anUc2_}>@H5v6n#Pwk;jaM(_vnIBK40m zeIOKum?89$F`;~pfQSl)!tXqV_{rEgCP5S|smLM6_{n*DGXKV4mMECnU;r-o<1g#V z&?<$vP*9N_(VQ1(zICX$$I?-3Mt$m@>i=MBm+i-z{vS6*BNNqBLKN-3whp21_H6&& z?f$(Qn;e!MueQ`{Z9SqacJI@eyKKv~_WrhHbjXiAy6Zyi&)=0CLGaZ2(BIzHNB4R6 z&oRA}=$%oe?DwW#ilyc@BIf_VUAFX)y0AEi%&}vwE*l zJu=tmd$3#AJt$(Nx`ud2O_SlUK#~Y3xt$oIs%w8&)1o359WlX9cOtuZZ^ckm0CscAJ z>BfEj0HuziY0`^pZV%H~nzgoet?l;?@Zzd$vD4fQ6UJtX(E~~SCu9TdG}{A>t)}H6 zPtrQ2Ad_<3)V-ZCw~;UZrpTAY|B>BP^00@pB73{W@^P0YLV3t~DWp+&Ce7c?Zm8gTy|GLbA=BXIyYw#4!qd+@+*Ha@SxoSW{#?1tl3+e~0BS zD*)t4xq_vESxMDZFaTD9UrEW4g8~(0L;b553z61A{dXFy#EyuIlKsK&75asMOuVt$ zuWrxwc)fmmK)u=e!}!)uzPS0?;x7*Vv*M`ZU#BL7*8g?VsG!ipSO4w#4`=`XIda%}cbFFZae^2$ zdieX|$CuoExgw)u{;Ai_{OxQ&a9-|Py4)Af{v#qnw*|&@n$X6h)Qw-th9jDunLD%uRmxI`7@Pryc9*X>a*=vG!m8>aR$5@w7Gk{PWL?5B9ZL zj&xZjBZ574WO?5=SFSgfWa=t5b=~KoB)pyvWM0-1QxI z{uf9T zCu8oJ$YbGLykVq#EW}q_Hb8fOa%2+U)h=?T%!XuUx6uiLHyYait zFBZ-kF^pGud%ntuJ~+QH^2q!t3kPm-&-s^Gs*H?6O!RnB?pssN2CRHNIPb03v7(*7 zslB~dJlY_Ob^gHo@Iet%7_(e7_)MfOCwD_&z>(YyPi7q|*fLXj&C1Bh3%HD!Lt(*T z`2dLQ}L$QUmhdz(=_MW`|{w%TB;FUVw^m_2hugX@24w{IV1 zkEYG$=jiD8OO^U7mb9lZOuhQS{R=)V6`LRQW)638(IlU^+HXOau=(CSD}PzoWL072 zCEAqE_q0JbIjgXrQ|=~QKB8WB``tT@)dv6m)BbnAsM#_tB&4jDo)AqZJ!p%=I0_^o zOr6YGST%<@M&QP|#hJTzcXoAk#jpG2n`6lje*ejO`t<2z6&24v@9jNq+@1`^IX@!e z3rt?jeCZiga4?hhl&sy&)!aNwRdBd)?SerOOAfJ;kxWd4E_XgF$?AL&d3N;SkEgs5 zS?~f=pqPS8)`~|X$KmcJ8F7klMCyQyUEeJgmwj0ll3B`qap4O%Pyz4c8qDv4U_N@~ z{P6H41$^hs!jujZf8xal2i%#-mUZBj;*ZA{>BQo&8Z3J*@5NpB$cjX6iS*w21g<*F zDOK)gWdt7|5R755^el}67%Z~0o0tp#pR;m)A$Nv%PkF?d;k~5%orN9!=})=hLInl8 zri@+@K1lBGxv=ND7JT9DjqSK)#jW@co;K$duO@LO#q1BtycBHl;`^_MDfWfaO<7p5 zmbv;mBkg>t%<`9*>G_p>KaAT15`hsv{vGGGSl&D<3~xFf{^W|IMY*}t-R17SJiqYl zXq^5JQwj+Q`K3H@XLd}=DP+x?x5+bqm|0z29e=GnvBN&YSABoYCCzkS&P)VfsuDMD z?77B&dr;W-V}626r_)_Hexm<%pl142fto@X*CfBbt2Otj(_Gl{@Inj^GyXmH*e=i+|kVX5p}p=G^?^8`ojp!=4)EJubWO4ZH_%Sil{Q-X45r zC9}x1vZO3lB>r>!Yi0fm3YLBS7E2eLm!k@D7&7F*bT5n-^W|Mr9sz$dGUhUqycPtq ztx<59YXH#;0XNJsa@^#P#MF0v2iS-iV|uJfM8xLDv*ON_{eAspL8LW&>X+wLd8(BM z-%vhs{;7=fW&7Vch;?KYPIq6!Kwri%=07Y@E`UX1z8Jm&6a15xiIES0;()#W*TW1S zWZWM#4A=N9e#eE0SaSB0D{@$705LLP@_!b}1z*Em7{Df#z!uTyb@}VyH@nV%IX6wP zW&RQ@@r?yb7DQbAns+Xlh_x@npbvgCZUJ}D5GgS>tWkBrde3U-Z`;MCRf>)pFsTU(z+MSU4+)9m-~T$o?gdu=0P^obq6dtUqIm|gR) zefw-@Z8n>|=f=5KpKE%Jy$EBvCI??=`=x~-rvEp0Z1lv5)A9D6_y;0GEUV~Q|11#o zljHyXQ#i^sp--EjYt`uYW{Pjef`ug*R*I(I(zu@FC;wea4ohzKS){ENtp z_#J=Bn&XiBa=^a>Shqvr;+%9C*)VL_XYXwoKW_5=4POkCPfk03^6J`xaF%aA zapW~`S?}Cjv9HpFuZh(!2oC;W8;czYi&?VZ)LcA50xibC6ck{ThZi0$6iAj(TT0Aq zA_5+JJ@^$b2RGN65m{6I_h7;M|IE06orGiIgr)}XQ5eIL1xp4-uHdC7A0T0K54Xl^ z4=?0$ks#E|Up=;GE(VM*Vdg>wmP9UlHSevjHcXD3i)}%YetO!=6&1;hW5uTU_)Y&>vFn5H-yX5XZOt&I-}QW7#(S*G z`S%3OUvYQav+ev;qMgu**Ml8qJ342%ELbnd4>u8(qy(qWBdf+rG$|wotv(+9Fh5s` zGLV4RZw3?G1Q%FE%;P2TPW<@bH@N2dFF>uAvJSD91qEBT7HpmMtPdOn`14*MAToOU ze|`QnF$>5O#A4u<%70**5NO78gD!(|u?;$}Dydy-yd`4Wi z#oF9*lYWKY$=0s31$YKl@yofAHJANe9>37`?$G|aIOXQzeoUltlig-* z#UvI(m;I)m?XB9gXHQp=Uw%pr@i68TBdG;_KHjruMA*D}@AvmkiH(iLZ5Rta3;+2B z&t&f4N`!LVu)kVd(| z#f6BFp~w#;Kf-YG-YR-4DvkVxxrYhy>(9Nh84O0k;GZG}t$$Le>aj)lSR*$jA^P+} z+?7Sg!cXmjS3}4!Q@$|i%ktiuzvR$kXZmp1IV*9}C5PT9h!k2Tq)N3iimMYn3a`ku zNTp=mg!!ukWsVgQapp_DCP7m2ASEdx5D|?azY$YDp7Oc3;DjU-f=Qn%d|9F*q3UzS zT&UuHKPM{6J@|QN75;V_6Mq3v&MHwDQ5bETZC$K(q^xQEc4v&>HA#Qb6vmRpTFz(*P}3I`F!5X{kmWG>-F9YKCb8lD}uNN(jtg!rDNkx;92xD(bk99B(Q~{p*c(Ty8Q(Q#P4&y zBu29#0_FP`%{*HolZh9j!NWkyk^qPNCaU%jIKo8apGngShKQ*#p9hKqH09iz2aaHgd4C`qBl}F#HsVY(rH=Ck|c$M79fKoB_$6Jq`#Kt8oT5< zAVgF+CCS5-U?2y6KG(Ly9^H#b$Z;A^sP+mBo+ZNQlKQF)}(LAT*IcY9h62*WhME7#-#p zB$`Ajl$GU#g@uo0z$DE;fZ9TNLU3V13sy!%s12Deq(MG=)$?yYBpy4FO5zEm3E-h6 z;pkwXBp!+L+t-kU5a%10tEaF3_Wk?VLuFpRL+vXMhfNQ*VFKG}|42u`yA_i;qBChk`mqHp7M4_J!abFt-jb9BF>rw-qxK&wLAvOr#%jPYvg zto@U#x0~JSmg;L z)_kl}q(lf6gLL6f1Z=Vj52CQNj(?)B(heU>IBUUnzw43(7w=$xeAY9cpTj0iRL&F4RVUeu8E z!`bNqU(f$Kq_}F8|EK30;qbBr!f)0DqkmN)sl!i?zJ5@P7-_x}Il$Vmq4qy#u*YO= z0DqkknWZ~Kgc0^ckpTKIBGUVY__{xd+uQCp)LztG8(xR45t3I;ogE!=o-5*A#%B5P zUOE{MptZYi9o~*LjIT4cde2LK%{B~~8mnFDT0M{HyH?ehd3OdVQczV@)eo6U0?H=e zHp@UCp_d_)AQuLIP(L6L@k1R!WfF6ui(G@3g7-s%kK3@2G`b3~{g3KE4=`%ps`*TZPbafO-K0B;XD_iw-70fFQNPk568%TM!lb$~tG0qP|KjL-t67HA7!z z2;Kv^Dinz%Zs4Ja4uaG`H1$UVQCR!g2=8(rmq^>A*wAcdzWX3uT8Q5T$rIH>B$cmm z>ZNN(kzd9o3z=im(Cu=KGQgEj0kSapQlXwmEirz)^#?kg&{T%QhF%<(wg`yB} z1A8iz_#Y`DTeNKLO>m?eQb1Fcg(2g;d8_No2245jw0#s~`d;k}w@lMaY3{DO6Oz z%ih{Yj~*ES7N18){eyyDzz1zD^X?5Rf9W|N0OwS+)%*4A#}6Mqq>-81{O!CBAWLw) z4D&y~E+lg|eZJi_oVh!T@Ju`$3y3*?G8sLKATavGb4|a$Q7( zHKL~f5tKwu5TWvm8DPa%E5scV*##L}LgXf~=jS9Of)a<4g=PTpfa4gBVp4dLFKv;I zEa(pvZ(?L3+`AViB*K)4)Ag`i!QKz^--6qb`w<`$BK(Pl^w$1%e%$sKSOEEOYnzh$ z&?JZkBZVl0IINK^=F4`{hluz=xG45L{MhJ0aS^Z1w~+d`T~;a2ObW zX|MG`;MgH{JW&1nf`5=o1CX8*+d(Ro1TlzhZ9MoX77`dBWoOnL9q~VwNN&mx6-c8V zcU=TpdfD6S8W?ysYe(cc@%`Z*r<&CI_I{D%*NW4%`? zDOKMk=jg&P-rHTMuuKs_@4#5F8=?u?^m(VcNVK4J(wlfCTd-2;w7& zj444M5s5{bki_Q*^^`dap~ldV=r_EAYzvefNgT8wB(el-4j?lF8I?s6MR{kHbE-Al zn=67i*2XzT*6kPg!%7%Ouq}~yg%0Cq>Jje4pPIQXDtJL+^B<{obn52+!hc)3)#Dc&+>DXrcC6KBTVZ9?MKXLmwcqgT^P89e^BxBw1`X7b zdCI)6NGtG)+T16@S1pO*eR}uq9k0B)de)FAJ077i??(AmAy|%kiHt@oVZRXXcVtTP zG#S6U-?cG(LN&Y&E6k1T<}R(a`)b&R9Viwn=?6Uo-do6-dSof)WLfx3+3a)gx#tsD zf#bERSSO=Rp`|n=xVAU^RKj?6U{#OiNI;eIi%~BB&=c=b?o1hXX0ARXBP0IPr%!|L z-Zf-oC@hUx5L174V7zHO=&g_LriLT7HhO8oVr4>NO{q~S)D0~r(#bh_$q{iDLL@hS z#cj1XJxI^yaL#6x)#4nBWTIyz#7QT{#Z6cW(aj?{@j^{>X}rKF9+4C(yuc1f%&MGbaSCt2_YAX zn6kQ!HC=f*HRpfsyN_HScU7gSx-NoK#)ZqR0CkHWSK2Rqg9@uzaYIX#9zVmL_ek|e9-vl_Q9H|LyDA^~^PeXjaG%SwpW>J?KMI{DGP)c4qj#rbTX85%N z_wv-;(ua_3(Pe*ctVB3;L3V$ehOo%yyw`cCJpd=eh&tGR8 zri!yN_nLc7kT+>KM9#7A_SETW`HxJ_ct!Sfd|tZ2a1!$@U;-%e*jce&wWmB0E zfW;6Wr@H@&I9)}Hd0jxOgH7!>ucxjSWV&4jT6EJQCK5OmQ63D&&k2I`h_TGf{nqp& z3C7a(-?feNvV%wc-VQOo@x z?t{&w7`QxYqxWypBCqCa$zT_lC>`!9Pf~dkC}16mh;k6KPC*3e;_wp)8*~Vmeh#R@ zf8XHX^q!H44*_S!^RfQ$-~aymuMtQ&2e=M{)oQB%4S@OB6Wq=u&!V@cV{u&Mf02yv&dq@WPG|%rWXcjghFdv)MJUj1MS8_7%73mqkTI*-lpm zTRJ;CFOiv$V*u+VFE1}8r6n=LVYS>KeTfZ?1D8L2I{s~DhwGwLJ6)wqikHr5)sgXQ zxId8JNn@3>#{|&t()NPK9E6YA8l*%~S??pkZ9aRHGpWUO*M#5)g7aeNiiE4M z7;Q+poj?mTN0^l9=3t%9wr1=X*V#o?v-9m>Zy{H_XN z)pTidWpUhrcnku9Oa)i)<{ss4kQ$%W8Q|?7T=*NYv{XB5zA=EHLAG&m8dUuhKK$uH zOsbLfztMOe9wm+gW+iyi!v(IdhdNvBYj#(|T((q%~ zCc2gNk|Q)w`U5F$4gz#hS4A=qo40vmOC2PE!%xJ}db}D32UWU;e?rp%NPeeIZ3(FA zNdZ2`!KtcCl^jF!GDek_%y?hYnhz`MdQjChTtim4at$4KKnn~Ej89E%4WHb~#j=7W zSYq@pNPl8bGd!IPTd0VW3G6~3~72Kh((pI>u@DHjV>hYWKEcWcu6`TG8x1tq&FGp zT|zW;6;Ko%0u4SM9Wca5-qZkC;5T`d@5!ir@sMbM7rk6lJjDWjFZgl<=&&^T&}wnO z7?JiBGkokwf?(5y(qwYQa*9#Aim`}Eon)$qv_Za~x}`X-(3imff5XoenMGrbFAuL9mJ-DA}gfUAY z3A%aEE{A6S)#3=^CFl`+(j?qU?dt|fZfFrrdfOx~_`|L9eS1LFAR=JTvO`zgn&aBs z>jwwjAR3c;yaFvJP9}^8-F>Ma26!~mUvF2s#2a07gF*VGUBf|G$g8KgWcFqD+;U*?9OJ)QI@$a*2t+uIU27z< zN#YJL2iQ6U8|@B}!B!0ndjwVVcsbWrR-Ptnho?AzaLpfYTefc93VW!*ymsCsM8<(j zjRX0h@D{CxfP|7Aie9&}3?9^Uk8HFy`=_y@G)Pkagy9b!GL|w-c zz>07yP;kU$!e}?E7!M+6hXywuG##uV{)Y}&5dJZ*(<9oBcpCa3gb_hY_st*fD&#Vp zD@ZQ?ht3;A7MHN1c%V>Id|m1t2}A;TxtZ{&TXNDJiIh@lHx+;qP@Hf?PYoKlsg%7% z{34}nNxWkB&CMzVoKYC$R}tS|Hw0cWFPDK7ffn}J7rd@dgyX^Rf?t#wOazSmc4HVq zI9UJ{QKe6K+^=V2pKOFiv&5aO+nY8* zcfji--a^x109XI)CY9nkeBZ9!;C*^PUK=&TX%Sste3S=QBgz`OSA^Fz*|NNIN{UTW0r?f%>=KQOh+ zFGOJ~z3pAU$H`#Vgp!_7ZcAI+Vt}!w&*z2Wl9>3pu<(-J(XEXGzA2`$GjsnA`06$Y zm~2LB85HKai0^K|b!Z8q#1KA=4G|3l9g?O+*_MpYs~LY2Od;;z&pe zS`7$T$MmOh0aU6u?SB*xU6)>Gb(DBEYG31WSA5n1;cU{FB|mav9_qVdNz=?15=s^Z zM{FYGj5LcK5n<$$2$I)}4<(P8F%GMU-zGgrLTUOG9Rkam2thGd_~ZTbx=`Xn8H9;D zB*mT0-E83|!C@dV;_LZh6hK9!jVt;S{k9$;tF|EVH3@dsi}O~{Xt)8vcYzu;WF28v z=x@~PnqM}WVbhOVsd6l2r6F4gpCQDfTO+6h^^uVLnk3Pd`xwebFvz!LC3Ic+1|>}m9oMR!#2&A-9rDF zy?0u*@MG`7g2M1JY~dIdc6jTYwz2t-nm;XrJpr4G!7rSGI$Tc z{n*EkANL^1^j@7TrTWNbe;cmvoARmc^+xEVdF%G=z?r%Eul0Rn_Q_?>cDIyPBcB)0 zQtH{*G~%P{wKS%lJsxy$Nz8+o`bYJBZ{+K>M2qg&k_3{N`j(c`H*t2-r7M5~DxBLo zG+nn$R*E)pWBloaRLoQ(&J~rHD9| zAixG-36P1kL;buaGe<~l3>=CV5n}Q7)et9vhhMTV1tEle@*Qa@u_85U(jtTk4w&AG zp)e9I14Tr*LC_Nkj}L&SkOD=oCXa3A0}{X~8WTzO0VeSL58)3~L9>yx777ThFMp02 zvW%bt^uhwj2qn=m3`^7j(g3Jzdy_h=xX!|Ckk}5q^dROq0YnJ!5Xkh^xH+KC6$H9S z?!Y8}T$yM|3w|_VxKDas1@K%i4)J1RGu>%X$AO}}`V8Wu*fBS6*G}mhrVIucO-@b@ z5pfe-5g>*~y6Y+o6qj_O2fqjD8_;JNX;xR*2sYkfS=fWu!vURDJtKG+dOA3G`U`*{ zK2}c-3LpD$Gdqlj8Mvz;vR0`~3kS_2o#+&f100w4&b;PMj5n@AjJrReYGq65rU#Sl z+u=s7Z0&e`U=gw=8F`|&{rz=q?;;V$JoF0eyA<|i{F{&NVZk)lgtqaZ?gTAhs5$_* zeNXU*V*Kk>cBZ^*_!ja%DnY@zimP6CcUK35hRz+St*v=A$~_QM@4L0ZH|3w=XM&54 z2ngmap&UTQCAs8if{Xp{5rR>N1WVV5d;H`j61yTx8!aVVU4Lzt*q2QoEQgX`bW@_-9P-W|@>5qE?fEy%>cATswaOkzH)vuLxh|NU9Fv6-8j zv|#XMqk|h;HVX)j-r-zJq!z!exC1|b_^7}Fu_TH|-NqE3-6JeP$CAzBzuRLDfOK;C zQQQ3U+iwN6Q}#=j3YD%YLjXF`wA@=uWwrVU$mm+$uEj;v>L|M9nhuesI4#lr(t;Hf zdlIEsNFQJo3%_kVctxdKLY=vN9Wp`k=VZ1RMF{xq89B!GUq`5PMV2J0iD1GZJ&Jnl zdh(%CfRFOp1g@;Of8!16G4^t%b{|qcO|%OU-4Y$qcJ}&m#5fjQjnekpwOF~Vu1LK6 zDC(ASHEKIrazl&tfW*P-=xsl%Mn-OENsfa^x^%NOf5NTpor$7gn@WH|=^HbwE=6 z0R63fkVZtc(Gg=82QlS4#mAVGKzN_k7nGHyl)DFZxG=YOCvUGXlQ#BZQ(8)|g3x|T zSS@A$vH$mkWy;0LO)Ht(`?U8Q+$b5D@Q4;DCM7RqVtLHLG@WQJs<=Zz<^>CB`#sVO zdz7mEj@6@0pvclBBKdxU^c=`UB<#SQYay1xfq1})k zj;OSed&Yvp`}Q&H?#pguSRmMTAZa`Rm7dsAD!izybj|X< zw3u#gYKz~>KmAXi{vseC`ju{K%m@-3e)K8+#;KK!<6Lf_leYTRcCV`Zq+Opp zt)jO0H=oK^6N|jlDzWDMhP;0-?2~R z=`iWMd#7A%*~#liGGdLSugD+2e((I|o=iR32BW3CFDEbyKi?rXK1|F~v$ z{Nk$>4bPtc`q$<3W9yRdTs+~at(|VE(T!p%E#TLSKxZfWIq%VXb@NUFtpa}%?JYqYhb zLhlS;=n9(5c>QoPYsT{#ZK>g`+^zBnT2k=48vme=x1N>{eB1tl_w*3%uO##0$j;+` zZR*rcOMkITI-PF1NoJYUqu;g%PK;l2;@tmB)i!3!E8Qo*v)5}>PMK&_zS&~gX&?M$ zaA@*htB^l59lzRnx@IiTIAtvSVs^jFzGM{_g)Z|o+P|fHI=$k!t*T1>Yme&VhO;{M zn^E}C-pR@XwaLEQT&~{ITC_kjyjgPnGTc^Y_sa&IBRXGN>2*5k%?T$eZ>?Pu)Y#R_ z`F3K@-`kS@vORwNxrMv!T^E%TS2zD@$+~ANandQK@$&Mn2*CvnItmh(#3XqF z`(;V!_Mw_L(|>P!^85AsyJ)|iFtxD9$PnhHrCY8`7--sLIW@m{t9JT6RNiy7xR~c! z6@}9}_yeC$wS7_Mt;zcLqq)NF%?f`d{{8&dbo2fNtLDWPhE1yn{G}fa84-W{R+Um0 zx-Z~}B>jbG6%G}t)fo%>(hiBx(^gYKf@TYF!e`S#NE;pozyjz_(# z^6oV}?Fd=Hd0S)nx+8aHK5C=qg0mYRdcIHp^!(nHV}IOQ&}H6#ywkB|OK*AN#zTn% z)pCEfG^=j(f7)i)-x=7mcYXBs@G$jLnaTHTKcOni&!25R#%wY47s;-jD0^obXo_Yy_jhnLsx z4*K%%+TvRVoO`|Bmoy~Y+nl)ep3d)o9V_Y%nhd*DHGk%XiG^p$Bb|(|t!A>GGLLNT zr(ga@*IpxJpey)4^~0RR;jS0FltUHsRa@`exnuO#+Iw~lz8qcqn(+F)A@849tx-w7 zvshTiUr<=#^gR8AxbQL&G50k>U9(@Yzi?@bVLTOo(GvzzUAFmj?x-D%(+rEoLZMZg z?%!dDZ#sEh^4;loo`olrlMbrF5ZA2v^)D@%WvyU_@~MrZeM`ALjW~3-6Rysx>ZZ1)(=32ay$jA(M z#Zghs3aaB8dAchc!sVsS7q7n@b)yNV!OMDZ1_y5RG(ASRC3ri0LRxv-~*gdus--9GY+C z&B^fu#FwVY4+iM{3_tjPddCsp;&%m#0jaYO@D2Vv@>Qr*7h7|ro&5?6w^Lh zt2R7yym43cL(eOY%hl*MTN`z!$#*3oa&o(=v_dZ@WoAG|6vM%Ib@?@P9}+v*%L z&NlS>*Gz6ME%3o7r|kxU3I>8!VG}90=@XKFhOM?s>r`t{`+X5YDG3&>!F_=UEkAD9 zy@e*ef+ZLy!p;#`Ow%$wTNibWPLhBaKSLlxE25H7RFA@r;8s_q$yb*Y`v+c(`8o#n zoU$q|?6s-QDa5tp85BD+L@0%ECCkY}N~+Of6E&m6)XWsGz2vnVdMo9 zDv;P7LNe?!mMiz3MCNt=IWlFlf(GXouc{|gV18@Jc-D9c>tS=qqFzB0K|6&8wYbR0 zLX+Hs7bNT$l%9(g+o&$r^YU_B0$d}{kF4X+#PJ+5My689cWgh5Jmq%#cWiY;TDSJ} zP)6Z^3$HV>z7VH%MW{ptwo&5BNi|1!*uCc59Cv-rqn1* zyAs+iit8e^B@{I~ww{Tdja`nso~;V3VjRZ;W435q2Bs_*VW{Ol zB!71JziajO7d$5!R40?=^D*np$h08S4pUrgZQ5;0;;7dYm{c(qH7QEjSem|Y?V2@6 z$K1TIxk35-zN`M2&g74wG%~bEWhoY68ee-LX03&EkQNowIN~En&+6>CWKTD-E3sWJ z>nh8>YA?A?O2L?$PM*wL|X)Q zVtrY#)|vaCF(#sblse`fnb*1BHD)LK%SJO;LWf>FyrGk1L`icBSl&Cew`1sd_S{n4 z&~f^cy&WresLB(3*Z>Ac4z^aTC9^yVX!dqy(hN*oUmJZsY6b~~-Yb|gz(_tkC5fO3FnZlI89gOm1r2EKs|H+v%?kkPE3n7$6&XL zxE*G++T9c^@QoA`NuXKEtL_`ClY0;`WH3`i|Knsz*3~0hKVUS#$aq;0c28-VtnI7# zG7?eo`9(tiWLC%y!`V>HcSBwwTMWO;7}i^WJO&Til~@SJx!Q@y(q=@@z^IsN z9&va0sb=JO7F+K1SeZkCA~y9Xz!oDzEXDQ7cf5TBX)t^$@XZ9hR4P!^WDkGIlbdjJ z{%&^C>d9U%6+|o#*jSpIVrrP-+fCbRsd%Yt`YvmQmK5WW? zv8z^Re*Um-2YqWvy8rHu=FF6n{=)+X=k{oZ$`=(LwXWG$CX&@p`X=^h4q(45!5bwCltuE%^hy-FQkA~xiMaF=8A{cMtj_`4uejw_VV0X_ zX7zijbuq)}Z)-iQHd|2`w>CK{^xO_)q*hbFgMs-N%K6orZj$7U8j~xl?D%l_eb%*l z6bi3hvoSYWM4x)I(3|k2n+Cb=P+cY%sxsqcid*2r9&UF_x^|!dOf3iNZ1PGAU)?F} zPYR0Nac)P~unuW+Rx!igE*h=A&nYC%JLGlePLn5BjUvy(!(zJNA0yFJSu^P?ugBwS zzqD*<5xlF~QhJ0c?mu$1B{3@{<1lwuyOJU%4>H%{7VK#>2D&cD}Ua33HPUr0f3rMMme;6xWHAg3#_GDIUKhoYz%Kzli&#UH} z{;Hm3-o8<24FgV!5Iq44Jd2gNNgy6f^su!9OYr??97hfd{N7jWRFJ8Z+njMS#qvp8 zW(vBR*3+iP`Pg1H+P}T#lSuWq8@;Y&pI>a0Z0Rm(jo$iT`mgFSXWpi=?1}~8HP{wy z$>N-YzvT-F3dikaXKg%#8#)veGum%r>C)i$j_D1blYS<|l9QbE>%juBsPzZ0ZMh9c zPIaYghm3OPqz*WrQpuisX?5)D&Cyxy*ORZgS`Fu!eygygTSR0~aWF~kSHaxE7iDf8 z0aa#tRJ6O@WsH-nGYi*B-yEtJn#ytUU) zy{vm;;opy%P@CZ4ouo;8a}MgRgC0x-WY@Nui#!N;qkCdfz+8t2^j1=Xx9}>mR+miw%8yVmVqO zOr}JvUt(Qzirfi|x(s!d4lTPB^v}1@(KCh;c1h-Fp2DLE#&*7z*CBukk?23*bc)hD zy|VYw0sVnJ75x(}z$o}DL?<|-cFZx zw|e5@5YQd=Zplc^jMBA(AR$2;L7OEMtVZa5eX7$b6D?SnCy_eCiYQJ7z6iG#7Ge}V z1UqP~GvvmlO}^iKc<<>a{(fzC2PW*Crx(?{ADweL{N;971We&HNb(C+{DEciy5COg z(BEgB{6i1(#r%(6s|s%1KA+mOI(UZ7dTfJ2#UX)AtKTbhG0V=AW9918oq z+qofsRq&P_5UW@HpFDWnk~n;0YJN0S@PJdW3jICgv!2}Pk@tEI0g||^OMA29PoSO+ z{*6y0{maH?yu&2d0LF`7MJVn!uvkJlBPy4pNMroW@HJ0_OLhB-lRTmwxlxTzTTD9W z9noK|rA42+%Psx9MA!Fd{Q<*IEf@{CkroZpU-7QHq-z+3SebmMYuLQbaovgX4_|Ao ze6dg+dqTa94cTz$o2O1jq0pdWG-go}=hVn3jH8A;8VWb4I(PSK(&4LBE45nPo~#UL zsdM}(>*Sor<*1YI-Hdmtg0;_VP7`5{xVMYj{c}V2*wmlBp*wJ5iMpLgEa&pk>x$B9 zM|?aJ9@T6-^D$|{hHYVd{r4QjVN;ab$VOIHZAfitshgGbv>LNeNJ46{ z@+JsX8vWpdfnjftvZ}P#8#lBhj@;OehIRZ&IG`mlaBjLv?*=B#jU+{lG-vLWezG^v zwXU?wcc3Wj0=H�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ν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%0Adb?lf@*Lclsfk0v*yq$2`t=+|mH~_s{iW(r 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%0DG91vhlF! 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ȌHv59HFV@73CqUtCX#jz1D`pvyxw5tls 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%0Ae?jIY3RbxCc4+iZrSaX_r|jm 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

rmXCE@=D8it0OZrRb#f4M*y__ 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

b zd<5hXQ=vlD0icnJO~pUW?4g{@SQ*JafJd2FabKd=q`}NcpV|{r5HW7R1}%CMvQTtq zaaYsX&huyE&iXp1`#ib;SRHhV0zBuS4z~`_NyWd~@>h+wSi0#SQ;s zeBHuen>F~Ekni@LT0Z~5Y1J3^lz%+u#;ea~9NV|p)>5%1v+LK&_6y5=Zxs8EN6lWL z%m`sA$4T|PyzThrsbG_mH*W}XQ(r(Lin4@wOao7nNWUZy=2^zua9~8ex>2PHje#*N zB~x(7%}Vod5pWqWjc;}Sk1BMv_U=Mrexl2?S_Fq2+Hb9ZLyLdb@MA+-m^u+WJrTp3 zEQ%J=M6tB0<|_5vR(ulTcFX+F&ueGHhxJdU4FL?Quz&W3>2;-vX=y+8yNP-?d>HFc zalwcCn9i!Oh=5@wc&zv3z+G+O-T4){r?E9=$r3K?l) z%)d2FMV{XwGH``Jta@@MBkJ!qt&zJ+sheI?z0B1-X{%pzWw|r2JkRNRL$xc7;|=R{ zNn$_@jYWfc3JRXJE=v#|qySE+uQ-sR1uNW`a^XM@;c|qNg|$1pBZ#t1rt3J-ue6P@ zfkOh00DVlk)=UED>S^@*VWDYDP=5g@D{V3=V+^#}8Ol^Ih))!Z>t(o%@)YMMjEbyn zZ;tA?TI;*Z*PQ9wOnp<=9o?M`-D{V%49T;nYKgUSWSDXtUI1<_&6-gzI7#_9<&2`| zL{YCJ+gYCV9FZgkNvv-+8ug^KV?`-LHkvi^j`Z~O*9Jk`k}_ZDy3Nv)Ema{3PX}gJ zi_;7q7n`Jer=>0^^8CTyyr1olv^vip+!s2yx;nR^t)}h4oX%H^FMfYyxxfDp_#DwS zVh|>)rQ39oJd`skR+kgN!{CBIg8@xN5@|Arl#lO1=D0%T%IQ>v_~B(06LN6x;>Bdl zO~zkpAhn8>f#6jU9Co)J^svN$G8V=4R4zOh_2KN>p^_Oyf=yH)JfER}pI-1%%0o&E zEnt&bDI0WDC8aQ*ZWa@d`mJ@SEG&U8!&$=@)3<@%+k{{w%R>X#q<%!=zbY@apd z@{Iq?wLabcP(Txd!y0gz#km)I)pQ)o{X8T0+|17QJK}ac>1PXbPoHA{)Zgn^JHQdk z2qNm6#0#hv1(6YW1!+VgIZD=|aa-$CF#G;zVrXuo=<{z`q$$G1dt$Foz0+)87YiLeo$hMnh6_IkW0mm z$^^$sT|_3)!vg41U!tLRVQ|@iestb5Tf)08rnt@?Z~SRhT)UsIHLg3edU+cdqI5HI zPk?|+Q@ZO1jHz-}W1UtDSafosiEF`l4DX>+k7MT#i&BJYh%jjFI3bNd2LI1WiZ@DZ z<^`qmaQF{5$83h%rFW8F$40b{Py>glkRf(3a;s7k?)h02xb-bMG#;VuI?^|~V9Bf_ z`<+kpJ$8`%%dnh_E5f~(GP+;caxS9alZ}#b(-5Ie>5QZUEi4Www}wZSpLkvMc9@Ab z7PM|O<(d#Ea6IZ2;ME2*pa!pJ8lDN0@HTu9##&DqcSu~MQuNj|8l=!QgBWK56|f%~ z$H8)+Ln{m?CMs}&Id>ihu?PkcXMuR?4KpwQ{ju2-w1tLNYt%^T&TD;b&Tm=jpc-k@ z1roe*d=lxd5YNeHPT#%4)9>bp{!h#b9)o4=HD<{y&6gmV9AEv)C|L{dyR}^vuG1s{ z+iH9buFFd{PAMtt6%f=q)A@$2BC&ZYzFHnz=?8>?NA46A${)SzP<00N?2osO^ee3% z=my(*)0BIQOwDQzmb6i|X^Pmj#_>VX>Xj`N=TYc#YztghnP;6bFRSiv!aG#;fkxCe+U zh(?oi3b<`rQTgJWVdTjS43nQ%`k3mSovw{?C$i*1XQ-f}m^XIfONci>X8fdwr@(aK zK267!RERn>ZiCk3p20XmY$m_Ko0yfOO2Nll9lwhuI-GEGM`f|Eprz}dZ5?licjjf} zTz(_2>z{GW+cknftq0nfclUsso}nsLLpLz@#R{H#MBhw+F=$Nm9p+odK~|spGuj~V zMlxE25gK!ifrKv9Q&;EHqkYM73%?^z6n#^LD!i$T9Ig}{l}lp(HR`7suDHwXi92R5 zqrfB~@U}PTYWQ{U;4!YtM;h7>#?*e%R_mKw?K>YGJFw6EGnd2!$+Tz|7QDQH4%8_Q ziykoK62;D=FU?-tBku~bOFAd~Lh8rBND+DZLt6%JfVvgDw@Ll0*pgai_!eYMNe$q~P_S0oGmzH&&Z_N3$_fQ8pvXzYyYs+5w^k9mgx+*2hLW@gwiLob+%}ExF zUgnqI{!oujG#7<2gR@u}vk^4Y*JU1_$ z8Ey^7z#Fnok#bJ>wIMnWgLfU>j5noXa;6f28DI9x_1=;DnUa*=qPP=Mp(l=6VqL74 z@XQQ6oYtC*1B+6zfxHr*iK!}9e8rJ+KzNl(Eq5aZAp~(*>GK2vfa2t3;BqkInUq&z zqE&gTUOS8+)oUXFE%Zv6Y@bGe0ck|_K1!FWio)38VMth})JTB%nTBF0x39Wli=Qv zcOP%aIq17uT>DjA*QEjN+}MvL1ttDvOq|vi7{tkq%Ub2-U8iKn!q#_!$2x~ZM{11K zH8ar%h#vtBr#ef+Q5YnB^rB{h@s-H7Qf?>!oKA?xYK3J# zJa5P^sqe9hyQ>Z#^(;gM~bMuV3?Kh0b86TXM`YFq}+!?0KL@6dj zRzarPkC^d6UNCj>)O^3PtbNKWqifYPUB0ZE$3*@KL5QseD4Qa5h%fzvF|{MGe_OOCaNJZp+teP66$>SMgi$#2NRHqolp;PL8 zo?=9HBXCBbaA}r24XA|%Ym`^Hax&XO;ueKx9(*m&1nZ_sD$s(ijNe74dRmx8=wM=_ zo>f2(C6V*r?cuG{bALP5+3ai0^@ew!^%eU5&1UH{bX9Snmio+(($gP{8WQcgt#Xi` z+Mgb8TFq264;-ATHffi4ifw#}ETvW$BY~=oN+~M z-Lcw^>9Uvyq98y6pUnD~SN(q>!9kw!>DNc%FEAxyD3N$V1=2k{kVi_}OO(IgWTkGG z^JpJ;3s!yF38gW58dsQDM-XxZ<{TiUM1a#ZggTJJ;V^BgqGZ~}H<4JNYlPTU+F~@y zu=8$5x9^<~|L9_Z)%1@s(KX)YY7n01hmqiITV!~TCjmh63k0YXW)U0WD$pc?E6pyz z_n|>NU*^d?3IH8U;e#J4CW=8`Ne5o3@L$6RCgB!i!FZVG9;>FNZvijGJD5byOGSt> zj>cbeQ!?M?^Cg~4>;c?)5FiM<9t4AxA)8>r85MP6#z%)AtPCC%BL9z!}n6CY16%Jc8+0 z0ZFQzgJqq&*AP(4QSoHW$TP)m#)|P&)O4V#DLeocr@m@gi^*lIeC6EVOOPY%1aa@X zhB@JJFNCm7ZB2;Zb1Y)?r4srDhmQ%{_rx(qH4eS_+MG4yh6~F+8MM10_H;_zl~7d( zq}{#VZxz%hMoYz;VpYi`Bx6AvZVYZsU_m*c)Kr`88Fl)Te&-Wm#U$U-&TG^mwIIsB z2vrzlDz)L~hQ}xPV$p$4OHW(2y6R$9Ji(O!Pugk~nFDW`rb23k+ctT^>RjI&j0JMk zT;5lqRi!rev{kkKP2ndLr9cId1y!zI0WBwXf~L zgfYJVX@QaJ_`V@G!7t$eVNr0!nO+_Cg0jS(4ttMuOSe=dg@BvcinOyL>KeKsHNt%~Za@eJh?QFd`s!RsvV(Nv2T(mZGRKq0lz+kek3(;Rs2__a{|iFumq=ado(doavxRf~dFo6sSKX__jf2WHJ&; z^(8{$sQEVK>iBp{70VJ!{nccKEOOxjhj0sK>VdlU7fWMdvjr#R@rdN#=HnRyL+Go9 zFGvN}?1`o0^8_cZp&0cp0B3mSL;&Im@Bl=Jv6FieFGN*_WLHnY$sPVleO`9U)~(O= z-bhfN`}5nrU*GPmIF>v6)PvuB9G@Ed-N&gTVqfzsOiNv8iN>MGs#3ELkm8mM{bt~% zNvm?%3RZ!7{nZ$&st{9>uT{rNmzzR0SO)pi}P^}DD4 zkxZU4`33%$Y?tT`$1Qb?7NZyEhcz@l&Rf&OC73Mg(0?4{2nT$kTzv8v5H(8#*v#qOI1z|M5N$y z^!h&5sy(d^J0pFl7Sp5zY1Fgd-?X_=vG-VqmC4*gqOHt4`xYpY7Z!cbB}!^mukUHl z1% zr7G>yPD74xZ&gvA9ox)c4=LvYXQgY`n}bW!)ygy*f?hm z*JbiDVUF5$WV^4W_c=;c?T!k@>`H4CIjg0tek7QkLNhl0{;Y#WjH};)ghrm(NR8^D zU|!|IVCedrWsJxOb?o8*c!LB@V_q~l(dN+d_wf`BPzO20aFJrM^y~M^#*!*PQvi13 z91eap@94epAFxZj^Vb2s&6vLBuCt^eGP;lWR`~v1+%?tW7)WU3@qAnekeEgolU!Gk zs4t$%qHsekw*HH1Uy&VLHHWCh0Z5hX7BDo;UTa@!-GXjkj$;)^F+UM$Ycy?VU! z*Y&Y?+}7K2=2bXcS|5JmK4lEBO5`q(r@&wTF@Y%eaG%c>vaDI1GhE^E#2+bn-BZQ4 zVm?%Bu6ZRuJ=8{RC*`KH6+k2_;A91}!a{Z$6L4KvV*Pe+m5b$8$2%_15&M!sYJhRA2;wf!t=M-U%#p!kjKE5D8PRP4>sTY`d%w*Oeuc!`|t(#$(A z*Zi^ft*mEGtog9<(iUO=Q{kO`y1OcKid)#1+5W}k#;Fm(_L83tAMP`-bz{EEny}=h zAR{&v2pARNQX6<9vTr+i1TAZ(+cK!WWJx_|O&RXn_@;YAfEL#8^2pHe_;$$nJ9v#H z*648Mrdry!%PGlcgZEQn8EdR20aUsqGW;QShpUL6W3J$#Ldvgt(|hFl18{B@OZ5Fx zp$A*q4<37zUExiWS{o-d1Lf*Xy@%4PDkS!-;;YF7nE&C7v3Qv~dk`O`GCA zxE1-k?wQV5m4o1Wm3qoFq`K(}0~AZT-k}AXTvPct?M%SP46&^jt)QYFl{O(uqd=1c z-Ewg{d!!SC&E9)a*X(=8>#pOHQW)sm3We-a9JT5pG)^WivbI@qh+G$xAU<14}W)!30K+nfIm|cYL%oljAUh-wcB6G5Q*w zAdEH+*^`IMZo6=6^yiBkV^-A|gLaH+N3x5J@%{b zUET}CoSOaZl*KQT18HNOgr~pFuKB1-8jqa9X}wUInl@>$pZXsu8syXYz@98DUeOWc z&(nkZ!IQBaeMWqZb$QD2sjlC#!)_cG?qRikJai%VN_ondfUtYLi4@gLi zkB=YPG>KVb!Ow3h&wC!=EaZ-bRiR?f-(-Qp_Qj8L4h2*`2&=3au^z{terwANkhbn~sT9LQbq ziR;JA&VzA3(|bV5!^bFfP`QmTcu~_EOAx44173+SwIrg&DgH>6az_p9TkYK$Z`6$4g3>PId54u zvAyvsnB4&ay@s1cu*FM4BCs@)y=Tj^k%v|FN}7;E!07zxx}A}cvF@mv_7UpDD8vC( zbvEaARdoI=T*xdb2>k1>gVr=+rYA`!ok=alm0~jghOGRdl&}1puth(3MMMR z;nB$sLCyDE4yT@lLU6&ztt#R!3W@U5tV!K}xK?H+7Mp+}b#Zz1KjzFYG1b7@9H?yW zI?oK$^11|ZYEQq{{>A#-qZ!QE`i!hp<@w%0bW~c@ogv2o?~QZuT&)@Fh>!jKMsQ40 ztm8I2k8}RY414hNYetepRf#F~h&l!|S1PS?Kl5zOgft6&-24m=AiWjCO5z9$#|>j7|sP+o^moBFK`IY zRPOpcA1KEyIsij1@tamy?>p}7e&C7!T<3kcqWRXw_I;A>ahnF5;agPDi@F+Qy}qX# zl*3Xnn)e;TAwCKpCiWa%82~K9&@-@94UQ(_<)}o)qLgLi9;aV(a(wD~$OHbVv{_Ho zBo<71h5ImLc0Ig7wq8|bGUQJBr?8Gv6Eb#U{N!5!h*-2{gVN}jN^70weD)ipc|X&x z;i-I`hg>aHji)Dfw@+?w%)MIKk>T6o8?-FHB=wKulb=+XJjchi-JBeox*s${YWrl> zG9_meWVgNef&gV5X{vD-uFf~A$Yp^{(pm+DP!hwc2z|gPLW^*#wjyIOc}l!I6^-%( zkz{j|+$|`UyAiRz=mXV0h8??it%(i=#fbWCwgpM?VZ*Ielj29kb#>m+Sy${En|ryd zGN zvv@~o*~w;XIAs^pIal2xC#i@;dWb;OMe#NzCbnF`S*x8$tPeDPdc z`{hbsuiW3J=hnR#HS{+7qD!eeH4rcZGFY&4(?Jv^&z$MshcNs(=iA0uVjj`#m$QK$U__59-p4_NJE~AVI1S1iT z0mNmrW9tZ1tDsVca_T6=#7%2u5^{T=h|#Xo&*67+7T9dH55-&YWa;p(nB-OUu;Z!O zgc>|%!c^5)M^RvbpGTf%*oQdk`%For5(%9LPmXW?UpNN<9d3eaG=Z)lm$+~QOzy^j z(05I(RTiL4G_rUyqndy=h;QgjpsVOR)tmAN6i1bLMbhKGdM4H4n!OWIGn8702vZ0# z3-*`>Tqrqz*Qf&_K>@b7E8m>$WUXI_@4~X~TYXI#o%i`pXLio({-&z0ZDWxCnWxXZ zHYi~Kj>P2n9dp;DIV0u1lk2dwBIFG1E$adV6rroOw>s@t>=6Dc{c`mzcDvS4ok(^h~ueAqXMIQ9llU7x<`v!1c7ZBjDm#L{CMXt zi;OHOHfrqpSIebC#a$y>8XHSCBqgO<%kA!Q${|b_k8hB5I~Du3fW>Z-JD+bkgKC*M zI}~)T<*A|INh+*KIN+}aBG&*Gf)U|Rus-Y(QDK$T@{QdaQ25vHSz;8rsmGA6Tkuia&!@J{sIeY<0try}bGf_Z zgCqMtNXs`KzuMRIpyh>G=U*H@y8k0b1M-PDB-iiEjhU||yZLXH-L#>gV8QoIO}(C4 z7E)f4x^?5ByMnG+V}z^0ib zIUj@cnU|EWL4it}ra?BOEk=d(kp?4)LWfEKLQ`FA3f=@8v5n-a&6Thy5oQ7GAX{%{ z7+xFS1FRlWbp*dCw}=7eDVX{fs41a!fns4o*KO94G2`0?uF0DipWHXDtLg2|?{NXO zS6gc9d>s|tw%U%FzQMlE7~fdmFDGZa-a9bV{@&V%#Mvv7l2@{r=gt!EM1)4fmZ{Ib zh~HSGu*Z}c64_|H+r&CRtDrm%YKww$Ot1oK-P1L}!>3AGR$}qAr0!_HkOkBme|oC4 zq=1hj^fEZ<|Iu_Na8Z}r{}%+MaRX%}lLRpnrx1%0Es{mhlxr+aS)yQ;XyJyILL!?B zI4)?3XqGH$4*4^(LNpL^!38PmwqUuSBZ%Hq94G-9nE(4c^zXi2x7cRB^E_w$oX@pMANtIXNda{Uf)OE_-G} z=bD?68>5n%R;8Q>t8gglGuct^{OpmVuCaFS;ACa!t8hc5gv&tx1{rCpAe@tf?aRUo zv0@StEux)qh%{<;!U zx#?R81JK(m#_{ium+ec(m&C4*8*}FRhbtrED`M_-JZZY}41Q79T9|nz)&e2GI-!U4fas=BL4{I&M|`qJz7xbHw;8 zwv6HwafZloPJMX9#H|L?d=7C2z%Y~smI?nkn|z4oDb#&NVFFxLc_-S6Zu})VSSfT( zXARHH3r3hP;9mP@$B@*ay9bTT?-b9Pt;R6O1a@K3$!tQ2MP+VuS!4uWQ<54rb5H^` z@qiKzXAlhwa42exH!dcsB)`UL19%86Vi;)zAD5R?aw8BJz8GJM4svE?k?j(6gn+7a z4||aSg-D)2xzfqa;nK+;dgteP{CA9<&JJ@qVq(V~pXAo4I+OKbt@V?X2Q^)zYb|fj z-3Hmp_0zYS=!s&u<=n?5XaR)Lggu#eJ@o*A8H3Oz77OiBtB6bD?W#UKpNL5~LD_)Tj}7mr8$)CXG$%2XuAyeyKUK=6AL-hZ^W zcI#i??HqEi<8#7>h=_JiV=4$Abez^76sJ|B2FMEP7nSWk68wsG8YJy)H{!-kJFoW? zMuohHn4HD#U-nTczDm}2T`ymPT@SRRi%F6pFWu1d!1o^w*}pkmGv$npX|T9s@Fjk( zhPHG^u#-aUd%hEU>$?S$%J->qoX~QFq@(DF!nOnvx!(7KU#11yXZma+hFI?q>}Bul z{+Oe-^>|r@%lw~g>N`7jyLH}LUfy!0>3;8&`@Jpa=jWOM;fJ?&wC-bgI@0tM&m^fo zq?APXSaJn2`V~C5jFl<$Y9t5pUJ@~_EC?VIF;5dTLX;AjQUL*iNtnZZ;Li_|r47?n zc$PmQ=-x2NXjBwN9D1c;2GH}gh8Y6$61(|HJcfZYCb92(QbYE%%A&od`7g8uGz^WY zd9mCa$8Ai|2e4p|q4Jo*NFGmeKEWr!OVB4CVI8$CaLdtdWDV1R7sAOzv~+We|CKkN zBJiYaWaX*ocoFZ7S0L7-AS~ga=n0XoL}2`f)Mh1n1e1_FU+`Q)M6nzK%i!}wrMiJE zZ4sBY>pJnAg5+U-h%;xz@zn2ETmA2xc=wzAx8I!8SbXm@hXSW3PcHgy+X&Z1ew|kT z|Gw4JHf{2A0~Yjn`N+{9!haqE0`2Tc04LmoiaLxr=mF{{ zo@Kx3m_lbHA4VnnXG>WC*`7t}(9!wh!5Kw>qWXA+Uot3~5JOvYS5hXweR3 z2WJqj0_lNJoQ^ZE=L1d-Tw3>HhR?Sb4jzkd+9GVDGiThF4_#c6KiBZcIAhyyMJeZ7 zlkcxC@BFOkev!Y`zIE_?&#S{VD{_Vq3F0bH4mA~BI9U=C5KXMA_=Yxh4N%`<2}wm3 z&$}Css-u2l%wn<9eC<0)k%G9uWDZ`J^xqHhm%+l{slLn%fEG9DSy50_$in^AQjB0i#h zbwOEv^Q=l{r780Tv7|fhCK<^wV}D?AvOeylBkQts*18zRt(=| z&L{Tw+_`OvE{klpr6+lsEh zUDPiw8A{mrHW{u;)QJoIUV2I5o6-x*wXGeWedp7Sj8xPWCVWfGC{cNMg%@1FWx{Q4}qJNtQ18smu~GD4{{3K=WcVu@Mq|X4sl1gT_?u1p}27 zL{!m~T(SJ}VqY{zM&4h}y?43w5ql@s*kJp2)?BeanYiv`_4lWrIaz%z_57m63mg`j zyE?)ww{oq!QY>dv>Mz7}J!`d4#k;!7t;ZUlZ}AB;%*;wxH^R+hwN#gnEWtX9bh9TE z`Bp>%5ixW(X&}Fitmk0JF~6ZaveZACKYg5 z_ZpM7V*F_rM>`N1ACWj!Bm&Lw zi#*0#)+m_6ZA!xgoaiw_Bf`YP&}bK)_=+S#{do?eRzZyUzDSc4s|da$PzXv)MAy22 zfH^yDz*kMr{~Wla`em=uh`ju=C-JQUaP!7^I!P4xM^>{__~7s{;(kO*@^+Fy@bxU- zOCwnqfpGJ1n{Anfx`B44V!5IN{+**!toyyq>%5cFYOEx@^b5pX3+t-SO3R<)GH&Ow z>kjtod%axgCban_+)0Jdv*Y)Izz33T%fnc{1`6cQxI~Xr?U2GX` z55gKo^7a?lVwDSm$|8Qo6%q>ILoY8*2iFvSUqt@EDY&AHRH=ERK12oVHpO3PvKj8B zoxgL)_S$_Nk@;g~;Y#&Sy-wc?3SYMBp9x33TW4QQv2LK2bT;XBsP$akYICRpb90zQ zRhs|PnZYk?Iu#XRep&dXO4?`XF}DDTx;^k0xO~P=?8S=V6u2)q#ENq226$UJO0aw_ zC0j%i-yl9G#}K-qYPr_!K+t~v*RZV$!;0)1+dRcliXDqI59HCgNVfwxA>JUmpm`wX zS*Zpw^Ix}bkDl04SZ4Ccoi?%pyBMB4Ne++AMNfdOLNJ`adbv6VGkepr~wY&zDK9XQZ1FfD*}@-KKTwJHAfITguSS5n{V<;-6@ z*FT>)eT%7I!z8Spz@Buuyb49;=()K5^hojGFG#}3S$K;p#efi>^T?2$_A2VV#PG`X z;brOC>+g>1ke&&Nbd)=gE@pf^Iz@IuzJ^(oA5#So=P6ngyFRos9#R0l#?lxS;7Ds3O0^xNuA!UC^YrP>0fDNIS z-nzul_aLfbpe2|H)g%AFJmy*Jkok|lNSKOFi&qa%#H{WmMBcjBL|g2nhP}j74IhJO zd+Kt7aS0Nlv4=*JJq4u~M!nuLJn8Q8+>TF^ZoZmWIe#JnW^Mg0+lGGGIe3H_<_O^Q zLX8N&I3m)jbyfQlye4ju2v!^j(tyPvd6J(>bk3>D@FdFe)F0*WMFYq~q#?X}R+m1Y z>_05_kVsaxZHsy#R2UorFHAp159=V|iAbd&wMrBXa5wlA?haaaPnYaayQ-hZoc*kg zp+YBW%O5=7)$DEk$UBKKL%9>J^UFT`V6S^{1Dmch>pVrTYGj;}NWV}i2s%`LgAzy& zVr$3as@%()vfN0<)auK$o;q!@1-pV zPy%^366>5C15`U_rPIX&qaGw5kN|s2=32)o0TUCfJ(DfJ);UZQn#~SYe(xt6sWYSCq;#z%?^R}paKl)^T*LQ_HWRAW-4)};KRQHg$j^UDpY$no!lTCvG~2go8q8dMUf z$*vnIiiM(KXA(A~rwgbd0#1s88Is=K2(lcxQNnZyTxAEg$_MFQ+HRPFS~Jzg;T%&v zTy;6l1_Mhra^0-;qmu5%#oVyA)85wA?B6+mV(ZzIlZ)1m$OEVJau<(4A)B|<+i#e#WKz7z>ADM*;ELDOe?I_ezs zt`Vc%!>?j}l`mAemJBn|@$^C$+BHNaf?^tg1ZaFzNv2-&USWe^A7XZ+C7l zm*V!3FFPpLk(w*i0f(Q1#0r!dpYow?$es-YMi+&6@iyqyjbfwc8%%-w-|F)ALru*k zF_u;3&A&+`*wv@$c1J47fZvRN zMO{oM)*D}S48Kf8PJ|UZn%N5G~cER~p>S8)$D?;_lH#&}j(T4S2;E=k#4VP(s5A8UIb%kA9c zJvAv^Ew0+u9lO^w&m=ECx8yYy3_Xz4W#f~SxVn6pL z>a1I&zr1)--_`?yEO-XQ_u3j$(Hd87J>_Qpttk1kC94}tEf$xGYq!g<^{|bPNK4yM zN1`-g`+)A)Ox9CfwhT2H}eTdbY` z)ZMSF=y)+13Lzt+te4;YME7DAuL@R!5fJ!D zf&>miOj#ixSK@x6U;)AiS5atkV>}%XeU#%#&XHtVKB(kJlsYBPU@F3^iF$c$!b7nu zxs%ByK+r_w!=#2pNQ1jcAdlX4w-}|L*m9eMASE-ktsmw*t6RXy(%6~v1@BW9Od2e` zaY&0eL5VGNOU_5(f{6Li;O;41jLsqdCR_}ZK}Y;L0Aax-dj{4FlvHKGEpvNR5R?+R^lA)pvcxW>P7(1*_y4x>?=pwAnOrK)rC4&UBT~8=Q+|x;{4K_O%>TP> z1HWD;L(0O-Xinrqk)lb8EC8lN(qab0o+4+&z9b0UmUR#Pj8c5doW1lNNW5pHfJRCK zP}ZlFXN=4D-GvHS$>Ht_c+;{pK7Cp1e$w@n(KQU$+ z-zYGZoBwLPmt^hmH&t}iT65HV=NIcb$5dE;TXpA*hljgv$%de{sa5b;wP*@rK!BAr zQvsZ0!R?2{eq%kP?f^K6i%@6_aT5{@2!m#esLKz5{|WhKm_IVL$`ebBMJ-w>7kPA+ zRNoXTO-7A#2qj}FWC@dILo@07dCnl++%n zO$fIsNHVJ7C-{;{JdC_!#ut*!sLYHiOv541iTk?ShY?LeFhGrAlyj7Wmz35eyKdmt z&_MGi)qU;lwLZRJ`=<;eBnYCPi*}b>4wl+0)7c%B?T1G547ITz;=V zdaWJ#_JzFs-&Q|ZUUaAKUH`dp9Saux=QT?~Q78;NY#~;N;bs)sDmMy*#0V5Y)nWhU z=M9+i$8k(B;wN~Ptn4D}0vtlxB3}B4ZpHN*S@gqqeC*#4)7jP3(rP{7f2}0>uadT0>w$`z>Ylcb*fs{%`Z$db zpH*vL^^2d3ZIi490|4#l^^nRqsSdEM0Q3C1m=^h$`E~pAGRYjkW@-1zG9gsc3l4V< z5UhlKlWMsaU@yTgRAMzn1ThbGM26j+E}T61!Aq~+|NFT+|5V?zHT69^c+#zfi3ub$@rAsivZ(slDw+m}g>94;xM@!02fW(SN_}*ZVU$5h;U6+q*tMtoD?5 zc&l_n&1qaQCyJm&JX7_f`_HQRRL&9RnVQFoy^}6Ge}6F@=xbcDv;SVQZHgj1yWW-f zR=lE+)npgE)M$wtBz?v*$&>|7Tqv)y0a9*2E@%z(kI2K7<)tvEpZfB+;{?6Cqxf{` z1WYV)-*uMDQHzX>;rdJ?0dM@B4-RjTJ<2sTHG~Rsqj3MExFZiwF+-pqyYb(%1pU$t zbC3PA!(A_Kj`QyNrPb zYWOpThHf7hdg0>K30zk54VEzLL&(v0yN$u|)K zW!{MeWUiZ(rGe2KBNFNIYoLpP{x7wWa zOgUlk+)Nq=2Wg|wVOsKDDnI>8&K~2)Ik66Zm_AH?@^s?;+Xrf-SE=Ezy0#}%EO(c* z(DdH(&mQ>+VbKncRIXySuKQx2NP=dBL_%DK)oQel>$jxS{@UQU)urmN7o$TuKncSq z2s@OJC{9LpDKJH~83KI4ddL5=ri9*I4wA7X41PlOs?TkLT~-+Lp9)NprT$Dy1`D6B;+*~mC9nAjA+bk`wavq#Re$ZE5Ps*=b zEY&S7KI&y$WpvU&cEsW)X@M8&E2=C@vD0UYAEEd=Z{_fvLxMe|+IoD44E=CO7^7SI zeZv&DlxnwR25eoeXv?wePU&b$NKTGTv$Y?n-P$8fv$d8qWn%DtQ!yV`YW6B_l(~~w zOFpB{eeCy~qa@|{%MydYScjx8Cx5|LeA!55jl`1HFsaK#3j^JcOsw2zD(1mtfH}*q zUas_J6zRb>$LxwnP@JwKMoS5kev{WT*t+>zpWI7>V%p07bos4$`MRpO(hC{JB(@Pi z8`vLdz98?7k(Ojs_;+!*nxl9(>F*Df#84UtFQbXfomC6NnVdRrGBG9ne|du`#`wkt z5R?i}RQ;~{Ft=<&r=}q$><4mR;Ucq{s>50Mj(6q0)NCh7%ocOQoHz&JO;E2~yj(gt zf*ekis*Z3UwDb0FSa(@3t6g{5n0z8^)7E_BczWZyl}%&HS(;Qm#clQJDJg$DS=(|g zZ(E_xA&m&= zZMUNRdRB6EN%=ocR!eKwNM%s3w5*%Bz$J6wx*i)7@?8gJG_$()QE9?Hg#Stq+dK*W zKV!{hk^vhQlAbiQ;*$q3@>Gqf6vu_g3tGi6Kte3Sl`=If&T==i=4HYeJQ3e%9mU$SP{R?Bwf`UaGx1UNb}#0w z6mf@ziK=FV*7MdG%GViO3T)8?PjA!w%HiIwrVz*BVkTEsw7RvWR6P8k%k1Ai%5rl_ zSC-{rUAt!CPhS%X>L8j7=th5WO~UO_&8UKAn?E67$Fd-1(wro)J=`NgjID-K+q_?T z>6t750(MhWi*(tk zp02TPII!KtwjlEBpwY^AjN0PXbw4BN zuP8Ul$E_>d|5sc7V^)ggnU;jqyn<6s%j}Gooww(H;c1i_PG|#^O<-9LE2FArFZFYQ zNxLy=J^`ju5FUXPh|X%^0PJJlfh6equ5m~v8BHyb7ad2%27HyM9(~+j7_8B+;j$7A z(Sbf;dv4@0>Z0bjiU)CBLhtBP@tx?_oy8pmE@csA2P}GOmqLWl*>Ae45#Niq}VE^z{}z`*}K7|wI}i{yKL&w6fyrSW$ZSff6a{Dr$%+l ztK*ZV*u#TmO8#(n*+n3Qdf{xVHZW8cj9sb-Ld=AYK!_?cy8z&@MFRZb8fnlXz$B;@ zcv7;qEv5r{gG&T4^eRI@Kp(B9_DoFYwcbhp0JKx0@ok56<9|P;%gy>?OnX(%=*)nv zo}NqBGDuYBM|3aQy?{Yxm^`Csy5m`4a7&&6jxHY#Z>=gs+=&c?=a(sq$*egA->4xw zM4i&I&OxD9`~v9=`34uN>^^~oOxx4yY}uX3*nqwty%D=!JF)%mLFJZ)qSW-mrKi8D z9FX$&i1JQCM4>|}5u!J(fpe!F%U}fNeNoGX)0q$Prhd( z<1+*bB;A#qfB~R_c_@*T@su)2Nev#Ca9_d4dSQz05@7x4ia&O|U9{0_g^O*rA+Bfl zsK6~NYibsn|4uO7zP$na8&$b#UAn+DcgFn4u2%7kY$cE8=u~Up<@ZJ#+cvote|74| z|1S5dSamiU`kV+?6q>5)7Hp+Zkey=@)hS}PZgPU;xYH_YU@#Ey z7ZqAO{uOx1$+WtZL6vnHUQ+lxM55ipwd5yMNAsJ9>$KJsqK)WR+Z*igp&VcN{dPa| zx*818&rdZ+Tb?KTLJdNQ)Xb!G0DCfKs7yw=f-OuZPGrQEiX+iJ844w^C~G;@x21|q z1c3H4r^_H(C^<2&BUyO_bsnXks)(UF>eE;=PmMt}W%pQ=H z*3OE?*5szvwqoy;4c;lYPxyBpunvvs`tKl}t?o>X=T$NRWR-YSJ#w5%q*UV}UKLFv zA;dLF@dl6vzlp3(;a6+$Q6kxZz<;XwjWVnhB$Y`TGX+mTU}(b;MN<>vz;n6eNh9;p zIYe8V!%wgBI$YhCT>s9*p0;bU^4Ga_ww zzOr(?8?u*qK$0buTxpQl7tocYO~x}K5ARJFHXwqHj;3G2{8c;43QQUCUMyC$n^8rq zED3TgFF*oa5P1Px{_mWM2H{OHeOZN(ETrPB^o|ba`plbr^BIXir8oa8NqgUBW?=V< zM)Y|6SZ>ww>%C{Y-LvL-jJT9-NP|G-de09}+v()2?QIH4Yu-63;Oa}>{%t1)wBJZ+ zS<*Jc`mj~Gu^3Bbis`*aE^dxL=efaUT~*i+Z73vPmH`E{Lu#qIANb2GCeO&%?Di~t^$da9}tKV z`B4<19>&1JgCYWTP#|LPiMD1YkH={xM2=}o+S7Kjv7p%_+Gu&ceG?)~I8Y+-9xs8* zRgcH`Qy~usk|G-Z!#NpKj!t7v-M~;KkHJV)9Ue+d>0Y%8VU?}yGSp3i$ndwC5uuNz)xOT5#g`Z((JR}U0!Rew||K{Ns5>cKAr{T*MhE~2!cuQfUdv>+}%t*R3mW=hj zJmY1II=QH<2b$zQ(Wk%9sUrwp&l5-Vj^Ipf|MC1b)N2)RtDQE6DN)2>9vue^%CHDxBnM$Otf3ESg{!??-*-#CG^l^4bCfr4U; znOK^;!Ej~c@f`-I_q%!2VxgQ?c@fyl?h{%6D`t_DiTq4>*4@Jh3CH+}sNael!rXh2 zwg5aBbgX3lGc3X4vyj%|s6Zr0?tC?|y@bj~d1w6)`T>#K#6?P10UjBRM$ocqw%sOE zU+0okwsk;>#KOcUl#l67t8%W4G=-mcjG!5=?LR_{q4%nfSBs+zK`fosy^~8YN@8E1 zNvU>Ko9<{!UEE(l`3|4RO13#%YuQ5v!j}>{(SODo!u(0@nzb; zz^z{measOqu266Ojf|vfA(3LY*z875SEW@g_9UhbQ1B^X0C)I8b*5zJhk!@$S;~P) zb_BqzvLcD*#4*aci3)>ZKtwxAdDUv`(4P86-F9~X0nfEE1|gBF(G9?(K>J}5<;oFxyr0BYHLz1VFYb|ojamg z5FjQ4ErR;^)?~0WM^P(u5Sfqx=Yn=37D!{+^NKHT%S4B((+X6}El-+mW>nvn8d^UQTs!<%>ZZjQ0jh-S)I8fl!B z*mb1)BU-0U%_U>B%8pzg7ml{F3#C~JQMu(wEca>XUd6DxydI}TXaJE>Z(>zwS zs=uS&{ahcL{2f>J?-=(ivEk7!{|DC&z1w1W`u|#Hw*JynH+JgS_)ph-diu3jbgxwI zYwir2HQ@=T1sUUf{@mH)@Xre#ePZ~#pVu`;-;OEi`tPaGtmj62um9raclTDxkY{r8 zEw+Ow0^w1~%Vc7aC?12OAkM2sq7|j_6rqKr6Kh8w12YSHIlDR?y|O;XJD15LWeK3c zL`gor0pL`SnWTqfWnn{d3_t1(i)4pJ>!5{UDN_PN8eq&y^!x3FXQ~!0Mp#@IVcDpI zm@50cFS9d_)-@p_XD_cG^6uMFV;v`Z%7}s$IWFGOQ0h-yJx|#BMu7XAsDLfIO?`_- zZ^?82YUP8jwW;>2=KDVApD-ySHX$)4`Qa~FZDF*6w%shb3Z9z)8}>qlX_S9Q=4{Wj z>-{GDXX}jgpoV^djuSK*3_dP|+m*zrY`U64M<6a+w~VgOZVIfyH}lPoai&p*`^&L;S@9^hvGGFLy3zZ6U2f%4VKM*(>M5PA=Oi1 z-)-Qrv6|*_lB+4B6Y+Ck0|2Pa1ZRagH=(0{4Ra_8aHMgi@?$OuBk z$jaJ)TA6dCnmLK6Br*bOzzrXhCkBv_09)Fi#0x_?;m1_(BjY_~Wvy^K*m0EpNb@vH zWVq+K;r68$Xq$WW>gv`@mJI9tl;$5d)|m!a@7GDb)KS(lckH7f@3g+MR&)B^z_BHP z_NMZc7hm*_E}L_6{fJYNX*laVY=E5{`BZxiwRX1kjluQUoqsz7#y{J?==@VQHs)FK z&e)qr!!z{(Te&pFKb0&n;W-~@0pp*3Vla^SiV6BZNsYE;zU{pB%29u>WsNH~21Z|A zd^tO-Y3V9+`ho0@)$O+$KC5`x)f+`^QmhgGj&J-MnRWS0Ye&JFX{pVI>dYX9u`Dg^ zr^^AcSWuDIsMM0rBsS$OCPKyOOy{GQFmisDd<^%g#LM z)mPjBgov`(Gzc+`^gj^#ONyqPZfoIi4TEOBo^Kih5b2|paYl2;I9&n#t|8G1Td zj(^j-e{=kJsr($fvUrxO(^g|<=8XDcL#^{#O}#cSGjr$hLBmfidSb4N&iO%k(%(Oy zO|o{n-M?4bWG?pa?3MChe#(#d#6kC3J<|evF?Y`?vj>y+$f`G{UzK(Q1P}XxT+vB@ zx(Jt%ymzn0aa(9wS%E`vnUnhxhsEsB83@(r>rJ_}WOq#PwsKgV7P zH%039$CRC*_&JO+ov_l0PP&U7j9exg5jGXNA|NC*F(4C}nt`$!gnTBoHnfVjfnKTR znkeQ#L_CqDym;aLahZ3<*p=R=D`}_q>f0Bp`+Dc@VYTNaYgNU=CDv3{wXf`vKhM|G z(N5=DINqeq-z;SE>HrHfr78ytWliQI5ZE63A+P7t6>!oSStA=p!W|TY1B~WMgAmh6 za-maXoO@>4G#|tjgDcz_Numfz2i`e(dF(|QqDnI#N}mcI80xjMV94U0KE6+We|_Ak zvu^(8>lrDhR+e|nE`M+!=EkME&i-ymZNquq9*JJj8?a_lB*t_p?wWW2!8vs@g`9Ib zdZ*Xr`1+mOX6L7dJBHhA8!^U%Mw%@e8#NK0(~#>GI0V>A$UTYQO25)GK4l5!^e{2W zlqAAKE9dzdYB5ZP3ybfZVFGgaS5LvKef(k3>Qt zT<9SSJWr3%vNl8LH_-F9RNY;%*Ys${kV4PQ5&a*x^H`CS;~hi&%JTS#r^ha9v47g{ zO4F@A0bLJ9`QPYlP5FLR)A6%2${#*o-ohxf>vfiX9K*$aj@cji1_#)L8&7XoE@LA%Suq<(! zi(};|A)?}ChK(*NRIf+ewXJGCOkGrEC@Yz8KTGyD*M#Y#o$jwFH6N?FX`kCMB|oz4 zwaOx4Qj@gH(4?uzF>dP+>XU=*r&-D~wt>h7Pp@7^=KC@Ni{kIkQtGY`rta{@fZ*cbv0uBhZ6KE1hIOpG^Ii!{ky* z_1{WQoUZh+*jE_Eu-nMlqMsUgWNFT>p}uprr?1Elcc3T}q8}6Gx9GCn>nn0rElv5Z z<^3!grkSd=%DSy+b?0BQiK}7FfQi={F66kHLz~y*+i3#^2$G;uKC2+BDwX9#!G>&X zdZyvr^Mmr&Xo9{Ud@mj87CzO2N%OW7v|}tVU1t&7ZVpwr;7qb6XDJjZNP_SsshLmK zyN{vWU4LhF;7`Lou`kn8)Nn9 z4u*QwCAAW#HxwxsvSvWnCs{EQBg#D7mz<8XIkO!5jVUHoLJ}9jTKn9z4RgEP$wTMx zn1i!)DkXerl2anoArV(NJj#$QVO!-TX}=Vsy*ah*HPI!ao&Z7}EUUJnkefvpggoo2 zweCc`;b66AM8bRj1wwTD|6!czVd!INJL`XQdG6Id6I-(;+t<^5^m4;50OgICXz>)% zSVX@RHEE(Q?(7nlkrDD3B{g3c?ZBEXv9mj;8a~CF@gP$aDM;{MsdWRM?KKDvsroJx z^#opu#w2r26t$ASfx~tdeymtZeo8qWo>+GkQ<-SVjU<1nk)#^|LMVw5U%2Hywx@k* zpOAAgDffqio6ksW{A^|0$tBit-d7V(R9HIe8dnUh@0U1F+A!zzBs{t}`_DH7^3@np z`@JXKBy{IvR{z-i-5M3RDkFVy_RVop2O`3fY5+b9|H#ytIMn*MQ00UkfO{0Q0)(14 zbE)y-YSW5UCl;+Ax1uTB(-{6!^bI15YxD=*^=x8JZ-2=)#H}%yH%&-L-2`SAx zjpGu!`iO#{C63&a^vvzLEb<{H5u}uSVdA2*l_z@3AGe|ROpGi{yd2^(^BmSQl}=VAbwYfEK;0i!P2jU+gn&s z!w(E~4-eIuDmS{Qwt#8}*&w^Ad8X46yKgVt3%UlNkvjFn;#=X77goRRyC?c;`-$Eu zKa@7D-YpJag3;!#4!8T}v)-eBx^DDLKA$lC>KCRNSS{5Ehl%a(r1}`ktCK@=+~!;0 zv4wM!c4O$4CA(!vh&1uhP9Y3AY3O47pHO&bhzCJ?kV8Z5_F6rN$;3VlT8N4Vng^&# zQ$+iL&zs+UjjmO5OnTLUQGgqUs>X@CU6Ss(2?pCf0YN51klt4ulK>KkCbk;DGJ;r) zXQ2DM*&t0@Bs5w?y#xeXHBDF|x3dRJo?8Nyjhf&zgXXfrkC1;Fq+NmE6eb7dmk%Jt zbcnuqWbzZ$up%_&ZUGgiB-zonDzRNcmc{P~s;Q`gf*$Q9iK6~2ur9VGgvX^ZyF~Ny zmR&>n_4M>fr+cS#4v*>hGq-tFMXPt)mAcN^SKD*zu7i}v>WBog~qJ}LNPNiuDYv53iWPAa7p{TY5n8@+J=^W{YNV{I={C*Esa(H-px^GG4 zVNB1<#Y^D%s3YlvFA_?T7F=8^!G?at-<#s+6d%IF+hpIq@O>%XAJy$W5F3_t)tQVfN5Lxbe36ctxI%dBp~;5^64 z7&>{fV&V743$y9eg&gm~)Zy=Zyw%xY@UnY)g^Na;78o3?pLVbBjnuD)mfkl{2}`@G}$%0wz2J7{-FH{ZN+FtqQzX#Hq@*h=iPRBKApmJEWRdrDMC!P z5`ptd8kk*z8geq)T(y)_Kg+q>bfK-%jzOxAlA^OrT!O|4dcxfv71nUqFeqBr=}0*$ zNGy>e;0@A^e#n zAoTBx1*%W;Sc2l@(l${mG_a#}JWgd+Md{Y(ykWPcz=2pU!M>EE@m)(z^ZE4uI@5B{%suPqEgz zw4W&{uO#Z&n&scM(^l*1I^4MP(%5NUxxos)lu(rzNrp|xTnwL{YtyHl7#yY7f7?fT z$3j9*d)h|Hxcy+`<(p?{))NOP(n6amW><0lDcf{6vX^Uc!Xwyo$AN7I+EDR@+m_h3O zDtLebEZ5_qruAWPcq$cw(PWkun4exSdoXhZQ6-jQZrL^5{)suRu~cw>WT3B8)9S_- zE#{oAwM&v(qROmY9c4-F&$y3uSnFsUAMO-r9!GtWK#H59yHq-uhWpz}UV!W-ei_t|Bbz5+dI|4=Yr3Kw#C~`+%2i0S zRY|WR)))J%w9x0&(8xRr64D(Y(rVC55D-l7nK>gU`Vl(~|LXtDg10wddpGPN^5U^>eQn<>oY(pz4KFy>u%WG+9R=MY3(Z_&J!{Rdc7&Bz*Os?WpV)S-=fvd1`919( zf8QZ3z$yMTzEO=smdf%{6myKs@u3}*%rl+*I?&ul6U=6m5PS2?O15WW!)mt+R>?3V zq3%H^=(h8iTtU>y?JyX~3kGW&DfA6uCjwkTNr@%N_m01MA%f5M)ljCORG+($!;B>| zH}L@s%eXT}GJgC$dLpsk6m>pemr`woQz_(=TX~cdRMLYrtbySwcM_)_%UX+b@jxDw?;l!ecAI5a-zhM zo-z!G&L~1`Wed4{H3?KkQvDK{xHxJ7DXv6My8_?xeNMN%)=)tfM-f#|y0EQkl)&by zF+h+a8M7ieJ1$}nFM9QJ!0!ke^uWYO#$Pob1c`xfx5W^1!1T=*ZQtridwz**e-4Zz zx8>sOiLHN>U0a@;?X!IMkeKTcY0auTRXU80(RwCEW<>TBE~mToL*Y>ba+rOZ_+T<@ z9ul6oPY$VYyyiI#Po=A>nd&92nCP`!8SPmz>;g|BTBfoI$<4`baBC7-N&S33S10C2 zvVEd74GmQMJu1yW1EO>K)qGBkkgon+*F~|2{Ai9=owyWI>m4(pjTg`L4RH6o_k9>a zOG=kZ%86e(H^_PuR`a469V@MUtfx|}MTzEhE=a7q{iR5!DXHh4qHOtVBxVTJ6|W}K zY^V%~XVLv_eS5%9F6jQ!6v zllune(Q$dziMJL%_^NKz{yn<#`rlvw;NwMqG*69TQk9UFC}zT;*x9@P60&A-_&|6D z8Nnjs3N$nAJ|#Qfu{Y1w<21wHF80f2zKsoxSBgNsn(YLl;Y&}4uE_C6M<;CeXGTnc zN<>wgR$Ulz(vXO^qa(&>S!6>sJfhDzXnQNuNC2^i*N;(wRVgJoem8lZ_&9`17X+jm z(gEDTvhw?+OMUIKSt2a!^xVJMtN-J{9>QGBZ+@5DgVm;=Q&y5kCEQu8;88K> z@}Z>gCP{9}$E=SLCtUQZIkGa86j&|)g8OPr_)^wip|j#=KCOx>2v7O~CQM^~^KN5X z7<9}xsYY9m>60GIw*I+2CNgn4q`Kp#KDigF#}~acEDQ5Gy6F6J**vBz9I=VBSwP zUPb`&Nlq!`F~v`48#!;|3Q#0z3lEt$c}v*DWAF;r8Lo_Xrr2ySFv(DVailxfdi3_# z5w&9$&02eMa9G%dRc=YATT||{o|7GdMJb(eNoVt)z@YM9&poYn)E#b`9?@0XZ_ zbz`z)3^S&+LOXv)X`wQtyr_1#1#;f^rjF@KT_F9GuugLLrz4^?o z9&qE{_u9Qs)ycZ*>Eu$H^57qTQ&C07OO(0aUNLg5yKQhoX00>jQ|hyf+7U-6JUGhc zi;+{zfWSd8I9XVv_GHP|;v1H0i@Uco%igZ#0^OQF;@>qi8VM=_^riUO(UFN$8-8&X zdm?TD`CcxR7>+_uU%GVIFl#|(-{pR8LY6D~WY_G)<}@O6(zF3YrhX5`T$47YZI=F5 zn|kIC2PD7BAF$a~=%Kv#0pULTagAbMxBKGHv!l|eTUAca7~&bd@k;7=L(J9^lV^B% z^v5!YhYpZQA-!>wnoI_8N%oFC<{t#zSDl19WlELI@g`gs5K0j?SyQOk)DoyolV&X% zB0+$}kg2g3m*`iM4nWMQG!6oyvfvSkkOvc6Ce#Z7U7KGwX1Xt_^e%LFe82JoFKwq~ zQrE-Mx`rEJ6K|qKrc=2o-%qjrFQ&P)qNCKZH|6gvXv=<+UGpw(rd=xEAw03VXXb%D z3tYBRYn3XS^eIVb$A7YLj_``ug>(IR@*rnE(A&acQTgEOfmDcO?1LlBTYJ0Hi@6nh zp3Udk5jY&W1x>WUdE49Rd!_=`~w5HW|JquL<>AE9e7cD9s1bUMBt;wUH0qf1Sp0oo%}|dwV(?fOfs*s{eCj z5VUGO`1YF|0DYA7q09d-7)CSsU_##AgUlIBf8k8C8A zziiagYK@J^iCr%famwTfO}>y<5~eoPLXG1p=?MJ9VEu@hh~?0S`2~C$e|@NJzo8aS zbLpw_`-zEc%gi3Hksr;VCMv0pP_`kK;MAn+4&qtJI?cH#TfT5+lSx`?gO?nOVRyPF zb4*5%xlch}*}bDIFS0w`bvVq#Xz=k`F^zH9V2lDT28e1aZVt8j$&SMpqlH#>Q#M)Uy3~Uq3ZO z8|t^{&J?$nD=aY2@^4>@$4PCi>lk9~Kq_M_8&gjBx1Y7{v_7b4_ZpUElX!Q-@q#(M zY*{Vq?(t>ZyDpC_-%qS9wp963??S(lQiot!A{!6ngxY) z;sY^;bhyQB6S@k2F>XcAN+*NY%!H=S0c|Z6P>mlH`(OVn#yXY71V8GRjC*4&m z6c*HwK|AYx5N*7*8p02?lW;)Y3&njXoiaoW?;t%57yG@TiA|WU{Pn*>PKco-3nIh( zVD@GGioIj(-dh^ndlREOjy_0d_B>BPBClFN|Mf>$0UR0DPOelXjbgTNzVulv<{@C( z2JPF?rigGjN!^Zlt#e!F#x4!M_8qUc%ckMaqcf-7G#gS=56mvNu5r8ngMa7XnD$de z_nDBfS;YzWPi1*}hC2)o056P=ETrTz0aw7_UiYh@fkOL|vOqzCm$SiFN3)%0bZL*# zqb0s9v`h;~WuXgOU{xvqUk-l727};BfbIHd=4P;STZ-l~yGOhcY%^77*G&v%k1oSY z0M5J%$r;rV>Rw=V5a=-5fEI=%x5w3eTo*fZdoaDKQ`}cfX_x@*_%498IHqdVGv}8Zun; zMqZG(@=C)5`%UAnItD)qh;Qz(`ATN$8`o}R`CrfQf9T{!%Sls(b(nwG`5Htww*~y! z-(1)EO-j`g>zcZozx9;}+i%#Z4_=B0TX?v3nXxTlY^KJRF}h0cOE+rHElw>{!${!! zbBr=nP3lY>zFJf3J-b$HK!iCGWLn(IrRJ!jVUF~%72i2iowsJtBjexq3ahPLeyp8>K~b@idSk;k zQWhz_5UOp0R85FRj$?Wqc~9v__Z>eX^t1Geguw~R)8L&nMweYSDbPF<^Y5Fzds^iZ zcHXffq7-~g{TV#cGNc5sMH~gDq=t%4bIJMq!$ZlgN`JMrZGH;R&XMUdf-0kInh_M$ zFvG8s@{nB+4Nj-S@vBn)LwO6tS|yrNz%NAT=t9DDi$4;g4gsYg>XPmpqw80?zvf87 zC{aMDFoNPw*EwgLtahK`{BoK0QuF&>O~;00Hp%dPjj34Rs=n<$1kVJ^h38!I@$czC3-zkA!D0HC=D$Y-TR(zhMSUe^V z%d_lA}N8HE@3JA1GXYr5z^7}ph7(5vNEc7zT6{Hha zf3>^H$hsiukK==C&#Fe;N@>9hCtKr_7-|P^{cEPdm*k8bo&P8FG&bD+?&9$t@pi;Y z@+gzYJz1}Eaq}czT@+B!@Rb@$M#~FJ>0jzEQq7{lS6b~Tj#l@*p+-6gO7QHte922; zp#sEhNq<8>tYr=rhPR{oYu8?x=W--(>2)2;QQVsMdw1Gc>nb{A>S9Wj-~kg8Kb*Ug zR#?Msi($Qkt$C(%ZlUQ|x~}nFV zmdr{t==0`CnHB)FAr$3l1TlpB7z>jAk3S-r`$c|JPd-))&{C$SwUz0q&2KXGXmeE^ z5m#dbuz+gvK!;il-j)Nmp5Jq@M@00;DC-1KqWL(kw&^mEk<3Xa!+sAM8SjL7l0`Iv zT9kk?fGm~COoCon~)RV=Gi6f^ofPMTomsJvVh7Ad^Rbc$o@^z%$sWyEM2+ct^Rlq^f|*BLHB=rdEX?Ib`mhmFS=oMrR`8>6rogt+jR(SPM-{?cVbJ6aJY(+GVB%YQ!VUE*q)TfYu02PQU@K zZ&JfgHe=v>F;D}t%)C~UIBL2VxJ$fN^zjcdjhKuhx7?R#7gT5VS0u8C(7swshQPk0 zKhmB`!bW2OB?N|2{A-`+xW^5XgsL4Zl1i0b9~;n3Va>~=n4@9|%^P};BU?+vgV7jSN)oAvJlNw+Vgd{5`UrGhd}${EVQ6H8r~&8cnja4#C-G32y&A=7!5kF?ve zsb~6_+R8eVT7W6`ae;9OXCy;Io43q`wr9x$;5Xs*tX~=YBVj6+qCQ`hoTZUu5 zN`C-x7CBhu0jez`$}v;+3ltE=U*?81RP2+nD9h=ztE1MeC-Ki%=HT!KfIy=F0>7rF zCXAx9k9drT43Gk$$v~T=FW{IVjNuJxZlZd|DE7s_8sV9SzZP#o4xn;<7)u&Pqw>o( z7ecu(iUeAF`(m*F$TEcylJLls5VU+B2a9Y-Gyw`Etu*84o8zCwQgsHg!+wEGiJiL&9v-FXqA;#Rse2S4nr9|AL+1Ua_z2Nv)&1<}QrIAM^6u(<%L|J9jqM z&#XY*tJc)q&c6>>>d*R9V%cr|*xKHjoR#t*%=*Y<#ja7X6c4YWn3jqrID0C2Lv}Hs z0R~^ayZd4hTvv9~Ux*qg$+ZM6k`u_x=*qXkvA@BU)%XAR+f&bV2meG|M5ah$1VanR zucQ4|56s$jFyWsBd_msa$<9X_Zr?t+t~I4=c*?^={x>e9(BS+}R@cr7%R!;Pw~r>; ze55oW_0*|T021vU0HjN9s~BWe_7$3T^ao<8g~+r=SkNc2nV%~*`$3&l=r zs>b^*N^0t0$3lg*w&Xayxju^ezf&LbWP*EhhP3MqVtV~Q!~f^GHAAoi+y(SV0SnXx;U>bH|D%p z^Dz+X!SOYNvZv3ZuQlQi-MDOni%A%7@qD4v9Vs3h=fyV{#U54kt1us*g>hcL&qPnO zm#V*_?jb6z>TI+Y;ZI#9t>#Y%7h-9Ssn{n}^Cl`O8xlpTXK3RP~J=M6-XH7tA zT>e0(o(2bvIUx4r$cnbgjIxLSTiM=T+SI()?fChut4V8UsqC8Hb;-YLL~`rFic{YJ z{rTm7qy4n;{LDVHXMH|)XM6h=2zZ#)&detyj!^Me_>r}SxU_<= zUo%2Rq98laQ4@DQWZppuQ)1UoZ`e`wdF0{3|0|ZRRKk`*!RzzCWKLAClqDUJluZY;A4p{jU3Eq~92Sw6t_m!wiRw z_Pvezg+8ZNxb}|oid|nk_)_8?j+9Y6tS%Mcer6voTs=wgU}Ob*6i=6>yW}Ukr}=pv zF?4jS;=Ep+uw_m0VBM7gqX*jftO-sZ<7~TrQEH|0lH>mj>Z+@-#C0(dxv4#)sqEww zx0f>P3a;A+x}5i}{M@`DpK7m7gl96&q}Hi^YHFv^L}s0b*3Fvqk@102)uUDQJm#DgOO=3torrg#E~=6j$A8muyCGqhfgay^2QTu} zJnYCguYiE}jBS28EwA_a*M&lrq-jHQPQ*9L{@l!E2j|9um8x6dabdARqZOwVRr&d{ z!i(6b8*>gGHS)Q&jfv0tEJTlWM_?d9QFt?s>yvXb9Gc50RQ#hy!d0wo|dxS{eOSqsI&gprF%v-PH;BfGQAP;bMT48Jqxcs z{^{BiEsZr3*04?`W~{54tqnfk*1zTFgn8oN@bCNtZ z2t#rG_&GQCtuGE9z!HOTG7{O?;}2B(u{|$!WFm=|W^OcSw*U51Uo%9#u|A&v_W`== zbNIDPjHUkNaoX&=eqo|$5-`u)C|~q~OJDuf??L;Se)a|mCpky#&Iu7*WBi`@AJ5PH z`gfbt6GkZL^}epx)!%D_XV%BMntk<7O<@$ir+HwlxueWVUDzc1jLg~y zt#;mv@Da45r2@0+YF7=<3Y4AjzP|V+2&B|rtG>XM^TX~ zOlK2r6IM{ECsHNVR|*;D(d0#a-+_waBi}T?ZHjaI zkp1QOKSxMjZs2reE%}=qZ(Vz*GVk|A7rTc!PupTzY8dXcqv%M`I6v!qw?})Dd>qo4 z@OLDFeyj3nXk&Bo^R=B>$=fISB^{v-4 zYy&rpc3kB=w%>}2?-VWKLBhxOb8vtCLV)YcF?}H@A677W=?YR7dp+L!uy#+Hp~Wpa zW7k&${I0!o@Z*9XeSY!t^LHQ9eaP}vJ^p86XzT6gjtO_K47PmpptAOO)${Dc=A5W_ z%tYI5*TG}@_463rv{jR)Dm--U`vK$hNjXLjy^G^LG)9b&9-x2{d_*}57F^QeVEPKN zThUCkgctaDIvLU_l%HhhmivL!7Lj- zDl?Tkd{NdT{nV-MTVB5E{ef{|gl^C@%i&X}-n`m!Z&Ik{1CKeG!B3vlU_wwDeE5pj z#^((1S^x61Ej?A?1)022f3;~sYm#Oi$?<_D;RlV|NC$Uuae)AOwg9M&m@_ruZZ2 z4k}v&PZ*dtD6&30VS2H-DX_oB$ux9puw|6l&hAUs4d(0VR=?d$NfNg?+tRkT^NzB8X z#uIn_mTYryaV@woTpm${Ts~0(Yh>K)X!d*+AU0%;IFmUE`wTq(CE@T9h1(QG<8W9=LhZWCG9^uCSCAN zd^Wi4v2XiO*n9?7w%`)ib@XFO?9dyPB4RuBG?vf^wc2NwQFf2j1&*2A7tmOVohqg#}0EOPUiHa6ayK-{A2(XG~e1F}k=&g**3iSaNu~ z!7yCQ2=Whfo4?w)D|OLJ*ZStT_FwTzMW6c*HZ|AnZn@8_xc^-dYt8R?{Gjzx^;5{^ zF`P9qiBcbrKYBr*Qm@tBbl9Ffk_I-Z_-7zeFLU4HQdYJTff`xuBK36hw7IoGOW{}X zsNNTVgGv~XUXoN%QEVTg2<3U#eD_sa6CuD}8DS%)Pjso%Ok5o!X`Vrc-gCu>pgoB3jNhK z02Hxyt&Fw6mtf6JIRBK1Sqhzsga=~uiGWvwHC-k7v0s_NNW9gkzHO^wfgZERUTxaxJ;;@x-nH9~k{bFCvkBOh2L`iI1fbC-n6+Mpy7Bk3yB| ze6037Kv~>@fHt+ZD{apcmLRR1D_>$+k882xhBEU~k>up}X|s@Tu++URT62kNjqrrc zDYg2lM<$88_FB(=db>NmY_m07;CvYeII9m3ibb$LC0%d}Lw|ygogh0#GvPRtLV^yvHWd!%&9dR!N>Ltj`%FP;M zhKJEbA2_aNHNDw`El>X_Z;`%@(+@t&j!k_2NBi33$0h9te1#)Bv~z=R>-tUuF6DsH z-fdHg3mHq74VP2CeHMb*RUvvRTQqOQ zVb&TWgRDq4>cs2bdQv7>Og1W)Kpbfdv&=d8ZkcJeLUNhwV7)MGrao{qCydPr;er4q z^|ZZb7!jBeAUEEdtVY6a_Uc?O@AVhn=BP8I$Ui zHf5UWdGKJ%&-tgXtc^~{k!f28iWHio5uZ<1#O9EHT=IBh&5^^@40^c8B ziFq18W}vL8J0hs{8=teYw6zF;g^HlQ>=K9EjT=B=OU1rOj$~08DnqWIZV!|yx|#@Y z-uAm{(eQCvzke1^%5d0HqL_6dOu{8Bk_-kxqqIOb+1Z3~45M^53kt=B@eO$UYXM7h z2kl&wIIBD=UqacS%yJ{EBjZ11_^wcR{tR~#irKro^iXjumF3Cau&ng(8XaeQ@& zL-*MSkH&Yj?-mR8q+wn5@rF+`k>mB(qt|_Nz2o2cEz6UimWVsqf;(B!e*Zy-XZ4eH zF-yz3r~0hl-12!-+At3fuW6G!78x~+k?`XWr;LIvPO1I2lnrp=KutqptFQmq?kg^m zPqKHud*+=W&O= zA#YcxDCe$)iXw%uw}jJ?hrs~ixX9Ir(F<5QFJuWhUGXK0tsd?@wq;~;-ast;d(p;S zI*&oN(@e8}>fM|-E-bFIefO!mXNK9Tf4<%Ki}y7d#`hNWbJmr$mOh9H;Ez zPA`15wdHfNqAlOiJCzevb&0HK3(XUMo%M#xjtc!;}iH zprc7rUjj@e`3UP4lbi=_AUKi0g)W<8q!h68RA7kPk-!2C;+ZN$Nj~KBF(bS#^imxe z>1bBl~I^K$tps7UME|I7b9^2YnX@2-~;jgetr%PT0?N{ zc|*_ef+LY0gJJ#@)f1d4DM_L)2{&#|_Y=!7uF9U2AmIiscD>xjl zCb)v54>CH)T>S^`szuw{`sbg*X_?RapY1SoDj9mzK6vyK`kE-$Enw?&`V z{DSG%xbtMmIksdc^SpKXz>Gi-qpm&q&$ItIxqErSdpCXZW9NR~V@gHJKXdl``gE?j zIi&PYr*FUcabTLwgd_j^cf)trhBl>Vr@wkD^=b21?fgU6iWV0XrVce`yy-c8W%iw) z?v6l-Vq1Tbu_zsXR9n_3cMf6JO?iU#(SY+0!}I!UO!I6nyq+_&X>WKOlPgM%m041R zL9h}gdXl?37=yiSx~bHxKP;N32AY)yEIknajUj?q?hj*qNs)E9`nvwx$oUZ`LhbIU z6N3C^IZ!2$o3=W*V~(-C*qAt18?(I@!sP7^InT)4h3jXjV@Ei}Mig4dt7C_2S6Fg7 zQwLUAi}m$eC+Nsf&Qd>-ns{R#$IVrr8zM*`Mz)8R&OcCrql-}#U!O8{)Xu`Dw~l0Us4ld`EvAh z?e15OSGNAXc=EdT4V_QRV_G^UG&J9WSQrY2&R060bhbFQGjjIz#;4bgxBfPv`r=XmOlWu)Em`O3BMA9i-> z{c=Ol>)c-FGw)0+GOzwlxA5&vZvDKX-Rc`AH$Q&717gSxF^LateB1t0*Lpju`rd@W z9UGaWxwifO@xd*>e|P@!SeF@vy%zXg-r$MVxn{mfY>E@z%URZn*Jc6agG@*HH(Q^_ zrV!4l^Mj=dugJW2^?rnGb+Ct@-#xmN2Dp2CVEqW2R%KuNlNvTd zYH>;SO^no!sd3TfI*fi-ck{QG-dR7|bNcl2XB>9gZ@4f@ZL98|d*Rr%qW<1BGyGl`7j?ouW)wwII64?zf26 z2q|7gfT>EZQl%TV)t2&(*_Pqj*zbTt)bWR4DaPmcMy1#8bhXoVCM*A}whapU(NLb~ zGa<3*Qs0MtV*6{JaIq3`&iQ*59hit_&NF?crKgqML9cn|)8(ltWVQuTJ>b7|u z9S=IPXv4de`26(Rga^NtTmFu%Jij`u;K=r3!RMECLtqrN{434yGN2GjhjA0lLC0A0 zzuZ!c`WfrLZ#q(Ou-CV}POf@wzU$Xty}PX9^jqsFz)k0H^WAc^I zq^A){k6U6M!h4k!oIUw&TT63g{MZ@py85{p_S@@A>YdRLsCID^q^QGqa2EIVoD~w! z2+Bpcu}^PH{j70fsnHKZ-01Rdp58o5lYEfm_P*6Z+`E^` zHWWbS!~!EEAY!+X7D+jhD-*y)ey+?sjEVEM_wd^UeRb!k^cuIovt$&(&EmQSBc|-M2XI&vSUi$iHdMp!b)7NRWR#a?{=;rSg{*JdLbT`5v;$La@TIr%s2-)_xmV`L^k8 zO!P$IUf_6=LyvjnVE4DKfe5w4x>JE`>R5Gk6i22JZT~XZv)Zu){%@#b!{;P6g`B)I zDu#vnI5?a0r$YfXL6i|TQ4gI%Qdh?aF-xv$~JK4*r z1gGg)^Y+d0^mDQg@L0C!<(1hblRkU=IR^bWUAVMJNOkk``)gZIp*B^|etq!puj;4$ z8=u`@+qNmWwYv2w&C>5=ZgL#-@cuf-gyth29z#kqoSa7bIc@g%(Qfqol+0xlhtJoH zu}QOc@Hbs{8giun!K3ygeX=S}r{7H(Q1MwuTHPSu-+tOt7ysyY=fNGnO`x{dw|RX` z$Ctv;(D>OS=fR&n8k;@nvmd_WKsIar$af1fQ@U+UQ>inyO^+KPM_$ARmfgz=OijNmEJESP16ruqdHsG5e^;8YALE1V_u8u zF^p4;PzDBT~pb-Fd1Ao0P;W*D!uw=M++ZC_jRS*#dV38{)m=Vl@FUHc7 zBM_HbYR=TPEnXdC%2kR6q?sZ}FP^9J3lZ6v4ZKO-DPALj^g=X6f7us7tN4hvH5eI0 z{%uLmg-WCnHcAyFT+& z*Un1;h4lF>J>fAitMGQwxo>P;D{h#xio%@>db#Q@$LT7^o&C%CeC6$nlkR@i`ln^_ zNWv9~5UDK==Rga$ML4OHDKNLouJ)C=AyUw^J;g9{ zNm;ssX`f1xUpT8U=gozS=Iu=PbdgdZ(M6ya%O$JJLQOHv(DR71B&R)#()+jqL;x(o zK1w={MBJ4tFJcO52l;Q+iB{{*4Uul+W9;;^z(pCln{w^12JBmS0$;TQix<@Nb)^Cx zemS(@+M?|vSGq`kY~#xrZ;EsO(!t?+TA!7%j>9WXoH$hJXZiEyYZAw_!O!P05Fdbs6HwVl>i#; zpU8VrkX?5eH}SfnCwMOeKRhlUt^K;zwa6aT&JR`&Y!6hFo2v}ZX0Esx zRh6g2L>PeN(b04L#DXEexlqn1aZor135Bck!5pLr?~^&uaxWx8xS68W91(EGz`UFK z`w@z?U+%3rSus0qe}zChhG``SHhs;J6O9w%lyYldmqVVYC9jQ?uty#MpeKH5W|LS- z$`2&1tDmLB&q~buwh~br6_r!&pY`M=CGN(`zYPQuh218V5=z|(!kEWemq{D9xXiMn z72tGpPI?HXU^{x9_K>-=4)Z%SqAm*rfGB*s!+ovzaHP^@o zFEjl^yE1$IOZ_uXyrN93Fc%LO%Ii#5FAgj3z7$>GVEyx^_wLCF-`^b(+V(41T+$mv zP|vpu4m?ilWA*djhYs>hy8K;9XoB_60mqN`^q!LH>9@)TiAmAt*|O4MKd}pnss%N1 zQ1%9q0(eG*ZkD(ozy}>0RiGxR9w93k4yrT%~&12$sLOXQrNi}Uvp>?dhM(IPtZ z_R0~2cg=5o)hC8}sf|u<=WS9A7p^pIEb}=Nl=&w4j)3B{MI&w4^Y?MU{+r)4jl1yu zuuHwXYusv_Y(tVlY00x|GN*l9xOvXyGk4F2wqC4D`oZ^kOSQE+`C)gZCE-@%XXjd$ z*X`QugkRQ}2A&(PBB^j$`M**ss>iM<;bGVE zO_Vb_CAb3ENmd{_7?nFp)_HZAITL=OSz1l`x!IPIdmmAqK9z?hSO0AKJMdTLkwah%RwRYz}Yoe;;q#6HVY+YOftb-^?i(*xT66Y%k zX_RZGzrKi_9xiLjJ5Bf{NM0!sxE5{+@;hCmFvtPoKh zK{$FZKzp)UfD_F?D_4TPrT8q!bnKcL?t2m_l?(K;yeO5YV4ftAOEoDz?y;nU*SPhxQTIEyXk^8Y zc9!szUUS^P@2&P4se&jh#bA}$AaZDSl=4mzQMtSB>|Gr$8P65kTVU>pt}1BRKs9B& zVNU)(^;VBK5uuwAR*-gNX4sUVxDn=AVFkT*+K-!ZaRxQS4uLOvLIQL)a(9$yC*@TSp@PT30<6FOfu7QsrVGA*Q%n@cl5`vAwNd zYW~%GrtRpUr6aZTZ2ez6KX!D}Z>#;$W1XJUEvg@vMr^hF{<6+F&Sgz~_~H4Q-_1P~ z1QT|1pGU>~{u2P}X47k4D;*~@jU=i4?%i&*36Fk{z$-ee8j?oU zno4CU2`p?Kif59u%y%+ZphlPipjc5jo~g<(vskPz;OKZM45;#oBqz*z@k&Zk2Ss59 zZizW`bAV+XA_mkV&W{h$Xfz#j+WkqirQUGZ-Z}g_t3fn}?Pv{!I&XZ(@+&CB7~gud+=4bWBs9N*!d9G^AOG;Sjp!Z0nf2(}>L0FGJ@%Y1!S&d#g)h&~ zY;s?eb?0$KQ^i{qZ}rK#yTfN=v)9Umr}tjS9_;(ue||0z_Uv1Cciervqq#2T;h$}< zc}3SFk`W)iIK$s5%&9=73iqR5CauViWW(9`AYE9Jm$|sWtViU@1_=x(?Fue9y(iir zR#&8${7>I4WqqjB-e$-vTJ*Am{f>VGTE0x17TC|bX1Feuat@Z_#c%CY{-*wgy|x<` zZ_JvbQaOA-V_KjB73KQuk#Yoblp1hCThbVKrCke8&G$6c*ViAq(i@OL&S-w_fTYl9 zD9x|E)1K-#Iw+_>Ai{jT+nOJHYeWo>(A*>lbk%Dg7z^Ju6q9j^a|2MaemVv2OI9YK zaG2fsj|htMsCZo4em=P)Bl&5EZwnqO7WfcjA?Nu^H{j zCVJ%ft&()~(50G5ZoC-N7QXBd{clxdn*${IBnd#t#t7`5#eN~eWJL)DuIndjC%^@>fdgA1@|{0Cs^d zj>w6!-S|xVZ5d8TGLD36JaJbRmPmjD0e-~SnEoK+$=6Gni-2P#e?K4hPQ7YYZPRLD zC01@$+BuX!LZUejUEuhw!_K+;`?-#lSdO~6EjzW*As!x1y0Fx*RQD!sbUmLMlK437 zd}#h*vI^n;}u-pZKzf1E!% zX1m9@IJX*Gk5}#8HTJg7*O&CWr^Xno_UP>|Bxk;Pc(b3&+C{xGqIMLmZp2=SW=pC% zToRee0t=k`h$x7ZHA(?iqp~u?uv684w5yBS1vi9U`(@0~%V$HZ|LQzHsD9=9JEHAA zS+Jsd?vv#ASB}{@b=n6%9y(e0{^Y6i?Du~D-Ojt4e<}TR)b_c>eQ)0x{QB~AgMB}j zzxwJ5)1b~^_J%|4N?Uu6-)~Q&&+5X;z|j%uPFZ zaw3zLWZnW9IOqxw>y0S^b$#rH@rE^ zDn1#xYKdEK8Ou%EkitX0^4#hLnSEOBbic0dd=}ODy;pKuNYdl)@>^Ux|E`Ajx#RQd z&X~?Ku}LlSKH2l`R6RTr4#U(AT5V(h+EHO`f!iCm3il8rY&k_Og+@hbpOWsum0G%` zmSaX~kK?N&>xJ+`;mZ;Z6&l*5r%665j6;p2JWd1>&Z=jk2(jzWg71*>1y2!~~BSc%(-^tda{NBKT#J<%- zHMQ5>-2-8K+`se4x|B^zPD%k$|%Vio{~O0uDDK!U{<45(j81IrSM>Owd~SJNu?{Q zB)s<0Tu$a#q5oM)EuI2h$%^TBZ+6o3Fe^kTkOR%_8Zq}@FtcE# zmd^Qb%NNB3j85)pySCKc{!qrym6rX+cbBa73Do!QtJiwyRuX`hec@m`HSg@!+@YIP zKZK>w*X9gCo=g{$cDF7TW;%e;1MiwHmL(a`T)%ANt;?%UeVZwrPBuf*%!^xh1f;fp z4P}nia+iVFJ!3xmyK7F%%wl(CyxOj_&i(b16I;*RKjP);>|H}k{>O2D1;`w1L+yye z*X47o;$5m?z2p3>bE9wDHs3qtNsregwO#7GO623)aw~iAO*Bn#cSL8q8`#a>YXF= zpifK6QKe2dn7^>TX?;6!Gjd*Leqx&0HX57-gP3TIdO}X;G;L>0Y3pQK)9F3546~~p zJgv&v);XrX)0p<(H?^I~PTKO0wjsi!!NUcVCQZ3HUP2s&GR zGDAi9kFwyTZX#KYAks$fiSav5KQmQqZC@kPmKg~VQl6u(g>88`Y}mC=r1(G?(>bMS zn}d@h^vI%h_-2DLdXxiYnVX@MZ81Kt^3ks%DDo$N||NE0lKlC`2o-v z(IhKfy;-b^^!^h0G;s71-_;N-t?dfUT_3gWR&-NTjAzI=H)@J0)g0+k9^fC;gypQ+ zy1iTac9olpZC{6bIh9`;Qk+$vEMDt#tT-S&ewcff1`ZWxjmFN_^2L@3mZ^3@fk92X z=-4#d`}cV5)z>q07e+Pd4ySYr%ZSw%B)U6b7Swet8cm_IEeEOSWBW)YPZX{nIy6}i zrPrUB!e$E5NZ^IAFw5w7x6}5z$MmOAGVwon;~Nr$UVXj$G@Vb1C|#ibCOTL zY~P$GJR8E(>Zy$p&Qir_J{jquOUgFLT*!P5bC3>2fb~_G=!0XJ#Yf#3UQn-1G8#Hv zESnPtk81nL(V9B=x#nc$|HEN^8ER{bAK=1I#ELlNpktNN}Db6W>52jfhnhb;G2`Wk}ODe-sg>`Iq;*hL!iotHEX25v`&POVm?1?|ygsq72J<7l&2m$}21Sq5 z!pHEDY~t>}V3(ltK6`*JV}g3##7bt7HhARQYlo?ix|9cYzlZtf;ewCB`>6MGvUSk* zadyfGRo$~U4lD56w5zc76@T3et5VGUe!8(BaHaPAv6O<(*ITDgUaqiFzO>cGY;^K* z3DlVTrQ0SH!&&FB`a&6tauaTgNwP<~A!Uh3phO*d3tXHsQ{r;(HyDD5%?j!@O0-zL z-c9{ixPL)+@7_P*K%EZ*bwg57{snLxWS{T8U2qJ{jikb$7Nok?&somBcrwL0U?9TuJh~U_L5}kF+RKVze~HDzi^o5Z#nwqa%Z~%F{9l5{9I^3^ARyOFvdqnwDy?a z5`tEYurVdkJ%0~|Ww}=24#W2)?6K|^XO2~6<}U*M>|$~cjJlLS&;tHoWMfFv6WWFl z={gc$9WSZtwnwRK#77JDd^YNG(A)^~+pgNdw19t+xQRrU@6$brw}X8%`o{L|;q;Qm z5M}vrZ9=G!HECroV{YxtrZtEpphfvoK8HDiK7}%}1Ua!6^DYR?!JJc|xiAEO|6rg2 zVrL{86oQclf^vvS!W&wSO{muFI4rI**R!_S=vF&WsGOU2!73qw%d;xcOx`$a@gV@lF|p~qwOPk=g@mP=*JZChJSzv9nyeL zc78H-uh9glMy>OEjm4@(h%xm$gTa{ z1s&49p`Y|~VUb^uU6E}-ppZwVbn|f#cQ<)&(FVI7fqq)EeVV_I9ccuo~rCO#26_`)%*!f5L`C*R&Uejpp z-Oqd2T>sdglakR80b8x#b1u~S@ZV9tA0n*>(}>bCSQ&L_AQPRt=Qw;K%nz{1(v7WT zMsT<&h)kNU2doIJ$AZJafC8{25KOQ+2omm)SVWGuZgq`|{V?R*rH|t#u0Qi{A(HQD z88Tcn=V7<4hInyXOuH&g| z`_4{lNqbvN`>@Wtv7I|&o?j}z`NbC#+_MVog?qxQWH0k0hu0t$AUCmIn34)zg+?V|C7k3_T5G!QI{ z_h(C3SxBdZ3RPj!!$U}Wnb_Dmy^B`k2X3KZx_^zjF?Qk z&-$Ry`l8K=$Vm}xUm^nQ@En%s_qMfuR#;GZGUdN0zx!s;)P=D2-MoYF2ZN^YHQ;!N zH<14lAz48>XW}SChaxLWiUfXR$0zuwPEQQ?Pjn~4(J$B z!V3#fYMUViM_zMK)sC=i);UYB3WO!z>%aH>CA^@hVD+e~eU)L+7e9CLXe`nR|8#n- zHw=8Q4B0%w-+qH1py-~i!NUW8D8oj@6@V6nc$<0{VdxSrB*IEQ17jJ4;_y0d++}m` z#uuUYRyYLu2}zAjdWL2CO0|oNjrQDi*dtxx0kJVi-&5G(b07JA<J`I%(Ngo3`i^ z6BF*lbo|{2^M6NUI|o2^$<~t25y_8}pW7urS({u}{rpyJH|NdlcY}xZBN*0FYs)mz zb{@SmjK8na`_V`Rl0fPo2w)MEPSg&9CQRf6>J!%{CRISc1!x56tIXTF5i3@dCT5X; zu&xtf*;Kpk4G&DHQ~n;#GxU?7iR(+OZ1PrTe3=_yVcaX0v9@`$bzhNLVhCgF5WzN0 zk%>bmzpzE9wKgGtdtNwqUEeV@vVEOL`CjwDuu2CHkJgXcfA1+lLBSocI9IJhQu!NS zPumF>_0A!gC8`xB`Xm&H2U-`C?P0@v*}2>b@0FH;p#o~xJ7_fxxdS8`289CS-L&~F z{~awKKxE?$4U?{I<>7(IH_@8qGld%!*DZ(r--!+g5b7e(;)23BqlC#5_#9#^s0*(~g(dxlwR$`+bm*(01YSn4!VKS6rL>j@ChsNKcd~^)kcZ zgnb;dFCK!k=a2C<0=VFF<3Nr1sjv+xDuS2imjp}8dNge-Fwe#H6j)WPhZ}yyuj%gj zZ1E{vWK%7%fHuod_{;8&wXIBSzi{c2DJtf(bT6x=Y3TK%NMFc>6zi>QulBpX&%KkM zXLjc1w+l~FdD7LGPP1!!jc-T4>gQL29hPw#*0jP-W*P!#H+dVkdjQi(l*rP@R83FL_wAkMBsKRlR$7!2FmMKfs)T<~y!V*pVEbyvxJy1=Z z{JDd=tuMCIF!=eqgIj-cd{xsJjEhmmHY)__g5DFj&Tc{E;8KE2`Mi?20=2Cq;07g; zi-TiHILGf7G1Vwc*K30V)LY?}X8}zE!!T3`lo0s{aTeztM99KfDaj)qF~XyY0Fn+l z!MSy;7YQ3V!4>=usuDc1oXGh90%+xzC>ve~RgoN&qy&T(5Pys}Sr*u{_Z-YDEexnv z`me?S&ZV42`=QWV>I)_!2$uu`tsIUQ$`VS~zIR*}G#! z_av9p%f;6I)$2rJh5tC)t@H7%?DoGJpTCmawk_XUlKi+?G^4ihd0lmLOfu=wgkdie zII?*PxKJ`XEN}*!FJUA%g@c#^4g&m0@^dm_6eR)zuY~xCuJX-z4n%-SUgleYh4I50 zOZcsJw>oy5f!DP$M+Q2*fvXX?gu_tSsKYJ8Na7%;1H&vY5+dS!8ofGnU9##JSUeBXY2ZDC9d9hK$kf(n$w|2VLmv z1*d?%u-M?1>Ag$`QZlv-^#ePJ1cj>h@)my6#jGqec?1o#$Xq8w6>WFUGgju*Y5Sz7 z?Yc9YP_}JqqAO}GNomDgV>Ez7VM+jkwt#bR;LHKQXU0pEj&@*HME&Fj+|=g|ctl?$ zyQIJp(|0PvYg5@raT!YdbEH(&YJ^Tyo4r{)!D`dpbCzGnN^w%WQr%~$XBbaPu~v*Gfw z=iMn99G&y=RXCu8TFAjp?c|hhC|*5j#+ks485^amtMs6O#?8y^{7|8eDXynZL=Gx*N==p z*0g-&6#sOSW8UU1Cw^^haUI-d&hETep7eZ0$F1bf;EwFhmQHg_(xb&mEw>sirt<^s z@<#aWoA|v=R$ox%eej3=pz_F6jFhatD#>jMMJ&^#4?Zl8OllAhj|I^(U5(Dh!}_Z} zdJnH9S9<@ER};>?htv4y5raX4{YF|#LoDvM4{2wc!V~m|4X%Z&uKLrJO{?o%Eo0AV zcGdET`{T(e4J>UPZt6AH)X4P1b^0pLs?D$@>f0B<>O0%i7|`_KTfdI}*3%FE?B4in zmV?VxXFBmrWOuvn%Cx`LB_|Y%&NtEOYCOnDEp7mqN1&o@*t0s6{j87h3rk(t?QSG2 zfP-?1SBK4QrXEDrx_EW@qSCxIV!0yYq=-R&sWxS%bmt8k&1}=xFXX_@0I_|>$vA%V zN8b8c8ow^Dy1Gyho7D82W=lfIB+KmJIdJpItBkbg6k{4|2r^iCeBcxP>>$&pyHk5? zX?c{orVlsAC}6dnjRP_lg1j9>2#)YRu3xj?I0r#wzt`7>ht2h_2|p2aSg!~vyQ0Vf z6g(0fnTu9mEi|vbx-jw}Gg1{npg>k07;>vehqRu~=SDRJAR29YtYrup=%0eqrkD}(#CB!f1Z|++WA{}h0XouY>wE4iHO7|`JKCB@=aiI; zfJji6kd*Y>C#MRncWQbfLkfI*B74EFyDF{FPKTV=C9loBv9X`>flRL|zq3Q-UbV6Z zOp;!N3~^M#b}Z{*_;>GL-P5|$S6EWw_`ClGztj;EyC*;WIlm3^vKQ&U32i&;+v%PE z{E{P70?DQaovp!>lM?s6x_7fVGRV-Zq3&Ovo|UP6OPwREV^{AGqv;WLpG}d6WtrGA zF`-p6nHUu*vaoYap96~&f(gWZow_#W!XCpqZb9^Ut_<&7C%`LquU|qWWG4xFGLGoO zI)tCmh4DkR2U;`dkcesO}|)byFn>e4xOwLA44YmMnjv0Vbx2c=Q^;uD4(Rpf$SSkrSA8bE^$Kw#^87?!e_&$ss!)!*_`N#6 zsOOf<+VIkfD9Uv7I|A5x9w+=TmlRXLe@@GW;a@775#Tx6C))djD}3)dk>V32maC;~BWh?7<$MWi+XzEZh3NB=>ndp5mB}K4_nC4r5 z{Q!T$9KN0~gdU)8*7!wBxotgZn1AAUjPiB6xcRS{eLGMV7*PU5zR}F_3I60yG%E!AS)p!$`3<<-ua68?t&s*5wU`T9+$MAx>X2pr@nt^7-b0$?Y|RNtGsD zb)5XnHR<8a zj#6|^Ovq^Q!|NCjuUABa9^S)zMBE=6Crfpe<`+y!)k>o-I-_=f>vmp425hV|wL6HC zi-pRNv1O5mIV;-3!m>rgbzqkVBdfO7t`$+ilpFn0^zEGsOpW{bm9~>xXq(si=+Qm$ zbNALYUPO%7$~;1$?7$@yn0$%Xob&(&&B*GHhtHftyl%PgXM^eDqUKXhtEbk^rIEv5n|cTwHR;{6?b(Z z-K4j^5ShTFFo=y!Lti4%9IE8!Xje!NqFPA9#2~R$+K_WYZMdq4tMCUzTC}xXH zUte!DLW&y-_(Dz~@_1yth`=*y29RZRf7eGTu%#kLR|WJe(W``%Z2d+}W1jUPrNSK2shg%liRa9Z{IyXP`&No<|ua$Z3s@EJ2>)RtVYlD(ks1pf+AVH1Bl<{LsBvtDTaqm zZH2W|D<--g)`Ek(QvAu+v5Py+k!O7b@v&618R%@7fXyTc61ue1rCiIbcTBh#7je>q zP@G5v*;9*45048iR^5wW+uEZ9EwKZd$w6(o`u6HV^IO^nh)i;*O^*vBN)sPc{{~z+ z6)ygJ*MR$qND0JKWDJd4wlP?JAfSoS0@NulDAnh!>2mt6>ZbIAiUg+xq*`4SOrStg zG(!67Q4oloRwJKtwJ&S%K-?q?EE444M5To*uvhK{SIljq;g^JXlr>>HwFfBt7S-ZH z06rEevv!n!`nv||$-h}cGUh~7>gq{G4;QY+a-tGGn?M4PRJvXDxy(9a5oD_?g}((% z1%S|dibIA}5bv;SfOzA}tB_IlrIe@GEg*wlxfG8WKxLFAr<*@t0tP(sO9A+CYa}j2 zfD!s(U>GA9gn!Em>v&Flski0PxluU=uaQnO11mS1XBPI{ZVpPxTQe~@>mO^*+{vRJ z|HA1##sblxy1Kf=)`zL+pkiO?7SLYx5nqS&^CG4Q(;^rL|Hcv_(;zc(=uG!UhSv09 z|+;^0IQs|h6?d_MX7hLm`&&DP_n@15sOv|3Zof~M2@7aED zgfHh-o!#@3En9DW^ws7$MLq4ECV7+6}u^l6U?$B5nM7|W~ zAYGyH+0Z0J;w;f8cB-=A>n7d4XvB-H>|Rz_&Dgj#?ybGBCCB*lsI z4RRNf+#oChog0xyvsY2;mt4OBB@M>+$hI)}kWFpoz)JGQ*u~kVw{iq;@Gj&I!y|58 zCUt^ir4>`1VMxhH;o;fW*??IGtGj3cVz5FSDzFW(AT2YPHL(tan#J>YUg0%j=9QO0O~eK~F;?GkSZrVdmVoD8m6f-~LxO~luil9kXmJ6rBU=k{QI1lv zAM&kT5fa`=ph33mD*jYmj?g)%Awz35*!Or_Wp&r!prP(|Yx>WmxIcdAyY)>wG{=Pq zF9^pZ7W1HhVeK`J0^>pfZ9ouxCA}&bV~o(LUyXAlk*HV#iMAxMDvKsn*zO(w%h1`m zsI3Tx;=}O-nn|1XY_ZL2IrnpF_FFBMPwuwVF+d7lUs*&obPNMS>5_? zueRrdJ1&4UG&XTYKaTzJ{JDN}5nY^{g7>0LUk_k`xV*@&yqI3Q?yApxX;Fjjt6)bymR6}gh=A1BU(>8I|uE7 zfTh@83J$dSVkH##K=X~vt|z`!f;??ZzXDIQ7jV`eXYKpc`<8j_mbm}ab4R;{jz*?n)R zpNL@cxex`v5cv^f5?F(oJk|Ml6oE`jOFhOpPqDw~EJ*CFoC$|jU#AB6FD*s-AD}!S zNsy30U@{9J%(>V!MefX&u2T(e>p>JLQe&@RIo&~Pr7TaFIo z>C`cs&0c|aDwTQE5&ib+j0rY{y2||n58N9?pw$oO5ouk#5MyVlh0D)j@Bm~D*sE}g(Hk-f`NM+m2MG99_47A7+p0TUV-hV}RXJ%+TgmCz zAs_%SkRNlYL&YG4bU7Q+5ydL=7pP`NF-*r|boH8sbK1GJqrNbwzvuFcuAp5a70EiP zvkk6dqJcLI&YsGG?r$Bgb*MdHh-D!wqQtG^PHRsaB!xzPq8D~tNmmAtpAh#OZt1iG z8XeKf%=Y%vMvV44>@%FL@%=2W7S9~l|0e3PROXp}`pV6!oCzu$ehO9WR9Mx%c;}eS zk(J@mY7f#<_FEj(_UW9MTHNoey;S$yqW`Un3rmgfr#Vm*9~oC3Uz|u|#7qyxj}z}{ z4XzaEh{M4=30_rzzJm1SAN6lG>oBVh+oQA^CqxXX;4VSYokAGvw-Z5~l%4NNS9D)kV1BP?@bHoRl1>(aslTvt-VXejvREiGy7{Gvr;($fqM8Pn`cVZ>!6>uZNeVsJZk zJCUg*8bG?M%Ly83D)eb)bp?zyyOHKAHt#mn(nz;!0Ly8p-6PSf@_v94Yvtg9L@Tu( zjQ-&EP!m3;WQ)9~WCYvq`>J{1;QZ>_*Q-0CIxK@bH;eq{cl_B*r)af#ZE|b(n8d_= z?$I8{i)`u&J9FQPUvOmf={|qF(fo#8dfAiYe{6F5^1UxBwzz9_?k~A)4q1Eu^>1R- zpIraXlG!mAbT@oI?L9ecYn|=Jr~n(~kWva%#ME*af(_Vboq>LFa9}N`)~m2}TA}$? zKp|z|QUnlCsJb^d$PyM5l*%b!0kBdI)vyl7&5<$(_PF7}9_!rgl_lm)TSF-yv+k&+ zMUOMUiCq0Shl&(_vJw=+O>c;>%qTX)rq!^=`hc?H*kbEqjx#4aw;ePmPmluCQ|Att zKQoW|aTg7I*^g55+oxTcr&Vjbt#e)Ec!>f8X;%8qV9wNXwzY>lG_7+nBp1i4hdB{3 za$?9C5A(L9HjEVyHjOB6^h!|rn^^zYk4wjR&Q1n^V25yhSc5|sswf9xPf9fR@`yMwe2g!1fKpD_+ z0XEWxAorqFgmRQ9Bfpt(Bbraa12=D(zMZp+g2e4qZMt7QG5&&U;sg?g|Ifo3l>8t8%Eh(@1Ex)trN&DmD$#uzXUY$9efA~Hhk<BM5UeYgF*gDP6E=V(ZYaIvYcX~6xE=I<0ai~mr;zVvm=}e$}1eur7 zjZ$aNX)dL%4`*|FH-jvp-QIw<8Tf2$x>7(72$oF_VmR*QaQ4V{tn9*?-;xY8AX#gTgQ^4TNIJ_QBP+i*TS;x zrgEGzl_$p5vHGg)TE`LA8Yv*xH!Mwj^?`TQ5DM~aU{5#Fcwp#xE;K36vHf1K%z-n? z=l-wKUO4%!xK&EEqytK<RFaHjgA#&rR?*oBcnhf=vP9YYrmht~x z2#O=6L`tj=#FR&*PW(QtT>lj}igXnbqNE7PmtslgcA+dvjSE(jPAR2%ONu9?&euXC z3?Q6J2GvhL@IA-HpEzXvnvxMuQMptvDOyJzAw_3=n6KED{-^2!>>ec;C!Bh`?|1qasIBXiG$er|{4B#x4Qfwua zS)zjcNR2-IXu+nxq_PXO!r(ckTN-!&Gi@J)E|LE*j+cTa7O6 zbmb~%+dWK23ko{(-?^h#ob5bsc>dFAbHzu!()(!C&T6fF)(o;Kiiwu{BXE|&g!4+J z7JreMw#G?RXlH;B$SWEoH7}HhUGDAW<(2sNzoRJqKfhj?&Gx6~ zU#ma1JoD{Hjp=++-3)b_U2(zJGcD9DLTSAi;O;C-?$e#Mbds7BW z=bv;*=o<_p7!r06n~bHx%q<4Vuow*-3T7Lu(`8Xan2~IJ4b#Y3SZNrNpaMBcO<|m3 z6E&*sPS{Io*D;@T4>i&z)IJyEMZ!Qz3Z)Y zZs5#uf_b3StG7?EhLIj3 zk#NdYoH`Gx^mJ%JJ@?NQBn}7)LHR`Qeh+LN|}X<gU7UBziiA+2J0DPxo|brdwoAUw{@L~_JY^B?Ytty4;~1?1w@fl~&&5c20RdfKbyjwpIj{D}-x z?kOx|C}`qlsV}28MqW~kxAh{(E zZ6H}iq!({=bHU?x^lrNbxZJq0V34EF2R8mXzg_3YWHaqA-EZL&^FE4=Ws)WxsFY1B zb(;8BsoJs=rIa+I0zau{`)?^PcZzC&xg=-PE%iqnb=$)`-TzxIpZNUm>!J|tBn^{` z7#DfIy1nBGhc#`E?bg`jE6@nnwY7OAU2J^*bV569O?R_NF?xAjT6u1W1{OlqRi`21 z0pX0xh(fCN@a=4ZGB=ZLzU2;%brE6JHfTFWvuN~biUHk${Zz0r@26dZT0faDd61<| z;YR@D0h;jgP4g&S*VCS%QsGijvMhra?N`F+!Dw|U6F0x5&VS*8Qs9UbiSq11WR4p; zy+HT+su0sDTb0yb@sB<^$ul*!=4X+W#a4QNjCYe;9ewYP@V(kI$+`#J%8#2`L%_Ld zx<&g*chwTWfieDo)GfwEB!oz5VJT@6e1cMW$+uzcmiL-w(XKQR;;Hj(?d>0T#I$lPRe<(;sQhMU*DT`HF;K2O5>7Etw za>!AN>=YkQjDaa}1F#Wpu(sl0fzpwGPEALOm97YJjpm^VYIgv>xE8D?MqLCPtS`Dr z)hCVqBy~toQG%|ZHi~5z$X-bYonU?G9}pnL>haS+q@J*EOm|91j*CjOpaTdEzMy-D zdQAdU(MtK77`#)Lq=H=nJ3Nk*U$R&Ip_d)%ErN~S_);Q+uTpmwuDK57K+ytEs@lcCrQwf9#-lks42tHqOEP(d?2rowNkv- zN^T2fe9y!Cobx{CsH>Fl>!Fw!TaGP1KgQqx*!~_*uS*LLPx5g- zRQ(MCZ2NP<@p5)*hCKGqU$RU2)uwUqXaDTXf}umDIjJVBzC7vge`|Clms$QXHV8e| z)>|lNO;W^o4QVqjsM0a4y>ju!raLCz`}?5U;3YBT{;;sWT;H#*uI{U`FC|6f-TD5X zPn?Pr2o)X97U7Q#`0Rz3m$f@D^vuY+IWe#EXsG*Kbl2vL?*FEBHF~a@Yt7&Ki6tJlE&7#8$9nNm1BxA~IpJ#YDXz5m zIzFeQeXGYq;UhiF{7Q1i$-?65v%?h&3M7jvPtb~RJ&(|V-g8ED4B)9eAKSH{Fef3f zjQ(%Dbh2|ytnab+Koqw6UBRXP!v}RFXBWlHc0QD!lQ8s~i{+tF_eQ&u6)((cPYn!g zj(;DIQA#Frj`Me;KF=@jB&fo&^G)Rf1=wtDOl2t;kVEat*v0p_zz-*xHeFn^<984@#k4lnf}=L04ou}u7@#x+omn#;X> zOanVG|7w=URjStt8&32pJ}$vKI6+6u-#__+~Ae8W>flHf3jjfn<9 z&sMOJ31a!9Mg@2`g)UcDIb>s0lS|`6Aju{Gt@ySx&~?30H>}*A{-Xl}NKqgRq%j|P zXEmORHYm-30}LW*kWN7dS6n}y%3$p;BUco&a(h?>zEB-ubfBDG6cZf4UmHhv@KNx8 zz&)}4e~B%>w{65%cLclUB&R<#`qbJ*!ErzIZ@Tktb*68P-8dnq^UGe(mHcAKy8k1X zaDRidKtCqI?)|Q|;lcHC6k*|Op|#TmMSMZ{oRp27n?hoA#{~`=>Ms}b;EHwI&K2|- z`JdBaPp|s+FI&n2wKwgO+ zk}oz*c_oz=9cEEhPWt(Vt{W((j|n4-5Cp+c#V zo^lK?pi*NaD4ckKXFGGZA*myPOg*?jkHy379~|+TBJ%L`AH917Q^>M%LStcBkOTBy z{>h&9o02CimQ>08}3u>i}ffd@(4sEm!(TTQdi{S_iD?+vJuo)7}<5 zJPD`BvS#&Vt78SP!|#;~)vbsg&gTqnzK;eJ6C$diQJC^=+*Tm2wu9~K>(FfSShwO@ zbuQsm-gGP={5ko|yeY&eGyQxWWQuUTR%>QWb^fuDe!jkbey$yTtq(-hE?#w)qw=fo zN(Nen3=55#c6!-wOM1|Bttf#DFqAbD7s>H8Eq@#f3)<`l&qmWiM8%ee*X-gcYONvE zuc%n7V&H10>#wB+g~Qm6{dtA!>YC=I79ZzUQhP7-4GZ%-_5ME#`dWuy>-^}pJP$EP zsiKTlk30I>sc6p>6LqtLewe2V)_Y>cqJ@6Gg>)w*m6cWWFNOFKk||I_?=>Qc|DFIe zC$%`)Vk=BsYq?`e8z4Am*GDNi35}0=SF0~gc5VPFP$TD`oDrq7{d|u-3_>zc-uQ@W zvIPOop=<=R$SI%>gsoSE(UoB` zE#KNaujVTL0=Qyl0HrE(~h9%}nFN@?3Q zpQT~*qqxf<-@JVUMD(^vsq!|1Gp}-ue=q;*y`*Vuv=&I4X7PxWT>^j>h{J#I_ouS& z`Oo{;-+4UkyysspFKqkgLvtVXygPcHW9#SuR-NYLzft$+K3np)?K}JSL>(wdhWh38 z=W6n+drX-#IBW7tf1TZ@DI)mPiN@t|gMa?WC%ACt$Jtl220XCs`@i)s@>v^A*qw9j zNUQUVr}LhS=WkAAu(mVL^{=!H=b6mz7ou-oD$UByzVMgDuLOp)*D`kjYrIk*c1Nwv7S0V43Xw_Mo!T`f`8>juu8a%~a$(jOW%zl2>gB4S`X1P*?CKN$5a2?m0e74Qd-B?%o*^^(7&pbb^Ek2{IPxQ}IKj~GmlJrI0 zfKdNqz^X>045m*W;eN#VC~2mK5>P!8amEd!b`)CJ{BU_zqLI6tP>BStfO|gIJ`se` zBuh&?_`j^%E;_IjI@p_+3CfVl)zzA`CpP5@BF>os`puvM(>REi5v5gw`jvqps#O~w zU{mOd_Htx~%n*D|h06w|LQm6p0pzAqjcCXz=SHT8Q_!j5?Fj3TR>BBxE0VH)+>k=& zQ%WVB({Lb#Ar3+za)WX%w1<)O*TAE<9PHfVu5b$Sa<$aiaeqoam^=`Pa&AkGsEHMc z(o2A-X|vXKD>?Q^a#uk_-q7&i_`8FNgmD@X6Jld%#`rwlMlR{=@iC|by{=ZO0;Zb@ zN2sa=m$d254|oPYU4G0Pp`pntt5g9(p5cmsuP%P3ucKk=|ANNb17^%hyP6;L+M30a zZ2vjE=!4-O#mu^G%lw$tMgm?weidul+vzI2|~$KY=L~^dE5+na@0C{ejL>E!RMZs zWQJDJ(nr*LPgpKaMCNLgk|bC#@iyMd8X0iE0tW1;7mw>Z5}8!J4lgocmRAfYA5K2wsic!!%T7l|AS_3rJ)8l zPBjT(!C>Q**OTI$sbM;rfC5CkRqNvTO<1iMK%LWlm2BeXBx&c`pX8yK9wxe^I1SFi z1Q{eGz(LA9@%WS#L3HF0H!H1*dCN!k%S9%EWZZ-z zU&cM+N^cElAzP!;fHzXrm>)HonnI!U>BNcNWOOvkOO|{Y*okXUE(Y5VAS2Ev;81Tm zZbX?U?(f2nCvnkG%5dfKhj6z+|h8a9U8jp#Vu%R=Iq!WUCdP`sCiogc-dTz`x8 z^tjV&>@(wf-Su3C3VOCe*i5Q#z8?u5bAJ8l-N>&3CxIK3V1;m=%e69jd|xvbO_^16 zKATDew~T}3Zoud`Jlq?W{#^TC#xA$kWyj2Tdv)^o89nI+AN$KB8{pFN)1wDA{ofP5 zzUzJweqhsgk3JWvc{%3}wz{stonR&2$gUQTJMWsO>qJ`DPto-kj&yXNJX6xUIKNMT z?GF*v@TGUbpXAxKs6v!87rV3`+wn$hepy*k!mKd>MF#Px)CiDY9@1j12LX%;3<(oH zP^3dP2MHvs<{>`GgG2VC-VkiSn=2Z{aoS~s3>vzXyk)==$q>Z?A$j*jZ82zbXY(>l zyh0R`RYUnATX(XxbboAie4%H=`3?QHdBQt}AD^0%QH+FA zZfT=DA-79qf*@CcA;qQ=g5X>Bl(RzH%u(Y_fCfSvl9(mymST5_(FvQPXV}J8o&&gi@Sl9d{ zwE&I~!HzqtFF{4P5P`6Zy3I$I455ow;k+x+^FtyUc;;Yc_6H0fF}fj}#XJ6gmn@v9 zi6I5QIxsfur$@u?{Dp7(hZWl=`_4L_{GZcz4zfM<`Oxhh=hM5K{qovYHQZR2(d8=1 z`O)NEPj&qlj>RNT_ha=pf9kjJNbuyDem;Fx9_O@{a(PTT;Jl^IvX2Hu80<>Ubnw<0 zRofrM_mZ8j&YAP^@(AYRD9af|UOzjYX#z?h$}Zq|vzI1S<5zLKiL^&!yOW*ct+nuF z$v(;%Q|kv{vfj%U#r_Wuk|a@IY=htv5h3TV^sTHj zXp#{cnn;CbXLKSra z4UCFHF(ppP4KY#!Ro3U>lz^}V+^RHBQf8kK4?GCfY7zE;W6S?(sj!&$>9VAUD1@K2 zajMUZu44Pz!r5b}bk)@@to}1C@O>jcWaPnDyhIguQd7s-uqp(DaVKz9+I=#=C2#yP zUre%yx@BD*vv>Y;^6FfmySgcXKRg~xd;#|5EA}G&X66$QTK-UzKjY~k{rnHyH<(E` ztB&0jaiF4NnY$(W`6fv8(;I$1n0GO)p=)^bP5Smq^4#`PO*&}G%Q?z)`KA+pe5bc{ z=)DvPjwUCSCGm8@Qp-aSOu-8Sa$qpn>ONq=iZKzI%%IGeMvJ=Aodc7Txk$oQ9`Rci zH0AtO1c1wA){gx;$6u+rI`c%HC-9rXvr>c<%CY5?N~Q!mRB|8)V8g>4W^WJ=VktEs zt%5?J5F%$-+Qja(?jxqo|-mf<_YG3 zZp=R}L>2b?jj;v7R^}pmf}<#{D|7H+o0*RzHOaYtZWTA7_>o|kBq_(smwu_qIAjY* zks6hG0qDPAjKy;87yQwBrtzo0zORD9$!VYa%v}FmvODIdN6*(ke<9lQ!_r*OjaJrk zu~fLeduN{e=ViH0h6A!3;qKO)Ypd&TTxn4pK5MXL<%{3EFzm(7jglM;p}zFkx4Gf$P^M1*qqQ?7d}KJSi;d7o0mFzv#$k$rZw)2?ht>$pm=9gXRKFPJye4Ho+@9%od_sHX$#3c^Gj9dBi}lcJnRIAIAV^)& zEMsB`K)G4m8$ZHb@I~A_GUp{$=yE|BdQh5IL8ci0Jf6=2cOn8L=-`PdO^7)S1ulE% zLU@^mDn%)1l6=1(YP4UvkY1o3v`0F|quKk{mGGihqKSzCCMH}&&n($?K zCsoSOST5c;Lt=H+9(19a>Y~|m=$eRu6udNPhYP_)D|=Rc$a8^NI zi;LP1fM0eR(ZCHD=qY@vrzwR1f*B?hlQLKf8x!--l)5xRAB-vmNdOcRy1ix!x!N31 z*so3Tpx2F)=W}7GsSYxMRtYssC#i8BHAWDus#z{Af>VlzNivaX%66m{dljz8 zJ}NJwEE%CyG6o%3Zis8-MF>_)g^ep*kMc-0DM!2|X$`_Et_?UC3opu`q0cO+H)qx@C&f_O0 zXo3pS{w0>KF8dmRs6kR37C{FBiBHiCTdq^}WwghRfGWvPR$3_rsfm)Zq_R||k+<>s z1&VMp1mS?Xnzf{2u#lIjCTh`u>WM@`_GXG{w_L(6e{rhoyTshT*5=*3yWaiv-MOC29bhQW zhI0`y-Ld|^`rM}=tK&a0Puv?5)8>61g1wR~&d1c|RcGN;445>TVzCM|$?Wu^ZL3t& z>>MiGWM1Q3qe#%?R?cXeG;?F2lL0X`pwx`F6bn-Em0o7f-bj5EKGKMT;#DhQ2Ex(# zcmx7vbb{~|Jrp31DrvrCfZY1#>ouLfDDLMQ7saE~ZWJxl&<{5_af6HqFe(l$;Yc7|f&TQSlm&XC9|H zDkY+14Sr>8u{UGJU5hp;edNWmugW{{^rcWnEeS(qE}-;?KT7%>LXJ*WP)XSf5TWjn ztf~T5W2QND{swrZ?1hrf#`h}epx2Y_5dP)rprDf5Q}#}d!|NivV|WPWG|0d(oGJ93 z-!Fh*Jb8exe_`FQ;EorM^$HJMQFy#pNXY+T06DCtbVo2x#qn6{i7|(=RG%5J>LwfoN(fA zj}Lpvz4^VgW%s!XW(M}JUif3t6!-VArf;mDRb-?)f%%slD^9`4&q>*Q6OU z0)w`M)M9h!(xj0~15%iLLth8IR;IgzjHgql?Zc+heBOlI&{0cIb%NF0GJ{F%RcfLc zYo+^^Av`>p^)%-4oWQE;&{EM)OCUY`0R3#m5?VtxL-WpkxbE{8MlPABTkVT@3wuBHE@@JZT5;5`%Z<@m#n;e#&M+ZHk~mXnrfKaU z7(TN}1E5St7yXlT`B0zJXmE81zfE6%a0i`c^XbVB*^)ZG&lf$8R_=KGHcQC1rSDjG zRx{k^4 zXE$?~(sUPCobR)|@Y3Hdf4XVQjhpdrY&kmJ{cY6Qj%eo(U%Z~#aASL9!_Ol#x-TZ2 z&;4;CedB(4H@{oj)gGC5VPjuozmXFrrZGHyF# zOZ5>G@bf1Z8bE-n5sw!NMxi&_mn=bQN8)9du2~ThE zHtTI~W`ajQ8iqrPth`mCqaLY9wYh=cK35Elw9y$+Q746(!enu3~gRiIpJ3 znP*EP@a#uV8p6t?j+dI_KdH4b;Wd6p?yTtt&rBJV_w)UkonMT6AfFApHCYd}CpYe; z56T)!pCo!QyckUh6+lt3(nVi*=N3eac0cD(EA64k7J8v;1!ky!aGB5-Qp;nfPn6%K zBP{OexEwT-zQiIMfAQ*F>1u3_r8q4)IPYJ=sIhnS{cIv5>DL3(A1b3CGhA?DX1)6Xl)n2J*jtwF8nUYv62>dQ1G zg0@VHA_rVWsVcYO*(2vBo6a=CvUh0wrZE-}BdZaxZB^TKUC5xYdF0(AIwmI1EI7W} z@7RRfU%l&ha$j?f=~nJH6SfdWwO~C!qdXdhPeSm)PTqcKwa7D9KE!X2Z>O2;Sz}^SO-~-9M*wH+X)IjJolCX5G#ImUdm+pXX|+ zZTR}TyxdQ(M|T~cd;jX&+GZeW)r?Z)>Wh3PQsoAFKM|Xxa0>mpdQ^uiTL^jD!3RbBE5up2YtJ>To#1t4& zx`g(lZOK$Ma)-@GimK+P%dFjtkQ>^t?5Btp9e?z3t^Z_dP?&3X-N+)}iq3;f)SDZ& z`xA{rs9QB)UG(!iCv|<>id?1C4cJdUcdKjjoAx4gLDG&#oNDWipv7(=_LZi64Gc55 zg`W{8zIIgT@;dW2_@yx#SVv7=6jk+?xlVMqbjO=BOH(1<2#NX-)A@pj8j zdjM;LP|=egf~~-QsxeT=P@UTN1fC+}5h%2pMr>V&y5Msg=AjWJEj6TolhFtc3^RtF z0wNPvH28$qCNQ{zj%r3Ofmzg{dE#bnJTj?4Hpbp-4gsn`%G}ska(*b%%tl6|kh#=s zjye{za^W}#1V=*@G#iLBcv>c3v%%0deqn#(`9AhNm|-t+b8uhz6kG*{h`#FH$>%rb z2PQ4JD*hx*JcybUhaz`v-HP$#ueE{oDRi9lxY3zid4Kl>ceq#Cu4dZ5@ z+(Pb779T5m^6k2F`7`4_>d|-ZQKqWpTt8ZrDtn@j`yHeU-&`!^Y-e zAq?Wcfp}*X&V?mz-TgTx8z0^xXh*=5bHgu|*224v^T^{Eq%4*Q=7Yq|kI0c3zI;H3@BKsu2g3?~=Bp08(_goP0qh`Yz^ zo5x}Xis-#`;`2_hjTw=#<3fcig@JxA1A-A<9tWq1A|&8s$gO<##Or3JNOj#GKpI@C zl&{EB-NPh{ur=;Gmovbw-e z)k`us%oKRZ1ECisC_6Kr7GGhcj8vscO!JOa=4`By^;Ql(ayVJjvwz zknaW`4pzhi{3)iDoW}pg6h;vxCFLYxi1tNPN1XoxjldGgEhEyQ6#zl%`#^;dZb@S| zJO_AZt*)EUzA}F2n^ifXxI>NY+u$x6t}%2%;ei?e_}*pxkFG$zp9vC#efQI zk;JfE@tk12Mm}gFQi4QB3GVbt!29;*zqEE>B;q+JO@*Nw&NQEcvxMeJai63i+qRs> zf3ShF7`Wps8-4Ub4}8|u*%~Iw%o48!=X=!=Zm$CKDl7x_Vo}R+2lyO~d*qw(FCa49 zxjc==5xry3$4*iln!nXI5FZW%iLY+op*ea(_& zFI+!li+O7B@TdC+6p+!#7`3FL?AWuD58jR5CIxdl3x9a#h!5s~K`zKN*Yq@CnV}E8 z982Z5^tHPtW#XumUwH?x#Mm&!%pv!!%^FDt^zwrs@rHiU)2BNxuWEIEda%`bZdL2G z_OwieVx1>4Jk709H(twQg(l|vzWVN;m(}0sUM(G9CS+FPnwfE-6>Zdi-O zw}18<<>l`#am*Ab6{upwfXx|?{ZW{LgMyOvcXIY;x!ubsL4)7Q`Sb?r@b z0Y+Ya_Nh@cspPy$3Tf1~h1**32-P4Yq{x|~;7kK{G>~dY>a_*F(<;(v{;Ri zm;#7oOp7oz&Jh1g^#o~z4#n_6!Xw6ak&;lr^uMunozp?rOi2}~#LN?%IDsqDpfs7e z{D`Clenv-F(TOTB7~xIAHX1ts>Pju^=F&M@@c_Jdi6^9Sf&dPLN|6}Hw^iUu?_p*c z<_sXhlus-+kc8g|?>}p1cU9SySWqyE{v1--h%fz|n`sHAzubP^6671=UpQ!I`E7q$ zF)i!jJ>(|%l!>z5T;yj{vX(RRi7_6I*Jk<=)&>Zy+3km0YbFobrdgyU1i|elxF^tM z+{ewPyslovlp6zjh5M6C-hHL3^J3Aoa~(OJi$&n)`?^ngetyaGV@bo+qR57}(2U$q z2IhIDX13P1@5-!iKhjZuX?48 zo#b6`P4aN5fRueaKN)U0^jaK1fF?m=#&hqpuX>8~0^%|BSZ&Dd!6AW>H4-%^Rmyqh z`;bZvXQ}BNW{VoV`;)zz(pe3nAg}^W*Vbf>Xbyd2WwLwBjG+;^L18xAf3y2N*!{yp zvgr56*gopfGj4{73(CtXV%>ena^Ra#Q;LwXaZQvg<}$oG6W$>HyKzPH zH5XAfn>%Lu)@wZn|1N+2gyf1|h2R6ELTJR6uDO~<&Z@|ditL7WEiBceZaP^ca2z-ex0P9iyBsSeg9D$X!C2)Tu*6&R!f zff$r@Avbgh9G)N6ov)$m`zwRrxs=srg2$9zlp#q*C?_MvA)jc_ju{Kp1kOa^6rS=w zZZj3XB2eA0hEP_+M*pXCh_6D=kYX|$4D2JQRQ?IoW@KZ^4$Z#95>3ghKGZsMM)|rW zzgfHCUxoJDdIgM&828NQTYj^9%jYkgf3>1w&C6#lKQnbbj%VGCJHmp-Kl9c3dkS~; z3fMSk=heZ}R&M@$&ANa)+F!4pI_ThIC}-Hamd}EM*Zy|$kVmE+NxWl~<-rRaNSNc> zZ#U$1fA?6u`_#c4=k>@=*X6Xjo34bY8(q(KyGj~dry8FBTUy4=S2`LzYeI9owknIW zc+e6GtTDbzVm}It**&W8&8#`^>~~j zhy0jgGlE5D6F((h=wUb=}hA0h^wE;NY%Tuf_7=xp&2Hwkz*;ELyf;Pc{O5JfotJu<{7AIPxHvv z;pL4V(I6!dhEMYrh(MV;NvNIqf-KdCzM!|UB#+GOQ8634g>4W2m|@a zOmiRLQvn)Hkm8020c~S3s3w&tWJ;gTgcW@wP9D1%HLbV5Y3|Z%lWV3Pp)d+j``N+sjhD{fB4DNlgEaIY`O9L=kr%=b=)&5DdzK|htC~4 z*Z0!#@xi}%^{#b2c5Ddj_u84e&(~%5WX4ot7dbu-a}^-^`i-)($CHj;9z411A9u{N z4Q?E^8w~aO;np38pZ#ppnakr=89#J%$|T!PVxqyzJHLKr`O3BS3$HK6;_WTH()C?N z>(x&uwO&2iFZyaUbNY^Yy7E|rbHmYF(L>I)PbZn6_*!kI`|Bf$6iWRUOi1?WU$$~o zU~*>I)q=s2c<;>13hovn1Dksxn^dZH3-Ky1E^1J|4{ zaCFpz<>@!5!j|E6D+nvIIFJ9w`h9O?7r4%RD54689EG%b^3{7W5DEap9DTNDpsn z?s#SC%B}gG!6DY@0rrfoO9Mihr^F}RiX+G^Q#u(~0X3W9)P678hXmt3Q?ef>`-tx& z8?8GbuT5xHi%I^@v$WsxKVN>J<>&(~?&Zfnz1h0p%+zmYzcxQQczBSn&uc$^=IB}X z=BH04g{Mw^sOhU~s;3-;drqN}!ebV7RmwmMsM4PKY;*VR4LNg8>c)E6D$)e47`P{x? zvf~x8tXNd`fu9=6s%`lZ>>-+U`!Q8q5gKJ@A*~_HYWXp&B}|<5c&M)z{nH$dWf(LL zh?i463n^Sn{ars?4}H%D5d*n7c#i%%rHv6WI&9!BsAK~#lR9?>8yG}=5fg-Zp?+h= zK~dRf;zN=d%x zH`+?X=CSVnB;UuC=B({{s_(x`HGrQraS8x6TbWJR=0=RwC2oH%|4BTXkFUJ6XJ-TXnKhiI~hrUb#G z+%49hSs=^xv0rl~ls+?gi~4psTrx3#~P`gdwE;ZCwAWIXkjRet`{bfBY9kzFKhQ4TZ|Zq zSkx?xG!!^(X#z%Q9jE%?CXx+ePza$D2dNn=XEkXHRinN&${IbZeIu*Q`?e22h})Yi zZFI5F2_rT1EU@y7#mUt3QtQ_;+Pp=)hCI_1%r$zf){J98(TPl^c+yo6lQSZs^!SC5 z6Mj9N;kwt>W!xOLJ(1LT-vX}U4c9zd_ikt|>c~%2S4#l5qYC}fklInD$KSK7Q)dO7 zP@jsjq^_yR2|2Z9QM#)q2jB_Rd?>~mOd}(Qh6i)2p%9k?hJ;Fnyh>ykD8cDr%TY?N zMKPWVzoG7V{*8WVUQP%dj^sDRfgsZY>sd2eh@C{JRBJh3i%%lz)Ho&{0SpOT0k#HX zBMzf#P#P#ARTL|ePe$w!PrpJrL&f_?-&yTLZ?QB)3W-sJ1_7`X7fn~KHm+OV$ZXcq z^p9ynWa79}EP}XcI8UxY`-z}X;wH56^P)3`mqN7?2@nGWPswXAM99@ zqi8e+ZzB>sJwv~QVN(B~w&WIlGO!}a0wq7=ljO3l#i(?bXA`VaS5cCCwYB@sVta$E znmiPH*k`yBjt!mUSKx@ZFOB_cb8YU{NnQUPxMN2?j~xV5|6qGW)CRmorpXwI(Mq!+ zK?LAQ4TYksy3$s{9ER+vM&fex!vaaDbEl;}-fGXAfK?p?hl!z`Vo_BkTD)kJ$s<+l|d7CFCs-pgwMtU=VkOn-{ z&0|_u5WJL9S>-W37as{jhk6&-CwvoTNy!-z?WT^Y9({Zj}tK_;q@5c6y`qd{qoh6=+LvuZS z^4wRZc6VKhcCL@kIU3q30KJ}P2;F;5pN50zh z!b|^m`{9CPG0uW64AoyT5K z7FocM2WmBqE%k*cJs~8p!0}3Fti6S?AHc_i8D^Lg;$T?5BHbfEhDWIwL=0H4qd05d%|TazNtc>D`*?D1FxGH$+~XSRTrimEV+M4{#;{ zaelLAAjmNeZ0D<TT8SzHz&CStW7mB*vk$GRA>(`Neol5oP2G4|s z8zrr-pKf%xFRZ^_=efFDk;8MpEm;=A*DBV7f-5v5rEn0#$eVtpQuAtLH_a1VBpNKZ3#us*QqYJeXYBv6XDf%4n50lc+% z5)w=MHOBjF@N?yN9*W>07`afF^mdW~Iy58saUeY24Oi?o76&yzZk9Ygv!#E##Jh;u&lYoL6m}!Jo849|T@JPJPE4S1MKBhttEEQspA}KWkN1h8@Al@lqf|OQFBS9?W7HI$d zSjF5hhq1*9VCZ`@UvAos6(XVDbffblb>+!GE;bF+Yr#0Hb8kd8wD)W6+Ildv)#ao&_Yf7XY;sC~*VZ*ZSpNluNm_|VkaBz8-nbcldpgrnP7K5|s2z=warz`a zNAn;-FMwdMxtZ%}ruDjDY zto8Zp+e4!m*?05CbrP$kS=WzFwOFHaJCEP!*WvEGFs!4s;nL+Z9qtpQ9;J(VcjRTY zG?i@p?rA0@PH$~M9aeR8UcE3ZG`jwo%h<>2kIGj5{_T&)mc10dHs)45aPgAg~4#LPCl*zEb8vPjFwIvJVN{D!=3cE}rZX>=uRWGnEGb(W5f7#FiS z>dU7Sa(_Hw(n6PiB;k1UkBdTs?JdE`+1ckVVxYQ*;Gr;WBA+WNq$`8C+_b8rGfNxE z`!Ko2HBowwr~p=t(1cjS=uMH8lOYuzH*J~7p-FT}edIQ{Lo;C~Q{5!bMN(jju1La& z>ds7)qv97cY7iKp);tD1puP%ZOjJNJfxdAfP5nu>T}GSVDCdG}H0P!GSNk+bda9jU zThckskR!;uX?WzUl=#cpB)g*46~k6g?=aGjE`V8*MDFKH5pq6(kbrAZ=7G4C$}=fm zZ<0!3G>IH3Dwkx!X&swdD>VJ;6xuS0jx7e43I{L^jJGOUu5>glp7fR9mM53=s6h$b z;r_oH`gmMaa{7Udt=--Ix=I-q@@d|UOL<>281efPy^kc^*6i=cqy>^pCQ}gIb+sqk z1WV!1-xGPNHzT9lE?>{o_|Nxk3)LT7_sr#$A1;(l5BeoDeYH^7m*ar_}|#_$t%A>=p#+xhp5nFJQgOX!StK2$h{A zX@W*2LphE|X`=58%6RdkmrmaCc*fQJ8Q&QDeuN!(SFijfJvS+Bhka&wXAr}d)iKBj zOcOmzh%uU)!UY6U*KTWyaywb7sBGV@<1B>E^}zxR-2;shubE-ci+@Ezd2v> z^!?wvTRtD~%uB}tY>n?7o%!E~5;I=;vGU35*Wr3=Q>=uE=uL%V(Hh^~HC*=J6!|gzG!clDqViD_- z=YRfT!&ItjA*;||1X@%q{$zTy_5owE5@$3nLI*y3A|$xO@Jtt=A9?UgsWp<@fw;O# zs!xs4jBr#bGKB4IVp37GyQGzw-Z~WIQ0kzoZC4C2T@^g7^Qv08mx?LB%oZ6ET-umh zeP~`b^E&QXa@&@veO|~)E_?0SPFMSmJkRyKOASyn+A_M^2b4zVt+{mJ@X*F#@#9~0 zWbgj%x*gTkg`HjBEzNbe56r#!#Axv=q5S}y zQEk(~X;FPD;&uv}U-yARMO%7&?#JL6z-r^+ph3->nf|~ zoc_Dt@oSK~CAKcbeILA!ki0twvf=pcih*4}JQ?NuqMw`XG+nL}k)9vhPn2Z*)5h+j zbPY+57gM3Wgq$F4GKXzvDGw(!whm}dZQQ5ci+3>2S0KrWQ+<%1Qt=IgIkMXNZ#F_& zk(d3kzzps1je^)h-8!K>D=5r*X|{7}NWqTs$4A%)R=CJUQ65zCvH6^6K&soxPjPY- zB1KCQCzLgyDs79<;eGyUbcKi{hQIi%2{4>rz3bm!d@PLbB-Ku`hdsW~4Jj2W#Fz^5=T%TIU=2VQIKVO%Q=~97 z82*B_wCtZgTeDF87<%=@33!2hpTE%DI-rnxYu`YKcp)BkrF@*%eaTa{cu8PQ*7kub zdu+A)+XE~wt{r9lg@2*{lxA?Jl2SwRR;IxSiB|^$yfA9_5If1a@rY5i>u^iRg`7Bfb)r}R zyJ@jerj7oeJ`?e&jGT{c-^yZb2Y0Rd>=o-3Z7zcn%v5T~mtzrR;kXrh&H71*O7jr0 zqUO7OP{j5F-nYRvGAkydG+-*lnP%%sl>$KCPeuR`z|fk`_|)Ss=Z$qwSd)}Ks(O@9 zjRfvkVszVylE`Zf87EIh{x?6du@(8qnCF~`PKo}(1bQhL0s!6T3V%%MLaEg2vIr($ zks-xQ^%9FTe2~R$w2jz{u>7J8(Wx`KSbwOVwl88qfb~!L+WPK{4O@~GB%|}@jvm>G zd`_kscml*2kkr5=;)F!Im)U``fUc&j_zZ@#mm#c%Cqt7HOM`m(5xxmJkr1WN*Ls9F*rfpT}mAlw#S?~2$GRgPh|7`!|^ zeny>KHT#-F``LPR7RM&it(JkZZcp}rT|ST*VD^wbe68m(xYlVhN7nkB4TF}fOB=E~ zJ9&QB*z{wVf8Tp|kDL}0^v>c}*Jcf`SpUb(!L^Qu*UUb?KWlie_I|PXjSs9J7#oP8 z4N3}HaHlgoCU8Si+5GUpW2Ih>(h6Caq~yOJor}ENg4y0A*C7r$?A9^Bu~3;rp3GPr zi~6&KgAhmYo)+5Etbt5nBqpedi0C%F?JPFIGhbwO`)cmr2El_55*b^39G3l(MLtYS zOK(M0+rwg|B@o%H=EE_HHJ#h_=s3EXoQ*U~R3Dlu_7*?EykTmiIoPg3N9nJ#tCFVq zhlEU=nEUe+rJdid&*|exNVJv>r62C^NYy2CwAFYK?oCU8zoo6%Qc&kVC-{e}4{nb|KF-Gl)=n!6PfCti^GtAX zh<$BVWm%ZZx*;aJ-KXtSpW6c4i$#r^)jo{poBa2l$ELs=LB_YG3C*-0P=f~HvLa^W zNN$hr2c%B0N6SJDzX?agt6m${+#6L=OA6QS$M#2Sn533f25eJApAC~sZAo&N$&Iuf z7=}F#+^j;C3CvC`Xtt*N9tS@q$hmP58LXJBkB7N3yYcmAW;#NkVG&ptt@-gaUDu9X zPg#?-hn;Ag6b(3i$9?$K-cZ+u(p(!D=)=dsV2O=YSwXF$eik9jo#}|+RGy$nHLF6x zSo$C-NuJfn*RgXLNixk#+!B9T@YgTCVEr$$TRxC&OK{@{r_ap-$rf5J!#=mkL$@6|#Sl50f3o#d|yN=#>);gS|^M79CAn*AtvD zOuMSG3J0kcjMd3ICT>$Jp*jxKUMGereLQ0Os#47dAa7VFZ6}t#k1jTrHzcDz2M^00 zKt2RszZ_Oq*Gu;1crLOzC7Ux*aZqY1OtqJqVUXEK#}`N`Q_H>k)I%oza|UP{NtxA; zEX454w%106#RL?#`p5bE9q+#;-)CL_>4S%E4;%Nw+98j;nN>CVaPKvRRTXECr6eaM z#k}g1U0K#O*4H;Yc6dQdw$t}mO48xDB=MrOn07voVi3|3p+_H)4vh$QC|FhNyS@DL z>Kx#kkwC+u!!DuTu*ON$W7k&kP#uY!yD>8?Kh%#GxWel<)+Oc+ zIF?xv)Bf$ZxBv0Z=$mgtQ#<<4dDB0=1FyWFnwz+>02eVFA0skCYO#D`5r+zR3PeZ@ z5ARp!8vHw$gIp>X#>wM%_)e|67V_uD1q4G9vR5F)EQjUggRJM{@J*>0A|hqD=CYPk ziy>aaTO}VQguzz6nT34AxHmRVq6{=p zpsQp8;0fXbcst@NqNtr}*XtbbAsmSKyi?Y*>YV@bYT_3?F06rRp5V>OB(M+#T@VqQ zM`d<4dyR-x!BCE65NT%}7AU%=d&>5;e5M^%{bxQ;jj-A*0dwpFlgsig@7$4c-q+Wc z?VWvxH}}r2DnrJD?U}kA)a+p{(DJS$wQk@1KgFDnfM#p)nH84q>#Q&~+pwGe1sj=l zrJY>ER$M^pzq&L=YCAMd-}NX0VZ`0&X5 z*K^(fn3YXTm-giP?EY9D)v=dGp}R9Ewy}y)cEz>Nv6{tOarA`phALMYeVQ}2wSoc^ zKB&zFv8HiDi`dnNq)nXTuFak0+~*MKoxk6Cdpn^fPbvi|m{<)yV#hlKL@X9N0f_SD zxKoh=D8qVg<@EbN3Z6ePxE6bFHkqo&lQ&N|`lmnr37kcDW2zPjBP^jZ_p9iBgApjW zD_@CcmaIcmjX~ch3utk?g8!0vz1PH{gc4w4c{(+WB1R~yp&r9h$Hz)5bV0-faz^~Z zB${S&+Ufz`X1iZ5a zobS^=aP7DOcUlMS54-K~?EJcMzTx30Xrs?OVNsgK`_h9)4V`SxPG}EF8nBe*91dW~9*u1Mp_AplKa%9LENQuqFo_@CO9df3`!0ee|@kC>&V^iJB zc@KmXI*OYK?=HPIs{UkP^vx~UvYFjK9c*y2tKhah74&CfBUyZ;5Yc$b&}7YH(3AwR zO7G+N_0(`+sJYoN9)%^*V?nT9$I%Q6!vK@1um(;bxI!ifz^W{aho7yU=0xwd+U%j@ z-sD1f_<51R+ge|Au^PN4#uk%LoVspOa$O(?HlE%CR7Bj^4mH8bMqxz|+`4$0OwZyx zz?9GSMaVA=rWXoHk*km^sHl4&K7f^=)?nMVR*hPa{u`}PQ!<~;LTfep$&27}&kFhI z$8sy?QK;YHP0};mhd_wJAs%k>VZ_XY*y2DJ*`g|356Udw_3G`#lism~ZA-Ie#`S!v zCjYfz6OxjT`dX~<=`|6yurOO)|F|t_R?8PLfo1P(URxHsCOmIM#-s0B`ybnt-{Yb5PkOVcc*YC?O~8D$qvLk}1o@lUxNcRx_9YRVRavyNhuaB>2##!fI9Se~n$GCr{(=5}#T#P@P;?|)s>*Uw z<%xzBPc5);LU>MB8fPMwvMYAZ{Q-O#U&lXCOaSd$2_AEbaGRbn%&8a&o+w^dk4LX6GQf| zV@K}G(O?Ys(WI2R!m2tV0HqfMCnO-B_DD`P*ejqo+Sm73>&8LE^V8=gz2MrndsKDS zus=!gc9)g{)YRUkuojnQ0Ged;$2Zr$Mokut~)5pT8#n{4PSW&(mQhJyiRLbFRW>h{t zLavhd=`dPB>YV&qT~KjHs&foqpTI)zyOVSGl2egatxqHE5&a6Pp+o>~lpd2-Q~H+!u=>hWy7XTZhn4{d$(^%r`6v!!u=?=4r4U)kY*plTthc8#i@ zaFi4crH#y*x>V;z_SGd??WG~55uvyH$2m6y%;u1^r5Eb~&Rj}S#5-6rCpaYD`N>NV zBZ7Yu98q0ojYx{>Uwjwm2gO07F;gfAjKyzXy7tLq))G;!R@fGrqUgzIxKxTq5D&~q z_zdyfsqL!d^3PWi+*I6}S%}HsnS!%x4lAQexB%}6VNrm(8R#TUwK#sjTfy|AV}pR1s&9%!&8e`qJHG5wi2psuS|r-d&V zsphrK5aKHjX~U;AJ+O0XlfB2$^ zl2zvuOLpG%htEYrPiz!d%R06=GWjs;$k^S{&Q8Pw3>hu&WM@f8E#W+TLG%2vPx$3j zSliYT9fmxXRwqWbP4HQh-#Is!k6DD-^vOm#c%959=hxfYO{dU>JUqq<9)RWD^EkYn48t_y>9DrR}4ngGtdA=jEJ?K2vNMizQt*`1YTtLD=S%= zl?Eft%xt4jTmZb${xRs+lp=xAtgzEFw_1OP5^a8zVp|3cLu%eoDbx1NxM4>u8aWTt zWnU>d1$&Xbl{Kb@jc?0+*cvsU5CWpeVN~k&JC-3OJ1!_KZz}f1h6@Z8V108cq+p

8Sp>|?cqS;vb47ZmQU6- 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�YZjBC1Lv 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 U?^stg_uyyd^uTFFejly>f03P%jQ{`u literal 0 HcmV?d00001 diff --git a/resources/icons/png/64x64.png b/resources/icons/png/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..b6d200479a262148a1f4eadc9a4c58d88acccb87 GIT binary patch literal 5197 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 z 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 VR0C5DOf@joz*GZM4a{ob{{SRtTcH2| literal 0 HcmV?d00001 diff --git a/src/CLAUDE.md b/src/CLAUDE.md new file mode 100644 index 00000000..77eeddd2 --- /dev/null +++ b/src/CLAUDE.md @@ -0,0 +1,31 @@ +# src/ Structure + +Three-process Electron architecture: + +## Processes +- `main/` - Node.js runtime (file system, IPC, lifecycle) +- `preload/` - Secure bridge (contextBridge API) +- `renderer/` - React/Chromium (UI, state, visualization) +- `shared/` - Cross-process types and utilities + +## Import Pattern +Use barrel exports from domain folders: +```typescript +import { ChunkBuilder, ProjectScanner } from './services'; +``` + +## IPC Communication +Exposed API via `window.electronAPI`, organized by domain: + +| Domain | Methods | Examples | +|--------|---------|---------| +| Sessions | 10 | `getProjects()`, `getSessions()`, `getSessionsPaginated()`, `getSessionDetail()`, `getSessionMetrics()`, `getWaterfallData()`, `getSubagentDetail()`, `searchSessions()`, `getAppVersion()` | +| Repository | 2 | `getRepositoryGroups()`, `getWorktreeSessions()` | +| Validation | 2 | `validatePath()`, `validateMentions()` | +| CLAUDE.md | 3 | `readClaudeMdFiles()`, `readDirectoryClaudeMd()`, `readMentionedFile()` | +| Config | 16 | `config.get()`, `config.update()`, `config.addTrigger()`, `config.openInEditor()`, `config.pinSession()`, `config.unpinSession()`, etc. | +| Notifications | 9 | `notifications.get()`, `notifications.markRead()`, `notifications.onNew()`, etc. | +| Utilities | 7 | `openPath()`, `openExternal()`, `onFileChange()`, `onTodoChange()`, `getZoomFactor()`, `onZoomFactorChanged()` | +| Session | 1 | `session.scrollToLine()` | + +Full API signatures in `src/preload/index.ts`, channel constants in `src/preload/constants/ipcChannels.ts`. diff --git a/src/main/CLAUDE.md b/src/main/CLAUDE.md new file mode 100644 index 00000000..1911b032 --- /dev/null +++ b/src/main/CLAUDE.md @@ -0,0 +1,32 @@ +# Main Process + +Node.js runtime handling file system, IPC, and app lifecycle. + +## Structure +- `index.ts` - App entry point, lifecycle management +- `ipc/` - IPC handlers organized by domain +- `services/` - Business logic by domain +- `types/` - Type definitions +- `utils/` - Utility functions +- `constants/` - Shared constants (messageTags, worktreePatterns) + +## IPC Organization +Handlers in `ipc/` by domain: +- `projects.ts` - Project listing +- `sessions.ts` - Session operations +- `search.ts` - Search functionality +- `subagents.ts` - Subagent details +- `validation.ts` - Path validation +- `utility.ts` - Shell & file operations +- `config.ts` - Configuration +- `notifications.ts` - Notifications + +## Adding IPC Handler +1. Add to domain file in `ipc/` +2. If new domain, create file and register in `handlers.ts` +3. Add type in `preload/index.ts` +4. Implement in appropriate service + +## File Watching +FileWatcher service monitors session files with 100ms debounce. +Notifies renderer of changes via IPC events. diff --git a/src/main/constants/messageTags.ts b/src/main/constants/messageTags.ts new file mode 100644 index 00000000..d5520132 --- /dev/null +++ b/src/main/constants/messageTags.ts @@ -0,0 +1,46 @@ +/** + * Message Tag Constants + * + * Centralized XML tag string literals used in message parsing and filtering. + */ + +// ============================================================================= +// System Output Tags +// ============================================================================= + +/** Local command stdout wrapper tag */ +export const LOCAL_COMMAND_STDOUT_TAG = ''; + +/** Local command stderr wrapper tag */ +export const LOCAL_COMMAND_STDERR_TAG = ''; + +/** Local command caveat wrapper tag */ +const LOCAL_COMMAND_CAVEAT_TAG = ''; + +/** System reminder wrapper tag */ +const SYSTEM_REMINDER_TAG = ''; + +// ============================================================================= +// Empty Output Tags +// ============================================================================= + +/** Empty stdout output */ +export const EMPTY_STDOUT = ''; + +/** Empty stderr output */ +export const EMPTY_STDERR = ''; + +// ============================================================================= +// Tag Arrays for Filtering +// ============================================================================= + +/** Tags that indicate system output (excludes from User chunks) */ +export const SYSTEM_OUTPUT_TAGS = [ + LOCAL_COMMAND_STDERR_TAG, + LOCAL_COMMAND_STDOUT_TAG, + LOCAL_COMMAND_CAVEAT_TAG, + SYSTEM_REMINDER_TAG, +] as const; + +/** Tags that indicate hard noise (messages filtered completely) */ +export const HARD_NOISE_TAGS = [LOCAL_COMMAND_CAVEAT_TAG, SYSTEM_REMINDER_TAG] as const; diff --git a/src/main/constants/worktreePatterns.ts b/src/main/constants/worktreePatterns.ts new file mode 100644 index 00000000..f2ed2c02 --- /dev/null +++ b/src/main/constants/worktreePatterns.ts @@ -0,0 +1,44 @@ +/** + * Worktree Pattern Constants + * + * Centralized worktree-related string literals to avoid duplication. + * These are used in GitIdentityResolver for detecting worktree sources and paths. + */ + +// ============================================================================= +// Directory Names +// ============================================================================= + +/** Standard git worktrees subdirectory */ +export const WORKTREES_DIR = 'worktrees'; + +/** Workspaces directory (used by conductor) */ +export const WORKSPACES_DIR = 'workspaces'; + +/** Tasks directory (used by auto-claude) */ +export const TASKS_DIR = 'tasks'; + +// ============================================================================= +// Worktree Source Identifiers +// ============================================================================= + +/** Cursor editor worktrees directory */ +export const CURSOR_DIR = '.cursor'; + +/** Vibe Kanban worktree source */ +export const VIBE_KANBAN_DIR = 'vibe-kanban'; + +/** Conductor worktree source */ +export const CONDUCTOR_DIR = 'conductor'; + +/** Auto-Claude worktree source */ +export const AUTO_CLAUDE_DIR = '.auto-claude'; + +/** 21st/1code worktree source */ +export const TWENTYFIRST_DIR = '.21st'; + +/** Claude Desktop worktrees directory */ +export const CLAUDE_WORKTREES_DIR = '.claude-worktrees'; + +/** ccswitch worktrees directory */ +export const CCSWITCH_DIR = '.ccswitch'; diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 00000000..6aa253de --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,295 @@ +/** + * Main process entry point for Claude Code Context. + * + * Responsibilities: + * - Initialize Electron app and main window + * - Set up IPC handlers for data access + * - Initialize services (ProjectScanner, SessionParser, etc.) + * - Start file watcher for live updates + * - Manage application lifecycle + */ + +import { + CACHE_CLEANUP_INTERVAL_MINUTES, + CACHE_TTL_MINUTES, + DEFAULT_WINDOW_HEIGHT, + DEFAULT_WINDOW_WIDTH, + DEV_SERVER_PORT, + getTrafficLightPositionForZoom, + MAX_CACHE_SESSIONS, + WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL, +} from '@shared/constants'; +import { createLogger } from '@shared/utils/logger'; +import { app, BrowserWindow } from 'electron'; +import { join } from 'path'; + +import { initializeIpcHandlers, removeIpcHandlers } from './ipc/handlers'; + +// Icon path - works for both dev and production +const getIconPath = (): string => { + const isDev = process.env.NODE_ENV === 'development'; + if (isDev) { + return join(process.cwd(), 'resources/icon.png'); + } + return join(__dirname, '../../resources/icon.png'); +}; + +const logger = createLogger('App'); +import { + ChunkBuilder, + configManager, + DataCache, + FileWatcher, + NotificationManager, + ProjectScanner, + SessionParser, + SubagentResolver, +} from './services'; + +// ============================================================================= +// Application State +// ============================================================================= + +let mainWindow: BrowserWindow | null = null; + +// Service instances +let projectScanner: ProjectScanner; +let sessionParser: SessionParser; +let subagentResolver: SubagentResolver; +let chunkBuilder: ChunkBuilder; +let dataCache: DataCache; +let fileWatcher: FileWatcher; +let notificationManager: NotificationManager; +let cleanupInterval: NodeJS.Timeout | null = null; + +/** + * Initializes all services. + */ +function initializeServices(): void { + logger.info('Initializing services...'); + + // Initialize services (paths are set automatically from environment) + projectScanner = new ProjectScanner(); + sessionParser = new SessionParser(projectScanner); + subagentResolver = new SubagentResolver(projectScanner); + chunkBuilder = new ChunkBuilder(); + const disableCache = process.env.CLAUDE_CONTEXT_DISABLE_CACHE === '1'; + dataCache = new DataCache(MAX_CACHE_SESSIONS, CACHE_TTL_MINUTES, !disableCache); + + logger.info(`Projects directory: ${projectScanner.getProjectsDir()}`); + + // Initialize IPC handlers + initializeIpcHandlers(projectScanner, sessionParser, subagentResolver, chunkBuilder, dataCache); + + // Initialize notification manager using singleton pattern + // This ensures IPC handlers and FileWatcher use the same instance + // Note: mainWindow will be set later via setMainWindow() when window is created + notificationManager = NotificationManager.getInstance(); + + // Start file watcher with notification manager for error detection + fileWatcher = new FileWatcher(dataCache); + fileWatcher.setNotificationManager(notificationManager); + fileWatcher.start(); + + // Forward file change events to renderer + // Note: Error detection is handled internally by FileWatcher via NotificationManager + fileWatcher.on('file-change', (event) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('file-change', event); + } + }); + + fileWatcher.on('todo-change', (event) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send('todo-change', event); + } + }); + + // Start automatic cache cleanup + cleanupInterval = dataCache.startAutoCleanup(CACHE_CLEANUP_INTERVAL_MINUTES); + + logger.info('Services initialized successfully'); +} + +/** + * Shuts down all services. + */ +function shutdownServices(): void { + logger.info('Shutting down services...'); + + // Stop file watcher + if (fileWatcher) { + fileWatcher.stop(); + } + + // Stop cache cleanup + if (cleanupInterval) { + clearInterval(cleanupInterval); + cleanupInterval = null; + } + + // Remove IPC handlers + removeIpcHandlers(); + + logger.info('Services shut down successfully'); +} + +/** + * Update native traffic-light position and notify renderer of the current zoom factor. + */ +function syncTrafficLightPosition(win: BrowserWindow): void { + const zoomFactor = win.webContents.getZoomFactor(); + const position = getTrafficLightPositionForZoom(zoomFactor); + win.setWindowButtonPosition(position); + win.webContents.send(WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL, zoomFactor); +} + +/** + * Creates the main application window. + */ +function createWindow(): void { + mainWindow = new BrowserWindow({ + width: DEFAULT_WINDOW_WIDTH, + height: DEFAULT_WINDOW_HEIGHT, + icon: getIconPath(), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + nodeIntegration: false, + contextIsolation: true, + }, + backgroundColor: '#1a1a1a', + titleBarStyle: 'hidden', + trafficLightPosition: getTrafficLightPositionForZoom(1), + title: 'Claude Code Context', + }); + + // Load the renderer + if (process.env.NODE_ENV === 'development') { + void mainWindow.loadURL(`http://localhost:${DEV_SERVER_PORT}`); + mainWindow.webContents.openDevTools(); + } else { + void mainWindow.loadFile(join(__dirname, '../renderer/index.html')); + } + + // Set traffic light position + notify renderer on first load + mainWindow.webContents.on('did-finish-load', () => { + if (mainWindow && !mainWindow.isDestroyed()) { + syncTrafficLightPosition(mainWindow); + } + }); + + // Sync traffic light position when zoom changes (Cmd+/-, Cmd+0) + // zoom-changed event doesn't fire in Electron 40, so we detect zoom keys directly. + // Also keeps zoom bounds within a practical readability range. + const MIN_ZOOM_LEVEL = -3; // ~70% + const MAX_ZOOM_LEVEL = 5; + const ZOOM_IN_KEYS = new Set(['+', '=']); + const ZOOM_OUT_KEYS = new Set(['-', '_']); + mainWindow.webContents.on('before-input-event', (event, input) => { + if (!mainWindow || mainWindow.isDestroyed()) return; + if (!input.meta || input.type !== 'keyDown') return; + + const currentLevel = mainWindow.webContents.getZoomLevel(); + + // Block zoom-out beyond minimum + if (ZOOM_OUT_KEYS.has(input.key) && currentLevel <= MIN_ZOOM_LEVEL) { + event.preventDefault(); + return; + } + // Block zoom-in beyond maximum + if (ZOOM_IN_KEYS.has(input.key) && currentLevel >= MAX_ZOOM_LEVEL) { + event.preventDefault(); + return; + } + + // For zoom keys (including Cmd+0 reset), defer sync until zoom is applied + if (ZOOM_IN_KEYS.has(input.key) || ZOOM_OUT_KEYS.has(input.key) || input.key === '0') { + setTimeout(() => { + if (mainWindow && !mainWindow.isDestroyed()) { + syncTrafficLightPosition(mainWindow); + } + }, 100); + } + }); + + mainWindow.on('closed', () => { + mainWindow = null; + // Clear main window reference from notification manager + if (notificationManager) { + notificationManager.setMainWindow(null); + } + }); + + // Handle renderer process crashes (render-process-gone replaces deprecated 'crashed' event) + mainWindow.webContents.on('render-process-gone', (_event, details) => { + logger.error('Renderer process gone:', details.reason, details.exitCode); + // Could show an error dialog or attempt to reload the window + }); + + // Set main window reference for notification manager + if (notificationManager) { + notificationManager.setMainWindow(mainWindow); + } + + logger.info('Main window created'); +} + +/** + * Application ready handler. + */ +void app.whenReady().then(() => { + logger.info('App ready, initializing...'); + + // Initialize services first + initializeServices(); + + // Apply configuration settings + const config = configManager.getConfig(); + + // Apply launch at login setting + app.setLoginItemSettings({ + openAtLogin: config.general.launchAtLogin, + }); + + // Apply dock visibility and icon (macOS) + if (process.platform === 'darwin') { + if (!config.general.showDockIcon) { + app.dock?.hide(); + } + // Set dock icon + app.dock?.setIcon(getIconPath()); + } + + // Then create window + createWindow(); + + // Listen for notification click events + notificationManager.on('notification-clicked', (_error) => { + if (mainWindow) { + mainWindow.show(); + mainWindow.focus(); + } + }); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); + +/** + * All windows closed handler. + */ +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +/** + * Before quit handler - cleanup. + */ +app.on('before-quit', () => { + shutdownServices(); +}); diff --git a/src/main/ipc/CLAUDE.md b/src/main/ipc/CLAUDE.md new file mode 100644 index 00000000..1462ee87 --- /dev/null +++ b/src/main/ipc/CLAUDE.md @@ -0,0 +1,57 @@ +# IPC Handlers + +Domain-organized IPC request handlers for main process. + +## Structure +``` +ipc/ +├── handlers.ts # Initialization and registration +├── config.ts # App configuration handlers +├── configValidation.ts # Config input validation/sanitization +├── guards.ts # IPC argument type guards +├── notifications.ts # Notification management +├── projects.ts # Project listing, repository grouping +├── search.ts # Session content search +├── sessions.ts # Session operations, pagination +├── subagents.ts # Subagent detail drill-down +├── utility.ts # Shell operations, file reading +└── validation.ts # Path validation, file mentioning +``` + +## Handler Pattern +Each domain module exports: +```typescript +// Setup with services +initialize{Domain}Handlers(services) + +// Register with ipcMain +register{Domain}Handlers(ipcMain) + +// Cleanup on app quit +remove{Domain}Handlers(ipcMain) +``` + +## Service Dependencies +`initializeIpcHandlers()` receives service instances: +- `ProjectScanner` - File system scanning +- `SessionParser` - JSONL parsing +- `SubagentResolver` - Subagent linking +- `ChunkBuilder` - Chunk analysis +- `DataCache` - Result caching + +## Response Pattern +Config handlers use `IpcResult` wrapper: +```typescript +return { success: true, data: result }; +return { success: false, error: message }; +``` + +Other handlers return data directly or `null` on error. + +## Adding New Handler +1. Add to existing domain file or create new file +2. Call `initialize{Domain}Handlers()` if new domain +3. Add `register/remove{Domain}Handlers` in `handlers.ts` +4. Add channel constant in `preload/constants/ipcChannels.ts` +5. Add method to ElectronAPI in `preload/index.ts` +6. Implement service logic in `src/main/services/` diff --git a/src/main/ipc/config.ts b/src/main/ipc/config.ts new file mode 100644 index 00000000..ecb2976c --- /dev/null +++ b/src/main/ipc/config.ts @@ -0,0 +1,628 @@ +/** + * IPC Handlers for App Configuration. + * + * Handlers: + * - config:get: Get full app configuration + * - config:update: Update a specific config section + * - config:addIgnoreRegex: Add an ignore pattern for notifications + * - config:removeIgnoreRegex: Remove an ignore pattern + * - config:addIgnoreRepository: Add a repository to ignore list + * - config:removeIgnoreRepository: Remove a repository from ignore list + * - config:snooze: Set snooze duration for notifications + * - config:clearSnooze: Clear the snooze timer + * - config:addTrigger: Add a new notification trigger + * - config:updateTrigger: Update an existing notification trigger + * - config:removeTrigger: Remove a notification trigger + * - config:getTriggers: Get all notification triggers + * - config:testTrigger: Test a trigger against historical session data + */ + +import { getErrorMessage } from '@shared/utils/errorHandling'; +import { createLogger } from '@shared/utils/logger'; +import { execFile } from 'child_process'; +import { BrowserWindow, dialog, type IpcMain, type IpcMainInvokeEvent } from 'electron'; + +import { + type AppConfig, + ConfigManager, + type NotificationTrigger, + type TriggerContentType, + type TriggerMatchField, + type TriggerMode, + type TriggerTokenType, +} from '../services'; + +import { validateConfigUpdatePayload } from './configValidation'; +import { validateTriggerId } from './guards'; + +import type { TriggerColor } from '@shared/constants/triggerColors'; + +const logger = createLogger('IPC:config'); + +// Get singleton instance +const configManager = ConfigManager.getInstance(); + +/** + * Response type for config operations + */ +interface ConfigResult { + success: boolean; + data?: T; + error?: string; +} + +/** + * Registers all config-related IPC handlers. + */ +export function registerConfigHandlers(ipcMain: IpcMain): void { + // Get full configuration + ipcMain.handle('config:get', handleGetConfig); + + // Update configuration section + ipcMain.handle('config:update', handleUpdateConfig); + + // Ignore regex pattern handlers + ipcMain.handle('config:addIgnoreRegex', handleAddIgnoreRegex); + ipcMain.handle('config:removeIgnoreRegex', handleRemoveIgnoreRegex); + + // Ignore repository handlers + ipcMain.handle('config:addIgnoreRepository', handleAddIgnoreRepository); + ipcMain.handle('config:removeIgnoreRepository', handleRemoveIgnoreRepository); + + // Snooze handlers + ipcMain.handle('config:snooze', handleSnooze); + ipcMain.handle('config:clearSnooze', handleClearSnooze); + + // Trigger management handlers + ipcMain.handle('config:addTrigger', handleAddTrigger); + ipcMain.handle('config:updateTrigger', handleUpdateTrigger); + ipcMain.handle('config:removeTrigger', handleRemoveTrigger); + ipcMain.handle('config:getTriggers', handleGetTriggers); + ipcMain.handle('config:testTrigger', handleTestTrigger); + + // Session pin handlers + ipcMain.handle('config:pinSession', handlePinSession); + ipcMain.handle('config:unpinSession', handleUnpinSession); + + // Dialog handlers + ipcMain.handle('config:selectFolders', handleSelectFolders); + + // Editor handlers + ipcMain.handle('config:openInEditor', handleOpenInEditor); + + logger.info('Config handlers registered (including trigger management)'); +} + +// ============================================================================= +// Handler Functions +// ============================================================================= + +/** + * Handler for 'config:get' IPC call. + * Returns the full app configuration. + */ +async function handleGetConfig(_event: IpcMainInvokeEvent): Promise> { + try { + const config = configManager.getConfig(); + return { success: true, data: config }; + } catch (error) { + logger.error('Error in config:get:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:update' IPC call. + * Updates a specific section of the configuration. + * Returns the full updated config. + */ +async function handleUpdateConfig( + _event: IpcMainInvokeEvent, + section: unknown, + data: unknown +): Promise> { + try { + const validation = validateConfigUpdatePayload(section, data); + if (!validation.valid) { + return { success: false, error: validation.error }; + } + + configManager.updateConfig(validation.section, validation.data); + const updatedConfig = configManager.getConfig(); + return { success: true, data: updatedConfig }; + } catch (error) { + logger.error('Error in config:update:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:addIgnoreRegex' IPC call. + * Adds a regex pattern to the notification ignore list. + */ +async function handleAddIgnoreRegex( + _event: IpcMainInvokeEvent, + pattern: string +): Promise { + try { + if (!pattern || typeof pattern !== 'string') { + return { success: false, error: 'Pattern is required and must be a string' }; + } + + // Validate that the pattern is a valid regex + try { + new RegExp(pattern); + } catch { + return { success: false, error: 'Invalid regex pattern' }; + } + + configManager.addIgnoreRegex(pattern); + return { success: true }; + } catch (error) { + logger.error('Error in config:addIgnoreRegex:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:removeIgnoreRegex' IPC call. + * Removes a regex pattern from the notification ignore list. + */ +async function handleRemoveIgnoreRegex( + _event: IpcMainInvokeEvent, + pattern: string +): Promise { + try { + if (!pattern || typeof pattern !== 'string') { + return { success: false, error: 'Pattern is required and must be a string' }; + } + + configManager.removeIgnoreRegex(pattern); + return { success: true }; + } catch (error) { + logger.error('Error in config:removeIgnoreRegex:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:addIgnoreRepository' IPC call. + * Adds a repository to the notification ignore list. + */ +async function handleAddIgnoreRepository( + _event: IpcMainInvokeEvent, + repositoryId: string +): Promise { + try { + if (!repositoryId || typeof repositoryId !== 'string') { + return { success: false, error: 'Repository ID is required and must be a string' }; + } + + configManager.addIgnoreRepository(repositoryId); + return { success: true }; + } catch (error) { + logger.error('Error in config:addIgnoreRepository:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:removeIgnoreRepository' IPC call. + * Removes a repository from the notification ignore list. + */ +async function handleRemoveIgnoreRepository( + _event: IpcMainInvokeEvent, + repositoryId: string +): Promise { + try { + if (!repositoryId || typeof repositoryId !== 'string') { + return { success: false, error: 'Repository ID is required and must be a string' }; + } + + configManager.removeIgnoreRepository(repositoryId); + return { success: true }; + } catch (error) { + logger.error('Error in config:removeIgnoreRepository:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:snooze' IPC call. + * Sets the snooze timer for notifications. + */ +async function handleSnooze(_event: IpcMainInvokeEvent, minutes: number): Promise { + try { + if (typeof minutes !== 'number' || minutes <= 0 || minutes > 24 * 60) { + return { success: false, error: 'Minutes must be a positive number' }; + } + + configManager.setSnooze(minutes); + return { success: true }; + } catch (error) { + logger.error('Error in config:snooze:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:clearSnooze' IPC call. + * Clears the snooze timer. + */ +async function handleClearSnooze(_event: IpcMainInvokeEvent): Promise { + try { + configManager.clearSnooze(); + return { success: true }; + } catch (error) { + logger.error('Error in config:clearSnooze:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:addTrigger' - Adds a new notification trigger. + */ +async function handleAddTrigger( + _event: IpcMainInvokeEvent, + trigger: { + id: string; + name: string; + enabled: boolean; + contentType: string; + mode?: TriggerMode; + requireError?: boolean; + toolName?: string; + matchField?: string; + matchPattern?: string; + ignorePatterns?: string[]; + tokenThreshold?: number; + tokenType?: TriggerTokenType; + repositoryIds?: string[]; + color?: string; + } +): Promise { + try { + if (!trigger.id || !trigger.name || !trigger.contentType) { + return { + success: false, + error: 'Trigger must have id, name, and contentType', + }; + } + + configManager.addTrigger({ + id: trigger.id, + name: trigger.name, + enabled: trigger.enabled, + contentType: trigger.contentType as TriggerContentType, + mode: trigger.mode ?? (trigger.requireError ? 'error_status' : 'content_match'), + requireError: trigger.requireError, + toolName: trigger.toolName, + matchField: trigger.matchField as TriggerMatchField | undefined, + matchPattern: trigger.matchPattern, + ignorePatterns: trigger.ignorePatterns, + tokenThreshold: trigger.tokenThreshold, + tokenType: trigger.tokenType, + repositoryIds: trigger.repositoryIds, + color: trigger.color as TriggerColor | undefined, + isBuiltin: false, + }); + + return { success: true }; + } catch (error) { + logger.error('Error in config:addTrigger:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to add trigger', + }; + } +} + +/** + * Handler for 'config:updateTrigger' - Updates an existing notification trigger. + */ +async function handleUpdateTrigger( + _event: IpcMainInvokeEvent, + triggerId: string, + updates: Partial<{ + name: string; + enabled: boolean; + contentType: string; + requireError: boolean; + toolName: string; + matchField: string; + matchPattern: string; + ignorePatterns: string[]; + mode: TriggerMode; + tokenThreshold: number; + tokenType: TriggerTokenType; + repositoryIds: string[]; + color: string; + }> +): Promise { + try { + const validatedTriggerId = validateTriggerId(triggerId); + if (!validatedTriggerId.valid) { + return { + success: false, + error: validatedTriggerId.error ?? 'Trigger ID is required', + }; + } + + configManager.updateTrigger(validatedTriggerId.value!, updates as Partial); + + return { success: true }; + } catch (error) { + logger.error('Error in config:updateTrigger:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to update trigger', + }; + } +} + +/** + * Handler for 'config:removeTrigger' - Removes a notification trigger. + */ +async function handleRemoveTrigger( + _event: IpcMainInvokeEvent, + triggerId: string +): Promise { + try { + const validatedTriggerId = validateTriggerId(triggerId); + if (!validatedTriggerId.valid) { + return { + success: false, + error: validatedTriggerId.error ?? 'Trigger ID is required', + }; + } + + configManager.removeTrigger(validatedTriggerId.value!); + + return { success: true }; + } catch (error) { + logger.error('Error in config:removeTrigger:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to remove trigger', + }; + } +} + +/** + * Handler for 'config:getTriggers' - Gets all notification triggers. + */ +async function handleGetTriggers( + _event: IpcMainInvokeEvent +): Promise> { + try { + const triggers = configManager.getTriggers(); + + return { success: true, data: triggers }; + } catch (error) { + logger.error('Error in config:getTriggers:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to get triggers', + }; + } +} + +/** + * Handler for 'config:testTrigger' - Tests a trigger against historical session data. + * Returns errors that would have been detected by the trigger. + * + * Safety: Results are truncated if: + * - More than 10,000 total matches found + * - More than 100 sessions scanned + * - Test runs longer than 30 seconds + */ +async function handleTestTrigger( + _event: IpcMainInvokeEvent, + trigger: NotificationTrigger +): Promise< + ConfigResult<{ + totalCount: number; + errors: { + id: string; + sessionId: string; + projectId: string; + message: string; + timestamp: number; + source: string; + toolUseId?: string; + subagentId?: string; + lineNumber?: number; + context: { projectName: string }; + }[]; + /** True if results were truncated due to safety limits */ + truncated?: boolean; + }> +> { + try { + const { errorDetector } = await import('../services'); + const result = await errorDetector.testTrigger(trigger, 50); + + // Map the DetectedError objects to the format expected by the renderer + // Include toolUseId, subagentId, and lineNumber for deep linking to exact error location + const errors = result.errors.map((error) => ({ + id: error.id, + sessionId: error.sessionId, + projectId: error.projectId, + message: error.message, + timestamp: error.timestamp, + source: error.source, + toolUseId: error.toolUseId, + subagentId: error.subagentId, + lineNumber: error.lineNumber, + context: { projectName: error.context.projectName }, + })); + + return { + success: true, + data: { totalCount: result.totalCount, errors, truncated: result.truncated }, + }; + } catch (error) { + logger.error('Error in config:testTrigger:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to test trigger', + }; + } +} + +/** + * Handler for 'config:pinSession' - Pins a session for a project. + */ +async function handlePinSession( + _event: IpcMainInvokeEvent, + projectId: string, + sessionId: string +): Promise { + try { + if (!projectId || typeof projectId !== 'string') { + return { success: false, error: 'Project ID is required and must be a string' }; + } + if (!sessionId || typeof sessionId !== 'string') { + return { success: false, error: 'Session ID is required and must be a string' }; + } + + configManager.pinSession(projectId, sessionId); + return { success: true }; + } catch (error) { + logger.error('Error in config:pinSession:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:unpinSession' - Unpins a session for a project. + */ +async function handleUnpinSession( + _event: IpcMainInvokeEvent, + projectId: string, + sessionId: string +): Promise { + try { + if (!projectId || typeof projectId !== 'string') { + return { success: false, error: 'Project ID is required and must be a string' }; + } + if (!sessionId || typeof sessionId !== 'string') { + return { success: false, error: 'Session ID is required and must be a string' }; + } + + configManager.unpinSession(projectId, sessionId); + return { success: true }; + } catch (error) { + logger.error('Error in config:unpinSession:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:openInEditor' - Opens the config JSON file in an external editor. + * Tries editors in order: $VISUAL, $EDITOR, cursor, code, then falls back to system open. + */ +async function handleOpenInEditor(_event: IpcMainInvokeEvent): Promise { + try { + const configPath = configManager.getConfigPath(); + + // Try editors in priority order + const editors: string[] = []; + if (process.env.VISUAL) editors.push(process.env.VISUAL); + if (process.env.EDITOR) editors.push(process.env.EDITOR); + editors.push('cursor', 'code', 'subl', 'zed'); + + for (const editor of editors) { + try { + await new Promise((resolve, reject) => { + const child = execFile(editor, [configPath], { timeout: 5000 }); + // If the process spawns successfully, resolve after a short delay + // (editors typically fork and the parent exits quickly) + const timer = setTimeout(() => resolve(), 500); + child.on('error', (err) => { + clearTimeout(timer); + reject(err); + }); + }); + return { success: true }; + } catch { + // Editor not found, try next + continue; + } + } + + // Fallback: open with system default + const { shell } = await import('electron'); + const errorMessage = await shell.openPath(configPath); + if (errorMessage) { + return { success: false, error: errorMessage }; + } + return { success: true }; + } catch (error) { + logger.error('Error in config:openInEditor:', error); + return { success: false, error: getErrorMessage(error) }; + } +} + +/** + * Handler for 'config:selectFolders' - Opens native folder selection dialog. + * Allows users to select one or more folders for trigger project scope. + */ +async function handleSelectFolders(_event: IpcMainInvokeEvent): Promise> { + try { + // Get the focused window for proper dialog parenting + const focusedWindow = BrowserWindow.getFocusedWindow(); + + // dialog.showOpenDialog accepts either (options) or (window, options) + const dialogOptions: Electron.OpenDialogOptions = { + properties: ['openDirectory', 'multiSelections'], + title: 'Select Project Folders', + buttonLabel: 'Select', + }; + + const result = focusedWindow + ? await dialog.showOpenDialog(focusedWindow, dialogOptions) + : await dialog.showOpenDialog(dialogOptions); + + if (result.canceled) { + return { success: true, data: [] }; + } + + return { success: true, data: result.filePaths }; + } catch (error) { + logger.error('Error in config:selectFolders:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to open folder dialog', + }; + } +} + +// ============================================================================= +// Cleanup +// ============================================================================= + +/** + * Removes all config-related IPC handlers. + * Should be called when shutting down. + */ +export function removeConfigHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('config:get'); + ipcMain.removeHandler('config:update'); + ipcMain.removeHandler('config:addIgnoreRegex'); + ipcMain.removeHandler('config:removeIgnoreRegex'); + ipcMain.removeHandler('config:addIgnoreRepository'); + ipcMain.removeHandler('config:removeIgnoreRepository'); + ipcMain.removeHandler('config:snooze'); + ipcMain.removeHandler('config:clearSnooze'); + ipcMain.removeHandler('config:addTrigger'); + ipcMain.removeHandler('config:updateTrigger'); + ipcMain.removeHandler('config:removeTrigger'); + ipcMain.removeHandler('config:getTriggers'); + ipcMain.removeHandler('config:testTrigger'); + ipcMain.removeHandler('config:pinSession'); + ipcMain.removeHandler('config:unpinSession'); + ipcMain.removeHandler('config:selectFolders'); + ipcMain.removeHandler('config:openInEditor'); + logger.info('Config handlers removed'); +} diff --git a/src/main/ipc/configValidation.ts b/src/main/ipc/configValidation.ts new file mode 100644 index 00000000..205ae670 --- /dev/null +++ b/src/main/ipc/configValidation.ts @@ -0,0 +1,292 @@ +/** + * Runtime validation for config:update IPC payloads. + * Prevents invalid/unknown data from mutating persisted config. + */ + +import type { + AppConfig, + DisplayConfig, + GeneralConfig, + NotificationConfig, + NotificationTrigger, +} from '../services'; + +type ConfigSection = keyof AppConfig; + +interface ValidationSuccess { + valid: true; + section: K; + data: Partial; +} + +interface ValidationFailure { + valid: false; + error: string; +} + +export type ConfigUpdateValidationResult = + | ValidationSuccess<'notifications'> + | ValidationSuccess<'general'> + | ValidationSuccess<'display'> + | ValidationFailure; + +const VALID_SECTIONS = new Set(['notifications', 'general', 'display']); +const MAX_SNOOZE_MINUTES = 24 * 60; + +function isPlainObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function isStringArray(value: unknown): value is string[] { + return Array.isArray(value) && value.every((item) => typeof item === 'string'); +} + +function isFiniteNumber(value: unknown): value is number { + return typeof value === 'number' && Number.isFinite(value); +} + +function isValidTrigger(trigger: unknown): trigger is NotificationTrigger { + if (!isPlainObject(trigger)) { + return false; + } + + if (typeof trigger.id !== 'string' || trigger.id.trim().length === 0) { + return false; + } + + if (typeof trigger.name !== 'string' || trigger.name.trim().length === 0) { + return false; + } + + if (typeof trigger.enabled !== 'boolean') { + return false; + } + + if ( + trigger.contentType !== 'tool_result' && + trigger.contentType !== 'tool_use' && + trigger.contentType !== 'thinking' && + trigger.contentType !== 'text' + ) { + return false; + } + + if ( + trigger.mode !== 'error_status' && + trigger.mode !== 'content_match' && + trigger.mode !== 'token_threshold' + ) { + return false; + } + + return true; +} + +function validateNotificationsSection( + data: unknown +): ValidationSuccess<'notifications'> | ValidationFailure { + if (!isPlainObject(data)) { + return { valid: false, error: 'notifications update must be an object' }; + } + + const allowedKeys: (keyof NotificationConfig)[] = [ + 'enabled', + 'soundEnabled', + 'includeSubagentErrors', + 'ignoredRegex', + 'ignoredRepositories', + 'snoozedUntil', + 'snoozeMinutes', + 'triggers', + ]; + + const result: Partial = {}; + + for (const [key, value] of Object.entries(data)) { + if (!allowedKeys.includes(key as keyof NotificationConfig)) { + return { + valid: false, + error: `notifications.${key} is not supported via config:update`, + }; + } + + switch (key as keyof NotificationConfig) { + case 'enabled': + if (typeof value !== 'boolean') { + return { valid: false, error: `notifications.${key} must be a boolean` }; + } + result.enabled = value; + break; + case 'soundEnabled': + if (typeof value !== 'boolean') { + return { valid: false, error: `notifications.${key} must be a boolean` }; + } + result.soundEnabled = value; + break; + case 'includeSubagentErrors': + if (typeof value !== 'boolean') { + return { valid: false, error: `notifications.${key} must be a boolean` }; + } + result.includeSubagentErrors = value; + break; + case 'ignoredRegex': + if (!isStringArray(value)) { + return { valid: false, error: `notifications.${key} must be a string[]` }; + } + result.ignoredRegex = value; + break; + case 'ignoredRepositories': + if (!isStringArray(value)) { + return { valid: false, error: `notifications.${key} must be a string[]` }; + } + result.ignoredRepositories = value; + break; + case 'snoozedUntil': + if (value !== null && !isFiniteNumber(value)) { + return { valid: false, error: 'notifications.snoozedUntil must be a number or null' }; + } + if (typeof value === 'number' && value < 0) { + return { valid: false, error: 'notifications.snoozedUntil must be >= 0' }; + } + result.snoozedUntil = value; + break; + case 'snoozeMinutes': + if (!isFiniteNumber(value) || !Number.isInteger(value)) { + return { valid: false, error: 'notifications.snoozeMinutes must be an integer' }; + } + if (value <= 0 || value > MAX_SNOOZE_MINUTES) { + return { + valid: false, + error: `notifications.snoozeMinutes must be between 1 and ${MAX_SNOOZE_MINUTES}`, + }; + } + result.snoozeMinutes = value; + break; + case 'triggers': + if (!Array.isArray(value) || !value.every((trigger) => isValidTrigger(trigger))) { + return { valid: false, error: 'notifications.triggers must be a valid trigger[]' }; + } + result.triggers = value; + break; + default: + return { valid: false, error: `Unsupported notifications key: ${key}` }; + } + } + + return { + valid: true, + section: 'notifications', + data: result, + }; +} + +function validateGeneralSection(data: unknown): ValidationSuccess<'general'> | ValidationFailure { + if (!isPlainObject(data)) { + return { valid: false, error: 'general update must be an object' }; + } + + const allowedKeys: (keyof GeneralConfig)[] = [ + 'launchAtLogin', + 'showDockIcon', + 'theme', + 'defaultTab', + ]; + + const result: Partial = {}; + + for (const [key, value] of Object.entries(data)) { + if (!allowedKeys.includes(key as keyof GeneralConfig)) { + return { valid: false, error: `general.${key} is not a valid setting` }; + } + + switch (key as keyof GeneralConfig) { + case 'launchAtLogin': + if (typeof value !== 'boolean') { + return { valid: false, error: `general.${key} must be a boolean` }; + } + result.launchAtLogin = value; + break; + case 'showDockIcon': + if (typeof value !== 'boolean') { + return { valid: false, error: `general.${key} must be a boolean` }; + } + result.showDockIcon = value; + break; + case 'theme': + if (value !== 'dark' && value !== 'light' && value !== 'system') { + return { valid: false, error: 'general.theme must be one of: dark, light, system' }; + } + result.theme = value; + break; + case 'defaultTab': + if (value !== 'dashboard' && value !== 'last-session') { + return { + valid: false, + error: 'general.defaultTab must be one of: dashboard, last-session', + }; + } + result.defaultTab = value; + break; + default: + return { valid: false, error: `Unsupported general key: ${key}` }; + } + } + + return { + valid: true, + section: 'general', + data: result, + }; +} + +function validateDisplaySection(data: unknown): ValidationSuccess<'display'> | ValidationFailure { + if (!isPlainObject(data)) { + return { valid: false, error: 'display update must be an object' }; + } + + const allowedKeys: (keyof DisplayConfig)[] = [ + 'showTimestamps', + 'compactMode', + 'syntaxHighlighting', + ]; + + const result: Partial = {}; + + for (const [key, value] of Object.entries(data)) { + if (!allowedKeys.includes(key as keyof DisplayConfig)) { + return { valid: false, error: `display.${key} is not a valid setting` }; + } + + if (typeof value !== 'boolean') { + return { valid: false, error: `display.${key} must be a boolean` }; + } + + result[key as keyof DisplayConfig] = value; + } + + return { + valid: true, + section: 'display', + data: result, + }; +} + +export function validateConfigUpdatePayload( + section: unknown, + data: unknown +): ConfigUpdateValidationResult { + if (typeof section !== 'string' || !VALID_SECTIONS.has(section as ConfigSection)) { + return { valid: false, error: 'Section must be one of: notifications, general, display' }; + } + + switch (section as ConfigSection) { + case 'notifications': + return validateNotificationsSection(data); + case 'general': + return validateGeneralSection(data); + case 'display': + return validateDisplaySection(data); + default: + return { valid: false, error: 'Invalid section' }; + } +} diff --git a/src/main/ipc/guards.ts b/src/main/ipc/guards.ts new file mode 100644 index 00000000..b544c151 --- /dev/null +++ b/src/main/ipc/guards.ts @@ -0,0 +1,148 @@ +/** + * IPC guard utilities for runtime validation and coercion. + * + * Main goals: + * - Reject malformed IDs and unbounded inputs at IPC boundaries + * - Keep validation logic consistent across handlers + */ + +import { isValidProjectId } from '@main/utils/pathDecoder'; + +const SESSION_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/; +const SUBAGENT_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/; +const NOTIFICATION_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/; +const TRIGGER_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/; + +const MAX_QUERY_LENGTH = 512; +const MAX_RESULTS = 200; +const MAX_PAGE_LIMIT = 200; + +interface ValidationResult { + valid: boolean; + value?: T; + error?: string; +} + +function validateString( + value: unknown, + fieldName: string, + maxLength: number = 256 +): ValidationResult { + if (typeof value !== 'string') { + return { valid: false, error: `${fieldName} must be a string` }; + } + + const trimmed = value.trim(); + if (trimmed.length === 0) { + return { valid: false, error: `${fieldName} cannot be empty` }; + } + + if (trimmed.length > maxLength) { + return { valid: false, error: `${fieldName} exceeds max length (${maxLength})` }; + } + + return { valid: true, value: trimmed }; +} + +export function validateProjectId(projectId: unknown): ValidationResult { + const basic = validateString(projectId, 'projectId'); + if (!basic.valid) { + return basic; + } + + if (!isValidProjectId(basic.value!)) { + return { valid: false, error: 'projectId is not a valid encoded Claude project path' }; + } + + return { valid: true, value: basic.value }; +} + +export function validateSessionId(sessionId: unknown): ValidationResult { + const basic = validateString(sessionId, 'sessionId', 128); + if (!basic.valid) { + return basic; + } + + if (!SESSION_ID_PATTERN.test(basic.value!)) { + return { valid: false, error: 'sessionId contains invalid characters' }; + } + + return { valid: true, value: basic.value }; +} + +export function validateSubagentId(subagentId: unknown): ValidationResult { + const basic = validateString(subagentId, 'subagentId', 128); + if (!basic.valid) { + return basic; + } + + if (!SUBAGENT_ID_PATTERN.test(basic.value!)) { + return { valid: false, error: 'subagentId contains invalid characters' }; + } + + return { valid: true, value: basic.value }; +} + +export function validateNotificationId(notificationId: unknown): ValidationResult { + const basic = validateString(notificationId, 'notificationId', 128); + if (!basic.valid) { + return basic; + } + + if (!NOTIFICATION_ID_PATTERN.test(basic.value!)) { + return { valid: false, error: 'notificationId contains invalid characters' }; + } + + return { valid: true, value: basic.value }; +} + +export function validateTriggerId(triggerId: unknown): ValidationResult { + const basic = validateString(triggerId, 'triggerId', 128); + if (!basic.valid) { + return basic; + } + + if (!TRIGGER_ID_PATTERN.test(basic.value!)) { + return { valid: false, error: 'triggerId contains invalid characters' }; + } + + return { valid: true, value: basic.value }; +} + +export function validateSearchQuery(query: unknown): ValidationResult { + if (typeof query !== 'string') { + return { valid: false, error: 'query must be a string' }; + } + + const trimmed = query.trim(); + if (trimmed.length === 0) { + return { valid: false, error: 'query cannot be empty' }; + } + + if (trimmed.length > MAX_QUERY_LENGTH) { + return { valid: false, error: `query exceeds max length (${MAX_QUERY_LENGTH})` }; + } + + return { valid: true, value: trimmed }; +} + +function coerceLimit(value: unknown, defaultValue: number, maxValue: number): number { + if (typeof value !== 'number' || !Number.isFinite(value)) { + return defaultValue; + } + + const normalized = Math.floor(value); + if (normalized <= 0) { + return defaultValue; + } + + return Math.min(normalized, maxValue); +} + +export function coerceSearchMaxResults(value: unknown, defaultValue: number = 50): number { + return coerceLimit(value, defaultValue, MAX_RESULTS); +} + +export function coercePageLimit(value: unknown, defaultValue: number = 20): number { + return coerceLimit(value, defaultValue, MAX_PAGE_LIMIT); +} diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts new file mode 100644 index 00000000..cdc6b905 --- /dev/null +++ b/src/main/ipc/handlers.ts @@ -0,0 +1,93 @@ +/** + * IPC Handlers - Orchestrates domain-specific handler modules. + * + * This module initializes and registers all IPC handlers from domain modules: + * - projects.ts: Project listing and repository groups + * - sessions.ts: Session operations and pagination + * - search.ts: Session search functionality + * - subagents.ts: Subagent detail retrieval + * - validation.ts: Path validation and scroll handling + * - utility.ts: Shell operations and file reading + * - notifications.ts: Notification management + * - config.ts: App configuration + */ + +import { createLogger } from '@shared/utils/logger'; +import { ipcMain } from 'electron'; + +import { registerConfigHandlers, removeConfigHandlers } from './config'; + +const logger = createLogger('IPC:handlers'); +import { registerNotificationHandlers, removeNotificationHandlers } from './notifications'; +import { + initializeProjectHandlers, + registerProjectHandlers, + removeProjectHandlers, +} from './projects'; +import { initializeSearchHandlers, registerSearchHandlers, removeSearchHandlers } from './search'; +import { + initializeSessionHandlers, + registerSessionHandlers, + removeSessionHandlers, +} from './sessions'; +import { + initializeSubagentHandlers, + registerSubagentHandlers, + removeSubagentHandlers, +} from './subagents'; +import { registerUtilityHandlers, removeUtilityHandlers } from './utility'; +import { registerValidationHandlers, removeValidationHandlers } from './validation'; + +import type { + ChunkBuilder, + DataCache, + ProjectScanner, + SessionParser, + SubagentResolver, +} from '../services'; + +/** + * Initializes IPC handlers with service instances. + */ +export function initializeIpcHandlers( + scanner: ProjectScanner, + parser: SessionParser, + resolver: SubagentResolver, + builder: ChunkBuilder, + cache: DataCache +): void { + // Initialize domain handlers with their required services + initializeProjectHandlers(scanner); + initializeSessionHandlers(scanner, parser, resolver, builder, cache); + initializeSearchHandlers(scanner); + initializeSubagentHandlers(builder, cache, parser, resolver); + + // Register all handlers + registerProjectHandlers(ipcMain); + registerSessionHandlers(ipcMain); + registerSearchHandlers(ipcMain); + registerSubagentHandlers(ipcMain); + registerValidationHandlers(ipcMain); + registerUtilityHandlers(ipcMain); + registerNotificationHandlers(ipcMain); + registerConfigHandlers(ipcMain); + + logger.info('All handlers registered'); +} + +/** + * Removes all IPC handlers. + * Should be called when shutting down. + */ +export function removeIpcHandlers(): void { + removeProjectHandlers(ipcMain); + removeSessionHandlers(ipcMain); + removeSearchHandlers(ipcMain); + removeSubagentHandlers(ipcMain); + removeValidationHandlers(ipcMain); + removeUtilityHandlers(ipcMain); + removeNotificationHandlers(ipcMain); + removeConfigHandlers(ipcMain); + + logger.info('All handlers removed'); +} diff --git a/src/main/ipc/notifications.ts b/src/main/ipc/notifications.ts new file mode 100644 index 00000000..7f1afe8e --- /dev/null +++ b/src/main/ipc/notifications.ts @@ -0,0 +1,186 @@ +/** + * IPC Handlers for Notification Operations. + * + * Handlers: + * - notifications:get: Get all notifications (paginated) + * - notifications:markRead: Mark notification as read + * - notifications:markAllRead: Mark all as read + * - notifications:delete: Delete a single notification + * - notifications:clear: Clear all notifications + * - notifications:getUnreadCount: Get unread count for badge + */ + +import { getErrorMessage } from '@shared/utils/errorHandling'; +import { createLogger } from '@shared/utils/logger'; +import { type IpcMain, type IpcMainInvokeEvent } from 'electron'; + +import { + type GetNotificationsOptions, + type GetNotificationsResult, + NotificationManager, +} from '../services'; + +import { coercePageLimit, validateNotificationId } from './guards'; + +const logger = createLogger('IPC:notifications'); + +/** + * Registers all notification-related IPC handlers. + * + * @param ipcMain - The Electron IpcMain instance + */ +export function registerNotificationHandlers(ipcMain: IpcMain): void { + ipcMain.handle('notifications:get', handleGetNotifications); + ipcMain.handle('notifications:markRead', handleMarkRead); + ipcMain.handle('notifications:markAllRead', handleMarkAllRead); + ipcMain.handle('notifications:delete', handleDelete); + ipcMain.handle('notifications:clear', handleClear); + ipcMain.handle('notifications:getUnreadCount', handleGetUnreadCount); + + logger.info('Notification handlers registered'); +} + +/** + * Removes all notification IPC handlers. + * Should be called when shutting down. + */ +export function removeNotificationHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('notifications:get'); + ipcMain.removeHandler('notifications:markRead'); + ipcMain.removeHandler('notifications:markAllRead'); + ipcMain.removeHandler('notifications:delete'); + ipcMain.removeHandler('notifications:clear'); + ipcMain.removeHandler('notifications:getUnreadCount'); + + logger.info('Notification handlers removed'); +} + +// ============================================================================= +// Handler Implementations +// ============================================================================= + +/** + * Handler for 'notifications:get' IPC call. + * Gets all notifications with optional pagination and filtering. + */ +async function handleGetNotifications( + _event: IpcMainInvokeEvent, + options?: GetNotificationsOptions +): Promise { + try { + const opts = options ?? {}; + const safeOptions: GetNotificationsOptions = { + limit: coercePageLimit(opts.limit, 20), + offset: + typeof opts.offset === 'number' && Number.isFinite(opts.offset) && opts.offset >= 0 + ? Math.floor(opts.offset) + : 0, + }; + const manager = NotificationManager.getInstance(); + const result = await manager.getNotifications(safeOptions); + return result; + } catch (error) { + logger.error('Error in notifications:get:', getErrorMessage(error)); + return { + notifications: [], + total: 0, + totalCount: 0, + unreadCount: 0, + hasMore: false, + }; + } +} + +/** + * Handler for 'notifications:markRead' IPC call. + * Marks a specific notification as read. + */ +async function handleMarkRead( + _event: IpcMainInvokeEvent, + notificationId: string +): Promise { + try { + const validatedNotification = validateNotificationId(notificationId); + if (!validatedNotification.valid) { + logger.error( + `notifications:markRead rejected: ${validatedNotification.error ?? 'Invalid notificationId'}` + ); + return false; + } + + const manager = NotificationManager.getInstance(); + const success = await manager.markRead(validatedNotification.value!); + return success; + } catch (error) { + logger.error(`Error in notifications:markRead for ${notificationId}:`, error); + return false; + } +} + +/** + * Handler for 'notifications:markAllRead' IPC call. + * Marks all notifications as read. + */ +async function handleMarkAllRead(_event: IpcMainInvokeEvent): Promise { + try { + const manager = NotificationManager.getInstance(); + const success = await manager.markAllRead(); + return success; + } catch (error) { + logger.error('Error in notifications:markAllRead:', error); + return false; + } +} + +/** + * Handler for 'notifications:delete' IPC call. + * Deletes a single notification. + */ +async function handleDelete(_event: IpcMainInvokeEvent, notificationId: string): Promise { + try { + const validatedNotification = validateNotificationId(notificationId); + if (!validatedNotification.valid) { + logger.error( + `notifications:delete rejected: ${validatedNotification.error ?? 'Invalid notificationId'}` + ); + return false; + } + + const manager = NotificationManager.getInstance(); + const success = manager.deleteNotification(validatedNotification.value!); + return success; + } catch (error) { + logger.error(`Error in notifications:delete for ${notificationId}:`, error); + return false; + } +} + +/** + * Handler for 'notifications:clear' IPC call. + * Clears all notifications. + */ +async function handleClear(_event: IpcMainInvokeEvent): Promise { + try { + const manager = NotificationManager.getInstance(); + const success = await manager.clearAll(); + return success; + } catch (error) { + logger.error('Error in notifications:clear:', error); + return false; + } +} + +/** + * Handler for 'notifications:getUnreadCount' IPC call. + * Gets the count of unread notifications for badge display. + */ +async function handleGetUnreadCount(_event: IpcMainInvokeEvent): Promise { + try { + const manager = NotificationManager.getInstance(); + const count = await manager.getUnreadCount(); + return count; + } catch (error) { + logger.error('Error in notifications:getUnreadCount:', error); + return 0; + } +} diff --git a/src/main/ipc/projects.ts b/src/main/ipc/projects.ts new file mode 100644 index 00000000..21d96677 --- /dev/null +++ b/src/main/ipc/projects.ts @@ -0,0 +1,109 @@ +/** + * IPC Handlers for Project Operations. + * + * Handlers: + * - get-projects: List all projects + * - get-repository-groups: List projects grouped by git repository + * - get-worktree-sessions: List sessions for a specific worktree + */ + +import { createLogger } from '@shared/utils/logger'; +import { type IpcMain, type IpcMainInvokeEvent } from 'electron'; + +import { type Project, type RepositoryGroup, type Session } from '../types'; + +import { validateProjectId } from './guards'; + +import type { ProjectScanner } from '../services'; + +const logger = createLogger('IPC:projects'); + +// Service instance - set via initialize +let projectScanner: ProjectScanner; + +/** + * Initializes project handlers with service instance. + */ +export function initializeProjectHandlers(scanner: ProjectScanner): void { + projectScanner = scanner; +} + +/** + * Registers all project-related IPC handlers. + */ +export function registerProjectHandlers(ipcMain: IpcMain): void { + ipcMain.handle('get-projects', handleGetProjects); + ipcMain.handle('get-repository-groups', handleGetRepositoryGroups); + ipcMain.handle('get-worktree-sessions', handleGetWorktreeSessions); + + logger.info('Project handlers registered'); +} + +/** + * Removes all project IPC handlers. + */ +export function removeProjectHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('get-projects'); + ipcMain.removeHandler('get-repository-groups'); + ipcMain.removeHandler('get-worktree-sessions'); + + logger.info('Project handlers removed'); +} + +// ============================================================================= +// Handler Implementations +// ============================================================================= + +/** + * Handler for 'get-projects' IPC call. + * Lists all projects from ~/.claude/projects/ + */ +async function handleGetProjects(_event: IpcMainInvokeEvent): Promise { + try { + const projects = await projectScanner.scan(); + return projects; + } catch (error) { + logger.error('Error in get-projects:', error); + return []; + } +} + +/** + * Handler for 'get-repository-groups' IPC call. + * Lists all projects grouped by git repository. + * Worktrees of the same repo are grouped together. + */ +async function handleGetRepositoryGroups(_event: IpcMainInvokeEvent): Promise { + try { + const groups = await projectScanner.scanWithWorktreeGrouping(); + return groups; + } catch (error) { + logger.error('Error in get-repository-groups:', error); + return []; + } +} + +/** + * Handler for 'get-worktree-sessions' IPC call. + * Lists all sessions for a specific worktree within a repository group. + */ +async function handleGetWorktreeSessions( + _event: IpcMainInvokeEvent, + worktreeId: string +): Promise { + try { + const validatedProject = validateProjectId(worktreeId); + if (!validatedProject.valid) { + logger.error( + `get-worktree-sessions rejected: ${validatedProject.error ?? 'Invalid worktreeId'}` + ); + return []; + } + + const sessions = await projectScanner.listWorktreeSessions(validatedProject.value!); + return sessions; + } catch (error) { + logger.error(`Error in get-worktree-sessions for ${worktreeId}:`, error); + return []; + } +} diff --git a/src/main/ipc/search.ts b/src/main/ipc/search.ts new file mode 100644 index 00000000..81797f02 --- /dev/null +++ b/src/main/ipc/search.ts @@ -0,0 +1,82 @@ +/** + * IPC Handlers for Search Operations. + * + * Handlers: + * - search-sessions: Search sessions in a project + */ + +import { createLogger } from '@shared/utils/logger'; +import { type IpcMain, type IpcMainInvokeEvent } from 'electron'; + +import { type SearchSessionsResult } from '../types'; + +import { coerceSearchMaxResults, validateProjectId, validateSearchQuery } from './guards'; + +const logger = createLogger('IPC:search'); + +import type { ProjectScanner } from '../services'; + +// Service instance - set via initialize +let projectScanner: ProjectScanner; + +/** + * Initializes search handlers with service instance. + */ +export function initializeSearchHandlers(scanner: ProjectScanner): void { + projectScanner = scanner; +} + +/** + * Registers all search-related IPC handlers. + */ +export function registerSearchHandlers(ipcMain: IpcMain): void { + ipcMain.handle('search-sessions', handleSearchSessions); + + logger.info('Search handlers registered'); +} + +/** + * Removes all search IPC handlers. + */ +export function removeSearchHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('search-sessions'); + + logger.info('Search handlers removed'); +} + +// ============================================================================= +// Handler Implementations +// ============================================================================= + +/** + * Handler for 'search-sessions' IPC call. + * Searches sessions in a project for a query string. + */ +async function handleSearchSessions( + _event: IpcMainInvokeEvent, + projectId: string, + query: string, + maxResults?: number +): Promise { + try { + const validatedProject = validateProjectId(projectId); + const validatedQuery = validateSearchQuery(query); + if (!validatedProject.valid || !validatedQuery.valid) { + logger.error( + `search-sessions rejected: ${validatedProject.error ?? validatedQuery.error ?? 'Invalid inputs'}` + ); + return { results: [], totalMatches: 0, sessionsSearched: 0, query }; + } + + const safeMaxResults = coerceSearchMaxResults(maxResults, 50); + const result = await projectScanner.searchSessions( + validatedProject.value!, + validatedQuery.value!, + safeMaxResults + ); + return result; + } catch (error) { + logger.error(`Error in search-sessions for project ${projectId}:`, error); + return { results: [], totalMatches: 0, sessionsSearched: 0, query }; + } +} diff --git a/src/main/ipc/sessions.ts b/src/main/ipc/sessions.ts new file mode 100644 index 00000000..7f76ac65 --- /dev/null +++ b/src/main/ipc/sessions.ts @@ -0,0 +1,305 @@ +/** + * IPC Handlers for Session Operations. + * + * Handlers: + * - get-sessions: List sessions for a project + * - get-sessions-paginated: List sessions with cursor-based pagination + * - get-session-detail: Get full session detail with subagents + * - get-session-groups: Get conversation groups for a session + * - get-session-metrics: Get metrics for a session + */ + +import { createLogger } from '@shared/utils/logger'; +import { type IpcMain, type IpcMainInvokeEvent } from 'electron'; + +import { DataCache } from '../services'; +import { + type ConversationGroup, + type PaginatedSessionsResult, + type Session, + type SessionDetail, + type SessionMetrics, + type SessionsPaginationOptions, +} from '../types'; + +import { coercePageLimit, validateProjectId, validateSessionId } from './guards'; + +import type { ChunkBuilder, ProjectScanner, SessionParser, SubagentResolver } from '../services'; +import type { WaterfallData } from '@shared/types'; + +const logger = createLogger('IPC:sessions'); + +// Service instances - set via initialize +let projectScanner: ProjectScanner; +let sessionParser: SessionParser; +let subagentResolver: SubagentResolver; +let chunkBuilder: ChunkBuilder; +let dataCache: DataCache; + +/** + * Initializes session handlers with service instances. + */ +export function initializeSessionHandlers( + scanner: ProjectScanner, + parser: SessionParser, + resolver: SubagentResolver, + builder: ChunkBuilder, + cache: DataCache +): void { + projectScanner = scanner; + sessionParser = parser; + subagentResolver = resolver; + chunkBuilder = builder; + dataCache = cache; +} + +/** + * Registers all session-related IPC handlers. + */ +export function registerSessionHandlers(ipcMain: IpcMain): void { + ipcMain.handle('get-sessions', handleGetSessions); + ipcMain.handle('get-sessions-paginated', handleGetSessionsPaginated); + ipcMain.handle('get-session-detail', handleGetSessionDetail); + ipcMain.handle('get-session-groups', handleGetSessionGroups); + ipcMain.handle('get-session-metrics', handleGetSessionMetrics); + ipcMain.handle('get-waterfall-data', handleGetWaterfallData); + + logger.info('Session handlers registered'); +} + +/** + * Removes all session IPC handlers. + */ +export function removeSessionHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('get-sessions'); + ipcMain.removeHandler('get-sessions-paginated'); + ipcMain.removeHandler('get-session-detail'); + ipcMain.removeHandler('get-session-groups'); + ipcMain.removeHandler('get-session-metrics'); + ipcMain.removeHandler('get-waterfall-data'); + + logger.info('Session handlers removed'); +} + +// ============================================================================= +// Handler Implementations +// ============================================================================= + +/** + * Handler for 'get-sessions' IPC call. + * Lists all sessions for a given project. + */ +async function handleGetSessions( + _event: IpcMainInvokeEvent, + projectId: string +): Promise { + try { + const validatedProject = validateProjectId(projectId); + if (!validatedProject.valid) { + logger.error(`get-sessions rejected: ${validatedProject.error ?? 'Invalid projectId'}`); + return []; + } + + const sessions = await projectScanner.listSessions(validatedProject.value!); + return sessions; + } catch (error) { + logger.error(`Error in get-sessions for project ${projectId}:`, error); + return []; + } +} + +/** + * Handler for 'get-sessions-paginated' IPC call. + * Lists sessions for a project with cursor-based pagination. + */ +async function handleGetSessionsPaginated( + _event: IpcMainInvokeEvent, + projectId: string, + cursor: string | null, + limit?: number, + options?: SessionsPaginationOptions +): Promise { + try { + const validatedProject = validateProjectId(projectId); + if (!validatedProject.valid) { + logger.error( + `get-sessions-paginated rejected: ${validatedProject.error ?? 'Invalid projectId'}` + ); + return { sessions: [], nextCursor: null, hasMore: false, totalCount: 0 }; + } + + const safeLimit = coercePageLimit(limit, 20); + const result = await projectScanner.listSessionsPaginated( + validatedProject.value!, + cursor, + safeLimit, + options + ); + return result; + } catch (error) { + logger.error(`Error in get-sessions-paginated for project ${projectId}:`, error); + return { sessions: [], nextCursor: null, hasMore: false, totalCount: 0 }; + } +} + +/** + * Handler for 'get-session-detail' IPC call. + * Gets full session detail including parsed chunks and subagents. + * Uses cache to avoid re-parsing large files. + */ +async function handleGetSessionDetail( + _event: IpcMainInvokeEvent, + projectId: string, + sessionId: string +): Promise { + try { + const validatedProject = validateProjectId(projectId); + const validatedSession = validateSessionId(sessionId); + if (!validatedProject.valid || !validatedSession.valid) { + logger.error( + `get-session-detail rejected: ${validatedProject.error ?? validatedSession.error ?? 'Invalid parameters'}` + ); + return null; + } + + const safeProjectId = validatedProject.value!; + const safeSessionId = validatedSession.value!; + const cacheKey = DataCache.buildKey(safeProjectId, safeSessionId); + + // Check cache first + let sessionDetail = dataCache.get(cacheKey); + + if (sessionDetail) { + return sessionDetail; + } + + // Get session metadata + const session = await projectScanner.getSession(safeProjectId, safeSessionId); + if (!session) { + logger.error(`Session not found: ${sessionId}`); + return null; + } + + // Parse session messages + const parsedSession = await sessionParser.parseSession(safeProjectId, safeSessionId); + + // Resolve subagents + const subagents = await subagentResolver.resolveSubagents( + safeProjectId, + safeSessionId, + parsedSession.taskCalls, + parsedSession.messages + ); + + // Build session detail with chunks + sessionDetail = chunkBuilder.buildSessionDetail(session, parsedSession.messages, subagents); + + // Cache the result + dataCache.set(cacheKey, sessionDetail); + + return sessionDetail; + } catch (error) { + logger.error(`Error in get-session-detail for ${projectId}/${sessionId}:`, error); + return null; + } +} + +/** + * Handler for 'get-session-groups' IPC call. + * Gets conversation groups for a session using the new buildGroups API. + * This is an alternative to chunks that provides a simpler, more natural grouping. + */ +async function handleGetSessionGroups( + _event: IpcMainInvokeEvent, + projectId: string, + sessionId: string +): Promise { + try { + const validatedProject = validateProjectId(projectId); + const validatedSession = validateSessionId(sessionId); + if (!validatedProject.valid || !validatedSession.valid) { + logger.error( + `get-session-groups rejected: ${validatedProject.error ?? validatedSession.error ?? 'Invalid parameters'}` + ); + return []; + } + const safeProjectId = validatedProject.value!; + const safeSessionId = validatedSession.value!; + + // Parse session messages + const parsedSession = await sessionParser.parseSession(safeProjectId, safeSessionId); + + // Resolve subagents + const subagents = await subagentResolver.resolveSubagents( + safeProjectId, + safeSessionId, + parsedSession.taskCalls, + parsedSession.messages + ); + + // Build conversation groups using the new API + const groups = chunkBuilder.buildGroups(parsedSession.messages, subagents); + + return groups; + } catch (error) { + logger.error(`Error in get-session-groups for ${projectId}/${sessionId}:`, error); + return []; + } +} + +/** + * Handler for 'get-session-metrics' IPC call. + * Gets metrics for a session without full detail. + */ +async function handleGetSessionMetrics( + _event: IpcMainInvokeEvent, + projectId: string, + sessionId: string +): Promise { + try { + const validatedProject = validateProjectId(projectId); + const validatedSession = validateSessionId(sessionId); + if (!validatedProject.valid || !validatedSession.valid) { + return null; + } + const safeProjectId = validatedProject.value!; + const safeSessionId = validatedSession.value!; + + // Try to get from cache first + const cacheKey = DataCache.buildKey(safeProjectId, safeSessionId); + const cached = dataCache.get(cacheKey); + + if (cached) { + return cached.metrics; + } + + // Parse session to get metrics + const parsedSession = await sessionParser.parseSession(safeProjectId, safeSessionId); + return parsedSession.metrics; + } catch (error) { + logger.error(`Error in get-session-metrics for ${projectId}/${sessionId}:`, error); + return null; + } +} + +/** + * Handler for 'get-waterfall-data' IPC call. + * Builds waterfall chart data for a session. + */ +async function handleGetWaterfallData( + _event: IpcMainInvokeEvent, + projectId: string, + sessionId: string +): Promise { + try { + const detail = await handleGetSessionDetail(_event, projectId, sessionId); + if (!detail) { + return null; + } + + return chunkBuilder.buildWaterfallData(detail.chunks, detail.processes); + } catch (error) { + logger.error(`Error in get-waterfall-data for ${projectId}/${sessionId}:`, error); + return null; + } +} diff --git a/src/main/ipc/subagents.ts b/src/main/ipc/subagents.ts new file mode 100644 index 00000000..d010e3c6 --- /dev/null +++ b/src/main/ipc/subagents.ts @@ -0,0 +1,124 @@ +/** + * IPC Handlers for Subagent Operations. + * + * Handlers: + * - get-subagent-detail: Get detailed information for a specific subagent + */ + +import { createLogger } from '@shared/utils/logger'; +import { type IpcMain, type IpcMainInvokeEvent } from 'electron'; + +import { type SubagentDetail } from '../types'; + +import { validateProjectId, validateSessionId, validateSubagentId } from './guards'; + +import type { ChunkBuilder, DataCache, SessionParser, SubagentResolver } from '../services'; + +const logger = createLogger('IPC:subagents'); + +// Service instances - set via initialize +let chunkBuilder: ChunkBuilder; +let dataCache: DataCache; +let sessionParser: SessionParser; +let subagentResolver: SubagentResolver; + +/** + * Initializes subagent handlers with service instances. + */ +export function initializeSubagentHandlers( + builder: ChunkBuilder, + cache: DataCache, + parser: SessionParser, + resolver: SubagentResolver +): void { + chunkBuilder = builder; + dataCache = cache; + sessionParser = parser; + subagentResolver = resolver; +} + +/** + * Registers all subagent-related IPC handlers. + */ +export function registerSubagentHandlers(ipcMain: IpcMain): void { + ipcMain.handle('get-subagent-detail', handleGetSubagentDetail); + + logger.info('Subagent handlers registered'); +} + +/** + * Removes all subagent IPC handlers. + */ +export function removeSubagentHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('get-subagent-detail'); + + logger.info('Subagent handlers removed'); +} + +// ============================================================================= +// Handler Implementations +// ============================================================================= + +/** + * Handler for 'get-subagent-detail' IPC call. + * Gets detailed information for a specific subagent for drill-down modal. + */ +async function handleGetSubagentDetail( + _event: IpcMainInvokeEvent, + projectId: string, + sessionId: string, + subagentId: string +): Promise { + try { + const validatedProject = validateProjectId(projectId); + const validatedSession = validateSessionId(sessionId); + const validatedSubagent = validateSubagentId(subagentId); + if (!validatedProject.valid || !validatedSession.valid || !validatedSubagent.valid) { + logger.error( + `get-subagent-detail rejected: ${ + validatedProject.error ?? + validatedSession.error ?? + validatedSubagent.error ?? + 'Invalid parameters' + }` + ); + return null; + } + const safeProjectId = validatedProject.value!; + const safeSessionId = validatedSession.value!; + const safeSubagentId = validatedSubagent.value!; + + const cacheKey = `subagent-${safeProjectId}-${safeSessionId}-${safeSubagentId}`; + + // Check cache first + let subagentDetail = dataCache.getSubagent(cacheKey); + + if (subagentDetail) { + return subagentDetail; + } + + // Build subagent detail + const builtDetail = await chunkBuilder.buildSubagentDetail( + safeProjectId, + safeSessionId, + safeSubagentId, + sessionParser, + subagentResolver + ); + + if (!builtDetail) { + logger.error(`Subagent not found: ${safeSubagentId}`); + return null; + } + + subagentDetail = builtDetail; + + // Cache the result + dataCache.setSubagent(cacheKey, subagentDetail); + + return subagentDetail; + } catch (error) { + logger.error(`Error in get-subagent-detail for ${subagentId}:`, error); + return null; + } +} diff --git a/src/main/ipc/utility.ts b/src/main/ipc/utility.ts new file mode 100644 index 00000000..c13dd394 --- /dev/null +++ b/src/main/ipc/utility.ts @@ -0,0 +1,230 @@ +/** + * IPC Handlers for Utility Operations. + * + * Handlers: + * - shell:openPath: Opens a folder or file in the system's default application + * - read-claude-md-files: Reads all global CLAUDE.md files for a project + * - read-directory-claude-md: Reads a specific directory's CLAUDE.md file + * - read-mentioned-file: Validates mentioned files for context injection + */ + +import { createLogger } from '@shared/utils/logger'; +import { app, type IpcMain, type IpcMainInvokeEvent, shell } from 'electron'; +import * as fs from 'fs'; + +import { type ClaudeMdFileInfo, readAllClaudeMdFiles, readDirectoryClaudeMd } from '../services'; + +const logger = createLogger('IPC:utility'); +import { validateFilePath, validateOpenPath } from '../utils/pathValidation'; +import { countTokens } from '../utils/tokenizer'; + +/** + * Registers all utility-related IPC handlers. + */ +export function registerUtilityHandlers(ipcMain: IpcMain): void { + ipcMain.handle('get-app-version', handleGetAppVersion); + ipcMain.handle('shell:openPath', handleShellOpenPath); + ipcMain.handle('shell:openExternal', handleShellOpenExternal); + ipcMain.handle('read-claude-md-files', handleReadClaudeMdFiles); + ipcMain.handle('read-directory-claude-md', handleReadDirectoryClaudeMd); + ipcMain.handle('read-mentioned-file', handleReadMentionedFile); + + logger.info('Utility handlers registered'); +} + +/** + * Removes all utility IPC handlers. + */ +export function removeUtilityHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('get-app-version'); + ipcMain.removeHandler('shell:openPath'); + ipcMain.removeHandler('shell:openExternal'); + ipcMain.removeHandler('read-claude-md-files'); + ipcMain.removeHandler('read-directory-claude-md'); + ipcMain.removeHandler('read-mentioned-file'); + + logger.info('Utility handlers removed'); +} + +// ============================================================================= +// Handler Implementations +// ============================================================================= + +/** + * Handler for 'get-app-version' IPC call. + * Returns the app version from package.json. + */ +function handleGetAppVersion(): string { + return app.getVersion(); +} + +/** + * Handler for 'shell:openExternal' IPC call. + * Opens a URL in the system's default browser. + */ +async function handleShellOpenExternal( + _event: IpcMainInvokeEvent, + url: string +): Promise<{ success: boolean; error?: string }> { + try { + let parsedUrl: URL; + try { + parsedUrl = new URL(url); + } catch { + return { success: false, error: 'Invalid URL' }; + } + + const protocol = parsedUrl.protocol.toLowerCase(); + if (protocol !== 'http:' && protocol !== 'https:' && protocol !== 'mailto:') { + logger.error(`shell:openExternal - invalid URL scheme: ${url}`); + return { success: false, error: 'Only http, https, and mailto URLs are allowed' }; + } + + await shell.openExternal(parsedUrl.toString()); + return { success: true }; + } catch (error) { + logger.error('Error in shell:openExternal:', error); + return { success: false, error: String(error) }; + } +} + +/** + * Handler for 'shell:openPath' IPC call. + * Opens a folder or file in the system's default application (Finder on macOS). + * Validates path security before opening. + */ +async function handleShellOpenPath( + _event: IpcMainInvokeEvent, + targetPath: string, + projectRoot?: string +): Promise<{ success: boolean; error?: string }> { + try { + // Validate path security + const validation = validateOpenPath(targetPath, projectRoot ?? null); + if (!validation.valid) { + logger.error(`shell:openPath - validation failed: ${validation.error ?? 'Unknown error'}`); + return { success: false, error: validation.error }; + } + + const safePath = validation.normalizedPath!; + + // Check if path exists + if (!fs.existsSync(safePath)) { + logger.error(`shell:openPath - path does not exist: ${safePath}`); + return { success: false, error: 'Path does not exist' }; + } + + // Open in default application (Finder on macOS) + const errorMessage = await shell.openPath(safePath); + if (errorMessage) { + logger.error(`shell:openPath - failed: ${errorMessage}`); + return { success: false, error: errorMessage }; + } + + return { success: true }; + } catch (error) { + logger.error('Error in shell:openPath:', error); + return { success: false, error: String(error) }; + } +} + +/** + * Handler for 'read-claude-md-files' IPC call. + * Reads all global CLAUDE.md files for a project. + */ +async function handleReadClaudeMdFiles( + _event: IpcMainInvokeEvent, + projectRoot: string +): Promise> { + try { + const result = readAllClaudeMdFiles(projectRoot); + // Convert Map to object for IPC serialization + const files: Record = {}; + result.files.forEach((info, key) => { + files[key] = info; + }); + + return files; + } catch (error) { + logger.error(`Error in read-claude-md-files:`, error); + return {}; + } +} + +/** + * Handler for 'read-directory-claude-md' IPC call. + * Reads a specific directory's CLAUDE.md file. + */ +async function handleReadDirectoryClaudeMd( + _event: IpcMainInvokeEvent, + dirPath: string +): Promise { + try { + const info = readDirectoryClaudeMd(dirPath); + return info; + } catch (error) { + logger.error(`Error in read-directory-claude-md:`, error); + return { + path: dirPath, + exists: false, + charCount: 0, + estimatedTokens: 0, + }; + } +} + +/** + * Handler for 'read-mentioned-file' IPC call. + * Validates mentioned files for context injection. + * Returns file info if file exists, is a regular file, within allowed directories, and within token limits. + * + * Security: Validates path against allowed directories and sensitive file patterns. + */ +async function handleReadMentionedFile( + _event: IpcMainInvokeEvent, + absolutePath: string, + projectRoot: string, + maxTokens: number = 25000 +): Promise<{ path: string; exists: boolean; charCount: number; estimatedTokens: number } | null> { + try { + // Validate path security + const validation = validateFilePath(absolutePath, projectRoot || null); + if (!validation.valid) { + return null; + } + + const safePath = validation.normalizedPath!; + + // Check if file exists + if (!fs.existsSync(safePath)) { + return null; + } + + // Check if it's a file (not directory) + const stats = fs.statSync(safePath); + if (!stats.isFile()) { + return null; + } + + // Read file content + const content = fs.readFileSync(safePath, 'utf8'); + + // Calculate tokens + const estimatedTokens = countTokens(content); + + // Check token limit + if (estimatedTokens > maxTokens) { + return null; + } + + return { + path: safePath, + exists: true, + charCount: content.length, + estimatedTokens, + }; + } catch (error) { + logger.error(`Error in read-mentioned-file for ${absolutePath}:`, error); + return null; + } +} diff --git a/src/main/ipc/validation.ts b/src/main/ipc/validation.ts new file mode 100644 index 00000000..edf52760 --- /dev/null +++ b/src/main/ipc/validation.ts @@ -0,0 +1,145 @@ +/** + * IPC Handlers for Validation Operations. + * + * Handlers: + * - validate-path: Validate if a file/directory path exists relative to project + * - validate-mentions: Batch validate path mentions (@file references) + * - session:scrollToLine: Deep link handler for scrolling to a specific line in a session + */ + +import { createLogger } from '@shared/utils/logger'; +import { type IpcMain, type IpcMainInvokeEvent } from 'electron'; +import * as fs from 'fs'; +import * as path from 'path'; + +const logger = createLogger('IPC:validation'); + +/** + * Registers all validation-related IPC handlers. + */ +export function registerValidationHandlers(ipcMain: IpcMain): void { + ipcMain.handle('validate-path', handleValidatePath); + ipcMain.handle('validate-mentions', handleValidateMentions); + ipcMain.handle('session:scrollToLine', handleScrollToLine); + + logger.info('Validation handlers registered'); +} + +/** + * Removes all validation IPC handlers. + */ +export function removeValidationHandlers(ipcMain: IpcMain): void { + ipcMain.removeHandler('validate-path'); + ipcMain.removeHandler('validate-mentions'); + ipcMain.removeHandler('session:scrollToLine'); + + logger.info('Validation handlers removed'); +} + +// ============================================================================= +// Security Helpers +// ============================================================================= + +/** + * Checks if a path is contained within a base directory. + * Prevents path traversal attacks (e.g., ../../etc/passwd). + */ +function isPathContained(fullPath: string, basePath: string): boolean { + const normalizedFull = path.normalize(fullPath); + const normalizedBase = path.normalize(basePath); + + // Ensure the full path starts with the base path followed by a separator + // or is exactly the base path + return normalizedFull === normalizedBase || normalizedFull.startsWith(normalizedBase + path.sep); +} + +// ============================================================================= +// Handler Implementations +// ============================================================================= + +/** + * Handler for 'validate-path' IPC call. + * Validates if a file/directory path exists relative to project. + */ +async function handleValidatePath( + _event: IpcMainInvokeEvent, + relativePath: string, + projectPath: string +): Promise<{ exists: boolean; isDirectory?: boolean }> { + try { + const fullPath = path.join(projectPath, relativePath); + + // Security: Ensure path doesn't escape project directory + if (!isPathContained(fullPath, projectPath)) { + logger.warn('validate-path blocked path traversal attempt:', relativePath); + return { exists: false }; + } + + if (!fs.existsSync(fullPath)) { + return { exists: false }; + } + + const stats = fs.statSync(fullPath); + return { + exists: true, + isDirectory: stats.isDirectory(), + }; + } catch { + return { exists: false }; + } +} + +/** + * Handler for 'validate-mentions' IPC call. + * Batch validates path mentions (@file references). + * Slash commands do not need validation. + */ +async function handleValidateMentions( + _event: IpcMainInvokeEvent, + mentions: { type: 'path'; value: string }[], + projectPath: string +): Promise> { + const results = new Map(); + + for (const mention of mentions) { + const fullPath = path.join(projectPath, mention.value); + + // Security: Skip paths that escape project directory + if (!isPathContained(fullPath, projectPath)) { + results.set(`@${mention.value}`, false); + continue; + } + + results.set(`@${mention.value}`, fs.existsSync(fullPath)); + } + + return Object.fromEntries(results); +} + +/** + * Handler for 'session:scrollToLine' IPC call. + * Used for deep linking from notifications to specific lines in a session. + * The actual scrolling happens in the renderer; this handler validates and returns the data. + */ +async function handleScrollToLine( + _event: IpcMainInvokeEvent, + sessionId: string, + lineNumber: number +): Promise<{ success: boolean; sessionId: string; lineNumber: number }> { + try { + if (!sessionId) { + logger.error('session:scrollToLine called with empty sessionId'); + return { success: false, sessionId: '', lineNumber: 0 }; + } + + if (typeof lineNumber !== 'number' || lineNumber < 0) { + logger.error('session:scrollToLine called with invalid lineNumber'); + return { success: false, sessionId, lineNumber: 0 }; + } + + return { success: true, sessionId, lineNumber }; + } catch (error) { + logger.error(`Error in session:scrollToLine:`, error); + return { success: false, sessionId: '', lineNumber: 0 }; + } +} diff --git a/src/main/services/CLAUDE.md b/src/main/services/CLAUDE.md new file mode 100644 index 00000000..9ed40777 --- /dev/null +++ b/src/main/services/CLAUDE.md @@ -0,0 +1,59 @@ +# Services + +Business logic organized by domain. + +## Domains +- `analysis/` - Chunk building, semantic steps, tool execution +- `discovery/` - Project/session scanning, subagent resolution +- `error/` - Error detection, trigger checking +- `infrastructure/` - Cache, file watching, config, notifications +- `parsing/` - JSONL parsing, message classification + +## Key Services + +### Analysis +- **ChunkBuilder** - Orchestrates chunk building +- **ChunkFactory** - Creates chunk objects +- **ConversationGroupBuilder** - Builds conversation groups +- **ProcessLinker** - Links subagents to chunks +- **SemanticStepExtractor** - Extracts steps +- **SemanticStepGrouper** - Groups semantic steps +- **SubagentDetailBuilder** - Builds subagent detail views +- **ToolExecutionBuilder** - Builds tool execution tracking +- **ToolResultExtractor** - Extracts tool results +- **ToolSummaryFormatter** - Formats tool summaries + +### Discovery +- **ProjectPathResolver** - Resolves project paths +- **ProjectScanner** - Scans ~/.claude/projects/ +- **SessionContentFilter** - Filters session content +- **SessionSearcher** - Searches session content +- **SubagentLocator** - Locates subagent files +- **SubagentResolver** - Parses subagent files, detects parallel execution, enriches team metadata/colors +- **SubprojectRegistry** - Tracks subproject associations +- **WorktreeGrouper** - Groups projects by git worktree + +### Parsing +- **SessionParser** - Parses JSONL files +- **MessageClassifier** - Categorizes messages (user, system, AI, noise) +- **ClaudeMdReader** - Reads CLAUDE.md configuration +- **GitIdentityResolver** - Resolves git identities + +### Error +- **ErrorDetector** - Per-tool-use token counting, returns `DetectedError[]` +- **ErrorMessageBuilder** - Builds error notification messages +- **ErrorTriggerChecker** - Matches against notification triggers +- **ErrorTriggerTester** - Tests triggers against historical data +- **TriggerMatcher** - Pattern matching for triggers + +### Infrastructure +- **DataCache** - LRU cache (50 entries, 10min TTL) +- **FileWatcher** - 100ms debounced file watching +- **ConfigManager** - App configuration +- **NotificationManager** - Notification handling +- **TriggerManager** - Notification trigger management + +## Adding Service +1. Create in appropriate domain folder +2. Export from domain's index.ts +3. Re-export from services/index.ts diff --git a/src/main/services/analysis/ChunkBuilder.ts b/src/main/services/analysis/ChunkBuilder.ts new file mode 100644 index 00000000..072ea18d --- /dev/null +++ b/src/main/services/analysis/ChunkBuilder.ts @@ -0,0 +1,443 @@ +/** + * ChunkBuilder service - Builds visualization chunks from parsed session data. + * + * Responsibilities: + * - Group messages into chunks (user message + responses) + * - Attach subagents to chunks + * - Build waterfall chart data + * - Calculate chunk metrics + * + * This module orchestrates chunk building using specialized modules: + * - MessageClassifier: Classify messages into categories + * - ChunkFactory: Create individual chunk objects + * - ProcessLinker: Link subagent processes to chunks + * - SemanticStepExtractor: Extract semantic steps from AI chunks + * - SemanticStepGrouper: Group semantic steps for UI + * - ToolExecutionBuilder: Build tool execution tracking + * - SubagentDetailBuilder: Build subagent drill-down details + * - ConversationGroupBuilder: Alternative grouping strategy + */ + +import { + type Chunk, + type ConversationGroup, + EMPTY_METRICS, + type EnhancedChunk, + isAIChunk, + isCompactChunk, + isSystemChunk, + isUserChunk, + type MessageCategory, + type ParsedMessage, + type Process, + type Session, + type SessionDetail, + type SessionMetrics, + type SubagentDetail, + type TokenUsage, +} from '@main/types'; +import { calculateMetrics } from '@main/utils/jsonl'; +import { createLogger } from '@shared/utils/logger'; + +import type { WaterfallData, WaterfallItem } from '@shared/types'; + +const logger = createLogger('Service:ChunkBuilder'); + +import { classifyMessages } from '../parsing/MessageClassifier'; + +import { + buildAIChunkFromBuffer, + buildCompactChunk, + buildSystemChunk, + buildUserChunk, +} from './ChunkFactory'; +import { buildGroups as buildConversationGroups } from './ConversationGroupBuilder'; +import { buildSubagentDetail as buildSubagentDetailFn } from './SubagentDetailBuilder'; + +import type { SubagentResolver } from '../discovery/SubagentResolver'; +import type { SessionParser } from '../parsing/SessionParser'; + +export class ChunkBuilder { + // =========================================================================== + // Chunk Building + // =========================================================================== + + /** + * Build chunks from messages using 4-category classification. + * Produces independent UserChunks, AIChunks, and SystemChunks. + * + * Categories: + * - User: Genuine user input (creates UserChunk, renders RIGHT) + * - System: Command output (creates SystemChunk, renders LEFT) + * - Hard Noise: Filtered out entirely (system metadata, caveats, reminders) + * - AI: All other messages grouped into AIChunks (renders LEFT) + * + * All chunk types are INDEPENDENT - no pairing between User and AI. + */ + buildChunks(messages: ParsedMessage[], subagents: Process[] = []): EnhancedChunk[] { + const chunks: EnhancedChunk[] = []; + + // Filter to main thread messages (non-sidechain) + const mainMessages = messages.filter((m) => !m.isSidechain); + logger.debug(`Total messages: ${messages.length}, Main thread: ${mainMessages.length}`); + + // Classify each message into categories using MessageClassifier + const classified = classifyMessages(mainMessages); + + // Log classification summary + const categoryCounts = new Map(); + for (const { category } of classified) { + categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1); + } + logger.debug('Message classification:', Object.fromEntries(categoryCounts)); + + // Build chunks from classification - AI chunks are INDEPENDENT + let aiBuffer: ParsedMessage[] = []; + + for (const { message, category } of classified) { + switch (category) { + case 'hardNoise': + // Skip - filtered out + break; + + case 'compact': + // Flush any buffered AI messages first + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + aiBuffer = []; + } + chunks.push(buildCompactChunk(message)); + break; + + case 'user': + // Flush any buffered AI messages first + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + aiBuffer = []; + } + chunks.push(buildUserChunk(message)); + break; + + case 'system': + // Flush any buffered AI messages first + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + aiBuffer = []; + } + chunks.push(buildSystemChunk(message)); + break; + + case 'ai': + aiBuffer.push(message); + break; + } + } + + // Flush remaining AI buffer + if (aiBuffer.length > 0) { + chunks.push(buildAIChunkFromBuffer(aiBuffer, subagents, messages)); + } + + // Log final chunk summary + const userChunkCount = chunks.filter(isUserChunk).length; + const aiChunkCount = chunks.filter(isAIChunk).length; + const systemChunkCount = chunks.filter(isSystemChunk).length; + const compactChunkCount = chunks.filter(isCompactChunk).length; + logger.debug( + `Created ${chunks.length} chunks: ${userChunkCount} user, ${aiChunkCount} AI, ${systemChunkCount} system, ${compactChunkCount} compact` + ); + + return chunks; + } + + // =========================================================================== + // Simplified Grouping Strategy (delegates to ConversationGroupBuilder) + // =========================================================================== + + /** + * Build conversation groups using simplified grouping strategy. + * Groups one user message with all AI responses until the next user message. + * + * This is a cleaner alternative to buildChunks() that: + * - Uses simpler time-based grouping + * - Separates Task executions from regular tool executions + * - Links subagents more explicitly via TaskExecution + */ + buildGroups(messages: ParsedMessage[], subagents: Process[]): ConversationGroup[] { + return buildConversationGroups(messages, subagents); + } + + // =========================================================================== + // Session Detail Building + // =========================================================================== + + /** + * Build a complete SessionDetail from parsed data. + */ + buildSessionDetail( + session: Session, + messages: ParsedMessage[], + subagents: Process[] + ): SessionDetail { + // Build chunks + const chunks = this.buildChunks(messages, subagents); + + // Calculate overall metrics + const metrics = calculateMetrics(messages); + + return { + session, + messages, + chunks, + processes: subagents, + metrics, + }; + } + + /** + * Build waterfall chart data from chunks and resolved processes. + */ + buildWaterfallData(chunks: Chunk[], processes: Process[]): WaterfallData { + const items: WaterfallItem[] = []; + + for (const chunk of chunks) { + const baseChunkItem: WaterfallItem = { + id: chunk.id, + label: this.getChunkLabel(chunk), + startTime: chunk.startTime, + endTime: chunk.endTime, + durationMs: chunk.durationMs, + tokenUsage: this.toTokenUsage(chunk.metrics), + level: 0, + type: 'chunk', + isParallel: false, + }; + items.push(baseChunkItem); + + if (isAIChunk(chunk)) { + for (const toolExec of chunk.toolExecutions) { + const endTime = toolExec.endTime ?? toolExec.startTime; + items.push({ + id: `tool-${toolExec.toolCall.id}`, + label: toolExec.toolCall.name, + startTime: toolExec.startTime, + endTime, + durationMs: + toolExec.durationMs ?? Math.max(endTime.getTime() - toolExec.startTime.getTime(), 0), + tokenUsage: { + input_tokens: 0, + output_tokens: 0, + }, + level: 1, + type: 'tool', + isParallel: false, + parentId: chunk.id, + }); + } + + for (const process of chunk.processes) { + items.push({ + id: `subagent-${process.id}`, + label: process.description || process.subagentType || process.id, + startTime: process.startTime, + endTime: process.endTime, + durationMs: process.durationMs, + tokenUsage: this.toTokenUsage(process.metrics), + level: 1, + type: 'subagent', + isParallel: process.isParallel, + parentId: chunk.id, + metadata: { + subagentType: process.subagentType, + messageCount: process.messages.length, + }, + }); + } + } + } + + // Add any process that was not attached to an AI chunk (defensive fallback) + for (const process of processes) { + const itemId = `subagent-${process.id}`; + if (items.some((item) => item.id === itemId)) { + continue; + } + items.push({ + id: itemId, + label: process.description || process.subagentType || process.id, + startTime: process.startTime, + endTime: process.endTime, + durationMs: process.durationMs, + tokenUsage: this.toTokenUsage(process.metrics), + level: 0, + type: 'subagent', + isParallel: process.isParallel, + metadata: { + subagentType: process.subagentType, + messageCount: process.messages.length, + }, + }); + } + + const sortedItems = [...items]; + sortedItems.sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); + + if (sortedItems.length === 0) { + const now = new Date(); + return { + items: [], + minTime: now, + maxTime: now, + totalDurationMs: 0, + }; + } + + const minTime = sortedItems.reduce( + (min, item) => (item.startTime.getTime() < min.getTime() ? item.startTime : min), + sortedItems[0].startTime + ); + const maxTime = sortedItems.reduce( + (max, item) => (item.endTime.getTime() > max.getTime() ? item.endTime : max), + sortedItems[0].endTime + ); + + return { + items: sortedItems, + minTime, + maxTime, + totalDurationMs: Math.max(maxTime.getTime() - minTime.getTime(), 0), + }; + } + + // =========================================================================== + // Utility Methods + // =========================================================================== + + /** + * Get total metrics for all chunks. + */ + getTotalChunkMetrics(chunks: (Chunk | EnhancedChunk)[]): SessionMetrics { + if (chunks.length === 0) { + return { ...EMPTY_METRICS }; + } + + let durationMs = 0; + let inputTokens = 0; + let outputTokens = 0; + let cacheReadTokens = 0; + let cacheCreationTokens = 0; + let messageCount = 0; + + for (const chunk of chunks) { + durationMs += chunk.durationMs; + inputTokens += chunk.metrics.inputTokens; + outputTokens += chunk.metrics.outputTokens; + cacheReadTokens += chunk.metrics.cacheReadTokens; + cacheCreationTokens += chunk.metrics.cacheCreationTokens; + messageCount += chunk.metrics.messageCount; + } + + return { + durationMs, + totalTokens: inputTokens + outputTokens, + inputTokens, + outputTokens, + cacheReadTokens, + cacheCreationTokens, + messageCount, + }; + } + + /** + * Find chunk containing a specific message UUID. + */ + findChunkByMessageId( + chunks: (Chunk | EnhancedChunk)[], + messageUuid: string + ): Chunk | EnhancedChunk | undefined { + return chunks.find((c) => { + // UserChunk: check userMessage + if (isUserChunk(c)) { + return c.userMessage.uuid === messageUuid; + } + // AIChunk: check responses + if (isAIChunk(c)) { + return c.responses.some((r) => r.uuid === messageUuid); + } + return false; + }); + } + + /** + * Find chunk containing a specific subagent. + * Only AIChunks have processes. + */ + findChunkBySubagentId( + chunks: (Chunk | EnhancedChunk)[], + subagentId: string + ): Chunk | EnhancedChunk | undefined { + return chunks.find((c) => { + if (isAIChunk(c)) { + return c.processes.some((s: Process) => s.id === subagentId); + } + return false; + }); + } + + private getChunkLabel(chunk: Chunk): string { + switch (chunk.chunkType) { + case 'user': + return 'User'; + case 'ai': + return 'Assistant'; + case 'system': + return 'System'; + case 'compact': + return 'Compact'; + default: + return 'Chunk'; + } + } + + private toTokenUsage(metrics: SessionMetrics): TokenUsage { + return { + input_tokens: metrics.inputTokens, + output_tokens: metrics.outputTokens, + cache_read_input_tokens: metrics.cacheReadTokens || undefined, + cache_creation_input_tokens: metrics.cacheCreationTokens || undefined, + }; + } + + // =========================================================================== + // Subagent Detail Building (for drill-down) + // =========================================================================== + + /** + * Build detailed information for a specific subagent. + * Used for drill-down modal to show subagent's internal execution. + * + * @param projectId - Project ID + * @param sessionId - Parent session ID (currently unused, kept for API consistency) + * @param subagentId - Subagent ID to load + * @param sessionParser - SessionParser instance for parsing subagent file + * @param subagentResolver - SubagentResolver instance for nested subagents + * @returns SubagentDetail or null if not found + */ + async buildSubagentDetail( + projectId: string, + sessionId: string, + subagentId: string, + sessionParser: SessionParser, + subagentResolver: SubagentResolver + ): Promise { + // Delegate to the extracted module, passing buildChunks as a callback + return buildSubagentDetailFn( + projectId, + sessionId, + subagentId, + sessionParser, + subagentResolver, + (messages, subagents) => this.buildChunks(messages, subagents) + ); + } +} diff --git a/src/main/services/analysis/ChunkFactory.ts b/src/main/services/analysis/ChunkFactory.ts new file mode 100644 index 00000000..340f8feb --- /dev/null +++ b/src/main/services/analysis/ChunkFactory.ts @@ -0,0 +1,203 @@ +/** + * ChunkFactory service - Creates individual chunk objects from messages. + * + * Responsibilities: + * - Build UserChunk from user messages + * - Build SystemChunk from command output messages + * - Build CompactChunk from summary messages + * - Build AIChunk from buffered AI messages + * - Calculate timing and metrics for chunks + */ + +import { + type EnhancedAIChunk, + type EnhancedCompactChunk, + type EnhancedSystemChunk, + type EnhancedUserChunk, + type ParsedMessage, + type Process, +} from '@main/types'; +import { calculateStepContext } from '@main/utils/contextAccumulator'; +import { calculateMetrics } from '@main/utils/jsonl'; +import { fillTimelineGaps } from '@main/utils/timelineGapFilling'; + +import { linkProcessesToAIChunk } from './ProcessLinker'; +import { extractSemanticStepsFromAIChunk } from './SemanticStepExtractor'; +import { buildSemanticStepGroups } from './SemanticStepGrouper'; +import { buildToolExecutions } from './ToolExecutionBuilder'; + +/** + * Generate a stable chunk ID based on message UUID. + * Using the message UUID ensures IDs are consistent across re-parses. + */ +function generateStableChunkId(prefix: string, message: ParsedMessage): string { + return `${prefix}-${message.uuid}`; +} + +/** + * Build a UserChunk from a user message. + */ +export function buildUserChunk(message: ParsedMessage): EnhancedUserChunk { + const id = generateStableChunkId('user', message); + const metrics = calculateMetrics([message]); + + return { + id, + chunkType: 'user', + userMessage: message, + startTime: message.timestamp, + endTime: message.timestamp, + durationMs: 0, + metrics, + rawMessages: [message], + }; +} + +/** + * Build a SystemChunk from a command output message. + */ +export function buildSystemChunk(message: ParsedMessage): EnhancedSystemChunk { + const id = generateStableChunkId('system', message); + const commandOutput = extractCommandOutput(message); + const metrics = calculateMetrics([message]); + + return { + id, + chunkType: 'system', + message, + commandOutput, + startTime: message.timestamp, + endTime: message.timestamp, + durationMs: 0, + metrics, + rawMessages: [message], + }; +} + +/** + * Build a CompactChunk from a compact summary message. + */ +export function buildCompactChunk(message: ParsedMessage): EnhancedCompactChunk { + const id = generateStableChunkId('compact', message); + const metrics = calculateMetrics([message]); + + return { + id, + chunkType: 'compact', + message, + startTime: message.timestamp, + endTime: message.timestamp, + durationMs: 0, + metrics, + rawMessages: [message], + }; +} + +/** + * Extract command output from tag. + */ +function extractCommandOutput(message: ParsedMessage): string { + const content = typeof message.content === 'string' ? message.content : ''; + const match = /([\s\S]*?)<\/local-command-stdout>/.exec(content); + const matchStderr = /([\s\S]*?)<\/local-command-stderr>/.exec(content); + if (match) { + return match[1]; + } + if (matchStderr) { + return matchStderr[1]; + } + return content; +} + +/** + * Build an AIChunk from buffered AI messages. + */ +export function buildAIChunkFromBuffer( + responses: ParsedMessage[], + subagents: Process[], + allMessages: ParsedMessage[] +): EnhancedAIChunk { + // Use first response message's UUID for stable ID + const id = + responses.length > 0 ? generateStableChunkId('ai', responses[0]) : `ai-empty-${Date.now()}`; // Fallback for edge case + const { startTime, endTime, durationMs } = calculateAIChunkTiming(responses); + const metrics = calculateMetrics(responses); + const toolExecutions = buildToolExecutions(responses); + + // Collect sidechain messages for this time range + const sidechainMessages = collectSidechainMessages(allMessages, startTime, endTime); + + const chunk: EnhancedAIChunk = { + id, + chunkType: 'ai', + responses, + startTime, + endTime, + durationMs, + metrics, + processes: [], + sidechainMessages, + toolExecutions, + semanticSteps: [], + rawMessages: responses, + }; + + // Link processes to this chunk + linkProcessesToAIChunk(chunk, subagents); + + // Extract semantic steps using the extracted module + chunk.semanticSteps = extractSemanticStepsFromAIChunk(chunk); + chunk.semanticSteps = fillTimelineGaps({ + steps: chunk.semanticSteps, + chunkStartTime: chunk.startTime, + chunkEndTime: chunk.endTime, + }); + calculateStepContext(chunk.semanticSteps, chunk.rawMessages); + chunk.semanticStepGroups = buildSemanticStepGroups(chunk.semanticSteps); + + return chunk; +} + +/** + * Calculate timing for AI chunks (responses only, no user message). + */ +function calculateAIChunkTiming(responses: ParsedMessage[]): { + startTime: Date; + endTime: Date; + durationMs: number; +} { + if (responses.length === 0) { + const now = new Date(); + return { startTime: now, endTime: now, durationMs: 0 }; + } + + const startTime = responses[0].timestamp; + let endTime = startTime; + for (const resp of responses) { + if (resp.timestamp > endTime) { + endTime = resp.timestamp; + } + } + + return { + startTime, + endTime, + durationMs: endTime.getTime() - startTime.getTime(), + }; +} + +/** + * Collect sidechain messages in a time range. + */ +function collectSidechainMessages( + messages: ParsedMessage[], + startTime: Date, + endTime: Date | undefined +): ParsedMessage[] { + return messages.filter((m) => { + if (!m.isSidechain) return false; + if (m.timestamp < startTime) return false; + if (endTime && m.timestamp >= endTime) return false; + return true; + }); +} diff --git a/src/main/services/analysis/ConversationGroupBuilder.ts b/src/main/services/analysis/ConversationGroupBuilder.ts new file mode 100644 index 00000000..96780091 --- /dev/null +++ b/src/main/services/analysis/ConversationGroupBuilder.ts @@ -0,0 +1,213 @@ +/** + * ConversationGroupBuilder - Alternative grouping strategy for conversation flow. + * + * Groups one user message with all AI responses until the next user message. + * This is a cleaner alternative to buildChunks() that: + * - Uses simpler time-based grouping + * - Separates Task executions from regular tool executions + * - Links subagents more explicitly via TaskExecution + */ + +import { + type ConversationGroup, + isParsedUserChunkMessage, + type ParsedMessage, + type Process, + type TaskExecution, + type ToolCall, + type ToolExecution, +} from '@main/types'; +import { calculateMetrics } from '@main/utils/jsonl'; + +/** + * Build conversation groups using simplified grouping strategy. + * Groups one user message with all AI responses until the next user message. + */ +export function buildGroups(messages: ParsedMessage[], subagents: Process[]): ConversationGroup[] { + const groups: ConversationGroup[] = []; + + // Step 1: Filter to main thread only (not sidechain) + const mainMessages = messages.filter((m) => !m.isSidechain); + + // Step 2: Find all REAL user messages (these start groups) + // Use isParsedUserChunkMessage to filter out noise + const userMessages = mainMessages.filter(isParsedUserChunkMessage); + + // Step 3: For each user message, collect all AI responses until next user message + for (let i = 0; i < userMessages.length; i++) { + const userMsg = userMessages[i]; + const nextUserMsg = userMessages[i + 1]; + + // Collect all messages between this user message and the next + const aiResponses = collectAIResponses(mainMessages, userMsg, nextUserMsg); + + // Separate Task tool results from regular tool executions + const { taskExecutions, regularToolExecutions } = separateTaskExecutions( + aiResponses, + subagents + ); + + // Link subagents to this group + const groupSubagents = linkSubagentsToGroup(userMsg, nextUserMsg, subagents); + + // Calculate metrics + const { startTime, endTime, durationMs } = calculateGroupTiming(userMsg, aiResponses); + const metrics = calculateMetrics([userMsg, ...aiResponses]); + + groups.push({ + id: `group-${i + 1}`, + type: 'user-ai-exchange', + userMessage: userMsg, + aiResponses, + processes: groupSubagents, + toolExecutions: regularToolExecutions, + taskExecutions, + startTime, + endTime, + durationMs, + metrics, + }); + } + + return groups; +} + +/** + * Collect AI responses between a user message and the next user message. + * Simpler than collectResponses - just uses timestamp boundaries. + */ +function collectAIResponses( + messages: ParsedMessage[], + userMsg: ParsedMessage, + nextUserMsg: ParsedMessage | undefined +): ParsedMessage[] { + const responses: ParsedMessage[] = []; + const startTime = userMsg.timestamp; + const endTime = nextUserMsg?.timestamp; + + for (const msg of messages) { + // Skip if before this user message + if (msg.timestamp <= startTime) continue; + + // Skip if at or after next user message + if (endTime && msg.timestamp >= endTime) continue; + + // Include ALL non-user messages (assistant + internal user messages) + if (msg.type === 'assistant' || (msg.type === 'user' && msg.isMeta === true)) { + responses.push(msg); + } + } + + return responses; +} + +/** + * Separate Task executions from regular tool executions. + * Task tools spawn subagents, so we track them separately to avoid duplication. + */ +function separateTaskExecutions( + responses: ParsedMessage[], + allSubagents: Process[] +): { taskExecutions: TaskExecution[]; regularToolExecutions: ToolExecution[] } { + const taskExecutions: TaskExecution[] = []; + const regularToolExecutions: ToolExecution[] = []; + + // Build map of tool_use_id -> subagent for Task calls + const taskIdToSubagent = new Map(); + for (const subagent of allSubagents) { + if (subagent.parentTaskId) { + taskIdToSubagent.set(subagent.parentTaskId, subagent); + } + } + + // Collect all tool calls + const toolCalls = new Map(); + for (const msg of responses) { + if (msg.type === 'assistant') { + for (const toolCall of msg.toolCalls) { + toolCalls.set(toolCall.id, { call: toolCall, timestamp: msg.timestamp }); + } + } + } + + // Match with results + for (const msg of responses) { + if (msg.type === 'user' && msg.isMeta === true && msg.sourceToolUseID) { + const callInfo = toolCalls.get(msg.sourceToolUseID); + if (!callInfo) continue; + + // Check if this is a Task call with a subagent + const subagent = taskIdToSubagent.get(msg.sourceToolUseID); + if (callInfo.call.name === 'Task' && subagent) { + // This is a Task execution + taskExecutions.push({ + taskCall: callInfo.call, + taskCallTimestamp: callInfo.timestamp, + subagent, + toolResult: msg, + resultTimestamp: msg.timestamp, + durationMs: msg.timestamp.getTime() - callInfo.timestamp.getTime(), + }); + } else { + // Regular tool execution + const result = msg.toolResults[0]; + if (result) { + regularToolExecutions.push({ + toolCall: callInfo.call, + result, + startTime: callInfo.timestamp, + endTime: msg.timestamp, + durationMs: msg.timestamp.getTime() - callInfo.timestamp.getTime(), + }); + } + } + } + } + + return { taskExecutions, regularToolExecutions }; +} + +/** + * Link subagents to a conversation group based on timing. + */ +function linkSubagentsToGroup( + userMsg: ParsedMessage, + nextUserMsg: ParsedMessage | undefined, + allSubagents: Process[] +): Process[] { + const groupSubagents: Process[] = []; + const startTime = userMsg.timestamp; + const endTime = nextUserMsg?.timestamp ?? new Date(Date.now() + 1000 * 60 * 60 * 24); // Far future if no next message + + // Collect subagents that start within this group's time range + for (const subagent of allSubagents) { + if (subagent.startTime >= startTime && subagent.startTime < endTime) { + groupSubagents.push(subagent); + } + } + + return groupSubagents; +} + +/** + * Calculate group timing from user message and AI responses. + */ +function calculateGroupTiming( + userMsg: ParsedMessage, + aiResponses: ParsedMessage[] +): { startTime: Date; endTime: Date; durationMs: number } { + const startTime = userMsg.timestamp; + + let endTime = startTime; + for (const resp of aiResponses) { + if (resp.timestamp > endTime) { + endTime = resp.timestamp; + } + } + + return { + startTime, + endTime, + durationMs: endTime.getTime() - startTime.getTime(), + }; +} diff --git a/src/main/services/analysis/ProcessLinker.ts b/src/main/services/analysis/ProcessLinker.ts new file mode 100644 index 00000000..addcc2c1 --- /dev/null +++ b/src/main/services/analysis/ProcessLinker.ts @@ -0,0 +1,61 @@ +/** + * ProcessLinker service - Links subagent processes to AI chunks. + * + * Uses a two-tier linking strategy: + * 1. Primary: parentTaskId matching - Links subagents to chunks containing the Task tool call + * that spawned them. This is reliable even when the response is still in progress. + * 2. Fallback: Timing-based - For orphaned subagents without parentTaskId, falls back to + * checking if the subagent's startTime falls within the chunk's time range. + */ + +import { type EnhancedAIChunk, type Process } from '@main/types'; + +/** + * Link processes to a single AI chunk. + * + * Uses a two-tier linking strategy: + * 1. Primary: parentTaskId matching - Links subagents to chunks containing the Task tool call + * that spawned them. This is reliable even when the response is still in progress. + * 2. Fallback: Timing-based - For orphaned subagents without parentTaskId, falls back to + * checking if the subagent's startTime falls within the chunk's time range. + */ +export function linkProcessesToAIChunk(chunk: EnhancedAIChunk, subagents: Process[]): void { + // Build set of Task tool IDs from this chunk's responses + const chunkTaskIds = new Set(); + for (const response of chunk.responses) { + for (const toolCall of response.toolCalls) { + if (toolCall.isTask) { + chunkTaskIds.add(toolCall.id); + } + } + } + + // Track which subagents have been linked + const linkedSubagentIds = new Set(); + + // Primary linking: Match subagents to Task calls by parentTaskId + for (const subagent of subagents) { + if (subagent.parentTaskId && chunkTaskIds.has(subagent.parentTaskId)) { + chunk.processes.push(subagent); + linkedSubagentIds.add(subagent.id); + } + } + + // Fallback linking: For orphaned subagents, use timing-based matching + // This handles edge cases where parentTaskId might not be set + for (const subagent of subagents) { + if (linkedSubagentIds.has(subagent.id)) { + continue; // Already linked via parentTaskId + } + + // Only use timing fallback if subagent has no parentTaskId + // (If it has parentTaskId but didn't match, it belongs to a different chunk) + if (!subagent.parentTaskId) { + if (subagent.startTime >= chunk.startTime && subagent.startTime <= chunk.endTime) { + chunk.processes.push(subagent); + } + } + } + + chunk.processes.sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); +} diff --git a/src/main/services/analysis/SemanticStepExtractor.ts b/src/main/services/analysis/SemanticStepExtractor.ts new file mode 100644 index 00000000..12c4195f --- /dev/null +++ b/src/main/services/analysis/SemanticStepExtractor.ts @@ -0,0 +1,210 @@ +/** + * SemanticStepExtractor - Extracts semantic steps from AI chunks. + * + * Semantic steps represent logical units of work within AI responses: + * - thinking: Claude's reasoning process + * - tool_call: Tool invocation + * - tool_result: Tool execution result + * - output: Text output from Claude + * - subagent: Nested agent execution + * - interruption: User interruption + */ + +import { countContentTokens } from '@main/utils/tokenizer'; + +import type { AIChunk, EnhancedAIChunk, SemanticStep } from '@main/types'; + +/** + * Extract semantic steps from AI chunk responses. + * Semantic steps represent logical units of work within responses. + * + * Note: ALL tool calls are included, including Task tools with subagents. + * Task tools are filtered in the renderer's buildDisplayItems, + * but they are kept here for accurate context token tracking in aggregateToolOutputs. + */ +export function extractSemanticStepsFromAIChunk(chunk: AIChunk | EnhancedAIChunk): SemanticStep[] { + const steps: SemanticStep[] = []; + let stepIdCounter = 0; + + // Note: Task tool calls are included in semantic steps for context token tracking. + // The renderer's buildDisplayItems filters Task tools with subagents. + + // Process only AI responses (no user message in AIChunk) + for (const msg of chunk.responses) { + if (msg.type === 'assistant') { + // Extract from content blocks + const content = Array.isArray(msg.content) ? msg.content : []; + + for (const block of content) { + if (block.type === 'thinking' && block.thinking) { + // Calculate tokens for thinking content (output from Claude) + const thinkingTokens = countContentTokens(block.thinking); + + steps.push({ + id: `${msg.uuid}-thinking-${stepIdCounter++}`, + type: 'thinking', + startTime: new Date(msg.timestamp), + durationMs: 0, // Estimated from token count + content: { + thinkingText: block.thinking, + tokenCount: thinkingTokens, // Pre-computed token count + }, + tokens: { + input: 0, + output: thinkingTokens, // Thinking is output from Claude + }, + context: msg.agentId ? 'subagent' : 'main', + agentId: msg.agentId, + sourceMessageId: msg.uuid, + }); + } + + if (block.type === 'tool_use' && block.id && block.name) { + // Include ALL tool calls in semantic steps, including Task tools with processes. + // Task tools with processes are filtered from DISPLAY in the renderer's buildDisplayItems, + // but they should be included here for accurate context token tracking. + // The renderer's aggregateToolOutputs will correctly count Task tool tokens + // as part of the main session's context consumption. + + // Calculate tool call tokens directly from name + input + // This reflects what actually enters the context window + const callTokens = countContentTokens(block.name + JSON.stringify(block.input)); + + steps.push({ + id: block.id, + type: 'tool_call', + startTime: new Date(msg.timestamp), + durationMs: 0, + content: { + toolName: block.name, + toolInput: block.input, + sourceModel: msg.model, + }, + tokens: { + input: callTokens, + output: 0, + }, + context: msg.agentId ? 'subagent' : 'main', + agentId: msg.agentId, + sourceMessageId: msg.uuid, + }); + } + + if (block.type === 'text' && block.text) { + // Calculate tokens for text output (Claude's generated text) + const textTokens = countContentTokens(block.text); + + steps.push({ + id: `${msg.uuid}-output-${stepIdCounter++}`, + type: 'output', + startTime: new Date(msg.timestamp), + durationMs: 0, + content: { + outputText: block.text, + tokenCount: textTokens, // Pre-computed token count for consistency + }, + tokens: { + input: 0, // Text output is generated by Claude, not input + output: textTokens, + }, + context: msg.agentId ? 'subagent' : 'main', + agentId: msg.agentId, + sourceMessageId: msg.uuid, + }); + } + } + } + + // Tool results from internal user messages + // Note: isMeta can be true or null in JSONL, so check for toolResults presence directly + if (msg.type === 'user' && msg.toolResults && msg.toolResults.length > 0) { + for (const result of msg.toolResults) { + steps.push({ + id: result.toolUseId, + type: 'tool_result', + startTime: new Date(msg.timestamp), + durationMs: 0, + content: { + toolResultContent: + typeof result.content === 'string' ? result.content : JSON.stringify(result.content), + isError: result.isError, + toolUseResult: msg.toolUseResult, // Enriched data from message + tokenCount: countContentTokens(result.content), // Pre-computed token count + }, + context: msg.agentId ? 'subagent' : 'main', + agentId: msg.agentId, + }); + } + } + + // User interruption messages + // These are user messages with array content containing text like "[Request interrupted by user]" + if (msg.type === 'user' && Array.isArray(msg.content)) { + let foundInterruption = false; + for (const block of msg.content) { + if (block.type === 'text' && block.text) { + const textContent = block.text; + // Check for interruption patterns + if ( + textContent.includes('[Request interrupted by user]') || + textContent.includes('[Request interrupted by user for tool use]') + ) { + steps.push({ + id: `${msg.uuid}-interruption-${stepIdCounter++}`, + type: 'interruption', + startTime: new Date(msg.timestamp), + durationMs: 0, + content: { + interruptionText: textContent, + }, + context: msg.agentId ? 'subagent' : 'main', + agentId: msg.agentId, + }); + foundInterruption = true; + } + } + } + + // User-rejected tool use (toolUseResult field is "User rejected tool use") + if (!foundInterruption && (msg.toolUseResult as unknown) === 'User rejected tool use') { + steps.push({ + id: `${msg.uuid}-interruption-${stepIdCounter++}`, + type: 'interruption', + startTime: new Date(msg.timestamp), + durationMs: 0, + content: { + interruptionText: 'Request interrupted by user', + }, + context: msg.agentId ? 'subagent' : 'main', + agentId: msg.agentId, + }); + } + } + } + + // Link processes as steps + for (const process of chunk.processes) { + steps.push({ + id: process.id, + type: 'subagent', + startTime: process.startTime, + endTime: process.endTime, + durationMs: process.durationMs, + content: { + subagentId: process.id, + subagentDescription: process.description, + }, + tokens: { + input: process.metrics.inputTokens, + output: process.metrics.outputTokens, + cached: process.metrics.cacheReadTokens, + }, + isParallel: process.isParallel, + context: 'subagent', + agentId: process.id, + }); + } + + // Sort by startTime + return steps.sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); +} diff --git a/src/main/services/analysis/SemanticStepGrouper.ts b/src/main/services/analysis/SemanticStepGrouper.ts new file mode 100644 index 00000000..9f3a9ec2 --- /dev/null +++ b/src/main/services/analysis/SemanticStepGrouper.ts @@ -0,0 +1,114 @@ +/** + * SemanticStepGrouper - Groups semantic steps for UI presentation. + * + * Groups steps by their source assistant message for collapsible UI. + * Steps from the same assistant message share the message UUID. + */ + +import type { SemanticStep, SemanticStepGroup } from '@main/types'; + +/** + * Build semantic step groups from steps. + * Groups steps by their source assistant message for collapsible UI. + */ +export function buildSemanticStepGroups(steps: SemanticStep[]): SemanticStepGroup[] { + const groups: SemanticStepGroup[] = []; + let groupIdCounter = 0; + + // Group steps by assistant message or standalone type + const stepsByGroup = new Map(); + + for (const step of steps) { + const messageId = extractMessageIdFromStep(step); + const existingSteps = stepsByGroup.get(messageId) ?? []; + existingSteps.push(step); + stepsByGroup.set(messageId, existingSteps); + } + + // Build groups + for (const [messageId, groupSteps] of stepsByGroup) { + const startTime = groupSteps[0].startTime; + const endTimes = groupSteps + .map((s) => s.endTime ?? new Date(s.startTime.getTime() + s.durationMs)) + .map((d) => d.getTime()); + const endTime = new Date(Math.max(...endTimes)); + const totalDuration = groupSteps.reduce((sum, s) => sum + s.durationMs, 0); + + groups.push({ + id: `group-${++groupIdCounter}`, + label: buildGroupLabel(groupSteps), + steps: groupSteps, + isGrouped: messageId !== null && groupSteps.length > 1, + sourceMessageId: messageId ?? undefined, + startTime, + endTime, + totalDuration, + }); + } + + // Sort by startTime + return groups.sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); +} + +/** + * Extract the assistant message ID from a step, or null if standalone. + * Steps from the same assistant message share the message UUID. + * Subagents, tool results, and interruptions are standalone (null). + */ +function extractMessageIdFromStep(step: SemanticStep): string | null { + // Use sourceMessageId if available + if (step.sourceMessageId) { + return step.sourceMessageId; + } + + // Standalone steps (not grouped) + if (step.type === 'subagent') return null; + if (step.type === 'tool_result') return null; + if (step.type === 'interruption') return null; + if (step.type === 'tool_call') return null; // Tool calls are standalone + + return null; +} + +/** + * Build a descriptive label for a group. + */ +function buildGroupLabel(steps: SemanticStep[]): string { + if (steps.length === 1) { + const step = steps[0]; + switch (step.type) { + case 'thinking': + return 'Thinking'; + case 'tool_call': + return `Tool: ${step.content.toolName ?? 'Unknown'}`; + case 'tool_result': + return `Result: ${step.content.isError ? 'Error' : 'Success'}`; + case 'subagent': + return step.content.subagentDescription ?? 'Subagent'; + case 'output': + return 'Output'; + case 'interruption': + return 'Interruption'; + } + } + + // Multiple steps grouped together + const hasThinking = steps.some((s) => s.type === 'thinking'); + const hasOutput = steps.some((s) => s.type === 'output'); + const toolCalls = steps.filter((s) => s.type === 'tool_call'); + + if (toolCalls.length > 0) { + return `Tools (${toolCalls.length})`; + } + if (hasThinking && hasOutput) { + return 'Assistant Response'; + } + if (hasThinking) { + return 'Thinking'; + } + if (hasOutput) { + return 'Output'; + } + + return `Response (${steps.length} steps)`; +} diff --git a/src/main/services/analysis/SubagentDetailBuilder.ts b/src/main/services/analysis/SubagentDetailBuilder.ts new file mode 100644 index 00000000..82fea77e --- /dev/null +++ b/src/main/services/analysis/SubagentDetailBuilder.ts @@ -0,0 +1,135 @@ +/** + * SubagentDetailBuilder - Builds detailed information for subagent drill-down. + * + * Loads subagent JSONL files, resolves nested subagents, and builds + * complete SubagentDetail objects for the drill-down modal. + */ + +import { + type EnhancedAIChunk, + type EnhancedChunk, + isEnhancedAIChunk, + type ParsedMessage, + type Process, + type SemanticStepGroup, + type SubagentDetail, +} from '@main/types'; +import { countTokens } from '@main/utils/tokenizer'; +import { createLogger } from '@shared/utils/logger'; + +const logger = createLogger('Service:SubagentDetailBuilder'); + +import { buildSemanticStepGroups } from './SemanticStepGrouper'; + +import type { SubagentResolver } from '../discovery/SubagentResolver'; +import type { SessionParser } from '../parsing/SessionParser'; + +/** + * Build detailed information for a specific subagent. + * Used for drill-down modal to show subagent's internal execution. + * + * @param projectId - Project ID + * @param _sessionId - Parent session ID (currently unused, kept for API consistency) + * @param subagentId - Subagent ID to load + * @param sessionParser - SessionParser instance for parsing subagent file + * @param subagentResolver - SubagentResolver instance for nested subagents + * @param buildChunksFn - Function to build chunks from messages and subagents + * @returns SubagentDetail or null if not found + */ +export async function buildSubagentDetail( + projectId: string, + _sessionId: string, // Unused but kept for API consistency + subagentId: string, + sessionParser: SessionParser, + subagentResolver: SubagentResolver, + buildChunksFn: (messages: ParsedMessage[], subagents: Process[]) => EnhancedChunk[] +): Promise { + try { + const fs = await import('fs/promises'); + const path = await import('path'); + const os = await import('os'); + + // Construct path to subagent JSONL file + const claudeDir = path.join(os.homedir(), '.claude', 'projects'); + const subagentPath = path.join(claudeDir, projectId, 'subagents', `agent-${subagentId}.jsonl`); + + // Check if file exists + try { + await fs.access(subagentPath); + } catch { + logger.warn(`Subagent file not found: ${subagentPath}`); + return null; + } + + // Parse subagent JSONL file + const parsedSession = await sessionParser.parseSessionFile(subagentPath); + + // Resolve nested subagents within this subagent + const nestedSubagents = await subagentResolver.resolveSubagents( + projectId, + subagentId, // Use subagentId as sessionId for nested resolution + parsedSession.taskCalls + ); + + // Build chunks with semantic steps + const chunks = buildChunksFn(parsedSession.messages, nestedSubagents); + + // Extract description (try to get from first user message) + let description = 'Subagent'; + if (parsedSession.messages.length > 0) { + const firstUserMsg = parsedSession.messages.find( + (m) => m.type === 'user' && typeof m.content === 'string' + ); + if (firstUserMsg && typeof firstUserMsg.content === 'string') { + description = firstUserMsg.content.substring(0, 100); + if (firstUserMsg.content.length > 100) { + description += '...'; + } + } + } + + // Calculate timing + const times = parsedSession.messages.map((m) => m.timestamp.getTime()); + const startTime = new Date(Math.min(...times)); + const endTime = new Date(Math.max(...times)); + const duration = endTime.getTime() - startTime.getTime(); + + // Calculate thinking tokens + let thinkingTokens = 0; + for (const msg of parsedSession.messages) { + if (msg.type === 'assistant' && Array.isArray(msg.content)) { + for (const block of msg.content) { + if (block.type === 'thinking' && block.thinking) { + thinkingTokens += countTokens(block.thinking); + } + } + } + } + + // Build semantic step groups from AI chunks only (UserChunks don't have semanticSteps) + const allSemanticSteps = chunks + .filter((c): c is EnhancedAIChunk => isEnhancedAIChunk(c)) + .flatMap((c) => c.semanticSteps); + const semanticStepGroups: SemanticStepGroup[] | undefined = + allSemanticSteps.length > 0 ? buildSemanticStepGroups(allSemanticSteps) : undefined; + + return { + id: subagentId, + description, + chunks, + semanticStepGroups, + startTime, + endTime, + duration, + metrics: { + inputTokens: parsedSession.metrics.inputTokens, + outputTokens: parsedSession.metrics.outputTokens, + thinkingTokens, + messageCount: parsedSession.metrics.messageCount, + }, + }; + } catch (error) { + logger.error(`Error building subagent detail for ${subagentId}:`, error); + return null; + } +} diff --git a/src/main/services/analysis/ToolExecutionBuilder.ts b/src/main/services/analysis/ToolExecutionBuilder.ts new file mode 100644 index 00000000..c7e2656d --- /dev/null +++ b/src/main/services/analysis/ToolExecutionBuilder.ts @@ -0,0 +1,82 @@ +/** + * ToolExecutionBuilder - Builds tool execution tracking from messages. + * + * Matches tool calls with their results using: + * 1. sourceToolUseID for accurate internal user message matching + * 2. toolResults array fallback for other patterns + */ + +import type { ParsedMessage, ToolCall, ToolExecution } from '@main/types'; + +/** + * Build tool execution tracking from messages. + * Enhanced to use sourceToolUseID for more accurate matching. + */ +export function buildToolExecutions(messages: ParsedMessage[]): ToolExecution[] { + const executions: ToolExecution[] = []; + const toolCallMap = new Map(); + + // First pass: collect all tool calls + for (const msg of messages) { + for (const toolCall of msg.toolCalls) { + toolCallMap.set(toolCall.id, { + call: toolCall, + startTime: msg.timestamp, + }); + } + } + + // Second pass: match with results and build executions + // Try sourceToolUseID first (most accurate), then fall back to toolResults array + for (const msg of messages) { + // Check if this message has a sourceToolUseID (internal user messages) + if (msg.sourceToolUseID) { + const callInfo = toolCallMap.get(msg.sourceToolUseID); + if (callInfo && msg.toolResults.length > 0) { + // Use the first tool result for this internal user message + const result = msg.toolResults[0]; + executions.push({ + toolCall: callInfo.call, + result, + startTime: callInfo.startTime, + endTime: msg.timestamp, + durationMs: msg.timestamp.getTime() - callInfo.startTime.getTime(), + }); + } + } + + // Also check toolResults array for any results not matched above + for (const result of msg.toolResults) { + // Skip if already matched via sourceToolUseID + const alreadyMatched = executions.some((e) => e.result?.toolUseId === result.toolUseId); + if (alreadyMatched) continue; + + const callInfo = toolCallMap.get(result.toolUseId); + if (callInfo) { + executions.push({ + toolCall: callInfo.call, + result, + startTime: callInfo.startTime, + endTime: msg.timestamp, + durationMs: msg.timestamp.getTime() - callInfo.startTime.getTime(), + }); + } + } + } + + // Add calls without results + for (const [id, callInfo] of toolCallMap) { + const hasResult = executions.some((e) => e.toolCall.id === id); + if (!hasResult) { + executions.push({ + toolCall: callInfo.call, + startTime: callInfo.startTime, + }); + } + } + + // Sort by start time + executions.sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); + + return executions; +} diff --git a/src/main/services/analysis/ToolResultExtractor.ts b/src/main/services/analysis/ToolResultExtractor.ts new file mode 100644 index 00000000..eccde23d --- /dev/null +++ b/src/main/services/analysis/ToolResultExtractor.ts @@ -0,0 +1,224 @@ +/** + * ToolResultExtractor service - Extracts tool results from messages. + * + * Provides utilities for: + * - Building tool_use maps for linking results to calls + * - Building tool_result maps for token estimation + * - Estimating token counts from content + * - Extracting tool results from various message formats + */ + +import { isToolResultContent, type ParsedMessage } from '@main/types'; +import { countContentTokens } from '@main/utils/tokenizer'; + +// ============================================================================= +// Types +// ============================================================================= + +/** + * Extracted tool result information for trigger matching. + */ +export interface ExtractedToolResult { + toolUseId: string; + isError: boolean; + content: string | unknown[]; + toolName?: string; +} + +/** + * Tool use information from assistant messages. + */ +export interface ToolUseInfo { + name: string; + input: Record; +} + +/** + * Tool result information for token estimation. + */ +export interface ToolResultInfo { + content: string | unknown[]; + isError: boolean; +} + +// ============================================================================= +// Map Building +// ============================================================================= + +/** + * Builds a map of tool_use_id to tool_use content. + * This allows linking tool_results back to their tool_use calls to check tool names. + */ +export function buildToolUseMap(messages: ParsedMessage[]): Map { + const map = new Map(); + + for (const message of messages) { + if (message.type !== 'assistant') continue; + + // Check content array for tool_use blocks + if (Array.isArray(message.content)) { + for (const block of message.content) { + if (block.type === 'tool_use') { + const toolUse = block; + map.set(toolUse.id, { + name: toolUse.name, + input: toolUse.input || {}, + }); + } + } + } + + // Also check toolCalls if present + if (message.toolCalls) { + for (const toolCall of message.toolCalls) { + map.set(toolCall.id, { + name: toolCall.name, + input: toolCall.input || {}, + }); + } + } + } + + return map; +} + +/** + * Builds a map of tool_use_id to tool_result content. + * Used for estimating output tokens per tool_use. + */ +export function buildToolResultMap(messages: ParsedMessage[]): Map { + const map = new Map(); + + for (const message of messages) { + // Check content array for tool_result blocks + if (Array.isArray(message.content)) { + for (const block of message.content) { + if (isToolResultContent(block)) { + map.set(block.tool_use_id, { + content: block.content, + isError: block.is_error === true, + }); + } + } + } + + // Also check toolResults array if present + if (message.toolResults) { + for (const toolResult of message.toolResults) { + map.set(toolResult.toolUseId, { + content: toolResult.content, + isError: toolResult.isError === true, + }); + } + } + + // Also check toolUseResult if present (enriched data) + if (message.toolUseResult && message.sourceToolUseID) { + const content = extractContentFromToolUseResult(message.toolUseResult); + const isError = + message.toolUseResult.isError === true || message.toolUseResult.is_error === true; + map.set(message.sourceToolUseID, { content, isError }); + } + } + + return map; +} + +// ============================================================================= +// Token Estimation +// ============================================================================= + +/** + * Estimates token count from content using the shared tokenizer. + * Uses the same calculation as ChunkBuilder for consistency with UI display. + */ +export function estimateTokens(content: string | unknown[] | Record): number { + if (typeof content === 'string') { + return countContentTokens(content); + } + // For objects/arrays, use countContentTokens which handles JSON.stringify + return countContentTokens(content as unknown[]); +} + +// ============================================================================= +// Tool Result Extraction +// ============================================================================= + +/** + * Extracts content string from toolUseResult. + */ +function extractContentFromToolUseResult(toolUseResult: Record): string { + if (typeof toolUseResult.error === 'string') { + return toolUseResult.error; + } + if (typeof toolUseResult.stderr === 'string' && toolUseResult.stderr.trim()) { + return toolUseResult.stderr; + } + if (typeof toolUseResult.content === 'string') { + return toolUseResult.content; + } + if (typeof toolUseResult.message === 'string') { + return toolUseResult.message; + } + return ''; +} + +/** + * Extracts tool results from a message. + * Handles multiple patterns of tool result storage. + * + * @param message - The parsed message to extract from + * @param findToolNameFn - Function to find tool name by tool use ID + */ +export function extractToolResults( + message: ParsedMessage, + findToolNameFn: (message: ParsedMessage, toolUseId: string) => string | null +): ExtractedToolResult[] { + const results: ExtractedToolResult[] = []; + + // Pattern 1: Check toolResults array on ParsedMessage + if (message.toolResults && message.toolResults.length > 0) { + for (const toolResult of message.toolResults) { + results.push({ + toolUseId: toolResult.toolUseId, + isError: toolResult.isError === true, + content: toolResult.content, + toolName: findToolNameFn(message, toolResult.toolUseId) ?? undefined, + }); + } + } + + // Pattern 2: Check toolUseResult field (enriched data from entry) + if (message.toolUseResult) { + const toolUseResult = message.toolUseResult; + const hasError = toolUseResult.isError === true || toolUseResult.is_error === true; + const toolUseId = + (typeof toolUseResult.toolUseId === 'string' ? toolUseResult.toolUseId : undefined) ?? + message.sourceToolUseID; + + if (toolUseId) { + results.push({ + toolUseId, + isError: hasError, + content: extractContentFromToolUseResult(toolUseResult), + toolName: typeof toolUseResult.toolName === 'string' ? toolUseResult.toolName : undefined, + }); + } + } + + // Pattern 3: Check content blocks for tool_result + if (Array.isArray(message.content)) { + for (const block of message.content) { + if (isToolResultContent(block)) { + results.push({ + toolUseId: block.tool_use_id, + isError: block.is_error === true, + content: block.content, + toolName: findToolNameFn(message, block.tool_use_id) ?? undefined, + }); + } + } + } + + return results; +} diff --git a/src/main/services/analysis/ToolSummaryFormatter.ts b/src/main/services/analysis/ToolSummaryFormatter.ts new file mode 100644 index 00000000..840d5b0c --- /dev/null +++ b/src/main/services/analysis/ToolSummaryFormatter.ts @@ -0,0 +1,113 @@ +/** + * ToolSummaryFormatter service - Formats tool information for display. + * + * Provides utilities for: + * - Extracting filenames from paths + * - Truncating long strings + * - Formatting token counts + * - Generating human-readable tool summaries + */ + +import { formatTokens } from '@shared/utils/tokenFormatting'; +import * as path from 'path'; + +// Re-export for backwards compatibility +export { formatTokens }; + +// ============================================================================= +// String Utilities +// ============================================================================= + +/** + * Extracts filename from a file path. + */ +function getFileName(filePath: string): string { + return path.basename(filePath) || filePath; +} + +/** + * Truncates a string to a maximum length with ellipsis. + */ +function truncate(str: string, maxLength: number): string { + if (str.length <= maxLength) return str; + return str.slice(0, maxLength) + '...'; +} + +// ============================================================================= +// Tool Summary Generation +// ============================================================================= + +/** + * Generates a human-readable summary for a tool call. + * Simplified version of LinkedToolItem's getToolSummary. + */ +export function getToolSummary(toolName: string, input: Record): string { + switch (toolName) { + case 'Edit': + case 'Read': + case 'Write': { + const filePath = input.file_path as string | undefined; + if (filePath) return getFileName(filePath); + return toolName; + } + + case 'Bash': { + const description = input.description as string | undefined; + const command = input.command as string | undefined; + if (description) return truncate(description, 50); + if (command) return truncate(command, 50); + return 'Bash'; + } + + case 'Grep': + case 'Glob': { + const pattern = input.pattern as string | undefined; + if (pattern) return `"${truncate(pattern, 30)}"`; + return toolName; + } + + case 'Task': { + const description = input.description as string | undefined; + const prompt = input.prompt as string | undefined; + const subagentType = input.subagent_type as string | undefined; + const desc = description ?? prompt; + const typeStr = subagentType ? `${subagentType} - ` : ''; + if (desc) return `${typeStr}${truncate(desc, 40)}`; + return subagentType ?? 'Task'; + } + + case 'Skill': { + const skill = input.skill as string | undefined; + if (skill) return skill; + return 'Skill'; + } + + case 'WebFetch': { + const url = input.url as string | undefined; + if (url) { + try { + const urlObj = new URL(url); + return truncate(urlObj.hostname + urlObj.pathname, 50); + } catch { + return truncate(url, 50); + } + } + return 'WebFetch'; + } + + case 'WebSearch': { + const query = input.query as string | undefined; + if (query) return `"${truncate(query, 40)}"`; + return 'WebSearch'; + } + + default: { + // Try common parameter names + const nameField = input.name ?? input.path ?? input.file ?? input.query ?? input.command; + if (typeof nameField === 'string') { + return truncate(nameField, 50); + } + return toolName; + } + } +} diff --git a/src/main/services/analysis/index.ts b/src/main/services/analysis/index.ts new file mode 100644 index 00000000..7f3adbe9 --- /dev/null +++ b/src/main/services/analysis/index.ts @@ -0,0 +1,26 @@ +/** + * Analysis services - Chunk building and session analysis. + * + * Exports: + * - ChunkBuilder: Builds visualization chunks from parsed session data + * - ChunkFactory: Creates individual chunk objects + * - ProcessLinker: Links subagent processes to chunks + * - ConversationGroupBuilder: Alternative grouping strategy + * - SemanticStepExtractor: Extracts semantic steps from AI chunks + * - SemanticStepGrouper: Groups semantic steps for UI + * - ToolExecutionBuilder: Builds tool execution tracking + * - ToolResultExtractor: Extracts results from tool calls + * - ToolSummaryFormatter: Formats tool summaries + * - SubagentDetailBuilder: Builds subagent drill-down details + */ + +export * from './ChunkBuilder'; +export * from './ChunkFactory'; +export * from './ConversationGroupBuilder'; +export * from './ProcessLinker'; +export * from './SemanticStepExtractor'; +export * from './SemanticStepGrouper'; +export * from './SubagentDetailBuilder'; +export * from './ToolExecutionBuilder'; +export * from './ToolResultExtractor'; +export * from './ToolSummaryFormatter'; diff --git a/src/main/services/discovery/ProjectPathResolver.ts b/src/main/services/discovery/ProjectPathResolver.ts new file mode 100644 index 00000000..b1861197 --- /dev/null +++ b/src/main/services/discovery/ProjectPathResolver.ts @@ -0,0 +1,118 @@ +/** + * ProjectPathResolver - Resolves encoded project IDs to canonical filesystem paths. + * + * Resolution order: + * 1) cwd hint (if provided and absolute) + * 2) cwd extracted from session JSONL files (authoritative) + * 3) decodePath(projectId) fallback (lossy, best-effort) + * + * Results are memoized per projectId and can be invalidated by file watcher events. + */ + +import { extractCwd } from '@main/utils/jsonl'; +import { decodePath, extractBaseDir, getProjectsBasePath } from '@main/utils/pathDecoder'; +import { createLogger } from '@shared/utils/logger'; +import * as fs from 'fs'; +import * as path from 'path'; + +import { subprojectRegistry } from './SubprojectRegistry'; + +const logger = createLogger('Discovery:ProjectPathResolver'); + +interface ResolveProjectPathOptions { + cwdHint?: string; + sessionPaths?: string[]; + forceRefresh?: boolean; +} + +export class ProjectPathResolver { + private readonly projectsDir: string; + private readonly projectPathCache = new Map(); + + constructor(projectsDir?: string) { + this.projectsDir = projectsDir ?? getProjectsBasePath(); + } + + /** + * Resolve a project ID to a canonical path. + */ + async resolveProjectPath( + projectId: string, + options?: ResolveProjectPathOptions + ): Promise { + const opts = options ?? {}; + + // Short-circuit for composite IDs: use the registry's cwd directly + const registryCwd = subprojectRegistry.getCwd(projectId); + if (registryCwd) { + this.projectPathCache.set(projectId, registryCwd); + return registryCwd; + } + + if (!opts.forceRefresh) { + const cached = this.projectPathCache.get(projectId); + if (cached) { + return cached; + } + } + + const cwdHint = opts.cwdHint?.trim(); + if (cwdHint && path.isAbsolute(cwdHint)) { + this.projectPathCache.set(projectId, cwdHint); + return cwdHint; + } + + const sessionPaths = opts.sessionPaths?.length + ? opts.sessionPaths + : this.listSessionPaths(projectId); + + for (const sessionPath of sessionPaths) { + try { + const cwd = await extractCwd(sessionPath); + if (cwd && path.isAbsolute(cwd)) { + this.projectPathCache.set(projectId, cwd); + return cwd; + } + } catch { + // Ignore unreadable or malformed files and continue to next candidate. + } + } + + const decoded = decodePath(extractBaseDir(projectId)); + this.projectPathCache.set(projectId, decoded); + return decoded; + } + + /** + * Invalidate a single project's cached path. + */ + invalidateProject(projectId: string): void { + this.projectPathCache.delete(projectId); + } + + /** + * Clear all cached project paths. + */ + clear(): void { + this.projectPathCache.clear(); + } + + private listSessionPaths(projectId: string): string[] { + const projectDir = path.join(this.projectsDir, extractBaseDir(projectId)); + if (!fs.existsSync(projectDir)) { + return []; + } + + try { + const entries = fs.readdirSync(projectDir, { withFileTypes: true }); + return entries + .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl')) + .map((entry) => path.join(projectDir, entry.name)); + } catch (error) { + logger.error(`Failed to read session files for ${projectId}:`, error); + return []; + } + } +} + +export const projectPathResolver = new ProjectPathResolver(); diff --git a/src/main/services/discovery/ProjectScanner.ts b/src/main/services/discovery/ProjectScanner.ts new file mode 100644 index 00000000..e142dbe7 --- /dev/null +++ b/src/main/services/discovery/ProjectScanner.ts @@ -0,0 +1,819 @@ +/** + * ProjectScanner service - Scans ~/.claude/projects/ directory and lists all projects. + * + * Responsibilities: + * - Read project directories from ~/.claude/projects/ + * - Decode directory names to original paths (with cwd fallback) + * - List session files for each project + * - Read task list data from ~/.claude/todos/ + * - Return sorted list of projects by recent activity + * + * Delegates to specialized services: + * - SessionContentFilter: Noise detection and message filtering + * - WorktreeGrouper: Git repository grouping + * - SubagentLocator: Subagent file lookup + * - SessionSearcher: Search functionality + */ + +import { + type PaginatedSessionsResult, + type Project, + type RepositoryGroup, + type SearchSessionsResult, + type Session, + type SessionCursor, + type SessionsPaginationOptions, +} from '@main/types'; +import { analyzeSessionFileMetadata, extractCwd } from '@main/utils/jsonl'; +import { + buildSessionPath, + buildSubagentsPath, + buildTodoPath, + extractBaseDir, + extractProjectName, + extractSessionId, + getProjectsBasePath, + getTodosBasePath, + isValidEncodedPath, +} from '@main/utils/pathDecoder'; +import { createLogger } from '@shared/utils/logger'; +import * as fs from 'fs'; +import * as path from 'path'; + +import { SessionContentFilter } from './SessionContentFilter'; +import { subprojectRegistry } from './SubprojectRegistry'; + +const logger = createLogger('Discovery:ProjectScanner'); +import { ProjectPathResolver } from './ProjectPathResolver'; +import { SessionSearcher } from './SessionSearcher'; +import { SubagentLocator } from './SubagentLocator'; +import { WorktreeGrouper } from './WorktreeGrouper'; + +export class ProjectScanner { + private readonly projectsDir: string; + private readonly todosDir: string; + private readonly contentPresenceCache = new Map< + string, + { mtimeMs: number; hasContent: boolean } + >(); + private readonly sessionMetadataCache = new Map< + string, + { + mtimeMs: number; + metadata: Awaited>; + } + >(); + + // Delegated services + private readonly sessionContentFilter: typeof SessionContentFilter; + private readonly worktreeGrouper: WorktreeGrouper; + private readonly subagentLocator: SubagentLocator; + private readonly sessionSearcher: SessionSearcher; + private readonly projectPathResolver: ProjectPathResolver; + + constructor(projectsDir?: string, todosDir?: string) { + this.projectsDir = projectsDir ?? getProjectsBasePath(); + this.todosDir = todosDir ?? getTodosBasePath(); + + // Initialize delegated services + this.sessionContentFilter = SessionContentFilter; + this.worktreeGrouper = new WorktreeGrouper(this.projectsDir); + this.subagentLocator = new SubagentLocator(this.projectsDir); + this.sessionSearcher = new SessionSearcher(this.projectsDir); + this.projectPathResolver = new ProjectPathResolver(this.projectsDir); + } + + // =========================================================================== + // Project Scanning + // =========================================================================== + + /** + * Scans the projects directory and returns a list of all projects. + * @returns Promise resolving to projects sorted by most recent activity + */ + async scan(): Promise { + try { + if (!fs.existsSync(this.projectsDir)) { + logger.warn(`Projects directory does not exist: ${this.projectsDir}`); + return []; + } + + // Clear the subproject registry on full re-scan + subprojectRegistry.clear(); + + const entries = fs.readdirSync(this.projectsDir, { withFileTypes: true }); + + // Filter to only directories with valid encoding pattern + const projectDirs = entries.filter( + (entry) => entry.isDirectory() && isValidEncodedPath(entry.name) + ); + + // Process each project directory (may return multiple projects per dir) + const projectArrays = await Promise.all(projectDirs.map((dir) => this.scanProject(dir.name))); + + // Flatten and sort by most recent + const validProjects = projectArrays.flat(); + validProjects.sort((a, b) => (b.mostRecentSession ?? 0) - (a.mostRecentSession ?? 0)); + + return validProjects; + } catch (error) { + logger.error('Error scanning projects directory:', error); + return []; + } + } + + // =========================================================================== + // Repository Grouping (Worktree Support) + // =========================================================================== + + /** + * Scans projects and groups them by git repository. + * Projects belonging to the same git repository (main repo + worktrees) + * are grouped together under a single RepositoryGroup. + * Non-git projects are represented as single-worktree groups. + * + * Sessions are filtered to exclude noise-only sessions, so counts + * accurately reflect visible sessions in the UI. + * + * @returns Promise resolving to RepositoryGroups sorted by most recent activity + */ + async scanWithWorktreeGrouping(): Promise { + try { + // 1. Scan all projects using existing logic + const projects = await this.scan(); + + if (projects.length === 0) { + return []; + } + + // 2. Delegate to WorktreeGrouper + return this.worktreeGrouper.groupByRepository(projects); + } catch (error) { + logger.error('Error scanning with worktree grouping:', error); + return []; + } + } + + /** + * Lists sessions for a specific worktree within a repository group. + * This is a convenience method that delegates to listSessions since + * worktree.id is the same as project.id. + * + * @param worktreeId - The worktree ID (same as project ID) + */ + async listWorktreeSessions(worktreeId: string): Promise { + return this.listSessions(worktreeId); + } + + // =========================================================================== + // Project Scanning (continued) + // =========================================================================== + + /** + * Scans a single project directory and returns project metadata. + * If sessions have different cwd values, splits into multiple projects. + */ + private async scanProject(encodedName: string): Promise { + try { + const projectPath = path.join(this.projectsDir, encodedName); + const entries = fs.readdirSync(projectPath, { withFileTypes: true }); + + // Get session files (.jsonl at root level) + const sessionFiles = entries.filter( + (entry) => entry.isFile() && entry.name.endsWith('.jsonl') + ); + + if (sessionFiles.length === 0) { + return []; + } + + // Collect file stats and cwd for each session + interface SessionInfo { + sessionId: string; + filePath: string; + mtimeMs: number; + birthtimeMs: number; + cwd: string | null; + } + + const sessionInfos: SessionInfo[] = await Promise.all( + sessionFiles.map(async (file) => { + const filePath = path.join(projectPath, file.name); + const stats = fs.statSync(filePath); + let cwd: string | null = null; + try { + cwd = await extractCwd(filePath); + } catch { + // Ignore unreadable files + } + return { + sessionId: extractSessionId(file.name), + filePath, + mtimeMs: stats.mtimeMs, + birthtimeMs: stats.birthtimeMs, + cwd, + }; + }) + ); + + // Group sessions by cwd + const cwdGroups = new Map(); + const baseName = extractProjectName(encodedName); + const decodedFallback = baseName; // Used when cwd is null + + for (const info of sessionInfos) { + const key = info.cwd ?? `__decoded__${decodedFallback}`; + const group = cwdGroups.get(key) ?? []; + group.push(info); + cwdGroups.set(key, group); + } + + // If only 1 unique cwd, return single project (current behavior) + if (cwdGroups.size <= 1) { + const allSessionIds = sessionInfos.map((s) => s.sessionId); + let mostRecentSession: number | undefined; + let createdAt = Date.now(); + for (const info of sessionInfos) { + if (!mostRecentSession || info.mtimeMs > mostRecentSession) { + mostRecentSession = info.mtimeMs; + } + if (info.birthtimeMs < createdAt) { + createdAt = info.birthtimeMs; + } + } + + const sessionPaths = sessionInfos.map((s) => s.filePath); + const actualPath = await this.projectPathResolver.resolveProjectPath(encodedName, { + sessionPaths, + }); + + return [ + { + id: encodedName, + path: actualPath, + name: baseName, + sessions: allSessionIds, + createdAt: Math.floor(createdAt), + mostRecentSession: mostRecentSession ? Math.floor(mostRecentSession) : undefined, + }, + ]; + } + + // Multiple unique cwds: split into subprojects + const projects: Project[] = []; + + // Find the "root" cwd (shortest path, or the one matching the decoded name) + const cwdKeys = [...cwdGroups.keys()].filter((k) => !k.startsWith('__decoded__')); + const rootCwd = cwdKeys.reduce( + (shortest, cwd) => (cwd.length <= shortest.length ? cwd : shortest), + cwdKeys[0] ?? '' + ); + + for (const [cwdKey, sessions] of cwdGroups) { + const isDecodedFallback = cwdKey.startsWith('__decoded__'); + const actualCwd = isDecodedFallback ? null : cwdKey; + + // Register in subproject registry + const sessionIds = sessions.map((s) => s.sessionId); + const compositeId = subprojectRegistry.register( + encodedName, + actualCwd ?? decodedFallback, + sessionIds + ); + + // Compute timestamps + let mostRecentSession: number | undefined; + let createdAt = Date.now(); + for (const info of sessions) { + if (!mostRecentSession || info.mtimeMs > mostRecentSession) { + mostRecentSession = info.mtimeMs; + } + if (info.birthtimeMs < createdAt) { + createdAt = info.birthtimeMs; + } + } + + // Build display name + let displayName: string; + if (!actualCwd || actualCwd === rootCwd) { + displayName = baseName; + } else { + // Use last segment of cwd for disambiguation + const lastSegment = path.basename(actualCwd); + displayName = `${baseName} (${lastSegment})`; + } + + projects.push({ + id: compositeId, + path: actualCwd ?? decodedFallback, + name: displayName, + sessions: sessionIds, + createdAt: Math.floor(createdAt), + mostRecentSession: mostRecentSession ? Math.floor(mostRecentSession) : undefined, + }); + } + + return projects; + } catch (error) { + logger.error(`Error scanning project ${encodedName}:`, error); + return []; + } + } + + /** + * Gets details for a specific project by ID. + * Handles composite IDs by scanning the base directory and finding the matching subproject. + */ + async getProject(projectId: string): Promise { + const baseDir = extractBaseDir(projectId); + const projectPath = path.join(this.projectsDir, baseDir); + + if (!fs.existsSync(projectPath)) { + return null; + } + + // For composite IDs, scan and find the matching subproject + if (subprojectRegistry.isComposite(projectId)) { + const projects = await this.scanProject(baseDir); + return projects.find((p) => p.id === projectId) ?? null; + } + + const projects = await this.scanProject(baseDir); + return projects.find((p) => p.id === projectId) ?? projects[0] ?? null; + } + + // =========================================================================== + // Session Listing + // =========================================================================== + + /** + * Lists all sessions for a given project with metadata. + * Filters out sessions that contain only noise messages. + */ + async listSessions(projectId: string): Promise { + try { + const baseDir = extractBaseDir(projectId); + const projectPath = path.join(this.projectsDir, baseDir); + const sessionFilter = subprojectRegistry.getSessionFilter(projectId); + + if (!fs.existsSync(projectPath)) { + return []; + } + + const entries = fs.readdirSync(projectPath, { withFileTypes: true }); + let sessionFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl')); + + // Filter to only sessions belonging to this subproject + if (sessionFilter) { + sessionFiles = sessionFiles.filter((f) => sessionFilter.has(extractSessionId(f.name))); + } + + const sessionPaths = sessionFiles.map((file) => path.join(projectPath, file.name)); + const decodedPath = await this.resolveProjectPathForId(projectId, sessionPaths); + + const sessions = await Promise.all( + sessionFiles.map(async (file) => { + const sessionId = extractSessionId(file.name); + const filePath = path.join(projectPath, file.name); + + // Check if session has non-noise messages (delegated to SessionContentFilter) + const hasContent = await this.hasDisplayableContent(filePath); + if (!hasContent) { + return null; // Filter out noise-only sessions + } + + return this.buildSessionMetadata(projectId, sessionId, filePath, decodedPath); + }) + ); + + // Filter out null results (noise-only sessions) + const validSessions = sessions.filter((s): s is Session => s !== null); + + // Sort by created date (most recent first) + validSessions.sort((a, b) => b.createdAt - a.createdAt); + + return validSessions; + } catch (error) { + logger.error(`Error listing sessions for project ${projectId}:`, error); + return []; + } + } + + /** + * Lists sessions for a project with cursor-based pagination. + * Efficiently fetches only the sessions needed for the current page. + * + * @param projectId - The project ID to list sessions for + * @param cursor - Base64-encoded cursor from previous page (null for first page) + * @param limit - Number of sessions to return (default 20) + * @returns Paginated result with sessions, cursor, and metadata + */ + async listSessionsPaginated( + projectId: string, + cursor: string | null, + limit: number = 20, + options?: SessionsPaginationOptions + ): Promise { + try { + const includeTotalCount = options?.includeTotalCount ?? false; + const prefilterAll = options?.prefilterAll ?? false; + const baseDir = extractBaseDir(projectId); + const projectPath = path.join(this.projectsDir, baseDir); + const sessionFilter = subprojectRegistry.getSessionFilter(projectId); + + if (!fs.existsSync(projectPath)) { + return { sessions: [], nextCursor: null, hasMore: false, totalCount: 0 }; + } + + // Step 1: Get all session files with their timestamps (lightweight stat calls) + const entries = fs.readdirSync(projectPath, { withFileTypes: true }); + let sessionFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl')); + + // Filter to only sessions belonging to this subproject + if (sessionFilter) { + sessionFiles = sessionFiles.filter((f) => sessionFilter.has(extractSessionId(f.name))); + } + + // Get stats for all session files + interface SessionFileInfo { + name: string; + sessionId: string; + timestamp: number; + filePath: string; + mtimeMs: number; + } + const fileInfos: SessionFileInfo[] = []; + + for (const file of sessionFiles) { + const filePath = path.join(projectPath, file.name); + try { + const stats = fs.statSync(filePath); + fileInfos.push({ + name: file.name, + sessionId: extractSessionId(file.name), + timestamp: stats.mtimeMs, + filePath, + mtimeMs: stats.mtimeMs, + }); + } catch { + // Skip files we can't stat + continue; + } + } + + // Step 2: Sort by timestamp descending (most recent first) + fileInfos.sort((a, b) => { + if (b.timestamp !== a.timestamp) { + return b.timestamp - a.timestamp; + } + // Tie-breaker: sort by sessionId alphabetically + return a.sessionId.localeCompare(b.sessionId); + }); + + // Step 3: Optionally pre-filter all sessions for accurate total count + // This is slower but provides exact totalCount. + let validSessionIds: Set | null = null; + let totalCount = 0; + if (prefilterAll) { + validSessionIds = new Set(); + for (const fileInfo of fileInfos) { + if (await this.hasDisplayableContent(fileInfo.filePath, fileInfo.mtimeMs)) { + validSessionIds.add(fileInfo.sessionId); + } + } + totalCount = validSessionIds.size; + } + + // Step 4: Apply cursor filter to find starting position + let startIndex = 0; + if (cursor) { + try { + const decoded = JSON.parse( + Buffer.from(cursor, 'base64').toString('utf8') + ) as SessionCursor; + startIndex = fileInfos.findIndex((info) => { + // Find the first item that comes AFTER the cursor + if (info.timestamp < decoded.timestamp) return true; + if (info.timestamp === decoded.timestamp && info.sessionId > decoded.sessionId) + return true; + return false; + }); + // If cursor not found, start from beginning + if (startIndex === -1) startIndex = fileInfos.length; + } catch { + // Invalid cursor, start from beginning + startIndex = 0; + } + } + + // Step 5: Fetch sessions for this page + const decodedPath = await this.resolveProjectPathForId( + projectId, + fileInfos.map((fileInfo) => fileInfo.filePath) + ); + const sessions: Session[] = []; + let scannedCandidates = 0; + + // Fast path: avoid pre-filtering everything. Scan until we have enough page items. + for (let i = startIndex; i < fileInfos.length; i++) { + const fileInfo = fileInfos[i]; + if (!fileInfo) { + continue; + } + scannedCandidates++; + + let hasContent: boolean; + if (validSessionIds) { + hasContent = validSessionIds.has(fileInfo.sessionId); + } else { + hasContent = await this.hasDisplayableContent(fileInfo.filePath, fileInfo.mtimeMs); + } + if (!hasContent) { + continue; + } + + const session = await this.buildSessionMetadata( + projectId, + fileInfo.sessionId, + fileInfo.filePath, + decodedPath + ); + sessions.push(session); + + if (sessions.length >= limit + 1) { + break; + } + } + + // Step 6: Build next cursor + let nextCursor: string | null = null; + const hasMore = sessions.length > limit || startIndex + scannedCandidates < fileInfos.length; + + const pageSessions = hasMore ? sessions.slice(0, limit) : sessions; + + // If total count wasn't precomputed, keep UI-safe lower bound + if (!includeTotalCount) { + // Lightweight mode: return a lower-bound count to avoid full scans. + totalCount = pageSessions.length + (hasMore ? 1 : 0); + } + + if (pageSessions.length > 0 && hasMore) { + const lastSession = pageSessions[pageSessions.length - 1]; + const lastFileInfo = fileInfos.find((f) => f.sessionId === lastSession.id); + if (lastFileInfo) { + const cursorData: SessionCursor = { + timestamp: lastFileInfo.timestamp, + sessionId: lastFileInfo.sessionId, + }; + nextCursor = Buffer.from(JSON.stringify(cursorData)).toString('base64'); + } + } + + return { + sessions: pageSessions, + nextCursor, + hasMore: nextCursor !== null, + totalCount, + }; + } catch (error) { + logger.error(`Error listing paginated sessions for project ${projectId}:`, error); + return { sessions: [], nextCursor: null, hasMore: false, totalCount: 0 }; + } + } + + /** + * Build session metadata from a session file. + */ + private async buildSessionMetadata( + projectId: string, + sessionId: string, + filePath: string, + projectPath: string + ): Promise { + const stats = fs.statSync(filePath); + const cachedMetadata = this.sessionMetadataCache.get(filePath); + const metadata = + cachedMetadata?.mtimeMs === stats.mtimeMs + ? cachedMetadata.metadata + : await analyzeSessionFileMetadata(filePath); + if (cachedMetadata?.mtimeMs !== stats.mtimeMs) { + this.sessionMetadataCache.set(filePath, { mtimeMs: stats.mtimeMs, metadata }); + } + + // Check for subagents (delegated to SubagentLocator) + const hasSubagents = this.subagentLocator.hasSubagentsSync(projectId, sessionId); + + // Load task list data if exists + const todoData = await this.loadTodoData(sessionId); + + return { + id: sessionId, + projectId, + projectPath, + todoData, + createdAt: Math.floor(stats.birthtimeMs), + firstMessage: metadata.firstUserMessage?.text, + messageTimestamp: metadata.firstUserMessage?.timestamp, + hasSubagents, + messageCount: metadata.messageCount, + isOngoing: metadata.isOngoing, + gitBranch: metadata.gitBranch ?? undefined, + }; + } + + /** + * Gets a single session's metadata. + */ + async getSession(projectId: string, sessionId: string): Promise { + const filePath = this.getSessionPath(projectId, sessionId); + + if (!fs.existsSync(filePath)) { + return null; + } + + const decodedPath = await this.resolveProjectPathForId(projectId); + return this.buildSessionMetadata(projectId, sessionId, filePath, decodedPath); + } + + // =========================================================================== + // Task List Data + // =========================================================================== + + /** + * Loads task list data for a session from ~/.claude/todos/{sessionId}.json + */ + async loadTodoData(sessionId: string): Promise { + try { + const todoPath = buildTodoPath(path.dirname(this.projectsDir), sessionId); + + if (!fs.existsSync(todoPath)) { + return undefined; + } + + const content = fs.readFileSync(todoPath, 'utf8'); + return JSON.parse(content) as unknown; + } catch (error) { + // Log but continue - task list data is non-critical + logger.debug(`Failed to load task list data for session ${sessionId}:`, error); + return undefined; + } + } + + // =========================================================================== + // Path Helpers + // =========================================================================== + + /** + * Gets the path to the session JSONL file. + */ + getSessionPath(projectId: string, sessionId: string): string { + return buildSessionPath(this.projectsDir, projectId, sessionId); + } + + /** + * Gets the path to the subagents directory. + */ + getSubagentsPath(projectId: string, sessionId: string): string { + return buildSubagentsPath(this.projectsDir, projectId, sessionId); + } + + /** + * Lists all session file paths for a project. + */ + async listSessionFiles(projectId: string): Promise { + try { + const baseDir = extractBaseDir(projectId); + const projectPath = path.join(this.projectsDir, baseDir); + const sessionFilter = subprojectRegistry.getSessionFilter(projectId); + + if (!fs.existsSync(projectPath)) { + return []; + } + + const entries = fs.readdirSync(projectPath, { withFileTypes: true }); + + let files = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl')); + + if (sessionFilter) { + files = files.filter((entry) => sessionFilter.has(extractSessionId(entry.name))); + } + + return files.map((entry) => path.join(projectPath, entry.name)); + } catch (error) { + logger.error(`Error listing session files for project ${projectId}:`, error); + return []; + } + } + + // =========================================================================== + // Subagent Detection (delegated to SubagentLocator) + // =========================================================================== + + /** + * Checks if a session has a subagents directory (async). + */ + async hasSubagents(projectId: string, sessionId: string): Promise { + return this.subagentLocator.hasSubagents(projectId, sessionId); + } + + /** + * Checks if a session has subagent files (session-specific only). + * Only checks the NEW structure: {projectId}/{sessionId}/subagents/ + * Verifies that at least one subagent file has non-empty content. + */ + hasSubagentsSync(projectId: string, sessionId: string): boolean { + return this.subagentLocator.hasSubagentsSync(projectId, sessionId); + } + + /** + * Lists all subagent files for a session from both NEW and OLD structures. + * Returns NEW structure files first, then OLD structure files. + */ + async listSubagentFiles(projectId: string, sessionId: string): Promise { + return this.subagentLocator.listSubagentFiles(projectId, sessionId); + } + + // =========================================================================== + // Utility Methods + // =========================================================================== + + /** + * Gets the base projects directory path. + */ + getProjectsDir(): string { + return this.projectsDir; + } + + /** + * Gets the base todos directory path. + */ + getTodosDir(): string { + return this.todosDir; + } + + /** + * Checks if the projects directory exists. + */ + projectsDirExists(): boolean { + return fs.existsSync(this.projectsDir); + } + + // =========================================================================== + // Search (delegated to SessionSearcher) + // =========================================================================== + + /** + * Searches sessions in a project for a query string. + * Filters out noise messages and returns matching content. + * + * @param projectId - The project ID to search in + * @param query - Search query string + * @param maxResults - Maximum number of results to return (default 50) + */ + async searchSessions( + projectId: string, + query: string, + maxResults: number = 50 + ): Promise { + return this.sessionSearcher.searchSessions(projectId, query, maxResults); + } + + /** + * Resolves the project path for a given project ID. + * For composite IDs, uses the registry's cwd directly. + * For plain IDs, delegates to ProjectPathResolver. + */ + private async resolveProjectPathForId( + projectId: string, + sessionPaths?: string[] + ): Promise { + const registryCwd = subprojectRegistry.getCwd(projectId); + if (registryCwd) { + return registryCwd; + } + const baseDir = extractBaseDir(projectId); + return this.projectPathResolver.resolveProjectPath(baseDir, { + sessionPaths, + }); + } + + /** + * Checks whether a session file has non-noise displayable content. + * Uses mtime-based memoization to avoid expensive re-parsing on repeated requests. + */ + private async hasDisplayableContent(filePath: string, mtimeMs?: number): Promise { + try { + const effectiveMtime = mtimeMs ?? fs.statSync(filePath).mtimeMs; + const cached = this.contentPresenceCache.get(filePath); + if (cached?.mtimeMs === effectiveMtime) { + return cached.hasContent; + } + + const hasContent = await this.sessionContentFilter.hasNonNoiseMessages(filePath); + this.contentPresenceCache.set(filePath, { mtimeMs: effectiveMtime, hasContent }); + return hasContent; + } catch { + return false; + } + } +} diff --git a/src/main/services/discovery/SessionContentFilter.ts b/src/main/services/discovery/SessionContentFilter.ts new file mode 100644 index 00000000..f146ff20 --- /dev/null +++ b/src/main/services/discovery/SessionContentFilter.ts @@ -0,0 +1,249 @@ +/** + * SessionContentFilter - Filters noise messages from sessions. + * + * Responsibilities: + * - Check if session files contain displayable content + * - Categorize messages as displayable or noise + * - Filter out system-generated and meta messages + * + * A session is displayable if it contains at least one: + * - Real user message (creates UserChunk) + * - System output message (creates SystemChunk) + * - Assistant message (creates AIChunk) + * - Compact boundary message (creates CompactChunk) + * + * Filtered out (hard noise): + * - system entries + * - summary entries + * - file-history-snapshot entries + * - queue-operation entries + * - user messages with ONLY or + * - synthetic assistant messages (model='') + */ + +import { type ChatHistoryEntry, type ContentBlock } from '@main/types'; +import { createLogger } from '@shared/utils/logger'; +import * as fs from 'fs'; +import * as readline from 'readline'; + +const logger = createLogger('Service:SessionContentFilter'); + +/** + * Hard noise tags - user messages with ONLY these tags are filtered out. + */ +const HARD_NOISE_TAGS = ['', '']; + +/** + * Hard noise entry types - these types are always filtered out. + */ +const HARD_NOISE_TYPES = ['system', 'summary', 'file-history-snapshot', 'queue-operation']; + +/** + * SessionContentFilter provides static methods for filtering noise messages. + */ +export class SessionContentFilter { + /** + * Checks if a session file contains any displayable conversation items. + * Returns true if the session has at least one message that would create + * a visible chunk (UserChunk, SystemChunk, AIChunk, or CompactChunk). + * + * Uses the same logic as ChunkBuilder to ensure consistency with ChatHistory: + * - Sessions that pass this check will have non-empty conversation.items + * - Sessions that fail will show "No conversation history" in ChatHistory + * + * @param filePath - Path to the session JSONL file + * @returns Promise resolving to true if session has displayable content + */ + static async hasNonNoiseMessages(filePath: string): Promise { + if (!fs.existsSync(filePath)) { + return false; + } + + const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' }); + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + try { + for await (const line of rl) { + if (!line.trim()) continue; + + try { + const entry = JSON.parse(line) as ChatHistoryEntry; + + // Skip entries without uuid (queue-operation, etc.) + if (!entry.uuid) { + continue; + } + + // Check if this entry would create a displayable chunk + // This aligns with ChunkBuilder.categorizeMessage() logic + if (SessionContentFilter.isDisplayableEntry(entry)) { + fileStream.destroy(); + return true; + } + } catch { + // Skip malformed lines + continue; + } + } + } catch (error) { + logger.error(`Error checking displayable messages in ${filePath}:`, error); + } + + // No displayable messages found + return false; + } + + /** + * Checks if a JSONL entry would create a displayable chunk. + * Mirrors the logic in ChunkBuilder.categorizeMessage() and isParsed*Message() guards. + * + * @param entry - The parsed JSONL entry + * @returns true if the entry would create a displayable chunk + */ + static isDisplayableEntry(entry: ChatHistoryEntry): boolean { + const entryType = entry.type; + + // Hard noise types - never displayed + if (HARD_NOISE_TYPES.includes(entryType)) { + return false; + } + + // Sidechain messages are subagent messages - not part of main conversation + if ('isSidechain' in entry && entry.isSidechain === true) { + return false; + } + + // Assistant messages - displayable (creates AIChunk) + // Filter synthetic messages (model='') + if (entryType === 'assistant') { + const assistantEntry = entry as { message?: { model?: string } }; + return assistantEntry.message?.model !== ''; + } + + // User messages - check for real user input vs noise + if (entryType === 'user') { + return SessionContentFilter.isDisplayableUserEntry(entry); + } + + return false; + } + + /** + * Checks if a user entry is displayable. + * + * @param entry - The user entry to check + * @returns true if the user entry would create a displayable chunk + */ + private static isDisplayableUserEntry(entry: ChatHistoryEntry): boolean { + const userEntry = entry as { + message?: { content?: string | ContentBlock[] }; + isMeta?: boolean; + }; + const content = userEntry.message?.content; + const isMeta = userEntry.isMeta; + + // Internal user messages (tool results) - part of AI response flow + // These ARE displayable as they're part of AIChunks + if (isMeta === true) { + return true; + } + + // String content + if (typeof content === 'string') { + return SessionContentFilter.isDisplayableStringContent(content); + } + + // Array content (newer format) + if (Array.isArray(content)) { + return SessionContentFilter.isDisplayableArrayContent(content); + } + + return false; + } + + /** + * Checks if string content is displayable. + * + * @param content - The string content to check + * @returns true if displayable + */ + private static isDisplayableStringContent(content: string): boolean { + const trimmed = content.trim(); + + // Check for hard noise tags - user messages with ONLY these tags + for (const tag of HARD_NOISE_TAGS) { + const openTag = tag; + const closeTag = tag.replace('<', '') || + trimmed.startsWith('') + ) { + return true; + } + + // Real user input (creates UserChunk) - displayable + if (trimmed.length > 0) { + return true; + } + + return false; + } + + /** + * Checks if array content is displayable. + * + * @param content - The array content to check + * @returns true if displayable + */ + private static isDisplayableArrayContent(content: ContentBlock[]): boolean { + // Check for tool_result blocks (part of AI response flow) + const hasToolResult = content.some((block: ContentBlock) => block.type === 'tool_result'); + if (hasToolResult) { + return true; + } + + // Check for text/image blocks (real user input) + const hasUserContent = content.some( + (block: ContentBlock) => block.type === 'text' || block.type === 'image' + ); + + if (hasUserContent) { + // Filter user interruption messages - but these are still displayable + if ( + content.length === 1 && + content[0].type === 'text' && + typeof content[0].text === 'string' && + content[0].text.startsWith('[Request interrupted by user') + ) { + // Interruptions are part of AI flow, still displayable + return true; + } + + // Check text blocks for hard noise tags + for (const block of content) { + if (block.type === 'text') { + const textBlock = block; + for (const tag of HARD_NOISE_TAGS) { + const closeTag = tag.replace('<', ' { + const results: SearchResult[] = []; + let sessionsSearched = 0; + + if (!query || query.trim().length === 0) { + return { results: [], totalMatches: 0, sessionsSearched: 0, query }; + } + + const normalizedQuery = query.toLowerCase().trim(); + + try { + const baseDir = extractBaseDir(projectId); + const projectPath = path.join(this.projectsDir, baseDir); + const sessionFilter = subprojectRegistry.getSessionFilter(projectId); + + try { + await fs.promises.access(projectPath, fs.constants.R_OK); + } catch { + return { results: [], totalMatches: 0, sessionsSearched: 0, query }; + } + + // Get all session files + const entries = await fs.promises.readdir(projectPath, { withFileTypes: true }); + const sessionFilesWithTime = await Promise.all( + entries + .filter((entry) => { + if (!entry.isFile() || !entry.name.endsWith('.jsonl')) return false; + // Filter to only sessions belonging to this subproject + if (sessionFilter) { + const sessionId = extractSessionId(entry.name); + return sessionFilter.has(sessionId); + } + return true; + }) + .map(async (entry) => { + const filePath = path.join(projectPath, entry.name); + try { + const stats = await fs.promises.stat(filePath); + return { name: entry.name, filePath, mtimeMs: stats.mtimeMs }; + } catch { + return null; + } + }) + ); + const sessionFiles = sessionFilesWithTime + .filter((entry): entry is { name: string; filePath: string; mtimeMs: number } => !!entry) + .sort((a, b) => b.mtimeMs - a.mtimeMs); + + // Search each session file + for (const file of sessionFiles) { + if (results.length >= maxResults) break; + + const sessionId = extractSessionId(file.name); + const filePath = file.filePath; + sessionsSearched++; + + try { + const sessionResults = await this.searchSessionFile( + projectId, + sessionId, + filePath, + normalizedQuery, + maxResults - results.length + ); + results.push(...sessionResults); + } catch { + // Skip files we can't read + continue; + } + } + + return { + results, + totalMatches: results.length, + sessionsSearched, + query, + }; + } catch (error) { + logger.error(`Error searching sessions for project ${projectId}:`, error); + return { results: [], totalMatches: 0, sessionsSearched: 0, query }; + } + } + + /** + * Searches a single session file for a query string. + * + * @param projectId - The project ID + * @param sessionId - The session ID + * @param filePath - Path to the session file + * @param query - Normalized search query (lowercase) + * @param maxResults - Maximum number of results to return + * @returns Array of search results + */ + async searchSessionFile( + projectId: string, + sessionId: string, + filePath: string, + query: string, + maxResults: number + ): Promise { + const results: SearchResult[] = []; + let sessionTitle: string | undefined; + const messages = await parseJsonlFile(filePath); + const chunks = this.chunkBuilder.buildChunks(messages, []); + + for (const chunk of chunks) { + if (results.length >= maxResults) { + break; + } + + if (isUserChunk(chunk)) { + const userText = this.extractUserSearchableText(chunk.userMessage); + if (!sessionTitle && userText) { + sessionTitle = userText.slice(0, 100); + } + if (!userText) { + continue; + } + const searchableEntry: SearchableEntry = { + text: userText, + groupId: chunk.id, + messageType: 'user', + itemType: 'user', + timestamp: chunk.userMessage.timestamp.getTime(), + messageUuid: chunk.userMessage.uuid, + }; + this.collectMatchesForEntry( + searchableEntry, + query, + results, + maxResults, + projectId, + sessionId, + sessionTitle + ); + continue; + } + + if (isEnhancedAIChunk(chunk)) { + const lastOutputStep = this.findLastOutputTextStep(chunk.semanticSteps); + const outputText = lastOutputStep?.content.outputText; + if (!lastOutputStep || !outputText) { + continue; + } + + const searchableEntry: SearchableEntry = { + text: outputText, + groupId: chunk.id, + messageType: 'assistant', + itemType: 'ai', + timestamp: lastOutputStep.startTime.getTime(), + messageUuid: lastOutputStep.sourceMessageId ?? chunk.responses[0]?.uuid ?? '', + }; + this.collectMatchesForEntry( + searchableEntry, + query, + results, + maxResults, + projectId, + sessionId, + sessionTitle + ); + } + } + + return results; + } + + private collectMatchesForEntry( + entry: SearchableEntry, + query: string, + results: SearchResult[], + maxResults: number, + projectId: string, + sessionId: string, + sessionTitle?: string + ): void { + const mdMatches = findMarkdownSearchMatches(entry.text, query); + if (mdMatches.length === 0) return; + + // Build plain text once for context snippet extraction + const plainText = extractMarkdownPlainText(entry.text); + const lowerPlain = plainText.toLowerCase(); + + for (const mdMatch of mdMatches) { + if (results.length >= maxResults) return; + + // Find approximate position in plain text for context extraction + let pos = 0; + for (let i = 0; i < mdMatch.matchIndexInItem; i++) { + const idx = lowerPlain.indexOf(query, pos); + if (idx === -1) break; + pos = idx + query.length; + } + const matchPos = lowerPlain.indexOf(query, pos); + const effectivePos = matchPos >= 0 ? matchPos : 0; + + const contextStart = Math.max(0, effectivePos - 50); + const contextEnd = Math.min(plainText.length, effectivePos + query.length + 50); + const context = plainText.slice(contextStart, contextEnd); + const matchedText = + matchPos >= 0 ? plainText.slice(matchPos, matchPos + query.length) : query; + + results.push({ + sessionId, + projectId, + sessionTitle: sessionTitle ?? 'Untitled Session', + matchedText, + context: + (contextStart > 0 ? '...' : '') + context + (contextEnd < plainText.length ? '...' : ''), + messageType: entry.messageType, + timestamp: entry.timestamp, + groupId: entry.groupId, + itemType: entry.itemType, + matchIndexInItem: mdMatch.matchIndexInItem, + matchStartOffset: effectivePos, + messageUuid: entry.messageUuid, + }); + } + } + + private extractUserSearchableText(message: ParsedMessage): string { + let rawText = ''; + if (typeof message.content === 'string') { + rawText = message.content; + } else if (Array.isArray(message.content)) { + rawText = message.content + .filter((block) => block.type === 'text') + .map((block) => block.text) + .join(''); + } + return sanitizeDisplayContent(rawText); + } + + private findLastOutputTextStep(steps: SemanticStep[]): SemanticStep | null { + for (let i = steps.length - 1; i >= 0; i--) { + const step = steps[i]; + if (step.type === 'output' && step.content.outputText) { + return step; + } + } + return null; + } +} diff --git a/src/main/services/discovery/SubagentLocator.ts b/src/main/services/discovery/SubagentLocator.ts new file mode 100644 index 00000000..94100497 --- /dev/null +++ b/src/main/services/discovery/SubagentLocator.ts @@ -0,0 +1,203 @@ +/** + * SubagentLocator - Locates and manages subagent files. + * + * Responsibilities: + * - Check if sessions have subagent files + * - List subagent files for a session + * - Handle both NEW and OLD subagent directory structures: + * - NEW: {projectId}/{sessionId}/subagents/agent-{agentId}.jsonl + * - OLD: {projectId}/agent-{agentId}.jsonl (legacy, still supported) + * - Determine subagent ownership for OLD structure + */ + +import { buildSubagentsPath, extractBaseDir } from '@main/utils/pathDecoder'; +import { createLogger } from '@shared/utils/logger'; +import * as fs from 'fs'; +import * as path from 'path'; + +const logger = createLogger('Discovery:SubagentLocator'); + +/** + * SubagentLocator provides methods for locating subagent files. + */ +export class SubagentLocator { + private readonly projectsDir: string; + + constructor(projectsDir: string) { + this.projectsDir = projectsDir; + } + + /** + * Checks if a session has subagent files (async). + * + * @param projectId - The project ID + * @param sessionId - The session ID + * @returns Promise resolving to true if subagents exist + */ + async hasSubagents(projectId: string, sessionId: string): Promise { + return this.hasSubagentsSync(projectId, sessionId); + } + + /** + * Checks if a session has subagent files (session-specific only). + * Only checks the NEW structure: {projectId}/{sessionId}/subagents/ + * Verifies that at least one subagent file has non-empty content. + * + * @param projectId - The project ID + * @param sessionId - The session ID + * @returns true if subagents exist + */ + hasSubagentsSync(projectId: string, sessionId: string): boolean { + // Check NEW structure: {projectId}/{sessionId}/subagents/ + const newSubagentsPath = this.getSubagentsPath(projectId, sessionId); + if (fs.existsSync(newSubagentsPath)) { + try { + const entries = fs.readdirSync(newSubagentsPath); + const subagentFiles = entries.filter( + (name) => name.startsWith('agent-') && name.endsWith('.jsonl') + ); + + // Check if at least one subagent file has content (not empty) + for (const fileName of subagentFiles) { + const filePath = path.join(newSubagentsPath, fileName); + try { + const stats = fs.statSync(filePath); + // File must have size > 0 and contain at least one line + if (stats.size > 0) { + const content = fs.readFileSync(filePath, 'utf8'); + if (content.trim().length > 0) { + return true; + } + } + } catch (error) { + // Skip this file if we can't read it - log for debugging + logger.debug(`SubagentLocator: Could not read file ${filePath}:`, error); + continue; + } + } + } catch { + // Ignore errors + } + } + + return false; + } + + /** + * Lists all subagent files for a session from both NEW and OLD structures. + * Returns NEW structure files first, then OLD structure files. + * + * @param projectId - The project ID + * @param sessionId - The session ID + * @returns Promise resolving to array of file paths + */ + async listSubagentFiles(projectId: string, sessionId: string): Promise { + const allFiles: string[] = []; + + try { + // Scan NEW structure: {projectId}/{sessionId}/subagents/agent-*.jsonl + const newSubagentsPath = this.getSubagentsPath(projectId, sessionId); + if (fs.existsSync(newSubagentsPath)) { + const entries = fs.readdirSync(newSubagentsPath, { withFileTypes: true }); + const newFiles = entries + .filter( + (entry) => + entry.isFile() && entry.name.startsWith('agent-') && entry.name.endsWith('.jsonl') + ) + .map((entry) => path.join(newSubagentsPath, entry.name)); + allFiles.push(...newFiles); + } + } catch (error) { + logger.error(`Error scanning NEW subagent structure for session ${sessionId}:`, error); + } + + try { + // Scan OLD structure: {projectId}/agent-*.jsonl + // Must filter by sessionId since all sessions share the same project root + const oldFiles = await this.getProjectRootSubagentFiles(projectId, sessionId); + allFiles.push(...oldFiles); + } catch (error) { + logger.error(`Error scanning OLD subagent structure for project ${projectId}:`, error); + } + + return allFiles; + } + + /** + * Gets subagent files from project root (OLD structure). + * Scans {projectId}/agent-*.jsonl files and filters by sessionId. + * + * In the OLD structure, all subagent files are in the project root, + * so we must read each file's first line to check if it belongs to the session. + * + * @param projectId - The project ID + * @param sessionId - The session ID + * @returns Promise resolving to array of file paths + */ + async getProjectRootSubagentFiles(projectId: string, sessionId: string): Promise { + try { + const projectPath = path.join(this.projectsDir, extractBaseDir(projectId)); + + if (!fs.existsSync(projectPath)) { + return []; + } + + const files = fs.readdirSync(projectPath); + const agentFiles = files + .filter((f) => f.startsWith('agent-') && f.endsWith('.jsonl')) + .map((f) => path.join(projectPath, f)); + + // Filter files by checking if their sessionId matches + const matchingFiles: string[] = []; + for (const filePath of agentFiles) { + if (await this.subagentBelongsToSession(filePath, sessionId)) { + matchingFiles.push(filePath); + } + } + + return matchingFiles; + } catch (error) { + logger.error(`Error reading project root for subagent files:`, error); + return []; + } + } + + /** + * Checks if a subagent file belongs to a specific session by reading its first line. + * Subagent files have a sessionId field that points to the parent session. + * + * @param filePath - Path to the subagent file + * @param sessionId - The session ID to check + * @returns Promise resolving to true if the subagent belongs to the session + */ + async subagentBelongsToSession(filePath: string, sessionId: string): Promise { + try { + // Read just the first line to check sessionId + const content = fs.readFileSync(filePath, 'utf-8'); + const firstNewline = content.indexOf('\n'); + const firstLine = firstNewline > 0 ? content.slice(0, firstNewline) : content; + + if (!firstLine.trim()) { + return false; + } + + const entry = JSON.parse(firstLine) as { sessionId?: string }; + return entry.sessionId === sessionId; + } catch (error) { + // If we can't read or parse the file, don't include it - log for debugging + logger.debug(`SubagentLocator: Could not parse file ${filePath}:`, error); + return false; + } + } + + /** + * Gets the path to the subagents directory. + * + * @param projectId - The project ID + * @param sessionId - The session ID + * @returns Path to the subagents directory + */ + getSubagentsPath(projectId: string, sessionId: string): string { + return buildSubagentsPath(this.projectsDir, projectId, sessionId); + } +} diff --git a/src/main/services/discovery/SubagentResolver.ts b/src/main/services/discovery/SubagentResolver.ts new file mode 100644 index 00000000..66b11391 --- /dev/null +++ b/src/main/services/discovery/SubagentResolver.ts @@ -0,0 +1,547 @@ +/** + * SubagentResolver service - Links Task calls to subagent files and detects parallelism. + * + * Responsibilities: + * - Find subagent JSONL files in {sessionId}/subagents/ directory + * - Parse each subagent file + * - Calculate start/end times and metrics + * - Detect parallel execution (100ms overlap threshold) + * - Link subagents to parent Task tool calls + */ + +import { type ParsedMessage, type Process, type SessionMetrics, type ToolCall } from '@main/types'; +import { calculateMetrics, checkMessagesOngoing, parseJsonlFile } from '@main/utils/jsonl'; +import { createLogger } from '@shared/utils/logger'; +import * as path from 'path'; + +import { type ProjectScanner } from './ProjectScanner'; + +const logger = createLogger('Discovery:SubagentResolver'); + +/** Parallel detection window in milliseconds */ +const PARALLEL_WINDOW_MS = 100; + +export class SubagentResolver { + private projectScanner: ProjectScanner; + + constructor(projectScanner: ProjectScanner) { + this.projectScanner = projectScanner; + } + + // =========================================================================== + // Main Resolution + // =========================================================================== + + /** + * Resolve all subagents for a session. + */ + async resolveSubagents( + projectId: string, + sessionId: string, + taskCalls: ToolCall[], + messages?: ParsedMessage[] + ): Promise { + // Get subagent files + const subagentFiles = await this.projectScanner.listSubagentFiles(projectId, sessionId); + + if (subagentFiles.length === 0) { + return []; + } + + // Parse all subagent files + const subagents = await Promise.all( + subagentFiles.map((filePath) => this.parseSubagentFile(filePath)) + ); + + // Filter out failed parses + const validSubagents = subagents.filter((s): s is Process => s !== null); + + // Link to Task calls using tool result data from parent session messages + this.linkToTaskCalls(validSubagents, taskCalls, messages ?? []); + + // Propagate team metadata to continuation files via parentUuid chain + this.propagateTeamMetadata(validSubagents); + + // Detect parallel execution + this.detectParallelExecution(validSubagents); + + // Enrich team metadata colors from messages + if (messages) { + this.enrichTeamColors(validSubagents, messages); + } + + // Sort by start time + validSubagents.sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); + + return validSubagents; + } + + // =========================================================================== + // Subagent Parsing + // =========================================================================== + + /** + * Parse a single subagent file. + */ + private async parseSubagentFile(filePath: string): Promise { + try { + const messages = await parseJsonlFile(filePath); + + if (messages.length === 0) { + return null; + } + + // Filter out warmup subagents - these are pre-warming agents spawned by Claude Code + // that have "Warmup" as the first user message and should not be displayed + if (this.isWarmupSubagent(messages)) { + return null; + } + + // Extract agent ID from filename (agent-{id}.jsonl) + const filename = path.basename(filePath); + const agentId = filename.replace(/^agent-/, '').replace(/\.jsonl$/, ''); + + // Filter out compact files (context compaction artifacts, not real subagents) + if (agentId.startsWith('acompact')) { + return null; + } + + // Calculate timing + const { startTime, endTime, durationMs } = this.calculateTiming(messages); + + // Calculate metrics + const metrics = calculateMetrics(messages); + + // Check if subagent is still in progress + const isOngoing = checkMessagesOngoing(messages); + + return { + id: agentId, + filePath, + messages, + startTime, + endTime, + durationMs, + metrics, + isParallel: false, // Will be set by detectParallelExecution + isOngoing, + }; + } catch (error) { + logger.error(`Error parsing subagent file ${filePath}:`, error); + return null; + } + } + + /** + * Check if this is a warmup subagent that should be filtered out. + * Warmup subagents are pre-warming agents spawned by Claude Code that have: + * - First user message with content exactly "Warmup" + * - isSidechain: true (all subagents have this) + */ + private isWarmupSubagent(messages: ParsedMessage[]): boolean { + // Find the first user message + const firstUserMessage = messages.find((m) => m.type === 'user'); + if (!firstUserMessage) { + return false; + } + + // Check if content is exactly "Warmup" (string, not array) + return firstUserMessage.content === 'Warmup'; + } + + /** + * Extract the summary attribute from the first tag in a subagent's messages. + * Returns the summary string if found, undefined otherwise. + * Used to match team member files to their spawning Task calls. + */ + private extractTeamMessageSummary(messages: ParsedMessage[]): string | undefined { + const firstUserMessage = messages.find((m) => m.type === 'user'); + if (!firstUserMessage) return undefined; + + const text = typeof firstUserMessage.content === 'string' ? firstUserMessage.content : ''; + const match = /]*\bsummary="([^"]+)"/.exec(text); + return match?.[1]; + } + + /** + * Calculate timing from messages. + */ + private calculateTiming(messages: ParsedMessage[]): { + startTime: Date; + endTime: Date; + durationMs: number; + } { + const timestamps = messages.map((m) => m.timestamp.getTime()).filter((t) => !isNaN(t)); + + if (timestamps.length === 0) { + const now = new Date(); + return { startTime: now, endTime: now, durationMs: 0 }; + } + + const minTime = Math.min(...timestamps); + const maxTime = Math.max(...timestamps); + + return { + startTime: new Date(minTime), + endTime: new Date(maxTime), + durationMs: maxTime - minTime, + }; + } + + // =========================================================================== + // Task Call Linking + // =========================================================================== + + /** + * Link subagents to their parent Task tool calls. + * + * Uses result-based matching: reads tool_result messages from the parent session + * to find agentId values, then matches subagent files by their ID. Falls back to + * positional matching (without wrap-around) for any remaining unmatched subagents. + * + * After matching, enriches subagents with Task call metadata (description, subagentType). + */ + private linkToTaskCalls( + subagents: Process[], + taskCalls: ToolCall[], + messages: ParsedMessage[] + ): void { + // Filter to only Task calls + const taskCallsOnly = taskCalls.filter((tc) => tc.isTask); + + if (taskCallsOnly.length === 0 || subagents.length === 0) { + return; + } + + // Build a map: agentId → taskCallId from tool result messages + // Tool results for Task calls contain an agentId field linking to the subagent file + const agentIdToTaskId = new Map(); + for (const msg of messages) { + if (!msg.toolUseResult) continue; + const result = msg.toolUseResult; + // Check both camelCase (regular subagents) and snake_case (team spawns) field names + const agentId = (result.agentId ?? result.agent_id) as string | undefined; + if (!agentId) continue; + + // Find the Task call ID from sourceToolUseID or toolResults[0].toolUseId + const taskCallId = msg.sourceToolUseID ?? msg.toolResults[0]?.toolUseId; + if (taskCallId) { + agentIdToTaskId.set(agentId, taskCallId); + } + } + + // Build a lookup from task call ID → ToolCall for enrichment + const taskCallById = new Map(taskCallsOnly.map((tc) => [tc.id, tc])); + + // Track which subagents and tasks got matched + const matchedSubagentIds = new Set(); + const matchedTaskIds = new Set(); + + // Phase 1: Result-based matching (agentId from tool results) + // Works for regular subagents (Explore, etc.) where agentId = file UUID + for (const subagent of subagents) { + const taskCallId = agentIdToTaskId.get(subagent.id); + if (!taskCallId) continue; + + const taskCall = taskCallById.get(taskCallId); + if (!taskCall) continue; + + this.enrichSubagentFromTask(subagent, taskCall); + matchedSubagentIds.add(subagent.id); + matchedTaskIds.add(taskCallId); + } + + // Phase 2: Description-based matching for team members + // Team spawns use agent_id = "name@team_name" (not a file UUID), so Phase 1 can't match them. + // Instead, match by comparing the Task description to the summary attribute in the + // subagent file's first tag. + const teamTaskCalls = taskCallsOnly.filter( + (tc) => !matchedTaskIds.has(tc.id) && tc.input?.team_name && tc.input?.name + ); + + if (teamTaskCalls.length > 0) { + // Pre-extract summaries from unmatched subagent files + const subagentSummaries = new Map(); + for (const subagent of subagents) { + if (matchedSubagentIds.has(subagent.id)) continue; + const summary = this.extractTeamMessageSummary(subagent.messages); + if (summary) { + subagentSummaries.set(subagent.id, summary); + } + } + + // Match each team Task call to the earliest subagent file with matching summary + for (const taskCall of teamTaskCalls) { + const description = taskCall.taskDescription; + if (!description) continue; + + let bestMatch: Process | undefined; + for (const subagent of subagents) { + if (matchedSubagentIds.has(subagent.id)) continue; + if (subagentSummaries.get(subagent.id) !== description) continue; + if (!bestMatch || subagent.startTime < bestMatch.startTime) { + bestMatch = subagent; + } + } + + if (bestMatch) { + this.enrichSubagentFromTask(bestMatch, taskCall); + matchedSubagentIds.add(bestMatch.id); + matchedTaskIds.add(taskCall.id); + } + } + } + + // Phase 3: Positional fallback for remaining unmatched non-team subagents (no wrap-around) + const unmatchedSubagents = [...subagents] + .filter((s) => !matchedSubagentIds.has(s.id)) + .sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); + const unmatchedTasks = taskCallsOnly.filter( + (tc) => !matchedTaskIds.has(tc.id) && !(tc.input?.team_name && tc.input?.name) + ); + + for (let i = 0; i < unmatchedSubagents.length && i < unmatchedTasks.length; i++) { + this.enrichSubagentFromTask(unmatchedSubagents[i], unmatchedTasks[i]); + } + } + + /** + * Enrich a subagent with metadata from its parent Task call. + * Intentionally mutates the subagent in place for consistency with other resolution methods. + */ + private enrichSubagentFromTask(subagent: Process, taskCall: ToolCall): void { + /* eslint-disable no-param-reassign -- Mutation is intentional; subagent is enriched in place */ + subagent.parentTaskId = taskCall.id; + subagent.description = taskCall.taskDescription; + subagent.subagentType = taskCall.taskSubagentType; + + // Extract team metadata from Task call input + const teamName = taskCall.input?.team_name as string | undefined; + const memberName = taskCall.input?.name as string | undefined; + if (teamName && memberName) { + subagent.team = { teamName, memberName, memberColor: '' }; + } + /* eslint-enable no-param-reassign -- End of intentional mutation block */ + } + + /** + * Enrich team member subagents with color information from tool results. + * Teammate spawned results contain color information. + */ + private enrichTeamColors(subagents: Process[], messages: ParsedMessage[]): void { + for (const msg of messages) { + if (!msg.toolUseResult) continue; + // sourceToolUseID may be absent on teammate_spawned results; + // fall back to toolResults[0].toolUseId + const sourceId = msg.sourceToolUseID ?? msg.toolResults[0]?.toolUseId; + if (!sourceId) continue; + const result = msg.toolUseResult; + if (result.status === 'teammate_spawned' && result.color) { + // Set color on ALL subagents sharing this parentTaskId + // (primary file + continuation files from parentUuid chain propagation) + for (const subagent of subagents) { + if (subagent.parentTaskId === sourceId && subagent.team) { + subagent.team.memberColor = result.color as string; + } + } + } + } + } + + /** + * Propagate team metadata to continuation files via parentUuid chain. + * + * Team members generate multiple JSONL files (one per activation/turn). + * Only the primary file is matched by linkToTaskCalls (Phase 2 description match). + * Continuation files (task assignments, shutdown responses) are linked to the + * same teammate by following the parentUuid chain: a continuation file's first + * message.parentUuid matches the last message.uuid of the previous file. + */ + private propagateTeamMetadata(subagents: Process[]): void { + // Build map: last message uuid → subagent (for chain lookups) + const lastUuidToSubagent = new Map(); + for (const subagent of subagents) { + if (subagent.messages.length === 0) continue; + const lastMsg = subagent.messages[subagent.messages.length - 1]; + if (lastMsg.uuid) { + lastUuidToSubagent.set(lastMsg.uuid, subagent); + } + } + + // For each subagent without team metadata, follow parentUuid chain + // to find an ancestor with team metadata and propagate it + const maxDepth = 10; + for (const subagent of subagents) { + if (subagent.team) continue; // Already has team metadata + if (subagent.messages.length === 0) continue; + + const firstMsg = subagent.messages[0]; + if (!firstMsg.parentUuid) continue; + + // Walk the chain upward + let ancestor: Process | undefined = lastUuidToSubagent.get(firstMsg.parentUuid); + let depth = 0; + + while (ancestor && !ancestor.team && depth < maxDepth) { + if (ancestor.messages.length === 0) break; + const parentUuid = ancestor.messages[0].parentUuid; + if (!parentUuid) break; + ancestor = lastUuidToSubagent.get(parentUuid); + depth++; + } + + if (ancestor?.team) { + subagent.team = { ...ancestor.team }; + subagent.parentTaskId = subagent.parentTaskId ?? ancestor.parentTaskId; + subagent.description = subagent.description ?? ancestor.description; + subagent.subagentType = subagent.subagentType ?? ancestor.subagentType; + } + } + } + + // =========================================================================== + // Parallel Detection + // =========================================================================== + + /** + * Detect parallel execution among subagents. + * Subagents with start times within PARALLEL_WINDOW_MS are marked as parallel. + */ + private detectParallelExecution(subagents: Process[]): void { + if (subagents.length < 2) return; + + // Sort by start time + const sorted = [...subagents].sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); + + // Group by start time buckets + const groups: Process[][] = []; + let currentGroup: Process[] = []; + let groupStartTime = 0; + + for (const agent of sorted) { + const startMs = agent.startTime.getTime(); + + if (currentGroup.length === 0) { + // Start new group + currentGroup.push(agent); + groupStartTime = startMs; + } else if (startMs - groupStartTime <= PARALLEL_WINDOW_MS) { + // Add to current group + currentGroup.push(agent); + } else { + // Finalize current group and start new one + if (currentGroup.length > 0) { + groups.push(currentGroup); + } + currentGroup = [agent]; + groupStartTime = startMs; + } + } + + // Don't forget the last group + if (currentGroup.length > 0) { + groups.push(currentGroup); + } + + // Mark agents in groups with multiple members as parallel + for (const group of groups) { + if (group.length > 1) { + for (const agent of group) { + agent.isParallel = true; + } + } + } + } + + // =========================================================================== + // Utility Methods + // =========================================================================== + + /** + * Get subagent by ID. + */ + findSubagentById(subagents: Process[], id: string): Process | undefined { + return subagents.find((s) => s.id === id); + } + + /** + * Get parallel subagent groups. + */ + getParallelGroups(subagents: Process[]): Process[][] { + const parallelAgents = subagents.filter((s) => s.isParallel); + if (parallelAgents.length === 0) return []; + + // Group by start time + const sorted = [...parallelAgents].sort( + (a, b) => a.startTime.getTime() - b.startTime.getTime() + ); + + const groups: Process[][] = []; + let currentGroup: Process[] = []; + let groupStartTime = 0; + + for (const agent of sorted) { + const startMs = agent.startTime.getTime(); + + if (currentGroup.length === 0) { + currentGroup.push(agent); + groupStartTime = startMs; + } else if (startMs - groupStartTime <= PARALLEL_WINDOW_MS) { + currentGroup.push(agent); + } else { + groups.push(currentGroup); + currentGroup = [agent]; + groupStartTime = startMs; + } + } + + if (currentGroup.length > 0) { + groups.push(currentGroup); + } + + return groups.filter((g) => g.length > 1); + } + + /** + * Calculate total metrics for all subagents. + */ + getTotalSubagentMetrics(subagents: Process[]): SessionMetrics { + if (subagents.length === 0) { + return { + durationMs: 0, + totalTokens: 0, + inputTokens: 0, + outputTokens: 0, + cacheReadTokens: 0, + cacheCreationTokens: 0, + messageCount: 0, + }; + } + + let totalDuration = 0; + let inputTokens = 0; + let outputTokens = 0; + let cacheReadTokens = 0; + let cacheCreationTokens = 0; + let messageCount = 0; + + for (const agent of subagents) { + totalDuration += agent.durationMs; + inputTokens += agent.metrics.inputTokens; + outputTokens += agent.metrics.outputTokens; + cacheReadTokens += agent.metrics.cacheReadTokens; + cacheCreationTokens += agent.metrics.cacheCreationTokens; + messageCount += agent.metrics.messageCount; + } + + return { + durationMs: totalDuration, + totalTokens: inputTokens + outputTokens, + inputTokens, + outputTokens, + cacheReadTokens, + cacheCreationTokens, + messageCount, + }; + } +} diff --git a/src/main/services/discovery/SubprojectRegistry.ts b/src/main/services/discovery/SubprojectRegistry.ts new file mode 100644 index 00000000..823996d7 --- /dev/null +++ b/src/main/services/discovery/SubprojectRegistry.ts @@ -0,0 +1,98 @@ +/** + * SubprojectRegistry - Maps composite project IDs to their split data. + * + * When multiple sessions in the same encoded directory have different `cwd` values, + * they are split into separate "subprojects". Each subproject gets a composite ID + * of the form `{encodedDir}::{sha256(cwd).slice(0,8)}`. + * + * This singleton registry tracks: + * - Which base directory a composite ID maps to + * - Which cwd each subproject represents + * - Which session IDs belong to each subproject + */ + +import * as crypto from 'crypto'; + +interface SubprojectEntry { + baseDir: string; + cwd: string; + sessionIds: Set; +} + +class SubprojectRegistryImpl { + private readonly entries = new Map(); + + /** + * Register a subproject and return its composite ID. + * + * @param baseDir - The encoded directory name (e.g., "-Users-name-project") + * @param cwd - The actual working directory for this subproject + * @param sessionIds - Session IDs belonging to this subproject + * @returns Composite ID in the form `{baseDir}::{hash}` + */ + register(baseDir: string, cwd: string, sessionIds: string[]): string { + const hash = crypto.createHash('sha256').update(cwd).digest('hex').slice(0, 8); + const compositeId = `${baseDir}::${hash}`; + this.entries.set(compositeId, { + baseDir, + cwd, + sessionIds: new Set(sessionIds), + }); + return compositeId; + } + + /** + * Extract the base directory from any project ID (composite or plain). + * For composite IDs (`{encoded}::{hash}`), returns the encoded part. + * For plain IDs, returns the ID as-is. + */ + getBaseDir(projectId: string): string { + const sep = projectId.indexOf('::'); + if (sep !== -1) { + return projectId.slice(0, sep); + } + return projectId; + } + + /** + * Check if a project ID is a composite (split) ID. + */ + isComposite(projectId: string): boolean { + return projectId.includes('::'); + } + + /** + * Get the session ID filter set for a composite project ID. + * Returns null for plain (non-composite) IDs. + */ + getSessionFilter(projectId: string): Set | null { + const entry = this.entries.get(projectId); + return entry?.sessionIds ?? null; + } + + /** + * Get the cwd for a composite project ID. + * Returns null for plain (non-composite) IDs. + */ + getCwd(projectId: string): string | null { + const entry = this.entries.get(projectId); + return entry?.cwd ?? null; + } + + /** + * Get the full entry for a composite project ID. + */ + getEntry(projectId: string): SubprojectEntry | undefined { + return this.entries.get(projectId); + } + + /** + * Clear all registered subprojects. Called at the start of a full re-scan. + */ + clear(): void { + this.entries.clear(); + } +} + +/** Module-level singleton */ +export const subprojectRegistry = new SubprojectRegistryImpl(); diff --git a/src/main/services/discovery/WorktreeGrouper.ts b/src/main/services/discovery/WorktreeGrouper.ts new file mode 100644 index 00000000..95bd0840 --- /dev/null +++ b/src/main/services/discovery/WorktreeGrouper.ts @@ -0,0 +1,200 @@ +/** + * WorktreeGrouper - Groups projects by git repository. + * + * Responsibilities: + * - Group projects that belong to the same git repository + * - Handle worktrees (main repo + worktrees grouped together) + * - Filter out empty worktrees (no visible sessions) + * - Sort worktrees by main first, then by most recent activity + */ + +import { + type Project, + type RepositoryGroup, + type RepositoryIdentity, + type Worktree, +} from '@main/types'; +import { extractBaseDir } from '@main/utils/pathDecoder'; +import * as path from 'path'; + +import { gitIdentityResolver } from '../parsing/GitIdentityResolver'; + +import { SessionContentFilter } from './SessionContentFilter'; +import { subprojectRegistry } from './SubprojectRegistry'; + +/** + * WorktreeGrouper provides methods for grouping projects by git repository. + */ +export class WorktreeGrouper { + private readonly projectsDir: string; + + constructor(projectsDir: string) { + this.projectsDir = projectsDir; + } + + /** + * Groups projects by git repository. + * Projects belonging to the same git repository (main repo + worktrees) + * are grouped together under a single RepositoryGroup. + * Non-git projects are represented as single-worktree groups. + * + * Sessions are filtered to exclude noise-only sessions, so counts + * accurately reflect visible sessions in the UI. + * + * @param projects - List of projects to group + * @returns Promise resolving to RepositoryGroups sorted by most recent activity + */ + async groupByRepository(projects: Project[]): Promise { + if (projects.length === 0) { + return []; + } + + // 1. Resolve repository identity for each project + const projectIdentities = new Map(); + const projectBranches = new Map(); + + await Promise.all( + projects.map(async (project) => { + const identity = await gitIdentityResolver.resolveIdentity(project.path); + projectIdentities.set(project.id, identity); + + // Also get branch name for display + const branch = await gitIdentityResolver.getBranch(project.path); + projectBranches.set(project.id, branch); + }) + ); + + // 2. Filter sessions for each project to only include non-noise sessions + const projectFilteredSessions = new Map(); + await Promise.all( + projects.map(async (project) => { + const baseDir = extractBaseDir(project.id); + const projectPath = path.join(this.projectsDir, baseDir); + const sessionFilter = subprojectRegistry.getSessionFilter(project.id); + const filteredSessions: string[] = []; + + for (const sessionId of project.sessions) { + // Skip sessions that don't belong to this subproject + if (sessionFilter && !sessionFilter.has(sessionId)) { + continue; + } + const sessionPath = path.join(projectPath, `${sessionId}.jsonl`); + if (await SessionContentFilter.hasNonNoiseMessages(sessionPath)) { + filteredSessions.push(sessionId); + } + } + + projectFilteredSessions.set(project.id, filteredSessions); + }) + ); + + // 3. Group projects by repository + const repoGroups = new Map< + string, + { + identity: RepositoryIdentity | null; + projects: Project[]; + branches: Map; + } + >(); + + for (const project of projects) { + const identity = projectIdentities.get(project.id) ?? null; + const branch = projectBranches.get(project.id) ?? null; + + // Use repository ID if available, otherwise use project ID (for non-git projects) + const groupId = identity?.id ?? project.id; + + if (!repoGroups.has(groupId)) { + repoGroups.set(groupId, { + identity, + projects: [], + branches: new Map(), + }); + } + + const group = repoGroups.get(groupId)!; + group.projects.push(project); + group.branches.set(project.id, branch); + } + + // 4. Convert to RepositoryGroup[] + const repositoryGroups: RepositoryGroup[] = []; + + for (const [groupId, group] of repoGroups) { + const worktrees: Worktree[] = group.projects.map((project) => { + const branch = group.branches.get(project.id) ?? null; + const isMainWorktree = !gitIdentityResolver.isWorktree(project.path); + // Use filtered sessions instead of raw sessions + const filteredSessions = projectFilteredSessions.get(project.id) ?? []; + // Detect worktree source for badge display + const source = gitIdentityResolver.detectWorktreeSource(project.path); + // Use source-aware display name generation + const displayName = gitIdentityResolver.getWorktreeDisplayName( + project.path, + source, + branch, + isMainWorktree + ); + + return { + id: project.id, + path: project.path, + name: displayName, + gitBranch: branch ?? undefined, + isMainWorktree, + source, + sessions: filteredSessions, + createdAt: project.createdAt, + mostRecentSession: project.mostRecentSession, + }; + }); + + // Filter out worktrees with 0 visible sessions + const nonEmptyWorktrees = worktrees.filter((wt) => wt.sessions.length > 0); + + // Skip this repository group if all worktrees are empty + if (nonEmptyWorktrees.length === 0) { + continue; + } + + // Sort worktrees: main first, then by most recent activity + nonEmptyWorktrees.sort((a, b) => { + if (a.isMainWorktree && !b.isMainWorktree) return -1; + if (!a.isMainWorktree && b.isMainWorktree) return 1; + return (b.mostRecentSession ?? 0) - (a.mostRecentSession ?? 0); + }); + + const totalSessions = nonEmptyWorktrees.reduce((sum, wt) => sum + wt.sessions.length, 0); + const mostRecentSession = Math.max( + ...nonEmptyWorktrees.map((wt) => wt.mostRecentSession ?? 0) + ); + + repositoryGroups.push({ + id: groupId, + identity: group.identity, + worktrees: nonEmptyWorktrees, + name: group.identity?.name ?? group.projects[0].name, + mostRecentSession: mostRecentSession > 0 ? mostRecentSession : undefined, + totalSessions, + }); + } + + // 5. Sort repository groups by most recent activity + repositoryGroups.sort((a, b) => (b.mostRecentSession ?? 0) - (a.mostRecentSession ?? 0)); + + return repositoryGroups; + } + + /** + * Lists sessions for a specific worktree. + * This is a convenience method that returns the worktree ID. + * + * @param worktreeId - The worktree ID (same as project ID) + * @returns The worktree ID for delegation to listSessions + */ + getWorktreeProjectId(worktreeId: string): string { + // Worktree ID is the same as project ID + return worktreeId; + } +} diff --git a/src/main/services/discovery/index.ts b/src/main/services/discovery/index.ts new file mode 100644 index 00000000..6c7b1897 --- /dev/null +++ b/src/main/services/discovery/index.ts @@ -0,0 +1,20 @@ +/** + * Discovery services - Scanning and locating session data. + * + * Exports: + * - ProjectScanner: Scans ~/.claude/projects/ for projects and sessions + * - SessionSearcher: Searches session content + * - SessionContentFilter: Filters session content for display + * - SubagentLocator: Locates subagent JSONL files + * - SubagentResolver: Resolves and links subagents to Task calls + * - WorktreeGrouper: Groups projects by git worktree + */ + +export * from './ProjectPathResolver'; +export * from './ProjectScanner'; +export * from './SessionContentFilter'; +export * from './SessionSearcher'; +export * from './SubagentLocator'; +export * from './SubagentResolver'; +export * from './SubprojectRegistry'; +export * from './WorktreeGrouper'; diff --git a/src/main/services/error/ErrorDetector.ts b/src/main/services/error/ErrorDetector.ts new file mode 100644 index 00000000..4bf67e24 --- /dev/null +++ b/src/main/services/error/ErrorDetector.ts @@ -0,0 +1,222 @@ +/** + * ErrorDetector service - Detects errors from parsed JSONL messages. + * + * This is the main orchestrator that coordinates between specialized modules: + * - ToolSummaryFormatter: Formats tool information for display + * - TriggerMatcher: Pattern matching utilities + * - ToolResultExtractor: Extracts tool results from messages + * - ErrorMessageBuilder: Builds error messages and DetectedError objects + * - ErrorTriggerChecker: Checks different trigger types + * - ErrorTriggerTester: Testing functionality for trigger preview + * + * Detection criteria: + * - Uses configurable triggers from ConfigManager + * - Supports tool_result triggers with requireError, toolName, and matchPattern + * - Supports tool_use triggers for future expansion + * - Supports token_threshold triggers for monitoring context usage + */ + +import { type ParsedMessage } from '@main/types'; + +import { + buildToolResultMap, + buildToolUseMap, + type ToolResultInfo, + type ToolUseInfo, +} from '../analysis/ToolResultExtractor'; +import { ConfigManager, type NotificationTrigger } from '../infrastructure/ConfigManager'; + +import { type DetectedError } from './ErrorMessageBuilder'; +import { + checkTokenThresholdTrigger, + checkToolResultTrigger, + checkToolUseTrigger, + matchesRepositoryScope, + preResolveRepositoryIds, +} from './ErrorTriggerChecker'; +import { testTrigger as testTriggerImpl } from './ErrorTriggerTester'; + +// ============================================================================= +// Error Detector Class +// ============================================================================= + +class ErrorDetector { + // =========================================================================== + // Main Detection Method + // =========================================================================== + + /** + * Detects errors from an array of parsed messages using configurable triggers. + * + * @param messages - Array of ParsedMessage objects from a session + * @param sessionId - The session ID + * @param projectId - The project ID (encoded directory name) + * @param filePath - Path to the JSONL file + * @returns Array of DetectedError objects + */ + async detectErrors( + messages: ParsedMessage[], + sessionId: string, + projectId: string, + filePath: string + ): Promise { + const errors: DetectedError[] = []; + + // Get enabled triggers from config + const configManager = ConfigManager.getInstance(); + const triggers = configManager.getEnabledTriggers(); + + if (triggers.length === 0) { + return errors; + } + + // Pre-resolve repository ID for this project to populate cache. + const cwdHint = + messages.find((message) => typeof message.cwd === 'string' && message.cwd.trim().length > 0) + ?.cwd ?? undefined; + await preResolveRepositoryIds([{ projectId, cwdHint }]); + + // Build tool_use map for linking results to calls + const toolUseMap = buildToolUseMap(messages); + // Build tool_result map for estimating output tokens + const toolResultMap = buildToolResultMap(messages); + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + const lineNumber = i + 1; // 1-based line numbers for JSONL + + // Check each trigger against this message + for (const trigger of triggers) { + const triggerErrors = this.checkTrigger( + message, + trigger, + toolUseMap, + toolResultMap, + sessionId, + projectId, + filePath, + lineNumber + ); + + // Add all detected errors (can be multiple for token_threshold mode) + errors.push(...triggerErrors); + } + } + + return errors; + } + + // =========================================================================== + // Trigger Checking (Router) + // =========================================================================== + + /** + * Checks if a message matches a specific trigger. + * Routes to the appropriate trigger checker based on trigger configuration. + * + * @param message - The parsed message to check + * @param trigger - The trigger configuration + * @param toolUseMap - Map of tool_use_id to tool_use content for linking results to calls + * @param toolResultMap - Map of tool_use_id to tool_result content for token estimation + * @param sessionId - Session ID + * @param projectId - Project ID + * @param filePath - File path + * @param lineNumber - Line number in JSONL + * @returns Array of DetectedError (can be multiple for token_threshold mode) + */ + private checkTrigger( + message: ParsedMessage, + trigger: NotificationTrigger, + toolUseMap: Map, + toolResultMap: Map, + sessionId: string, + projectId: string, + filePath: string, + lineNumber: number + ): DetectedError[] { + // Check repository scope first - if repositoryIds is set, only trigger for matching repositories + if (!matchesRepositoryScope(projectId, trigger.repositoryIds)) { + return []; + } + + // Use the mode directly (mode is now required in NotificationTrigger) + const effectiveMode = trigger.mode; + + // Handle token_threshold mode - check each tool_use individually + if (effectiveMode === 'token_threshold') { + return checkTokenThresholdTrigger( + message, + trigger, + toolResultMap, + sessionId, + projectId, + filePath, + lineNumber + ); + } + + // Handle tool_result triggers + if (trigger.contentType === 'tool_result') { + const error = checkToolResultTrigger( + message, + trigger, + toolUseMap, + sessionId, + projectId, + filePath, + lineNumber + ); + return error ? [error] : []; + } + + // Handle tool_use triggers (for future expansion) + if (trigger.contentType === 'tool_use') { + const error = checkToolUseTrigger( + message, + trigger, + sessionId, + projectId, + filePath, + lineNumber + ); + return error ? [error] : []; + } + + return []; + } + + // =========================================================================== + // Trigger Testing (Preview Feature) + // =========================================================================== + + /** + * Tests a trigger configuration against historical session data. + * Returns a list of errors that would have been detected. + * + * Safety features (handled by ErrorTriggerTester): + * - Limits returned errors to 50 + * - Caps totalCount at 10,000 to prevent indefinite counting + * - Stops scanning after 100 sessions + * - Aborts after 30 seconds + * + * @param trigger - The trigger configuration to test + * @param limit - Maximum number of results to return (default 50) + */ + public async testTrigger( + trigger: NotificationTrigger, + limit: number = 50 + ): Promise<{ + totalCount: number; + errors: DetectedError[]; + /** True if results were truncated due to safety limits */ + truncated?: boolean; + }> { + return testTriggerImpl(trigger, limit); + } +} + +// ============================================================================= +// Singleton Export +// ============================================================================= + +export const errorDetector = new ErrorDetector(); diff --git a/src/main/services/error/ErrorMessageBuilder.ts b/src/main/services/error/ErrorMessageBuilder.ts new file mode 100644 index 00000000..6f3ad900 --- /dev/null +++ b/src/main/services/error/ErrorMessageBuilder.ts @@ -0,0 +1,185 @@ +/** + * ErrorMessageBuilder service - Builds error messages and DetectedError objects. + * + * Provides utilities for: + * - Extracting error messages from tool results + * - Finding tool names by ID + * - Creating DetectedError objects + * - Truncating messages for display + */ + +import { type ContentBlock, type ParsedMessage } from '@main/types'; +import { randomUUID } from 'crypto'; + +import { type ExtractedToolResult } from '../analysis/ToolResultExtractor'; + +import type { TriggerColor } from '@shared/constants/triggerColors'; + +// ============================================================================= +// Types +// ============================================================================= + +/** + * Represents a detected error from a Claude Code session. + */ +export interface DetectedError { + /** UUID for unique identification */ + id: string; + /** Unix timestamp when error was detected */ + timestamp: number; + /** Session ID where error occurred */ + sessionId: string; + /** Project ID (encoded directory name) */ + projectId: string; + /** Path to the JSONL file */ + filePath: string; + /** Source of the error - tool name or 'assistant' */ + source: string; + /** Error message content */ + message: string; + /** Line number in JSONL for deep linking */ + lineNumber?: number; + /** Tool use ID for precise deep linking to the specific tool item */ + toolUseId?: string; + /** Subagent ID when error originates from a subagent session */ + subagentId?: string; + /** Trigger color key for notification dot and highlight */ + triggerColor?: TriggerColor; + /** ID of the trigger that produced this notification */ + triggerId?: string; + /** Human-readable name of the trigger that produced this notification */ + triggerName?: string; + /** Additional context about the error */ + context: { + /** Human-readable project name */ + projectName: string; + /** Current working directory when error occurred */ + cwd?: string; + }; +} + +/** + * Parameters for creating a DetectedError. + */ +export interface CreateDetectedErrorParams { + sessionId: string; + projectId: string; + filePath: string; + projectName: string; + lineNumber: number; + source: string; + message: string; + timestamp: Date; + cwd?: string; + toolUseId?: string; + subagentId?: string; + triggerColor?: TriggerColor; + triggerId?: string; + triggerName?: string; +} + +// ============================================================================= +// Error Message Extraction +// ============================================================================= + +/** + * Extracts error message from a tool result. + */ +export function extractErrorMessage(result: ExtractedToolResult): string { + if (typeof result.content === 'string') { + return result.content.trim() || 'Unknown error'; + } + + if (Array.isArray(result.content)) { + const texts: string[] = []; + for (const item of result.content) { + if (item && typeof item === 'object' && 'type' in item) { + const block = item as ContentBlock; + if (block.type === 'text' && 'text' in block) { + texts.push(block.text); + } + } + } + return texts.join('\n').trim() || 'Unknown error'; + } + + return 'Unknown error'; +} + +// ============================================================================= +// Tool Name Lookup +// ============================================================================= + +/** + * Finds tool name from message's tool calls by tool use ID. + */ +function findToolName(message: ParsedMessage, toolUseId: string): string | null { + if (message.toolCalls) { + const toolCall = message.toolCalls.find((tc) => tc.id === toolUseId); + if (toolCall) { + return toolCall.name; + } + } + return null; +} + +/** + * Finds tool name by searching tool_use_id in the message context. + */ +export function findToolNameByToolUseId(message: ParsedMessage, toolUseId: string): string | null { + // First check toolCalls + const fromToolCalls = findToolName(message, toolUseId); + if (fromToolCalls) return fromToolCalls; + + // Check sourceToolUseID if this message is a tool result + if (message.sourceToolUseID === toolUseId && message.toolUseResult) { + if (typeof message.toolUseResult.toolName === 'string') { + return message.toolUseResult.toolName; + } + } + + return null; +} + +// ============================================================================= +// Message Truncation +// ============================================================================= + +/** + * Truncates error message to a reasonable length for display. + */ +function truncateMessage(message: string, maxLength: number = 500): string { + if (message.length <= maxLength) { + return message; + } + return message.slice(0, maxLength) + '...'; +} + +// ============================================================================= +// DetectedError Creation +// ============================================================================= + +/** + * Creates a DetectedError object with all required fields. + */ +export function createDetectedError(params: CreateDetectedErrorParams): DetectedError { + return { + id: randomUUID(), + timestamp: params.timestamp.getTime(), + sessionId: params.sessionId, + projectId: params.projectId, + filePath: params.filePath, + source: params.source, + message: truncateMessage(params.message), + lineNumber: params.lineNumber, + toolUseId: params.toolUseId, + subagentId: params.subagentId, + triggerColor: params.triggerColor, + triggerId: params.triggerId, + triggerName: params.triggerName, + context: { + projectName: params.projectName, + cwd: params.cwd, + }, + }; +} diff --git a/src/main/services/error/ErrorTriggerChecker.ts b/src/main/services/error/ErrorTriggerChecker.ts new file mode 100644 index 00000000..9ddbf3de --- /dev/null +++ b/src/main/services/error/ErrorTriggerChecker.ts @@ -0,0 +1,461 @@ +/** + * ErrorTriggerChecker service - Checks different trigger types against messages. + * + * Provides utilities for: + * - Checking tool_result triggers + * - Checking tool_use triggers + * - Checking token threshold triggers + * - Validating project scope + */ + +import { type ParsedMessage } from '@main/types'; +import { extractProjectName } from '@main/utils/pathDecoder'; + +import { + estimateTokens, + extractToolResults, + type ToolResultInfo, + type ToolUseInfo, +} from '../analysis/ToolResultExtractor'; +import { formatTokens, getToolSummary } from '../analysis/ToolSummaryFormatter'; +import { projectPathResolver } from '../discovery/ProjectPathResolver'; +import { type NotificationTrigger } from '../infrastructure/ConfigManager'; +import { gitIdentityResolver } from '../parsing/GitIdentityResolver'; + +import { + createDetectedError, + type DetectedError, + extractErrorMessage, + findToolNameByToolUseId, +} from './ErrorMessageBuilder'; +import { + extractToolUseField, + getContentBlocks, + matchesIgnorePatterns, + matchesPattern, +} from './TriggerMatcher'; + +// ============================================================================= +// Repository Scope Checking +// ============================================================================= + +// Cache for projectId -> repositoryId mapping to avoid repeated resolution +const repositoryIdCache = new Map(); + +interface RepositoryScopeTarget { + projectId: string; + cwdHint?: string; +} + +/** + * Resolves a projectId to its repositoryId using GitIdentityResolver. + * Results are cached for performance. + * @param projectId - The encoded project ID (e.g., "-Users-username-myproject") + * @returns Repository ID or null if not resolvable + */ +async function resolveRepositoryId(target: string | RepositoryScopeTarget): Promise { + const projectId = typeof target === 'string' ? target : target.projectId; + const cwdHint = typeof target === 'string' ? undefined : target.cwdHint; + + // Check cache first + if (repositoryIdCache.has(projectId)) { + return repositoryIdCache.get(projectId) ?? null; + } + + const projectPath = await projectPathResolver.resolveProjectPath(projectId, { cwdHint }); + + // Resolve repository identity + const identity = await gitIdentityResolver.resolveIdentity(projectPath); + const repositoryId = identity?.id ?? null; + + // Cache the result + repositoryIdCache.set(projectId, repositoryId); + + return repositoryId; +} + +/** + * Synchronous version of resolveRepositoryId using cached values only. + * If not cached, attempts synchronous resolution via path heuristics. + */ +function resolveRepositoryIdSync(projectId: string): string | null { + // Check cache first + if (repositoryIdCache.has(projectId)) { + return repositoryIdCache.get(projectId) ?? null; + } + + // For sync context, we can't do async resolution + // The async version should be called during initialization + return null; +} + +/** + * Checks if the project matches the trigger's repository scope. + * @param projectId - The encoded project ID (e.g., "-Users-username-myproject") + * @param repositoryIds - Optional list of repository group IDs to scope the trigger to + * @returns true if trigger should apply, false if it should be skipped + */ +export function matchesRepositoryScope(projectId: string, repositoryIds?: string[]): boolean { + // If no repository IDs specified, trigger applies to all repositories + if (!repositoryIds || repositoryIds.length === 0) { + return true; + } + + // Get the repository ID for this project (from cache) + const repositoryId = resolveRepositoryIdSync(projectId); + + // If we can't resolve the repository ID, don't match + if (!repositoryId) { + return false; + } + + // Check if the repository ID matches any of the configured IDs + return repositoryIds.includes(repositoryId); +} + +/** + * Pre-resolves repository IDs for a list of project IDs. + * Call this before checking triggers to populate the cache. + */ +export async function preResolveRepositoryIds( + targets: (string | RepositoryScopeTarget)[] +): Promise { + const uniqueTargets = new Map(); + + for (const target of targets) { + if (typeof target === 'string') { + if (!uniqueTargets.has(target)) { + uniqueTargets.set(target, { projectId: target }); + } + continue; + } + + const existing = uniqueTargets.get(target.projectId); + if (!existing) { + uniqueTargets.set(target.projectId, target); + continue; + } + + // Prefer a target with cwd hint if one was provided. + if (!existing.cwdHint && target.cwdHint) { + uniqueTargets.set(target.projectId, target); + } + } + + await Promise.all( + Array.from(uniqueTargets.values()).map((target) => resolveRepositoryId(target)) + ); +} + +// ============================================================================= +// Tool Result Trigger Checking +// ============================================================================= + +/** + * Checks if a tool_result matches a trigger. + */ +export function checkToolResultTrigger( + message: ParsedMessage, + trigger: NotificationTrigger, + toolUseMap: Map, + sessionId: string, + projectId: string, + filePath: string, + lineNumber: number +): DetectedError | null { + const toolResults = extractToolResults(message, findToolNameByToolUseId); + + for (const result of toolResults) { + // If requireError is true, only match when is_error is true + if (trigger.requireError) { + if (!result.isError) { + continue; + } + + // Extract error message for ignore pattern checking + const errorMessage = extractErrorMessage(result); + + // Check ignore patterns - if any match, skip this error + if (matchesIgnorePatterns(errorMessage, trigger.ignorePatterns)) { + continue; + } + + // Create detected error + return createDetectedError({ + sessionId, + projectId, + filePath, + projectName: extractProjectName(projectId), + lineNumber, + source: result.toolName ?? 'tool_result', + message: errorMessage, + timestamp: message.timestamp, + cwd: message.cwd, + toolUseId: result.toolUseId, + triggerColor: trigger.color, + triggerId: trigger.id, + triggerName: trigger.name, + }); + } + + // Non-error tool_result triggers (if toolName is specified) + if (trigger.toolName) { + const toolUse = toolUseMap.get(result.toolUseId); + if (toolUse?.name !== trigger.toolName) { + continue; + } + + // Match against content if matchField is 'content' + if (trigger.matchField === 'content' && trigger.matchPattern) { + const content = + typeof result.content === 'string' ? result.content : JSON.stringify(result.content); + if (!matchesPattern(content, trigger.matchPattern)) { + continue; + } + if (matchesIgnorePatterns(content, trigger.ignorePatterns)) { + continue; + } + + return createDetectedError({ + sessionId, + projectId, + filePath, + projectName: extractProjectName(projectId), + lineNumber, + source: trigger.toolName, + message: `Tool result matched: ${content.slice(0, 200)}`, + timestamp: message.timestamp, + cwd: message.cwd, + toolUseId: result.toolUseId, + triggerColor: trigger.color, + triggerId: trigger.id, + triggerName: trigger.name, + }); + } + } + } + + return null; +} + +// ============================================================================= +// Tool Use Trigger Checking +// ============================================================================= + +/** + * Checks if a tool_use matches a trigger. + */ +export function checkToolUseTrigger( + message: ParsedMessage, + trigger: NotificationTrigger, + sessionId: string, + projectId: string, + filePath: string, + lineNumber: number +): DetectedError | null { + if (message.type !== 'assistant') return null; + + const contentBlocks = getContentBlocks(message); + + for (const block of contentBlocks) { + if (block.type !== 'tool_use') continue; + + const toolUse = block as { + type: 'tool_use'; + id: string; + name: string; + input?: Record; + }; + + // Check tool name if specified + if (trigger.toolName && toolUse.name !== trigger.toolName) { + continue; + } + + // Extract the field to match based on matchField + // If no matchField specified (e.g., "Any Tool"), match against entire input JSON + const fieldValue = trigger.matchField + ? extractToolUseField(toolUse, trigger.matchField) + : toolUse.input + ? JSON.stringify(toolUse.input) + : null; + if (!fieldValue) continue; + + // Check match pattern + if (trigger.matchPattern && !matchesPattern(fieldValue, trigger.matchPattern)) { + continue; + } + + // Check ignore patterns + if (matchesIgnorePatterns(fieldValue, trigger.ignorePatterns)) { + continue; + } + + // Match found! + return createDetectedError({ + sessionId, + projectId, + filePath, + projectName: extractProjectName(projectId), + lineNumber, + source: toolUse.name, + message: `${trigger.matchField ?? 'tool_use'}: ${fieldValue.slice(0, 200)}`, + timestamp: message.timestamp, + cwd: message.cwd, + toolUseId: toolUse.id, + triggerColor: trigger.color, + triggerId: trigger.id, + triggerName: trigger.name, + }); + } + + return null; +} + +// ============================================================================= +// Token Threshold Trigger Checking +// ============================================================================= + +/** + * Check if individual tool_use blocks exceed the token threshold. + * Returns an array of DetectedError for each tool_use that exceeds the threshold. + * + * Token calculation (matches context window impact): + * - Tool call tokens: estimated from name + JSON.stringify(input) (what enters context) + * - Tool result tokens: estimated from tool_result.content (what Claude reads) + * - Total = call + result + */ +export function checkTokenThresholdTrigger( + message: ParsedMessage, + trigger: NotificationTrigger, + toolResultMap: Map, + sessionId: string, + projectId: string, + filePath: string, + lineNumber: number +): DetectedError[] { + const errors: DetectedError[] = []; + + // Only check for token_threshold mode + if (trigger.mode !== 'token_threshold' || !trigger.tokenThreshold) { + return errors; + } + + // Only check assistant messages that contain tool_use blocks + if (message.type !== 'assistant') { + return errors; + } + + const tokenType = trigger.tokenType ?? 'total'; + const threshold = trigger.tokenThreshold; + + // Collect all tool_use blocks from message + const toolUseBlocks: { id: string; name: string; input: Record }[] = []; + + // Check content array for tool_use blocks + if (Array.isArray(message.content)) { + for (const block of message.content) { + if (block.type === 'tool_use') { + const toolUse = block; + toolUseBlocks.push({ + id: toolUse.id, + name: toolUse.name, + input: toolUse.input || {}, + }); + } + } + } + + // Also check toolCalls array if present + if (message.toolCalls) { + for (const toolCall of message.toolCalls) { + // Avoid duplicates + if (!toolUseBlocks.some((t) => t.id === toolCall.id)) { + toolUseBlocks.push({ + id: toolCall.id, + name: toolCall.name, + input: toolCall.input || {}, + }); + } + } + } + + if (toolUseBlocks.length === 0) { + return errors; + } + + // Check each tool_use block individually + for (const toolUse of toolUseBlocks) { + // Check tool name filter if specified + if (trigger.toolName && toolUse.name !== trigger.toolName) { + continue; + } + + // Calculate tool call tokens directly from name + input + // This reflects what actually enters the context window + const toolCallTokens = estimateTokens(toolUse.name + JSON.stringify(toolUse.input)); + + // Calculate tool result tokens (what Claude reads back) + let toolResultTokens = 0; + const toolResult = toolResultMap.get(toolUse.id); + if (toolResult) { + toolResultTokens = estimateTokens(toolResult.content); + } + + // Calculate token count based on tokenType + // Note: 'input' here means tool CALL tokens (what enters context) + // 'output' here means tool RESULT tokens (what Claude reads) + let tokenCount = 0; + switch (tokenType) { + case 'input': + // Tool call tokens (name + input that enters context) + tokenCount = toolCallTokens; + break; + case 'output': + // Tool result tokens (what Claude reads - success message, file content for Read, etc.) + tokenCount = toolResultTokens; + break; + case 'total': + // Both: full context impact of the tool operation + tokenCount = toolCallTokens + toolResultTokens; + break; + } + + // Check threshold + if (tokenCount <= threshold) { + continue; + } + + // Build summary for the tool + const toolSummary = getToolSummary(toolUse.name, toolUse.input); + + // Build message with tool info and token type for clarity + const tokenTypeLabel = tokenType === 'total' ? '' : ` ${tokenType}`; + const tokenMessage = `${toolUse.name} - ${toolSummary} : ~${formatTokens(tokenCount)}${tokenTypeLabel} tokens`; + + // Check ignore patterns + if (matchesIgnorePatterns(tokenMessage, trigger.ignorePatterns)) { + continue; + } + + errors.push( + createDetectedError({ + sessionId, + projectId, + filePath, + projectName: extractProjectName(projectId), + lineNumber, + source: toolUse.name, + message: tokenMessage, + timestamp: message.timestamp, + cwd: message.cwd, + toolUseId: toolUse.id, + triggerColor: trigger.color, + triggerId: trigger.id, + triggerName: trigger.name, + }) + ); + } + + return errors; +} diff --git a/src/main/services/error/ErrorTriggerTester.ts b/src/main/services/error/ErrorTriggerTester.ts new file mode 100644 index 00000000..e92329f9 --- /dev/null +++ b/src/main/services/error/ErrorTriggerTester.ts @@ -0,0 +1,329 @@ +/** + * ErrorTriggerTester service - Testing functionality for trigger preview. + * + * Provides utilities for: + * - Testing trigger configurations against historical session data + * - Running single trigger detection for preview functionality + */ + +import { type ParsedMessage } from '@main/types'; +import { parseJsonlFile } from '@main/utils/jsonl'; +import { createLogger } from '@shared/utils/logger'; +import * as path from 'path'; + +import { + buildToolResultMap, + buildToolUseMap, + type ToolResultInfo, + type ToolUseInfo, +} from '../analysis/ToolResultExtractor'; +import { ProjectScanner } from '../discovery/ProjectScanner'; +import { type NotificationTrigger } from '../infrastructure/ConfigManager'; + +const logger = createLogger('Service:ErrorTriggerTester'); + +import { type DetectedError } from './ErrorMessageBuilder'; +import { + checkTokenThresholdTrigger, + checkToolResultTrigger, + checkToolUseTrigger, + matchesRepositoryScope, + preResolveRepositoryIds, +} from './ErrorTriggerChecker'; + +// ============================================================================= +// Trigger Testing (Preview Feature) +// ============================================================================= + +/** + * Safety limits to prevent resource exhaustion from faulty triggers. + * + * Strategy: Stop as soon as we find enough results, not after scanning N sessions. + * This allows finding rare patterns (like .env) while still being fast for common patterns. + */ +const TEST_LIMITS = { + /** Maximum number of errors to return (primary stop condition) */ + MAX_ERRORS: 50, + /** Maximum totalCount to track (prevents indefinite counting) */ + MAX_TOTAL_COUNT: 10_000, + /** Maximum time in ms before aborting (30 seconds) - main safety limit */ + TIMEOUT_MS: 30_000, +} as const; + +/** + * State object used during trigger testing to track progress and limits. + */ +interface TestState { + errors: DetectedError[]; + totalCount: number; + sessionsScanned: number; + truncated: boolean; + startTime: number; + effectiveLimit: number; +} + +/** + * Checks if the test should stop due to hitting safety limits. + * Returns a reason string if should stop, null if should continue. + * + * Stop conditions (in order of priority): + * 1. Found enough errors (effectiveLimit) - success, no warning + * 2. Timeout (30s) - safety limit + * 3. Total count limit (10k) - prevent counting forever + */ +function shouldStopTest(state: TestState): string | null { + // Primary stop condition: found enough errors + if (state.errors.length >= state.effectiveLimit) { + return null; // Stop but don't log - we have enough errors (success case) + } + + // Safety limits + if (Date.now() - state.startTime > TEST_LIMITS.TIMEOUT_MS) { + return 'Trigger test timed out after 30 seconds'; + } + if (state.totalCount >= TEST_LIMITS.MAX_TOTAL_COUNT) { + return 'Trigger test stopped after reaching count limit'; + } + + return null; +} + +/** + * Tests a trigger configuration against historical session data. + * Returns a list of errors that would have been detected. + * + * Strategy: Scan sessions until we find enough results or hit safety limits. + * This allows finding rare patterns while staying fast for common patterns. + * + * Stop conditions: + * - Found enough errors (limit) - primary success condition + * - Timeout (30s) - safety limit + * - Total count reached (10k) - prevents infinite counting + * + * @param trigger - The trigger configuration to test + * @param limit - Maximum number of results to return (default 50, capped at MAX_ERRORS) + */ +export async function testTrigger( + trigger: NotificationTrigger, + limit: number = TEST_LIMITS.MAX_ERRORS +): Promise<{ + totalCount: number; + errors: DetectedError[]; + /** True if results were truncated due to safety limits */ + truncated?: boolean; +}> { + const projectScanner = new ProjectScanner(); + + const state: TestState = { + errors: [], + totalCount: 0, + sessionsScanned: 0, + truncated: false, + startTime: Date.now(), + effectiveLimit: Math.min(limit, TEST_LIMITS.MAX_ERRORS), + }; + + try { + // Get list of all projects + const projects = await projectScanner.scan(); + + // Process each project to find session files + for (const project of projects) { + // Check safety limits before processing project + const stopReason = shouldStopTest(state); + if (stopReason) { + logger.warn(stopReason); + state.truncated = true; + break; + } + + // Early exit if we have enough errors (no truncation warning needed) + if (state.errors.length >= state.effectiveLimit) break; + + const sessionFiles = await projectScanner.listSessionFiles(project.id); + + // Pre-resolve repository ID for this project. + await preResolveRepositoryIds([{ projectId: project.id, cwdHint: project.path }]); + + // Process each session file (most recent first) + const shouldBreakOuter = await processSessionFiles( + sessionFiles, + trigger, + project.id, + state, + parseJsonlFile + ); + + if (shouldBreakOuter) break; + } + + return { totalCount: state.totalCount, errors: state.errors, truncated: state.truncated }; + } catch (error) { + logger.error('Error testing trigger:', error); + return { totalCount: 0, errors: [] }; + } +} + +/** + * Processes session files for a single project. + * Returns true if outer loop should break, false otherwise. + */ +async function processSessionFiles( + sessionFiles: string[], + trigger: NotificationTrigger, + projectId: string, + state: TestState, + parseFile: (path: string) => Promise +): Promise { + for (const filePath of sessionFiles) { + // Check safety limits + const stopReason = shouldStopTest(state); + if (stopReason) { + logger.warn(stopReason); + state.truncated = true; + return true; // Break outer loop + } + + // Early exit if we have enough errors + if (state.errors.length >= state.effectiveLimit) return false; + + try { + state.sessionsScanned++; + + // Parse session file + const messages = await parseFile(filePath); + + // Extract sessionId from file path + const filename = path.basename(filePath); + const sessionId = filename.replace(/\.jsonl$/, ''); + + // Test the trigger against each message + const sessionErrors = detectErrorsWithTrigger( + messages, + trigger, + sessionId, + projectId, + filePath + ); + + // Update totalCount but cap it + const newTotal = state.totalCount + sessionErrors.length; + if (newTotal >= TEST_LIMITS.MAX_TOTAL_COUNT) { + state.totalCount = TEST_LIMITS.MAX_TOTAL_COUNT; + state.truncated = true; + } else { + state.totalCount = newTotal; + } + + // Add errors up to limit + for (const error of sessionErrors) { + if (state.errors.length >= state.effectiveLimit) break; + state.errors.push(error); + } + } catch (error) { + // Skip files that can't be parsed + logger.error(`Error parsing session file ${filePath}:`, error); + continue; + } + } + + return false; // Don't break outer loop +} + +/** + * Detects errors from messages using a single trigger. + * Used by testTrigger for preview functionality. + */ +function detectErrorsWithTrigger( + messages: ParsedMessage[], + trigger: NotificationTrigger, + sessionId: string, + projectId: string, + filePath: string +): DetectedError[] { + const errors: DetectedError[] = []; + + // Build tool_use map for linking results to calls + const toolUseMap = buildToolUseMap(messages); + // Build tool_result map for estimating output tokens + const toolResultMap = buildToolResultMap(messages); + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + const lineNumber = i + 1; // 1-based line numbers for JSONL + + const triggerErrors = checkTrigger( + message, + trigger, + toolUseMap, + toolResultMap, + sessionId, + projectId, + filePath, + lineNumber + ); + + // Add all detected errors (can be multiple for token_threshold mode) + errors.push(...triggerErrors); + } + + return errors; +} + +/** + * Checks if a message matches a specific trigger. + * Internal helper for detectErrorsWithTrigger. + */ +function checkTrigger( + message: ParsedMessage, + trigger: NotificationTrigger, + toolUseMap: Map, + toolResultMap: Map, + sessionId: string, + projectId: string, + filePath: string, + lineNumber: number +): DetectedError[] { + // Check repository scope first - if repositoryIds is set, only trigger for matching repositories + if (!matchesRepositoryScope(projectId, trigger.repositoryIds)) { + return []; + } + + // Use the mode directly (mode is now required in NotificationTrigger) + const effectiveMode = trigger.mode; + + // Handle token_threshold mode - check each tool_use individually + if (effectiveMode === 'token_threshold') { + return checkTokenThresholdTrigger( + message, + trigger, + toolResultMap, + sessionId, + projectId, + filePath, + lineNumber + ); + } + + // Handle tool_result triggers + if (trigger.contentType === 'tool_result') { + const error = checkToolResultTrigger( + message, + trigger, + toolUseMap, + sessionId, + projectId, + filePath, + lineNumber + ); + return error ? [error] : []; + } + + // Handle tool_use triggers (for future expansion) + if (trigger.contentType === 'tool_use') { + const error = checkToolUseTrigger(message, trigger, sessionId, projectId, filePath, lineNumber); + return error ? [error] : []; + } + + return []; +} diff --git a/src/main/services/error/TriggerMatcher.ts b/src/main/services/error/TriggerMatcher.ts new file mode 100644 index 00000000..7e06c47c --- /dev/null +++ b/src/main/services/error/TriggerMatcher.ts @@ -0,0 +1,82 @@ +/** + * TriggerMatcher service - Pattern matching utilities for trigger checking. + * + * Provides utilities for: + * - Regex pattern matching (with ReDoS protection) + * - Ignore pattern checking + * - Extracting fields from tool_use blocks + * - Getting content blocks from messages + */ + +import { type ContentBlock, type ParsedMessage } from '@main/types'; +import { createSafeRegExp } from '@main/utils/regexValidation'; + +// ============================================================================= +// Pattern Matching +// ============================================================================= + +/** + * Checks if content matches a pattern. + * Uses validated regex to prevent ReDoS attacks. + */ +export function matchesPattern(content: string, pattern: string): boolean { + const regex = createSafeRegExp(pattern, 'i'); + if (!regex) { + // Pattern is invalid or potentially dangerous, reject match + return false; + } + return regex.test(content); +} + +/** + * Checks if content matches any of the ignore patterns. + * Uses validated regex to prevent ReDoS attacks. + */ +export function matchesIgnorePatterns(content: string, ignorePatterns?: string[]): boolean { + if (!ignorePatterns || ignorePatterns.length === 0) { + return false; + } + + for (const pattern of ignorePatterns) { + const regex = createSafeRegExp(pattern, 'i'); + if (regex?.test(content)) { + return true; + } + // Invalid or potentially dangerous patterns are skipped + } + + return false; +} + +// ============================================================================= +// Field Extraction +// ============================================================================= + +/** + * Extracts the specified field from a tool_use block. + */ +export function extractToolUseField( + toolUse: { name: string; input?: Record }, + matchField?: string +): string | null { + if (!matchField || !toolUse.input) return null; + + const value = toolUse.input[matchField]; + if (typeof value === 'string') { + return value; + } + if (value !== undefined) { + return JSON.stringify(value); + } + return null; +} + +/** + * Gets content blocks from a message, handling both array and object formats. + */ +export function getContentBlocks(message: ParsedMessage): ContentBlock[] { + if (Array.isArray(message.content)) { + return message.content; + } + return []; +} diff --git a/src/main/services/error/index.ts b/src/main/services/error/index.ts new file mode 100644 index 00000000..87d5febe --- /dev/null +++ b/src/main/services/error/index.ts @@ -0,0 +1,16 @@ +/** + * Error services - Error detection and notification triggers. + * + * Exports: + * - ErrorDetector: Detects errors in parsed messages + * - ErrorTriggerChecker: Checks messages against notification triggers + * - ErrorTriggerTester: Tests triggers against historical data + * - ErrorMessageBuilder: Builds error notification messages + * - TriggerMatcher: Matches content against trigger patterns + */ + +export * from './ErrorDetector'; +export * from './ErrorMessageBuilder'; +export * from './ErrorTriggerChecker'; +export * from './ErrorTriggerTester'; +export * from './TriggerMatcher'; diff --git a/src/main/services/index.ts b/src/main/services/index.ts new file mode 100644 index 00000000..417b8e82 --- /dev/null +++ b/src/main/services/index.ts @@ -0,0 +1,16 @@ +/** + * Services barrel export - Re-exports all services for backward compatibility. + * + * Domain organization: + * - analysis/: Chunk building and session analysis + * - discovery/: Scanning and locating session data + * - error/: Error detection and notification triggers + * - infrastructure/: Core application infrastructure + * - parsing/: Parsing JSONL and configuration files + */ + +export * from './analysis'; +export * from './discovery'; +export * from './error'; +export * from './infrastructure'; +export * from './parsing'; diff --git a/src/main/services/infrastructure/ConfigManager.ts b/src/main/services/infrastructure/ConfigManager.ts new file mode 100644 index 00000000..4f6fa6c9 --- /dev/null +++ b/src/main/services/infrastructure/ConfigManager.ts @@ -0,0 +1,681 @@ +/** + * ConfigManager service - Manages app configuration stored at ~/.claude/claude-code-context-config.json. + * + * Responsibilities: + * - Load configuration from disk on initialization + * - Provide default values for all configuration fields + * - Save configuration changes to disk + * - Manage notification settings (ignore patterns, projects, snooze) + * - Handle JSON parse errors gracefully + */ + +import { validateRegexPattern } from '@main/utils/regexValidation'; +import { createLogger } from '@shared/utils/logger'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { DEFAULT_TRIGGERS, TriggerManager } from './TriggerManager'; + +import type { TriggerColor } from '@shared/constants/triggerColors'; + +const logger = createLogger('Service:ConfigManager'); + +const CONFIG_DIR = path.join(os.homedir(), '.claude'); +const CONFIG_FILENAME = 'claude-code-context-config.json'; +const DEFAULT_CONFIG_PATH = path.join(CONFIG_DIR, CONFIG_FILENAME); + +// =========================================================================== +// Types +// =========================================================================== + +export interface NotificationConfig { + enabled: boolean; + soundEnabled: boolean; + ignoredRegex: string[]; + ignoredRepositories: string[]; // Repository group IDs to ignore + snoozedUntil: number | null; // Unix timestamp (ms) when snooze ends + snoozeMinutes: number; // Default snooze duration + /** Whether to include errors from subagent sessions */ + includeSubagentErrors: boolean; + /** Notification triggers - define when to generate notifications */ + triggers: NotificationTrigger[]; +} + +/** + * Content types that can trigger notifications. + */ +export type TriggerContentType = 'tool_result' | 'tool_use' | 'thinking' | 'text'; + +/** + * Known tool names that can be filtered for tool_use triggers. + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used for type derivation only +const KNOWN_TOOL_NAMES = [ + 'Bash', + 'Task', + 'TodoWrite', + 'Read', + 'Write', + 'Edit', + 'Grep', + 'Glob', + 'WebFetch', + 'WebSearch', + 'LSP', + 'Skill', + 'NotebookEdit', + 'AskUserQuestion', + 'KillShell', + 'TaskOutput', +] as const; + +/** + * Tool names that can be filtered for tool_use triggers. + * Accepts known tool names or any custom tool name. + */ +export type TriggerToolName = (typeof KNOWN_TOOL_NAMES)[number] | (string & Record); + +/** + * Match fields available for different content types and tools. + */ +export type MatchFieldForToolResult = 'content'; +export type MatchFieldForBash = 'command' | 'description'; +export type MatchFieldForTask = 'description' | 'prompt' | 'subagent_type'; +export type MatchFieldForRead = 'file_path'; +export type MatchFieldForWrite = 'file_path' | 'content'; +export type MatchFieldForEdit = 'file_path' | 'old_string' | 'new_string'; +export type MatchFieldForGlob = 'pattern' | 'path'; +export type MatchFieldForGrep = 'pattern' | 'path' | 'glob'; +export type MatchFieldForWebFetch = 'url' | 'prompt'; +export type MatchFieldForWebSearch = 'query'; +export type MatchFieldForSkill = 'skill' | 'args'; +export type MatchFieldForThinking = 'thinking'; +export type MatchFieldForText = 'text'; + +/** + * Combined type for all possible match fields. + */ +export type TriggerMatchField = + | MatchFieldForToolResult + | MatchFieldForBash + | MatchFieldForTask + | MatchFieldForRead + | MatchFieldForWrite + | MatchFieldForEdit + | MatchFieldForGlob + | MatchFieldForGrep + | MatchFieldForWebFetch + | MatchFieldForWebSearch + | MatchFieldForSkill + | MatchFieldForThinking + | MatchFieldForText; + +/** + * Trigger mode determines how the trigger evaluates conditions. + * - 'error_status': Triggers when is_error is true (simple boolean check) + * - 'content_match': Triggers when content matches a regex pattern + * - 'token_threshold': Triggers when token count exceeds threshold + */ +export type TriggerMode = 'error_status' | 'content_match' | 'token_threshold'; + +/** + * Token type for threshold triggers. + */ +export type TriggerTokenType = 'input' | 'output' | 'total'; + +/** + * Notification trigger configuration. + * Defines when notifications should be generated. + */ +export interface NotificationTrigger { + /** Unique identifier for this trigger */ + id: string; + /** Human-readable name for this trigger */ + name: string; + /** Whether this trigger is enabled */ + enabled: boolean; + /** Content type to match */ + contentType: TriggerContentType; + /** For tool_use/tool_result: specific tool name to match */ + toolName?: TriggerToolName; + /** Whether this is a built-in trigger (cannot be deleted) */ + isBuiltin?: boolean; + /** Regex patterns to IGNORE (skip notification if content matches any of these) */ + ignorePatterns?: string[]; + + // === Discriminated Union Mode === + /** Trigger evaluation mode */ + mode: TriggerMode; + + // === Mode: error_status === + /** For error_status mode: always triggers on is_error=true */ + requireError?: boolean; + + // === Mode: content_match === + /** For content_match mode: field to match against */ + matchField?: TriggerMatchField; + /** For content_match mode: regex pattern to match */ + matchPattern?: string; + + // === Mode: token_threshold === + /** For token_threshold mode: minimum token count to trigger */ + tokenThreshold?: number; + /** For token_threshold mode: which token type to check */ + tokenType?: TriggerTokenType; + + // === Repository Scope === + /** If set, this trigger only applies to these repository group IDs */ + repositoryIds?: string[]; + + // === Display === + /** Color for notification dot and navigation highlight (preset key or hex string) */ + color?: TriggerColor; +} + +export interface GeneralConfig { + launchAtLogin: boolean; + showDockIcon: boolean; + theme: 'dark' | 'light' | 'system'; + defaultTab: 'dashboard' | 'last-session'; +} + +export interface DisplayConfig { + showTimestamps: boolean; + compactMode: boolean; + syntaxHighlighting: boolean; +} + +export interface SessionsConfig { + pinnedSessions: Record; +} + +export interface AppConfig { + notifications: NotificationConfig; + general: GeneralConfig; + display: DisplayConfig; + sessions: SessionsConfig; +} + +// Config section keys for type-safe updates +export type ConfigSection = keyof AppConfig; + +// =========================================================================== +// Default Configuration +// =========================================================================== + +// Default regex patterns for common non-actionable notifications +const DEFAULT_IGNORED_REGEX = ["The user doesn't want to proceed with this tool use\\."]; + +const DEFAULT_CONFIG: AppConfig = { + notifications: { + enabled: true, + soundEnabled: true, + ignoredRegex: [...DEFAULT_IGNORED_REGEX], + ignoredRepositories: [], + snoozedUntil: null, + snoozeMinutes: 30, + includeSubagentErrors: true, + triggers: DEFAULT_TRIGGERS, + }, + general: { + launchAtLogin: false, + showDockIcon: true, + theme: 'dark', + defaultTab: 'dashboard', + }, + display: { + showTimestamps: true, + compactMode: false, + syntaxHighlighting: true, + }, + sessions: { + pinnedSessions: {}, + }, +}; + +// =========================================================================== +// ConfigManager Class +// =========================================================================== + +export class ConfigManager { + private config: AppConfig; + private readonly configPath: string; + private static instance: ConfigManager | null = null; + private triggerManager: TriggerManager; + + constructor(configPath?: string) { + this.configPath = configPath ?? DEFAULT_CONFIG_PATH; + this.config = this.loadConfig(); + this.triggerManager = new TriggerManager(this.config.notifications.triggers, () => + this.saveConfig() + ); + } + + // =========================================================================== + // Singleton Pattern + // =========================================================================== + + /** + * Gets the singleton instance of ConfigManager. + */ + static getInstance(): ConfigManager { + ConfigManager.instance ??= new ConfigManager(); + return ConfigManager.instance; + } + + /** + * Resets the singleton instance (useful for testing). + */ + static resetInstance(): void { + ConfigManager.instance = null; + } + + // =========================================================================== + // Config Loading & Saving + // =========================================================================== + + /** + * Loads configuration from disk. + * Returns default config if file doesn't exist or is invalid. + */ + private loadConfig(): AppConfig { + try { + if (!fs.existsSync(this.configPath)) { + logger.info('No config file found, using defaults'); + return this.deepClone(DEFAULT_CONFIG); + } + + const content = fs.readFileSync(this.configPath, 'utf8'); + const parsed = JSON.parse(content) as Partial; + + // Merge with defaults to ensure all fields exist + return this.mergeWithDefaults(parsed); + } catch (error) { + logger.error('Error loading config, using defaults:', error); + return this.deepClone(DEFAULT_CONFIG); + } + } + + /** + * Saves the current configuration to disk. + */ + private saveConfig(): void { + try { + this.persistConfig(this.config); + logger.info('Config saved'); + } catch (error) { + logger.error('Error saving config:', error); + } + } + + /** + * Persists configuration to the canonical path. + */ + private persistConfig(config: AppConfig): void { + const configDir = path.dirname(this.configPath); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); + } + + const content = JSON.stringify(config, null, 2); + fs.writeFileSync(this.configPath, content, 'utf8'); + } + + /** + * Merges loaded config with defaults to ensure all fields exist. + * Special handling for triggers array to preserve existing triggers + * and add any missing builtin triggers. + */ + private mergeWithDefaults(loaded: Partial): AppConfig { + const loadedNotifications = loaded.notifications ?? ({} as Partial); + const loadedTriggers = loadedNotifications.triggers ?? []; + + // Merge triggers: preserve existing triggers, add missing builtin ones + const mergedTriggers = TriggerManager.mergeTriggers(loadedTriggers, DEFAULT_TRIGGERS); + + return { + notifications: { + ...DEFAULT_CONFIG.notifications, + ...loadedNotifications, + triggers: mergedTriggers, + }, + general: { + ...DEFAULT_CONFIG.general, + ...(loaded.general ?? {}), + }, + display: { + ...DEFAULT_CONFIG.display, + ...(loaded.display ?? {}), + }, + sessions: { + ...DEFAULT_CONFIG.sessions, + ...(loaded.sessions ?? {}), + }, + }; + } + + /** + * Deep clones an object. + */ + private deepClone(obj: T): T { + return JSON.parse(JSON.stringify(obj)) as T; + } + + // =========================================================================== + // Config Access + // =========================================================================== + + /** + * Gets the full configuration object. + */ + getConfig(): AppConfig { + return this.deepClone(this.config); + } + + /** + * Gets the configuration file path. + */ + getConfigPath(): string { + return this.configPath; + } + + // =========================================================================== + // Config Updates + // =========================================================================== + + /** + * Updates a section of the configuration. + * @param section - The config section to update ('notifications', 'general', 'display') + * @param data - Partial data to merge into the section + */ + updateConfig(section: K, data: Partial): AppConfig { + this.config[section] = { + ...this.config[section], + ...data, + }; + this.saveConfig(); + return this.getConfig(); + } + + // =========================================================================== + // Notification Ignore Regex Management + // =========================================================================== + + /** + * Adds a regex pattern to the ignore list. + * Validates pattern for safety to prevent ReDoS attacks. + * @param pattern - Regex pattern string to add + * @returns Updated config + */ + addIgnoreRegex(pattern: string): AppConfig { + if (!pattern || pattern.trim().length === 0) { + return this.getConfig(); + } + + const trimmedPattern = pattern.trim(); + + // Validate regex pattern (includes ReDoS protection) + const validation = validateRegexPattern(trimmedPattern); + if (!validation.valid) { + logger.error(`ConfigManager: Invalid regex pattern: ${validation.error ?? 'Unknown error'}`); + return this.getConfig(); + } + + // Check for duplicates + if (this.config.notifications.ignoredRegex.includes(trimmedPattern)) { + return this.getConfig(); + } + + this.config.notifications.ignoredRegex.push(trimmedPattern); + this.saveConfig(); + return this.getConfig(); + } + + /** + * Removes a regex pattern from the ignore list. + * @param pattern - Regex pattern string to remove + * @returns Updated config + */ + removeIgnoreRegex(pattern: string): AppConfig { + const index = this.config.notifications.ignoredRegex.indexOf(pattern); + if (index !== -1) { + this.config.notifications.ignoredRegex.splice(index, 1); + this.saveConfig(); + } + return this.getConfig(); + } + + // =========================================================================== + // Notification Ignore Repository Management + // =========================================================================== + + /** + * Adds a repository to the ignore list. + * @param repositoryId - Repository group ID to add + * @returns Updated config + */ + addIgnoreRepository(repositoryId: string): AppConfig { + if (!repositoryId || repositoryId.trim().length === 0) { + return this.getConfig(); + } + + const trimmedRepositoryId = repositoryId.trim(); + + // Check for duplicates + if (this.config.notifications.ignoredRepositories.includes(trimmedRepositoryId)) { + return this.getConfig(); + } + + this.config.notifications.ignoredRepositories.push(trimmedRepositoryId); + this.saveConfig(); + return this.getConfig(); + } + + /** + * Removes a repository from the ignore list. + * @param repositoryId - Repository group ID to remove + * @returns Updated config + */ + removeIgnoreRepository(repositoryId: string): AppConfig { + const index = this.config.notifications.ignoredRepositories.indexOf(repositoryId); + if (index !== -1) { + this.config.notifications.ignoredRepositories.splice(index, 1); + this.saveConfig(); + } + return this.getConfig(); + } + + // =========================================================================== + // Trigger Management (delegated to TriggerManager) + // =========================================================================== + + /** + * Adds a new notification trigger. + * @param trigger - The trigger configuration to add + * @returns Updated config + */ + addTrigger(trigger: NotificationTrigger): AppConfig { + this.config.notifications.triggers = this.triggerManager.add(trigger); + return this.deepClone(this.config); + } + + /** + * Updates an existing notification trigger. + * @param triggerId - ID of the trigger to update + * @param updates - Partial trigger configuration to apply + * @returns Updated config + */ + updateTrigger(triggerId: string, updates: Partial): AppConfig { + this.config.notifications.triggers = this.triggerManager.update(triggerId, updates); + return this.deepClone(this.config); + } + + /** + * Removes a notification trigger. + * Built-in triggers cannot be removed. + * @param triggerId - ID of the trigger to remove + * @returns Updated config + */ + removeTrigger(triggerId: string): AppConfig { + this.config.notifications.triggers = this.triggerManager.remove(triggerId); + return this.deepClone(this.config); + } + + /** + * Gets all notification triggers. + * @returns Array of notification triggers + */ + getTriggers(): NotificationTrigger[] { + return this.triggerManager.getAll(); + } + + /** + * Gets enabled notification triggers only. + * @returns Array of enabled notification triggers + */ + getEnabledTriggers(): NotificationTrigger[] { + return this.triggerManager.getEnabled(); + } + + // =========================================================================== + // Snooze Management + // =========================================================================== + + /** + * Sets the snooze period for notifications. + * Alias: snooze() + * @param minutes - Number of minutes to snooze (uses config default if not provided) + * @returns Updated config + */ + setSnooze(minutes?: number): AppConfig { + const snoozeMinutes = minutes ?? this.config.notifications.snoozeMinutes; + const snoozedUntil = Date.now() + snoozeMinutes * 60 * 1000; + + this.config.notifications.snoozedUntil = snoozedUntil; + this.saveConfig(); + + logger.info( + `ConfigManager: Notifications snoozed until ${new Date(snoozedUntil).toISOString()}` + ); + return this.getConfig(); + } + + /** + * Alias for setSnooze() for convenience. + */ + snooze(minutes?: number): AppConfig { + return this.setSnooze(minutes); + } + + /** + * Clears the snooze period, re-enabling notifications. + * @returns Updated config + */ + clearSnooze(): AppConfig { + this.config.notifications.snoozedUntil = null; + this.saveConfig(); + + logger.info('Snooze cleared'); + return this.getConfig(); + } + + /** + * Checks if notifications are currently snoozed. + * Automatically clears expired snooze. + * @returns true if currently snoozed, false otherwise + */ + isSnoozed(): boolean { + const snoozedUntil = this.config.notifications.snoozedUntil; + + if (snoozedUntil === null) { + return false; + } + + // Check if snooze has expired + if (Date.now() >= snoozedUntil) { + // Auto-clear expired snooze + this.config.notifications.snoozedUntil = null; + this.saveConfig(); + return false; + } + + return true; + } + + // =========================================================================== + // Session Pin Management + // =========================================================================== + + /** + * Pins a session for a project. + * @param projectId - The project ID + * @param sessionId - The session ID to pin + */ + pinSession(projectId: string, sessionId: string): void { + const pins = this.config.sessions.pinnedSessions[projectId] ?? []; + + // Check for duplicates + if (pins.some((p) => p.sessionId === sessionId)) { + return; + } + + // Prepend (most recently pinned first) + this.config.sessions.pinnedSessions[projectId] = [{ sessionId, pinnedAt: Date.now() }, ...pins]; + this.saveConfig(); + } + + /** + * Unpins a session for a project. + * @param projectId - The project ID + * @param sessionId - The session ID to unpin + */ + unpinSession(projectId: string, sessionId: string): void { + const pins = this.config.sessions.pinnedSessions[projectId]; + if (!pins) return; + + this.config.sessions.pinnedSessions[projectId] = pins.filter((p) => p.sessionId !== sessionId); + + // Clean up empty arrays + if (this.config.sessions.pinnedSessions[projectId].length === 0) { + delete this.config.sessions.pinnedSessions[projectId]; + } + + this.saveConfig(); + } + + // =========================================================================== + // Utility Methods + // =========================================================================== + + /** + * Resets configuration to defaults. + * @returns Updated config + */ + resetToDefaults(): AppConfig { + this.config = this.deepClone(DEFAULT_CONFIG); + this.triggerManager.setTriggers(this.config.notifications.triggers); + this.saveConfig(); + logger.info('Config reset to defaults'); + return this.getConfig(); + } + + /** + * Reloads configuration from disk. + * Useful if config was modified externally. + * @returns Updated config + */ + reload(): AppConfig { + this.config = this.loadConfig(); + this.triggerManager.setTriggers(this.config.notifications.triggers); + logger.info('Config reloaded from disk'); + return this.getConfig(); + } +} + +// =========================================================================== +// Singleton Export +// =========================================================================== + +/** Singleton instance for convenience */ +export const configManager = ConfigManager.getInstance(); diff --git a/src/main/services/infrastructure/DataCache.ts b/src/main/services/infrastructure/DataCache.ts new file mode 100644 index 00000000..250039bd --- /dev/null +++ b/src/main/services/infrastructure/DataCache.ts @@ -0,0 +1,356 @@ +/** + * DataCache service - LRU cache for parsed session data. + * + * Responsibilities: + * - Cache parsed SessionDetail objects to avoid re-parsing + * - LRU eviction policy with configurable max size + * - TTL-based expiration + * - Provide cache invalidation for file changes + */ + +import { type SessionDetail, type SubagentDetail } from '@main/types'; +import { createLogger } from '@shared/utils/logger'; + +const logger = createLogger('Service:DataCache'); + +interface CacheEntry { + value: T; + + timestamp: number; + version: number; // Cache schema version +} + +// Union type for cached values + +type CachedValue = SessionDetail | SubagentDetail; + +export class DataCache { + private cache: Map>; + private maxSize: number; + private ttl: number; // Time-to-live in milliseconds + private enabled: boolean; // Whether caching is enabled + private static readonly CURRENT_VERSION = 2; // Increment when cache structure changes + + constructor(maxSize: number = 50, ttlMinutes: number = 10, enabled: boolean = true) { + this.cache = new Map(); + this.maxSize = maxSize; + this.ttl = ttlMinutes * 60 * 1000; + this.enabled = enabled; + } + + /** + * Enable or disable caching. + */ + setEnabled(enabled: boolean): void { + this.enabled = enabled; + if (!enabled) { + // Clear cache when disabling + this.cache.clear(); + } + } + + /** + * Check if caching is enabled. + */ + isEnabled(): boolean { + return this.enabled; + } + + // =========================================================================== + // Cache Operations + // =========================================================================== + + /** + * Gets a cached session detail. + * @param key - Cache key in format "projectId/sessionId" + * @returns The cached SessionDetail, or undefined if not found or expired + */ + get(key: string): SessionDetail | undefined { + if (!this.enabled) { + return undefined; + } + + const entry = this.cache.get(key); + + if (!entry) { + return undefined; + } + + // Check if entry version is outdated + if (entry.version !== DataCache.CURRENT_VERSION) { + logger.info(`DataCache: Invalidating outdated cache entry (v${entry.version}): ${key}`); + this.cache.delete(key); + return undefined; + } + + // Check if entry has expired + const now = Date.now(); + if (now - entry.timestamp > this.ttl) { + this.cache.delete(key); + return undefined; + } + + // Move to end (mark as recently used) + this.cache.delete(key); + this.cache.set(key, entry); + + return entry.value as SessionDetail; + } + + /** + * Gets a cached subagent detail. + * @param key - Cache key in format "subagent-projectId-sessionId-subagentId" + * @returns The cached SubagentDetail, or undefined if not found or expired + */ + getSubagent(key: string): SubagentDetail | undefined { + if (!this.enabled) { + return undefined; + } + + const entry = this.cache.get(key); + + if (!entry) { + return undefined; + } + + // Check if entry version is outdated + if (entry.version !== DataCache.CURRENT_VERSION) { + logger.info( + `DataCache: Invalidating outdated subagent cache entry (v${entry.version}): ${key}` + ); + this.cache.delete(key); + return undefined; + } + + // Check if entry has expired + const now = Date.now(); + if (now - entry.timestamp > this.ttl) { + this.cache.delete(key); + return undefined; + } + + // Move to end (mark as recently used) + this.cache.delete(key); + this.cache.set(key, entry); + + return entry.value as SubagentDetail; + } + + /** + * Internal method to set a value in the cache. + * Handles LRU eviction and cache entry creation. + */ + private setInternal(key: string, value: CachedValue): void { + if (!this.enabled) { + return; + } + + // If at max size, remove least recently used (first entry) + if (this.cache.size >= this.maxSize) { + const firstKey = this.cache.keys().next().value; + if (firstKey) { + this.cache.delete(firstKey); + } + } + + this.cache.set(key, { + value, + timestamp: Date.now(), + version: DataCache.CURRENT_VERSION, + }); + } + + /** + * Sets a value in the cache. + * @param key - Cache key in format "projectId/sessionId" + * @param value - The SessionDetail to cache + */ + set(key: string, value: SessionDetail): void { + this.setInternal(key, value); + } + + /** + * Sets a subagent detail value in the cache. + * @param key - Cache key in format "subagent-projectId-sessionId-subagentId" + * @param value - The SubagentDetail to cache + */ + setSubagent(key: string, value: SubagentDetail): void { + this.setInternal(key, value); + } + + /** + * Checks if a key exists in the cache and is not expired. + * @param key - Cache key to check + * @returns true if key exists and is valid, false otherwise + */ + has(key: string): boolean { + return this.get(key) !== undefined; + } + + // =========================================================================== + // Key Building + // =========================================================================== + + /** + * Build a cache key from project and session IDs. + */ + static buildKey(projectId: string, sessionId: string): string { + return `${projectId}/${sessionId}`; + } + + /** + * Parse a cache key into project and session IDs. + */ + static parseKey(key: string): { projectId: string; sessionId: string } | null { + const parts = key.split('/'); + if (parts.length !== 2) return null; + return { projectId: parts[0], sessionId: parts[1] }; + } + + // =========================================================================== + // Invalidation + // =========================================================================== + + /** + * Invalidates a specific cache entry. + * @param key - Cache key to invalidate + */ + invalidate(key: string): void { + this.cache.delete(key); + } + + /** + * Invalidates a cache entry by project and session IDs. + */ + invalidateSession(projectId: string, sessionId: string): void { + this.invalidate(DataCache.buildKey(projectId, sessionId)); + this.invalidateSubagentSession(projectId, sessionId); + } + + /** + * Invalidates all cached subagent details for a session. + */ + invalidateSubagentSession(projectId: string, sessionId: string): void { + const prefix = `subagent-${projectId}-${sessionId}-`; + const keysToDelete: string[] = []; + for (const key of this.cache.keys()) { + if (key.startsWith(prefix)) { + keysToDelete.push(key); + } + } + for (const key of keysToDelete) { + this.cache.delete(key); + } + } + + /** + * Invalidates all cache entries for a project. + * @param projectId - The project ID + */ + invalidateProject(projectId: string): void { + const keysToDelete: string[] = []; + + for (const key of this.cache.keys()) { + if (key.startsWith(`${projectId}/`)) { + keysToDelete.push(key); + } + } + + for (const key of keysToDelete) { + this.cache.delete(key); + } + } + + /** + * Clears the entire cache. + */ + clear(): void { + this.cache.clear(); + } + + // =========================================================================== + // Cache Management + // =========================================================================== + + /** + * Gets current cache size. + * @returns Number of entries in the cache + */ + size(): number { + return this.cache.size; + } + + /** + * Gets cache statistics. + * @returns Object with cache stats + */ + stats(): { + size: number; + maxSize: number; + ttlMinutes: number; + keys: string[]; + } { + return { + size: this.cache.size, + maxSize: this.maxSize, + ttlMinutes: this.ttl / 60000, + keys: Array.from(this.cache.keys()), + }; + } + + /** + * Removes expired and outdated entries from the cache. + * Should be called periodically to prevent memory bloat. + */ + cleanExpired(): number { + const now = Date.now(); + const keysToDelete: string[] = []; + + for (const [key, entry] of this.cache.entries()) { + // Remove if expired OR outdated version + if (now - entry.timestamp > this.ttl || entry.version !== DataCache.CURRENT_VERSION) { + keysToDelete.push(key); + } + } + + for (const key of keysToDelete) { + this.cache.delete(key); + } + + if (keysToDelete.length > 0) { + logger.info(`DataCache: Cleaned ${keysToDelete.length} expired/outdated entries`); + } + + return keysToDelete.length; + } + + /** + * Starts automatic cleanup of expired entries. + * @param intervalMinutes - How often to run cleanup (default: 5 minutes) + * @returns Timer handle that can be used to stop cleanup + */ + startAutoCleanup(intervalMinutes: number = 5): NodeJS.Timeout { + const intervalMs = intervalMinutes * 60 * 1000; + return setInterval(() => { + this.cleanExpired(); + }, intervalMs); + } + + /** + * Gets all cached session IDs for a project. + */ + getProjectSessionIds(projectId: string): string[] { + const sessionIds: string[] = []; + + for (const key of this.cache.keys()) { + if (key.startsWith(`${projectId}/`)) { + const parsed = DataCache.parseKey(key); + if (parsed) { + sessionIds.push(parsed.sessionId); + } + } + } + + return sessionIds; + } +} diff --git a/src/main/services/infrastructure/FileWatcher.ts b/src/main/services/infrastructure/FileWatcher.ts new file mode 100644 index 00000000..3263db84 --- /dev/null +++ b/src/main/services/infrastructure/FileWatcher.ts @@ -0,0 +1,697 @@ +/** + * FileWatcher service - Watches for changes in Claude Code project files. + * + * Responsibilities: + * - Watch ~/.claude/projects/ directory for session changes + * - Watch ~/.claude/todos/ directory for todo changes + * - Detect new/modified/deleted files + * - Emit events to notify renderer process + * - Invalidate cache entries when files change + * - Detect errors in changed session files and notify NotificationManager + */ + +import { type FileChangeEvent, type ParsedMessage } from '@main/types'; +import { parseJsonlFile, parseJsonlLine } from '@main/utils/jsonl'; +import { getProjectsBasePath, getTodosBasePath } from '@main/utils/pathDecoder'; +import { createLogger } from '@shared/utils/logger'; +import { EventEmitter } from 'events'; +import * as fs from 'fs'; +import * as path from 'path'; + +import { projectPathResolver } from '../discovery/ProjectPathResolver'; +import { errorDetector } from '../error/ErrorDetector'; + +import { ConfigManager } from './ConfigManager'; +import { type DataCache } from './DataCache'; +import { type NotificationManager } from './NotificationManager'; + +const logger = createLogger('Service:FileWatcher'); + +/** Debounce window for file change events */ +const DEBOUNCE_MS = 100; +/** Retry delay when watched directories are unavailable or watcher errors occur */ +const WATCHER_RETRY_MS = 2000; +/** Interval for periodic catch-up scan to detect missed fs.watch events */ +const CATCH_UP_INTERVAL_MS = 30_000; +/** Only catch-up scan files modified within this window */ +const CATCH_UP_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour + +interface AppendedParseResult { + messages: ParsedMessage[]; + parsedLineCount: number; + consumedBytes: number; +} + +interface ActiveSessionFile { + projectId: string; + sessionId: string; + subagentId?: string; +} + +export class FileWatcher extends EventEmitter { + private projectsWatcher: fs.FSWatcher | null = null; + private todosWatcher: fs.FSWatcher | null = null; + private retryTimer: NodeJS.Timeout | null = null; + private projectsPath: string; + private todosPath: string; + private dataCache: DataCache; + private notificationManager: NotificationManager | null = null; + private isWatching: boolean = false; + private debounceTimers = new Map(); + /** Track last processed line count per file for incremental error detection */ + private lastProcessedLineCount = new Map(); + /** Track last processed file size in bytes for append-only parsing optimization */ + private lastProcessedSize = new Map(); + /** Active session files tracked for periodic catch-up scan */ + private activeSessionFiles = new Map(); + /** Timer for periodic catch-up scan */ + private catchUpTimer: NodeJS.Timeout | null = null; + /** Files currently being processed (concurrency guard) */ + private processingInProgress = new Set(); + /** Files that need reprocessing after current processing completes */ + private pendingReprocess = new Set(); + + constructor(dataCache: DataCache, projectsPath?: string, todosPath?: string) { + super(); + this.projectsPath = projectsPath ?? getProjectsBasePath(); + this.todosPath = todosPath ?? getTodosBasePath(); + this.dataCache = dataCache; + } + + /** + * Sets the NotificationManager for error detection integration. + * Must be called before start() to enable error notifications. + */ + setNotificationManager(manager: NotificationManager): void { + this.notificationManager = manager; + } + + // =========================================================================== + // Watcher Control + // =========================================================================== + + /** + * Starts watching the projects and todos directories. + */ + start(): void { + if (this.isWatching) { + logger.warn('Already watching'); + return; + } + + this.isWatching = true; + this.ensureWatchers(); + this.startCatchUpTimer(); + } + + /** + * Stops all watchers. + */ + stop(): void { + this.isWatching = false; + + if (this.retryTimer) { + clearTimeout(this.retryTimer); + this.retryTimer = null; + } + + if (this.projectsWatcher) { + this.projectsWatcher.close(); + this.projectsWatcher = null; + } + + if (this.todosWatcher) { + this.todosWatcher.close(); + this.todosWatcher = null; + } + + // Clear any pending debounce timers + for (const timer of this.debounceTimers.values()) { + clearTimeout(timer); + } + this.debounceTimers.clear(); + + // Clear catch-up timer + if (this.catchUpTimer) { + clearInterval(this.catchUpTimer); + this.catchUpTimer = null; + } + + // Clear error detection tracking + this.lastProcessedLineCount.clear(); + this.lastProcessedSize.clear(); + this.activeSessionFiles.clear(); + this.processingInProgress.clear(); + this.pendingReprocess.clear(); + + logger.info('Stopped watching'); + } + + /** + * Starts the projects directory watcher. + */ + private startProjectsWatcher(): void { + if (this.projectsWatcher) { + return; + } + + try { + if (!fs.existsSync(this.projectsPath)) { + logger.warn(`FileWatcher: Projects directory does not exist: ${this.projectsPath}`); + this.scheduleWatcherRetry(); + return; + } + + this.projectsWatcher = fs.watch( + this.projectsPath, + { recursive: true }, + (eventType, filename) => { + if (filename) { + this.handleProjectsChange(eventType, filename); + } + } + ); + this.attachWatcherRecovery(this.projectsWatcher, 'projects'); + + logger.info(`FileWatcher: Started watching projects at ${this.projectsPath}`); + } catch (error) { + logger.error('Error starting projects watcher:', error); + this.projectsWatcher = null; + this.scheduleWatcherRetry(); + } + } + + /** + * Starts the todos directory watcher. + */ + private startTodosWatcher(): void { + if (this.todosWatcher) { + return; + } + + try { + if (!fs.existsSync(this.todosPath)) { + // Todos directory may not exist yet - that's OK + this.scheduleWatcherRetry(); + return; + } + + this.todosWatcher = fs.watch(this.todosPath, (eventType, filename) => { + if (filename) { + this.handleTodosChange(eventType, filename); + } + }); + this.attachWatcherRecovery(this.todosWatcher, 'todos'); + + logger.info(`FileWatcher: Started watching todos at ${this.todosPath}`); + } catch (error) { + logger.error('Error starting todos watcher:', error); + this.todosWatcher = null; + this.scheduleWatcherRetry(); + } + } + + private ensureWatchers(): void { + if (!this.isWatching) { + return; + } + + this.startProjectsWatcher(); + this.startTodosWatcher(); + + if (!this.projectsWatcher || !this.todosWatcher) { + this.scheduleWatcherRetry(); + } + } + + private scheduleWatcherRetry(): void { + if (!this.isWatching || this.retryTimer) { + return; + } + + this.retryTimer = setTimeout(() => { + this.retryTimer = null; + this.ensureWatchers(); + }, WATCHER_RETRY_MS); + } + + private attachWatcherRecovery(watcher: fs.FSWatcher, watcherType: 'projects' | 'todos'): void { + watcher.on('error', (error) => { + logger.error(`FileWatcher: ${watcherType} watcher error:`, error); + if (watcherType === 'projects') { + this.projectsWatcher = null; + } else { + this.todosWatcher = null; + } + this.scheduleWatcherRetry(); + }); + + watcher.on('close', () => { + if (!this.isWatching) { + return; + } + if (watcherType === 'projects') { + this.projectsWatcher = null; + } else { + this.todosWatcher = null; + } + this.scheduleWatcherRetry(); + }); + } + + // =========================================================================== + // Event Handling + // =========================================================================== + + /** + * Handles file change events in the projects directory. + */ + private handleProjectsChange(eventType: string, filename: string): void { + try { + // Ignore non-JSONL files + if (!filename.endsWith('.jsonl')) { + return; + } + + // Debounce rapid changes to the same file + this.debounce(filename, () => this.processProjectsChange(eventType, filename)); + } catch (error) { + logger.error('Error handling projects change:', error); + } + } + + /** + * Process a debounced projects change. + */ + private processProjectsChange(eventType: string, filename: string): void { + const parts = filename.split(path.sep); + const projectId = parts[0]; + + if (!projectId) return; + + const fullPath = path.join(this.projectsPath, filename); + const fileExists = fs.existsSync(fullPath); + + // Determine change type + let changeType: FileChangeEvent['type']; + if (eventType === 'rename') { + changeType = fileExists ? 'add' : 'unlink'; + } else { + changeType = 'change'; + } + + // Parse session ID and check if it's a subagent + let sessionId: string | undefined; + let isSubagent = false; + + // Session file at project root: projectId/sessionId.jsonl + if (parts.length === 2) { + sessionId = path.basename(parts[1], '.jsonl'); + } + // Subagent file: projectId/sessionId/subagents/agent-hash.jsonl + else if (parts.length === 4 && parts[2] === 'subagents') { + sessionId = parts[1]; + isSubagent = true; + } + + if (sessionId) { + // Invalidate cache + this.dataCache.invalidateSession(projectId, sessionId); + projectPathResolver.invalidateProject(projectId); + if (changeType === 'unlink') { + this.clearErrorTracking(fullPath); + } + + // Emit event + const event: FileChangeEvent = { + type: changeType, + path: fullPath, + projectId, + sessionId, + isSubagent, + }; + + this.emit('file-change', event); + logger.info( + `FileWatcher: ${changeType} ${isSubagent ? 'subagent' : 'session'} - ${filename}` + ); + + // Detect errors in changed session files (not deleted files) + if (changeType !== 'unlink' && this.notificationManager) { + if (isSubagent) { + // Only process subagent files if config allows + const config = ConfigManager.getInstance().getConfig(); + if (config.notifications.includeSubagentErrors) { + const subagentFilename = path.basename(parts[3], '.jsonl'); + const subagentId = subagentFilename.replace(/^agent-/, ''); + this.activeSessionFiles.set(fullPath, { projectId, sessionId, subagentId }); + this.detectErrorsInSessionFile(projectId, sessionId, fullPath, subagentId).catch( + (err) => { + logger.error('Error detecting errors in subagent file:', err); + } + ); + } + } else { + this.activeSessionFiles.set(fullPath, { projectId, sessionId }); + this.detectErrorsInSessionFile(projectId, sessionId, fullPath).catch((err) => { + logger.error('Error detecting errors in session file:', err); + }); + } + } + } + } + + // =========================================================================== + // Error Detection + // =========================================================================== + + /** + * Detects errors in a session file and sends notifications. + * Uses incremental processing to only check new lines since last check. + */ + private async detectErrorsInSessionFile( + projectId: string, + sessionId: string, + filePath: string, + subagentId?: string + ): Promise { + if (!this.notificationManager) { + return; + } + + // Concurrency guard: if already processing this file, mark for reprocessing + if (this.processingInProgress.has(filePath)) { + this.pendingReprocess.add(filePath); + return; + } + + this.processingInProgress.add(filePath); + try { + // Get the last processed line count for this file + const lastLineCount = this.lastProcessedLineCount.get(filePath) ?? 0; + const lastSize = this.lastProcessedSize.get(filePath) ?? 0; + const fileStats = await fs.promises.stat(filePath); + const currentSize = fileStats.size; + + // Fast path: no size change means no new data + if (currentSize === lastSize && lastLineCount > 0) { + return; + } + + const canUseIncrementalAppend = lastLineCount > 0 && currentSize > lastSize; + let newMessages: ParsedMessage[] = []; + let currentLineCount: number; + let processedSize: number; + + if (canUseIncrementalAppend) { + const appended = await this.parseAppendedMessages(filePath, lastSize); + newMessages = appended.messages; + currentLineCount = lastLineCount + appended.parsedLineCount; + processedSize = lastSize + appended.consumedBytes; + } else { + // Fallback for first-read, truncation, or rewrite scenarios + const messages = await parseJsonlFile(filePath); + currentLineCount = messages.length; + newMessages = messages.slice(lastLineCount); + // Re-stat after full parse to capture bytes written during the parse + const postParseStats = await fs.promises.stat(filePath); + processedSize = postParseStats.size; + } + + // If no new lines, skip processing + if (currentLineCount <= lastLineCount) { + this.lastProcessedSize.set(filePath, processedSize); + return; + } + + // Detect errors in new messages + // Note: We pass the offset-adjusted line numbers to errorDetector + const errors = await errorDetector.detectErrors(newMessages, sessionId, projectId, filePath); + + // Adjust line numbers to account for the offset and annotate subagent errors + for (const error of errors) { + if (error.lineNumber !== undefined) { + error.lineNumber = error.lineNumber + lastLineCount; + } + if (subagentId) { + error.subagentId = subagentId; + } + } + + // Notify for each detected error + for (const error of errors) { + await this.notificationManager.addError(error); + } + + // Update the last processed line count + this.lastProcessedLineCount.set(filePath, currentLineCount); + this.lastProcessedSize.set(filePath, processedSize); + + if (errors.length > 0) { + logger.info(`FileWatcher: Detected ${errors.length} errors in ${filePath}`); + } + } catch (err) { + logger.error(`FileWatcher: Error processing session file for errors: ${filePath}`, err); + } finally { + this.processingInProgress.delete(filePath); + + // If a reprocess was requested while we were processing, run again + if (this.pendingReprocess.has(filePath)) { + this.pendingReprocess.delete(filePath); + this.detectErrorsInSessionFile(projectId, sessionId, filePath, subagentId).catch((e) => { + logger.error('Error during reprocessing of session file:', e); + }); + } + } + } + + /** + * Clears the error detection tracking for a specific file. + * Call this when a file is deleted or to force re-processing. + */ + clearErrorTracking(filePath: string): void { + this.lastProcessedLineCount.delete(filePath); + this.lastProcessedSize.delete(filePath); + this.activeSessionFiles.delete(filePath); + } + + /** + * Clears all error detection tracking. + */ + clearAllErrorTracking(): void { + this.lastProcessedLineCount.clear(); + this.lastProcessedSize.clear(); + this.activeSessionFiles.clear(); + } + + /** + * Parse only newly appended JSONL lines from the given byte offset. + */ + private async parseAppendedMessages( + filePath: string, + startOffset: number + ): Promise { + const parsedMessages: ParsedMessage[] = []; + const stream = fs.createReadStream(filePath, { start: startOffset, encoding: 'utf8' }); + + let buffer = ''; + let consumedBytes = 0; + let parsedLineCount = 0; + for await (const chunk of stream) { + buffer += chunk; + const lines = buffer.split('\n'); + buffer = lines.pop() ?? ''; + + for (const rawLine of lines) { + consumedBytes += Buffer.byteLength(`${rawLine}\n`, 'utf8'); + const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine; + if (!line.trim()) { + continue; + } + try { + const parsed = parseJsonlLine(line); + if (parsed) { + parsedMessages.push(parsed); + parsedLineCount++; + } + } catch { + // Ignore malformed appended lines; full parse path will recover on next rewrite. + } + } + } + + // Handle final line without trailing newline + if (buffer.trim()) { + try { + const parsed = parseJsonlLine(buffer); + if (parsed) { + parsedMessages.push(parsed); + parsedLineCount++; + consumedBytes += Buffer.byteLength(buffer, 'utf8'); + } + } catch { + // Keep offset pinned until this trailing partial becomes a complete line. + } + } + + return { + messages: parsedMessages, + parsedLineCount, + consumedBytes, + }; + } + + /** + * Handles file change events in the todos directory. + */ + private handleTodosChange(eventType: string, filename: string): void { + try { + // Only handle JSON files + if (!filename.endsWith('.json')) { + return; + } + + // Debounce rapid changes + this.debounce(`todos/${filename}`, () => this.processTodosChange(eventType, filename)); + } catch (error) { + logger.error('Error handling todos change:', error); + } + } + + /** + * Process a debounced todos change. + */ + private processTodosChange(eventType: string, filename: string): void { + // Session ID is the filename without extension + const sessionId = path.basename(filename, '.json'); + const fullPath = path.join(this.todosPath, filename); + const fileExists = fs.existsSync(fullPath); + + // Determine change type + let changeType: FileChangeEvent['type']; + if (eventType === 'rename') { + changeType = fileExists ? 'add' : 'unlink'; + } else { + changeType = 'change'; + } + + // Emit event (we don't have projectId for todos) + const event: FileChangeEvent = { + type: changeType, + path: fullPath, + sessionId, + isSubagent: false, + }; + + this.emit('todo-change', event); + logger.info(`FileWatcher: ${changeType} todo - ${filename}`); + } + + // =========================================================================== + // Catch-Up Scan + // =========================================================================== + + /** + * Starts the periodic catch-up timer to detect file growth missed by fs.watch. + * FSEvents on macOS can coalesce, delay, or drop events. This timer polls + * tracked active session files every CATCH_UP_INTERVAL_MS to detect unprocessed growth. + */ + private startCatchUpTimer(): void { + if (this.catchUpTimer) { + return; + } + + this.catchUpTimer = setInterval(() => { + this.runCatchUpScan().catch((err) => { + logger.error('Error during catch-up scan:', err); + }); + }, CATCH_UP_INTERVAL_MS); + } + + /** + * Scans active session files for unprocessed growth. + * Only checks files modified within the last hour. + */ + private async runCatchUpScan(): Promise { + if (!this.notificationManager || this.activeSessionFiles.size === 0) { + return; + } + + const now = Date.now(); + + for (const [filePath, info] of this.activeSessionFiles) { + try { + const stats = await fs.promises.stat(filePath); + + // Skip files not modified recently + if (now - stats.mtimeMs > CATCH_UP_MAX_AGE_MS) { + this.activeSessionFiles.delete(filePath); + continue; + } + + const lastSize = this.lastProcessedSize.get(filePath) ?? 0; + if (stats.size > lastSize) { + logger.info(`FileWatcher: Catch-up scan detected growth in ${filePath}`); + await this.detectErrorsInSessionFile( + info.projectId, + info.sessionId, + filePath, + info.subagentId + ); + } + } catch (err) { + // File may have been deleted between iterations + if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + this.activeSessionFiles.delete(filePath); + this.clearErrorTracking(filePath); + } else { + logger.error(`FileWatcher: Error during catch-up stat for ${filePath}:`, err); + } + } + } + } + + // =========================================================================== + // Debouncing + // =========================================================================== + + /** + * Debounce a function call for a specific key. + */ + private debounce(key: string, fn: () => void): void { + // Clear existing timer for this key + const existingTimer = this.debounceTimers.get(key); + if (existingTimer) { + clearTimeout(existingTimer); + } + + // Set new timer + const timer = setTimeout(() => { + this.debounceTimers.delete(key); + fn(); + }, DEBOUNCE_MS); + + this.debounceTimers.set(key, timer); + } + + // =========================================================================== + // Status + // =========================================================================== + + /** + * Returns whether the watcher is currently active. + */ + isActive(): boolean { + return this.isWatching; + } + + /** + * Returns watched paths. + */ + getWatchedPaths(): { projects: string; todos: string } { + return { + projects: this.projectsPath, + todos: this.todosPath, + }; + } +} diff --git a/src/main/services/infrastructure/NotificationManager.ts b/src/main/services/infrastructure/NotificationManager.ts new file mode 100644 index 00000000..c37f1f0b --- /dev/null +++ b/src/main/services/infrastructure/NotificationManager.ts @@ -0,0 +1,657 @@ +/** + * NotificationManager service - Manages native macOS notifications and error history. + * + * Responsibilities: + * - Store error history at ~/.claude/claude-code-context-notifications.json (max 100 entries) + * - Show native macOS notifications using Electron's Notification API + * - Implement throttling (5 seconds per unique error hash) + * - Respect config.notifications.enabled and snoozedUntil + * - Filter errors matching ignoredRegex patterns + * - Filter errors from ignoredProjects + * - Auto-prune notifications over 100 on startup + * - Emit IPC events to renderer: notification:new, notification:updated + */ + +import { createLogger } from '@shared/utils/logger'; +import { type BrowserWindow, Notification } from 'electron'; +import { EventEmitter } from 'events'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { type DetectedError } from '../error/ErrorMessageBuilder'; + +const logger = createLogger('Service:NotificationManager'); +import { projectPathResolver } from '../discovery/ProjectPathResolver'; +import { gitIdentityResolver } from '../parsing/GitIdentityResolver'; + +import { ConfigManager } from './ConfigManager'; + +// Re-export DetectedError for backward compatibility +export type { DetectedError }; + +/** + * Stored notification with read status. + */ +export interface StoredNotification extends DetectedError { + /** Whether the notification has been read */ + isRead: boolean; + /** When the notification was created (may differ from error timestamp) */ + createdAt: number; +} + +/** + * Pagination options for getNotifications. + */ +export interface GetNotificationsOptions { + /** Number of notifications to return */ + limit?: number; + /** Number of notifications to skip */ + offset?: number; +} + +/** + * Result of getNotifications call. + */ +export interface GetNotificationsResult { + /** Notifications for this page */ + notifications: StoredNotification[]; + /** Total number of notifications */ + total: number; + /** Total count (alias for IPC compatibility) */ + totalCount: number; + /** Number of unread notifications */ + unreadCount: number; + /** Whether there are more notifications to load */ + hasMore: boolean; +} + +// ============================================================================= +// Constants +// ============================================================================= + +/** Maximum number of notifications to store */ +const MAX_NOTIFICATIONS = 100; + +/** Throttle window in milliseconds (5 seconds) */ +const THROTTLE_MS = 5000; + +/** Path to notifications storage file */ +const NOTIFICATIONS_PATH = path.join( + os.homedir(), + '.claude', + 'claude-code-context-notifications.json' +); + +// ============================================================================= +// NotificationManager Class +// ============================================================================= + +export class NotificationManager extends EventEmitter { + private static instance: NotificationManager | null = null; + private notifications: StoredNotification[] = []; + private configManager: ConfigManager; + private mainWindow: BrowserWindow | null = null; + private throttleMap = new Map(); + private isInitialized: boolean = false; + + constructor(configManager?: ConfigManager) { + super(); + this.configManager = configManager ?? ConfigManager.getInstance(); + } + + // =========================================================================== + // Singleton Pattern + // =========================================================================== + + /** + * Gets the singleton instance of NotificationManager. + */ + static getInstance(): NotificationManager { + if (!NotificationManager.instance) { + NotificationManager.instance = new NotificationManager(); + NotificationManager.instance.initialize(); + } + return NotificationManager.instance; + } + + /** + * Resets the singleton instance (useful for testing). + */ + static resetInstance(): void { + NotificationManager.instance = null; + } + + /** + * Sets the singleton instance (useful for dependency injection). + */ + static setInstance(instance: NotificationManager): void { + NotificationManager.instance = instance; + } + + // =========================================================================== + // Initialization + // =========================================================================== + + /** + * Initializes the notification manager. + * Loads existing notifications and prunes if needed. + */ + initialize(): void { + if (this.isInitialized) { + return; + } + + this.loadNotifications(); + this.pruneNotifications(); + this.isInitialized = true; + + logger.info(`NotificationManager: Initialized with ${this.notifications.length} notifications`); + } + + /** + * Sets the main window reference for sending IPC events. + */ + setMainWindow(window: BrowserWindow | null): void { + this.mainWindow = window; + } + + // =========================================================================== + // Persistence + // =========================================================================== + + /** + * Loads notifications from disk. + */ + private loadNotifications(): void { + try { + if (fs.existsSync(NOTIFICATIONS_PATH)) { + const data = fs.readFileSync(NOTIFICATIONS_PATH, 'utf8'); + const parsed = JSON.parse(data) as unknown; + + if (Array.isArray(parsed)) { + this.notifications = parsed as StoredNotification[]; + } else { + logger.warn('Invalid notifications file format, starting fresh'); + this.notifications = []; + } + } + } catch (error) { + logger.error('Error loading notifications:', error); + this.notifications = []; + } + } + + /** + * Saves notifications to disk. + */ + private saveNotifications(): void { + try { + // Ensure directory exists + const dir = path.dirname(NOTIFICATIONS_PATH); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(NOTIFICATIONS_PATH, JSON.stringify(this.notifications, null, 2), 'utf8'); + } catch (error) { + logger.error('Error saving notifications:', error); + } + } + + /** + * Prunes notifications to MAX_NOTIFICATIONS entries. + * Removes oldest notifications first. + */ + private pruneNotifications(): void { + if (this.notifications.length > MAX_NOTIFICATIONS) { + // Sort by createdAt descending (newest first) + this.notifications.sort((a, b) => b.createdAt - a.createdAt); + + // Keep only the newest MAX_NOTIFICATIONS + const removed = this.notifications.length - MAX_NOTIFICATIONS; + this.notifications = this.notifications.slice(0, MAX_NOTIFICATIONS); + this.saveNotifications(); + + logger.info(`NotificationManager: Pruned ${removed} old notifications`); + } + } + + // =========================================================================== + // Error Filtering + // =========================================================================== + + /** + * Generates a unique hash for throttling based on projectId + message. + */ + private generateErrorHash(error: DetectedError): string { + return `${error.projectId}:${error.message}`; + } + + /** + * Checks if an error should be throttled. + */ + private isThrottled(error: DetectedError): boolean { + const hash = this.generateErrorHash(error); + const lastSeen = this.throttleMap.get(hash); + + if (lastSeen && Date.now() - lastSeen < THROTTLE_MS) { + return true; + } + + // Update throttle map + this.throttleMap.set(hash, Date.now()); + + // Clean up old entries periodically + this.cleanupThrottleMap(); + + return false; + } + + /** + * Cleans up old entries from the throttle map. + */ + private cleanupThrottleMap(): void { + const now = Date.now(); + const expiredThreshold = now - THROTTLE_MS * 2; + + const keysToDelete: string[] = []; + this.throttleMap.forEach((timestamp, hash) => { + if (timestamp < expiredThreshold) { + keysToDelete.push(hash); + } + }); + + for (const key of keysToDelete) { + this.throttleMap.delete(key); + } + } + + /** + * Checks if notifications are currently enabled based on config. + */ + private areNotificationsEnabled(): boolean { + const config = this.configManager.getConfig(); + + // Check if notifications are globally disabled + if (!config.notifications.enabled) { + return false; + } + + // Check if notifications are snoozed + if (config.notifications.snoozedUntil) { + if (Date.now() < config.notifications.snoozedUntil) { + return false; + } else { + // Snooze has expired, clear it + this.configManager.clearSnooze(); + } + } + + return true; + } + + /** + * Checks if an error matches any ignored regex patterns. + */ + private matchesIgnoredRegex(error: DetectedError): boolean { + const config = this.configManager.getConfig(); + const patterns = config.notifications.ignoredRegex; + + if (!patterns || patterns.length === 0) { + return false; + } + + for (const pattern of patterns) { + try { + const regex = new RegExp(pattern, 'i'); + if (regex.test(error.message)) { + return true; + } + } catch { + // Invalid regex pattern, skip + logger.warn(`NotificationManager: Invalid regex pattern: ${pattern}`); + } + } + + return false; + } + + /** + * Checks if the error is from an ignored repository. + * Resolves the project path to a repository ID and checks against ignored list. + */ + private async isFromIgnoredRepository(error: DetectedError): Promise { + const config = this.configManager.getConfig(); + const ignoredRepositories = config.notifications.ignoredRepositories; + + if (!ignoredRepositories || ignoredRepositories.length === 0) { + return false; + } + + // Resolve project ID to repository ID using canonical path resolution. + const projectPath = await projectPathResolver.resolveProjectPath(error.projectId, { + cwdHint: error.context.cwd, + }); + const identity = await gitIdentityResolver.resolveIdentity(projectPath); + + if (!identity) { + return false; + } + + return ignoredRepositories.includes(identity.id); + } + + /** + * Determines if an error should generate a notification. + */ + private async shouldNotify(error: DetectedError): Promise { + // Check if notifications are enabled + if (!this.areNotificationsEnabled()) { + return false; + } + + // Check if error is from an ignored repository + if (await this.isFromIgnoredRepository(error)) { + return false; + } + + // Check if error matches an ignored regex + if (this.matchesIgnoredRegex(error)) { + return false; + } + + // Check throttling (for native toast dedup only — storage is unconditional) + if (this.isThrottled(error)) { + return false; + } + + return true; + } + + // =========================================================================== + // Native Notifications + // =========================================================================== + + /** + * Shows a native macOS notification for an error. + */ + private showNativeNotification(error: DetectedError): void { + // Check if Notification is supported + if (!Notification.isSupported()) { + logger.warn('Native notifications not supported'); + return; + } + + const config = this.configManager.getConfig(); + + const notification = new Notification({ + title: 'Claude Code Error', + subtitle: error.context.projectName, + body: error.message.slice(0, 200), + sound: config.notifications.soundEnabled ? 'default' : undefined, + }); + + notification.on('click', () => { + // Focus app window + if (this.mainWindow && !this.mainWindow.isDestroyed()) { + this.mainWindow.show(); + this.mainWindow.focus(); + + // Send deep link to renderer + this.mainWindow.webContents.send('notification:clicked', error); + } + + // Emit event for other listeners + this.emit('notification-clicked', error); + }); + + notification.show(); + } + + // =========================================================================== + // IPC Event Emission + // =========================================================================== + + /** + * Emits a notification:new event to the renderer. + */ + private emitNewNotification(notification: StoredNotification): void { + if (this.mainWindow && !this.mainWindow.isDestroyed()) { + this.mainWindow.webContents.send('notification:new', notification); + } + + this.emit('notification-new', notification); + } + + /** + * Emits a notification:updated event to the renderer. + */ + private emitNotificationUpdated(): void { + if (this.mainWindow && !this.mainWindow.isDestroyed()) { + this.mainWindow.webContents.send('notification:updated', { + total: this.notifications.length, + unreadCount: this.getUnreadCountSync(), + }); + } + + this.emit('notification-updated', { + total: this.notifications.length, + unreadCount: this.getUnreadCountSync(), + }); + } + + // =========================================================================== + // Public API + // =========================================================================== + + /** + * Adds an error and shows a notification if enabled. + * @param error - The detected error to add + * @returns The stored notification, or null if filtered/throttled + */ + async addError(error: DetectedError): Promise { + // Deduplicate by toolUseId: the same tool call can appear in both the + // subagent JSONL file and the parent session JSONL (as a progress event). + // Keep the subagent-annotated version (with subagentId) when possible. + if (error.toolUseId) { + const existingIndex = this.notifications.findIndex((n) => n.toolUseId === error.toolUseId); + if (existingIndex !== -1) { + const existing = this.notifications[existingIndex]; + if (!existing.subagentId && error.subagentId) { + // Replace: prefer the subagent-annotated version + this.notifications.splice(existingIndex, 1); + } else { + // Already have a (better or equal) version — skip + return null; + } + } + } + + const storedNotification: StoredNotification = { + ...error, + isRead: false, + createdAt: Date.now(), + }; + + // Add to the beginning of the list (newest first) + this.notifications.unshift(storedNotification); + + // Prune if needed + this.pruneNotifications(); + + // Save to disk + this.saveNotifications(); + + // Emit new notification event + this.emitNewNotification(storedNotification); + // Emit authoritative counters (total/unread) so renderer badge stays in sync. + this.emitNotificationUpdated(); + + // Show native notification if enabled and not filtered + if (await this.shouldNotify(error)) { + this.showNativeNotification(error); + } + + return storedNotification; + } + + /** + * Gets a paginated list of notifications. + * @param options - Pagination options + * @returns Paginated notifications result + */ + async getNotifications(options?: GetNotificationsOptions): Promise { + const limit = options?.limit ?? 20; + const offset = options?.offset ?? 0; + + // Notifications are already sorted newest first + const notifications = this.notifications.slice(offset, offset + limit); + const total = this.notifications.length; + const hasMore = offset + notifications.length < total; + + return { + notifications, + total, + totalCount: total, + unreadCount: this.getUnreadCountSync(), + hasMore, + }; + } + + /** + * Marks a notification as read. + * @param id - The notification ID to mark as read + * @returns true if found and marked, false otherwise + */ + async markRead(id: string): Promise { + const notification = this.notifications.find((n) => n.id === id); + + if (!notification) { + return false; + } + + if (!notification.isRead) { + notification.isRead = true; + this.saveNotifications(); + this.emitNotificationUpdated(); + } + + return true; + } + + /** + * Marks all notifications as read. + * @returns true on success + */ + async markAllRead(): Promise { + let changed = false; + + for (const notification of this.notifications) { + if (!notification.isRead) { + notification.isRead = true; + changed = true; + } + } + + if (changed) { + this.saveNotifications(); + this.emitNotificationUpdated(); + } + + return true; + } + + /** + * Clears all notifications. + */ + clear(): void { + this.notifications = []; + this.saveNotifications(); + this.emitNotificationUpdated(); + } + + /** + * Clears all notifications (async version for IPC). + * @returns true on success + */ + async clearAll(): Promise { + this.clear(); + return true; + } + + /** + * Gets the count of unread notifications. + * @returns Number of unread notifications (Promise for IPC compatibility) + */ + async getUnreadCount(): Promise { + return this.notifications.filter((n) => !n.isRead).length; + } + + /** + * Gets the count of unread notifications (sync version). + * @returns Number of unread notifications + */ + getUnreadCountSync(): number { + return this.notifications.filter((n) => !n.isRead).length; + } + + /** + * Gets a specific notification by ID. + * @param id - The notification ID + * @returns The notification or undefined if not found + */ + getNotification(id: string): StoredNotification | undefined { + return this.notifications.find((n) => n.id === id); + } + + /** + * Deletes a specific notification. + * @param id - The notification ID to delete + * @returns true if found and deleted, false otherwise + */ + deleteNotification(id: string): boolean { + const index = this.notifications.findIndex((n) => n.id === id); + + if (index === -1) { + return false; + } + + this.notifications.splice(index, 1); + this.saveNotifications(); + this.emitNotificationUpdated(); + + return true; + } + + // =========================================================================== + // Stats + // =========================================================================== + + /** + * Gets statistics about notifications. + */ + getStats(): { + total: number; + unread: number; + byProject: Record; + bySource: Record; + } { + const byProject: Record = {}; + const bySource: Record = {}; + + for (const notification of this.notifications) { + const projectName = notification.context.projectName; + byProject[projectName] = (byProject[projectName] || 0) + 1; + + bySource[notification.source] = (bySource[notification.source] || 0) + 1; + } + + return { + total: this.notifications.length, + unread: this.getUnreadCountSync(), + byProject, + bySource, + }; + } +} diff --git a/src/main/services/infrastructure/TriggerManager.ts b/src/main/services/infrastructure/TriggerManager.ts new file mode 100644 index 00000000..3b781382 --- /dev/null +++ b/src/main/services/infrastructure/TriggerManager.ts @@ -0,0 +1,318 @@ +/** + * TriggerManager - Manages notification triggers. + * + * Handles CRUD operations for notification triggers including: + * - Adding, updating, and removing triggers + * - Validating trigger configurations (with ReDoS protection) + * - Managing builtin vs custom triggers + */ + +import { validateRegexPattern } from '@main/utils/regexValidation'; + +import type { NotificationTrigger } from './ConfigManager'; + +// =========================================================================== +// Types +// =========================================================================== + +export interface TriggerValidationResult { + valid: boolean; + errors: string[]; +} + +// =========================================================================== +// Default Triggers +// =========================================================================== + +/** + * Default built-in notification triggers. + */ +export const DEFAULT_TRIGGERS: NotificationTrigger[] = [ + { + id: 'builtin-tool-result-error', + name: 'Tool Result Error', + enabled: true, + contentType: 'tool_result', + mode: 'error_status', + requireError: true, + ignorePatterns: [ + "The user doesn't want to proceed with this tool use\\.", + '\\[Request interrupted by user for tool use\\]', + ], + isBuiltin: true, + color: 'red', + }, + { + id: 'builtin-bash-command', + name: '.env File Access Alert', + enabled: true, + contentType: 'tool_use', + toolName: 'Bash', + mode: 'content_match', + matchField: 'command', + matchPattern: '/.env', + isBuiltin: true, + color: 'red', + }, + { + id: 'builtin-high-token-usage', + name: 'High Token Usage', + enabled: true, + contentType: 'tool_result', + mode: 'token_threshold', + tokenThreshold: 8000, + tokenType: 'total', + color: 'orange', + isBuiltin: true, + }, +]; + +// =========================================================================== +// TriggerManager Class +// =========================================================================== + +export class TriggerManager { + private triggers: NotificationTrigger[]; + private readonly onSave: () => void; + + constructor(triggers: NotificationTrigger[], onSave: () => void) { + this.triggers = triggers; + this.onSave = onSave; + } + + // =========================================================================== + // CRUD Operations + // =========================================================================== + + /** + * Gets all notification triggers. + */ + getAll(): NotificationTrigger[] { + return this.deepClone(this.triggers); + } + + /** + * Gets enabled notification triggers only. + */ + getEnabled(): NotificationTrigger[] { + return this.deepClone(this.triggers.filter((t) => t.enabled)); + } + + /** + * Gets a trigger by ID. + */ + getById(triggerId: string): NotificationTrigger | undefined { + const trigger = this.triggers.find((t) => t.id === triggerId); + return trigger ? this.deepClone(trigger) : undefined; + } + + /** + * Adds a new notification trigger. + * @throws Error if trigger with same ID already exists + */ + add(trigger: NotificationTrigger): NotificationTrigger[] { + // Check if trigger with same ID already exists + if (this.triggers.some((t) => t.id === trigger.id)) { + throw new Error(`Trigger with ID "${trigger.id}" already exists`); + } + + // Validate trigger + const validation = this.validate(trigger); + if (!validation.valid) { + throw new Error(`Invalid trigger: ${validation.errors.join(', ')}`); + } + + this.triggers = [...this.triggers, trigger]; + this.onSave(); + return this.getAll(); + } + + /** + * Updates an existing notification trigger. + * @throws Error if trigger not found + */ + update(triggerId: string, updates: Partial): NotificationTrigger[] { + const index = this.triggers.findIndex((t) => t.id === triggerId); + + if (index === -1) { + throw new Error(`Trigger with ID "${triggerId}" not found`); + } + + // Extract allowedUpdates without isBuiltin (which cannot be changed) + const allowedUpdates = Object.fromEntries( + Object.entries(updates).filter(([key]) => key !== 'isBuiltin') + ) as Partial; + + const updated = { ...this.triggers[index], ...allowedUpdates }; + + // Ensure mode is set (for backward compatibility with old triggers) + if (!updated.mode) { + updated.mode = this.inferMode(updated); + } + + // Validate updated trigger + const validation = this.validate(updated); + if (!validation.valid) { + throw new Error(`Invalid trigger update: ${validation.errors.join(', ')}`); + } + + this.triggers = this.triggers.map((t, i) => (i === index ? updated : t)); + this.onSave(); + return this.getAll(); + } + + /** + * Infers trigger mode from trigger properties for backward compatibility. + */ + private inferMode( + trigger: Partial + ): 'error_status' | 'content_match' | 'token_threshold' { + if (trigger.requireError) return 'error_status'; + if (trigger.matchPattern || trigger.matchField) return 'content_match'; + if (trigger.tokenThreshold !== undefined) return 'token_threshold'; + return 'error_status'; // default fallback + } + + /** + * Removes a notification trigger. + * Built-in triggers cannot be removed. + * @throws Error if trigger not found or is builtin + */ + remove(triggerId: string): NotificationTrigger[] { + const trigger = this.triggers.find((t) => t.id === triggerId); + + if (!trigger) { + throw new Error(`Trigger with ID "${triggerId}" not found`); + } + + if (trigger.isBuiltin) { + throw new Error('Cannot remove built-in triggers. Disable them instead.'); + } + + this.triggers = this.triggers.filter((t) => t.id !== triggerId); + this.onSave(); + return this.getAll(); + } + + // =========================================================================== + // Validation + // =========================================================================== + + /** + * Validates a trigger configuration. + */ + validate(trigger: NotificationTrigger): TriggerValidationResult { + const errors: string[] = []; + + // Required fields + if (!trigger.id || trigger.id.trim() === '') { + errors.push('Trigger ID is required'); + } + + if (!trigger.name || trigger.name.trim() === '') { + errors.push('Trigger name is required'); + } + + if (!trigger.contentType) { + errors.push('Content type is required'); + } + + if (!trigger.mode) { + errors.push('Trigger mode is required'); + } + + // Mode-specific validation + if (trigger.mode === 'content_match') { + // matchField is required unless it's tool_use with "Any Tool" (no toolName) + // In that case, we match against the entire JSON input + if (!trigger.matchField && !(trigger.contentType === 'tool_use' && !trigger.toolName)) { + errors.push('Match field is required for content_match mode'); + } + // Validate regex pattern if provided (with ReDoS protection) + if (trigger.matchPattern) { + const validation = validateRegexPattern(trigger.matchPattern); + if (!validation.valid) { + errors.push(validation.error ?? 'Invalid regex pattern'); + } + } + } + + if (trigger.mode === 'token_threshold') { + if (trigger.tokenThreshold === undefined || trigger.tokenThreshold < 0) { + errors.push('Token threshold must be a non-negative number'); + } + if (!trigger.tokenType) { + errors.push('Token type is required for token_threshold mode'); + } + } + + // Validate ignore patterns (with ReDoS protection) + if (trigger.ignorePatterns) { + for (const pattern of trigger.ignorePatterns) { + const validation = validateRegexPattern(pattern); + if (!validation.valid) { + errors.push( + `Invalid ignore pattern "${pattern}": ${validation.error ?? 'Unknown error'}` + ); + } + } + } + + return { + valid: errors.length === 0, + errors, + }; + } + + // =========================================================================== + // Trigger Merging + // =========================================================================== + + /** + * Merges loaded triggers with default triggers. + * - Preserves all existing triggers (including user-modified builtin triggers) + * - Adds any missing builtin triggers from defaults + * - Removes deprecated builtin triggers that are no longer in defaults + */ + static mergeTriggers( + loaded: NotificationTrigger[], + defaults: NotificationTrigger[] = DEFAULT_TRIGGERS + ): NotificationTrigger[] { + // Get IDs of current builtin triggers + const builtinIds = new Set(defaults.filter((t) => t.isBuiltin).map((t) => t.id)); + + // Filter out deprecated builtin triggers (builtin triggers not in current defaults) + const filtered = loaded.filter((t) => !t.isBuiltin || builtinIds.has(t.id)); + + // Add any missing builtin triggers from defaults + for (const defaultTrigger of defaults) { + if (defaultTrigger.isBuiltin) { + const existsInFiltered = filtered.some((t) => t.id === defaultTrigger.id); + if (!existsInFiltered) { + filtered.push(defaultTrigger); + } + } + } + + return filtered; + } + + // =========================================================================== + // Internal Methods + // =========================================================================== + + /** + * Updates the internal triggers array. + * Used by ConfigManager when loading config. + */ + setTriggers(triggers: NotificationTrigger[]): void { + this.triggers = triggers; + } + + /** + * Deep clones an object. + */ + private deepClone(obj: T): T { + return JSON.parse(JSON.stringify(obj)) as T; + } +} diff --git a/src/main/services/infrastructure/index.ts b/src/main/services/infrastructure/index.ts new file mode 100644 index 00000000..8a928c7d --- /dev/null +++ b/src/main/services/infrastructure/index.ts @@ -0,0 +1,16 @@ +/** + * Infrastructure services - Core application infrastructure. + * + * Exports: + * - DataCache: LRU cache with TTL for parsed session data + * - FileWatcher: Watches for file changes with debouncing + * - ConfigManager: App configuration management + * - TriggerManager: Notification trigger management (used internally by ConfigManager) + * - NotificationManager: Notification handling and persistence + */ + +export * from './ConfigManager'; +export * from './DataCache'; +export * from './FileWatcher'; +export * from './NotificationManager'; +export * from './TriggerManager'; diff --git a/src/main/services/parsing/ClaudeMdReader.ts b/src/main/services/parsing/ClaudeMdReader.ts new file mode 100644 index 00000000..6c0ca859 --- /dev/null +++ b/src/main/services/parsing/ClaudeMdReader.ts @@ -0,0 +1,293 @@ +/** + * ClaudeMdReader service - Reads CLAUDE.md files and calculates token counts. + * + * Responsibilities: + * - Read CLAUDE.md files from various locations + * - Calculate character counts and estimate token counts + * - Handle file not found gracefully + * - Support tilde (~) expansion to home directory + */ + +import { encodePath } from '@main/utils/pathDecoder'; +import { countTokens } from '@main/utils/tokenizer'; +import { createLogger } from '@shared/utils/logger'; +import { app } from 'electron'; +import * as fs from 'fs'; +import * as path from 'path'; + +const logger = createLogger('Service:ClaudeMdReader'); + +// =========================================================================== +// Types +// =========================================================================== + +export interface ClaudeMdFileInfo { + path: string; + exists: boolean; + charCount: number; + estimatedTokens: number; // charCount / 4 +} + +export interface ClaudeMdReadResult { + files: Map; +} + +// =========================================================================== +// Helper Functions +// =========================================================================== + +/** + * Expands tilde (~) in a path to the actual home directory. + * @param filePath - Path that may contain ~ + * @returns Expanded path with ~ replaced by home directory + */ +function expandTilde(filePath: string): string { + if (filePath.startsWith('~')) { + const homeDir = app.getPath('home'); + return path.join(homeDir, filePath.slice(1)); + } + return filePath; +} + +// =========================================================================== +// Main Functions +// =========================================================================== + +/** + * Reads a single CLAUDE.md file and returns its info. + * @param filePath - Path to the CLAUDE.md file (supports ~ expansion) + * @returns ClaudeMdFileInfo with file details + */ +function readClaudeMdFile(filePath: string): ClaudeMdFileInfo { + const expandedPath = expandTilde(filePath); + + try { + if (!fs.existsSync(expandedPath)) { + return { + path: expandedPath, + exists: false, + charCount: 0, + estimatedTokens: 0, + }; + } + + const content = fs.readFileSync(expandedPath, 'utf8'); + const charCount = content.length; + const estimatedTokens = countTokens(content); + + return { + path: expandedPath, + exists: true, + charCount, + estimatedTokens, + }; + } catch (error) { + // Handle permission denied, file not readable, etc. + logger.error(`Error reading CLAUDE.md file at ${expandedPath}:`, error); + return { + path: expandedPath, + exists: false, + charCount: 0, + estimatedTokens: 0, + }; + } +} + +/** + * Reads all .md files in a directory and returns combined info. + * Used for project rules directory. + * @param dirPath - Path to the directory (supports ~ expansion) + * @returns ClaudeMdFileInfo with combined stats from all .md files + */ +function readDirectoryMdFiles(dirPath: string): ClaudeMdFileInfo { + const expandedPath = expandTilde(dirPath); + + try { + if (!fs.existsSync(expandedPath)) { + return { + path: expandedPath, + exists: false, + charCount: 0, + estimatedTokens: 0, + }; + } + + const stats = fs.statSync(expandedPath); + if (!stats.isDirectory()) { + return { + path: expandedPath, + exists: false, + charCount: 0, + estimatedTokens: 0, + }; + } + + const mdFiles = collectMdFiles(expandedPath); + + if (mdFiles.length === 0) { + return { + path: expandedPath, + exists: false, + charCount: 0, + estimatedTokens: 0, + }; + } + + let totalCharCount = 0; + const allContent: string[] = []; + + for (const filePath of mdFiles) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + totalCharCount += content.length; + allContent.push(content); + } catch { + // Skip files we can't read + continue; + } + } + + // Count tokens on combined content for accuracy + const estimatedTokens = countTokens(allContent.join('\n')); + + return { + path: expandedPath, + exists: true, + charCount: totalCharCount, + estimatedTokens, + }; + } catch (error) { + logger.error(`Error reading directory ${expandedPath}:`, error); + return { + path: expandedPath, + exists: false, + charCount: 0, + estimatedTokens: 0, + }; + } +} + +/** + * Recursively collect all .md files in a directory tree. + */ +function collectMdFiles(dir: string): string[] { + const mdFiles: string[] = []; + try { + const entries = fs.readdirSync(dir); + for (const entry of entries) { + const fullPath = path.join(dir, entry); + try { + const stats = fs.statSync(fullPath); + if (stats.isFile() && entry.endsWith('.md')) { + mdFiles.push(fullPath); + } else if (stats.isDirectory()) { + mdFiles.push(...collectMdFiles(fullPath)); + } + } catch { + continue; + } + } + } catch { + // Directory not readable + } + return mdFiles; +} + +/** + * Returns the platform-specific enterprise CLAUDE.md path. + */ +function getEnterprisePath(): string { + switch (process.platform) { + case 'win32': + return 'C:\\Program Files\\ClaudeCode\\CLAUDE.md'; + case 'darwin': + return '/Library/Application Support/ClaudeCode/CLAUDE.md'; + default: + return '/etc/claude-code/CLAUDE.md'; + } +} + +/** + * Reads auto memory MEMORY.md file for a project. + * Only reads the first 200 lines, matching Claude Code behavior. + */ +function readAutoMemoryFile(projectRoot: string): ClaudeMdFileInfo { + const expandedRoot = expandTilde(projectRoot); + const encoded = encodePath(expandedRoot); + const homeDir = app.getPath('home'); + const memoryPath = path.join(homeDir, '.claude', 'projects', encoded, 'memory', 'MEMORY.md'); + + try { + if (!fs.existsSync(memoryPath)) { + return { path: memoryPath, exists: false, charCount: 0, estimatedTokens: 0 }; + } + + const content = fs.readFileSync(memoryPath, 'utf8'); + // Only first 200 lines, matching Claude Code behavior + const lines = content.split('\n'); + const truncated = lines.slice(0, 200).join('\n'); + const charCount = truncated.length; + const estimatedTokens = countTokens(truncated); + + return { path: memoryPath, exists: true, charCount, estimatedTokens }; + } catch (error) { + logger.error(`Error reading auto memory at ${memoryPath}:`, error); + return { path: memoryPath, exists: false, charCount: 0, estimatedTokens: 0 }; + } +} + +/** + * Reads all potential CLAUDE.md locations for a project. + * @param projectRoot - The root directory of the project + * @returns ClaudeMdReadResult with Map of path -> ClaudeMdFileInfo + */ +export function readAllClaudeMdFiles(projectRoot: string): ClaudeMdReadResult { + const files = new Map(); + const expandedProjectRoot = expandTilde(projectRoot); + + // 1. Enterprise CLAUDE.md (platform-specific path) + const enterprisePath = getEnterprisePath(); + files.set('enterprise', readClaudeMdFile(enterprisePath)); + + // 2. User memory: ~/.claude/CLAUDE.md + const userMemoryPath = '~/.claude/CLAUDE.md'; + files.set('user', readClaudeMdFile(userMemoryPath)); + + // 3. Project memory: ${projectRoot}/CLAUDE.md + const projectMemoryPath = path.join(expandedProjectRoot, 'CLAUDE.md'); + files.set('project', readClaudeMdFile(projectMemoryPath)); + + // 4. Project memory alt: ${projectRoot}/.claude/CLAUDE.md + const projectMemoryAltPath = path.join(expandedProjectRoot, '.claude', 'CLAUDE.md'); + files.set('project-alt', readClaudeMdFile(projectMemoryAltPath)); + + // 5. Project rules: ${projectRoot}/.claude/rules/*.md + const projectRulesPath = path.join(expandedProjectRoot, '.claude', 'rules'); + files.set('project-rules', readDirectoryMdFiles(projectRulesPath)); + + // 6. Project local: ${projectRoot}/CLAUDE.local.md + const projectLocalPath = path.join(expandedProjectRoot, 'CLAUDE.local.md'); + files.set('project-local', readClaudeMdFile(projectLocalPath)); + + // 7. User rules: ~/.claude/rules/**/*.md + const homeDir = app.getPath('home'); + const userRulesPath = path.join(homeDir, '.claude', 'rules'); + files.set('user-rules', readDirectoryMdFiles(userRulesPath)); + + // 8. Auto memory: ~/.claude/projects//memory/MEMORY.md + files.set('auto-memory', readAutoMemoryFile(projectRoot)); + + return { files }; +} + +/** + * Reads a specific directory's CLAUDE.md file. + * Used for directory-specific CLAUDE.md detected from file reads. + * @param dirPath - Path to the directory (supports ~ expansion) + * @returns ClaudeMdFileInfo for the CLAUDE.md file in that directory + */ +export function readDirectoryClaudeMd(dirPath: string): ClaudeMdFileInfo { + const expandedDirPath = expandTilde(dirPath); + const claudeMdPath = path.join(expandedDirPath, 'CLAUDE.md'); + return readClaudeMdFile(claudeMdPath); +} diff --git a/src/main/services/parsing/GitIdentityResolver.ts b/src/main/services/parsing/GitIdentityResolver.ts new file mode 100644 index 00000000..80608dd6 --- /dev/null +++ b/src/main/services/parsing/GitIdentityResolver.ts @@ -0,0 +1,674 @@ +/** + * GitIdentityResolver service - Resolves git repository identity from project paths. + * + * Responsibilities: + * - Detect if a path is inside a git worktree vs main repository + * - Extract the main repository path from worktree's .git file + * - Get git remote URL for repository identity + * - Build consistent repository identity across all worktrees + * + * Git worktree detection: + * - Main repo: .git is a directory + * - Worktree: .git is a file containing "gitdir: /path/to/main/.git/worktrees/" + */ + +import { + AUTO_CLAUDE_DIR, + CCSWITCH_DIR, + CLAUDE_WORKTREES_DIR, + CONDUCTOR_DIR, + CURSOR_DIR, + TASKS_DIR, + TWENTYFIRST_DIR, + VIBE_KANBAN_DIR, + WORKSPACES_DIR, + WORKTREES_DIR, +} from '@main/constants/worktreePatterns'; +import { type RepositoryIdentity, type WorktreeSource } from '@main/types'; +import { createLogger } from '@shared/utils/logger'; +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; + +const logger = createLogger('Service:GitIdentityResolver'); + +class GitIdentityResolver { + /** + * Resolve repository identity from a project path. + * + * Algorithm: + * 1. Check if path/.git exists on filesystem + * 2. If .git is a file (worktree), read gitdir to find main repo + * 3. If .git is a directory (main repo), use it directly + * 4. Extract remote URL from .git/config + * 5. Build RepositoryIdentity with consistent ID + * 6. FALLBACK: If path doesn't exist, use heuristics based on path patterns + * + * @param projectPath - The filesystem path to check + * @returns RepositoryIdentity or null if not a git repo + */ + async resolveIdentity(projectPath: string): Promise { + try { + const gitPath = path.join(projectPath, '.git'); + + // First, try filesystem-based resolution + if (fs.existsSync(gitPath)) { + const stats = fs.statSync(gitPath); + + let mainGitDir: string; + + if (stats.isFile()) { + // This is a worktree - parse the .git file to find main repo + const gitFileContent = fs.readFileSync(gitPath, 'utf-8').trim(); + const gitDirMatch = /^gitdir:\s*(\S[^\r\n]*)$/m.exec(gitFileContent); + + if (!gitDirMatch) { + logger.warn(`Invalid .git file format at ${gitPath}`); + return this.resolveIdentityFromPath(projectPath); + } + + let worktreeGitDir = gitDirMatch[1].trim(); + + // Handle relative paths in gitdir (resolve relative to the .git file location) + if (!path.isAbsolute(worktreeGitDir)) { + worktreeGitDir = path.resolve(projectPath, worktreeGitDir); + } + + mainGitDir = this.extractMainGitDir(worktreeGitDir); + } else if (stats.isDirectory()) { + mainGitDir = gitPath; + } else { + return this.resolveIdentityFromPath(projectPath); + } + + // Normalize the path to handle symlinks (e.g., /tmp -> /private/var/folders) + // This ensures all worktrees of the same repo get the same ID + try { + mainGitDir = fs.realpathSync(mainGitDir); + } catch { + // If realpath fails (e.g., path doesn't exist), use as-is + } + + // Extract remote URL from config + const remoteUrl = this.getRemoteUrl(mainGitDir); + + // Generate consistent repository ID based on the CANONICAL main git directory + const repoId = this.generateRepoId(remoteUrl, mainGitDir); + + // Extract repository name from path or remote URL + const repoName = this.extractRepoName(remoteUrl, mainGitDir); + + return { + id: repoId, + remoteUrl: remoteUrl ?? undefined, + mainGitDir, + name: repoName, + }; + } + + // Fallback: path doesn't exist, use heuristic resolution + return this.resolveIdentityFromPath(projectPath); + } catch (error) { + logger.error(`Error resolving git identity for ${projectPath}:`, error); + // Try fallback even on error + return this.resolveIdentityFromPath(projectPath); + } + } + + /** + * Fallback: Resolve repository identity from path patterns when filesystem is unavailable. + * Uses heuristics to detect common worktree path patterns. + * + * Patterns supported: + * - /.cursor/worktrees/{repo}/{worktree-name} + * - /vibe-kanban/worktrees/{issue-branch}/{repo} + * - /T/vibe-kanban/worktrees/{issue-branch}/{repo} + * - Regular paths: use last component as repo name + */ + private resolveIdentityFromPath(projectPath: string): RepositoryIdentity | null { + const repoName = this.extractRepoNameFromPath(projectPath); + + if (!repoName) { + return null; + } + + // Generate ID from full path (since no remote URL, avoids colliding same-named repos) + const repoId = this.generateRepoId(null, projectPath); + + return { + id: repoId, + remoteUrl: undefined, + mainGitDir: repoName, // Use repo name as placeholder + name: repoName, + }; + } + + /** + * Extract repository name from path using heuristics. + * Works for both existing and deleted worktrees based on path patterns. + * + * Patterns: + * - /.cursor/worktrees/{repo}/{worktree} → repo + * - /vibe-kanban/worktrees/{issue-branch}/{repo} → repo (last component) + * - /conductor/workspaces/{repo}/{subpath} → repo + * - /.auto-claude/worktrees/tasks/{task-id} → parent repo (2 levels up from .auto-claude) + * - /.21st/worktrees/{id}/{name} → parent repo + * - /.claude-worktrees/{repo}/{name} → repo + * - /.ccswitch/worktrees/{repo}/{name} → repo + * - Default: last path component + */ + private extractRepoNameFromPath(projectPath: string): string | null { + const parts = projectPath.split(path.sep).filter(Boolean); + + if (parts.length === 0) { + return null; + } + + // Pattern 1: /.cursor/worktrees/{repo}/{worktree-name} + const cursorWorktreeIdx = parts.indexOf(CURSOR_DIR); + if (cursorWorktreeIdx >= 0 && parts[cursorWorktreeIdx + 1] === WORKTREES_DIR) { + if (parts[cursorWorktreeIdx + 2]) { + return parts[cursorWorktreeIdx + 2]; + } + } + + // Pattern 2: /vibe-kanban/worktrees/{issue-branch}/{repo} + const vibeKanbanIdx = parts.indexOf(VIBE_KANBAN_DIR); + if (vibeKanbanIdx >= 0 && parts[vibeKanbanIdx + 1] === WORKTREES_DIR) { + // The repo name is the LAST component (after issue-branch) + return parts[parts.length - 1]; + } + + // Pattern 3: /conductor/workspaces/{repo}/{subpath} + const conductorIdx = parts.indexOf(CONDUCTOR_DIR); + if (conductorIdx >= 0 && parts[conductorIdx + 1] === WORKSPACES_DIR) { + if (parts[conductorIdx + 2]) { + return parts[conductorIdx + 2]; + } + } + + // Pattern 4: /.auto-claude/worktrees/tasks/{task-id} + // Repo is typically the directory containing .auto-claude + const autoClaudeIdx = parts.indexOf(AUTO_CLAUDE_DIR); + if (autoClaudeIdx > 0 && parts[autoClaudeIdx + 1] === WORKTREES_DIR) { + return parts[autoClaudeIdx - 1]; // Parent directory is the repo + } + + // Pattern 5: /.21st/worktrees/{id}/{name} + const twentyFirstIdx = parts.indexOf(TWENTYFIRST_DIR); + if (twentyFirstIdx > 0 && parts[twentyFirstIdx + 1] === WORKTREES_DIR) { + return parts[twentyFirstIdx - 1]; // Parent directory is the repo + } + + // Pattern 6: /.claude-worktrees/{repo}/{name} + const claudeWorktreesIdx = parts.indexOf(CLAUDE_WORKTREES_DIR); + if (claudeWorktreesIdx >= 0 && parts[claudeWorktreesIdx + 1]) { + return parts[claudeWorktreesIdx + 1]; + } + + // Pattern 7: /.ccswitch/worktrees/{repo}/{name} + const ccswitchIdx = parts.indexOf(CCSWITCH_DIR); + if (ccswitchIdx >= 0 && parts[ccswitchIdx + 1] === WORKTREES_DIR) { + if (parts[ccswitchIdx + 2]) { + return parts[ccswitchIdx + 2]; + } + } + + // Default: use the last component + return parts[parts.length - 1]; + } + + /** + * Determine if a path is a worktree (vs main repo). + * Worktrees have a .git file, main repos have a .git directory. + * Uses path heuristics if filesystem is not available (for deleted worktrees). + */ + isWorktree(projectPath: string): boolean { + // First, try path-based heuristics (works for deleted worktrees) + const parts = projectPath.split(path.sep).filter(Boolean); + + // Check for known worktree patterns - these are ALWAYS worktrees + if (parts.includes(CURSOR_DIR) && parts.includes(WORKTREES_DIR)) { + return true; + } + if (parts.includes(VIBE_KANBAN_DIR) && parts.includes(WORKTREES_DIR)) { + return true; + } + if (parts.includes(AUTO_CLAUDE_DIR) && parts.includes(WORKTREES_DIR)) { + return true; + } + if (parts.includes(TWENTYFIRST_DIR) && parts.includes(WORKTREES_DIR)) { + return true; + } + if (parts.includes(CLAUDE_WORKTREES_DIR)) { + return true; + } + if (parts.includes(CCSWITCH_DIR) && parts.includes(WORKTREES_DIR)) { + return true; + } + if (parts.includes(CONDUCTOR_DIR) && parts.includes(WORKSPACES_DIR)) { + // Subpaths in conductor/workspaces are worktrees + const conductorIdx = parts.indexOf(CONDUCTOR_DIR); + if (conductorIdx >= 0 && parts.length > conductorIdx + 3) { + return true; // Has subpath after workspaces/{repo} + } + } + + // Fallback: check filesystem if available + try { + const gitPath = path.join(projectPath, '.git'); + if (fs.existsSync(gitPath)) { + const stats = fs.statSync(gitPath); + return stats.isFile(); + } + } catch { + // Ignore errors - filesystem might not be available + } + + return false; + } + + /** + * Extract the main .git directory path from a worktree's gitdir. + * + * @param worktreeGitDir - Path like "/path/to/main/.git/worktrees/" + * @returns Path to main .git directory like "/path/to/main/.git" + */ + private extractMainGitDir(worktreeGitDir: string): string { + // worktreeGitDir is typically: /path/to/main/.git/worktrees/ + // We need to go up two levels to get to .git + const parts = worktreeGitDir.split(path.sep); + const worktreesIndex = parts.lastIndexOf(WORKTREES_DIR); + + if (worktreesIndex > 0) { + // Return everything up to and including .git + return parts.slice(0, worktreesIndex).join(path.sep); + } + + // Fallback: try to find .git in the path + const gitIndex = worktreeGitDir.lastIndexOf('.git'); + if (gitIndex > 0) { + return worktreeGitDir.substring(0, gitIndex + 4); // +4 for ".git" + } + + // Last resort: return as-is + return worktreeGitDir; + } + + /** + * Get git remote URL from a repository's config file. + * + * @param gitDir - Path to the .git directory + * @returns Remote URL or null if not found + */ + private getRemoteUrl(gitDir: string): string | null { + try { + const configPath = path.join(gitDir, 'config'); + if (!fs.existsSync(configPath)) { + return null; + } + + const configContent = fs.readFileSync(configPath, 'utf-8'); + + // Parse git config to find [remote "origin"] section + const lines = configContent.split(/\r?\n/); + let inOriginRemote = false; + + for (const line of lines) { + const trimmed = line.trim(); + + // Check for remote "origin" section + if (/^\[remote\s+"origin"\]$/.exec(trimmed)) { + inOriginRemote = true; + continue; + } + + // Check for new section (exit origin remote) + if (trimmed.startsWith('[') && inOriginRemote) { + break; + } + + // Look for url = ... in origin remote section + if (inOriginRemote && trimmed.startsWith('url')) { + const urlMatch = /^url\s*=\s*(\S[^\r\n]*)$/.exec(trimmed); + if (urlMatch) { + return urlMatch[1].trim(); + } + } + } + + return null; + } catch (error) { + logger.error(`Error reading git config at ${gitDir}:`, error); + return null; + } + } + + /** + * Generate consistent repository ID. + * Uses the LOCAL DIRECTORY NAME as the primary identifier to ensure consistent grouping + * across filesystem-based and path-based resolution. + * + * IMPORTANT: We prioritize local directory name over remote URL repo name because: + * 1. Path-based resolution (for deleted worktrees) can only use directory names + * 2. Users may clone repos with different local names than remote names + * 3. We need consistent grouping regardless of whether filesystem exists + * + * @param _remoteUrl - Git remote URL (unused, kept for API compatibility) + * @param mainGitDirOrName - Path to main .git directory, or repo name for path-based resolution + * @returns Consistent hash-based ID + */ + private generateRepoId(remoteUrl: string | null, mainGitDirOrName: string): string { + // When a remote URL is available, use directory name for grouping + // (worktrees of the same repo have same dir name but different paths) + // When NO remote URL, use the full path to avoid colliding repos with same name + let identity: string; + + if (mainGitDirOrName.includes(path.sep) || mainGitDirOrName.includes('/')) { + if (remoteUrl) { + // Has remote → use dir name (allows worktree grouping) + const parentDir = path.dirname(mainGitDirOrName); + identity = path.basename(parentDir); + } else { + // No remote → use full path to distinguish same-named repos. + // For filesystem-based calls, mainGitDirOrName is like /path/.git → strip .git + // For fallback calls, mainGitDirOrName is the project path itself + identity = mainGitDirOrName.endsWith('.git') + ? path.dirname(mainGitDirOrName) + : mainGitDirOrName; + } + } else { + // It's already just a name (from path-based resolution fallback) + identity = mainGitDirOrName; + } + + // Normalize and generate hash + const normalized = identity.toLowerCase().trim(); + + // Generate SHA-256 hash and take first 12 characters + const hash = crypto.createHash('sha256').update(normalized).digest('hex'); + return hash.substring(0, 12); + } + + /** + * Extract repository name from git directory path. + * Always uses the LOCAL directory name for consistency with path-based resolution. + * + * @param _remoteUrl - Git remote URL (unused, kept for API compatibility) + * @param mainGitDir - Path to main .git directory + * @returns Repository name for display + */ + private extractRepoName(_remoteUrl: string | null, mainGitDir: string): string { + // Always use local directory name for consistency + // /Users/username/projectname/.git -> projectname + // /Users/username/projectname/.git -> projectname + const parentDir = path.dirname(mainGitDir); + return path.basename(parentDir); + } + + /** + * Get the git branch for a worktree. + * + * @param projectPath - The filesystem path to check + * @returns Branch name or null + */ + async getBranch(projectPath: string): Promise { + try { + const gitPath = path.join(projectPath, '.git'); + + if (!fs.existsSync(gitPath)) { + return null; + } + + const stats = fs.statSync(gitPath); + let headPath: string; + + if (stats.isFile()) { + // Worktree - read .git file to find the HEAD location + const gitFileContent = fs.readFileSync(gitPath, 'utf-8').trim(); + const gitDirMatch = /^gitdir:\s*(\S[^\r\n]*)$/.exec(gitFileContent); + + if (!gitDirMatch) { + return null; + } + + headPath = path.join(gitDirMatch[1], 'HEAD'); + } else { + // Main repo + headPath = path.join(gitPath, 'HEAD'); + } + + if (!fs.existsSync(headPath)) { + return null; + } + + const headContent = fs.readFileSync(headPath, 'utf-8').trim(); + + // Check if HEAD is a symbolic ref (branch) + const refMatch = /^ref:\s*refs\/heads\/(.+)$/.exec(headContent); + if (refMatch) { + return refMatch[1]; + } + + // HEAD is detached (commit hash) + return 'detached HEAD'; + } catch (error) { + logger.error(`Error reading git branch for ${projectPath}:`, error); + return null; + } + } + + /** + * Detect the worktree source based on path patterns. + * This method works purely on path patterns and does NOT require filesystem access, + * ensuring detection works even for deleted worktrees. + * + * Supported patterns: + * - vibe-kanban: /tmp/vibe-kanban/worktrees/{issue-branch}/{repo} + * - conductor: /Users/.../conductor/workspaces/{repo}/{workspace} + * - auto-claude: /Users/.../.auto-claude/worktrees/tasks/{task-id} + * - 21st: /Users/.../.21st/worktrees/{id}/{name} + * - claude-desktop: /Users/.../.claude-worktrees/{repo}/{name} + * - ccswitch: /Users/.../.ccswitch/worktrees/{repo}/{name} + * - git: Standard git worktree (fallback if none of the above match) + * - unknown: Non-git or undetectable + * + * @param projectPath - The filesystem path to check + * @returns WorktreeSource identifier + */ + detectWorktreeSource(projectPath: string): WorktreeSource { + const parts = projectPath.split(path.sep).filter(Boolean); + + // Pattern: vibe-kanban + // /tmp/vibe-kanban/worktrees/{issue-branch}/{repo} + // /private/var/folders/.../vibe-kanban/worktrees/{issue-branch}/{repo} + if (parts.includes(VIBE_KANBAN_DIR) && parts.includes(WORKTREES_DIR)) { + return 'vibe-kanban'; + } + + // Pattern: conductor + // /Users/.../conductor/workspaces/{repo}/{workspace} + if (parts.includes(CONDUCTOR_DIR) && parts.includes(WORKSPACES_DIR)) { + return 'conductor'; + } + + // Pattern: auto-claude + // /Users/.../.auto-claude/worktrees/tasks/{task-id} + if (parts.includes(AUTO_CLAUDE_DIR) && parts.includes(WORKTREES_DIR)) { + return 'auto-claude'; + } + + // Pattern: 21st (1code) + // /Users/.../.21st/worktrees/{id}/{name} + if (parts.includes(TWENTYFIRST_DIR) && parts.includes(WORKTREES_DIR)) { + return '21st'; + } + + // Pattern: claude-desktop + // /Users/.../.claude-worktrees/{repo}/{name} + if (parts.includes(CLAUDE_WORKTREES_DIR)) { + return 'claude-desktop'; + } + + // Pattern: ccswitch + // /Users/.../.ccswitch/worktrees/{repo}/{name} + if (parts.includes(CCSWITCH_DIR) && parts.includes(WORKTREES_DIR)) { + return 'ccswitch'; + } + + // Check if it's a standard git repo (only if filesystem exists) + // For deleted repos, we'll return 'git' as fallback since we can't verify + try { + const gitPath = path.join(projectPath, '.git'); + if (fs.existsSync(gitPath)) { + return 'git'; + } + } catch { + // Ignore errors - filesystem might not be available + } + + // Default to 'git' for paths that don't match known patterns + // This is a reasonable default since most worktrees come from git + return 'git'; + } + + /** + * Get the display name for a worktree based on its source. + * Extracts the meaningful identifier from the path based on the pattern. + * + * @param projectPath - The filesystem path + * @param source - The detected worktree source + * @param branch - The git branch (if available) + * @param isMainWorktree - Whether this is the main worktree + * @returns Display name for the worktree + */ + getWorktreeDisplayName( + projectPath: string, + source: WorktreeSource, + branch: string | null, + isMainWorktree: boolean + ): string { + const parts = projectPath.split(path.sep).filter(Boolean); + + switch (source) { + case 'vibe-kanban': { + // Pattern: vibe-kanban/worktrees/{issue-branch}/{repo} + // Display: {issue-branch} (e.g., "92a6-kanban-extension") + const worktreesIdx = parts.indexOf(WORKTREES_DIR); + if (worktreesIdx >= 0 && parts[worktreesIdx + 1]) { + return parts[worktreesIdx + 1]; + } + break; + } + + case 'conductor': { + // Pattern: conductor/workspaces/{repo}/{workspace} + // Display: {workspace} (e.g., "san-francisco") + const workspacesIdx = parts.indexOf(WORKSPACES_DIR); + if (workspacesIdx >= 0 && parts[workspacesIdx + 2]) { + return parts[workspacesIdx + 2]; + } + break; + } + + case 'auto-claude': { + // Pattern: .auto-claude/worktrees/tasks/{task-id} + // Display: {task-id} (e.g., "002-hjell") + const tasksIdx = parts.indexOf(TASKS_DIR); + if (tasksIdx >= 0 && parts[tasksIdx + 1]) { + return parts[tasksIdx + 1]; + } + // Fallback: last component + return parts[parts.length - 1]; + } + + case '21st': { + // Pattern: .21st/worktrees/{id}/{name with [bracket-id]} + // e.g., "mkp2f9a3y7x1s2nr 3b06478 [colonial-swordfish-fcad5f]" + // Display: Extract from brackets (e.g., "colonial-swordfish-fcad5f") + const lastPart = parts[parts.length - 1]; + // Extract content from square brackets using indexOf to avoid regex backtracking + const bracketStart = lastPart.indexOf('['); + const bracketEnd = lastPart.indexOf(']', bracketStart); + if (bracketStart !== -1 && bracketEnd !== -1 && bracketEnd > bracketStart + 1) { + return lastPart.slice(bracketStart + 1, bracketEnd); + } + // Fallback: use the last part as-is + return lastPart; + } + + case 'claude-desktop': { + // Pattern: .claude-worktrees/{repo}/{name} + // Display: {name} (e.g., "keen-sinoussi") + const claudeWorktreesIdx = parts.indexOf(CLAUDE_WORKTREES_DIR); + if (claudeWorktreesIdx >= 0 && parts[claudeWorktreesIdx + 2]) { + return parts[claudeWorktreesIdx + 2]; + } + break; + } + + case 'ccswitch': { + // Pattern: .ccswitch/worktrees/{repo}/{name} + // Display: {name} (e.g., "just-explain-my-repo-briefly") + const ccswitchWorktreesIdx = parts.indexOf(CCSWITCH_DIR); + if (ccswitchWorktreesIdx >= 0) { + const worktreesIdx = parts.indexOf(WORKTREES_DIR, ccswitchWorktreesIdx); + if (worktreesIdx >= 0 && parts[worktreesIdx + 2]) { + return parts[worktreesIdx + 2]; + } + } + break; + } + + case 'git': + // Standard git worktree - use branch or path-based name + if (isMainWorktree) { + return branch ?? 'main'; + } + // For non-main git worktrees, try to get the worktree name from .git file + return this.getGitWorktreeName(projectPath) ?? branch ?? parts[parts.length - 1]; + + case 'unknown': + default: + // Non-git project - use last path component + return parts[parts.length - 1] ?? 'unknown'; + } + + // Fallback for any case that didn't return + return branch ?? parts[parts.length - 1] ?? 'unknown'; + } + + /** + * Get the worktree name from git's internal tracking. + * Reads .git file to find the worktree name in .git/worktrees/{name} + * + * @param projectPath - The filesystem path + * @returns Worktree name or null + */ + private getGitWorktreeName(projectPath: string): string | null { + try { + const gitPath = path.join(projectPath, '.git'); + if (!fs.existsSync(gitPath)) return null; + + const stats = fs.statSync(gitPath); + if (!stats.isFile()) return null; + + const content = fs.readFileSync(gitPath, 'utf-8'); + const match = /gitdir:\s*(\S[^\r\n]*)/.exec(content); + if (!match) return null; + + // gitdir: /main/.git/worktrees/my-worktree-name + const gitdirParts = match[1].trim().split(path.sep); + const worktreesIdx = gitdirParts.lastIndexOf(WORKTREES_DIR); + if (worktreesIdx >= 0 && gitdirParts[worktreesIdx + 1]) { + return gitdirParts[worktreesIdx + 1]; + } + return null; + } catch { + return null; + } + } +} + +// Export singleton instance +export const gitIdentityResolver = new GitIdentityResolver(); diff --git a/src/main/services/parsing/MessageClassifier.ts b/src/main/services/parsing/MessageClassifier.ts new file mode 100644 index 00000000..fd1aab93 --- /dev/null +++ b/src/main/services/parsing/MessageClassifier.ts @@ -0,0 +1,65 @@ +/** + * MessageClassifier service - Classifies messages into categories for chunk building. + * + * Categories: + * - User: Genuine user input (creates UserChunk, renders RIGHT) + * - System: Command output (creates SystemChunk, renders LEFT) + * - Compact: Summary messages from conversation compaction + * - Hard Noise: Filtered out entirely (system metadata, caveats, reminders) + * - AI: All other messages grouped into AIChunks (renders LEFT) + */ + +import { + isParsedCompactMessage, + isParsedHardNoiseMessage, + isParsedSystemChunkMessage, + isParsedUserChunkMessage, + type MessageCategory, + type ParsedMessage, +} from '@main/types'; + +/** + * Result of classifying a message. + */ +export interface ClassifiedMessage { + message: ParsedMessage; + category: MessageCategory; +} + +/** + * Classify all messages into categories. + */ +export function classifyMessages(messages: ParsedMessage[]): ClassifiedMessage[] { + return messages.map((message) => ({ + message, + category: categorizeMessage(message), + })); +} + +/** + * Categorize a single message into one of five categories. + */ +function categorizeMessage(message: ParsedMessage): MessageCategory { + // Check hard noise first (filtered out) + if (isParsedHardNoiseMessage(message)) { + return 'hardNoise'; + } + + // Check compact summary (before system/user to catch it early) + if (isParsedCompactMessage(message)) { + return 'compact'; + } + + // Check system (command output) + if (isParsedSystemChunkMessage(message)) { + return 'system'; + } + + // Check user (real user input) + if (isParsedUserChunkMessage(message)) { + return 'user'; + } + + // Everything else is AI (assistant messages, tool results, etc.) + return 'ai'; +} diff --git a/src/main/services/parsing/SessionParser.ts b/src/main/services/parsing/SessionParser.ts new file mode 100644 index 00000000..e9c00547 --- /dev/null +++ b/src/main/services/parsing/SessionParser.ts @@ -0,0 +1,378 @@ +/** + * SessionParser service - Parses Claude Code session JSONL files. + * + * Responsibilities: + * - Parse JSONL files into structured messages + * - Extract all message metadata + * - Identify tool calls and tool results + * - Calculate session metrics + */ + +import { + isParsedInternalUserMessage, + isParsedRealUserMessage, + type ParsedMessage, + type SessionMetrics, + type ToolCall, + type ToolResult, +} from '@main/types'; +import { + calculateMetrics, + extractTextContent, + getTaskCalls, + parseJsonlFile, +} from '@main/utils/jsonl'; +import * as path from 'path'; + +import { type ProjectScanner } from '../discovery/ProjectScanner'; + +/** + * Result of parsing a session file. + */ +export interface ParsedSession { + /** All parsed messages */ + messages: ParsedMessage[]; + /** Session metrics */ + metrics: SessionMetrics; + /** All Task calls found in the session */ + taskCalls: ToolCall[]; + /** Messages grouped by type */ + byType: { + user: ParsedMessage[]; // All user messages + realUser: ParsedMessage[]; // Only real user messages (not tool results) + internalUser: ParsedMessage[]; // Only tool result messages + assistant: ParsedMessage[]; + system: ParsedMessage[]; + other: ParsedMessage[]; + }; + /** Sidechain messages */ + sidechainMessages: ParsedMessage[]; + /** Main thread messages (non-sidechain) */ + mainMessages: ParsedMessage[]; +} + +export class SessionParser { + private projectScanner: ProjectScanner; + + constructor(projectScanner: ProjectScanner) { + this.projectScanner = projectScanner; + } + + // =========================================================================== + // Core Parsing + // =========================================================================== + + /** + * Parse a session JSONL file and return structured data. + */ + async parseSession(projectId: string, sessionId: string): Promise { + const sessionPath = this.projectScanner.getSessionPath(projectId, sessionId); + return this.parseSessionFile(sessionPath); + } + + /** + * Parse a JSONL file at the given path. + */ + async parseSessionFile(filePath: string): Promise { + const messages = await parseJsonlFile(filePath); + return this.processMessages(messages); + } + + /** + * Process parsed messages into structured data. + */ + private processMessages(messages: ParsedMessage[]): ParsedSession { + // Group by type + const byType = { + user: messages.filter((m) => m.type === 'user'), + realUser: messages.filter(isParsedRealUserMessage), + internalUser: messages.filter(isParsedInternalUserMessage), + assistant: messages.filter((m) => m.type === 'assistant'), + system: messages.filter((m) => m.type === 'system'), + other: messages.filter( + (m) => m.type !== 'user' && m.type !== 'assistant' && m.type !== 'system' + ), + }; + + // Separate sidechain and main messages + const sidechainMessages = messages.filter((m) => m.isSidechain); + const mainMessages = messages.filter((m) => !m.isSidechain); + + // Calculate metrics + const metrics = calculateMetrics(messages); + + // Extract all Task calls + const taskCalls = getTaskCalls(messages); + + return { + messages, + metrics, + taskCalls, + byType, + sidechainMessages, + mainMessages, + }; + } + + // =========================================================================== + // Message Queries + // =========================================================================== + + /** + * Get user messages from a parsed session. + */ + getUserMessages(session: ParsedSession): ParsedMessage[] { + return session.byType.user; + } + + /** + * Get assistant messages from a parsed session. + */ + getAssistantMessages(session: ParsedSession): ParsedMessage[] { + return session.byType.assistant; + } + + /** + * Get messages in a time range. + */ + getMessagesInRange(messages: ParsedMessage[], startTime: Date, endTime: Date): ParsedMessage[] { + return messages.filter((m) => m.timestamp >= startTime && m.timestamp <= endTime); + } + + /** + * Get responses to a specific user message. + * Finds all assistant messages that follow the user message until the next user message. + */ + getResponses(messages: ParsedMessage[], userMessageUuid: string): ParsedMessage[] { + const userMsgIndex = messages.findIndex((m) => m.uuid === userMessageUuid); + if (userMsgIndex === -1) return []; + + const responses: ParsedMessage[] = []; + + for (let i = userMsgIndex + 1; i < messages.length; i++) { + const msg = messages[i]; + + // Stop at next user message + if (msg.type === 'user') break; + + // Include assistant responses + if (msg.type === 'assistant') { + responses.push(msg); + } + } + + return responses; + } + + // =========================================================================== + // Tool Call Analysis + // =========================================================================== + + /** + * Get all Task (subagent) calls from messages. + */ + getTaskCalls(messages: ParsedMessage[]): ToolCall[] { + return getTaskCalls(messages); + } + + /** + * Get all tool calls of a specific type. + */ + getToolCallsByName(messages: ParsedMessage[], toolName: string): ToolCall[] { + return messages.flatMap((m) => m.toolCalls.filter((tc) => tc.name === toolName)); + } + + /** + * Find the tool result for a specific tool call. + */ + findToolResult( + messages: ParsedMessage[], + toolCallId: string + ): { message: ParsedMessage; result: ToolResult } | null { + for (const msg of messages) { + const result = msg.toolResults.find((tr) => tr.toolUseId === toolCallId); + if (result) { + return { message: msg, result }; + } + } + return null; + } + + // =========================================================================== + // Timing Analysis + // =========================================================================== + + /** + * Get the time range of messages. + */ + getTimeRange(messages: ParsedMessage[]): { start: Date; end: Date; durationMs: number } { + if (messages.length === 0) { + const now = new Date(); + return { start: now, end: now, durationMs: 0 }; + } + + const timestamps = messages.map((m) => m.timestamp.getTime()); + let min = timestamps[0]; + let max = timestamps[0]; + for (let i = 1; i < timestamps.length; i++) { + if (timestamps[i] < min) min = timestamps[i]; + if (timestamps[i] > max) max = timestamps[i]; + } + const start = new Date(min); + const end = new Date(max); + + return { + start, + end, + durationMs: end.getTime() - start.getTime(), + }; + } + + /** + * Calculate metrics for a subset of messages. + */ + calculateMetrics(messages: ParsedMessage[]): SessionMetrics { + return calculateMetrics(messages); + } + + // =========================================================================== + // Text Extraction + // =========================================================================== + + /** + * Extract text content from a message. + */ + extractText(message: ParsedMessage): string { + return extractTextContent(message); + } + + /** + * Get a preview of a message (first N characters). + */ + getMessagePreview(message: ParsedMessage, maxLength: number = 100): string { + const text = extractTextContent(message); + if (text.length <= maxLength) return text; + return text.substring(0, maxLength) + '...'; + } + + // =========================================================================== + // Message Threading + // =========================================================================== + + /** + * Build a parent-child message tree. + */ + buildMessageTree(messages: ParsedMessage[]): Map { + const tree = new Map(); + + for (const msg of messages) { + const parentId = msg.parentUuid ?? 'root'; + if (!tree.has(parentId)) { + tree.set(parentId, []); + } + tree.get(parentId)!.push(msg); + } + + return tree; + } + + /** + * Get child messages of a specific message. + */ + getChildMessages(messages: ParsedMessage[], parentUuid: string): ParsedMessage[] { + return messages.filter((m) => m.parentUuid === parentUuid); + } + + /** + * Get the conversation thread for a message (ancestors + descendants). + */ + getThread(messages: ParsedMessage[], messageUuid: string): ParsedMessage[] { + const thread: ParsedMessage[] = []; + const messageMap = new Map(messages.map((m) => [m.uuid, m])); + + // Get ancestors + let current = messageMap.get(messageUuid); + const ancestors: ParsedMessage[] = []; + + while (current) { + ancestors.unshift(current); + current = current.parentUuid ? messageMap.get(current.parentUuid) : undefined; + } + + thread.push(...ancestors); + + // Get descendants + const descendants = this.getDescendants(messages, messageUuid); + // Add descendants that aren't already in thread + for (const desc of descendants) { + if (!thread.find((m) => m.uuid === desc.uuid)) { + thread.push(desc); + } + } + + return thread; + } + + /** + * Get all descendants of a message. + */ + private getDescendants(messages: ParsedMessage[], parentUuid: string): ParsedMessage[] { + const result: ParsedMessage[] = []; + const children = messages.filter((m) => m.parentUuid === parentUuid); + + for (const child of children) { + result.push(child); + result.push(...this.getDescendants(messages, child.uuid)); + } + + return result; + } + + // =========================================================================== + // Subagent File Parsing + // =========================================================================== + + /** + * Parse a subagent JSONL file. + */ + async parseSubagentFile(filePath: string): Promise<{ + messages: ParsedMessage[]; + metrics: SessionMetrics; + }> { + const messages = await parseJsonlFile(filePath); + const metrics = calculateMetrics(messages); + + return { messages, metrics }; + } + + /** + * Parse all subagent files for a session. + */ + async parseAllSubagents( + projectId: string, + sessionId: string + ): Promise< + Map< + string, + { + filePath: string; + messages: ParsedMessage[]; + metrics: SessionMetrics; + } + > + > { + const subagentFiles = await this.projectScanner.listSubagentFiles(projectId, sessionId); + const results = new Map(); + + for (const filePath of subagentFiles) { + // Extract agent ID from filename (agent-{id}.jsonl) + const filename = path.basename(filePath); + const agentId = filename.replace(/^agent-/, '').replace(/\.jsonl$/, ''); + + const { messages, metrics } = await this.parseSubagentFile(filePath); + results.set(agentId, { filePath, messages, metrics }); + } + + return results; + } +} diff --git a/src/main/services/parsing/index.ts b/src/main/services/parsing/index.ts new file mode 100644 index 00000000..998fcdf8 --- /dev/null +++ b/src/main/services/parsing/index.ts @@ -0,0 +1,14 @@ +/** + * Parsing services - Parsing JSONL and configuration files. + * + * Exports: + * - SessionParser: Parses JSONL session files + * - MessageClassifier: Classifies messages into categories + * - ClaudeMdReader: Reads CLAUDE.md configuration files + * - GitIdentityResolver: Resolves git identities from sessions + */ + +export * from './ClaudeMdReader'; +export * from './GitIdentityResolver'; +export * from './MessageClassifier'; +export * from './SessionParser'; diff --git a/src/main/types/chunks.ts b/src/main/types/chunks.ts new file mode 100644 index 00000000..063801ff --- /dev/null +++ b/src/main/types/chunks.ts @@ -0,0 +1,503 @@ +/** + * Chunk and visualization types for Claude Code Context. + * + * This module contains: + * - Chunk types (UserChunk, AIChunk, SystemChunk, CompactChunk) + * - Process/subagent execution types + * - Conversation grouping types + * - Semantic step types for detailed visualization + * - Enhanced chunk types with visualization data + * - Session detail types + * - Chunk type guards + * - Constants + */ + +import { type Session, type SessionMetrics } from './domain'; +import { type ToolUseResultData } from './jsonl'; +import { type ParsedMessage, type ToolCall, type ToolResult } from './messages'; + +// ============================================================================= +// Process Types (Subagent Execution) +// ============================================================================= + +/** + * Resolved subagent information. + */ +export interface Process { + /** Agent ID extracted from filename */ + id: string; + /** Path to the subagent JSONL file */ + filePath: string; + /** Parsed messages from the subagent session */ + messages: ParsedMessage[]; + /** When the subagent started */ + startTime: Date; + /** When the subagent ended */ + endTime: Date; + /** Duration in milliseconds */ + durationMs: number; + /** Aggregated metrics for the subagent */ + metrics: SessionMetrics; + /** Task description from parent Task call */ + description?: string; + /** Subagent type from Task call (e.g., "Explore", "Plan") */ + subagentType?: string; + /** Whether executed in parallel with other subagents */ + isParallel: boolean; + /** The tool_use ID of the Task call that spawned this */ + parentTaskId?: string; + /** Whether this subagent is still in progress */ + isOngoing?: boolean; + /** + * Main session impact tokens - the tokens the Task tool_call and tool_result + * consume in the main session's context window. This is different from the + * subagent's internal token usage (metrics/messages). + */ + mainSessionImpact?: { + /** Task tool_use input tokens (prompt, config) */ + callTokens: number; + /** Task tool_result output tokens (subagent's return value) */ + resultTokens: number; + /** Total tokens affecting main session */ + totalTokens: number; + }; + /** Team metadata - present when this subagent is a team member */ + team?: { + teamName: string; + memberName: string; + memberColor: string; + }; +} + +// ============================================================================= +// Chunk Types (for visualization) +// ============================================================================= + +/** + * Base chunk properties shared by all chunk types. + */ +interface BaseChunk { + /** Unique chunk identifier */ + id: string; + /** When the chunk started */ + startTime: Date; + /** When the chunk ended */ + endTime: Date; + /** Duration in milliseconds */ + durationMs: number; + /** Aggregated metrics for the chunk */ + metrics: SessionMetrics; +} + +/** + * User chunk - represents a single user input message. + * This is separate from AI responses to support independent visualization. + */ +export interface UserChunk extends BaseChunk { + /** Discriminator for chunk type */ + chunkType: 'user'; + /** The user message */ + userMessage: ParsedMessage; +} + +/** + * AI chunk - represents all assistant responses to a user message. + * Contains responses, tool executions, and subagent spawns. + * + * NOTE: AI chunks are independent - they no longer reference a parent user chunk. + */ +export interface AIChunk extends BaseChunk { + /** Discriminator for chunk type */ + chunkType: 'ai'; + /** All assistant responses and internal messages */ + responses: ParsedMessage[]; + /** Processes spawned during this chunk */ + processes: Process[]; + /** Sidechain messages within this chunk */ + sidechainMessages: ParsedMessage[]; + /** Tool executions in this chunk */ + toolExecutions: ToolExecution[]; +} + +/** + * System chunk - represents command output rendered like AI. + */ +export interface SystemChunk extends BaseChunk { + chunkType: 'system'; + message: ParsedMessage; + commandOutput: string; // Extracted from +} + +/** + * Compact boundary chunk - marks where conversation was compacted. + */ +export interface CompactChunk extends BaseChunk { + chunkType: 'compact'; + message: ParsedMessage; +} + +/** + * A chunk can be either a user input, AI response, system output, or compact boundary. + * This discriminated union enables separate visualization and processing. + */ +export type Chunk = UserChunk | AIChunk | SystemChunk | CompactChunk; + +/** + * Tool execution with timing information. + */ +export interface ToolExecution { + /** The tool call */ + toolCall: ToolCall; + /** The tool result if received */ + result?: ToolResult; + /** When the tool was called */ + startTime: Date; + /** When the result was received */ + endTime?: Date; + /** Duration in milliseconds */ + durationMs?: number; +} + +// ============================================================================= +// Conversation Group Types (Simplified Grouping Strategy) +// ============================================================================= + +/** + * Task execution links a Task tool call to its subagent execution. + * This provides a complete view of async subagent work initiated by Task tool. + */ +export interface TaskExecution { + /** The Task tool_use block that initiated the subagent */ + taskCall: ToolCall; + /** When the Task tool was called */ + taskCallTimestamp: Date; + /** The linked subagent execution */ + subagent: Process; + /** The isMeta:true tool_result message for this Task */ + toolResult: ParsedMessage; + /** When the tool result was received */ + resultTimestamp: Date; + /** Duration from task call to result */ + durationMs: number; +} + +/** + * ConversationGroup represents a natural grouping in the conversation flow: + * - One real user message (isMeta: false, string content) + * - All AI responses until the next user message (assistant messages + internal messages) + * - Subagents spawned during this group + * - Tool executions (excluding Task tools with subagents to avoid duplication) + * - Task executions (Task tools with their subagent results) + * + * This is a simplified alternative to Chunks that focuses on natural conversation boundaries. + */ +export interface ConversationGroup { + /** Unique group identifier */ + id: string; + /** Group type - currently only one type but extensible */ + type: 'user-ai-exchange'; + /** The real user message that starts this group (isMeta: false) */ + userMessage: ParsedMessage; + /** All AI responses: assistant messages and internal messages (tool results, etc.) */ + aiResponses: ParsedMessage[]; + /** Processes spawned during this group */ + processes: Process[]; + /** Tool executions (excluding Task tools that have matching processes) */ + toolExecutions: ToolExecution[]; + /** Task tool calls with their subagent executions */ + taskExecutions: TaskExecution[]; + /** When the group started (user message timestamp) */ + startTime: Date; + /** When the group ended (last AI response timestamp) */ + endTime: Date; + /** Duration in milliseconds */ + durationMs: number; + /** Aggregated metrics for the group */ + metrics: SessionMetrics; +} + +// ============================================================================= +// Semantic Step Types (Enhanced Chunk Visualization) +// ============================================================================= + +/** + * Semantic step types for breakdown within responses. + */ +export type SemanticStepType = + | 'thinking' // Extended thinking content + | 'tool_call' // Tool invocation + | 'tool_result' // Tool result received + | 'subagent' // Subagent execution + | 'output' // Main text output + | 'interruption'; // User interruption + +/** + * A semantic step represents a logical unit of work within a response. + * + * Note: Task tool_use blocks are filtered during extraction when corresponding + * subagents exist. Since Task calls spawn async subagents, the tool_call and + * subagent represent the same execution. Filtering prevents duplicate entries + * Orphaned Task calls (without matching subagents) are + * retained as fallback to ensure visibility of all work. + */ +export interface SemanticStep { + /** Unique step identifier */ + id: string; + /** Step type */ + type: SemanticStepType; + /** When the step started */ + startTime: Date; + /** When the step ended */ + endTime?: Date; + /** Duration in milliseconds */ + durationMs: number; + + /** Content varies by type */ + content: { + thinkingText?: string; // For thinking + toolName?: string; // For tool_call/result + toolInput?: unknown; // For tool_call + toolResultContent?: string; // For tool_result + isError?: boolean; // For tool_result + toolUseResult?: ToolUseResultData; // For tool_result - enriched data from message + tokenCount?: number; // For tool_result - pre-computed token count + subagentId?: string; // For subagent + subagentDescription?: string; + outputText?: string; // For output + sourceModel?: string; // For tool_call - model from source assistant message + interruptionText?: string; // For interruption - the interruption message text + }; + + /** Token attribution */ + tokens?: { + input: number; + output: number; + cached?: number; + }; + + /** Parallel execution */ + isParallel?: boolean; + groupId?: string; + + /** Context (main agent vs subagent) */ + context: 'main' | 'subagent'; + agentId?: string; + + /** Source message UUID (for grouping steps by assistant message) */ + sourceMessageId?: string; + + /** Effective end time after gap filling (extends to next step or chunk end) */ + effectiveEndTime?: Date; + + /** Effective duration including waiting time until next step */ + effectiveDurationMs?: number; + + /** Whether timing was gap-filled vs having original endTime */ + isGapFilled?: boolean; + + /** Context tokens for this step (cache_read + cache_creation + input) */ + contextTokens?: number; + + /** Cumulative context up to this step (session-wide accumulation) */ + accumulatedContext?: number; + + /** Token breakdown for step-level estimation */ + tokenBreakdown?: { + input: number; + output: number; + cacheRead: number; + cacheCreation: number; + }; +} + +/** + * Semantic step group for collapsible visualization. + * Groups multiple micro-steps by their source assistant message. + */ +export interface SemanticStepGroup { + /** Unique group ID */ + id: string; + /** Display label (e.g., "Assistant Response", "Tool: Read") */ + label: string; + /** Steps in this group */ + steps: SemanticStep[]; + /** true if multiple steps grouped, false if standalone */ + isGrouped: boolean; + /** Assistant message UUID if grouped */ + sourceMessageId?: string; + /** Earliest step start */ + startTime: Date; + /** Latest step end */ + endTime: Date; + /** Sum of all step durations */ + totalDuration: number; +} + +// ============================================================================= +// Enhanced Chunk Types +// ============================================================================= + +/** + * Enhanced AI chunk with semantic step breakdown. + * This extends AIChunk with additional visualization data. + */ +export interface EnhancedAIChunk extends AIChunk { + /** Semantic steps extracted from messages */ + semanticSteps: SemanticStep[]; + /** Semantic steps grouped for collapsible UI */ + semanticStepGroups?: SemanticStepGroup[]; + /** Raw messages for debug sidebar */ + rawMessages: ParsedMessage[]; +} + +/** + * Enhanced user chunk with additional metadata. + */ +export interface EnhancedUserChunk extends UserChunk { + /** Raw messages for debug sidebar */ + rawMessages: ParsedMessage[]; +} + +/** + * Enhanced system chunk with additional metadata. + */ +export interface EnhancedSystemChunk extends SystemChunk { + /** Raw messages for debug sidebar */ + rawMessages: ParsedMessage[]; +} + +/** + * Enhanced compact chunk with additional metadata. + */ +export interface EnhancedCompactChunk extends CompactChunk { + /** Raw messages for debug sidebar */ + rawMessages: ParsedMessage[]; +} + +/** + * Enhanced chunk can be user, AI, system, or compact type. + */ +export type EnhancedChunk = + | EnhancedUserChunk + | EnhancedAIChunk + | EnhancedSystemChunk + | EnhancedCompactChunk; + +// ============================================================================= +// Session Detail (complete parsed session) +// ============================================================================= + +/** + * Complete parsed session with all data. + */ +export interface SessionDetail { + /** Session metadata */ + session: Session; + /** All messages in the session */ + messages: ParsedMessage[]; + /** Messages grouped into chunks */ + chunks: Chunk[]; + /** All processes in the session */ + processes: Process[]; + /** Aggregated metrics for the entire session */ + metrics: SessionMetrics; +} + +/** + * Detailed subagent information for drill-down modal. + * Contains parsed execution data for a specific subagent. + */ +export interface SubagentDetail { + /** Agent ID */ + id: string; + /** Task description */ + description: string; + /** Subagent's chunks with semantic breakdown */ + chunks: EnhancedChunk[]; + /** Semantic step groups for visualization */ + semanticStepGroups?: SemanticStepGroup[]; + /** Start time */ + startTime: Date; + /** End time */ + endTime: Date; + /** Duration in milliseconds */ + duration: number; + /** Token and message metrics */ + metrics: { + inputTokens: number; + outputTokens: number; + thinkingTokens: number; + messageCount: number; + }; +} + +// ============================================================================= +// Utility Types +// ============================================================================= + +/** + * File watching event. + */ +export interface FileChangeEvent { + type: 'add' | 'change' | 'unlink'; + path: string; + projectId?: string; + sessionId?: string; + isSubagent: boolean; +} + +// ============================================================================= +// Constants +// ============================================================================= + +/** + * Empty metrics constant for initialization. + */ +export const EMPTY_METRICS: SessionMetrics = { + durationMs: 0, + totalTokens: 0, + inputTokens: 0, + outputTokens: 0, + cacheReadTokens: 0, + cacheCreationTokens: 0, + messageCount: 0, +}; + +// ============================================================================= +// Chunk Type Guards +// ============================================================================= + +/** + * Type guard to check if a chunk is a UserChunk. + */ +export function isUserChunk(chunk: Chunk | EnhancedChunk): chunk is UserChunk { + return 'chunkType' in chunk && chunk.chunkType === 'user'; +} + +/** + * Type guard to check if a chunk is an AIChunk. + */ +export function isAIChunk(chunk: Chunk | EnhancedChunk): chunk is AIChunk { + return 'chunkType' in chunk && chunk.chunkType === 'ai'; +} + +/** + * Type guard to check if a chunk is an EnhancedAIChunk. + */ +export function isEnhancedAIChunk(chunk: Chunk | EnhancedChunk): chunk is EnhancedAIChunk { + return isAIChunk(chunk) && 'semanticSteps' in chunk; +} + +/** + * Type guard to check if a chunk is a SystemChunk. + */ +export function isSystemChunk(chunk: Chunk | EnhancedChunk): chunk is SystemChunk { + return 'chunkType' in chunk && chunk.chunkType === 'system'; +} + +/** + * Type guard to check if a chunk is a CompactChunk. + */ +export function isCompactChunk(chunk: Chunk | EnhancedChunk): chunk is CompactChunk { + return 'chunkType' in chunk && chunk.chunkType === 'compact'; +} diff --git a/src/main/types/domain.ts b/src/main/types/domain.ts new file mode 100644 index 00000000..a884fe1b --- /dev/null +++ b/src/main/types/domain.ts @@ -0,0 +1,283 @@ +/** + * Domain/business entity types for Claude Code Context. + * + * These types represent the application's domain model: + * - Projects and sessions + * - Repository and worktree grouping + * - Search and pagination + * - Token usage and metrics + */ + +import { type UsageMetadata } from './jsonl'; + +// ============================================================================= +// Application-Specific Type Aliases +// ============================================================================= + +/** + * Token usage statistics (alias for API compatibility). + * Maps to UsageMetadata from the spec. + */ +export type TokenUsage = UsageMetadata; + +/** + * Message type classification for parsed messages. + */ +export type MessageType = + | 'user' + | 'assistant' + | 'system' + | 'summary' + | 'file-history-snapshot' + | 'queue-operation'; + +/** + * Message category for chunk building. + * Used to classify messages into one of four categories for independent chunk creation. + */ +export type MessageCategory = 'user' | 'system' | 'hardNoise' | 'ai' | 'compact'; + +// ============================================================================= +// Project & Session Types +// ============================================================================= + +/** + * Project information derived from ~/.claude/projects/ directory. + */ +export interface Project { + /** Encoded directory name (e.g., "-Users-username-projectname") */ + id: string; + /** Decoded actual filesystem path */ + path: string; + /** Display name (last path segment) */ + name: string; + /** List of session IDs (JSONL filenames without extension) */ + sessions: string[]; + /** Unix timestamp when project directory was created */ + createdAt: number; + /** Unix timestamp of most recent session activity */ + mostRecentSession?: number; +} + +/** + * Session metadata and summary. + */ +export interface Session { + /** Session UUID (JSONL filename without extension) */ + id: string; + /** Parent project ID */ + projectId: string; + /** Project filesystem path */ + projectPath: string; + /** Task list data from ~/.claude/todos/{id}.json if exists */ + todoData?: unknown; + /** Unix timestamp when session file was created */ + createdAt: number; + /** First user message text (for preview) */ + firstMessage?: string; + /** Timestamp of first user message (RFC3339) */ + messageTimestamp?: string; + /** Whether this session has subagents */ + hasSubagents: boolean; + /** Total message count in the session */ + messageCount: number; + /** Whether the session is ongoing (last AI response has no output yet) */ + isOngoing?: boolean; + /** Git branch name if available */ + gitBranch?: string; +} + +/** + * Aggregated metrics for a session or chunk. + */ +export interface SessionMetrics { + /** Duration in milliseconds */ + durationMs: number; + /** Total tokens (input + output) */ + totalTokens: number; + /** Input tokens */ + inputTokens: number; + /** Output tokens */ + outputTokens: number; + /** Cache read tokens */ + cacheReadTokens: number; + /** Cache creation tokens */ + cacheCreationTokens: number; + /** Number of messages */ + messageCount: number; + /** Estimated cost in USD */ + costUsd?: number; +} + +// ============================================================================= +// Repository & Worktree Grouping Types +// ============================================================================= + +/** + * Worktree source identifies how/where the worktree was created. + * Used for badge display and source-specific naming strategies. + */ +export type WorktreeSource = + | 'vibe-kanban' // /tmp/vibe-kanban/worktrees/{issue-branch}/{repo} + | 'conductor' // /Users/.../conductor/workspaces/{repo}/{workspace} + | 'auto-claude' // /Users/.../.auto-claude/worktrees/tasks/{task-id} + | '21st' // /Users/.../.21st/worktrees/{id}/{name [bracket-id]} + | 'claude-desktop' // /Users/.../.claude-worktrees/{repo}/{name} + | 'ccswitch' // /Users/.../.ccswitch/worktrees/{repo}/{name} + | 'git' // Standard git worktree (main repo or detached) + | 'unknown'; // Non-git project or undetectable + +/** + * Git repository identity for grouping worktrees. + * Multiple projects (worktrees) can share the same RepositoryIdentity. + */ +export interface RepositoryIdentity { + /** Unique identifier - hash of remote URL or main repo path */ + id: string; + /** Git remote URL if available (e.g., "https://github.com/org/repo.git") */ + remoteUrl?: string; + /** Path to the main git directory (e.g., "/Users/username/projectname/.git") */ + mainGitDir: string; + /** Display name for the repository (e.g., "projectname") */ + name: string; +} + +/** + * A worktree represents a single working directory of a git repository. + * In the grouped view, projects become worktrees under a RepositoryGroup. + */ +export interface Worktree { + /** Encoded directory name (same as Project.id) */ + id: string; + /** Decoded actual filesystem path */ + path: string; + /** Display name (worktree-specific, e.g., branch name or "main") */ + name: string; + /** Git branch name if available */ + gitBranch?: string; + /** Whether this is the main worktree (not a detached worktree) */ + isMainWorktree: boolean; + /** Worktree source for badge display (vibe-kanban, conductor, etc.) */ + source: WorktreeSource; + /** List of session IDs */ + sessions: string[]; + /** Unix timestamp when first session was created */ + createdAt: number; + /** Unix timestamp of most recent session activity */ + mostRecentSession?: number; +} + +/** + * A repository group contains all worktrees of a single git repository. + * This is the top-level entity when worktree grouping is enabled. + * Non-git projects are represented as single-worktree RepositoryGroups. + */ +export interface RepositoryGroup { + /** Unique identifier from RepositoryIdentity.id (or project.id for non-git) */ + id: string; + /** Repository identity information (null for non-git projects) */ + identity: RepositoryIdentity | null; + /** All worktrees of this repository */ + worktrees: Worktree[]; + /** Display name (derived from repo name) */ + name: string; + /** Unix timestamp of most recent session across all worktrees */ + mostRecentSession?: number; + /** Total session count across all worktrees */ + totalSessions: number; +} + +// ============================================================================= +// Search Types +// ============================================================================= + +/** + * A single search result from searching sessions. + */ +export interface SearchResult { + /** Session ID where match was found */ + sessionId: string; + /** Project ID */ + projectId: string; + /** Session title/first message */ + sessionTitle: string; + /** The matched text (trimmed) */ + matchedText: string; + /** Context around the match */ + context: string; + /** Message type (user/assistant) */ + messageType: 'user' | 'assistant'; + /** Timestamp of the message */ + timestamp: number; + /** Stable chat group ID used by in-session navigation (e.g., "user-..." or "ai-...") */ + groupId?: string; + /** Searchable item type used for in-session matching */ + itemType?: 'user' | 'ai'; + /** Match index within the item's searchable text (0-based) */ + matchIndexInItem?: number; + /** Character offset of the match within the searchable text */ + matchStartOffset?: number; + /** Source message UUID for diagnostics/fallback mapping */ + messageUuid?: string; +} + +/** + * Result of a search operation. + */ +export interface SearchSessionsResult { + /** Search results */ + results: SearchResult[]; + /** Total matches found */ + totalMatches: number; + /** Sessions searched */ + sessionsSearched: number; + /** Search query used */ + query: string; +} + +// ============================================================================= +// Pagination Types +// ============================================================================= + +/** + * Cursor for session pagination. + * Uses timestamp + sessionId as a composite cursor for stable pagination. + */ +export interface SessionCursor { + /** Unix timestamp (birthtimeMs) of the session file */ + timestamp: number; + /** Session ID for tie-breaking when timestamps are equal */ + sessionId: string; +} + +/** + * Result of paginated session listing. + */ +export interface PaginatedSessionsResult { + /** Sessions for this page */ + sessions: Session[]; + /** Cursor for next page (null if no more pages) */ + nextCursor: string | null; + /** Whether there are more sessions to load */ + hasMore: boolean; + /** Total count of sessions (for display purposes) */ + totalCount: number; +} + +/** + * Options controlling paginated session listing behavior. + */ +export interface SessionsPaginationOptions { + /** + * Whether to compute an accurate totalCount by scanning all sessions. + * Disable for faster background refreshes. + * @default true + */ + includeTotalCount?: boolean; + /** + * Whether to pre-filter all session files before paging. + * Disable for faster top-of-list refreshes. + * @default true + */ + prefilterAll?: boolean; +} diff --git a/src/main/types/index.ts b/src/main/types/index.ts new file mode 100644 index 00000000..8a83aac7 --- /dev/null +++ b/src/main/types/index.ts @@ -0,0 +1,24 @@ +/** + * Type definitions index - re-exports all types from focused modules. + * + * Import from this module to get all types: + * import { ParsedMessage, Chunk, Session } from '../types'; + * + * Or import from specific modules for focused imports: + * import { ContentBlock } from '../types/jsonl'; + * import { Session } from '../types/domain'; + * import { ParsedMessage } from '../types/messages'; + * import { Chunk, isAIChunk } from '../types/chunks'; + */ + +// JSONL format types (raw data from disk) +export * from './jsonl'; + +// Domain/business entities +export type * from './domain'; + +// Parsed message types and guards +export * from './messages'; + +// Chunk and visualization types +export * from './chunks'; diff --git a/src/main/types/jsonl.ts b/src/main/types/jsonl.ts new file mode 100644 index 00000000..6435a707 --- /dev/null +++ b/src/main/types/jsonl.ts @@ -0,0 +1,326 @@ +/** + * JSONL format types - raw data structures from Claude Code session files. + * + * These types represent the exact format stored in .jsonl files at: + * ~/.claude/projects/{project_name}/{session_uuid}.jsonl + * + * Content type guards and entry type guards are included here as they + * operate directly on the raw JSONL structures. + */ + +// ============================================================================= +// Core Type Aliases +// ============================================================================= + +type EntryType = + | 'user' + | 'assistant' + | 'system' + | 'summary' + | 'file-history-snapshot' + | 'queue-operation'; + +type ContentType = 'text' | 'thinking' | 'tool_use' | 'tool_result' | 'image'; + +type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | null; + +// ============================================================================= +// Content Blocks +// ============================================================================= + +interface BaseContent { + type: ContentType; +} + +export interface TextContent extends BaseContent { + type: 'text'; + text: string; +} + +export interface ThinkingContent extends BaseContent { + type: 'thinking'; + thinking: string; + signature: string; +} + +export interface ToolUseContent extends BaseContent { + type: 'tool_use'; + id: string; + name: string; + input: Record; +} + +export interface ToolResultContent extends BaseContent { + type: 'tool_result'; + tool_use_id: string; + content: string | ContentBlock[]; + is_error?: boolean; +} + +export interface ImageContent extends BaseContent { + type: 'image'; + source: { + type: 'base64'; + media_type: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp'; + data: string; + }; +} + +export type ContentBlock = + | TextContent + | ThinkingContent + | ToolUseContent + | ToolResultContent + | ImageContent; + +// ============================================================================= +// Usage Metadata +// ============================================================================= + +export interface UsageMetadata { + input_tokens: number; + output_tokens: number; + cache_read_input_tokens?: number; + cache_creation_input_tokens?: number; +} + +// ============================================================================= +// Messages +// ============================================================================= + +interface UserMessage { + role: 'user'; + content: string | ContentBlock[]; +} + +interface AssistantMessage { + role: 'assistant'; + model: string; + id: string; + type: 'message'; + content: ContentBlock[]; + stop_reason: StopReason; + stop_sequence: string | null; + usage: UsageMetadata; +} + +// ============================================================================= +// JSONL Entries +// ============================================================================= + +interface BaseEntry { + type: EntryType; + timestamp?: string; + uuid?: string; +} + +/** + * Base for conversational entries (user, assistant, system). + * + * Sidechain behavior: + * - isSidechain: false -> Main agent message + * - isSidechain: true -> Subagent message + * - sessionId: For subagents, points to parent session UUID + */ +interface ConversationalEntry extends BaseEntry { + parentUuid: string | null; + isSidechain: boolean; + userType: 'external'; + cwd: string; + sessionId: string; + version: string; + gitBranch: string; + slug?: string; +} + +/** + * Tool use result data - preserves full structure from JSONL entries. + * + * The structure varies significantly by tool type: + * - File tools: { type, success, filePath, content, structuredPatch, ... } + * - Task tools: { status, prompt, agentId, content, totalDurationMs, totalTokens, usage, ... } + * - AskUserQuestion: { questions, answers } + * - Bash: { stdout, stderr, exitCode, ... } + * + * Using Record to preserve all data without loss. + */ +export type ToolUseResultData = Record; + +/** + * CRITICAL: User entries serve two purposes: + * + * 1. Real User Input (chunk starters): + * - isMeta: false or undefined + * - content: string + * - These START new chunks + * + * 2. Response Messages (part of response flow): + * a) Internal (tool results): + * - isMeta: true + * - content: array with tool_result blocks + * b) Interruptions: + * - isMeta: false + * - content: array (not string) + */ +export interface UserEntry extends ConversationalEntry { + type: 'user'; + message: UserMessage; + isMeta?: boolean; + agentId?: string; + + toolUseResult?: ToolUseResultData; + sourceToolUseID?: string; + sourceToolAssistantUUID?: string; +} + +export interface AssistantEntry extends ConversationalEntry { + type: 'assistant'; + message: AssistantMessage; + requestId: string; + agentId?: string; +} + +export interface SystemEntry extends ConversationalEntry { + type: 'system'; + subtype: 'turn_duration' | 'init'; + durationMs: number; + isMeta: boolean; +} + +export interface SummaryEntry extends BaseEntry { + type: 'summary'; + summary: string; + leafUuid: string; +} + +export interface FileHistorySnapshotEntry extends BaseEntry { + type: 'file-history-snapshot'; + messageId: string; + snapshot: { + messageId: string; + trackedFileBackups: Record; + timestamp: string; + }; + isSnapshotUpdate: boolean; +} + +export interface QueueOperationEntry extends BaseEntry { + type: 'queue-operation'; + operation: string; +} + +export type ChatHistoryEntry = + | UserEntry + | AssistantEntry + | SystemEntry + | SummaryEntry + | FileHistorySnapshotEntry + | QueueOperationEntry; + +/** + * Conversational entries - entries that represent chat messages. + * These share common properties like message, cwd, gitBranch, etc. + */ +export type ConversationalChatEntry = UserEntry | AssistantEntry | SystemEntry; + +// ============================================================================= +// Content Type Guards +// ============================================================================= + +export function isTextContent(content: ContentBlock): content is TextContent { + return content.type === 'text'; +} + +export function isToolResultContent(content: ContentBlock): content is ToolResultContent { + return content.type === 'tool_result'; +} + +/** + * Type guard to check if an entry is a conversational entry. + */ +export function isConversationalEntry(entry: ChatHistoryEntry): entry is ConversationalChatEntry { + return entry.type === 'user' || entry.type === 'assistant' || entry.type === 'system'; +} + +// ============================================================================= +// Subagent Directory Structures +// ============================================================================= + +/** + * Claude Code supports two subagent directory structures: + * + * NEW STRUCTURE (Current): + * ~/.claude/projects/ + * {project_name}/ + * {session_uuid}.jsonl <- Main agent + * {session_uuid}/ + * agent_{agent_uuid}.jsonl <- Subagents + * + * OLD STRUCTURE (Legacy, still supported): + * ~/.claude/projects/ + * {project_name}/ + * {session_uuid}.jsonl <- Main agent + * agent_{agent_uuid}.jsonl <- Subagents (at root) + * + * Identification: + * - Main agent: isSidechain: false (or undefined) + * - Subagent: isSidechain: true + * - Linking: subagent.sessionId === parent session UUID + * + * When scanning for subagents: + * 1. First check {session_uuid}/ subdirectory (new structure) + * 2. Fall back to project root for agent_*.jsonl (old structure) + * 3. Match by sessionId field to link to parent + */ + +// ============================================================================= +// Message Flow Pattern +// ============================================================================= + +/** + * Typical conversation flow: + * + * 1. User types -> type: "user", isMeta: false, content: string -> TRIGGER MESSAGE (STARTS CHUNK) + * 2. Assistant responds -> type: "assistant", may contain tool_use -> FLOW MESSAGE (PART OF RESPONSE) + * 3. Tool executes -> type: "user", isMeta: true, contains tool_result -> FLOW MESSAGE (PART OF RESPONSE) + * 4. User interrupts -> type: "user", isMeta: false, content: array -> FLOW MESSAGE (PART OF RESPONSE) + * 5. Assistant continues -> type: "assistant" -> FLOW MESSAGE (PART OF RESPONSE) + * + * Message Categories (New 4-Category System): + * + * 1. USER MESSAGES (create UserChunks): + * - Genuine user input that initiates a new request/response cycle + * - Detected by: isParsedUserChunkMessage() type guard + * - Requirements: type='user', isMeta!=true, has text/image content + * - Excludes: , , + * - Allows: (slash commands like /model are visible user input) + * + * 2. SYSTEM MESSAGES (create SystemChunks): + * - Command output from slash commands + * - Detected by: isParsedSystemChunkMessage() type guard + * - Contains tag + * - Renders on LEFT side like AI responses + * + * 3. HARD NOISE MESSAGES (filtered out): + * - System-generated metadata that should NEVER be displayed + * - Detected by: isParsedHardNoiseMessage() type guard + * - Includes: system/summary/file-history-snapshot/queue-operation entries + * - Includes: User messages with ONLY or + * + * 4. AI MESSAGES (create AIChunks): + * - All assistant messages and flow messages between User/System/HardNoise + * - Includes: assistant messages, tool results, interruptions + * - Consecutive AI messages are grouped into single AIChunk + * - AIChunks are INDEPENDENT - no longer paired with UserChunks + * + * Key Rules: + * - User messages START UserChunks (render on RIGHT) + * - System messages START SystemChunks (render on LEFT) + * - AI messages are GROUPED into independent AIChunks (render on LEFT) + * - Hard noise messages are FILTERED OUT entirely + * + * Tool Linking: + * - tool_use.id in assistant message + * - tool_result.tool_use_id in internal user message + * - Also: sourceToolUseID field directly on internal user entry + */ diff --git a/src/main/types/messages.ts b/src/main/types/messages.ts new file mode 100644 index 00000000..c6266d4f --- /dev/null +++ b/src/main/types/messages.ts @@ -0,0 +1,376 @@ +/** + * Parsed message types and type guards for Claude Code Context. + * + * ParsedMessage is the application's internal representation after parsing + * raw JSONL entries. This module also contains type guards for classifying + * parsed messages into categories for chunk building. + */ + +import { + EMPTY_STDERR, + EMPTY_STDOUT, + HARD_NOISE_TAGS, + LOCAL_COMMAND_STDERR_TAG, + LOCAL_COMMAND_STDOUT_TAG, + SYSTEM_OUTPUT_TAGS, +} from '../constants/messageTags'; + +import { type MessageType, type TokenUsage } from './domain'; +import { type ContentBlock, type ToolUseResultData } from './jsonl'; + +// ============================================================================= +// Tool Types +// ============================================================================= + +/** + * Tool call extracted from assistant message. + */ +export interface ToolCall { + /** Tool use ID for linking to results */ + id: string; + /** Tool name */ + name: string; + /** Tool input parameters */ + input: Record; + /** Whether this is a Task (subagent) tool call */ + isTask: boolean; + /** Task description if isTask */ + taskDescription?: string; + /** Task subagent type if isTask */ + taskSubagentType?: string; +} + +/** + * Tool result extracted from user message. + */ +export interface ToolResult { + /** Corresponding tool_use ID */ + toolUseId: string; + /** Result content */ + content: string | unknown[]; + /** Whether the tool execution errored */ + isError: boolean; +} + +// ============================================================================= +// Parsed Message +// ============================================================================= + +/** + * Parsed and enriched message from JSONL. + * This is the application's internal representation after parsing raw JSONL entries. + */ +export interface ParsedMessage { + /** Unique message identifier */ + uuid: string; + /** Parent message UUID for threading */ + parentUuid: string | null; + /** Message type */ + type: MessageType; + /** Message timestamp */ + timestamp: Date; + /** Message role if present */ + role?: string; + /** Message content (string or content blocks) */ + content: ContentBlock[] | string; + /** Token usage for this message */ + usage?: TokenUsage; + /** Model used for this response */ + model?: string; + // Metadata + /** Current working directory when message was created */ + cwd?: string; + /** Git branch context */ + gitBranch?: string; + /** Agent ID for subagent messages */ + agentId?: string; + /** Whether this is a sidechain message */ + isSidechain: boolean; + /** Whether this is a meta message */ + isMeta: boolean; + /** User type ("external" for user input) */ + userType?: string; + // Extracted tool information + /** Tool calls made in this message */ + toolCalls: ToolCall[]; + /** Tool results received in this message */ + toolResults: ToolResult[]; + /** Source tool use ID if this is a tool result message */ + sourceToolUseID?: string; + /** Source assistant UUID if this is a tool result message */ + sourceToolAssistantUUID?: string; + /** Tool use result information if this is a tool result message */ + toolUseResult?: ToolUseResultData; + /** Whether this is a compact summary boundary message */ + isCompactSummary?: boolean; +} + +// ============================================================================= +// ParsedMessage Type Guards +// ============================================================================= + +/** + * Type guard to check if a ParsedMessage is a real user message. + * This wraps the spec's type guard but works with ParsedMessage instead of UserEntry. + * + * Accepts both formats: + * - Older sessions: content as string + * - Newer sessions: content as array with text/image blocks + * + * Excludes command output messages (with ) which should + * be treated as system responses, not user input that starts new chunks. + */ +export function isParsedRealUserMessage(msg: ParsedMessage): boolean { + if (msg.type !== 'user') return false; + if (msg.isMeta) return false; + + const content = msg.content; + + // String content format (older sessions) + if (typeof content === 'string') { + return true; + } + + // Array content format (newer sessions) + if (Array.isArray(content)) { + // Check if it contains text or image blocks (real user input) + // Exclude arrays with only tool_result blocks (those are internal messages) + return content.some((block) => block.type === 'text' || block.type === 'image'); + } + + return false; +} + +/** + * Type guard for User chunk creation - genuine user input that starts User chunks. + * + * Returns true if message should create a User chunk: + * - type='user' + * - isMeta!=true + * - Has text/image content + * - Content does NOT contain: , , + * - Content MAY contain: (slash commands like /model ARE user input) + * + * Example User chunk messages: + * - "Help me debug this code" + * - "/model Switch to sonnet" + * + * NOT User chunks: + * - "Set model to..." -> System chunk + * - "..." -> Hard noise + * - "..." -> Hard noise + */ +export function isParsedUserChunkMessage(msg: ParsedMessage): boolean { + if (msg.type !== 'user') return false; + if (msg.isMeta === true) return false; + if (isParsedTeammateMessage(msg)) return false; + + const content = msg.content; + + // Check string content + if (typeof content === 'string') { + const trimmed = content.trim(); + + // Exclude messages that are system output or system metadata + // These tags indicate system-generated content, not user input + for (const tag of SYSTEM_OUTPUT_TAGS) { + if (trimmed.startsWith(tag)) { + return false; + } + } + + // is ALLOWED - it's user-initiated slash commands + // Remaining content is genuine user input + return trimmed.length > 0; + } + + // Array content format (newer sessions) + if (Array.isArray(content)) { + // Must contain text or image blocks for real user input + const hasUserContent = content.some((block) => block.type === 'text' || block.type === 'image'); + + if (!hasUserContent) { + return false; + } + + // Filter out user interruption messages (should be part of AI response flow) + // These have exactly 1 text block with content like "[Request interrupted by user]" + // or "[Request interrupted by user for tool use]" + if ( + content.length === 1 && + content[0].type === 'text' && + typeof content[0].text === 'string' && + content[0].text.startsWith('[Request interrupted by user') + ) { + return false; + } + + // Check text blocks for excluded tags + for (const block of content) { + if (block.type === 'text') { + const textBlock = block; + for (const tag of SYSTEM_OUTPUT_TAGS) { + if (textBlock.text.startsWith(tag)) { + return false; + } + } + } + } + + return true; + } + + return false; +} + +/** + * Type guard for System chunk creation - command output messages. + * + * Returns true if message should create a System chunk: + * - type='user' (confusingly, command output comes as user entries in JSONL) + * - Contains tag + * + * System chunks render on the LEFT side (like AI responses) with neutral gray styling. + * + * Example: + * ``` + * { + * type: "user", + * content: "Set model to sonnet..." + * } + * ``` + */ +export function isParsedSystemChunkMessage(msg: ParsedMessage): boolean { + if (msg.type !== 'user') return false; + + const content = msg.content; + + if (typeof content === 'string') { + return ( + content.startsWith(LOCAL_COMMAND_STDOUT_TAG) || content.startsWith(LOCAL_COMMAND_STDERR_TAG) + ); + } + + // Array content - check text blocks + if (Array.isArray(content)) { + return content.some( + (block) => block.type === 'text' && block.text.startsWith(LOCAL_COMMAND_STDOUT_TAG) + ); + } + + return false; +} + +/** + * Type guard to check if a ParsedMessage is an internal user message. + * This wraps the spec's type guard but works with ParsedMessage instead of UserEntry. + */ +export function isParsedInternalUserMessage(msg: ParsedMessage): boolean { + return msg.type === 'user' && msg.isMeta === true; +} + +/** + * Hard noise message (ParsedMessage version) - NEVER rendered or counted in the UI. + * This wraps isHardNoiseMessage() but works with ParsedMessage instead of ChatHistoryEntry. + * + * Filtered messages: + * - Messages with parentUuid: null (orphaned/root messages that shouldn't display) + * - e.g., compact_boundary system messages, root-level meta messages + * + * Filtered types: + * - 'system' entries + * - 'summary' entries + * - 'file-history-snapshot' entries + * - 'queue-operation' entries + * + * Filtered user messages: + * - Messages containing ONLY these system metadata tags (no real content): + * - + * - + * - Empty command output: + * - Interruption messages: [Request interrupted by user...] + * + * Filtered assistant messages: + * - Synthetic messages with model='' (system-generated placeholders) + */ +export function isParsedHardNoiseMessage(msg: ParsedMessage): boolean { + // Filter structural metadata types - these should never be displayed + if (msg.type === 'system') return true; + if (msg.type === 'summary') return true; + if (msg.type === 'file-history-snapshot') return true; + if (msg.type === 'queue-operation') return true; + + // Filter synthetic assistant messages (system-generated placeholders) + if (msg.type === 'assistant' && msg.model === '') { + return true; + } + + // Filter user messages with ONLY system metadata tags (no real content) + if (msg.type === 'user') { + const content = msg.content; + + if (typeof content === 'string') { + // Check if content contains ONLY noise tags (trim whitespace) + const trimmedContent = content.trim(); + + // If the content is wrapped in a noise tag, it's hard noise + for (const tag of HARD_NOISE_TAGS) { + const openTag = tag; + const closeTag = tag.replace('<', 'content + */ +const TEAMMATE_MESSAGE_REGEX = /^ block.type === 'text' && TEAMMATE_MESSAGE_REGEX.test(block.text.trim()) + ); + } + return false; +} diff --git a/src/main/utils/contextAccumulator.ts b/src/main/utils/contextAccumulator.ts new file mode 100644 index 00000000..a768b30c --- /dev/null +++ b/src/main/utils/contextAccumulator.ts @@ -0,0 +1,34 @@ +import { type ParsedMessage, type SemanticStep } from '../types'; + +/** + * Calculate context for each step using its source message's usage data. + * Each step's context is calculated independently from its source message. + */ +export function calculateStepContext(steps: SemanticStep[], messages: ParsedMessage[]): void { + for (const step of steps) { + // Find source message for this step + const msg = messages.find((m) => m.uuid === step.sourceMessageId); + + // Calculate context from message usage + if (msg?.usage) { + const cacheRead = msg.usage.cache_read_input_tokens ?? 0; + const cacheCreation = msg.usage.cache_creation_input_tokens ?? 0; + const inputTokens = msg.usage.input_tokens ?? 0; + + // Context = input tokens sent to API (cache_read + cache_creation + regular input) + step.accumulatedContext = inputTokens + cacheRead + cacheCreation; + } else if (step.tokens) { + // For steps that already have token info (like subagents) + step.accumulatedContext = (step.tokens.input ?? 0) + (step.tokens.cached ?? 0); + } + + // Individual step doesn't contribute tokens (message-level tracking) + step.contextTokens = 0; + step.tokenBreakdown = { + input: 0, + output: 0, + cacheRead: 0, + cacheCreation: 0, + }; + } +} diff --git a/src/main/utils/jsonl.ts b/src/main/utils/jsonl.ts new file mode 100644 index 00000000..1bf314cd --- /dev/null +++ b/src/main/utils/jsonl.ts @@ -0,0 +1,473 @@ +/** + * Utilities for parsing JSONL (JSON Lines) files used by Claude Code sessions. + * + * JSONL format: One JSON object per line + * - Each line is a complete, valid JSON object + * - Lines are separated by newline characters + * - Empty lines should be skipped + */ + +import { isCommandOutputContent, sanitizeDisplayContent } from '@shared/utils/contentSanitizer'; +import { createLogger } from '@shared/utils/logger'; +import * as fs from 'fs'; +import * as readline from 'readline'; + +const logger = createLogger('Util:jsonl'); + +import { + type ChatHistoryEntry, + type ContentBlock, + EMPTY_METRICS, + isConversationalEntry, + isParsedUserChunkMessage, + isTextContent, + type MessageType, + type ParsedMessage, + type SessionMetrics, + type TokenUsage, + type ToolCall, +} from '../types'; + +// Import from extracted modules +import { extractToolCalls, extractToolResults } from './toolExtraction'; + +// Re-export for backwards compatibility +export { extractCwd } from './metadataExtraction'; +export { checkMessagesOngoing } from './sessionStateDetection'; + +// ============================================================================= +// Core Parsing Functions +// ============================================================================= + +/** + * Parse a JSONL file line by line using streaming. + * This avoids loading the entire file into memory. + */ +export async function parseJsonlFile(filePath: string): Promise { + const messages: ParsedMessage[] = []; + + if (!fs.existsSync(filePath)) { + return messages; + } + + const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' }); + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + for await (const line of rl) { + if (!line.trim()) continue; + + try { + const parsed = parseJsonlLine(line); + if (parsed) { + messages.push(parsed); + } + } catch (error) { + logger.error(`Error parsing line in ${filePath}:`, error); + } + } + + return messages; +} + +/** + * Parse a single JSONL line into a ParsedMessage. + * Returns null for invalid/unsupported lines. + */ +export function parseJsonlLine(line: string): ParsedMessage | null { + if (!line.trim()) { + return null; + } + + const entry = JSON.parse(line) as ChatHistoryEntry; + return parseChatHistoryEntry(entry); +} + +// ============================================================================= +// Entry Parsing +// ============================================================================= + +/** + * Parse a single JSONL entry into a ParsedMessage. + */ +function parseChatHistoryEntry(entry: ChatHistoryEntry): ParsedMessage | null { + // Skip entries without uuid (usually metadata) + if (!entry.uuid) { + return null; + } + + const type = parseMessageType(entry.type); + if (!type) { + return null; + } + + // Handle different entry types + let content: string | ContentBlock[] = ''; + let role: string | undefined; + let usage: TokenUsage | undefined; + let model: string | undefined; + let cwd: string | undefined; + let gitBranch: string | undefined; + let agentId: string | undefined; + let isSidechain = false; + let isMeta = false; + let userType: string | undefined; + let sourceToolUseID: string | undefined; + let sourceToolAssistantUUID: string | undefined; + let toolUseResult: Record | undefined; + let parentUuid: string | null = null; + + // Extract properties based on entry type + let isCompactSummary = false; + if (isConversationalEntry(entry)) { + // Common properties from ConversationalEntry base + cwd = entry.cwd; + gitBranch = entry.gitBranch; + isSidechain = entry.isSidechain ?? false; + userType = entry.userType; + parentUuid = entry.parentUuid ?? null; + + // Type-specific properties + if (entry.type === 'user') { + content = entry.message.content ?? ''; + role = entry.message.role; + agentId = entry.agentId; + isMeta = entry.isMeta ?? false; + sourceToolUseID = entry.sourceToolUseID; + sourceToolAssistantUUID = entry.sourceToolAssistantUUID; + toolUseResult = entry.toolUseResult; + // Check for isCompactSummary on user entry (may exist on raw JSONL) + isCompactSummary = 'isCompactSummary' in entry && entry.isCompactSummary === true; + } else if (entry.type === 'assistant') { + content = entry.message.content; + role = entry.message.role; + usage = entry.message.usage; + model = entry.message.model; + agentId = entry.agentId; + } else if (entry.type === 'system') { + isMeta = entry.isMeta ?? false; + } + } + + // Extract tool calls and results + const toolCalls = extractToolCalls(content); + const toolResultsList = extractToolResults(content); + + return { + uuid: entry.uuid, + parentUuid, + type, + timestamp: entry.timestamp ? new Date(entry.timestamp) : new Date(), + role, + content, + usage, + model, + // Metadata + cwd, + gitBranch, + agentId, + isSidechain, + isMeta, + userType, + isCompactSummary, + // Tool info + toolCalls, + toolResults: toolResultsList, + sourceToolUseID, + sourceToolAssistantUUID, + toolUseResult, + }; +} + +/** + * Parse message type string into enum. + */ +function parseMessageType(type?: string): MessageType | null { + switch (type) { + case 'user': + return 'user'; + case 'assistant': + return 'assistant'; + case 'system': + return 'system'; + case 'summary': + return 'summary'; + case 'file-history-snapshot': + return 'file-history-snapshot'; + case 'queue-operation': + return 'queue-operation'; + default: + // Unknown types are skipped + return null; + } +} + +// ============================================================================= +// Metrics Calculation +// ============================================================================= + +/** + * Calculate session metrics from parsed messages. + */ +export function calculateMetrics(messages: ParsedMessage[]): SessionMetrics { + if (messages.length === 0) { + return { ...EMPTY_METRICS }; + } + + let inputTokens = 0; + let outputTokens = 0; + let cacheReadTokens = 0; + let cacheCreationTokens = 0; + const costUsd = 0; + + // Get timestamps for duration (loop instead of Math.min/max spread to avoid stack overflow on large sessions) + const timestamps = messages.map((m) => m.timestamp.getTime()).filter((t) => !isNaN(t)); + + let minTime = 0; + let maxTime = 0; + if (timestamps.length > 0) { + minTime = timestamps[0]; + maxTime = timestamps[0]; + for (let i = 1; i < timestamps.length; i++) { + if (timestamps[i] < minTime) minTime = timestamps[i]; + if (timestamps[i] > maxTime) maxTime = timestamps[i]; + } + } + + for (const msg of messages) { + if (msg.usage) { + inputTokens += msg.usage.input_tokens ?? 0; + outputTokens += msg.usage.output_tokens ?? 0; + cacheReadTokens += msg.usage.cache_read_input_tokens ?? 0; + cacheCreationTokens += msg.usage.cache_creation_input_tokens ?? 0; + } + } + + return { + durationMs: maxTime - minTime, + totalTokens: inputTokens + cacheCreationTokens + cacheReadTokens + outputTokens, + inputTokens, + outputTokens, + cacheReadTokens, + cacheCreationTokens, + messageCount: messages.length, + costUsd: costUsd > 0 ? costUsd : undefined, + }; +} + +// ============================================================================= +// Utility Functions +// ============================================================================= + +/** + * Extract text content from a message for display. + * This version applies content sanitization to filter XML-like tags. + */ +export function extractTextContent(message: ParsedMessage): string { + let rawText: string; + + if (typeof message.content === 'string') { + rawText = message.content; + } else { + rawText = message.content + .filter(isTextContent) + .map((block) => block.text) + .join('\n'); + } + + // Apply sanitization to remove XML-like tags for display + return sanitizeDisplayContent(rawText); +} + +/** + * Get all Task calls from a list of messages. + */ +export function getTaskCalls(messages: ParsedMessage[]): ToolCall[] { + return messages.flatMap((m) => m.toolCalls.filter((tc) => tc.isTask)); +} + +export interface SessionFileMetadata { + firstUserMessage: { text: string; timestamp: string } | null; + messageCount: number; + isOngoing: boolean; + gitBranch: string | null; +} + +/** + * Analyze key session metadata in a single streaming pass. + * This avoids multiple file scans when listing sessions. + */ +export async function analyzeSessionFileMetadata(filePath: string): Promise { + if (!fs.existsSync(filePath)) { + return { + firstUserMessage: null, + messageCount: 0, + isOngoing: false, + gitBranch: null, + }; + } + + const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' }); + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + let firstUserMessage: { text: string; timestamp: string } | null = null; + let firstCommandMessage: { text: string; timestamp: string } | null = null; + let messageCount = 0; + let gitBranch: string | null = null; + + let activityIndex = 0; + let lastEndingIndex = -1; + let hasAnyOngoingActivity = false; + let hasActivityAfterLastEnding = false; + // Track tool_use IDs that are shutdown responses so their tool_results are also ending events + const shutdownToolIds = new Set(); + + for await (const line of rl) { + const trimmed = line.trim(); + if (!trimmed) { + continue; + } + + let entry: ChatHistoryEntry; + try { + entry = JSON.parse(trimmed) as ChatHistoryEntry; + } catch { + continue; + } + + const parsed = parseChatHistoryEntry(entry); + if (!parsed) { + continue; + } + + if (isParsedUserChunkMessage(parsed)) { + messageCount++; + } + + if (!gitBranch && 'gitBranch' in entry && entry.gitBranch) { + gitBranch = entry.gitBranch; + } + + if (!firstUserMessage && entry.type === 'user') { + const content = entry.message?.content; + if (typeof content === 'string') { + if (isCommandOutputContent(content)) { + // Skip + } else if (content.startsWith('[Request interrupted by user')) { + // Skip interruption messages + } else if (content.startsWith('')) { + if (!firstCommandMessage) { + const commandMatch = /\/([^<]+)<\/command-name>/.exec(content); + const commandName = commandMatch ? `/${commandMatch[1]}` : '/command'; + firstCommandMessage = { + text: commandName, + timestamp: entry.timestamp ?? new Date().toISOString(), + }; + } + } else { + const sanitized = sanitizeDisplayContent(content); + if (sanitized.length > 0) { + firstUserMessage = { + text: sanitized.substring(0, 500), + timestamp: entry.timestamp ?? new Date().toISOString(), + }; + } + } + } else if (Array.isArray(content)) { + const textContent = content + .filter(isTextContent) + .map((b) => b.text) + .join(' '); + if ( + textContent && + !textContent.startsWith('') && + !textContent.startsWith('[Request interrupted by user') + ) { + const sanitized = sanitizeDisplayContent(textContent); + if (sanitized.length > 0) { + firstUserMessage = { + text: sanitized.substring(0, 500), + timestamp: entry.timestamp ?? new Date().toISOString(), + }; + } + } + } + } + + // Ongoing detection with one-pass activity tracking. + if (parsed.type === 'assistant' && Array.isArray(parsed.content)) { + for (const block of parsed.content) { + if (block.type === 'thinking' && block.thinking) { + hasAnyOngoingActivity = true; + if (lastEndingIndex >= 0) { + hasActivityAfterLastEnding = true; + } + activityIndex++; + } else if (block.type === 'tool_use' && block.id) { + if (block.name === 'ExitPlanMode') { + lastEndingIndex = activityIndex++; + hasActivityAfterLastEnding = false; + } else if ( + block.name === 'SendMessage' && + block.input?.type === 'shutdown_response' && + block.input?.approve === true + ) { + // SendMessage shutdown_response = agent is shutting down (ending event) + shutdownToolIds.add(block.id); + lastEndingIndex = activityIndex++; + hasActivityAfterLastEnding = false; + } else { + hasAnyOngoingActivity = true; + if (lastEndingIndex >= 0) { + hasActivityAfterLastEnding = true; + } + activityIndex++; + } + } else if (block.type === 'text' && block.text && String(block.text).trim().length > 0) { + lastEndingIndex = activityIndex++; + hasActivityAfterLastEnding = false; + } + } + } else if (parsed.type === 'user' && Array.isArray(parsed.content)) { + // Check if this is a user-rejected tool use (ending event, not ongoing activity) + const isRejection = + 'toolUseResult' in entry && + (entry as unknown as Record).toolUseResult === 'User rejected tool use'; + + for (const block of parsed.content) { + if (block.type === 'tool_result' && block.tool_use_id) { + if (shutdownToolIds.has(block.tool_use_id) || isRejection) { + // Shutdown tool result or user rejection = ending event + lastEndingIndex = activityIndex++; + hasActivityAfterLastEnding = false; + } else { + hasAnyOngoingActivity = true; + if (lastEndingIndex >= 0) { + hasActivityAfterLastEnding = true; + } + activityIndex++; + } + } else if ( + block.type === 'text' && + typeof block.text === 'string' && + block.text.startsWith('[Request interrupted by user') + ) { + lastEndingIndex = activityIndex++; + hasActivityAfterLastEnding = false; + } + } + } + } + + return { + firstUserMessage: firstUserMessage ?? firstCommandMessage, + messageCount, + isOngoing: lastEndingIndex === -1 ? hasAnyOngoingActivity : hasActivityAfterLastEnding, + gitBranch, + }; +} diff --git a/src/main/utils/metadataExtraction.ts b/src/main/utils/metadataExtraction.ts new file mode 100644 index 00000000..41a24dec --- /dev/null +++ b/src/main/utils/metadataExtraction.ts @@ -0,0 +1,44 @@ +/** + * Metadata extraction utilities for parsing first messages and session context from JSONL files. + */ + +import { createLogger } from '@shared/utils/logger'; +import * as fs from 'fs'; +import * as readline from 'readline'; + +import { type ChatHistoryEntry } from '../types'; + +const logger = createLogger('Util:metadataExtraction'); + +/** + * Extract CWD (current working directory) from the first entry. + * Used to get the actual project path from encoded directory names. + */ +export async function extractCwd(filePath: string): Promise { + if (!fs.existsSync(filePath)) { + return null; + } + + const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' }); + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + try { + for await (const line of rl) { + if (!line.trim()) continue; + + const entry = JSON.parse(line) as ChatHistoryEntry; + // Only conversational entries have cwd + if ('cwd' in entry && entry.cwd) { + fileStream.destroy(); + return entry.cwd; + } + } + } catch (error) { + logger.error(`Error extracting cwd from ${filePath}:`, error); + } + + return null; +} diff --git a/src/main/utils/pathDecoder.ts b/src/main/utils/pathDecoder.ts new file mode 100644 index 00000000..fea1038b --- /dev/null +++ b/src/main/utils/pathDecoder.ts @@ -0,0 +1,228 @@ +import * as os from 'os'; +import * as path from 'path'; + +/** + * Utility functions for encoding/decoding Claude Code project directory names. + * + * Directory naming pattern: + * - Path: /Users/username/projectname + * - Encoded: -Users-username-projectname + * + * IMPORTANT: This encoding is LOSSY for paths containing dashes. + * For accurate path resolution, use extractCwd() from jsonl.ts to read + * the actual cwd from session files. + */ + +// ============================================================================= +// Core Encoding/Decoding +// ============================================================================= + +/** + * Encodes an absolute path into Claude Code's directory naming format. + * Replaces all path separators (/ and \) with dashes. + * + * @param absolutePath - The absolute path to encode (e.g., "/Users/username/projectname") + * @returns The encoded directory name (e.g., "-Users-username-projectname") + */ +export function encodePath(absolutePath: string): string { + if (!absolutePath) { + return ''; + } + + const encoded = absolutePath.replace(/[/\\]/g, '-'); + + // Ensure leading dash for absolute paths + return encoded.startsWith('-') ? encoded : `-${encoded}`; +} + +/** + * Decodes a project directory name to its original path. + * Note: This is a best-effort decode. Paths with dashes cannot be decoded accurately. + * + * @param encodedName - The encoded directory name (e.g., "-Users-username-projectname") + * @returns The decoded path (e.g., "/Users/username/projectname") + */ +export function decodePath(encodedName: string): string { + if (!encodedName) { + return ''; + } + + // Remove leading dash if present (indicates absolute path) + const withoutLeadingDash = encodedName.startsWith('-') ? encodedName.slice(1) : encodedName; + + // Replace dashes with slashes + const decodedPath = withoutLeadingDash.replace(/-/g, '/'); + + // Windows paths may decode to "C:/..." + if (/^[a-zA-Z]:\//.test(decodedPath)) { + return decodedPath; + } + + // Ensure leading slash for POSIX-style absolute paths + return decodedPath.startsWith('/') ? decodedPath : `/${decodedPath}`; +} + +/** + * Extract the project name (last path segment) from an encoded directory name. + * + * @param encodedName - The encoded directory name + * @returns The project name + */ +export function extractProjectName(encodedName: string): string { + const decoded = decodePath(encodedName); + const segments = decoded.split('/').filter(Boolean); + return segments[segments.length - 1] || encodedName; +} + +// ============================================================================= +// Validation +// ============================================================================= + +/** + * Validates if a directory name follows the Claude Code encoding pattern. + * + * @param encodedName - The directory name to validate + * @returns true if valid, false otherwise + */ +export function isValidEncodedPath(encodedName: string): boolean { + if (!encodedName) { + return false; + } + + // Must start with a dash (indicates absolute path) + if (!encodedName.startsWith('-')) { + return false; + } + + // Allow only expected encoded characters: + // - alphanumeric, underscores, dots, spaces, dashes + // - optional ":" for Windows drive notation (e.g., -C:-Users-name-project) + const validPattern = /^-[a-zA-Z0-9_.\s:-]+$/; + if (!validPattern.test(encodedName)) { + return false; + } + + // Windows-style drive syntax is allowed only at the beginning after "-" + // e.g. "-C:-Users-name-project". Reject stray ":" elsewhere. + const firstColon = encodedName.indexOf(':'); + if (firstColon === -1) { + return true; + } + + if (!/^-[a-zA-Z]:/.test(encodedName)) { + return false; + } + + return !encodedName.includes(':', firstColon + 1); +} + +/** + * Validates a project ID that may be either a plain encoded path or + * a composite subproject ID (`{encodedPath}::{8-char-hex}`). + * + * @param projectId - The project ID to validate + * @returns true if valid + */ +export function isValidProjectId(projectId: string): boolean { + if (!projectId) { + return false; + } + + const sep = projectId.indexOf('::'); + if (sep === -1) { + // Plain encoded path + return isValidEncodedPath(projectId); + } + + // Composite ID: validate base part and hash suffix + const basePart = projectId.slice(0, sep); + const hashPart = projectId.slice(sep + 2); + + return isValidEncodedPath(basePart) && /^[a-f0-9]{8}$/.test(hashPart); +} + +/** + * Extract the base directory (encoded path) from a project ID. + * For composite IDs (`{encoded}::{hash}`), returns the encoded part. + * For plain IDs, returns the ID as-is. + */ +export function extractBaseDir(projectId: string): string { + const sep = projectId.indexOf('::'); + if (sep !== -1) { + return projectId.slice(0, sep); + } + return projectId; +} + +// ============================================================================= +// Session ID Extraction +// ============================================================================= + +/** + * Extract session ID from a JSONL filename. + * + * @param filename - The filename (e.g., "abc123.jsonl") + * @returns The session ID (e.g., "abc123") + */ +export function extractSessionId(filename: string): string { + return filename.replace(/\.jsonl$/, ''); +} + +// ============================================================================= +// Path Construction +// ============================================================================= + +/** + * Construct the path to a session JSONL file. + * Handles composite project IDs by extracting the base directory. + */ +export function buildSessionPath(basePath: string, projectId: string, sessionId: string): string { + return path.join(basePath, extractBaseDir(projectId), `${sessionId}.jsonl`); +} + +/** + * Construct the path to a session's subagents directory. + * Handles composite project IDs by extracting the base directory. + */ +export function buildSubagentsPath(basePath: string, projectId: string, sessionId: string): string { + return path.join(basePath, extractBaseDir(projectId), sessionId, 'subagents'); +} + +/** + * Construct the path to a task list file (stored in todos directory). + */ +export function buildTodoPath(claudeBasePath: string, sessionId: string): string { + return path.join(claudeBasePath, 'todos', `${sessionId}.json`); +} + +// ============================================================================= +// Home Directory +// ============================================================================= + +/** + * Get the user's home directory. + */ +function getHomeDir(): string { + return process.env.HOME || process.env.USERPROFILE || os.homedir() || '/'; +} + +/** + * Get the Claude config base path (~/.claude). + */ +function getClaudeBasePath(): string { + return path.join(getHomeDir(), '.claude'); +} + +/** + * Get the projects directory path (~/.claude/projects). + */ +export function getProjectsBasePath(): string { + return path.join(getClaudeBasePath(), 'projects'); +} + +/** + * Get the todos directory path (~/.claude/todos). + */ +export function getTodosBasePath(): string { + return path.join(getClaudeBasePath(), 'todos'); +} diff --git a/src/main/utils/pathValidation.ts b/src/main/utils/pathValidation.ts new file mode 100644 index 00000000..985fb326 --- /dev/null +++ b/src/main/utils/pathValidation.ts @@ -0,0 +1,265 @@ +/** + * Path Validation Utilities. + * + * Provides security sandboxing for file path access to prevent + * unauthorized access to sensitive system files. + */ + +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +/** + * Sensitive file patterns that should never be accessible. + * These are checked against the normalized absolute path. + */ +const SENSITIVE_PATTERNS: RegExp[] = [ + // SSH keys and config + /[/\\]\.ssh[/\\]/i, + // AWS credentials + /[/\\]\.aws[/\\]/i, + // GCP credentials + /[/\\]\.config[/\\]gcloud[/\\]/i, + // Azure credentials + /[/\\]\.azure[/\\]/i, + // Environment files (anywhere in path) + /[/\\]\.env($|\.)/i, + // Git credentials + /[/\\]\.git-credentials$/i, + /[/\\]\.gitconfig$/i, + // NPM tokens + /[/\\]\.npmrc$/i, + // Docker credentials + /[/\\]\.docker[/\\]config\.json$/i, + // Kubernetes config + /[/\\]\.kube[/\\]config$/i, + // Password files + /[/\\]\.password/i, + /[/\\]\.secret/i, + // Private keys + /[/\\]id_rsa$/i, + /[/\\]id_ed25519$/i, + /[/\\]id_ecdsa$/i, + /[/\\][^/\\]*\.pem$/i, + /[/\\][^/\\]*\.key$/i, + // System files + /^\/etc\/passwd$/, + /^\/etc\/shadow$/, + // Credentials in filename + /credentials\.json$/i, + /secrets\.json$/i, + /tokens\.json$/i, +]; + +/** + * Result of path validation. + */ +export interface PathValidationResult { + valid: boolean; + error?: string; + normalizedPath?: string; +} + +function normalizeForCompare(input: string, isWindows: boolean): string { + const normalized = path.normalize(input); + return isWindows ? normalized.toLowerCase() : normalized; +} + +function isPathWithinRoot(targetPath: string, rootPath: string): boolean { + return targetPath === rootPath || targetPath.startsWith(rootPath + path.sep); +} + +function resolveRealPathIfExists(inputPath: string): string | null { + try { + return fs.realpathSync.native(inputPath); + } catch { + return null; + } +} + +/** + * Checks if a path matches any sensitive file patterns. + * + * @param normalizedPath - The normalized absolute path to check + * @returns true if path matches a sensitive pattern + */ +function matchesSensitivePattern(normalizedPath: string): boolean { + return SENSITIVE_PATTERNS.some((pattern) => pattern.test(normalizedPath)); +} + +/** + * Checks if a path is within allowed directories. + * + * Allowed directories: + * - The project path itself + * - The ~/.claude directory (for session data) + * + * @param normalizedPath - The normalized absolute path to check + * @param projectPath - The project root path (can be null for global access) + * @returns true if path is within allowed directories + */ +export function isPathWithinAllowedDirectories( + normalizedPath: string, + projectPath: string | null +): boolean { + const isWindows = process.platform === 'win32'; + const normalizedTarget = normalizeForCompare(normalizedPath, isWindows); + const homeDir = os.homedir(); + const claudeDir = path.join(homeDir, '.claude'); + const normalizedClaudeDir = normalizeForCompare(claudeDir, isWindows); + + // Always allow access to ~/.claude for session data + if (isPathWithinRoot(normalizedTarget, normalizedClaudeDir)) { + return true; + } + + // If project path provided, allow access within project + if (projectPath) { + const normalizedProjectPath = normalizeForCompare(projectPath, isWindows); + if (isPathWithinRoot(normalizedTarget, normalizedProjectPath)) { + return true; + } + } + + return false; +} + +/** + * Validates a file path for safe reading. + * + * Security checks performed: + * 1. Path must be absolute + * 2. Path traversal prevention (no ..) + * 3. Must be within allowed directories (project or ~/.claude) + * 4. Must not match sensitive file patterns + * + * @param filePath - The file path to validate + * @param projectPath - The project root path (can be null for global access) + * @returns Validation result with normalized path if valid + */ +export function validateFilePath( + filePath: string, + projectPath: string | null +): PathValidationResult { + // Must be a non-empty string + if (!filePath || typeof filePath !== 'string') { + return { valid: false, error: 'Invalid file path' }; + } + + // Expand ~ to home directory + const expandedPath = filePath.startsWith('~') + ? path.join(os.homedir(), filePath.slice(1)) + : filePath; + + // Must be absolute path + const normalizedInput = path.normalize(expandedPath); + if (!path.isAbsolute(normalizedInput)) { + return { valid: false, error: 'Path must be absolute' }; + } + + // Normalize and resolve the path to remove traversal segments safely + const normalizedPath = path.resolve(normalizedInput); + + // Check against sensitive patterns + if (matchesSensitivePattern(normalizedPath)) { + return { valid: false, error: 'Access to sensitive files is not allowed' }; + } + + // Check if within allowed directories + if (!isPathWithinAllowedDirectories(normalizedPath, projectPath)) { + return { + valid: false, + error: 'Path is outside allowed directories (project or ~/.claude)', + }; + } + + // If target exists, validate real path containment to prevent symlink escapes. + const realTargetPath = resolveRealPathIfExists(normalizedPath); + if (realTargetPath) { + const isWindows = process.platform === 'win32'; + const normalizedRealTarget = normalizeForCompare(realTargetPath, isWindows); + if (matchesSensitivePattern(normalizedRealTarget)) { + return { valid: false, error: 'Access to sensitive files is not allowed' }; + } + + const realProjectPath = projectPath + ? (resolveRealPathIfExists(projectPath) ?? path.resolve(path.normalize(projectPath))) + : null; + + if (!isPathWithinAllowedDirectories(normalizedRealTarget, realProjectPath)) { + return { + valid: false, + error: 'Path is outside allowed directories (project or ~/.claude)', + }; + } + } + + return { valid: true, normalizedPath }; +} + +/** + * Validates a path for shell:openPath operation. + * More permissive than file reading - allows opening project directories + * and Claude data directories. + * + * @param targetPath - The path to open + * @param projectPath - The project root path (can be null) + * @returns Validation result + */ +export function validateOpenPath( + targetPath: string, + projectPath: string | null +): PathValidationResult { + if (!targetPath || typeof targetPath !== 'string') { + return { valid: false, error: 'Invalid path' }; + } + + // Expand ~ to home directory + const expandedPath = targetPath.startsWith('~') + ? path.join(os.homedir(), targetPath.slice(1)) + : targetPath; + + const normalizedPath = path.resolve(path.normalize(expandedPath)); + + // Must be absolute after expansion + if (!path.isAbsolute(normalizedPath)) { + return { valid: false, error: 'Path must be absolute' }; + } + + // Check against sensitive patterns (still block sensitive files) + if (matchesSensitivePattern(normalizedPath)) { + return { valid: false, error: 'Cannot open sensitive files' }; + } + + // For shell:openPath, we're more permissive but still require + // the path to be within project or claude directories + if (!isPathWithinAllowedDirectories(normalizedPath, projectPath)) { + return { + valid: false, + error: 'Path is outside allowed directories', + }; + } + + // If target exists, validate real path containment to prevent symlink escapes. + const realTargetPath = resolveRealPathIfExists(normalizedPath); + if (realTargetPath) { + const isWindows = process.platform === 'win32'; + const normalizedRealTarget = normalizeForCompare(realTargetPath, isWindows); + if (matchesSensitivePattern(normalizedRealTarget)) { + return { valid: false, error: 'Cannot open sensitive files' }; + } + + const realProjectPath = projectPath + ? (resolveRealPathIfExists(projectPath) ?? path.resolve(path.normalize(projectPath))) + : null; + + if (!isPathWithinAllowedDirectories(normalizedRealTarget, realProjectPath)) { + return { + valid: false, + error: 'Path is outside allowed directories', + }; + } + } + + return { valid: true, normalizedPath }; +} diff --git a/src/main/utils/regexValidation.ts b/src/main/utils/regexValidation.ts new file mode 100644 index 00000000..0f6cdcd1 --- /dev/null +++ b/src/main/utils/regexValidation.ts @@ -0,0 +1,182 @@ +/** + * Regex Validation Utilities. + * + * Provides security validation for user-supplied regex patterns + * to prevent ReDoS (Regular Expression Denial of Service) attacks. + */ + +/** + * Maximum allowed length for a regex pattern. + */ +const MAX_PATTERN_LENGTH = 100; + +/** + * Patterns that indicate potentially problematic regex constructs. + * These can cause exponential backtracking (ReDoS). + */ +const DANGEROUS_PATTERNS: RegExp[] = [ + // Nested quantifiers: (a+)+, (a*)+, (a+)*, (a*)* + /\([^)]{0,50}[+*][^)]{0,50}\)[+*]/, + // Overlapping alternation with quantifiers: (a|a)+ + /\([^)|]{0,50}\|[^)]{0,50}\)[+*]/, + // Multiple quantifiers on same group: a{1,}+ + /[+*]\{/, + /\}[+*]/, + // Backreferences with quantifiers (can cause exponential time) + /\\[1-9][+*]/, + // Very long character classes with quantifiers + /\[[^\]]{20}\][+*]/, +]; + +/** + * Characters that need to be balanced in a valid regex. + */ +const BALANCED_PAIRS: [string, string][] = [ + ['(', ')'], + ['[', ']'], + ['{', '}'], +]; + +/** + * Result of regex pattern validation. + */ +export interface RegexValidationResult { + valid: boolean; + error?: string; +} + +/** + * Checks if brackets in a string are balanced. + */ +function areBracketsBalanced(pattern: string): boolean { + const stack: string[] = []; + const openBrackets = new Map(BALANCED_PAIRS.map(([open, close]) => [open, close])); + const closeBrackets = new Map(BALANCED_PAIRS.map(([open, close]) => [close, open])); + + let escaped = false; + let inCharClass = false; + + for (const char of pattern) { + if (escaped) { + escaped = false; + continue; + } + + if (char === '\\') { + escaped = true; + continue; + } + + // Track character class state + if (char === '[' && !inCharClass) { + inCharClass = true; + stack.push(char); + continue; + } + + if (char === ']' && inCharClass) { + inCharClass = false; + if (stack.length === 0 || stack[stack.length - 1] !== '[') { + return false; + } + stack.pop(); + continue; + } + + // Skip bracket matching inside character classes + if (inCharClass) { + continue; + } + + if (openBrackets.has(char)) { + stack.push(char); + } else if (closeBrackets.has(char)) { + const expectedOpen = closeBrackets.get(char); + if (stack.length === 0 || stack[stack.length - 1] !== expectedOpen) { + return false; + } + stack.pop(); + } + } + + return stack.length === 0; +} + +/** + * Validates a regex pattern for safety and correctness. + * + * Security checks performed: + * 1. Length limit (max 100 chars) + * 2. Dangerous pattern detection (nested quantifiers, etc.) + * 3. Balanced brackets + * 4. Valid regex syntax (via RegExp constructor) + * + * @param pattern - The regex pattern to validate + * @returns Validation result with error message if invalid + */ +export function validateRegexPattern(pattern: string): RegexValidationResult { + // Empty pattern check + if (!pattern || typeof pattern !== 'string') { + return { valid: false, error: 'Pattern must be a non-empty string' }; + } + + // Length check + if (pattern.length > MAX_PATTERN_LENGTH) { + return { + valid: false, + error: `Pattern too long (max ${MAX_PATTERN_LENGTH} characters)`, + }; + } + + // Check for dangerous patterns that could cause ReDoS + for (const dangerous of DANGEROUS_PATTERNS) { + if (dangerous.test(pattern)) { + return { + valid: false, + error: 'Pattern contains constructs that could cause performance issues', + }; + } + } + + // Check bracket balance + if (!areBracketsBalanced(pattern)) { + return { + valid: false, + error: 'Pattern has unbalanced brackets', + }; + } + + // Try to compile the regex to check for syntax errors + try { + new RegExp(pattern); + } catch (e) { + const message = e instanceof Error ? e.message : 'Unknown error'; + return { + valid: false, + error: `Invalid regex syntax: ${message}`, + }; + } + + return { valid: true }; +} + +/** + * Creates a safe RegExp from a pattern, returning null if invalid. + * This is a convenience wrapper that validates and creates the regex. + * + * @param pattern - The regex pattern + * @param flags - Optional regex flags (default: 'i' for case-insensitive) + * @returns The compiled RegExp or null if validation fails + */ +export function createSafeRegExp(pattern: string, flags: string = 'i'): RegExp | null { + const validation = validateRegexPattern(pattern); + if (!validation.valid) { + return null; + } + + try { + return new RegExp(pattern, flags); + } catch { + return null; + } +} diff --git a/src/main/utils/sessionStateDetection.ts b/src/main/utils/sessionStateDetection.ts new file mode 100644 index 00000000..6e953338 --- /dev/null +++ b/src/main/utils/sessionStateDetection.ts @@ -0,0 +1,151 @@ +/** + * Session state detection utilities for determining if sessions are ongoing. + */ + +import { type ParsedMessage } from '../types'; + +/** Activity types for tracking session state */ +type ActivityType = + | 'text_output' + | 'thinking' + | 'tool_use' + | 'tool_result' + | 'interruption' + | 'exit_plan_mode'; + +/** Activity entry with type and order index */ +interface Activity { + type: ActivityType; + index: number; +} + +/** Check if a toolUseResult value indicates a user-rejected tool use */ +function isToolUseRejection(toolUseResult: unknown): boolean { + return toolUseResult === 'User rejected tool use'; +} + +/** Check if a tool_use block is a SendMessage shutdown_response with approve: true */ +function isShutdownResponse(block: { name?: string; input?: Record }): boolean { + return ( + block.name === 'SendMessage' && + block.input?.type === 'shutdown_response' && + block.input?.approve === true + ); +} + +/** + * Check if activities indicate an ongoing session. + * Shared logic used by checkMessagesOngoing. + * + * @param activities - Array of tracked activities in order + * @returns boolean - true if ongoing + */ +function isOngoingFromActivities(activities: Activity[]): boolean { + if (activities.length === 0) { + return false; + } + + // Find the index of the last "ending" event (text_output, interruption, or exit_plan_mode) + let lastEndingIndex = -1; + for (let i = activities.length - 1; i >= 0; i--) { + const actType = activities[i].type; + if (actType === 'text_output' || actType === 'interruption' || actType === 'exit_plan_mode') { + lastEndingIndex = activities[i].index; + break; + } + } + + // If no ending event found, check if there's any AI activity at all + if (lastEndingIndex === -1) { + return activities.some( + (a) => a.type === 'thinking' || a.type === 'tool_use' || a.type === 'tool_result' + ); + } + + // Check if there are any AI activities AFTER the last ending event + for (const activity of activities) { + if ( + activity.index > lastEndingIndex && + (activity.type === 'thinking' || + activity.type === 'tool_use' || + activity.type === 'tool_result') + ) { + return true; + } + } + + return false; +} + +/** + * Check if messages indicate an ongoing session (AI response in progress). + * + * A session is considered "ongoing" if there are AI-related activities + * (thinking, tool_use, tool_result) AFTER the last "ending" event (text output or interruption). + * + * Special case: ExitPlanMode tool_use is treated as an ending event, not a continuation. + * This is because ExitPlanMode signals the end of plan mode and contains the final plan content. + * + * This is the core logic shared between session files and subagent messages. + * + * @param messages - Array of ParsedMessage to check + * @returns boolean - true if ongoing + */ +export function checkMessagesOngoing(messages: ParsedMessage[]): boolean { + const activities: Activity[] = []; + let activityIndex = 0; + // Track tool_use IDs that are shutdown responses so their tool_results are also ending events + const shutdownToolIds = new Set(); + + for (const msg of messages) { + if (msg.type === 'assistant' && Array.isArray(msg.content)) { + // Process assistant message content blocks + for (const block of msg.content) { + if (block.type === 'thinking' && block.thinking) { + activities.push({ type: 'thinking', index: activityIndex++ }); + } else if (block.type === 'tool_use' && block.id) { + // ExitPlanMode is a special ending tool - treat it like an ending event + if (block.name === 'ExitPlanMode') { + activities.push({ type: 'exit_plan_mode', index: activityIndex++ }); + } else if (isShutdownResponse(block)) { + // SendMessage shutdown_response = agent is shutting down (ending event) + shutdownToolIds.add(block.id); + activities.push({ type: 'interruption', index: activityIndex++ }); + } else { + activities.push({ type: 'tool_use', index: activityIndex++ }); + } + } else if (block.type === 'text' && block.text && String(block.text).trim().length > 0) { + activities.push({ type: 'text_output', index: activityIndex++ }); + } + } + } else if (msg.type === 'user' && Array.isArray(msg.content)) { + // Check if this is a user-rejected tool use (ending event, not ongoing activity) + const isRejection = isToolUseRejection(msg.toolUseResult); + + // Check for tool results and interruptions in internal user messages + for (const block of msg.content) { + if (block.type === 'tool_result' && block.tool_use_id) { + if (shutdownToolIds.has(block.tool_use_id)) { + // Shutdown tool result = ending event + activities.push({ type: 'interruption', index: activityIndex++ }); + } else if (isRejection) { + // User rejection = ending event (like interruption) + activities.push({ type: 'interruption', index: activityIndex++ }); + } else { + activities.push({ type: 'tool_result', index: activityIndex++ }); + } + } + // Check for interruption message - this ends the session + if ( + block.type === 'text' && + typeof block.text === 'string' && + block.text.startsWith('[Request interrupted by user') + ) { + activities.push({ type: 'interruption', index: activityIndex++ }); + } + } + } + } + + return isOngoingFromActivities(activities); +} diff --git a/src/main/utils/timelineGapFilling.ts b/src/main/utils/timelineGapFilling.ts new file mode 100644 index 00000000..c5b0fe1e --- /dev/null +++ b/src/main/utils/timelineGapFilling.ts @@ -0,0 +1,53 @@ +import { type SemanticStep } from '../types'; + +interface GapFillingInput { + steps: SemanticStep[]; + chunkStartTime: Date; + chunkEndTime: Date; +} + +/** + * Fill timeline gaps so steps extend to next step's start. + * Handles parallel steps (don't extend past each other). + * Preserves real timing for subagents. + */ +export function fillTimelineGaps(input: GapFillingInput): SemanticStep[] { + const { steps, chunkEndTime } = input; + + if (steps.length === 0) return []; + + // Sort by startTime + const sorted = [...steps].sort((a, b) => a.startTime.getTime() - b.startTime.getTime()); + + for (let i = 0; i < sorted.length; i++) { + const step = sorted[i]; + + // Keep original timing for subagents and steps with meaningful duration + if (step.type === 'subagent' && step.endTime && step.durationMs > 100) { + step.effectiveEndTime = step.endTime; + step.effectiveDurationMs = step.durationMs; + step.isGapFilled = false; + continue; + } + + // Find next non-parallel step + let nextStepStart: Date | null = null; + for (let j = i + 1; j < sorted.length; j++) { + const candidate = sorted[j]; + + // Skip parallel siblings (within 100ms window) + const timeDiff = candidate.startTime.getTime() - step.startTime.getTime(); + if (timeDiff < 100) continue; + + nextStepStart = candidate.startTime; + break; + } + + // Set effective end time + step.effectiveEndTime = nextStepStart ?? chunkEndTime; + step.effectiveDurationMs = step.effectiveEndTime.getTime() - step.startTime.getTime(); + step.isGapFilled = true; + } + + return sorted; +} diff --git a/src/main/utils/tokenizer.ts b/src/main/utils/tokenizer.ts new file mode 100644 index 00000000..9951fb4e --- /dev/null +++ b/src/main/utils/tokenizer.ts @@ -0,0 +1,46 @@ +/** + * Tokenizer utility for token counting. + * + * This module provides functions to estimate tokens in text content by + * dividing character length by 4. + * + * Usage: + * - Main process: Import and use directly + * - Renderer: Token counts should be pre-computed in main process and passed via IPC + */ + +/** + * Count tokens in a string by dividing length by 4. + * Uses character count estimation instead of exact tokenizer. + * + * @param text - The text to tokenize + * @returns Number of tokens (estimated) + */ +export function countTokens(text: string | undefined | null): number { + if (!text || text.length === 0) { + return 0; + } + + // Estimate tokens using character length / 4 approximation + return Math.ceil(text.length / 4); +} + +/** + * Count tokens for content that may be a string or array. + * Arrays are stringified before counting. + * + * @param content - String or array content + * @returns Number of tokens + */ +export function countContentTokens(content: string | unknown[] | undefined | null): number { + if (!content) { + return 0; + } + + if (typeof content === 'string') { + return countTokens(content); + } + + // For array content, stringify and count + return countTokens(JSON.stringify(content)); +} diff --git a/src/main/utils/toolExtraction.ts b/src/main/utils/toolExtraction.ts new file mode 100644 index 00000000..de963083 --- /dev/null +++ b/src/main/utils/toolExtraction.ts @@ -0,0 +1,63 @@ +/** + * Tool extraction utilities for parsing tool calls and results from JSONL content blocks. + */ + +import type { ContentBlock, ToolCall, ToolResult } from '../types'; + +/** + * Extract tool calls from content blocks. + */ +export function extractToolCalls(content: ContentBlock[] | string): ToolCall[] { + if (typeof content === 'string') { + return []; + } + + const toolCalls: ToolCall[] = []; + + for (const block of content) { + if (block.type === 'tool_use' && block.id && block.name) { + const input = block.input ?? {}; + const isTask = block.name === 'Task'; + + const toolCall: ToolCall = { + id: block.id, + name: block.name, + input, + isTask, + }; + + // Extract Task-specific info + if (isTask) { + toolCall.taskDescription = input.description as string | undefined; + toolCall.taskSubagentType = input.subagent_type as string | undefined; + } + + toolCalls.push(toolCall); + } + } + + return toolCalls; +} + +/** + * Extract tool results from content blocks. + */ +export function extractToolResults(content: ContentBlock[] | string): ToolResult[] { + if (typeof content === 'string') { + return []; + } + + const toolResults: ToolResult[] = []; + + for (const block of content) { + if (block.type === 'tool_result' && block.tool_use_id) { + toolResults.push({ + toolUseId: block.tool_use_id, + content: block.content ?? '', + isError: block.is_error ?? false, + }); + } + } + + return toolResults; +} diff --git a/src/preload/CLAUDE.md b/src/preload/CLAUDE.md new file mode 100644 index 00000000..bd5df472 --- /dev/null +++ b/src/preload/CLAUDE.md @@ -0,0 +1,61 @@ +# Preload Process + +Secure bridge between main and renderer processes via Electron's contextBridge. + +## Structure +- `index.ts` - ElectronAPI implementation +- `constants/ipcChannels.ts` - IPC channel name constants + +## ElectronAPI Organization +Groups exposed methods by domain: + +### Session APIs +- `getProjects()`, `getSessions()`, `getSessionsPaginated()` +- `getSessionDetail()`, `getSessionMetrics()`, `getWaterfallData()` +- `getSessionGroups()`, `searchSessions()`, `getAppVersion()` + +### Repository APIs +- `getRepositoryGroups()`, `getWorktreeSessions()` + +### Validation APIs +- `validatePath()`, `validateMentions()` + +### CLAUDE.md APIs +- `readClaudeMdFiles()`, `readDirectoryClaudeMd()`, `readMentionedFile()` + +### Notifications +- `notifications.{get,markRead,markAllRead,delete,clear,getUnreadCount}` +- `notifications.{onNew,onUpdated,onClicked}` - Event listeners + +### Config API +- `config.{get,update}` - Read/write config +- `config.{addTrigger,updateTrigger,removeTrigger,getTriggers,testTrigger}` +- `config.{addIgnoreRegex,removeIgnoreRegex,addIgnoreRepository,removeIgnoreRepository}` +- `config.{snooze,clearSnooze,selectFolders}` +- `config.{openInEditor,pinSession,unpinSession}` + +### Utilities +- `openPath()` - Shell operations +- `openExternal()` - Open URLs in browser +- `onFileChange()` - File watcher events +- `getZoomFactor()` - Get current zoom level +- `onZoomFactorChanged()` - Zoom change listener +- `session.scrollToLine()` - Deep link navigation + +## IPC Pattern +Config operations use `IpcResult` wrapper pattern: +```typescript +interface IpcResult { + success: boolean; + data?: T; + error?: string; +} +``` +The `invokeIpcWithResult()` helper unwraps and throws on failure. + +## Adding New IPC Methods +1. Define channel constant in `constants/ipcChannels.ts` +2. Implement handler in `src/main/ipc/{domain}.ts` +3. Register in `handlers.ts` via `register{Domain}Handlers()` +4. Add method to ElectronAPI in `preload/index.ts` +5. Update `@shared/types/ElectronAPI` if cross-process type needed diff --git a/src/preload/constants/ipcChannels.ts b/src/preload/constants/ipcChannels.ts new file mode 100644 index 00000000..845dee5a --- /dev/null +++ b/src/preload/constants/ipcChannels.ts @@ -0,0 +1,60 @@ +/** + * IPC Channel Constants + * + * Centralized IPC channel names to avoid string duplication in preload bridge. + */ + +// ============================================================================= +// Config API Channels +// ============================================================================= + +/** Get application config */ +export const CONFIG_GET = 'config:get'; + +/** Update config section */ +export const CONFIG_UPDATE = 'config:update'; + +/** Add regex pattern to ignore list */ +export const CONFIG_ADD_IGNORE_REGEX = 'config:addIgnoreRegex'; + +/** Remove regex pattern from ignore list */ +export const CONFIG_REMOVE_IGNORE_REGEX = 'config:removeIgnoreRegex'; + +/** Add repository to ignore list */ +export const CONFIG_ADD_IGNORE_REPOSITORY = 'config:addIgnoreRepository'; + +/** Remove repository from ignore list */ +export const CONFIG_REMOVE_IGNORE_REPOSITORY = 'config:removeIgnoreRepository'; + +/** Snooze notifications */ +export const CONFIG_SNOOZE = 'config:snooze'; + +/** Clear notification snooze */ +export const CONFIG_CLEAR_SNOOZE = 'config:clearSnooze'; + +/** Add notification trigger */ +export const CONFIG_ADD_TRIGGER = 'config:addTrigger'; + +/** Update notification trigger */ +export const CONFIG_UPDATE_TRIGGER = 'config:updateTrigger'; + +/** Remove notification trigger */ +export const CONFIG_REMOVE_TRIGGER = 'config:removeTrigger'; + +/** Get all triggers */ +export const CONFIG_GET_TRIGGERS = 'config:getTriggers'; + +/** Test a trigger */ +export const CONFIG_TEST_TRIGGER = 'config:testTrigger'; + +/** Select folders dialog */ +export const CONFIG_SELECT_FOLDERS = 'config:selectFolders'; + +/** Open config file in external editor */ +export const CONFIG_OPEN_IN_EDITOR = 'config:openInEditor'; + +/** Pin a session */ +export const CONFIG_PIN_SESSION = 'config:pinSession'; + +/** Unpin a session */ +export const CONFIG_UNPIN_SESSION = 'config:unpinSession'; diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 00000000..3d1ce1e1 --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,291 @@ +import { WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL } from '@shared/constants'; +import { contextBridge, ipcRenderer } from 'electron'; + +import { + CONFIG_ADD_IGNORE_REGEX, + CONFIG_ADD_IGNORE_REPOSITORY, + CONFIG_ADD_TRIGGER, + CONFIG_CLEAR_SNOOZE, + CONFIG_GET, + CONFIG_GET_TRIGGERS, + CONFIG_OPEN_IN_EDITOR, + CONFIG_PIN_SESSION, + CONFIG_REMOVE_IGNORE_REGEX, + CONFIG_REMOVE_IGNORE_REPOSITORY, + CONFIG_REMOVE_TRIGGER, + CONFIG_SELECT_FOLDERS, + CONFIG_SNOOZE, + CONFIG_TEST_TRIGGER, + CONFIG_UNPIN_SESSION, + CONFIG_UPDATE, + CONFIG_UPDATE_TRIGGER, +} from './constants/ipcChannels'; + +import type { + AppConfig, + ElectronAPI, + NotificationTrigger, + SessionsPaginationOptions, + TriggerTestResult, +} from '@shared/types'; + +// ============================================================================= +// IPC Result Types and Helpers +// ============================================================================= + +/** + * Standard IPC result structure returned by main process handlers. + * All config-related IPC calls return this shape. + */ +interface IpcResult { + success: boolean; + data?: T; + error?: string; +} + +interface IpcFileChangePayload { + type: 'add' | 'change' | 'unlink'; + path: string; + projectId?: string; + sessionId?: string; + isSubagent: boolean; +} + +/** + * Type-safe IPC invoker for operations that return IpcResult. + * Throws an Error if the IPC call fails, otherwise returns the typed data. + */ +async function invokeIpcWithResult(channel: string, ...args: unknown[]): Promise { + const result = (await ipcRenderer.invoke(channel, ...args)) as IpcResult; + if (!result.success) { + throw new Error(result.error ?? 'Unknown error'); + } + return result.data as T; +} + +// Keep latest zoom factor cached even before renderer UI subscribes. +let currentZoomFactor = 1; +ipcRenderer.on( + WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL, + (_event: Electron.IpcRendererEvent, zoomFactor: unknown) => { + if (typeof zoomFactor === 'number' && Number.isFinite(zoomFactor)) { + currentZoomFactor = zoomFactor; + } + } +); + +// ============================================================================= +// Electron API Implementation +// ============================================================================= + +// Expose protected methods that allow the renderer process to use +// the ipcRenderer without exposing the entire object +const electronAPI: ElectronAPI = { + getAppVersion: () => ipcRenderer.invoke('get-app-version'), + getProjects: () => ipcRenderer.invoke('get-projects'), + getSessions: (projectId: string) => ipcRenderer.invoke('get-sessions', projectId), + getSessionsPaginated: ( + projectId: string, + cursor: string | null, + limit?: number, + options?: SessionsPaginationOptions + ) => ipcRenderer.invoke('get-sessions-paginated', projectId, cursor, limit, options), + searchSessions: (projectId: string, query: string, maxResults?: number) => + ipcRenderer.invoke('search-sessions', projectId, query, maxResults), + getSessionDetail: (projectId: string, sessionId: string) => + ipcRenderer.invoke('get-session-detail', projectId, sessionId), + getSessionMetrics: (projectId: string, sessionId: string) => + ipcRenderer.invoke('get-session-metrics', projectId, sessionId), + getWaterfallData: (projectId: string, sessionId: string) => + ipcRenderer.invoke('get-waterfall-data', projectId, sessionId), + getSubagentDetail: (projectId: string, sessionId: string, subagentId: string) => + ipcRenderer.invoke('get-subagent-detail', projectId, sessionId, subagentId), + getSessionGroups: (projectId: string, sessionId: string) => + ipcRenderer.invoke('get-session-groups', projectId, sessionId), + + // Repository grouping (worktree support) + getRepositoryGroups: () => ipcRenderer.invoke('get-repository-groups'), + getWorktreeSessions: (worktreeId: string) => + ipcRenderer.invoke('get-worktree-sessions', worktreeId), + + // Validation methods + validatePath: (relativePath: string, projectPath: string) => + ipcRenderer.invoke('validate-path', relativePath, projectPath), + validateMentions: (mentions: { type: 'path'; value: string }[], projectPath: string) => + ipcRenderer.invoke('validate-mentions', mentions, projectPath), + + // CLAUDE.md reading methods + readClaudeMdFiles: (projectRoot: string) => + ipcRenderer.invoke('read-claude-md-files', projectRoot), + readDirectoryClaudeMd: (dirPath: string) => + ipcRenderer.invoke('read-directory-claude-md', dirPath), + readMentionedFile: (absolutePath: string, projectRoot: string, maxTokens?: number) => + ipcRenderer.invoke('read-mentioned-file', absolutePath, projectRoot, maxTokens), + + // Notifications API + notifications: { + get: (options?: { limit?: number; offset?: number }) => + ipcRenderer.invoke('notifications:get', options), + markRead: (id: string) => ipcRenderer.invoke('notifications:markRead', id), + markAllRead: () => ipcRenderer.invoke('notifications:markAllRead'), + delete: (id: string) => ipcRenderer.invoke('notifications:delete', id), + clear: () => ipcRenderer.invoke('notifications:clear'), + getUnreadCount: () => ipcRenderer.invoke('notifications:getUnreadCount'), + onNew: (callback: (event: unknown, error: unknown) => void): (() => void) => { + ipcRenderer.on( + 'notification:new', + callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void + ); + return (): void => { + ipcRenderer.removeListener( + 'notification:new', + callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void + ); + }; + }, + onUpdated: ( + callback: (event: unknown, payload: { total: number; unreadCount: number }) => void + ): (() => void) => { + ipcRenderer.on( + 'notification:updated', + callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void + ); + return (): void => { + ipcRenderer.removeListener( + 'notification:updated', + callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void + ); + }; + }, + onClicked: (callback: (event: unknown, data: unknown) => void): (() => void) => { + ipcRenderer.on( + 'notification:clicked', + callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void + ); + return (): void => { + ipcRenderer.removeListener( + 'notification:clicked', + callback as (event: Electron.IpcRendererEvent, ...args: unknown[]) => void + ); + }; + }, + }, + + // Config API - uses typed helper to unwrap { success, data, error } responses + config: { + get: async (): Promise => { + return invokeIpcWithResult(CONFIG_GET); + }, + update: async (section: string, data: object): Promise => { + return invokeIpcWithResult(CONFIG_UPDATE, section, data); + }, + addIgnoreRegex: async (pattern: string): Promise => { + await invokeIpcWithResult(CONFIG_ADD_IGNORE_REGEX, pattern); + // Re-fetch config after mutation + return invokeIpcWithResult(CONFIG_GET); + }, + removeIgnoreRegex: async (pattern: string): Promise => { + await invokeIpcWithResult(CONFIG_REMOVE_IGNORE_REGEX, pattern); + return invokeIpcWithResult(CONFIG_GET); + }, + addIgnoreRepository: async (repositoryId: string): Promise => { + await invokeIpcWithResult(CONFIG_ADD_IGNORE_REPOSITORY, repositoryId); + return invokeIpcWithResult(CONFIG_GET); + }, + removeIgnoreRepository: async (repositoryId: string): Promise => { + await invokeIpcWithResult(CONFIG_REMOVE_IGNORE_REPOSITORY, repositoryId); + return invokeIpcWithResult(CONFIG_GET); + }, + snooze: async (minutes: number): Promise => { + await invokeIpcWithResult(CONFIG_SNOOZE, minutes); + return invokeIpcWithResult(CONFIG_GET); + }, + clearSnooze: async (): Promise => { + await invokeIpcWithResult(CONFIG_CLEAR_SNOOZE); + return invokeIpcWithResult(CONFIG_GET); + }, + addTrigger: async (trigger: Omit): Promise => { + await invokeIpcWithResult(CONFIG_ADD_TRIGGER, trigger); + // Return updated config + return invokeIpcWithResult(CONFIG_GET); + }, + updateTrigger: async ( + triggerId: string, + updates: Partial + ): Promise => { + await invokeIpcWithResult(CONFIG_UPDATE_TRIGGER, triggerId, updates); + // Return updated config + return invokeIpcWithResult(CONFIG_GET); + }, + removeTrigger: async (triggerId: string): Promise => { + await invokeIpcWithResult(CONFIG_REMOVE_TRIGGER, triggerId); + // Return updated config + return invokeIpcWithResult(CONFIG_GET); + }, + getTriggers: async (): Promise => { + return invokeIpcWithResult(CONFIG_GET_TRIGGERS); + }, + testTrigger: async (trigger: NotificationTrigger): Promise => { + return invokeIpcWithResult(CONFIG_TEST_TRIGGER, trigger); + }, + selectFolders: async (): Promise => { + return invokeIpcWithResult(CONFIG_SELECT_FOLDERS); + }, + openInEditor: async (): Promise => { + return invokeIpcWithResult(CONFIG_OPEN_IN_EDITOR); + }, + pinSession: async (projectId: string, sessionId: string): Promise => { + return invokeIpcWithResult(CONFIG_PIN_SESSION, projectId, sessionId); + }, + unpinSession: async (projectId: string, sessionId: string): Promise => { + return invokeIpcWithResult(CONFIG_UNPIN_SESSION, projectId, sessionId); + }, + }, + + // Deep link navigation + session: { + scrollToLine: (sessionId: string, lineNumber: number) => + ipcRenderer.invoke('session:scrollToLine', sessionId, lineNumber), + }, + + // Zoom factor sync (used for traffic-light-safe layout) + getZoomFactor: async (): Promise => currentZoomFactor, + onZoomFactorChanged: (callback: (zoomFactor: number) => void): (() => void) => { + const listener = (_event: Electron.IpcRendererEvent, zoomFactor: unknown): void => { + if (typeof zoomFactor !== 'number' || !Number.isFinite(zoomFactor)) return; + currentZoomFactor = zoomFactor; + callback(zoomFactor); + }; + ipcRenderer.on(WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL, listener); + return (): void => { + ipcRenderer.removeListener(WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL, listener); + }; + }, + + // File change events (real-time updates) + onFileChange: (callback: (event: IpcFileChangePayload) => void): (() => void) => { + const listener = (_event: Electron.IpcRendererEvent, data: IpcFileChangePayload): void => + callback(data); + ipcRenderer.on('file-change', listener); + return (): void => { + ipcRenderer.removeListener('file-change', listener); + }; + }, + + // Shell operations + openPath: (targetPath: string, projectRoot?: string) => + ipcRenderer.invoke('shell:openPath', targetPath, projectRoot), + openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), + + onTodoChange: (callback: (event: IpcFileChangePayload) => void): (() => void) => { + const listener = (_event: Electron.IpcRendererEvent, data: IpcFileChangePayload): void => + callback(data); + ipcRenderer.on('todo-change', listener); + return (): void => { + ipcRenderer.removeListener('todo-change', listener); + }; + }, +}; + +// Use contextBridge to securely expose the API to the renderer process +contextBridge.exposeInMainWorld('electronAPI', electronAPI); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx new file mode 100644 index 00000000..82446a5d --- /dev/null +++ b/src/renderer/App.tsx @@ -0,0 +1,32 @@ +import React, { useEffect } from 'react'; + +import { ErrorBoundary } from './components/common/ErrorBoundary'; +import { TabbedLayout } from './components/layout/TabbedLayout'; +import { useTheme } from './hooks/useTheme'; +import { initializeNotificationListeners } from './store'; + +export const App = (): React.JSX.Element => { + // Initialize theme on app load + useTheme(); + + // Dismiss splash screen once React is ready + useEffect(() => { + const splash = document.getElementById('splash'); + if (splash) { + splash.style.opacity = '0'; + setTimeout(() => splash.remove(), 300); + } + }, []); + + // Initialize IPC event listeners (notifications, file changes) + useEffect(() => { + const cleanup = initializeNotificationListeners(); + return cleanup; + }, []); + + return ( + + + + ); +}; diff --git a/src/renderer/CLAUDE.md b/src/renderer/CLAUDE.md new file mode 100644 index 00000000..a9346ae1 --- /dev/null +++ b/src/renderer/CLAUDE.md @@ -0,0 +1,82 @@ +# Renderer Process + +React application running in Chromium. + +## Structure +- `App.tsx` - Root layout +- `main.tsx` - React entry point +- `index.css` - Global styles with Tailwind +- `components/` - UI components by feature +- `store/` - Zustand state (slices pattern) +- `hooks/` - Custom React hooks +- `utils/` - Renderer utilities +- `types/` - Renderer type definitions +- `constants/` - CSS variables (`cssVariables.ts`), layout constants (`layout.ts`), team colors (`teamColors.ts`) +- `contexts/` - React contexts (`TabUIContext.tsx`, `useTabUIContext.ts`) + +## Component Organization +``` +components/ +├── chat/ # Chat display, message items, viewers, context panel +├── common/ # Shared components (badges, dropdowns, token display) +├── dashboard/ # Dashboard views +├── layout/ # Layout components (headers, shells) +├── notifications/ # Notification panels and badges +├── search/ # Search UI and results +├── settings/ # Settings UI +└── sidebar/ # Sidebar navigation +``` + +## Types (`types/`) +- `data.ts` - Core data types (ParsedMessage, SemanticStep, SessionMetrics) +- `groups.ts` - Chat groups (UserGroup, AIGroup, SystemGroup, AIGroupDisplayItem union) +- `contextInjection.ts` - Context tracking (ContextInjection union, ContextStats, ContextPhaseInfo) +- `claudeMd.ts` - CLAUDE.md injection types +- `panes.ts` - Pane layout types +- `tabs.ts` - Tab management types +- `notifications.ts` - Notification types +- `api.ts` - API types + +## Utils (`utils/`) +- `contextTracker.ts` - Visible context tracking (computeContextStats, processSessionContextWithPhases) +- `claudeMdTracker.ts` - CLAUDE.md injection detection +- `aiGroupEnhancer.ts` - AI group enrichment (linkToolCallsToResults, buildDisplayItems) +- `aiGroupHelpers.ts` - AI group utility functions +- `displayItemBuilder.ts` - Display item construction +- `displaySummary.ts` - Display summary generation +- `formatters.ts` - Display formatting +- `groupTransformer.ts` - Chat item grouping +- `lastOutputDetector.ts` - Last output detection +- `modelExtractor.ts` - Model name extraction +- `pathDisplay.ts` - Path display formatting +- `pathUtils.ts` - Path utility functions +- `slashCommandExtractor.ts` - Slash command extraction +- `stringUtils.ts` - String utility functions +- `toolLinkingEngine.ts` - Tool call/result linking +- `toolRendering/` - Tool rendering helpers + - `toolContentChecks.ts` - Tool content validation + - `toolSummaryHelpers.ts` - Tool summary formatting + - `toolTokens.ts` - Tool token utilities + +## Hooks +- `useAutoScrollBottom` - Auto-scroll chat to bottom +- `useKeyboardShortcuts` - Keyboard shortcuts +- `useTabNavigationController` - Turn navigation with highlighting +- `useTabUI` - Per-tab UI state access +- `useTheme` - Dark/light theme toggle +- `useVisibleAIGroup` - Viewport-aware AI group tracking +- `useZoomFactor` - Zoom level management +- `navigation/utils.ts` - Navigation utility functions + +## Contexts +- `contexts/TabUIContext.tsx` - Per-tab UI state isolation +- `contexts/useTabUIContext.ts` - Context consumer hook + +## State Management +Zustand store with slices pattern: +- Each domain has data, selectedId, loading, error +- Actions grouped by domain +- Selectors for derived state + +## Virtual Scrolling +Use `@tanstack/react-virtual` for large lists (sessions, messages). diff --git a/src/renderer/components/CLAUDE.md b/src/renderer/components/CLAUDE.md new file mode 100644 index 00000000..99978beb --- /dev/null +++ b/src/renderer/components/CLAUDE.md @@ -0,0 +1,81 @@ +# Components + +UI components organized by feature domain. + +## Structure +``` +components/ +├── chat/ # Session message display +│ ├── items/ # Individual message/tool items +│ │ ├── linkedTool/ # Tool call/result display helpers +│ │ ├── BaseItem # Base item wrapper +│ │ ├── baseItemHelpers # Base item utility functions +│ │ ├── ExecutionTrace # Execution trace display +│ │ ├── LinkedToolItem # Tool call with linked result +│ │ ├── MetricsPill # Metrics badge display +│ │ ├── SlashItem # Slash command display +│ │ ├── SubagentItem # Subagent execution display +│ │ ├── TeammateMessageItem # Team message cards +│ │ ├── ThinkingItem # Extended thinking display +│ │ └── TextItem # Text output display +│ ├── viewers/ # Content viewers (JSON, code, diff) +│ ├── SessionContextPanel/ # Visible context tracking panel +│ │ ├── components/ # Section wrappers (ClaudeMdFilesSection, ToolOutputsSection, UserMessagesSection, etc.) +│ │ ├── items/ # Per-injection item renderers (ClaudeMdItem, ToolOutputItem, UserMessageItem, etc.) +│ │ ├── DirectoryTree/ # CLAUDE.md directory navigation +│ │ ├── utils/ # Formatting helpers +│ │ ├── index.tsx # Main panel component +│ │ └── types.ts # SectionType constants, panel props +│ ├── AIChatGroup.tsx # AI response group display +│ ├── ChatHistory.tsx # Chat timeline container +│ ├── ChatHistoryEmptyState.tsx # Empty state display +│ ├── ChatHistoryItem.tsx # Individual history item +│ ├── ChatHistoryLoadingState.tsx # Loading state display +│ ├── CompactBoundary.tsx # Compaction event boundary marker +│ ├── ContextBadge.tsx # Per-turn context injection popover badge +│ ├── DisplayItemList.tsx # Display item list rendering +│ ├── LastOutputDisplay.tsx # Last output display +│ ├── SystemChatGroup.tsx # System message group display +│ ├── UserChatGroup.tsx # User message display +│ ├── markdownComponents.tsx # Custom markdown renderers +│ └── searchHighlightUtils.ts # Search highlight utilities +├── common/ # Shared UI primitives +│ ├── CopyButton # Copy to clipboard button +│ ├── CopyablePath # Clickable, copyable file path +│ ├── ErrorBoundary # React error boundary +│ ├── OngoingIndicator # Session in-progress indicator +│ ├── RepositoryDropdown # Repository selector dropdown +│ ├── TokenUsageDisplay # Token breakdown with context stats hover +│ └── WorktreeBadge # Git worktree badge +├── dashboard/ # Overview and listing pages +├── layout/ # App shell, sidebars, headers +├── notifications/ # Notification panels and badges +├── search/ # Search UI and results +├── settings/ # Settings pages and controls +│ ├── components/ # Reusable setting controls (SettingRow, SettingsToggle, etc.) +│ ├── hooks/ # Settings-specific hooks +│ ├── sections/ # Setting sections (General, Notifications, Advanced) +│ └── NotificationTriggerSettings/ # Trigger config UI +│ ├── components/ # Trigger form components +│ ├── hooks/ # Trigger form hooks +│ └── utils/ # Trigger utilities +└── sidebar/ # Project/session navigation +``` + +## Adding Components +1. Choose appropriate parent directory by feature +2. If used across features, place in `common/` +3. Use Tailwind with theme-aware CSS variables +4. Connect to store via `useStore()` hook if needed +5. Colocate related hooks/utils in same directory + +## Component Guidelines +- One component per file, PascalCase naming +- Use functional components with hooks +- Prefer composition over prop drilling +- Use `TabUIContext` for per-tab UI state + +## Virtual Scrolling +Use `@tanstack/react-virtual` for lists > 100 items: +- Session lists in sidebar +- Message lists in chat view diff --git a/src/renderer/components/chat/AIChatGroup.tsx b/src/renderer/components/chat/AIChatGroup.tsx new file mode 100644 index 00000000..7bf24642 --- /dev/null +++ b/src/renderer/components/chat/AIChatGroup.tsx @@ -0,0 +1,529 @@ +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; + +import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; +import { useTabUI } from '@renderer/hooks/useTabUI'; +import { useStore } from '@renderer/store'; +import { enhanceAIGroup, type PrecedingSlashInfo } from '@renderer/utils/aiGroupEnhancer'; +import { extractSlashInfo, isCommandContent } from '@shared/utils/contentSanitizer'; +import { getModelColorClass } from '@shared/utils/modelParser'; +import { estimateTokens } from '@shared/utils/tokenFormatting'; +import { format } from 'date-fns'; +import { Bot, ChevronDown, Clock } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import { TokenUsageDisplay } from '../common/TokenUsageDisplay'; + +import { ContextBadge } from './ContextBadge'; +import { DisplayItemList } from './DisplayItemList'; +import { LastOutputDisplay } from './LastOutputDisplay'; + +import type { ContextStats } from '@renderer/types/contextInjection'; +import type { + AIGroup, + AIGroupDisplayItem, + EnhancedAIGroup, + UserGroup, +} from '@renderer/types/groups'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +/** + * Extract slash info from a UserGroup's message content. + * Returns PrecedingSlashInfo if the user message was a slash invocation, + * null otherwise. + */ +function extractPrecedingSlashInfo( + userGroup: UserGroup | undefined +): PrecedingSlashInfo | undefined { + if (!userGroup) return undefined; + + const msg = userGroup.message; + const content = msg.content; + + // Check if this is a slash message (has tags) + if (typeof content === 'string' && isCommandContent(content)) { + const slashInfo = extractSlashInfo(content); + if (slashInfo) { + return { + name: slashInfo.name, + message: slashInfo.message, + args: slashInfo.args, + commandMessageUuid: msg.uuid, + timestamp: new Date(msg.timestamp), + }; + } + } + + return undefined; +} + +/** + * Format duration in milliseconds to human-readable string. + * Examples: "1.2s", "45s", "1m 30s", "5m" + */ +function formatDuration(ms: number): string { + if (ms < 1000) { + return `${ms}ms`; + } + const seconds = Math.floor(ms / 1000); + if (seconds < 60) { + const decimal = ms % 1000 >= 100 ? `.${Math.floor((ms % 1000) / 100)}` : ''; + return `${seconds}${decimal}s`; + } + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + if (remainingSeconds === 0) { + return `${minutes}m`; + } + return `${minutes}m ${remainingSeconds}s`; +} + +interface AIChatGroupProps { + aiGroup: AIGroup; + /** Tool use ID to highlight for error deep linking */ + highlightToolUseId?: string; + /** Custom highlight color from trigger */ + highlightColor?: TriggerColor; + /** Register ref for individual tool items (for precise scroll targeting) */ + registerToolRef?: (toolId: string, el: HTMLElement | null) => void; +} + +/** + * Checks if a tool ID exists within the display items (including nested subagents). + */ +function containsToolUseId(items: AIGroupDisplayItem[], toolUseId: string): boolean { + for (const item of items) { + if (item.type === 'tool' && item.tool.id === toolUseId) { + return true; + } + // Check nested subagent messages for the tool ID + if (item.type === 'subagent' && item.subagent.messages) { + for (const msg of item.subagent.messages) { + if (msg.toolCalls?.some((tc) => tc.id === toolUseId)) { + return true; + } + if (msg.toolResults?.some((tr) => tr.toolUseId === toolUseId)) { + return true; + } + } + } + } + return false; +} + +/** + * AIChatGroup displays an AI response using a clean, minimal card-based design. + * + * Features: + * - Card container with subtle zinc styling + * - Clickable header with Bot icon, "Claude" label, and items summary + * - LastOutputDisplay: Always visible last output (text or tool result) + * - DisplayItemList: Shows items when expanded with inline expansion support + * - Manages local expansion state and inline item expansion + */ +const AIChatGroupInner = ({ + aiGroup, + highlightToolUseId, + highlightColor, + registerToolRef, +}: Readonly): React.JSX.Element => { + // Per-tab UI state for expansion (completely isolated per tab) + const { + tabId, + isAIGroupExpanded: isAIGroupExpandedForTab, + toggleAIGroupExpansion, + getExpandedDisplayItemIds, + toggleDisplayItemExpansion, + expandDisplayItem, + } = useTabUI(); + + // Per-tab session data, falling back to global state + const projectRoot = useStore((s) => { + const td = tabId ? s.tabSessionData[tabId] : null; + return (td?.sessionDetail ?? s.sessionDetail)?.session?.projectPath; + }); + const isSessionOngoing = useStore((s) => { + const id = s.selectedSessionId; + if (!id) return false; + return s.sessions.find((sess) => sess.id === id)?.isOngoing ?? false; + }); + + // Per-tab session data subscriptions, falling back to global state + const { + sessionClaudeMdStats, + sessionContextStats, + sessionPhaseInfo, + conversation, + searchExpandedAIGroupIds, + searchExpandedSubagentIds, + searchCurrentDisplayItemId, + } = useStore( + useShallow((s) => { + const td = tabId ? s.tabSessionData[tabId] : null; + return { + sessionClaudeMdStats: td?.sessionClaudeMdStats ?? s.sessionClaudeMdStats, + sessionContextStats: td?.sessionContextStats ?? s.sessionContextStats, + sessionPhaseInfo: td?.sessionPhaseInfo ?? s.sessionPhaseInfo, + conversation: td?.conversation ?? s.conversation, + searchExpandedAIGroupIds: s.searchExpandedAIGroupIds, + searchExpandedSubagentIds: s.searchExpandedSubagentIds, + searchCurrentDisplayItemId: s.searchCurrentDisplayItemId, + }; + }) + ); + + // Notification color map for tool item dots + const notifications = useStore((s) => s.notifications); + const notificationColorMap = useMemo(() => { + const map = new Map(); + for (const n of notifications) { + if (n.toolUseId && n.triggerColor) { + map.set(n.toolUseId, n.triggerColor); + } + } + return map; + }, [notifications]); + + // Derived state from store values + const claudeMdStats = sessionClaudeMdStats?.get(aiGroup.id); + const contextStats: ContextStats | undefined = sessionContextStats?.get(aiGroup.id); + + // Phase data for this AI group + const phaseNumber = sessionPhaseInfo?.aiGroupPhaseMap.get(aiGroup.id); + const totalPhases = sessionPhaseInfo?.phases.length ?? 0; + + // Find the preceding UserGroup for this AIGroup to extract slash info + // eslint-disable-next-line react-hooks/preserve-manual-memoization -- React Compiler can't preserve this; manual memo needed for O(n) traversal + const precedingSlash = useMemo(() => { + if (!conversation?.items) return undefined; + + // Find the index of this AIGroup in the conversation + const aiGroupIndex = conversation.items.findIndex( + (item) => item.type === 'ai' && item.group.id === aiGroup.id + ); + + if (aiGroupIndex <= 0) return undefined; + + // Look backwards for the nearest UserGroup + for (let i = aiGroupIndex - 1; i >= 0; i--) { + const item = conversation.items[i]; + if (item.type === 'user') { + return extractPrecedingSlashInfo(item.group); + } + // Stop if we hit another AI group (shouldn't happen in normal flow) + if (item.type === 'ai') break; + } + + return undefined; + }, [conversation?.items, aiGroup.id]); + + // Enhance the AI group to get display-ready data + const enhanced: EnhancedAIGroup = useMemo( + () => enhanceAIGroup(aiGroup, claudeMdStats, precedingSlash), + [aiGroup, claudeMdStats, precedingSlash] + ); + + // Check if this group should be expanded for search results + const shouldExpandForSearch = searchExpandedAIGroupIds.has(aiGroup.id); + + // Check if this group contains the highlighted error tool + const containsHighlightedError = useMemo(() => { + if (!highlightToolUseId) return false; + return containsToolUseId(enhanced.displayItems, highlightToolUseId); + }, [enhanced.displayItems, highlightToolUseId]); + + // Get the LAST assistant message's usage (represents current context window snapshot) + // This is the correct metric to display - not the summed values across all messages + const lastUsage = useMemo(() => { + const responses = aiGroup.responses || []; + // Find the last assistant message with usage data + for (let i = responses.length - 1; i >= 0; i--) { + const msg = responses[i]; + if (msg.type === 'assistant' && msg.usage) { + return msg.usage; + } + } + return null; + }, [aiGroup.responses]); + + // Calculate thinking and text output tokens from assistant message content blocks + // These are estimated from the actual content, providing breakdown of output token usage + const { thinkingTokens, textOutputTokens } = useMemo(() => { + let thinking = 0; + let textOutput = 0; + + const responses = aiGroup.responses || []; + for (const msg of responses) { + if (msg.type === 'assistant' && Array.isArray(msg.content)) { + for (const block of msg.content) { + if (block.type === 'thinking' && block.thinking) { + thinking += estimateTokens(block.thinking); + } else if (block.type === 'text' && block.text) { + textOutput += estimateTokens(block.text); + } + } + } + } + + return { thinkingTokens: thinking, textOutputTokens: textOutput }; + }, [aiGroup.responses]); + + // Auto-expand if contains error or search result, or if manually expanded + const isExpanded = + isAIGroupExpandedForTab(aiGroup.id) || containsHighlightedError || shouldExpandForSearch; + + // Helper function to find the item ID containing the highlighted tool + const findHighlightedItemId = useCallback( + (toolUseId: string): string | null => { + for (let i = 0; i < enhanced.displayItems.length; i++) { + const item = enhanced.displayItems[i]; + if (item.type === 'tool' && item.tool.id === toolUseId) { + return `tool-${item.tool.id}-${i}`; + } + // For subagents, expand the subagent item + if (item.type === 'subagent' && item.subagent.messages) { + for (const msg of item.subagent.messages) { + if ( + msg.toolCalls?.some((tc) => tc.id === toolUseId) || + msg.toolResults?.some((tr) => tr.toolUseId === toolUseId) + ) { + return `subagent-${item.subagent.id}-${i}`; + } + } + } + } + return null; + }, + [enhanced.displayItems] + ); + + // Get expanded item IDs for this AI group (per-tab) + const expandedItemIds = useMemo( + () => getExpandedDisplayItemIds(aiGroup.id), + [getExpandedDisplayItemIds, aiGroup.id] + ); + + // Track which highlightToolUseId we've already processed to prevent infinite loops + const processedHighlightRef = useRef(null); + + // Effect to auto-expand display item when highlightToolUseId is set + // AI group expansion is now handled by the navigation coordinator + // This only handles display item expansion which requires enhanced data + useEffect(() => { + if (!highlightToolUseId || !containsHighlightedError) { + // Reset ref when highlight is cleared + if (!highlightToolUseId) { + processedHighlightRef.current = null; + } + return; + } + + // Skip if we've already processed this exact highlight + if (processedHighlightRef.current === highlightToolUseId) { + return; + } + + // Mark as processed BEFORE making any state changes + processedHighlightRef.current = highlightToolUseId; + + // Find and expand the display item containing the highlighted tool + // No delay needed - navigation coordinator ensures DOM is stable before highlight + const itemId = findHighlightedItemId(highlightToolUseId); + if (itemId) { + expandDisplayItem(aiGroup.id, itemId); + } + }, [ + highlightToolUseId, + containsHighlightedError, + aiGroup.id, + expandDisplayItem, + findHighlightedItemId, + ]); + + // Track which search we've already processed to prevent infinite loops + const processedSearchRef = useRef(null); + + // Effect to auto-expand display items when search navigates to this group + // Note: AI group expansion is handled by derived isExpanded (shouldExpandForSearch) + useEffect(() => { + if (!shouldExpandForSearch) { + processedSearchRef.current = null; + return; + } + + // Create a unique key for this search state + const searchKey = `${searchCurrentDisplayItemId ?? ''}-${Array.from(searchExpandedSubagentIds).join(',')}`; + if (processedSearchRef.current === searchKey) { + return; + } + processedSearchRef.current = searchKey; + + // Expand the specific display item containing the search result (uses per-tab state) + if (searchCurrentDisplayItemId) { + expandDisplayItem(aiGroup.id, searchCurrentDisplayItemId); + } + + // If any subagents in this group need their trace expanded for search, expand them + for (let i = 0; i < enhanced.displayItems.length; i++) { + const item = enhanced.displayItems[i]; + if (item.type === 'subagent' && searchExpandedSubagentIds.has(item.subagent.id)) { + const subagentItemId = `subagent-${item.subagent.id}-${i}`; + expandDisplayItem(aiGroup.id, subagentItemId); + } + } + }, [ + shouldExpandForSearch, + searchCurrentDisplayItemId, + searchExpandedSubagentIds, + enhanced.displayItems, + aiGroup.id, + expandDisplayItem, + ]); + + // Determine if there's content to toggle + const hasToggleContent = enhanced.displayItems.length > 0; + + // Handle item click - toggle inline expansion using store action + const handleItemClick = (itemId: string): void => { + toggleDisplayItemExpansion(aiGroup.id, itemId); + }; + + return ( +
    + {/* Header Row */} + {hasToggleContent && ( +
    + {/* Clickable toggle area */} +
    toggleAIGroupExpansion(aiGroup.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleAIGroupExpansion(aiGroup.id); + } + }} + > + + + Claude + + + {/* Main agent model */} + {enhanced.mainModel && ( + + {enhanced.mainModel.name} + + )} + + {/* Subagent models if different */} + {enhanced.subagentModels.length > 0 && ( + <> + + → + + + {enhanced.subagentModels.map((m, i) => ( + + {i > 0 && ', '} + {m.name} + + ))} + + + )} + + + · + + + {enhanced.itemsSummary} + + +
    + + {/* Right side: Context badge, Token usage, Timestamp (non-clickable) */} +
    + {/* Context injection badge (CLAUDE.md, mentioned files, tool outputs) */} + {contextStats && } + + {/* Token usage - show last assistant message's usage (context window snapshot) */} + {lastUsage && ( + + )} + + {/* Duration */} + {aiGroup.durationMs > 0 && ( + + + {formatDuration(aiGroup.durationMs)} + + )} + + {/* Timestamp - receded for visual hierarchy */} + {enhanced.lastOutput?.timestamp && ( + + {format(enhanced.lastOutput.timestamp, 'h:mm:ss a')} + + )} +
    +
    + )} + + {/* Expandable Content */} + {hasToggleContent && isExpanded && ( +
    + +
    + )} + + {/* Always-visible Output */} +
    + +
    +
    + ); +}; + +export const AIChatGroup = React.memo(AIChatGroupInner); diff --git a/src/renderer/components/chat/ChatHistory.tsx b/src/renderer/components/chat/ChatHistory.tsx new file mode 100644 index 00000000..143b58ed --- /dev/null +++ b/src/renderer/components/chat/ChatHistory.tsx @@ -0,0 +1,745 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { useAutoScrollBottom } from '@renderer/hooks/useAutoScrollBottom'; +import { useTabNavigationController } from '@renderer/hooks/useTabNavigationController'; +import { useTabUI } from '@renderer/hooks/useTabUI'; +import { useVisibleAIGroup } from '@renderer/hooks/useVisibleAIGroup'; +import { useStore } from '@renderer/store'; +import { useVirtualizer } from '@tanstack/react-virtual'; +import { useShallow } from 'zustand/react/shallow'; + +import { SessionContextPanel } from './SessionContextPanel/index'; +import { ChatHistoryEmptyState } from './ChatHistoryEmptyState'; +import { ChatHistoryItem } from './ChatHistoryItem'; +import { ChatHistoryLoadingState } from './ChatHistoryLoadingState'; + +import type { ContextInjection } from '@renderer/types/contextInjection'; + +/** + * Waits for two requestAnimationFrame cycles, allowing the virtualizer to render. + */ +function waitForDoubleRaf(): Promise { + return new Promise((resolve) => + requestAnimationFrame(() => requestAnimationFrame(() => resolve())) + ); +} + +interface ChatHistoryProps { + /** Tab ID for per-tab state isolation (scroll position, deep links) */ + tabId?: string; +} + +export const ChatHistory = ({ tabId }: ChatHistoryProps): JSX.Element => { + const VIRTUALIZATION_THRESHOLD = 120; + const ESTIMATED_CHAT_ITEM_HEIGHT = 260; + + // Per-tab UI state (context panel, scroll position, expansion) from useTabUI + const { + isContextPanelVisible, + setContextPanelVisible, + savedScrollTop, + saveScrollPosition, + expandAIGroup, + expandSubagentTrace, + selectedContextPhase, + setSelectedContextPhase, + } = useTabUI(); + + // Global store subscriptions (shared data) + const { + searchQuery, + currentSearchIndex, + searchMatches, + openTabs, + activeTabId, + consumeTabNavigation, + setSearchQuery, + syncSearchMatchesWithRendered, + selectSearchMatch, + setTabVisibleAIGroup, + } = useStore( + useShallow((s) => ({ + searchQuery: s.searchQuery, + currentSearchIndex: s.currentSearchIndex, + searchMatches: s.searchMatches, + openTabs: s.openTabs, + activeTabId: s.activeTabId, + consumeTabNavigation: s.consumeTabNavigation, + setSearchQuery: s.setSearchQuery, + syncSearchMatchesWithRendered: s.syncSearchMatchesWithRendered, + selectSearchMatch: s.selectSearchMatch, + setTabVisibleAIGroup: s.setTabVisibleAIGroup, + })) + ); + + // Per-tab session data (each tab renders its own session independently) + const tabData = useStore( + useShallow((s) => { + const td = tabId ? s.tabSessionData[tabId] : null; + return { + conversation: td?.conversation ?? s.conversation, + conversationLoading: td?.conversationLoading ?? s.conversationLoading, + sessionContextStats: td?.sessionContextStats ?? s.sessionContextStats, + sessionPhaseInfo: td?.sessionPhaseInfo ?? s.sessionPhaseInfo, + sessionDetail: td?.sessionDetail ?? s.sessionDetail, + }; + }) + ); + const { + conversation, + conversationLoading, + sessionContextStats, + sessionPhaseInfo, + sessionDetail, + } = tabData; + + // State for Context button hover (local state OK - doesn't need per-tab isolation) + const [isContextButtonHovered, setIsContextButtonHovered] = useState(false); + + // Determine if this tab instance is currently active + // Use tabId prop if provided, otherwise fall back to activeTabId (for backwards compatibility) + const effectiveTabId = tabId ?? activeTabId; + const isThisTabActive = effectiveTabId === activeTabId; + + // Get THIS tab's pending navigation request + const thisTab = effectiveTabId ? openTabs.find((t) => t.id === effectiveTabId) : null; + const pendingNavigation = thisTab?.pendingNavigation; + + // Compute all accumulated context injections (phase-aware) + const { allContextInjections, lastAiGroupTotalTokens } = useMemo(() => { + if (!sessionContextStats || !conversation?.items.length) { + return { allContextInjections: [] as ContextInjection[], lastAiGroupTotalTokens: undefined }; + } + + // Determine which phase to show + const effectivePhase = selectedContextPhase; + + // If a specific phase is selected, find the last AI group in that phase + let targetAiGroupId: string | undefined; + if (effectivePhase !== null && sessionPhaseInfo) { + const phase = sessionPhaseInfo.phases.find((p) => p.phaseNumber === effectivePhase); + if (phase) { + targetAiGroupId = phase.lastAIGroupId; + } + } + + // Default: use the last AI group overall + if (!targetAiGroupId) { + const lastAiItem = [...conversation.items].reverse().find((item) => item.type === 'ai'); + if (lastAiItem?.type !== 'ai') { + return { + allContextInjections: [] as ContextInjection[], + lastAiGroupTotalTokens: undefined, + }; + } + targetAiGroupId = lastAiItem.group.id; + } + + const stats = sessionContextStats.get(targetAiGroupId); + const injections = stats?.accumulatedInjections ?? []; + + // Get total tokens from the target AI group + let totalTokens: number | undefined; + const targetItem = conversation.items.find( + (item) => item.type === 'ai' && item.group.id === targetAiGroupId + ); + if (targetItem?.type === 'ai') { + const responses = targetItem.group.responses || []; + for (let i = responses.length - 1; i >= 0; i--) { + const msg = responses[i]; + if (msg.type === 'assistant' && msg.usage) { + const usage = msg.usage; + totalTokens = + (usage.input_tokens ?? 0) + + (usage.output_tokens ?? 0) + + (usage.cache_read_input_tokens ?? 0) + + (usage.cache_creation_input_tokens ?? 0); + break; + } + } + } + + return { allContextInjections: injections, lastAiGroupTotalTokens: totalTokens }; + }, [sessionContextStats, conversation, selectedContextPhase, sessionPhaseInfo]); + + // State for navigation highlight (blue, used for Turn navigation from CLAUDE.md panel) + const [isNavigationHighlight, setIsNavigationHighlight] = useState(false); + const navigationHighlightTimerRef = useRef | null>(null); + + // Refs map for AI groups, chat items, and individual tool items (for scrolling) + const aiGroupRefs = useRef>(new Map()); + const chatItemRefs = useRef>(new Map()); + const toolItemRefs = useRef>(new Map()); + + // Shared scroll container ref - used by both auto-scroll and navigation coordinator + const scrollContainerRef = useRef(null); + + const isSearchActive = searchQuery.trim().length > 0; + const shouldVirtualize = (conversation?.items.length ?? 0) >= VIRTUALIZATION_THRESHOLD; + const emptyRenderedSyncCountRef = useRef(0); + + const setSearchQueryForTab = useCallback( + (query: string): void => { + setSearchQuery(query, conversation); + }, + [setSearchQuery, conversation] + ); + + const groupIndexMap = useMemo(() => { + const map = new Map(); + if (!conversation?.items) { + return map; + } + conversation.items.forEach((item, index) => { + map.set(item.group.id, index); + }); + return map; + }, [conversation]); + + const rowVirtualizer = useVirtualizer({ + count: shouldVirtualize ? (conversation?.items.length ?? 0) : 0, + getScrollElement: () => scrollContainerRef.current, + estimateSize: () => ESTIMATED_CHAT_ITEM_HEIGHT, + overscan: 8, + measureElement: (element) => element.getBoundingClientRect().height, + }); + + const ensureGroupVisible = useCallback( + async (groupId: string) => { + if (!shouldVirtualize) { + return; + } + const index = groupIndexMap.get(groupId); + if (index === undefined) { + return; + } + rowVirtualizer.scrollToIndex(index, { align: 'center' }); + // Wait 2 RAF frames so the virtualizer has time to render the target row + await waitForDoubleRaf(); + }, + [groupIndexMap, rowVirtualizer, shouldVirtualize] + ); + + // Sticky context button height (py-3 = 12px padding * 2 + button height ~28px + pt-3 = 12px) + // Total: approximately 52px, round up to 60px for safety + const STICKY_BUTTON_OFFSET = allContextInjections.length > 0 ? 60 : 0; + + // Unified navigation controller - replaces useNavigationCoordinator + useSearchContextNavigation + // Must be created before useAutoScrollBottom so we can pass shouldDisableAutoScroll + const { + highlightedGroupId, + setHighlightedGroupId, + highlightToolUseId: controllerToolUseId, + isSearchHighlight, + highlightColor, + shouldDisableAutoScroll, + } = useTabNavigationController({ + isActiveTab: isThisTabActive, + pendingNavigation, + conversation, + conversationLoading, + consumeTabNavigation, + tabId: effectiveTabId ?? '', + aiGroupRefs, + chatItemRefs, + toolItemRefs, + expandAIGroup, + expandSubagentTrace, + scrollContainerRef, + stickyOffset: STICKY_BUTTON_OFFSET, + ensureGroupVisible, + setSearchQuery: setSearchQueryForTab, + selectSearchMatch, + }); + + const effectiveHighlightToolUseId = controllerToolUseId ?? undefined; + + // Keep search match indices aligned with this tab's rendered conversation. + // This avoids stale/global match lists after tab switches or in-place refreshes. + useEffect(() => { + if (!isThisTabActive || !searchQuery.trim()) { + return; + } + setSearchQuery(searchQuery, conversation); + }, [isThisTabActive, searchQuery, conversation, setSearchQuery]); + + // Canonicalize matches from rendered mark elements (DOM order). + // This guarantees that nth navigation follows the exact nth visible highlight. + // Skip when virtualizing: only a subset of items are rendered, so DOM-based sync + // would produce an incomplete match list. The store-level matches are already correct. + useEffect(() => { + if (!isThisTabActive || !isSearchActive || !conversation || shouldVirtualize) { + emptyRenderedSyncCountRef.current = 0; + return; + } + + let frameA = 0; + let frameB = 0; + let cancelled = false; + + const run = (): void => { + const container = scrollContainerRef.current; + if (!container || cancelled) return; + + const renderedMatches: { itemId: string; matchIndexInItem: number }[] = []; + const marks = container.querySelectorAll( + 'mark[data-search-item-id][data-search-match-index]' + ); + for (const mark of marks) { + const itemId = mark.dataset.searchItemId; + const matchIndexRaw = mark.dataset.searchMatchIndex; + const matchIndex = matchIndexRaw !== undefined ? Number(matchIndexRaw) : Number.NaN; + if (!itemId || !Number.isFinite(matchIndex)) continue; + renderedMatches.push({ itemId, matchIndexInItem: matchIndex }); + } + + // Prevent transient "0 marks" snapshots during mount from wiping results. + if (renderedMatches.length === 0 && searchMatches.length > 0) { + emptyRenderedSyncCountRef.current += 1; + if (emptyRenderedSyncCountRef.current < 3) { + return; + } + } else { + emptyRenderedSyncCountRef.current = 0; + } + + syncSearchMatchesWithRendered(renderedMatches); + }; + + // Wait for highlight marks to be mounted and stabilized. + frameA = requestAnimationFrame(() => { + frameB = requestAnimationFrame(run); + }); + + return () => { + cancelled = true; + cancelAnimationFrame(frameA); + cancelAnimationFrame(frameB); + }; + }, [ + isThisTabActive, + isSearchActive, + shouldVirtualize, + conversation, + currentSearchIndex, + searchMatches, + syncSearchMatchesWithRendered, + ]); + + // Track shouldDisableAutoScroll transitions for scroll restore coordination + const prevShouldDisableRef = useRef(shouldDisableAutoScroll); + + const { registerAIGroupRef } = useVisibleAIGroup({ + onVisibleChange: (aiGroupId) => { + if (effectiveTabId) { + setTabVisibleAIGroup(effectiveTabId, aiGroupId); + } + }, + threshold: 0.5, + rootRef: scrollContainerRef, + }); + + // Auto-scroll to bottom when new content is added + // Disabled during navigation to prevent conflicts with deep link scrolling + // Uses shared scrollContainerRef created above + // resetKey ensures auto-scroll state resets when switching tabs/sessions + useAutoScrollBottom([conversation?.items.length], { + threshold: 150, + smoothDuration: 300, + disabled: shouldDisableAutoScroll, + externalRef: scrollContainerRef, + resetKey: effectiveTabId, + }); + + // Callback to register AI group refs (combines with visibility hook) + const registerAIGroupRefCombined = useCallback( + (groupId: string) => { + const visibilityRef = registerAIGroupRef(groupId); + return (el: HTMLElement | null) => { + if (typeof visibilityRef === 'function') visibilityRef(el); + if (el) aiGroupRefs.current.set(groupId, el); + else aiGroupRefs.current.delete(groupId); + }; + }, + [registerAIGroupRef] + ); + + // Handler to navigate to a specific turn (AI group) from CLAUDE.md panel + const handleNavigateToTurn = useCallback( + (turnIndex: number) => { + if (!conversation) return; + const targetItem = conversation.items.find( + (item) => item.type === 'ai' && item.group.turnIndex === turnIndex + ); + if (targetItem?.type !== 'ai') return; + + const run = async (): Promise => { + const groupId = targetItem.group.id; + await ensureGroupVisible(groupId); + const element = aiGroupRefs.current.get(groupId); + if (!element) return; + + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + setHighlightedGroupId(groupId); + setIsNavigationHighlight(true); + if (navigationHighlightTimerRef.current) { + clearTimeout(navigationHighlightTimerRef.current); + } + navigationHighlightTimerRef.current = setTimeout(() => { + setHighlightedGroupId(null); + setIsNavigationHighlight(false); + navigationHighlightTimerRef.current = null; + }, 2000); + }; + void run(); + }, + [conversation, ensureGroupVisible, setHighlightedGroupId] + ); + + // Scroll to current search result when it changes + useEffect(() => { + const currentMatch = currentSearchIndex >= 0 ? searchMatches[currentSearchIndex] : null; + if (!currentMatch) return; + + let frameId = 0; + let attempt = 0; + let cancelled = false; + + /** + * Promote a mark element to "current" (demote any previous) and scroll to it. + */ + const promoteAndScroll = (el: HTMLElement): void => { + const container = scrollContainerRef.current; + if (container) { + container + .querySelectorAll('mark[data-search-result="current"]') + .forEach((prev) => { + /* eslint-disable no-param-reassign -- Directly mutating DOM element style/attributes is necessary for search result highlighting */ + prev.setAttribute('data-search-result', 'match'); + prev.style.backgroundColor = 'var(--highlight-bg-inactive)'; + prev.style.color = 'var(--highlight-text-inactive)'; + prev.style.boxShadow = ''; + /* eslint-enable no-param-reassign -- Re-enable after DOM mutations */ + }); + } + /* eslint-disable no-param-reassign -- Directly mutating DOM element style/attributes is necessary for current search result highlighting */ + el.setAttribute('data-search-result', 'current'); + el.style.backgroundColor = 'var(--highlight-bg)'; + el.style.color = 'var(--highlight-text)'; + el.style.boxShadow = '0 0 0 1px var(--highlight-ring)'; + /* eslint-enable no-param-reassign -- Re-enable after DOM mutations */ + el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }; + + /** + * DOM text-search fallback: walk text nodes inside the group element to find the + * Nth occurrence of the search query, then scroll the enclosing element into view. + * This works even when React hasn't created elements (ReactMarkdown + * component memoization, render timing, etc.). + */ + const fallbackDOMSearch = (): boolean => { + const groupEl = + chatItemRefs.current.get(currentMatch.itemId) ?? + aiGroupRefs.current.get(currentMatch.itemId); + if (!groupEl) return false; + + const query = useStore.getState().searchQuery; + if (!query) return false; + const lowerQuery = query.toLowerCase(); + let count = 0; + + // Scope to [data-search-content] elements to exclude UI chrome + // (timestamps, labels, buttons) from text-node walking + const searchRoots = groupEl.querySelectorAll('[data-search-content]'); + const roots = searchRoots.length > 0 ? Array.from(searchRoots) : [groupEl]; + + for (const root of roots) { + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); + let node: Node | null; + while ((node = walker.nextNode())) { + const text = node.textContent ?? ''; + const lowerText = text.toLowerCase(); + let pos = 0; + while ((pos = lowerText.indexOf(lowerQuery, pos)) !== -1) { + if (count === currentMatch.matchIndexInItem) { + const parent = node.parentElement; + if (parent) { + parent.scrollIntoView({ behavior: 'smooth', block: 'center' }); + return true; + } + } + count++; + pos += lowerQuery.length; + } + } + } + return false; + }; + + const tryScrollToResult = (): void => { + const container = scrollContainerRef.current; + if (!container) return; + + // Primary: find mark by item ID + match index + const el = container.querySelector( + `mark[data-search-item-id="${CSS.escape(currentMatch.itemId)}"][data-search-match-index="${currentMatch.matchIndexInItem}"]` + ); + if (el) { + promoteAndScroll(el); + return; + } + + // Secondary: align by global order (nth rendered mark) as canonical fallback. + if (attempt >= 3) { + const orderedMarks = Array.from( + container.querySelectorAll( + 'mark[data-search-item-id][data-search-match-index]' + ) + ); + const byGlobal = orderedMarks[currentSearchIndex]; + if (byGlobal) { + promoteAndScroll(byGlobal); + return; + } + } + + // After a few frames, try fallback DOM text search + if (attempt >= 6) { + if (fallbackDOMSearch()) return; + } + + // Keep retrying (marks may appear after async render) + if (attempt < 60) { + attempt++; + frameId = requestAnimationFrame(tryScrollToResult); + } + }; + + const run = async (): Promise => { + await ensureGroupVisible(currentMatch.itemId); + if (cancelled) return; + frameId = requestAnimationFrame(tryScrollToResult); + }; + + void run(); + return () => { + cancelled = true; + cancelAnimationFrame(frameId); + }; + }, [currentSearchIndex, searchMatches, scrollContainerRef, ensureGroupVisible]); + + // Track previous active state to detect when THIS tab becomes active/inactive + const wasActiveRef = useRef(isThisTabActive); + + // Save scroll position when THIS tab becomes inactive + useEffect(() => { + const wasActive = wasActiveRef.current; + wasActiveRef.current = isThisTabActive; + + // If this tab just became inactive, save its scroll position + if (wasActive && !isThisTabActive && scrollContainerRef.current) { + saveScrollPosition(scrollContainerRef.current.scrollTop); + } + }, [isThisTabActive, saveScrollPosition, scrollContainerRef]); + + // Also save on unmount (e.g., when tab is closed) + useEffect(() => { + const scrollContainer = scrollContainerRef.current; + return () => { + if (scrollContainer) { + saveScrollPosition(scrollContainer.scrollTop); + } + }; + }, [saveScrollPosition, scrollContainerRef]); + + // Restore scroll position when THIS tab becomes active with saved position + // Uses shouldDisableAutoScroll (covers full navigation lifecycle) instead of pendingNavigation + // After navigation completes (transition true→false), save current position to prevent stale restore + useEffect(() => { + const wasDisabled = prevShouldDisableRef.current; + prevShouldDisableRef.current = shouldDisableAutoScroll; + + // Navigation just completed — save current scroll position, skip restore + if (wasDisabled && !shouldDisableAutoScroll && scrollContainerRef.current) { + saveScrollPosition(scrollContainerRef.current.scrollTop); + return; + } + + if ( + isThisTabActive && + savedScrollTop !== undefined && + scrollContainerRef.current && + !conversationLoading && + !shouldDisableAutoScroll + ) { + let frameA = 0; + let frameB = 0; + // Use double RAF so layout + virtual rows settle before restore. + frameA = requestAnimationFrame(() => { + frameB = requestAnimationFrame(() => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollTop = savedScrollTop; + } + }); + }); + return () => { + cancelAnimationFrame(frameA); + cancelAnimationFrame(frameB); + }; + } + }, [ + isThisTabActive, + savedScrollTop, + conversationLoading, + scrollContainerRef, + shouldDisableAutoScroll, + saveScrollPosition, + ]); + + useEffect(() => { + return () => { + if (navigationHighlightTimerRef.current) { + clearTimeout(navigationHighlightTimerRef.current); + } + }; + }, []); + + // Register ref for user/system chat items + const registerChatItemRef = useCallback((groupId: string) => { + return (el: HTMLElement | null) => { + if (el) chatItemRefs.current.set(groupId, el); + else chatItemRefs.current.delete(groupId); + }; + }, []); + + // Register ref for individual tool items (for precise scroll targeting) + const registerToolRef = useCallback((toolId: string, el: HTMLElement | null) => { + if (el) toolItemRefs.current.set(toolId, el); + else toolItemRefs.current.delete(toolId); + }, []); + + // Loading state + if (conversationLoading) return ; + + // Empty state + if (!conversation || conversation.items.length === 0) return ; + + return ( +
    +
    + {/* Chat content */} +
    + {/* Sticky Context button */} + {allContextInjections.length > 0 && ( +
    + +
    + )} +
    0 ? '-2rem' : 0 }} + > +
    + {shouldVirtualize ? ( +
    + {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const item = conversation.items[virtualRow.index]; + if (!item) return null; + return ( +
    + +
    + ); + })} +
    + ) : ( + conversation.items.map((item) => ( + + )) + )} +
    +
    +
    + + {/* Context panel sidebar */} + {isContextPanelVisible && allContextInjections.length > 0 && ( +
    + setContextPanelVisible(false)} + projectRoot={sessionDetail?.session?.projectPath} + onNavigateToTurn={handleNavigateToTurn} + totalSessionTokens={lastAiGroupTotalTokens} + phaseInfo={sessionPhaseInfo ?? undefined} + selectedPhase={selectedContextPhase} + onPhaseChange={setSelectedContextPhase} + /> +
    + )} +
    +
    + ); +}; diff --git a/src/renderer/components/chat/ChatHistoryEmptyState.tsx b/src/renderer/components/chat/ChatHistoryEmptyState.tsx new file mode 100644 index 00000000..c98a2864 --- /dev/null +++ b/src/renderer/components/chat/ChatHistoryEmptyState.tsx @@ -0,0 +1,14 @@ +/** + * Empty state for ChatHistory when no conversation exists. + */ +export const ChatHistoryEmptyState = (): JSX.Element => { + return ( +
    +
    +
    💬
    +
    No conversation history
    +
    This session does not contain any messages yet.
    +
    +
    + ); +}; diff --git a/src/renderer/components/chat/ChatHistoryItem.tsx b/src/renderer/components/chat/ChatHistoryItem.tsx new file mode 100644 index 00000000..d611fea1 --- /dev/null +++ b/src/renderer/components/chat/ChatHistoryItem.tsx @@ -0,0 +1,133 @@ +import React from 'react'; + +import { + getHighlightProps, + HIGHLIGHT_CLASSES, + isPresetColorKey, + type TriggerColor, +} from '@shared/constants/triggerColors'; + +import { AIChatGroup } from './AIChatGroup'; +import { CompactBoundary } from './CompactBoundary'; +import { SystemChatGroup } from './SystemChatGroup'; +import { UserChatGroup } from './UserChatGroup'; + +import type { ChatItem } from '@renderer/types/groups'; + +interface ChatHistoryItemProps { + readonly item: ChatItem; + readonly highlightedGroupId: string | null; + readonly highlightToolUseId?: string; + readonly isSearchHighlight: boolean; + readonly isNavigationHighlight: boolean; + readonly highlightColor?: TriggerColor; + readonly registerChatItemRef: (groupId: string) => (el: HTMLElement | null) => void; + readonly registerAIGroupRef: (groupId: string) => (el: HTMLElement | null) => void; + /** Register ref for individual tool items (for precise scroll targeting) */ + readonly registerToolRef: (toolId: string, el: HTMLElement | null) => void; +} + +/** + * Get highlight class/style based on type: search (yellow), navigation (blue), error (custom color) + */ +function getHighlight( + isHighlighted: boolean, + isSearchHighlight: boolean, + isNavigationHighlight: boolean, + highlightColor?: TriggerColor +): { className: string; style?: React.CSSProperties } { + if (!isHighlighted) return { className: 'ring-0 bg-transparent' }; + if (isSearchHighlight) return { className: 'ring-2 ring-yellow-500/30 bg-yellow-500/5' }; + if (isNavigationHighlight) return { className: 'ring-2 ring-blue-500/30 bg-blue-500/5' }; + const key = highlightColor ?? 'red'; + if (isPresetColorKey(key)) return { className: HIGHLIGHT_CLASSES[key] }; + return getHighlightProps(key); +} + +/** + * Renders a single chat history item (user, system, ai, or compact). + */ +const ChatHistoryItemInner = ({ + item, + highlightedGroupId, + highlightToolUseId, + isSearchHighlight, + isNavigationHighlight, + highlightColor, + registerChatItemRef, + registerAIGroupRef, + registerToolRef, +}: ChatHistoryItemProps): JSX.Element | null => { + switch (item.type) { + case 'user': { + const isHighlighted = highlightedGroupId === item.group.id; + const hl = getHighlight( + isHighlighted, + isSearchHighlight, + isNavigationHighlight, + highlightColor + ); + return ( +
    + +
    + ); + } + case 'system': { + const isHighlighted = highlightedGroupId === item.group.id; + const hl = getHighlight( + isHighlighted, + isSearchHighlight, + isNavigationHighlight, + highlightColor + ); + return ( +
    + +
    + ); + } + case 'ai': { + const isHighlighted = highlightedGroupId === item.group.id; + // Pass highlightToolUseId to ALL AI groups (when not search/navigation) + // Each group will check if it contains the tool and expand accordingly + // This fixes issues where timestamp matching might fail to find the correct group + const toolUseIdForGroup = + !isSearchHighlight && !isNavigationHighlight ? highlightToolUseId : undefined; + const hl = getHighlight( + isHighlighted, + isSearchHighlight, + isNavigationHighlight, + highlightColor + ); + return ( +
    + +
    + ); + } + case 'compact': + return ; + default: + return null; + } +}; + +export const ChatHistoryItem = React.memo(ChatHistoryItemInner); diff --git a/src/renderer/components/chat/ChatHistoryLoadingState.tsx b/src/renderer/components/chat/ChatHistoryLoadingState.tsx new file mode 100644 index 00000000..d03c60d7 --- /dev/null +++ b/src/renderer/components/chat/ChatHistoryLoadingState.tsx @@ -0,0 +1,24 @@ +/** + * Loading skeleton for ChatHistory while conversation is loading. + */ +export const ChatHistoryLoadingState = (): JSX.Element => { + return ( +
    +
    + {/* Loading skeleton */} + {[1, 2, 3].map((i) => ( +
    + {/* User message skeleton - right aligned */} +
    +
    +
    + {/* AI response skeleton - left aligned with border accent */} +
    +
    +
    +
    + ))} +
    +
    + ); +}; diff --git a/src/renderer/components/chat/CompactBoundary.tsx b/src/renderer/components/chat/CompactBoundary.tsx new file mode 100644 index 00000000..e5548d41 --- /dev/null +++ b/src/renderer/components/chat/CompactBoundary.tsx @@ -0,0 +1,171 @@ +import React, { useState } from 'react'; +import ReactMarkdown from 'react-markdown'; + +import { + CODE_BG, + CODE_BORDER, + COLOR_TEXT_MUTED, + COLOR_TEXT_SECONDARY, + TOOL_CALL_BG, + TOOL_CALL_BORDER, + TOOL_CALL_TEXT, +} from '@renderer/constants/cssVariables'; +import { formatTokensCompact as formatTokens } from '@shared/utils/tokenFormatting'; +import { format } from 'date-fns'; +import { ChevronRight, Layers } from 'lucide-react'; +import remarkGfm from 'remark-gfm'; + +import { CopyButton } from '../common/CopyButton'; + +import { markdownComponents } from './markdownComponents'; + +import type { CompactGroup } from '@renderer/types/groups'; + +interface CompactBoundaryProps { + compactGroup: CompactGroup; +} + +/** + * CompactBoundary displays an interactive, collapsible marker indicating where + * the conversation was compacted. + * + * Features: + * - Minimalist design with subtle border and hover states + * - Click to expand/collapse compacted content + * - Scrollable content area with enforced max-height + * - Linear/Notion-inspired aesthetics + */ +export const CompactBoundary = ({ + compactGroup, +}: Readonly): React.JSX.Element => { + const { timestamp, message } = compactGroup; + const [isExpanded, setIsExpanded] = useState(false); + + // Extract content from message + const getCompactContent = (): string => { + if (!message?.content) return ''; + + if (typeof message.content === 'string') { + return message.content; + } + + // If it's an array of content blocks, extract text + if (Array.isArray(message.content)) { + return message.content + .filter((block: { type: string; text?: string }) => block.type === 'text') + .map((block: { type: string; text?: string }) => block.text ?? '') + .join('\n\n'); + } + + return ''; + }; + + const compactContent = getCompactContent(); + + return ( +
    + {/* Collapsible Header - Amber/orange accent for distinction */} + + + {/* Expanded Content */} + {isExpanded && ( +
    + {compactContent && } + + {/* Content - scrollable with left accent bar */} +
    + {compactContent ? ( + + {compactContent} + + ) : ( +
    + +
    +

    + Conversation Compacted +

    +

    + Previous messages were summarized to save context. The full conversation history + is preserved in the session file. +

    +
    +
    + )} +
    +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/ContextBadge.tsx b/src/renderer/components/chat/ContextBadge.tsx new file mode 100644 index 00000000..737ee229 --- /dev/null +++ b/src/renderer/components/chat/ContextBadge.tsx @@ -0,0 +1,571 @@ +/** + * ContextBadge - Displays a compact badge showing unified context injections. + * Shows count of NEW injections (CLAUDE.md, mentioned files, tool outputs) with hover popover. + * Replaces the standalone ClaudeMdBadge with a unified view of all context sources. + */ + +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { + COLOR_BORDER, + COLOR_BORDER_SUBTLE, + COLOR_SURFACE_RAISED, + COLOR_TEXT, + COLOR_TEXT_MUTED, + COLOR_TEXT_SECONDARY, +} from '@renderer/constants/cssVariables'; +import { resolveAbsolutePath, shortenDisplayPath } from '@renderer/utils/pathDisplay'; +import { formatTokensCompact as formatTokens } from '@shared/utils/tokenFormatting'; +import { ChevronRight } from 'lucide-react'; + +import { CopyablePath } from '../common/CopyablePath'; + +import type { + ClaudeMdContextInjection, + ContextInjection, + ContextStats, + MentionedFileInjection, + TaskCoordinationInjection, + ThinkingTextInjection, + ToolOutputInjection, + UserMessageInjection, +} from '@renderer/types/contextInjection'; + +interface ContextBadgeProps { + stats: ContextStats; + projectRoot?: string; +} + +/** + * Type guard for ClaudeMdContextInjection. + */ +function isClaudeMdInjection(inj: ContextInjection): inj is ClaudeMdContextInjection { + return inj.category === 'claude-md'; +} + +/** + * Type guard for MentionedFileInjection. + */ +function isMentionedFileInjection(inj: ContextInjection): inj is MentionedFileInjection { + return inj.category === 'mentioned-file'; +} + +/** + * Type guard for ToolOutputInjection. + */ +function isToolOutputInjection(inj: ContextInjection): inj is ToolOutputInjection { + return inj.category === 'tool-output'; +} + +/** + * Type guard for ThinkingTextInjection. + */ +function isThinkingTextInjection(inj: ContextInjection): inj is ThinkingTextInjection { + return inj.category === 'thinking-text'; +} + +/** + * Type guard for TaskCoordinationInjection. + */ +function isTaskCoordinationInjection(inj: ContextInjection): inj is TaskCoordinationInjection { + return inj.category === 'task-coordination'; +} + +/** + * Type guard for UserMessageInjection. + */ +function isUserMessageInjection(inj: ContextInjection): inj is UserMessageInjection { + return inj.category === 'user-message'; +} + +/** + * Section component for expandable groups in the popover. + */ +const PopoverSection = ({ + title, + count, + tokenCount, + children, + defaultExpanded = false, +}: Readonly<{ + title: string; + count: number; + tokenCount: number; + children: React.ReactNode; + defaultExpanded?: boolean; +}>): React.ReactElement => { + const [expanded, setExpanded] = useState(defaultExpanded); + + return ( +
    + {/* Section header */} +
    { + e.stopPropagation(); + setExpanded(!expanded); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + setExpanded(!expanded); + } + }} + > + + + {title} ({count}) ~{formatTokens(tokenCount)} tokens + +
    + {/* Section content */} + {expanded &&
    {children}
    } +
    + ); +}; + +export const ContextBadge = ({ + stats, + projectRoot, +}: Readonly): React.ReactElement | null => { + const [showPopover, setShowPopover] = useState(false); + const [popoverStyle, setPopoverStyle] = useState({}); + const [arrowStyle, setArrowStyle] = useState({}); + const containerRef = useRef(null); + const popoverRef = useRef(null); + + // Calculate total new count + const totalNew = useMemo( + () => + stats.newCounts.claudeMd + + stats.newCounts.mentionedFiles + + stats.newCounts.toolOutputs + + stats.newCounts.thinkingText + + stats.newCounts.taskCoordination + + stats.newCounts.userMessages, + [stats.newCounts] + ); + + // Filter new injections by category + const newClaudeMdInjections = useMemo( + () => stats.newInjections.filter(isClaudeMdInjection), + [stats.newInjections] + ); + + const newMentionedFileInjections = useMemo( + () => stats.newInjections.filter(isMentionedFileInjection), + [stats.newInjections] + ); + + const newToolOutputInjections = useMemo( + () => stats.newInjections.filter(isToolOutputInjection), + [stats.newInjections] + ); + + const newThinkingTextInjections = useMemo( + () => stats.newInjections.filter(isThinkingTextInjection), + [stats.newInjections] + ); + + const newTaskCoordinationInjections = useMemo( + () => stats.newInjections.filter(isTaskCoordinationInjection), + [stats.newInjections] + ); + + const newUserMessageInjections = useMemo( + () => stats.newInjections.filter(isUserMessageInjection), + [stats.newInjections] + ); + + // Calculate total new tokens + const totalNewTokens = useMemo( + () => stats.newInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [stats.newInjections] + ); + + // Calculate token totals per section + const claudeMdTokens = useMemo( + () => newClaudeMdInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [newClaudeMdInjections] + ); + + const mentionedFileTokens = useMemo( + () => newMentionedFileInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [newMentionedFileInjections] + ); + + const toolOutputTokens = useMemo( + () => newToolOutputInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [newToolOutputInjections] + ); + + const thinkingTextTokens = useMemo( + () => newThinkingTextInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [newThinkingTextInjections] + ); + + const taskCoordinationTokens = useMemo( + () => newTaskCoordinationInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [newTaskCoordinationInjections] + ); + + const userMessageTokens = useMemo( + () => newUserMessageInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [newUserMessageInjections] + ); + + // Linear-style neutral badge — uses theme-aware CSS variables + const badgeStyle: React.CSSProperties = { + backgroundColor: COLOR_SURFACE_RAISED, + border: `1px solid ${COLOR_BORDER}`, + color: COLOR_TEXT_SECONDARY, + }; + + // Calculate popover position based on trigger element + useEffect(() => { + if (showPopover && containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + const popoverWidth = 300; + const margin = 12; + + // Determine if popover should open left or right + const openLeft = rect.left + popoverWidth > viewportWidth - 20; + + // Determine if popover should open above or below + const spaceBelow = viewportHeight - rect.bottom - margin; + const spaceAbove = rect.top - margin; + const openAbove = spaceBelow < 200 && spaceAbove > spaceBelow; + + const maxHeight = Math.max(openAbove ? spaceAbove : spaceBelow, 120) - 8; + + queueMicrotask(() => { + setPopoverStyle({ + position: 'fixed', + ...(openAbove ? { bottom: viewportHeight - rect.top + 4 } : { top: rect.bottom + 4 }), + left: openLeft ? rect.right - popoverWidth : rect.left, + minWidth: 260, + maxWidth: 340, + maxHeight, + overflowY: 'auto', + zIndex: 99999, + }); + + setArrowStyle({ + position: 'absolute', + ...(openAbove + ? { + bottom: -4, + borderRight: `1px solid ${COLOR_BORDER}`, + borderBottom: `1px solid ${COLOR_BORDER}`, + borderLeft: 'none', + borderTop: 'none', + } + : { + top: -4, + borderLeft: `1px solid ${COLOR_BORDER}`, + borderTop: `1px solid ${COLOR_BORDER}`, + borderRight: 'none', + borderBottom: 'none', + }), + [openLeft ? 'right' : 'left']: 12, + width: 8, + height: 8, + transform: 'rotate(45deg)', + backgroundColor: COLOR_SURFACE_RAISED, + }); + }); + } + }, [showPopover]); + + // Handle click outside and scroll to close popover + useEffect(() => { + if (!showPopover) return; + + const isInsideRect = (el: HTMLElement | null, x: number, y: number): boolean => { + if (!el) return false; + const rect = el.getBoundingClientRect(); + return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; + }; + + const handleClickOutside = (e: MouseEvent): void => { + // Use coordinate-based hit test — reliable with portals, scrollbars, and re-renders + if ( + isInsideRect(popoverRef.current, e.clientX, e.clientY) || + isInsideRect(containerRef.current, e.clientX, e.clientY) + ) { + return; + } + setShowPopover(false); + }; + + const handleScroll = (e: Event): void => { + // Don't close if scrolling inside the popover + if (popoverRef.current && e.target instanceof Node && popoverRef.current.contains(e.target)) { + return; + } + setShowPopover(false); + }; + + document.addEventListener('mousedown', handleClickOutside); + window.addEventListener('scroll', handleScroll, true); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + window.removeEventListener('scroll', handleScroll, true); + }; + }, [showPopover]); + + // Only render if there are new injections + if (totalNew === 0) { + return null; + } + + return ( +
    { + e.stopPropagation(); + setShowPopover(!showPopover); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + setShowPopover(!showPopover); + } + }} + > + {/* Badge */} + + Context + +{totalNew} + + + {/* Popover - rendered via Portal to escape stacking context */} + {showPopover && + createPortal( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events -- dialog uses stopPropagation only, not interactive +
    e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + > + {/* Arrow pointer */} +
    + + {/* Title */} +
    + New Context Injected In This Turn +
    + + {/* Sections */} +
    + {/* User Messages section */} + {newUserMessageInjections.length > 0 && ( + + {newUserMessageInjections.map((injection) => ( +
    +
    + + Turn {injection.turnIndex + 1} + + + ~{formatTokens(injection.estimatedTokens)} tokens + +
    + {injection.textPreview && ( +
    + {injection.textPreview} +
    + )} +
    + ))} +
    + )} + + {/* CLAUDE.md Files section */} + {newClaudeMdInjections.length > 0 && ( + + {newClaudeMdInjections.map((injection) => { + const displayPath = + shortenDisplayPath(injection.path, projectRoot) || injection.displayName; + const absolutePath = resolveAbsolutePath(injection.path, projectRoot); + return ( +
    + +
    + ~{formatTokens(injection.estimatedTokens)} tokens +
    +
    + ); + })} +
    + )} + + {/* Mentioned Files section */} + {newMentionedFileInjections.length > 0 && ( + + {newMentionedFileInjections.map((injection) => { + const displayPath = shortenDisplayPath(injection.path, projectRoot); + const absolutePath = resolveAbsolutePath(injection.path, projectRoot); + return ( +
    + +
    + ~{formatTokens(injection.estimatedTokens)} tokens +
    +
    + ); + })} +
    + )} + + {/* Tool Outputs section */} + {newToolOutputInjections.length > 0 && ( + + {newToolOutputInjections.map((injection) => + injection.toolBreakdown.map((tool, idx) => ( +
    + {tool.toolName} + + ~{formatTokens(tool.tokenCount)} tokens + +
    + )) + )} +
    + )} + + {/* Task Coordination section */} + {newTaskCoordinationInjections.length > 0 && ( + + {newTaskCoordinationInjections.map((injection) => + injection.breakdown.map((item, idx) => ( +
    + {item.label} + + ~{formatTokens(item.tokenCount)} tokens + +
    + )) + )} +
    + )} + + {/* Thinking + Text section */} + {newThinkingTextInjections.length > 0 && ( + + {newThinkingTextInjections.map((injection) => ( +
    +
    + Turn {injection.turnIndex + 1} +
    +
    + {injection.breakdown.map((item, idx) => ( +
    + + {item.type === 'thinking' ? 'Thinking' : 'Text'} + + + ~{formatTokens(item.tokenCount)} tokens + +
    + ))} +
    +
    + ))} +
    + )} +
    + + {/* Total tokens footer */} +
    + Total new tokens + + ~{formatTokens(totalNewTokens)} tokens + +
    +
    , + document.body + )} +
    + ); +}; diff --git a/src/renderer/components/chat/DisplayItemList.tsx b/src/renderer/components/chat/DisplayItemList.tsx new file mode 100644 index 00000000..e0e82e30 --- /dev/null +++ b/src/renderer/components/chat/DisplayItemList.tsx @@ -0,0 +1,232 @@ +import React, { useCallback, useState } from 'react'; + +import { LinkedToolItem } from './items/LinkedToolItem'; +import { SlashItem } from './items/SlashItem'; +import { SubagentItem } from './items/SubagentItem'; +import { TeammateMessageItem } from './items/TeammateMessageItem'; +import { TextItem } from './items/TextItem'; +import { ThinkingItem } from './items/ThinkingItem'; + +import type { AIGroupDisplayItem } from '@renderer/types/groups'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +interface DisplayItemListProps { + items: AIGroupDisplayItem[]; + onItemClick: (itemId: string) => void; + expandedItemIds: Set; + aiGroupId: string; + /** Tool use ID to highlight for error deep linking */ + highlightToolUseId?: string; + /** Custom highlight color from trigger */ + highlightColor?: TriggerColor; + /** Map of tool use ID to trigger color for notification dots */ + notificationColorMap?: Map; + /** Optional callback to register tool element refs for scroll targeting */ + registerToolRef?: (toolId: string, el: HTMLDivElement | null) => void; +} + +/** + * Truncates text to a maximum length and adds ellipsis if needed. + */ +function truncateText(text: string, maxLength: number): string { + if (text.length <= maxLength) { + return text; + } + return text.substring(0, maxLength) + '...'; +} + +/** + * Renders a flat list of AIGroupDisplayItem[] into the appropriate components. + * + * This component maps each display item to its corresponding component based on type: + * - thinking -> ThinkingItem + * - output -> TextItem + * - tool -> LinkedToolItem + * - subagent -> SubagentItem + * - slash -> SlashItem + * + * The list is completely flat with no nested toggles or hierarchies. + */ +export const DisplayItemList = ({ + items, + onItemClick, + expandedItemIds, + aiGroupId, + highlightToolUseId, + highlightColor, + notificationColorMap, + registerToolRef, +}: Readonly): React.JSX.Element => { + // Reply-link highlight: when hovering a reply badge, dim everything except the linked pair + const [replyLinkToolId, setReplyLinkToolId] = useState(null); + + const handleReplyHover = useCallback((toolId: string | null) => { + setReplyLinkToolId(toolId); + }, []); + + /** Check if an item is part of the currently highlighted reply link */ + const isItemInReplyLink = (item: AIGroupDisplayItem): boolean => { + if (!replyLinkToolId) return false; + if (item.type === 'tool' && item.tool.id === replyLinkToolId) return true; + if (item.type === 'teammate_message' && item.teammateMessage.replyToToolId === replyLinkToolId) + return true; + return false; + }; + + if (!items || items.length === 0) { + return ( +
    + No items to display +
    + ); + } + + return ( +
    + {items.map((item, index) => { + let itemKey = ''; + let element: React.ReactNode = null; + + switch (item.type) { + case 'thinking': { + itemKey = `thinking-${index}`; + const thinkingStep = { + id: itemKey, + type: 'thinking' as const, + startTime: item.timestamp, + endTime: item.timestamp, + durationMs: 0, + content: { thinkingText: item.content, tokenCount: item.tokenCount }, + tokens: { input: 0, output: item.tokenCount ?? 0 }, + context: 'main' as const, + }; + element = ( + onItemClick(itemKey)} + isExpanded={expandedItemIds.has(itemKey)} + /> + ); + break; + } + + case 'output': { + itemKey = `output-${index}`; + const textStep = { + id: itemKey, + type: 'output' as const, + startTime: item.timestamp, + endTime: item.timestamp, + durationMs: 0, + content: { outputText: item.content, tokenCount: item.tokenCount }, + tokens: { input: 0, output: item.tokenCount ?? 0 }, + context: 'main' as const, + }; + element = ( + onItemClick(itemKey)} + isExpanded={expandedItemIds.has(itemKey)} + /> + ); + break; + } + + case 'tool': { + itemKey = `tool-${item.tool.id}-${index}`; + element = ( + onItemClick(itemKey)} + isExpanded={expandedItemIds.has(itemKey)} + isHighlighted={highlightToolUseId === item.tool.id} + highlightColor={highlightColor} + notificationDotColor={notificationColorMap?.get(item.tool.id)} + registerRef={ + registerToolRef ? (el) => registerToolRef(item.tool.id, el) : undefined + } + /> + ); + break; + } + + case 'subagent': { + itemKey = `subagent-${item.subagent.id}-${index}`; + const subagentStep = { + id: itemKey, + type: 'subagent' as const, + startTime: item.subagent.startTime, + endTime: item.subagent.endTime, + durationMs: item.subagent.durationMs, + content: { + subagentId: item.subagent.id, + subagentDescription: item.subagent.description, + }, + isParallel: item.subagent.isParallel, + context: 'main' as const, + }; + element = ( + onItemClick(itemKey)} + isExpanded={expandedItemIds.has(itemKey)} + aiGroupId={aiGroupId} + highlightToolUseId={highlightToolUseId} + highlightColor={highlightColor} + notificationColorMap={notificationColorMap} + registerToolRef={registerToolRef} + /> + ); + break; + } + + case 'slash': { + itemKey = `slash-${item.slash.name}-${index}`; + element = ( + onItemClick(itemKey)} + isExpanded={expandedItemIds.has(itemKey)} + /> + ); + break; + } + + case 'teammate_message': { + itemKey = `teammate-${item.teammateMessage.id}-${index}`; + element = ( + onItemClick(itemKey)} + isExpanded={expandedItemIds.has(itemKey)} + onReplyHover={handleReplyHover} + /> + ); + break; + } + + default: + return null; + } + + // Apply reply-link spotlight: dim items not in the highlighted pair + const isDimmed = replyLinkToolId !== null && !isItemInReplyLink(item); + return ( +
    + {element} +
    + ); + })} +
    + ); +}; diff --git a/src/renderer/components/chat/LastOutputDisplay.tsx b/src/renderer/components/chat/LastOutputDisplay.tsx new file mode 100644 index 00000000..16d47d7a --- /dev/null +++ b/src/renderer/components/chat/LastOutputDisplay.tsx @@ -0,0 +1,244 @@ +import React from 'react'; +import ReactMarkdown from 'react-markdown'; + +import { useStore } from '@renderer/store'; +import { AlertTriangle, CheckCircle, FileCheck, XCircle } from 'lucide-react'; +import remarkGfm from 'remark-gfm'; +import { useShallow } from 'zustand/react/shallow'; + +import { CopyButton } from '../common/CopyButton'; +import { OngoingBanner } from '../common/OngoingIndicator'; + +import { createMarkdownComponents, markdownComponents } from './markdownComponents'; +import { createSearchContext } from './searchHighlightUtils'; + +import type { AIGroupLastOutput } from '@renderer/types/groups'; + +interface LastOutputDisplayProps { + lastOutput: AIGroupLastOutput | null; + aiGroupId: string; + /** Whether this is the last AI group in the conversation */ + isLastGroup?: boolean; + /** Whether the session is ongoing (from sessions array, same source as sidebar) */ + isSessionOngoing?: boolean; +} + +/** + * LastOutputDisplay shows the always-visible last text output OR last tool result. + * This is what the user sees as "the answer" from the AI. + * + * Features: + * - Shows text output with elegant prose styling + * - Shows tool result with tool name and icon + * - Handles error states for tool results + * - Shows timestamp + * - Expandable for long content + */ +export const LastOutputDisplay = ({ + lastOutput, + aiGroupId, + isLastGroup = false, + isSessionOngoing = false, +}: Readonly): React.JSX.Element | null => { + const { searchQuery, searchMatches, currentSearchIndex } = useStore( + useShallow((s) => ({ + searchQuery: s.searchQuery, + searchMatches: s.searchMatches, + currentSearchIndex: s.currentSearchIndex, + })) + ); + const isTextOutput = lastOutput?.type === 'text' && Boolean(lastOutput.text); + + // Create search context (fresh each render so counter starts at 0) + const searchCtx = + searchQuery && isTextOutput + ? createSearchContext(searchQuery, aiGroupId, searchMatches, currentSearchIndex) + : null; + + // Create markdown components with optional search highlighting + // When search is active, create fresh each render (match counter is stateful and must start at 0) + // useMemo would cache stale closures when parent re-renders without search deps changing + const mdComponents = searchCtx ? createMarkdownComponents(searchCtx) : markdownComponents; + + // Show ongoing banner if this is the last AI group and session is ongoing + // This uses the same source (sessions array) as the sidebar green dot for consistency + if (isLastGroup && isSessionOngoing) { + return ; + } + + if (!lastOutput) { + return null; + } + + const { type } = lastOutput; + + // Render text output + if (type === 'text' && lastOutput.text) { + const textContent = lastOutput.text || ''; + + return ( +
    + + + {/* Content - scrollable */} +
    + + {textContent} + +
    +
    + ); + } + + // Render tool result + if (type === 'tool_result' && lastOutput.toolResult) { + const isError = lastOutput.isError ?? false; + const Icon = isError ? XCircle : CheckCircle; + + return ( +
    + {/* Header */} +
    + + {lastOutput.toolName && ( + + {lastOutput.toolName} + + )} + {isError && ( + + Error + + )} +
    + + {/* Content */} +
    +
    +            {lastOutput.toolResult}
    +          
    +
    +
    + ); + } + + // Render interruption as a simple horizontal banner + if (type === 'interruption') { + return ( +
    + + + Request interrupted by user + +
    + ); + } + + // Render plan_exit (ExitPlanMode) with plan content in markdown + if (type === 'plan_exit' && lastOutput.planContent) { + const planContent = lastOutput.planContent || ''; + const planPreamble = lastOutput.planPreamble; + + return ( +
    + {/* Preamble text (e.g., "The plan is complete. Let me exit plan mode...") */} + {planPreamble && ( +
    +
    + + {planPreamble} + +
    +
    + )} + + {/* Plan content block */} +
    + {/* Header */} +
    +
    + + + Plan Ready for Approval + +
    + +
    + + {/* Plan content - scrollable */} +
    + + {planContent} + +
    +
    +
    + ); + } + + return null; +}; diff --git a/src/renderer/components/chat/SessionContextPanel/DirectoryTree/DirectoryTreeNode.tsx b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/DirectoryTreeNode.tsx new file mode 100644 index 00000000..28f06a05 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/DirectoryTreeNode.tsx @@ -0,0 +1,125 @@ +/** + * DirectoryTreeNode - Recursive component for rendering directory tree nodes. + */ + +import React, { useState } from 'react'; + +import { CopyablePath } from '@renderer/components/common/CopyablePath'; +import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; +import { ChevronRight } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; +import { formatFirstSeen, parseTurnIndex } from '../utils/pathParsing'; + +import type { TreeNode } from './types'; + +interface DirectoryTreeNodeProps { + node: TreeNode; + depth?: number; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const DirectoryTreeNode = ({ + node, + depth = 0, + onNavigateToTurn, +}: Readonly): React.ReactElement | null => { + const [expanded, setExpanded] = useState(true); + const indent = depth * 12; + + const sortedChildren = Array.from(node.children.values()).sort((a, b) => { + if (a.isFile && !b.isFile) return -1; + if (!a.isFile && b.isFile) return 1; + return a.name.localeCompare(b.name); + }); + + if (node.isFile) { + const turnIndex = node.firstSeenInGroup ? parseTurnIndex(node.firstSeenInGroup) : -1; + const isClickable = onNavigateToTurn && turnIndex >= 0; + + return ( +
    + + (~{formatTokens(node.tokens ?? 0)}) + {node.firstSeenInGroup && + (isClickable ? ( + + ) : ( + + @{formatFirstSeen(node.firstSeenInGroup)} + + ))} +
    + ); + } + + return ( +
    + {node.name && ( +
    { + e.stopPropagation(); + setExpanded(!expanded); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + setExpanded(!expanded); + } + }} + > + + {node.name}/ +
    + )} + {expanded && + sortedChildren.map((child) => ( + + ))} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts new file mode 100644 index 00000000..48e38d43 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/buildDirectoryTree.ts @@ -0,0 +1,47 @@ +/** + * Build a directory tree structure from CLAUDE.md injections. + */ + +import type { TreeNode } from './types'; +import type { ClaudeMdContextInjection } from '@renderer/types/contextInjection'; + +/** + * Build a tree structure from a list of directory CLAUDE.md injections. + */ +export function buildDirectoryTree( + injections: ClaudeMdContextInjection[], + projectRoot: string +): TreeNode { + const root: TreeNode = { name: '', path: '', isFile: false, children: new Map() }; + + for (const injection of injections) { + let relativePath = injection.path; + if (projectRoot && relativePath.startsWith(projectRoot)) { + relativePath = relativePath.slice(projectRoot.length); + if (relativePath.startsWith('/') || relativePath.startsWith('\\')) + relativePath = relativePath.slice(1); + } + + const parts = relativePath.split(/[\\/]/); + let current = root; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + const isLast = i === parts.length - 1; + + if (!current.children.has(part)) { + current.children.set(part, { + name: part, + path: isLast ? injection.path : '', + isFile: isLast && part === 'CLAUDE.md', + tokens: isLast ? injection.estimatedTokens : undefined, + firstSeenInGroup: isLast ? injection.firstSeenInGroup : undefined, + children: new Map(), + }); + } + current = current.children.get(part)!; + } + } + + return root; +} diff --git a/src/renderer/components/chat/SessionContextPanel/DirectoryTree/types.ts b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/types.ts new file mode 100644 index 00000000..386c5e7f --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/DirectoryTree/types.ts @@ -0,0 +1,12 @@ +/** + * Type definitions for DirectoryTree components. + */ + +export interface TreeNode { + name: string; + path: string; + isFile: boolean; + tokens?: number; + firstSeenInGroup?: string; + children: Map; +} diff --git a/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdFilesSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdFilesSection.tsx new file mode 100644 index 00000000..f3e38b4f --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdFilesSection.tsx @@ -0,0 +1,90 @@ +/** + * ClaudeMdFilesSection - Section for displaying CLAUDE.md files with nested groups. + */ + +import React, { useMemo } from 'react'; + +import { CLAUDE_MD_GROUP_CONFIG, CLAUDE_MD_GROUP_ORDER } from '../types'; + +import { ClaudeMdSubSection } from './ClaudeMdSection'; +import { CollapsibleSection } from './CollapsibleSection'; + +import type { ClaudeMdGroupCategory } from '../types'; +import type { ClaudeMdContextInjection } from '@renderer/types/contextInjection'; + +interface ClaudeMdFilesSectionProps { + injections: ClaudeMdContextInjection[]; + tokenCount: number; + isExpanded: boolean; + onToggle: () => void; + projectRoot: string; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const ClaudeMdFilesSection = ({ + injections, + tokenCount, + isExpanded, + onToggle, + projectRoot, + onNavigateToTurn, +}: Readonly): React.ReactElement | null => { + // Group CLAUDE.md injections by category + const claudeMdGroups = useMemo(() => { + const groups = new Map(); + + for (const category of CLAUDE_MD_GROUP_ORDER) { + groups.set(category, []); + } + + for (const injection of injections) { + for (const [category, config] of Object.entries(CLAUDE_MD_GROUP_CONFIG)) { + if (config.sources.includes(injection.source)) { + const group = groups.get(category as ClaudeMdGroupCategory) ?? []; + group.push(injection); + groups.set(category as ClaudeMdGroupCategory, group); + break; + } + } + } + + return groups; + }, [injections]); + + // Get non-empty CLAUDE.md groups + const nonEmptyClaudeMdGroups = useMemo( + () => + CLAUDE_MD_GROUP_ORDER.filter((category) => { + const group = claudeMdGroups.get(category); + return group && group.length > 0; + }), + [claudeMdGroups] + ); + + if (injections.length === 0) return null; + + return ( + + {nonEmptyClaudeMdGroups.map((category) => { + const group = claudeMdGroups.get(category) ?? []; + const config = CLAUDE_MD_GROUP_CONFIG[category]; + return ( + + ); + })} + + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdSection.tsx new file mode 100644 index 00000000..90c665ab --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/ClaudeMdSection.tsx @@ -0,0 +1,86 @@ +/** + * ClaudeMdSection - CLAUDE.md files section with nested Global/Project/Directory groups. + */ + +import React, { useState } from 'react'; + +import { ChevronRight } from 'lucide-react'; + +import { buildDirectoryTree } from '../DirectoryTree/buildDirectoryTree'; +import { DirectoryTreeNode } from '../DirectoryTree/DirectoryTreeNode'; +import { ClaudeMdItem } from '../items/ClaudeMdItem'; +import { formatTokens } from '../utils/formatting'; + +import type { ClaudeMdContextInjection } from '@renderer/types/contextInjection'; + +interface ClaudeMdSubSectionProps { + label: string; + injections: ClaudeMdContextInjection[]; + isDirectory: boolean; + projectRoot: string; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const ClaudeMdSubSection = ({ + label, + injections, + isDirectory, + projectRoot, + onNavigateToTurn, +}: Readonly): React.ReactElement => { + const [expanded, setExpanded] = useState(true); + const sectionTokens = injections.reduce((sum, inj) => sum + inj.estimatedTokens, 0); + + return ( +
    +
    setExpanded(!expanded)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setExpanded(!expanded); + } + }} + > + + {label} + + {injections.length} + + (~{formatTokens(sectionTokens)}) +
    + + {expanded && ( +
    + {isDirectory ? ( + + ) : ( + injections.map((injection) => ( + + )) + )} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/CollapsibleSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/CollapsibleSection.tsx new file mode 100644 index 00000000..bbceba0c --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/CollapsibleSection.tsx @@ -0,0 +1,77 @@ +/** + * CollapsibleSection - Generic collapsible wrapper for content sections. + */ + +import React from 'react'; + +import { ChevronDown, ChevronRight } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; + +interface CollapsibleSectionProps { + title: string; + count: number; + tokenCount: number; + isExpanded: boolean; + onToggle: () => void; + children: React.ReactNode; +} + +export const CollapsibleSection = ({ + title, + count, + tokenCount, + isExpanded, + onToggle, + children, +}: Readonly): React.ReactElement => { + return ( +
    + + + {isExpanded && ( +
    + {children} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/MentionedFilesSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/MentionedFilesSection.tsx new file mode 100644 index 00000000..75dd31a1 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/MentionedFilesSection.tsx @@ -0,0 +1,50 @@ +/** + * MentionedFilesSection - Section for displaying mentioned files. + */ + +import React from 'react'; + +import { MentionedFileItem } from '../items/MentionedFileItem'; + +import { CollapsibleSection } from './CollapsibleSection'; + +import type { MentionedFileInjection } from '@renderer/types/contextInjection'; + +interface MentionedFilesSectionProps { + injections: MentionedFileInjection[]; + tokenCount: number; + isExpanded: boolean; + onToggle: () => void; + projectRoot?: string; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const MentionedFilesSection = ({ + injections, + tokenCount, + isExpanded, + onToggle, + projectRoot, + onNavigateToTurn, +}: Readonly): React.ReactElement | null => { + if (injections.length === 0) return null; + + return ( + + {injections.map((injection) => ( + + ))} + + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx b/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx new file mode 100644 index 00000000..042e12cf --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx @@ -0,0 +1,155 @@ +/** + * SessionContextHeader - Header component with title, help tooltip, and token stats. + */ + +import React from 'react'; + +import { + COLOR_BORDER, + COLOR_BORDER_SUBTLE, + COLOR_SURFACE_OVERLAY, + COLOR_TEXT, + COLOR_TEXT_MUTED, + COLOR_TEXT_SECONDARY, +} from '@renderer/constants/cssVariables'; +import { FileText, X } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; + +import { SessionContextHelpTooltip } from './SessionContextHelpTooltip'; + +import type { ContextPhaseInfo } from '@renderer/types/contextInjection'; + +interface SessionContextHeaderProps { + injectionCount: number; + totalTokens: number; + totalSessionTokens?: number; + onClose?: () => void; + phaseInfo?: ContextPhaseInfo; + selectedPhase: number | null; + onPhaseChange: (phase: number | null) => void; +} + +export const SessionContextHeader = ({ + injectionCount, + totalTokens, + totalSessionTokens, + onClose, + phaseInfo, + selectedPhase, + onPhaseChange, +}: Readonly): React.ReactElement => { + return ( +
    + {/* Title row */} +
    +
    + +

    + Visible Context +

    + + {injectionCount} + +
    +
    + + {onClose && ( + + )} +
    +
    + + {/* Token comparison stats */} +
    +
    + {/* Visible Context tokens */} +
    + Visible: + + ~{formatTokens(totalTokens)} + +
    + {/* Total Session tokens (if provided) */} + {totalSessionTokens !== undefined && totalSessionTokens > 0 && ( +
    + Total: + + {formatTokens(totalSessionTokens)} + +
    + )} +
    + {/* Percentage of total */} + {totalSessionTokens !== undefined && totalSessionTokens > 0 && ( + + {Math.min((totalTokens / totalSessionTokens) * 100, 100).toFixed(1)}% of total + + )} +
    + + {/* Phase selector - only shown when compactions exist */} + {phaseInfo && phaseInfo.phases.length > 1 && ( +
    + + Phase: + + {phaseInfo.phases.map((phase) => ( + + ))} + +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/SessionContextHelpTooltip.tsx b/src/renderer/components/chat/SessionContextPanel/components/SessionContextHelpTooltip.tsx new file mode 100644 index 00000000..776fd4be --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/SessionContextHelpTooltip.tsx @@ -0,0 +1,184 @@ +/** + * SessionContextHelpTooltip - Help tooltip explaining Visible Context vs Total Tokens. + */ + +import React, { useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { HelpCircle } from 'lucide-react'; + +export const SessionContextHelpTooltip = (): React.ReactElement => { + const [showTooltip, setShowTooltip] = useState(false); + const [tooltipStyle, setTooltipStyle] = useState({}); + const [arrowStyle, setArrowStyle] = useState({}); + const containerRef = useRef(null); + const tooltipRef = useRef(null); + const hideTimeoutRef = useRef | null>(null); + + const clearHideTimeout = (): void => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + hideTimeoutRef.current = null; + } + }; + + const handleMouseEnter = (): void => { + clearHideTimeout(); + setShowTooltip(true); + }; + + const handleMouseLeave = (): void => { + clearHideTimeout(); + hideTimeoutRef.current = setTimeout(() => setShowTooltip(false), 150); + }; + + useEffect(() => { + return () => clearHideTimeout(); + }, []); + + // Close tooltip on scroll + useEffect(() => { + if (!showTooltip) return; + + const handleScroll = (): void => { + setShowTooltip(false); + }; + + window.addEventListener('scroll', handleScroll, true); + return () => window.removeEventListener('scroll', handleScroll, true); + }, [showTooltip]); + + // Calculate tooltip position based on trigger element + useEffect(() => { + if (showTooltip && containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + const tooltipWidth = 288; // w-72 = 18rem = 288px + + setTooltipStyle({ + position: 'fixed', + top: rect.bottom + 8, + left: rect.right - tooltipWidth, + width: tooltipWidth, + zIndex: 99999, + }); + + setArrowStyle({ + position: 'absolute', + top: -4, + right: 12, + width: 8, + height: 8, + transform: 'rotate(45deg)', + backgroundColor: 'var(--color-surface-raised)', + borderLeft: '1px solid var(--color-border)', + borderTop: '1px solid var(--color-border)', + }); + } + }, [showTooltip]); + + return ( +
    { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setShowTooltip(!showTooltip); + } + }} + > + + + {showTooltip && + createPortal( +
    + {/* Arrow */} +
    + +
    + {/* What is Visible Context */} +
    +
    + What is Visible Context? +
    +

    + Tokens consumed by file reads, tool outputs, and configuration files (CLAUDE.md) + that are injected into the conversation. +

    +
    + + {/* Difference with Total */} +
    +
    + Total Context vs Visible Context +
    +
    +
    + + Total: + + + Total tokens that are injected into the conversation + +
    +
    + + Visible: + + + Subset of tokens that you can optimize & debug + +
    +
    +
    + + {/* Tips */} +
    +
    + Optimization Tips +
    +
      +
    • Shorten large CLAUDE.md files
    • +
    • Split large @-mentioned files
    • +
    • Adjust MCP tool output verbosity
    • +
    +
    +
    +
    , + document.body + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/TaskCoordinationSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/TaskCoordinationSection.tsx new file mode 100644 index 00000000..508eccdb --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/TaskCoordinationSection.tsx @@ -0,0 +1,47 @@ +/** + * TaskCoordinationSection - Section for displaying task coordination injections. + */ + +import React from 'react'; + +import { TaskCoordinationItem } from '../items/TaskCoordinationItem'; + +import { CollapsibleSection } from './CollapsibleSection'; + +import type { TaskCoordinationInjection } from '@renderer/types/contextInjection'; + +interface TaskCoordinationSectionProps { + injections: TaskCoordinationInjection[]; + tokenCount: number; + isExpanded: boolean; + onToggle: () => void; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const TaskCoordinationSection = ({ + injections, + tokenCount, + isExpanded, + onToggle, + onNavigateToTurn, +}: Readonly): React.ReactElement | null => { + if (injections.length === 0) return null; + + return ( + + {injections.map((injection) => ( + + ))} + + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/ThinkingTextSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/ThinkingTextSection.tsx new file mode 100644 index 00000000..88d6f333 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/ThinkingTextSection.tsx @@ -0,0 +1,47 @@ +/** + * ThinkingTextSection - Section for displaying thinking text. + */ + +import React from 'react'; + +import { ThinkingTextItem } from '../items/ThinkingTextItem'; + +import { CollapsibleSection } from './CollapsibleSection'; + +import type { ThinkingTextInjection } from '@renderer/types/contextInjection'; + +interface ThinkingTextSectionProps { + injections: ThinkingTextInjection[]; + tokenCount: number; + isExpanded: boolean; + onToggle: () => void; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const ThinkingTextSection = ({ + injections, + tokenCount, + isExpanded, + onToggle, + onNavigateToTurn, +}: Readonly): React.ReactElement | null => { + if (injections.length === 0) return null; + + return ( + + {injections.map((injection) => ( + + ))} + + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/ToolOutputsSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/ToolOutputsSection.tsx new file mode 100644 index 00000000..57de5672 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/ToolOutputsSection.tsx @@ -0,0 +1,47 @@ +/** + * ToolOutputsSection - Section for displaying tool outputs. + */ + +import React from 'react'; + +import { ToolOutputItem } from '../items/ToolOutputItem'; + +import { CollapsibleSection } from './CollapsibleSection'; + +import type { ToolOutputInjection } from '@renderer/types/contextInjection'; + +interface ToolOutputsSectionProps { + injections: ToolOutputInjection[]; + tokenCount: number; + isExpanded: boolean; + onToggle: () => void; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const ToolOutputsSection = ({ + injections, + tokenCount, + isExpanded, + onToggle, + onNavigateToTurn, +}: Readonly): React.ReactElement | null => { + if (injections.length === 0) return null; + + return ( + + {injections.map((injection) => ( + + ))} + + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/components/UserMessagesSection.tsx b/src/renderer/components/chat/SessionContextPanel/components/UserMessagesSection.tsx new file mode 100644 index 00000000..76f4914a --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/components/UserMessagesSection.tsx @@ -0,0 +1,47 @@ +/** + * UserMessagesSection - Section for displaying user message injections. + */ + +import React from 'react'; + +import { UserMessageItem } from '../items/UserMessageItem'; + +import { CollapsibleSection } from './CollapsibleSection'; + +import type { UserMessageInjection } from '@renderer/types/contextInjection'; + +interface UserMessagesSectionProps { + injections: UserMessageInjection[]; + tokenCount: number; + isExpanded: boolean; + onToggle: () => void; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const UserMessagesSection = ({ + injections, + tokenCount, + isExpanded, + onToggle, + onNavigateToTurn, +}: Readonly): React.ReactElement | null => { + if (injections.length === 0) return null; + + return ( + + {injections.map((injection) => ( + + ))} + + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/index.tsx b/src/renderer/components/chat/SessionContextPanel/index.tsx new file mode 100644 index 00000000..c72387bf --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/index.tsx @@ -0,0 +1,250 @@ +/** + * SessionContextPanel - Panel showing all context injections for a session. + * Displays CLAUDE.md files, mentioned files, and tool outputs in collapsible sections. + */ + +import React, { useMemo, useState } from 'react'; + +import { COLOR_BORDER, COLOR_SURFACE, COLOR_TEXT_MUTED } from '@renderer/constants/cssVariables'; + +import { ClaudeMdFilesSection } from './components/ClaudeMdFilesSection'; +import { MentionedFilesSection } from './components/MentionedFilesSection'; +import { SessionContextHeader } from './components/SessionContextHeader'; +import { TaskCoordinationSection } from './components/TaskCoordinationSection'; +import { ThinkingTextSection } from './components/ThinkingTextSection'; +import { ToolOutputsSection } from './components/ToolOutputsSection'; +import { UserMessagesSection } from './components/UserMessagesSection'; +import { + SECTION_CLAUDE_MD, + SECTION_MENTIONED_FILES, + SECTION_TASK_COORDINATION, + SECTION_THINKING_TEXT, + SECTION_TOOL_OUTPUTS, + SECTION_USER_MESSAGES, +} from './types'; + +import type { SectionType, SessionContextPanelProps } from './types'; +import type { + ClaudeMdContextInjection, + MentionedFileInjection, + TaskCoordinationInjection, + ThinkingTextInjection, + ToolOutputInjection, + UserMessageInjection, +} from '@renderer/types/contextInjection'; + +export const SessionContextPanel = ({ + injections, + onClose, + projectRoot, + onNavigateToTurn, + totalSessionTokens, + phaseInfo, + selectedPhase, + onPhaseChange, +}: Readonly): React.ReactElement => { + // Track which main sections are expanded + const [expandedSections, setExpandedSections] = useState>( + new Set([ + SECTION_USER_MESSAGES, + SECTION_CLAUDE_MD, + SECTION_MENTIONED_FILES, + SECTION_TOOL_OUTPUTS, + SECTION_TASK_COORDINATION, + SECTION_THINKING_TEXT, + ]) + ); + + // Separate injections by category + const { + claudeMdInjections, + mentionedFileInjections, + toolOutputInjections, + thinkingTextInjections, + taskCoordinationInjections, + userMessageInjections, + } = useMemo(() => { + const claudeMd: ClaudeMdContextInjection[] = []; + const mentionedFiles: MentionedFileInjection[] = []; + const toolOutputs: ToolOutputInjection[] = []; + const thinkingText: ThinkingTextInjection[] = []; + const taskCoordination: TaskCoordinationInjection[] = []; + const userMessages: UserMessageInjection[] = []; + + for (const injection of injections) { + switch (injection.category) { + case 'claude-md': + claudeMd.push(injection); + break; + case 'mentioned-file': + mentionedFiles.push(injection); + break; + case 'tool-output': + toolOutputs.push(injection); + break; + case 'thinking-text': + thinkingText.push(injection); + break; + case 'task-coordination': + taskCoordination.push(injection); + break; + case 'user-message': + userMessages.push(injection); + break; + } + } + + // Sort mentioned files and tool outputs by tokens descending + mentionedFiles.sort((a, b) => b.estimatedTokens - a.estimatedTokens); + toolOutputs.sort((a, b) => b.estimatedTokens - a.estimatedTokens); + // Sort task coordination by tokens descending + taskCoordination.sort((a, b) => b.estimatedTokens - a.estimatedTokens); + // Sort thinking-text by turn index ascending + thinkingText.sort((a, b) => a.turnIndex - b.turnIndex); + // Sort user messages by turn index ascending + userMessages.sort((a, b) => a.turnIndex - b.turnIndex); + + return { + claudeMdInjections: claudeMd, + mentionedFileInjections: mentionedFiles, + toolOutputInjections: toolOutputs, + thinkingTextInjections: thinkingText, + taskCoordinationInjections: taskCoordination, + userMessageInjections: userMessages, + }; + }, [injections]); + + // Calculate total tokens + const totalTokens = useMemo( + () => injections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [injections] + ); + + // Section token counts + const claudeMdTokens = useMemo( + () => claudeMdInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [claudeMdInjections] + ); + + const mentionedFilesTokens = useMemo( + () => mentionedFileInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [mentionedFileInjections] + ); + + const toolOutputsTokens = useMemo( + () => toolOutputInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [toolOutputInjections] + ); + + const thinkingTextTokens = useMemo( + () => thinkingTextInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [thinkingTextInjections] + ); + + const taskCoordinationTokens = useMemo( + () => taskCoordinationInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [taskCoordinationInjections] + ); + + const userMessagesTokens = useMemo( + () => userMessageInjections.reduce((sum, inj) => sum + inj.estimatedTokens, 0), + [userMessageInjections] + ); + + // Toggle section expansion + const toggleSection = (section: SectionType): void => { + setExpandedSections((prev) => { + const next = new Set(prev); + if (next.has(section)) { + next.delete(section); + } else { + next.add(section); + } + return next; + }); + }; + + return ( +
    + + + {/* Content */} +
    + {injections.length === 0 ? ( +
    + No context injections detected in this session +
    + ) : ( + <> + toggleSection(SECTION_USER_MESSAGES)} + onNavigateToTurn={onNavigateToTurn} + /> + + toggleSection(SECTION_CLAUDE_MD)} + projectRoot={projectRoot ?? ''} + onNavigateToTurn={onNavigateToTurn} + /> + + toggleSection(SECTION_MENTIONED_FILES)} + projectRoot={projectRoot} + onNavigateToTurn={onNavigateToTurn} + /> + + toggleSection(SECTION_TOOL_OUTPUTS)} + onNavigateToTurn={onNavigateToTurn} + /> + + toggleSection(SECTION_TASK_COORDINATION)} + onNavigateToTurn={onNavigateToTurn} + /> + + toggleSection(SECTION_THINKING_TEXT)} + onNavigateToTurn={onNavigateToTurn} + /> + + )} +
    +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/items/ClaudeMdItem.tsx b/src/renderer/components/chat/SessionContextPanel/items/ClaudeMdItem.tsx new file mode 100644 index 00000000..6b25072b --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/items/ClaudeMdItem.tsx @@ -0,0 +1,76 @@ +/** + * ClaudeMdItem - Single CLAUDE.md file item display. + */ + +import React from 'react'; + +import { CopyablePath } from '@renderer/components/common/CopyablePath'; +import { resolveAbsolutePath, shortenDisplayPath } from '@renderer/utils/pathDisplay'; + +import { formatTokens } from '../utils/formatting'; +import { formatFirstSeen, parseTurnIndex } from '../utils/pathParsing'; + +import type { ClaudeMdContextInjection } from '@renderer/types/contextInjection'; + +interface ClaudeMdItemProps { + injection: ClaudeMdContextInjection; + projectRoot?: string; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const ClaudeMdItem = ({ + injection, + projectRoot, + onNavigateToTurn, +}: Readonly): React.ReactElement => { + const turnIndex = parseTurnIndex(injection.firstSeenInGroup); + const isClickable = onNavigateToTurn && turnIndex >= 0; + const displayPath = shortenDisplayPath(injection.path, projectRoot); + const absolutePath = resolveAbsolutePath(injection.path, projectRoot); + + return ( +
    + +
    + + ~{formatTokens(injection.estimatedTokens)} tokens + + {isClickable ? ( + + ) : ( + + @{formatFirstSeen(injection.firstSeenInGroup)} + + )} +
    +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/items/MentionedFileItem.tsx b/src/renderer/components/chat/SessionContextPanel/items/MentionedFileItem.tsx new file mode 100644 index 00000000..8f7449a2 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/items/MentionedFileItem.tsx @@ -0,0 +1,91 @@ +/** + * MentionedFileItem - Single mentioned file item display. + */ + +import React from 'react'; + +import { CopyablePath } from '@renderer/components/common/CopyablePath'; +import { resolveAbsolutePath, shortenDisplayPath } from '@renderer/utils/pathDisplay'; +import { File } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; + +import type { MentionedFileInjection } from '@renderer/types/contextInjection'; + +interface MentionedFileItemProps { + injection: MentionedFileInjection; + projectRoot?: string; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const MentionedFileItem = ({ + injection, + projectRoot, + onNavigateToTurn, +}: Readonly): React.ReactElement => { + const turnIndex = injection.firstSeenTurnIndex; + const isClickable = onNavigateToTurn && turnIndex >= 0; + const displayPath = shortenDisplayPath(injection.path, projectRoot); + const absolutePath = resolveAbsolutePath(injection.path, projectRoot); + + return ( +
    +
    + + + {!injection.exists && ( + + missing + + )} +
    +
    + + ~{formatTokens(injection.estimatedTokens)} tokens + + {isClickable ? ( + onNavigateToTurn(turnIndex)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + onNavigateToTurn(turnIndex); + } + }} + > + @Turn {turnIndex + 1} + + ) : ( + + @Turn {turnIndex + 1} + + )} +
    +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/items/TaskCoordinationItem.tsx b/src/renderer/components/chat/SessionContextPanel/items/TaskCoordinationItem.tsx new file mode 100644 index 00000000..62832646 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/items/TaskCoordinationItem.tsx @@ -0,0 +1,116 @@ +/** + * TaskCoordinationItem - Single task coordination injection with expandable breakdown. + */ + +import React, { useState } from 'react'; + +import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; +import { ChevronRight, Users } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; + +import type { TaskCoordinationInjection } from '@renderer/types/contextInjection'; + +interface TaskCoordinationItemProps { + injection: TaskCoordinationInjection; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const TaskCoordinationItem = ({ + injection, + onNavigateToTurn, +}: Readonly): React.ReactElement => { + const [expanded, setExpanded] = useState(false); + const turnIndex = injection.turnIndex; + const isClickable = onNavigateToTurn && turnIndex >= 0; + const hasBreakdown = injection.breakdown.length > 0; + + const containerContent = ( + <> + {hasBreakdown && ( + + )} + + {isClickable ? ( + { + e.stopPropagation(); + onNavigateToTurn(turnIndex); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation(); + onNavigateToTurn(turnIndex); + } + }} + > + @Turn {turnIndex + 1} + + ) : ( + + @Turn {turnIndex + 1} + + )} + + ~{formatTokens(injection.estimatedTokens)} tokens + + + {injection.breakdown.length} item{injection.breakdown.length !== 1 ? 's' : ''} + + + ); + + return ( +
    + {hasBreakdown ? ( + + ) : ( +
    {containerContent}
    + )} + + {expanded && hasBreakdown && ( +
    + {injection.breakdown.map((item, idx) => ( +
    + {item.label} + + ~{formatTokens(item.tokenCount)} + +
    + ))} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/items/ThinkingTextItem.tsx b/src/renderer/components/chat/SessionContextPanel/items/ThinkingTextItem.tsx new file mode 100644 index 00000000..92be27b0 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/items/ThinkingTextItem.tsx @@ -0,0 +1,96 @@ +/** + * ThinkingTextItem - Single thinking text item with expandable breakdown. + */ + +import React, { useState } from 'react'; + +import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; +import { Brain, ChevronRight } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; + +import type { ThinkingTextInjection } from '@renderer/types/contextInjection'; + +interface ThinkingTextItemProps { + injection: ThinkingTextInjection; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const ThinkingTextItem = ({ + injection, + onNavigateToTurn, +}: Readonly): React.ReactElement => { + const [expanded, setExpanded] = useState(false); + const turnIndex = injection.turnIndex; + const isClickable = onNavigateToTurn && turnIndex >= 0; + + return ( +
    + + + {expanded && injection.breakdown.length > 0 && ( +
    + {injection.breakdown.map((item, idx) => ( +
    + + {item.type === 'thinking' ? 'Thinking' : 'Text'} + + + ~{formatTokens(item.tokenCount)} + +
    + ))} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/items/ToolBreakdownItem.tsx b/src/renderer/components/chat/SessionContextPanel/items/ToolBreakdownItem.tsx new file mode 100644 index 00000000..dec23d2c --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/items/ToolBreakdownItem.tsx @@ -0,0 +1,38 @@ +/** + * ToolBreakdownItem - Single tool breakdown item display. + */ + +import React from 'react'; + +import { formatTokens } from '../utils/formatting'; + +import type { ToolTokenBreakdown } from '@renderer/types/contextInjection'; + +interface ToolBreakdownItemProps { + tool: ToolTokenBreakdown; +} + +export const ToolBreakdownItem = ({ + tool, +}: Readonly): React.ReactElement => { + return ( +
    + {tool.toolName} + + ~{formatTokens(tool.tokenCount)} + + {tool.isError && ( + + error + + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/items/ToolOutputItem.tsx b/src/renderer/components/chat/SessionContextPanel/items/ToolOutputItem.tsx new file mode 100644 index 00000000..fe46a522 --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/items/ToolOutputItem.tsx @@ -0,0 +1,113 @@ +/** + * ToolOutputItem - Single tool output item with expandable breakdown. + */ + +import React, { useState } from 'react'; + +import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; +import { ChevronRight, Wrench } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; + +import { ToolBreakdownItem } from './ToolBreakdownItem'; + +import type { ToolOutputInjection } from '@renderer/types/contextInjection'; + +interface ToolOutputItemProps { + injection: ToolOutputInjection; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const ToolOutputItem = ({ + injection, + onNavigateToTurn, +}: Readonly): React.ReactElement => { + const [expanded, setExpanded] = useState(false); + const turnIndex = injection.turnIndex; + const isClickable = onNavigateToTurn && turnIndex >= 0; + const hasBreakdown = injection.toolBreakdown.length > 0; + + const containerContent = ( + <> + {hasBreakdown && ( + + )} + + {isClickable ? ( + { + e.stopPropagation(); + onNavigateToTurn(turnIndex); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation(); + onNavigateToTurn(turnIndex); + } + }} + > + @Turn {turnIndex + 1} + + ) : ( + + @Turn {turnIndex + 1} + + )} + + ~{formatTokens(injection.estimatedTokens)} tokens + + + {injection.toolCount} tool{injection.toolCount !== 1 ? 's' : ''} + + + ); + + return ( +
    + {hasBreakdown ? ( + + ) : ( +
    {containerContent}
    + )} + + {expanded && hasBreakdown && ( +
    + {injection.toolBreakdown.map((tool, idx) => ( + + ))} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/items/UserMessageItem.tsx b/src/renderer/components/chat/SessionContextPanel/items/UserMessageItem.tsx new file mode 100644 index 00000000..ce3219fa --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/items/UserMessageItem.tsx @@ -0,0 +1,69 @@ +/** + * UserMessageItem - Single user message item showing turn link, tokens, and preview. + */ + +import React from 'react'; + +import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; +import { MessageSquare } from 'lucide-react'; + +import { formatTokens } from '../utils/formatting'; + +import type { UserMessageInjection } from '@renderer/types/contextInjection'; + +interface UserMessageItemProps { + injection: UserMessageInjection; + onNavigateToTurn?: (turnIndex: number) => void; +} + +export const UserMessageItem = ({ + injection, + onNavigateToTurn, +}: Readonly): React.ReactElement => { + const turnIndex = injection.turnIndex; + const isClickable = onNavigateToTurn && turnIndex >= 0; + + return ( +
    +
    + + {isClickable ? ( + onNavigateToTurn(turnIndex)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + onNavigateToTurn(turnIndex); + } + }} + > + @Turn {turnIndex + 1} + + ) : ( + + @Turn {turnIndex + 1} + + )} + + ~{formatTokens(injection.estimatedTokens)} tokens + +
    + {injection.textPreview && ( +
    + {injection.textPreview} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/SessionContextPanel/types.ts b/src/renderer/components/chat/SessionContextPanel/types.ts new file mode 100644 index 00000000..ef1aeb4e --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/types.ts @@ -0,0 +1,79 @@ +/** + * Type definitions for SessionContextPanel components. + */ + +import type { ClaudeMdSource } from '@renderer/types/claudeMd'; +import type { ContextInjection, ContextPhaseInfo } from '@renderer/types/contextInjection'; + +// ============================================================================= +// Props Interface +// ============================================================================= + +export interface SessionContextPanelProps { + /** All accumulated context injections */ + injections: ContextInjection[]; + /** Close button handler */ + onClose?: () => void; + /** Project root for relative path display */ + projectRoot?: string; + /** Click Turn N to navigate to that turn */ + onNavigateToTurn?: (turnIndex: number) => void; + /** Total session tokens (input + output + cache) for comparison */ + totalSessionTokens?: number; + /** Phase information for phase selector */ + phaseInfo?: ContextPhaseInfo; + /** Currently selected phase (null = current/latest) */ + selectedPhase: number | null; + /** Callback to change selected phase */ + onPhaseChange: (phase: number | null) => void; +} + +// ============================================================================= +// Section Types +// ============================================================================= + +/** Section type constants */ +export const SECTION_CLAUDE_MD = 'claude-md' as const; +export const SECTION_MENTIONED_FILES = 'mentioned-files' as const; +export const SECTION_TOOL_OUTPUTS = 'tool-outputs' as const; +export const SECTION_THINKING_TEXT = 'thinking-text' as const; +export const SECTION_TASK_COORDINATION = 'task-coordination' as const; +export const SECTION_USER_MESSAGES = 'user-messages' as const; + +/** Section identifiers for collapsible panels */ +export type SectionType = + | typeof SECTION_CLAUDE_MD + | typeof SECTION_MENTIONED_FILES + | typeof SECTION_TOOL_OUTPUTS + | typeof SECTION_THINKING_TEXT + | typeof SECTION_TASK_COORDINATION + | typeof SECTION_USER_MESSAGES; + +// ============================================================================= +// CLAUDE.md Group Types +// ============================================================================= + +/** Group category for CLAUDE.md files */ +export type ClaudeMdGroupCategory = 'global' | 'project' | 'directory'; + +interface ClaudeMdGroupConfig { + label: string; + sources: ClaudeMdSource[]; +} + +export const CLAUDE_MD_GROUP_CONFIG: Record = { + global: { + label: 'Global', + sources: ['enterprise', 'user-memory', 'user-rules', 'auto-memory'], + }, + project: { + label: 'Project', + sources: ['project-memory', 'project-rules', 'project-local'], + }, + directory: { + label: 'Directory', + sources: ['directory'], + }, +}; + +export const CLAUDE_MD_GROUP_ORDER: ClaudeMdGroupCategory[] = ['global', 'project', 'directory']; diff --git a/src/renderer/components/chat/SessionContextPanel/utils/formatting.ts b/src/renderer/components/chat/SessionContextPanel/utils/formatting.ts new file mode 100644 index 00000000..364b7bab --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/utils/formatting.ts @@ -0,0 +1,6 @@ +/** + * Formatting utilities for SessionContextPanel. + */ + +// Re-export from shared module for backwards compatibility +export { formatTokensCompact as formatTokens } from '@shared/utils/tokenFormatting'; diff --git a/src/renderer/components/chat/SessionContextPanel/utils/pathParsing.ts b/src/renderer/components/chat/SessionContextPanel/utils/pathParsing.ts new file mode 100644 index 00000000..a31426fa --- /dev/null +++ b/src/renderer/components/chat/SessionContextPanel/utils/pathParsing.ts @@ -0,0 +1,23 @@ +/** + * Path parsing utilities for SessionContextPanel. + */ + +/** + * Format the firstSeenInGroup value into a human-readable string. + * Converts "ai-0" -> "Turn 1", "ai-1" -> "Turn 2", etc. + */ +export function formatFirstSeen(groupId: string): string { + const turnIndex = parseTurnIndex(groupId); + if (turnIndex < 0) return groupId; + return `Turn ${turnIndex + 1}`; +} + +/** + * Extract turn index from groupId. Returns -1 if invalid. + * "ai-0" -> 0, "ai-1" -> 1, etc. + */ +export function parseTurnIndex(groupId: string): number { + const match = /^ai-(\d+)$/.exec(groupId); + if (!match) return -1; + return parseInt(match[1], 10); +} diff --git a/src/renderer/components/chat/SystemChatGroup.tsx b/src/renderer/components/chat/SystemChatGroup.tsx new file mode 100644 index 00000000..92f03e01 --- /dev/null +++ b/src/renderer/components/chat/SystemChatGroup.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +import { format } from 'date-fns'; +import { Terminal } from 'lucide-react'; + +import type { SystemGroup } from '@renderer/types/groups'; + +// Module-level constant - safe because .replace() resets lastIndex on g-flagged regexes +const ANSI_ESCAPE_REGEX = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g'); + +interface SystemChatGroupProps { + systemGroup: SystemGroup; +} + +/** + * SystemChatGroup displays command output (e.g., /model response). + * Renders on LEFT side like AI, but with neutral/gray styling. + */ +const SystemChatGroupInner = ({ + systemGroup, +}: Readonly): React.JSX.Element => { + const { commandOutput, timestamp } = systemGroup; + + // Clean ANSI escape codes from output + const cleanOutput = commandOutput.replace(ANSI_ESCAPE_REGEX, ''); + + return ( +
    +
    + {/* Header - system icon */} +
    + + + System + + · + {format(timestamp, 'h:mm:ss a')} +
    + + {/* Content - theme-aware neutral styling */} +
    +
    +            {cleanOutput}
    +          
    +
    +
    +
    + ); +}; + +export const SystemChatGroup = React.memo(SystemChatGroupInner); diff --git a/src/renderer/components/chat/UserChatGroup.tsx b/src/renderer/components/chat/UserChatGroup.tsx new file mode 100644 index 00000000..7b1a296e --- /dev/null +++ b/src/renderer/components/chat/UserChatGroup.tsx @@ -0,0 +1,471 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import ReactMarkdown, { type Components } from 'react-markdown'; + +import { useTabUI } from '@renderer/hooks/useTabUI'; +import { useStore } from '@renderer/store'; +import { createLogger } from '@shared/utils/logger'; +import { format } from 'date-fns'; +import { User } from 'lucide-react'; +import remarkGfm from 'remark-gfm'; +import { useShallow } from 'zustand/react/shallow'; + +import { CopyButton } from '../common/CopyButton'; + +import { + createSearchContext, + highlightSearchInChildren, + type SearchContext, +} from './searchHighlightUtils'; + +import type { UserGroup } from '@renderer/types/groups'; + +const logger = createLogger('Component:UserChatGroup'); + +// Pattern for @paths only (file references) +const PATH_PATTERN = /@([^\s,)}\]]+)/g; + +interface UserChatGroupProps { + userGroup: UserGroup; +} + +/** + * Recursively walks React children and replaces text nodes containing @path + * references with styled spans using validated path state. + */ +// eslint-disable-next-line sonarjs/function-return-type -- React child manipulation inherently returns mixed node types +function highlightTextNode(text: string, validatedPaths: Record): React.ReactNode { + const pathPattern = /@[^\s,)}\]]+/g; + const parts: React.ReactNode[] = []; + let lastIndex = 0; + let match; + + pathPattern.lastIndex = 0; + while ((match = pathPattern.exec(text)) !== null) { + if (match.index > lastIndex) { + parts.push(text.slice(lastIndex, match.index)); + } + + const fullMatch = match[0]; + const isValid = validatedPaths[fullMatch] === true; + + if (isValid) { + parts.push( + + {fullMatch} + + ); + } else { + parts.push(fullMatch); + } + + lastIndex = match.index + fullMatch.length; + } + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + if (parts.length === 0) return text; + if (parts.length === 1) return parts[0]; + return parts; +} + +// eslint-disable-next-line sonarjs/function-return-type -- React child manipulation inherently returns mixed node types +function highlightPaths( + children: React.ReactNode, + validatedPaths: Record +): React.ReactNode { + // eslint-disable-next-line sonarjs/function-return-type -- React child manipulation inherently returns mixed node types + return React.Children.map(children, (child): React.ReactNode => { + if (typeof child === 'string') { + return highlightTextNode(child, validatedPaths); + } + + if (React.isValidElement<{ children?: React.ReactNode }>(child) && child.props.children) { + return React.cloneElement( + child, + undefined, + highlightPaths(child.props.children, validatedPaths) + ); + } + + return child; + }); +} + +/** + * Creates markdown components for user bubble rendering. + * Uses chat-user CSS variables for consistent styling and wraps + * text-bearing elements through highlightPaths for @path tag injection + * and optional search term highlighting. + */ +function createUserMarkdownComponents( + validatedPaths: Record, + searchCtx: SearchContext | null +): Components { + const userTextColor = 'var(--chat-user-text)'; + + // Compose path highlighting with optional search highlighting + // eslint-disable-next-line sonarjs/function-return-type -- React child manipulation inherently returns mixed node types + const hl = (children: React.ReactNode): React.ReactNode => { + const withPaths = highlightPaths(children, validatedPaths); + return searchCtx ? highlightSearchInChildren(withPaths, searchCtx) : withPaths; + }; + + return { + h1: ({ children }) => ( +

    + {hl(children)} +

    + ), + h2: ({ children }) => ( +

    + {hl(children)} +

    + ), + h3: ({ children }) => ( +

    + {hl(children)} +

    + ), + h4: ({ children }) => ( +

    + {hl(children)} +

    + ), + h5: ({ children }) => ( +
    + {hl(children)} +
    + ), + h6: ({ children }) => ( +
    + {hl(children)} +
    + ), + + p: ({ children }) => ( +

    + {hl(children)} +

    + ), + + // Inline elements — no hl(); parent block element's hl() descends here + a: ({ href, children }) => ( +
    + {children} + + ), + + strong: ({ children }) => ( + + {children} + + ), + + em: ({ children }) => ( + + {children} + + ), + + del: ({ children }) => ( + + {children} + + ), + + code: ({ className, children }) => { + const hasLanguageClass = className?.includes('language-'); + const content = typeof children === 'string' ? children : ''; + const isMultiLine = content.includes('\n'); + const isBlock = (hasLanguageClass ?? false) || isMultiLine; + + if (isBlock) { + return ( + + {hl(children)} + + ); + } + // Inline code — no hl() + return ( + + {children} + + ); + }, + + pre: ({ children }) => ( +
    +        {children}
    +      
    + ), + + blockquote: ({ children }) => ( +
    + {hl(children)} +
    + ), + + ul: ({ children }) => ( +
      + {children} +
    + ), + ol: ({ children }) => ( +
      + {children} +
    + ), + li: ({ children }) => ( +
  1. + {hl(children)} +
  2. + ), + + table: ({ children }) => ( +
    + + {children} +
    +
    + ), + thead: ({ children }) => ( + {children} + ), + th: ({ children }) => ( + + {hl(children)} + + ), + td: ({ children }) => ( + + {hl(children)} + + ), + + hr: () =>
    , + }; +} + +/** + * UserChatGroup displays a user's input message. + * Features: + * - Right-aligned bubble layout with subtle blue styling + * - Header with user icon, label, and timestamp + * - Markdown rendering with inline highlighted mentions (@paths) + * - Copy button on hover + * - Toggle for long content (>500 chars) + * - Shows image count indicator + */ +const UserChatGroupInner = ({ userGroup }: Readonly): React.JSX.Element => { + const { content, timestamp, id: groupId } = userGroup; + const [isManuallyExpanded, setIsManuallyExpanded] = useState(false); + const [validatedPaths, setValidatedPaths] = useState>({}); + + // Get projectPath from per-tab session data, falling back to global state + const { tabId } = useTabUI(); + const projectPath = useStore((s) => { + const td = tabId ? s.tabSessionData[tabId] : null; + return (td?.sessionDetail ?? s.sessionDetail)?.session?.projectPath; + }); + + // Get search state for highlighting + const { searchQuery, searchMatches, currentSearchIndex } = useStore( + useShallow((s) => ({ + searchQuery: s.searchQuery, + searchMatches: s.searchMatches, + currentSearchIndex: s.currentSearchIndex, + })) + ); + + const hasImages = content.images.length > 0; + // Use rawText to preserve /commands inline + const textContent = content.rawText ?? content.text ?? ''; + const isLongContent = textContent.length > 500; + + // Extract @path mentions from text + const pathMentions = useMemo(() => { + if (!textContent) return []; + const result: { value: string; raw: string }[] = []; + const pathPattern = new RegExp(PATH_PATTERN.source, PATH_PATTERN.flags); + let match; + while ((match = pathPattern.exec(textContent)) !== null) { + result.push({ value: match[1], raw: match[0] }); + } + return result; + }, [textContent]); + + // Validate @path mentions via IPC + useEffect(() => { + if (pathMentions.length === 0 || !projectPath) return; + let isCurrent = true; + + const validatePaths = async (): Promise => { + try { + const toValidate = pathMentions.map((m) => ({ type: 'path' as const, value: m.value })); + const results = await window.electronAPI.validateMentions(toValidate, projectPath); + if (isCurrent) { + setValidatedPaths(results); + } + } catch (err) { + logger.error('Path validation failed:', err); + if (isCurrent) { + setValidatedPaths({}); + } + } + }; + + void validatePaths(); + return () => { + isCurrent = false; + }; + }, [textContent, projectPath, pathMentions]); + + const effectiveValidatedPaths = useMemo( + () => (pathMentions.length === 0 || !projectPath ? {} : validatedPaths), + [pathMentions.length, projectPath, validatedPaths] + ); + + // Create search context (fresh each render so counter starts at 0) + const searchCtx = searchQuery + ? createSearchContext(searchQuery, groupId, searchMatches, currentSearchIndex) + : null; + + // Base markdown components (no search) — safe to memoize + const userMarkdownComponentsBase = useMemo( + () => createUserMarkdownComponents(effectiveValidatedPaths, null), + [effectiveValidatedPaths] + ); + // When search is active, create fresh each render (match counter is stateful and must start at 0) + // useMemo would cache stale closures when parent re-renders without search deps changing + const userMarkdownComponents = searchCtx + ? createUserMarkdownComponents(effectiveValidatedPaths, searchCtx) + : userMarkdownComponentsBase; + + // Auto-expand when search is active and this message has ANY matches. + // Without this, the pre-counter searches full text but the renderer only + // shows the first 500 chars — creating phantom matches. + const shouldAutoExpand = useMemo(() => { + if (!searchQuery || !isLongContent) return false; + return searchMatches.some((m) => m.itemId === groupId); + }, [searchQuery, isLongContent, searchMatches, groupId]); + + // Combined expansion state: manual toggle or auto-expand for search + const isExpanded = isManuallyExpanded || shouldAutoExpand; + + // Determine display text + const displayText = + isLongContent && !isExpanded ? textContent.slice(0, 500) + '...' : textContent; + + return ( +
    +
    + {/* Header - right aligned with improved hierarchy */} +
    + + {format(timestamp, 'h:mm:ss a')} + + + You + + +
    + + {/* Content - polished bubble with subtle depth */} + {textContent && ( +
    + + +
    + + {displayText} + +
    + {isLongContent && ( + + )} +
    + )} + + {/* Images indicator */} + {hasImages && ( +
    + {content.images.length} image{content.images.length > 1 ? 's' : ''} attached +
    + )} +
    +
    + ); +}; + +export const UserChatGroup = React.memo(UserChatGroupInner); diff --git a/src/renderer/components/chat/items/BaseItem.tsx b/src/renderer/components/chat/items/BaseItem.tsx new file mode 100644 index 00000000..3f160ce4 --- /dev/null +++ b/src/renderer/components/chat/items/BaseItem.tsx @@ -0,0 +1,192 @@ +import React from 'react'; + +import { TOOL_ITEM_MUTED } from '@renderer/constants/cssVariables'; +import { getTriggerColorDef, type TriggerColor } from '@shared/constants/triggerColors'; +import { ChevronRight } from 'lucide-react'; + +import { formatDuration, formatTokens, getStatusDotColor } from './baseItemHelpers'; + +// ============================================================================= +// Types +// ============================================================================= + +export type ItemStatus = 'ok' | 'error' | 'pending' | 'orphaned'; + +interface BaseItemProps { + /** Icon component to display */ + icon: React.ReactNode; + /** Primary label (e.g., "Thinking", "Output", tool name) */ + label: string; + /** Summary text shown after the label */ + summary?: string; + /** Token count to display */ + tokenCount?: number; + /** Label for tokens (default: "tokens") */ + tokenLabel?: string; + /** Status indicator (green/red/gray dot) */ + status?: ItemStatus; + /** Duration in milliseconds */ + durationMs?: number; + /** Click handler for toggling */ + onClick: () => void; + /** Whether the item is expanded */ + isExpanded: boolean; + /** Whether the item has expandable content */ + hasExpandableContent?: boolean; + /** Additional classes for highlighting (e.g., error deep linking) */ + highlightClasses?: string; + /** Inline styles for highlighting (used by custom hex colors) */ + highlightStyle?: React.CSSProperties; + /** Notification dot color for custom triggers */ + notificationDotColor?: TriggerColor; + /** Children rendered when expanded */ + children?: React.ReactNode; +} + +// ============================================================================= +// Helper Components +// ============================================================================= + +/** + * Small status dot indicator. + */ +export const StatusDot: React.FC<{ status: ItemStatus }> = ({ status }) => { + return ( + + ); +}; + +// ============================================================================= +// Main Component +// ============================================================================= + +/** + * BaseItem provides a consistent layout for all expandable items in the chat view. + * + * Layout: + * - Clickable header row with icon, label, summary, tokens, status, and chevron + * - Expanded content area with left border indent + * + * Used by: ThinkingItem, TextItem, LinkedToolItem, SlashItem, SubagentItem + */ +export const BaseItem: React.FC = ({ + icon, + label, + summary, + tokenCount, + tokenLabel = 'tokens', + status, + durationMs, + onClick, + isExpanded, + hasExpandableContent = true, + highlightClasses = '', + highlightStyle, + notificationDotColor, + children, +}) => { + return ( +
    + {/* Clickable Header */} +
    { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }} + className="group flex cursor-pointer items-center gap-2 rounded px-2 py-1.5" + style={{ backgroundColor: 'transparent' }} + onMouseEnter={(e) => + Object.assign(e.currentTarget.style, { backgroundColor: 'var(--tool-item-hover-bg)' }) + } + onMouseLeave={(e) => + Object.assign(e.currentTarget.style, { backgroundColor: 'transparent' }) + } + > + {/* Icon */} + + {icon} + + + {/* Label */} + + {label} + + + {/* Separator and Summary */} + {summary && ( + <> + + - + + + {summary} + + + )} + + {/* Spacer if no summary */} + {!summary && } + + {/* Token count badge */} + {tokenCount != null && tokenCount > 0 && ( + + ~{formatTokens(tokenCount)} {tokenLabel} + + )} + + {/* Status indicator - hidden when notification dot replaces it */} + {status && !notificationDotColor && } + + {/* Notification dot (replaces status dot when present) */} + {notificationDotColor && ( + + )} + + {/* Duration */} + {durationMs !== undefined && ( + + {formatDuration(durationMs)} + + )} + + {/* Expand/collapse chevron */} + {hasExpandableContent && ( + + )} +
    + + {/* Expanded Content */} + {isExpanded && children && ( +
    + {children} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/items/ExecutionTrace.tsx b/src/renderer/components/chat/items/ExecutionTrace.tsx new file mode 100644 index 00000000..d6010405 --- /dev/null +++ b/src/renderer/components/chat/items/ExecutionTrace.tsx @@ -0,0 +1,151 @@ +import React, { useState } from 'react'; + +import { CARD_ICON_MUTED } from '@renderer/constants/cssVariables'; +import { truncateText } from '@renderer/utils/aiGroupEnhancer'; + +import { LinkedToolItem } from './LinkedToolItem'; +import { TextItem } from './TextItem'; +import { ThinkingItem } from './ThinkingItem'; + +import type { AIGroupDisplayItem } from '@renderer/types/groups'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +// ============================================================================= +// Types +// ============================================================================= + +interface ExecutionTraceProps { + items: AIGroupDisplayItem[]; + aiGroupId: string; + highlightToolUseId?: string; + /** Custom highlight color from trigger */ + highlightColor?: TriggerColor; + /** Map of tool use ID to trigger color for notification dots */ + notificationColorMap?: Map; + searchExpandedItemId?: string | null; + /** Optional callback to register tool element refs for scroll targeting */ + registerToolRef?: (toolId: string, el: HTMLDivElement | null) => void; +} + +// ============================================================================= +// Execution Trace Component +// ============================================================================= + +export const ExecutionTrace: React.FC = ({ + items, + aiGroupId: _aiGroupId, + highlightToolUseId, + highlightColor, + notificationColorMap, + searchExpandedItemId, + registerToolRef, +}): React.JSX.Element => { + const [manualExpandedItemId, setManualExpandedItemId] = useState(null); + + // Use searchExpandedItemId if set, otherwise use manually expanded item + const expandedItemId = searchExpandedItemId ?? manualExpandedItemId; + + const handleItemClick = (itemId: string): void => { + setManualExpandedItemId((prev) => (prev === itemId ? null : itemId)); + }; + + if (!items || items.length === 0) { + return ( +
    + No execution items +
    + ); + } + + return ( +
    + {items.map((item, index) => { + switch (item.type) { + case 'thinking': { + const itemId = `subagent-thinking-${index}`; + const thinkingStep = { + id: itemId, + type: 'thinking' as const, + startTime: item.timestamp, + endTime: item.timestamp, + durationMs: 0, + content: { thinkingText: item.content, tokenCount: item.tokenCount }, + tokens: { input: 0, output: item.tokenCount ?? 0 }, + context: 'subagent' as const, + }; + const preview = truncateText(item.content, 150); + const isExpanded = expandedItemId === itemId; + return ( + handleItemClick(itemId)} + isExpanded={isExpanded} + /> + ); + } + + case 'output': { + const itemId = `subagent-output-${index}`; + const textStep = { + id: itemId, + type: 'output' as const, + startTime: item.timestamp, + endTime: item.timestamp, + durationMs: 0, + content: { outputText: item.content, tokenCount: item.tokenCount }, + tokens: { input: 0, output: item.tokenCount ?? 0 }, + context: 'subagent' as const, + }; + const preview = truncateText(item.content, 150); + const isExpanded = expandedItemId === itemId; + return ( + handleItemClick(itemId)} + isExpanded={isExpanded} + /> + ); + } + + case 'tool': { + const itemId = `subagent-tool-${item.tool.id}`; + const isExpanded = expandedItemId === itemId; + const isHighlighted = highlightToolUseId === item.tool.id; + return ( + handleItemClick(itemId)} + isExpanded={isExpanded} + isHighlighted={isHighlighted} + highlightColor={highlightColor} + notificationDotColor={notificationColorMap?.get(item.tool.id)} + registerRef={ + registerToolRef ? (el) => registerToolRef(item.tool.id, el) : undefined + } + /> + ); + } + + case 'subagent': + return ( +
    + Nested: {item.subagent.description ?? item.subagent.id} +
    + ); + + default: + return null; + } + })} +
    + ); +}; diff --git a/src/renderer/components/chat/items/LinkedToolItem.tsx b/src/renderer/components/chat/items/LinkedToolItem.tsx new file mode 100644 index 00000000..c11798d1 --- /dev/null +++ b/src/renderer/components/chat/items/LinkedToolItem.tsx @@ -0,0 +1,207 @@ +/** + * LinkedToolItem + * + * Main component for rendering linked tool calls in the chat view. + * Uses specialized viewers for different tool types and shared utilities + * for summary generation and token calculation. + */ + +import React, { useRef } from 'react'; + +import { CARD_ICON_MUTED } from '@renderer/constants/cssVariables'; +import { getTeamColorSet } from '@renderer/constants/teamColors'; +import { + getToolContextTokens, + getToolStatus, + getToolSummary, + hasEditContent, + hasReadContent, + hasSkillInstructions, + hasWriteContent, +} from '@renderer/utils/toolRendering'; +import { + getToolHighlightProps, + getTriggerColorDef, + isPresetColorKey, + TOOL_HIGHLIGHT_CLASSES, + type TriggerColor, +} from '@shared/constants/triggerColors'; +import { Wrench } from 'lucide-react'; + +import { BaseItem, StatusDot } from './BaseItem'; +import { formatDuration } from './baseItemHelpers'; +import { + DefaultToolViewer, + EditToolViewer, + ReadToolViewer, + SkillToolViewer, + ToolErrorDisplay, + WriteToolViewer, +} from './linkedTool'; + +import type { LinkedToolItem as LinkedToolItemType } from '@renderer/types/groups'; + +interface LinkedToolItemProps { + linkedTool: LinkedToolItemType; + onClick: () => void; + isExpanded: boolean; + /** Whether this item should be highlighted for error deep linking */ + isHighlighted?: boolean; + /** Custom highlight color from trigger */ + highlightColor?: TriggerColor; + /** Notification dot color for this tool item */ + notificationDotColor?: TriggerColor; + /** Optional ref registration callback for external scroll control */ + registerRef?: (el: HTMLDivElement | null) => void; +} + +export const LinkedToolItem: React.FC = ({ + linkedTool, + onClick, + isExpanded, + isHighlighted, + highlightColor, + notificationDotColor, + registerRef, +}) => { + const status = getToolStatus(linkedTool); + const summary = getToolSummary(linkedTool.name, linkedTool.input); + const elementRef = useRef(null); + + // Combined ref callback - handles both internal ref and external registration + const handleRef = (el: HTMLDivElement | null): void => { + // Update internal ref + (elementRef as React.MutableRefObject).current = el; + // Call external registration if provided + registerRef?.(el); + }; + + // Render teammate_spawned results as a minimal inline row + const isTeammateSpawned = linkedTool.result?.toolUseResult?.status === 'teammate_spawned'; + if (isTeammateSpawned) { + const teamResult = linkedTool.result!.toolUseResult!; + const name = (teamResult.name as string) || 'teammate'; + const color = (teamResult.color as string) || ''; + const colors = getTeamColorSet(color); + return ( +
    + + + {name} + + + Teammate spawned + +
    + ); + } + + // Render SendMessage shutdown_request as a minimal inline row + const isShutdownRequest = + linkedTool.name === 'SendMessage' && linkedTool.input?.type === 'shutdown_request'; + if (isShutdownRequest) { + const target = (linkedTool.input?.recipient as string) || 'teammate'; + return ( +
    + + + Shutdown requested →{' '} + {target} + +
    + ); + } + + // Note: We no longer scroll locally - the navigation coordinator handles this + // via the registered ref. This prevents double-scroll issues. + + // Highlight animation for error deep linking (supports custom hex) + const effectiveColor = highlightColor ?? 'red'; + let highlightClasses = ''; + let highlightStyle: React.CSSProperties | undefined; + if (isHighlighted) { + if (isPresetColorKey(effectiveColor)) { + highlightClasses = TOOL_HIGHLIGHT_CLASSES[effectiveColor]; + } else { + const hp = getToolHighlightProps(effectiveColor); + highlightClasses = hp.className; + highlightStyle = hp.style; + } + } + + // Determine which specialized viewer to use + const useReadViewer = + linkedTool.name === 'Read' && hasReadContent(linkedTool) && !linkedTool.result?.isError; + const useEditViewer = linkedTool.name === 'Edit' && hasEditContent(linkedTool); + const useWriteViewer = + linkedTool.name === 'Write' && hasWriteContent(linkedTool) && !linkedTool.result?.isError; + const useSkillViewer = linkedTool.name === 'Skill' && hasSkillInstructions(linkedTool); + const useDefaultViewer = !useReadViewer && !useEditViewer && !useWriteViewer && !useSkillViewer; + + // Check if we should show error display for Read/Write tools + const showReadError = linkedTool.name === 'Read' && linkedTool.result?.isError; + const showWriteError = linkedTool.name === 'Write' && linkedTool.result?.isError; + + return ( +
    + + } + label={linkedTool.name} + summary={summary} + tokenCount={getToolContextTokens(linkedTool)} + status={status} + durationMs={linkedTool.durationMs} + onClick={onClick} + isExpanded={isExpanded} + highlightClasses={highlightClasses} + highlightStyle={highlightStyle} + notificationDotColor={notificationDotColor} + > + {/* Read tool with CodeBlockViewer */} + {useReadViewer && } + + {/* Edit tool with DiffViewer */} + {useEditViewer && } + + {/* Write tool */} + {useWriteViewer && } + + {/* Skill tool with instructions */} + {useSkillViewer && } + + {/* Default rendering for other tools */} + {useDefaultViewer && } + + {/* Error output for Read tool */} + {showReadError && } + + {/* Error output for Write tool */} + {showWriteError && } + + {/* Orphaned indicator */} + {linkedTool.isOrphaned && ( +
    + + No result received +
    + )} + + {/* Timing */} +
    + Duration: {formatDuration(linkedTool.durationMs)} +
    +
    +
    + ); +}; diff --git a/src/renderer/components/chat/items/MetricsPill.tsx b/src/renderer/components/chat/items/MetricsPill.tsx new file mode 100644 index 00000000..73213a29 --- /dev/null +++ b/src/renderer/components/chat/items/MetricsPill.tsx @@ -0,0 +1,179 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { + CARD_ICON_MUTED, + CARD_SEPARATOR, + CARD_TEXT_LIGHT, + COLOR_TEXT_MUTED, + TAG_BG, + TAG_BORDER, + TAG_TEXT, +} from '@renderer/constants/cssVariables'; +import { formatTokensCompact } from '@renderer/utils/formatters'; + +// ============================================================================= +// Types +// ============================================================================= + +interface MetricsPillProps { + mainSessionImpact?: { + callTokens: number; + resultTokens: number; + totalTokens: number; + }; + lastUsage?: { + input_tokens: number; + output_tokens: number; + cache_read_input_tokens?: number; + cache_creation_input_tokens?: number; + }; + /** Label override for the right segment (e.g. "Context Window" for team members) */ + isolatedLabel?: string; +} + +// ============================================================================= +// Unified Metrics Pill - Compact monospace pill with tooltip +// ============================================================================= + +export const MetricsPill = ({ + mainSessionImpact, + lastUsage, + isolatedLabel, +}: Readonly): React.ReactElement | null => { + const [showTooltip, setShowTooltip] = useState(false); + const [tooltipStyle, setTooltipStyle] = useState({}); + const containerRef = useRef(null); + const hideTimeoutRef = useRef | null>(null); + + const hasMainImpact = mainSessionImpact && mainSessionImpact.totalTokens > 0; + const hasIsolated = lastUsage && lastUsage.input_tokens + lastUsage.output_tokens > 0; + + const isolatedTotal = lastUsage + ? lastUsage.input_tokens + + lastUsage.output_tokens + + (lastUsage.cache_read_input_tokens ?? 0) + + (lastUsage.cache_creation_input_tokens ?? 0) + : 0; + + const clearHideTimeout = (): void => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + hideTimeoutRef.current = null; + } + }; + + const handleMouseEnter = (): void => { + clearHideTimeout(); + setShowTooltip(true); + }; + + const handleMouseLeave = (): void => { + clearHideTimeout(); + hideTimeoutRef.current = setTimeout(() => setShowTooltip(false), 100); + }; + + useEffect(() => { + if (showTooltip && containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + const tooltipWidth = 220; + let left = rect.left + rect.width / 2 - tooltipWidth / 2; + if (left < 8) left = 8; + if (left + tooltipWidth > window.innerWidth - 8) { + left = window.innerWidth - tooltipWidth - 8; + } + setTooltipStyle({ + position: 'fixed', + bottom: window.innerHeight - rect.top + 6, + left, + width: tooltipWidth, + zIndex: 99999, + }); + } + }, [showTooltip]); + + useEffect(() => { + if (!showTooltip) return; + const handleScroll = (): void => setShowTooltip(false); + window.addEventListener('scroll', handleScroll, true); + return () => window.removeEventListener('scroll', handleScroll, true); + }, [showTooltip]); + + useEffect(() => { + return () => clearHideTimeout(); + }, []); + + if (!hasMainImpact && !hasIsolated) { + return null; + } + + const mainValue = hasMainImpact ? formatTokensCompact(mainSessionImpact.totalTokens) : null; + const isolatedValue = hasIsolated ? formatTokensCompact(isolatedTotal) : null; + const rightLabel = isolatedLabel ?? 'Isolated Usage'; + + return ( + <> +
    + {mainValue && {mainValue}} + {mainValue && isolatedValue && |} + {isolatedValue && {isolatedValue}} +
    + + {showTooltip && + createPortal( +
    +
    + {hasMainImpact && ( +
    + Main Context + + {mainSessionImpact.totalTokens.toLocaleString()} + +
    + )} + {hasIsolated && ( +
    + {rightLabel} + + {isolatedTotal.toLocaleString()} + +
    + )} +
    + {hasMainImpact && hasIsolated + ? 'Left: parent injection · Right: internal' + : hasMainImpact + ? 'Tokens injected to parent' + : 'Internal token usage'} +
    +
    +
    , + document.body + )} + + ); +}; diff --git a/src/renderer/components/chat/items/SlashItem.tsx b/src/renderer/components/chat/items/SlashItem.tsx new file mode 100644 index 00000000..146a35ec --- /dev/null +++ b/src/renderer/components/chat/items/SlashItem.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +import { Slash } from 'lucide-react'; + +import { MarkdownViewer } from '../viewers'; + +import { BaseItem } from './BaseItem'; + +import type { SlashItem as SlashItemType } from '@renderer/types/groups'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +interface SlashItemProps { + slash: SlashItemType; + onClick: () => void; + isExpanded: boolean; + /** Additional classes for highlighting (e.g., error deep linking) */ + highlightClasses?: string; + /** Inline styles for highlighting (used by custom hex colors) */ + highlightStyle?: React.CSSProperties; + /** Notification dot color for custom triggers */ + notificationDotColor?: TriggerColor; +} + +/** + * SlashItem displays a slash command invocation. + * This unified component handles all slash types: + * - Skills (e.g., /isolate-context) + * - Built-in commands (e.g., /model, /context) + * - Plugin commands + * - MCP commands + * - User-defined commands + */ +export const SlashItem: React.FC = ({ + slash, + onClick, + isExpanded, + highlightClasses, + highlightStyle, + notificationDotColor, +}) => { + const hasInstructions = !!slash.instructions; + + // Display args or message as the description + const description = slash.args ?? slash.message; + + return ( + } + label={`/${slash.name}`} + summary={description} + tokenCount={slash.instructionsTokenCount} + tokenLabel="tokens" + status={hasInstructions ? 'ok' : undefined} + onClick={onClick} + isExpanded={isExpanded} + hasExpandableContent={hasInstructions} + highlightClasses={highlightClasses} + highlightStyle={highlightStyle} + notificationDotColor={notificationDotColor} + > + {hasInstructions && ( + + )} + + ); +}; diff --git a/src/renderer/components/chat/items/SubagentItem.tsx b/src/renderer/components/chat/items/SubagentItem.tsx new file mode 100644 index 00000000..8112fe6f --- /dev/null +++ b/src/renderer/components/chat/items/SubagentItem.tsx @@ -0,0 +1,538 @@ +import React, { useCallback, useMemo, useState } from 'react'; + +import { + CARD_BG, + CARD_BORDER_STYLE, + CARD_HEADER_BG, + CARD_HEADER_HOVER, + CARD_ICON_MUTED, + CARD_SEPARATOR, + CARD_TEXT_LIGHT, + CARD_TEXT_LIGHTER, + COLOR_TEXT_MUTED, + COLOR_TEXT_SECONDARY, + TAG_BG, + TAG_BORDER, + TAG_TEXT, +} from '@renderer/constants/cssVariables'; +import { getTeamColorSet } from '@renderer/constants/teamColors'; +import { useTabUI } from '@renderer/hooks/useTabUI'; +import { useStore } from '@renderer/store'; +import { buildDisplayItemsFromMessages, buildSummary } from '@renderer/utils/aiGroupEnhancer'; +import { formatDuration } from '@renderer/utils/formatters'; +import { getHighlightProps, type TriggerColor } from '@shared/constants/triggerColors'; +import { getModelColorClass, parseModelString } from '@shared/utils/modelParser'; +import { + ArrowUpRight, + Bot, + CheckCircle2, + ChevronRight, + CircleDot, + Loader2, + Sigma, + Terminal, +} from 'lucide-react'; + +import { ExecutionTrace } from './ExecutionTrace'; +import { MetricsPill } from './MetricsPill'; + +import type { Process, SemanticStep } from '@renderer/types/data'; + +// ============================================================================= +// Types +// ============================================================================= + +interface SubagentItemProps { + step: SemanticStep; + subagent: Process; + onClick: () => void; + isExpanded: boolean; + aiGroupId: string; + /** Tool use ID to highlight for error deep linking */ + highlightToolUseId?: string; + /** Custom highlight color from trigger */ + highlightColor?: TriggerColor; + /** Map of tool use ID to trigger color for notification dots */ + notificationColorMap?: Map; + /** Optional callback to register tool element refs for scroll targeting */ + registerToolRef?: (toolId: string, el: HTMLDivElement | null) => void; +} + +// ============================================================================= +// Main Component - Linear-style DevTools Card +// ============================================================================= + +export const SubagentItem: React.FC = ({ + step, + subagent, + onClick, + isExpanded, + aiGroupId, + highlightToolUseId, + highlightColor, + notificationColorMap, + registerToolRef, +}) => { + const description = subagent.description ?? step.content.subagentDescription ?? 'Subagent'; + const subagentType = subagent.subagentType ?? 'Task'; + const truncatedDesc = description.length > 60 ? description.slice(0, 60) + '...' : description; + + // Team member colors (when this subagent is a team member) + const teamColors = subagent.team ? getTeamColorSet(subagent.team.memberColor) : null; + + // Detect shutdown-only team activations (trivial: just a shutdown_response) + const isShutdownOnly = useMemo(() => { + if (!subagent.team || !subagent.messages?.length) return false; + const assistantMsgs = subagent.messages.filter((m) => m.type === 'assistant'); + if (assistantMsgs.length !== 1) return false; + const calls = assistantMsgs[0].toolCalls ?? []; + return ( + calls.length === 1 && + calls[0].name === 'SendMessage' && + calls[0].input?.type === 'shutdown_response' + ); + }, [subagent.team, subagent.messages]); + + // Per-tab trace expansion state (replaces local useState for true per-tab isolation) + const { isSubagentTraceExpanded, toggleSubagentTraceExpansion } = useTabUI(); + const isTraceManuallyExpanded = isSubagentTraceExpanded(subagent.id); + + // Check if contains highlighted error + // Also matches when the highlight targets the parent Task tool_use that spawned this subagent + const containsHighlightedError = useMemo(() => { + if (!highlightToolUseId) return false; + // Match parent Task tool_use ID (trigger matched the Task call itself) + if (subagent.parentTaskId === highlightToolUseId) return true; + // Match inner tool calls/results within the subagent + if (!subagent.messages) return false; + for (const msg of subagent.messages) { + if (msg.toolCalls?.some((tc) => tc.id === highlightToolUseId)) return true; + if (msg.toolResults?.some((tr) => tr.toolUseId === highlightToolUseId)) return true; + } + return false; + }, [highlightToolUseId, subagent.parentTaskId, subagent.messages]); + + // Build display items + const displayItems = useMemo(() => { + if ((!isExpanded && !containsHighlightedError) || !subagent.messages?.length) { + return []; + } + return buildDisplayItemsFromMessages(subagent.messages, []); + }, [isExpanded, containsHighlightedError, subagent.messages]); + + // Build summary + const itemsSummary = useMemo(() => { + if (!isExpanded && !containsHighlightedError) { + const toolCount = + subagent.messages?.filter( + (m) => + m.type === 'assistant' && + Array.isArray(m.content) && + m.content.some((b) => b.type === 'tool_use') + ).length ?? 0; + return toolCount > 0 ? `${toolCount} tools` : ''; + } + return buildSummary(displayItems); + }, [isExpanded, containsHighlightedError, displayItems, subagent.messages]); + + // Model info + const modelInfo = useMemo(() => { + const msg = subagent.messages?.find( + (m) => m.type === 'assistant' && m.model && m.model !== '' + ); + return msg?.model ? parseModelString(msg.model) : null; + }, [subagent.messages]); + + // Last usage + const lastUsage = useMemo(() => { + const messages = subagent.messages ?? []; + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].type === 'assistant' && messages[i].usage) { + return messages[i].usage; + } + } + return null; + }, [subagent.messages]); + + // Search expansion + const searchExpandedSubagentIds = useStore((s) => s.searchExpandedSubagentIds); + const searchCurrentSubagentItemId = useStore((s) => s.searchCurrentSubagentItemId); + const shouldExpandForSearch = searchExpandedSubagentIds.has(subagent.id); + + // Combine manual expansion with auto-expansion for errors/search + const isTraceExpanded = + isTraceManuallyExpanded || containsHighlightedError || shouldExpandForSearch; + const [isTraceHeaderHovered, setIsTraceHeaderHovered] = useState(false); + + // Outer card highlight when this subagent contains the highlighted tool + const outerHighlight = useMemo(() => { + if (!containsHighlightedError) + return { className: '', style: undefined as React.CSSProperties | undefined }; + return getHighlightProps(highlightColor); + }, [containsHighlightedError, highlightColor]); + + // Register outer card as a tool ref target for the parent Task tool_use ID + // so the navigation controller can scroll directly to this SubagentItem + const outerCardRef = useCallback( + (el: HTMLDivElement | null) => { + if (subagent.parentTaskId && registerToolRef) { + registerToolRef(subagent.parentTaskId, el); + } + }, + [subagent.parentTaskId, registerToolRef] + ); + + // Cumulative metrics for team members — show total output generated + const cumulativeMetrics = useMemo(() => { + if (!subagent.team || !subagent.metrics) return undefined; + const turnCount = + subagent.messages?.filter((m) => m.type === 'assistant' && m.usage).length ?? 0; + return { + outputTokens: subagent.metrics.outputTokens, + turnCount, + }; + }, [subagent.team, subagent.metrics, subagent.messages]); + + // Computed values for metrics + const hasMainImpact = subagent.mainSessionImpact && subagent.mainSessionImpact.totalTokens > 0; + const hasIsolated = lastUsage && lastUsage.input_tokens + lastUsage.output_tokens > 0; + const isolatedTotal = lastUsage + ? lastUsage.input_tokens + + lastUsage.output_tokens + + (lastUsage.cache_read_input_tokens ?? 0) + + (lastUsage.cache_creation_input_tokens ?? 0) + : 0; + + // Shutdown-only team activations: minimal inline row (no metrics, no expand) + if (isShutdownOnly && teamColors && subagent.team) { + return ( +
    + + + {subagent.team.memberName} + + + Shutdown confirmed + + + + {formatDuration(subagent.durationMs)} + +
    + ); + } + + return ( +
    + {/* ========== Level 1: Clickable Header ========== */} +
    { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }} + className="flex cursor-pointer items-center gap-2 px-3 py-2 transition-colors" + style={{ + backgroundColor: isExpanded ? CARD_HEADER_BG : 'transparent', + borderBottom: isExpanded ? CARD_BORDER_STYLE : 'none', + }} + > + {/* Expand chevron */} + + + {/* Icon - colored dot for team members, Bot icon for regular subagents */} + {teamColors ? ( + + ) : ( + + )} + + {/* Type badge - team member name or generic type */} + {teamColors && subagent.team ? ( + + {subagent.team.memberName} + + ) : ( + + {subagentType} + + )} + + {/* Model */} + {modelInfo && ( + + {modelInfo.name} + + )} + + {/* Description */} + + {truncatedDesc} + + + {/* Status indicator */} + {subagent.isOngoing ? ( + + ) : ( + + )} + + {/* Unified Metrics Pill — team members don't show mainSessionImpact + (spawn cost only; real main impact comes from teammate messages) */} + + + {/* Duration */} + + {formatDuration(subagent.durationMs)} + +
    + + {/* ========== Level 1 Expanded: Dashboard Content ========== */} + {isExpanded && ( +
    + {/* ========== Row 1: Meta Info (Horizontal Flow) ========== */} +
    + + Type{' '} + + {subagentType} + + + + + Duration{' '} + + {formatDuration(subagent.durationMs)} + + + {modelInfo && ( + <> + + + Model{' '} + + {modelInfo.name} + + + + )} + + + ID{' '} + + {subagent.id.slice(0, 8)} + + +
    + + {/* ========== Row 2: Context Usage (Clean List) ========== */} + {(hasMainImpact ?? hasIsolated) && ( +
    + {/* Overline title */} +
    + Context Usage +
    + + {/* Token rows - floating alignment */} +
    + {hasMainImpact && !subagent.team && ( +
    +
    + + + Main Context + +
    + + {subagent.mainSessionImpact!.totalTokens.toLocaleString()} + +
    + )} + + {cumulativeMetrics && ( +
    +
    + + + Total Output + +
    + + {cumulativeMetrics.outputTokens.toLocaleString()} + + {' '} + ({cumulativeMetrics.turnCount} turns) + + +
    + )} + + {hasIsolated && ( +
    +
    + + + {subagent.team ? 'Context Window' : 'Isolated Usage'} + +
    + + {isolatedTotal.toLocaleString()} + +
    + )} +
    +
    + )} + + {/* ========== Level 2: Execution Trace Toggle ========== */} + {displayItems.length > 0 && ( +
    + {/* Trace Header (clickable) */} +
    { + e.stopPropagation(); + toggleSubagentTraceExpansion(subagent.id); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + toggleSubagentTraceExpansion(subagent.id); + } + }} + className="flex cursor-pointer items-center gap-2 px-3 py-2 transition-colors" + style={{ + borderBottom: isTraceExpanded ? CARD_BORDER_STYLE : 'none', + backgroundColor: isTraceHeaderHovered ? CARD_HEADER_HOVER : 'transparent', + }} + onMouseEnter={() => setIsTraceHeaderHovered(true)} + onMouseLeave={() => setIsTraceHeaderHovered(false)} + > + + + + Execution Trace + + + · {itemsSummary} + +
    + + {/* Trace Content */} + {isTraceExpanded && ( +
    + +
    + )} +
    + )} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/items/TeammateMessageItem.tsx b/src/renderer/components/chat/items/TeammateMessageItem.tsx new file mode 100644 index 00000000..ebe286cf --- /dev/null +++ b/src/renderer/components/chat/items/TeammateMessageItem.tsx @@ -0,0 +1,263 @@ +import React, { useMemo } from 'react'; + +import { + CARD_BG, + CARD_BORDER_STYLE, + CARD_HEADER_BG, + CARD_ICON_MUTED, + CARD_TEXT_LIGHT, +} from '@renderer/constants/cssVariables'; +import { getTeamColorSet } from '@renderer/constants/teamColors'; +import { formatTokensCompact } from '@renderer/utils/formatters'; +import { ChevronRight, CornerDownLeft, MessageSquare, RefreshCw } from 'lucide-react'; + +import { MarkdownViewer } from '../viewers/MarkdownViewer'; + +import type { TeammateMessage } from '@renderer/types/groups'; + +// ============================================================================= +// Types +// ============================================================================= + +interface TeammateMessageItemProps { + teammateMessage: TeammateMessage; + onClick: () => void; + isExpanded: boolean; + /** Callback to spotlight the reply link: pass toolId on hover, null on leave */ + onReplyHover?: (toolId: string | null) => void; + /** Additional classes for highlighting (e.g., error deep linking) */ + highlightClasses?: string; + /** Inline styles for highlighting (used by custom hex colors) */ + highlightStyle?: React.CSSProperties; +} + +/** Operational noise types that should be rendered minimally */ +const NOISE_TYPES = new Set([ + 'idle_notification', + 'shutdown_approved', + 'teammate_terminated', + 'shutdown_request', +]); + +/** Human-readable labels for noise message types */ +const NOISE_LABELS: Record = { + idle_notification: 'Idle', + shutdown_approved: 'Shutdown confirmed', + teammate_terminated: 'Terminated', + shutdown_request: 'Shutdown requested', +}; + +/** + * Detect operational noise in teammate message content. + * Returns label if noise, null if real content. + */ +function detectNoise(content: string, teammateId: string): string | null { + // System messages are always noise + if (teammateId === 'system') { + const trimmed = content.trim(); + if (trimmed.startsWith('{')) { + try { + const parsed = JSON.parse(trimmed) as { type?: string; message?: string }; + if (parsed.type && NOISE_TYPES.has(parsed.type)) { + return parsed.message ?? NOISE_LABELS[parsed.type] ?? parsed.type; + } + } catch { + // Not JSON, fall through + } + } + return trimmed.length < 200 ? trimmed : null; + } + + // Non-system: check if content is a JSON operational message + const trimmed = content.trim(); + if (!trimmed.startsWith('{')) return null; + try { + const parsed = JSON.parse(trimmed) as { type?: string }; + if (parsed.type && NOISE_TYPES.has(parsed.type)) { + return NOISE_LABELS[parsed.type] ?? parsed.type; + } + } catch { + // Not JSON + } + return null; +} + +// ============================================================================= +// Resend Detection +// ============================================================================= + +const RESEND_PATTERNS = [ + /\bresend/i, + /\bre-send/i, + /\bsent\b.{0,20}\bearlier/i, + /\balready\s+sent/i, + /\bsent\s+in\s+my\s+previous/i, +]; + +function isResendMessage(message: TeammateMessage): boolean { + // Check summary first (cheaper) + if (RESEND_PATTERNS.some((p) => p.test(message.summary))) return true; + // Check first 300 chars of content + const contentSnippet = message.content.slice(0, 300); + return RESEND_PATTERNS.some((p) => p.test(contentSnippet)); +} + +// ============================================================================= +// Component +// ============================================================================= + +/** + * TeammateMessageItem - Card component for teammate messages. + * + * Visual distinction from SubagentItem: + * - Left color accent border (3px) + * - "Message" type label after name badge + * - No metrics pill, no duration, no model info + * + * Operational noise (idle/shutdown/terminated) renders as minimal inline text. + */ +export const TeammateMessageItem: React.FC = ({ + teammateMessage, + onClick, + isExpanded, + onReplyHover, + highlightClasses = '', + highlightStyle, +}) => { + const colors = getTeamColorSet(teammateMessage.color); + + // Detect operational noise + const noiseLabel = useMemo( + () => detectNoise(teammateMessage.content, teammateMessage.teammateId), + [teammateMessage.content, teammateMessage.teammateId] + ); + + // Detect resent/duplicate messages + const isResend = useMemo(() => isResendMessage(teammateMessage), [teammateMessage]); + + // Noise: minimal inline row (no card, no expand) + if (noiseLabel) { + return ( +
    + + + {teammateMessage.teammateId} + + + {noiseLabel} + +
    + ); + } + + // Real message: full card with visual distinction + const truncatedSummary = + teammateMessage.summary.length > 80 + ? teammateMessage.summary.slice(0, 80) + '...' + : teammateMessage.summary; + + return ( +
    + {/* Header */} +
    { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }} + className="flex cursor-pointer items-center gap-2 px-3 py-2 transition-colors" + style={{ + backgroundColor: isExpanded ? CARD_HEADER_BG : 'transparent', + borderBottom: isExpanded ? CARD_BORDER_STYLE : 'none', + }} + > + + + {/* Message icon — distinguishes from SubagentItem's Bot/dot icon */} + + + {/* Teammate name badge */} + + {teammateMessage.teammateId} + + + {/* "Message" type label — parallels SubagentItem's model info */} + + Message + + + {/* Reply indicator — shows which SendMessage triggered this response */} + {teammateMessage.replyToSummary && ( + onReplyHover?.(teammateMessage.replyToToolId ?? null)} + onMouseLeave={() => onReplyHover?.(null)} + > + + + {teammateMessage.replyToSummary} + + + )} + + {/* Resend badge — marks duplicate/resent messages */} + {isResend && ( + + + Resent + + )} + + {/* Summary */} + + {truncatedSummary || 'Teammate message'} + + + {/* Context impact — tokens injected into main session */} + {teammateMessage.tokenCount != null && teammateMessage.tokenCount > 0 && ( + + ~{formatTokensCompact(teammateMessage.tokenCount)} tokens + + )} +
    + + {/* Expanded content */} + {isExpanded && ( +
    + +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/items/TextItem.tsx b/src/renderer/components/chat/items/TextItem.tsx new file mode 100644 index 00000000..63b9d17c --- /dev/null +++ b/src/renderer/components/chat/items/TextItem.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { MessageSquare } from 'lucide-react'; + +import { MarkdownViewer } from '../viewers'; + +import { BaseItem } from './BaseItem'; +import { truncateText } from './baseItemHelpers'; + +import type { SemanticStep } from '@renderer/types/data'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +interface TextItemProps { + step: SemanticStep; + preview: string; + onClick: () => void; + isExpanded: boolean; + /** Additional classes for highlighting (e.g., error deep linking) */ + highlightClasses?: string; + /** Inline styles for highlighting (used by custom hex colors) */ + highlightStyle?: React.CSSProperties; + /** Notification dot color for custom triggers */ + notificationDotColor?: TriggerColor; +} + +export const TextItem: React.FC = ({ + step, + preview, + onClick, + isExpanded, + highlightClasses, + highlightStyle, + notificationDotColor, +}) => { + const fullContent = step.content.outputText ?? preview; + const truncatedPreview = truncateText(preview, 60); + + // Get token count from step.tokens.output or step.content.tokenCount + const tokenCount = step.tokens?.output ?? step.content.tokenCount ?? 0; + + return ( + } + label="Output" + summary={truncatedPreview} + tokenCount={tokenCount} + onClick={onClick} + isExpanded={isExpanded} + highlightClasses={highlightClasses} + highlightStyle={highlightStyle} + notificationDotColor={notificationDotColor} + > + + + ); +}; diff --git a/src/renderer/components/chat/items/ThinkingItem.tsx b/src/renderer/components/chat/items/ThinkingItem.tsx new file mode 100644 index 00000000..e1034ef4 --- /dev/null +++ b/src/renderer/components/chat/items/ThinkingItem.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { Brain } from 'lucide-react'; + +import { MarkdownViewer } from '../viewers'; + +import { BaseItem } from './BaseItem'; +import { truncateText } from './baseItemHelpers'; + +import type { SemanticStep } from '@renderer/types/data'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +interface ThinkingItemProps { + step: SemanticStep; + preview: string; + onClick: () => void; + isExpanded: boolean; + /** Additional classes for highlighting (e.g., error deep linking) */ + highlightClasses?: string; + /** Inline styles for highlighting (used by custom hex colors) */ + highlightStyle?: React.CSSProperties; + /** Notification dot color for custom triggers */ + notificationDotColor?: TriggerColor; +} + +export const ThinkingItem: React.FC = ({ + step, + preview, + onClick, + isExpanded, + highlightClasses, + highlightStyle, + notificationDotColor, +}) => { + const fullContent = step.content.thinkingText ?? preview; + const truncatedPreview = truncateText(preview, 60); + + // Get token count from step.tokens.output or step.content.tokenCount + const tokenCount = step.tokens?.output ?? step.content.tokenCount ?? 0; + + return ( + } + label="Thinking" + summary={truncatedPreview} + tokenCount={tokenCount} + onClick={onClick} + isExpanded={isExpanded} + highlightClasses={highlightClasses} + highlightStyle={highlightStyle} + notificationDotColor={notificationDotColor} + > + + + ); +}; diff --git a/src/renderer/components/chat/items/baseItemHelpers.ts b/src/renderer/components/chat/items/baseItemHelpers.ts new file mode 100644 index 00000000..ff2b1724 --- /dev/null +++ b/src/renderer/components/chat/items/baseItemHelpers.ts @@ -0,0 +1,42 @@ +/** + * Helper functions for BaseItem component. + * Extracted to a separate file to comply with react-refresh/only-export-components. + */ + +import { formatTokens } from '@shared/utils/tokenFormatting'; + +import type { ItemStatus } from './BaseItem'; + +// Re-export for backwards compatibility +export { formatTokens }; + +/** + * Formats duration in milliseconds to a human-readable string. + */ +export function formatDuration(ms: number | undefined): string { + if (ms === undefined) return '...'; + if (ms < 1000) return `${Math.round(ms)}ms`; + return `${(ms / 1000).toFixed(1)}s`; +} + +/** + * Truncates text to a maximum length with ellipsis. + */ +export function truncateText(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + return text.slice(0, maxLength) + '...'; +} + +/** + * Get background color for status dot. + * Returns CSS value (hex for semantic colors, CSS variable for neutral). + */ +export function getStatusDotColor(status: ItemStatus): string { + const colors: Record = { + ok: '#22c55e', // green-500 - semantic success + error: '#ef4444', // red-500 - semantic error + pending: '#eab308', // yellow-500 - semantic pending + orphaned: 'var(--tool-item-muted)', // theme-aware neutral + }; + return colors[status]; +} diff --git a/src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx b/src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx new file mode 100644 index 00000000..1be3f906 --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/DefaultToolViewer.tsx @@ -0,0 +1,67 @@ +/** + * DefaultToolViewer + * + * Default rendering for tools that don't have specialized viewers. + */ + +import React from 'react'; + +import { type ItemStatus, StatusDot } from '../BaseItem'; + +import { renderInput, renderOutput } from './renderHelpers'; + +import type { LinkedToolItem } from '@renderer/types/groups'; + +interface DefaultToolViewerProps { + linkedTool: LinkedToolItem; + status: ItemStatus; +} + +export const DefaultToolViewer: React.FC = ({ linkedTool, status }) => { + return ( + <> + {/* Input Section */} +
    +
    + Input +
    +
    + {renderInput(linkedTool.name, linkedTool.input)} +
    +
    + + {/* Output Section */} + {!linkedTool.isOrphaned && linkedTool.result && ( +
    +
    + Output + +
    +
    + {renderOutput(linkedTool.result.content)} +
    +
    + )} + + ); +}; diff --git a/src/renderer/components/chat/items/linkedTool/EditToolViewer.tsx b/src/renderer/components/chat/items/linkedTool/EditToolViewer.tsx new file mode 100644 index 00000000..b924a9be --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/EditToolViewer.tsx @@ -0,0 +1,73 @@ +/** + * EditToolViewer + * + * Renders the Edit tool with DiffViewer. + */ + +import React from 'react'; + +import { DiffViewer } from '@renderer/components/chat/viewers'; + +import { type ItemStatus, StatusDot } from '../BaseItem'; +import { formatTokens } from '../baseItemHelpers'; + +import { renderOutput } from './renderHelpers'; + +import type { LinkedToolItem } from '@renderer/types/groups'; + +interface EditToolViewerProps { + linkedTool: LinkedToolItem; + status: ItemStatus; +} + +export const EditToolViewer: React.FC = ({ linkedTool, status }) => { + const toolUseResult = linkedTool.result?.toolUseResult as Record | undefined; + + const filePath = (toolUseResult?.filePath as string) || (linkedTool.input.file_path as string); + const oldString = + (toolUseResult?.oldString as string) || (linkedTool.input.old_string as string) || ''; + const newString = + (toolUseResult?.newString as string) || (linkedTool.input.new_string as string) || ''; + + return ( +
    + + + {/* Show result status if available */} + {!linkedTool.isOrphaned && linkedTool.result != null && ( +
    +
    + Result + + {linkedTool.result?.tokenCount !== undefined && linkedTool.result.tokenCount > 0 && ( + + ~{formatTokens(linkedTool.result.tokenCount)} tokens + + )} +
    +
    + {renderOutput(linkedTool.result.content)} +
    +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx b/src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx new file mode 100644 index 00000000..f0eeb688 --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/ReadToolViewer.tsx @@ -0,0 +1,65 @@ +/** + * ReadToolViewer + * + * Renders the Read tool result using CodeBlockViewer. + */ + +import React from 'react'; + +import { CodeBlockViewer } from '@renderer/components/chat/viewers'; + +import type { LinkedToolItem } from '@renderer/types/groups'; + +interface ReadToolViewerProps { + linkedTool: LinkedToolItem; +} + +export const ReadToolViewer: React.FC = ({ linkedTool }) => { + const filePath = linkedTool.input.file_path as string; + + // Prefer enriched toolUseResult data + const toolUseResult = linkedTool.result?.toolUseResult as Record | undefined; + const fileData = toolUseResult?.file as + | { + content?: string; + startLine?: number; + totalLines?: number; + numLines?: number; + } + | undefined; + + // Get content: prefer enriched file data, fall back to raw result content + let content: string; + if (fileData?.content) { + content = fileData.content; + } else { + const resultContent = linkedTool.result?.content; + content = + typeof resultContent === 'string' + ? resultContent + : Array.isArray(resultContent) + ? resultContent + .map((item: unknown) => (typeof item === 'string' ? item : JSON.stringify(item))) + .join('\n') + : JSON.stringify(resultContent, null, 2); + } + + // Get line range + const startLine = fileData?.startLine ?? (linkedTool.input.offset as number | undefined) ?? 1; + const numLinesRead = fileData?.numLines; + const limit = linkedTool.input.limit as number | undefined; + const endLine = numLinesRead + ? startLine + numLinesRead - 1 + : limit + ? startLine + limit - 1 + : undefined; + + return ( + + ); +}; diff --git a/src/renderer/components/chat/items/linkedTool/SkillToolViewer.tsx b/src/renderer/components/chat/items/linkedTool/SkillToolViewer.tsx new file mode 100644 index 00000000..c6447d79 --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/SkillToolViewer.tsx @@ -0,0 +1,67 @@ +/** + * SkillToolViewer + * + * Renders the Skill tool with its instructions in a code block viewer style. + */ + +import React from 'react'; + +import { CodeBlockViewer } from '@renderer/components/chat/viewers'; + +import type { LinkedToolItem } from '@renderer/types/groups'; + +interface SkillToolViewerProps { + linkedTool: LinkedToolItem; +} + +export const SkillToolViewer: React.FC = ({ linkedTool }) => { + const skillInstructions = linkedTool.skillInstructions; + const skillName = (linkedTool.input.skill as string) || 'Unknown Skill'; + + const resultContent = linkedTool.result?.content; + const resultText = + typeof resultContent === 'string' + ? resultContent + : Array.isArray(resultContent) + ? resultContent + .map((item: unknown) => (typeof item === 'string' ? item : JSON.stringify(item))) + .join('\n') + : ''; + + return ( +
    + {/* Initial result */} + {resultText && ( +
    +
    + Result +
    +
    + {resultText} +
    +
    + )} + + {/* Skill instructions */} + {skillInstructions && ( +
    +
    + Skill Instructions +
    + +
    + )} +
    + ); +}; diff --git a/src/renderer/components/chat/items/linkedTool/ToolErrorDisplay.tsx b/src/renderer/components/chat/items/linkedTool/ToolErrorDisplay.tsx new file mode 100644 index 00000000..13f3fd76 --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/ToolErrorDisplay.tsx @@ -0,0 +1,43 @@ +/** + * ToolErrorDisplay + * + * Displays error output for tool results. + */ + +import React from 'react'; + +import { StatusDot } from '../BaseItem'; + +import { renderOutput } from './renderHelpers'; + +import type { LinkedToolItem } from '@renderer/types/groups'; + +interface ToolErrorDisplayProps { + linkedTool: LinkedToolItem; +} + +export const ToolErrorDisplay: React.FC = ({ linkedTool }) => { + if (!linkedTool.result?.isError) return null; + + return ( +
    +
    + Error + +
    +
    + {renderOutput(linkedTool.result.content)} +
    +
    + ); +}; diff --git a/src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx b/src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx new file mode 100644 index 00000000..22cd90b1 --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/WriteToolViewer.tsx @@ -0,0 +1,32 @@ +/** + * WriteToolViewer + * + * Renders the Write tool result. + */ + +import React from 'react'; + +import { CodeBlockViewer } from '@renderer/components/chat/viewers'; + +import type { LinkedToolItem } from '@renderer/types/groups'; + +interface WriteToolViewerProps { + linkedTool: LinkedToolItem; +} + +export const WriteToolViewer: React.FC = ({ linkedTool }) => { + const toolUseResult = linkedTool.result?.toolUseResult as Record | undefined; + + const filePath = (toolUseResult?.filePath as string) || (linkedTool.input.file_path as string); + const content = (toolUseResult?.content as string) || (linkedTool.input.content as string) || ''; + const isCreate = toolUseResult?.type === 'create'; + + return ( +
    +
    + {isCreate ? 'Created file' : 'Wrote to file'} +
    + +
    + ); +}; diff --git a/src/renderer/components/chat/items/linkedTool/index.ts b/src/renderer/components/chat/items/linkedTool/index.ts new file mode 100644 index 00000000..5c415dac --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/index.ts @@ -0,0 +1,12 @@ +/** + * Linked Tool Sub-components + * + * Exports all specialized tool viewer components. + */ + +export { DefaultToolViewer } from './DefaultToolViewer'; +export { EditToolViewer } from './EditToolViewer'; +export { ReadToolViewer } from './ReadToolViewer'; +export { SkillToolViewer } from './SkillToolViewer'; +export { ToolErrorDisplay } from './ToolErrorDisplay'; +export { WriteToolViewer } from './WriteToolViewer'; diff --git a/src/renderer/components/chat/items/linkedTool/renderHelpers.tsx b/src/renderer/components/chat/items/linkedTool/renderHelpers.tsx new file mode 100644 index 00000000..634ee641 --- /dev/null +++ b/src/renderer/components/chat/items/linkedTool/renderHelpers.tsx @@ -0,0 +1,116 @@ +/** + * Render Helpers + * + * Shared rendering functions for tool input and output. + */ + +import React from 'react'; + +import { + COLOR_TEXT, + COLOR_TEXT_MUTED, + DIFF_ADDED_TEXT, + DIFF_REMOVED_TEXT, +} from '@renderer/constants/cssVariables'; + +/** + * Renders the input section based on tool type with theme-aware styling. + */ +export function renderInput(toolName: string, input: Record): React.ReactElement { + // Special rendering for Edit tool - show diff-like format + if (toolName === 'Edit') { + const filePath = input.file_path as string | undefined; + const oldString = input.old_string as string | undefined; + const newString = input.new_string as string | undefined; + const replaceAll = input.replace_all as boolean | undefined; + + return ( +
    + {filePath && ( +
    + {filePath} + {replaceAll && ( + + (replace all) + + )} +
    + )} + {oldString && ( +
    + {oldString.split('\n').map((line, i) => ( +
    - {line}
    + ))} +
    + )} + {newString && ( +
    + {newString.split('\n').map((line, i) => ( +
    + {line}
    + ))} +
    + )} +
    + ); + } + + // Special rendering for Bash tool + if (toolName === 'Bash') { + const command = input.command as string | undefined; + const description = input.description as string | undefined; + + return ( +
    + {description && ( +
    + {description} +
    + )} + {command && ( + + {command} + + )} +
    + ); + } + + // Special rendering for Read tool + if (toolName === 'Read') { + const filePath = input.file_path as string | undefined; + const offset = input.offset as number | undefined; + const limit = input.limit as number | undefined; + + return ( +
    +
    {filePath}
    + {(offset !== undefined || limit !== undefined) && ( +
    + {offset !== undefined && `offset: ${offset}`} + {offset !== undefined && limit !== undefined && ', '} + {limit !== undefined && `limit: ${limit}`} +
    + )} +
    + ); + } + + // Default: JSON format + return ( +
    +      {JSON.stringify(input, null, 2)}
    +    
    + ); +} + +/** + * Renders the output section with theme-aware styling. + */ +export function renderOutput(content: string | unknown[]): React.ReactElement { + const displayText = typeof content === 'string' ? content : JSON.stringify(content, null, 2); + return ( +
    +      {displayText}
    +    
    + ); +} diff --git a/src/renderer/components/chat/markdownComponents.tsx b/src/renderer/components/chat/markdownComponents.tsx new file mode 100644 index 00000000..5ab54e9d --- /dev/null +++ b/src/renderer/components/chat/markdownComponents.tsx @@ -0,0 +1,228 @@ +import React from 'react'; + +import { PROSE_BODY } from '@renderer/constants/cssVariables'; + +import { highlightSearchInChildren, type SearchContext } from './searchHighlightUtils'; + +import type { Components } from 'react-markdown'; + +/** + * Create inline markdown components for rendering prose content. + * When searchCtx is provided, search term highlighting is applied + * to text nodes while preserving full markdown rendering. + */ +export function createMarkdownComponents(searchCtx: SearchContext | null): Components { + const hl = (children: React.ReactNode): React.ReactNode => + searchCtx ? highlightSearchInChildren(children, searchCtx) : children; + + return { + // Headings - Bold text with generous spacing to break up content + h1: ({ children }) => ( +

    + {hl(children)} +

    + ), + h2: ({ children }) => ( +

    + {hl(children)} +

    + ), + h3: ({ children }) => ( +

    + {hl(children)} +

    + ), + h4: ({ children }) => ( +

    + {hl(children)} +

    + ), + h5: ({ children }) => ( +
    + {hl(children)} +
    + ), + h6: ({ children }) => ( +
    + {hl(children)} +
    + ), + + // Paragraphs + p: ({ children }) => ( +

    + {hl(children)} +

    + ), + + // Links — inline element, no hl(); parent block element's hl() descends here + a: ({ href, children }) => ( + + {children} + + ), + + // Strong/Bold — inline element, no hl() + strong: ({ children }) => ( + + {children} + + ), + + // Emphasis/Italic — inline element, no hl() + em: ({ children }) => ( + + {children} + + ), + + // Strikethrough — inline element, no hl() + del: ({ children }) => ( + + {children} + + ), + + // Inline code vs block code + code: ({ className, children }) => { + const hasLanguageClass = className?.includes('language-'); + const content = typeof children === 'string' ? children : ''; + const isMultiLine = content.includes('\n'); + const isBlock = (hasLanguageClass ?? false) || isMultiLine; + + if (isBlock) { + return ( + + {hl(children)} + + ); + } + // Inline code — no hl(); parent block element's hl() descends here + return ( + + {children} + + ); + }, + + // Code blocks + pre: ({ children }) => ( +
    +        {children}
    +      
    + ), + + // Blockquotes + blockquote: ({ children }) => ( +
    + {hl(children)} +
    + ), + + // Lists + ul: ({ children }) => ( +
      + {children} +
    + ), + ol: ({ children }) => ( +
      + {children} +
    + ), + li: ({ children }) => ( +
  3. + {hl(children)} +
  4. + ), + + // Tables + table: ({ children }) => ( +
    + + {children} +
    +
    + ), + thead: ({ children }) => ( + {children} + ), + th: ({ children }) => ( + + {hl(children)} + + ), + td: ({ children }) => ( + + {hl(children)} + + ), + + // Horizontal rule + hr: () =>
    , + }; +} + +/** Default markdown components without search highlighting (used by CompactBoundary) */ +export const markdownComponents: Components = createMarkdownComponents(null); diff --git a/src/renderer/components/chat/searchHighlightUtils.ts b/src/renderer/components/chat/searchHighlightUtils.ts new file mode 100644 index 00000000..873351b4 --- /dev/null +++ b/src/renderer/components/chat/searchHighlightUtils.ts @@ -0,0 +1,147 @@ +/** + * Search highlighting utilities for use within ReactMarkdown components. + * Recursively processes React children to highlight search term matches + * while preserving the markdown-rendered element tree. + */ + +import React from 'react'; + +import type { SearchMatch } from '@renderer/store/types'; + +// Highlight styles matching SearchHighlight.tsx +const baseStyles: React.CSSProperties = { + borderRadius: '0.125rem', + padding: '0 0.125rem', +}; + +const currentHighlightStyles: React.CSSProperties = { + ...baseStyles, + backgroundColor: 'var(--highlight-bg)', + color: 'var(--highlight-text)', + boxShadow: '0 0 0 1px var(--highlight-ring)', +}; + +const inactiveHighlightStyles: React.CSSProperties = { + ...baseStyles, + backgroundColor: 'var(--highlight-bg-inactive)', + color: 'var(--highlight-text-inactive)', +}; + +export interface SearchContext { + itemId: string; + query: string; + lowerQuery: string; + /** Mutable counter tracking match index within the item, incremented as text nodes are processed */ + matchCounter: { current: number }; + isCurrentItem: boolean; + currentMatchIndexInItem: number | null; +} + +/** + * Create a SearchContext from store state. + * Returns null if no search is active. + */ +export function createSearchContext( + searchQuery: string, + itemId: string, + searchMatches: SearchMatch[], + currentSearchIndex: number +): SearchContext | null { + if (!searchQuery || searchQuery.trim().length === 0) return null; + + const currentMatch = currentSearchIndex >= 0 ? searchMatches[currentSearchIndex] : null; + const isCurrentItem = currentMatch?.itemId === itemId; + + return { + itemId, + query: searchQuery, + lowerQuery: searchQuery.toLowerCase(), + matchCounter: { current: 0 }, + isCurrentItem, + currentMatchIndexInItem: isCurrentItem ? (currentMatch?.matchIndexInItem ?? null) : null, + }; +} + +/** + * Highlight search term matches in a text string. + * Increments matchCounter for each match found. + */ +// eslint-disable-next-line sonarjs/function-return-type -- mixed text/element return +function highlightSearchText(text: string, ctx: SearchContext): React.ReactNode { + const lowerText = text.toLowerCase(); + const parts: React.ReactNode[] = []; + let lastIndex = 0; + let pos = 0; + + while ((pos = lowerText.indexOf(ctx.lowerQuery, pos)) !== -1) { + if (pos > lastIndex) { + parts.push(text.slice(lastIndex, pos)); + } + + const isCurrentResult = + ctx.isCurrentItem && ctx.currentMatchIndexInItem === ctx.matchCounter.current; + + parts.push( + React.createElement( + 'mark', + { + key: `s-${pos}-${ctx.matchCounter.current}`, + style: isCurrentResult ? currentHighlightStyles : inactiveHighlightStyles, + 'data-search-result': isCurrentResult ? 'current' : 'match', + 'data-search-item-id': ctx.itemId, + 'data-search-match-index': ctx.matchCounter.current, + }, + text.slice(pos, pos + ctx.query.length) + ) + ); + + lastIndex = pos + ctx.query.length; + pos = lastIndex; + ctx.matchCounter.current++; + } + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + if (parts.length === 0) return text; + if (parts.length === 1) return parts[0]; + return parts; +} + +/** + * Recursively process React children to highlight search terms in text nodes. + * Preserves the React element tree structure (markdown components, etc.) + * while adding tags to text content. + */ +// eslint-disable-next-line sonarjs/function-return-type -- React child manipulation inherently returns mixed node types +export function highlightSearchInChildren( + children: React.ReactNode, + ctx: SearchContext +): React.ReactNode { + // eslint-disable-next-line sonarjs/function-return-type -- React child manipulation inherently returns mixed node types + return React.Children.map(children, (child): React.ReactNode => { + if (typeof child === 'string') { + return highlightSearchText(child, ctx); + } + + if (React.isValidElement<{ children?: React.ReactNode }>(child)) { + // Skip elements already created by search highlighting to prevent + // double-counting when hl() is applied at multiple markdown component levels + // (e.g., both the `strong` and `p` components process the same text) + if (child.type === 'mark' && (child.props as Record)['data-search-result']) { + return child; + } + + if (child.props.children) { + return React.cloneElement( + child, + undefined, + highlightSearchInChildren(child.props.children, ctx) + ); + } + } + + return child; + }); +} diff --git a/src/renderer/components/chat/viewers/CodeBlockViewer.tsx b/src/renderer/components/chat/viewers/CodeBlockViewer.tsx new file mode 100644 index 00000000..d694f3f1 --- /dev/null +++ b/src/renderer/components/chat/viewers/CodeBlockViewer.tsx @@ -0,0 +1,244 @@ +import React, { useMemo, useState } from 'react'; + +import { getBaseName } from '@renderer/utils/pathUtils'; +import { createLogger } from '@shared/utils/logger'; +import { Check, Copy, FileCode } from 'lucide-react'; + +const logger = createLogger('Component:CodeBlockViewer'); + +import { highlightLine } from './syntaxHighlighter'; + +// ============================================================================= +// Types +// ============================================================================= + +interface CodeBlockViewerProps { + fileName: string; // e.g., "src/components/Header.tsx" + content: string; // The actual file content + language?: string; // Inferred from file extension if not provided + startLine?: number; // If partial read, starting line + endLine?: number; // If partial read, ending line + maxHeight?: string; // CSS max-height class (default: "max-h-96") +} + +// ============================================================================= +// Language Detection +// ============================================================================= + +const EXTENSION_LANGUAGE_MAP: Record = { + // JavaScript/TypeScript + '.ts': 'typescript', + '.tsx': 'tsx', + '.js': 'javascript', + '.jsx': 'jsx', + '.mjs': 'javascript', + '.cjs': 'javascript', + + // Python + '.py': 'python', + '.pyw': 'python', + '.pyx': 'python', + + // Web + '.html': 'html', + '.htm': 'html', + '.css': 'css', + '.scss': 'scss', + '.sass': 'sass', + '.less': 'less', + + // Data formats + '.json': 'json', + '.jsonl': 'json', + '.yaml': 'yaml', + '.yml': 'yaml', + '.toml': 'toml', + '.xml': 'xml', + + // Shell + '.sh': 'bash', + '.bash': 'bash', + '.zsh': 'zsh', + '.fish': 'fish', + + // Systems + '.rs': 'rust', + '.go': 'go', + '.c': 'c', + '.h': 'c', + '.cpp': 'cpp', + '.cc': 'cpp', + '.hpp': 'hpp', + '.java': 'java', + '.kt': 'kotlin', + '.swift': 'swift', + + // Config + '.env': 'env', + '.gitignore': 'gitignore', + '.dockerignore': 'dockerignore', + '.md': 'markdown', + '.mdx': 'mdx', + + // Other + '.sql': 'sql', + '.graphql': 'graphql', + '.gql': 'graphql', + '.vue': 'vue', + '.svelte': 'svelte', + '.rb': 'ruby', + '.php': 'php', + '.lua': 'lua', + '.r': 'r', + '.R': 'r', +}; + +/** + * Infer language from file name/extension. + */ +function inferLanguage(fileName: string): string { + // Check for dotfiles with specific names + const baseName = getBaseName(fileName); + if (baseName === 'Dockerfile') return 'dockerfile'; + if (baseName === 'Makefile') return 'makefile'; + if (baseName.startsWith('.env')) return 'env'; + + // Extract extension + const extMatch = /(\.[^./]+)$/.exec(fileName); + if (extMatch) { + const ext = extMatch[1].toLowerCase(); + return EXTENSION_LANGUAGE_MAP[ext] ?? 'text'; + } + + return 'text'; +} + +// ============================================================================= +// Component +// ============================================================================= + +export const CodeBlockViewer: React.FC = ({ + fileName, + content, + language, + startLine = 1, + endLine, + maxHeight = 'max-h-96', +}): React.JSX.Element => { + const [isCopied, setIsCopied] = useState(false); + + // Infer language from file extension if not provided + const detectedLanguage = language ?? inferLanguage(fileName); + + // Split content into lines + const lines = useMemo(() => content.split('\n'), [content]); + const totalLines = lines.length; + + // Calculate the actual line range for display + const actualEndLine = endLine ?? startLine + totalLines - 1; + + // Handle copy + const handleCopy = async (): Promise => { + try { + await navigator.clipboard.writeText(content); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch { + logger.error('Failed to copy to clipboard'); + } + }; + + // Extract just the filename for display + const displayFileName = getBaseName(fileName) || fileName; + + return ( +
    + {/* Header */} +
    +
    + + + {displayFileName} + + {(startLine > 1 || endLine) && ( + + (lines {startLine}-{actualEndLine}) + + )} + + {detectedLanguage} + +
    + + {/* Copy button */} + +
    + + {/* Code content */} +
    +
    +          
    +            {lines.map((line, index) => {
    +              const lineNumber = startLine + index;
    +              return (
    +                
    + {/* Line number */} + + {lineNumber} + + {/* Code line */} + + {highlightLine(line, detectedLanguage)} + +
    + ); + })} +
    +
    +
    +
    + ); +}; diff --git a/src/renderer/components/chat/viewers/DiffViewer.tsx b/src/renderer/components/chat/viewers/DiffViewer.tsx new file mode 100644 index 00000000..719e4395 --- /dev/null +++ b/src/renderer/components/chat/viewers/DiffViewer.tsx @@ -0,0 +1,372 @@ +import React from 'react'; + +import { + CODE_BG, + CODE_BORDER, + CODE_FILENAME, + CODE_HEADER_BG, + CODE_LINE_NUMBER, + COLOR_TEXT_MUTED, + COLOR_TEXT_SECONDARY, + DIFF_ADDED_BG, + DIFF_ADDED_BORDER, + DIFF_ADDED_TEXT, + DIFF_REMOVED_BG, + DIFF_REMOVED_BORDER, + DIFF_REMOVED_TEXT, + TAG_BG, + TAG_BORDER, + TAG_TEXT, +} from '@renderer/constants/cssVariables'; +import { getBaseName } from '@renderer/utils/pathUtils'; +import { formatTokens } from '@shared/utils/tokenFormatting'; +import { Pencil } from 'lucide-react'; + +// ============================================================================= +// Types +// ============================================================================= + +interface DiffViewerProps { + fileName: string; // The file being edited + oldString: string; // The original text being replaced + newString: string; // The new text + maxHeight?: string; // CSS max-height class (default: "max-h-96") + tokenCount?: number; // Optional token count to display in header +} + +interface DiffLine { + type: 'removed' | 'added' | 'context'; + content: string; + lineNumber: number; +} + +// ============================================================================= +// Diff Algorithm (LCS-based) +// ============================================================================= + +/** + * Computes the Longest Common Subsequence matrix for two arrays of strings. + */ +function computeLCSMatrix(oldLines: string[], newLines: string[]): number[][] { + const m = oldLines.length; + const n = newLines.length; + const matrix: number[][] = Array.from({ length: m + 1 }, () => + Array.from({ length: n + 1 }, () => 0) + ); + + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (oldLines[i - 1] === newLines[j - 1]) { + matrix[i][j] = matrix[i - 1][j - 1] + 1; + } else { + matrix[i][j] = Math.max(matrix[i - 1][j], matrix[i][j - 1]); + } + } + } + + return matrix; +} + +/** + * Backtrack through LCS matrix to generate diff lines. + */ +function generateDiff(oldLines: string[], newLines: string[]): DiffLine[] { + const matrix = computeLCSMatrix(oldLines, newLines); + const result: DiffLine[] = []; + + let i = oldLines.length; + let j = newLines.length; + let lineNumber = 1; + + // Temporary storage for backtracking + const temp: DiffLine[] = []; + + while (i > 0 || j > 0) { + if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) { + // Lines are the same - context + temp.push({ type: 'context', content: oldLines[i - 1], lineNumber: 0 }); + i--; + j--; + } else if (j > 0 && (i === 0 || matrix[i][j - 1] >= matrix[i - 1][j])) { + // Line was added + temp.push({ type: 'added', content: newLines[j - 1], lineNumber: 0 }); + j--; + } else if (i > 0) { + // Line was removed + temp.push({ type: 'removed', content: oldLines[i - 1], lineNumber: 0 }); + i--; + } + } + + // Reverse and assign line numbers + temp.reverse(); + for (const line of temp) { + line.lineNumber = lineNumber++; + result.push(line); + } + + return result; +} + +/** + * Computes diff statistics. + */ +function computeStats(diffLines: DiffLine[]): { added: number; removed: number } { + let added = 0; + let removed = 0; + + for (const line of diffLines) { + if (line.type === 'added') added++; + if (line.type === 'removed') removed++; + } + + return { added, removed }; +} + +// ============================================================================= +// Language Detection +// ============================================================================= + +const EXTENSION_LANGUAGE_MAP: Record = { + // JavaScript/TypeScript + '.ts': 'typescript', + '.tsx': 'tsx', + '.js': 'javascript', + '.jsx': 'jsx', + '.mjs': 'javascript', + '.cjs': 'javascript', + + // Python + '.py': 'python', + '.pyw': 'python', + '.pyx': 'python', + + // Web + '.html': 'html', + '.htm': 'html', + '.css': 'css', + '.scss': 'scss', + '.sass': 'sass', + '.less': 'less', + + // Data formats + '.json': 'json', + '.jsonl': 'json', + '.yaml': 'yaml', + '.yml': 'yaml', + '.toml': 'toml', + '.xml': 'xml', + + // Shell + '.sh': 'bash', + '.bash': 'bash', + '.zsh': 'zsh', + '.fish': 'fish', + + // Systems + '.rs': 'rust', + '.go': 'go', + '.c': 'c', + '.h': 'c', + '.cpp': 'cpp', + '.cc': 'cpp', + '.hpp': 'hpp', + '.java': 'java', + '.kt': 'kotlin', + '.swift': 'swift', + + // Config + '.env': 'env', + '.gitignore': 'gitignore', + '.dockerignore': 'dockerignore', + '.md': 'markdown', + '.mdx': 'mdx', + + // Other + '.sql': 'sql', + '.graphql': 'graphql', + '.gql': 'graphql', + '.vue': 'vue', + '.svelte': 'svelte', + '.rb': 'ruby', + '.php': 'php', + '.lua': 'lua', + '.r': 'r', + '.R': 'r', +}; + +/** + * Infer language from file name/extension. + */ +function inferLanguage(fileName: string): string { + // Check for dotfiles with specific names + const baseName = getBaseName(fileName); + if (baseName === 'Dockerfile') return 'dockerfile'; + if (baseName === 'Makefile') return 'makefile'; + if (baseName.startsWith('.env')) return 'env'; + + // Extract extension + const extMatch = /(\.[^./]+)$/.exec(fileName); + if (extMatch) { + const ext = extMatch[1].toLowerCase(); + return EXTENSION_LANGUAGE_MAP[ext] ?? 'text'; + } + + return 'text'; +} + +// ============================================================================= +// Diff Line Component +// ============================================================================= + +interface DiffLineRowProps { + line: DiffLine; +} + +const DiffLineRow: React.FC = ({ line }): React.JSX.Element => { + // Theme-aware styles using CSS variables + const getStyles = ( + type: DiffLine['type'] + ): { bg: string; text: string; border: string; prefix: string } => { + switch (type) { + case 'removed': + return { + bg: DIFF_REMOVED_BG, + text: DIFF_REMOVED_TEXT, + border: DIFF_REMOVED_BORDER, + prefix: '-', + }; + case 'added': + return { + bg: DIFF_ADDED_BG, + text: DIFF_ADDED_TEXT, + border: DIFF_ADDED_BORDER, + prefix: '+', + }; + default: + return { + bg: 'transparent', + text: COLOR_TEXT_SECONDARY, + border: 'transparent', + prefix: ' ', + }; + } + }; + + const style = getStyles(line.type); + + return ( +
    + {/* Line number */} + + {line.lineNumber} + + {/* Prefix */} + + {style.prefix} + + {/* Content */} + + {line.content || ' '} + +
    + ); +}; + +// ============================================================================= +// Main Component +// ============================================================================= + +export const DiffViewer: React.FC = ({ + fileName, + oldString, + newString, + maxHeight = 'max-h-96', + tokenCount, +}): React.JSX.Element => { + // Compute diff + const oldLines = oldString.split('\n'); + const newLines = newString.split('\n'); + const diffLines = generateDiff(oldLines, newLines); + const stats = computeStats(diffLines); + + // Infer language from file extension + const detectedLanguage = inferLanguage(fileName); + + // Format summary + const displayName = getBaseName(fileName); + + return ( +
    + {/* Header - matches CodeBlockViewer style */} +
    + + + {displayName} + + + {detectedLanguage} + + - + + {stats.added > 0 && ( + + +{stats.added} + + )} + {stats.removed > 0 && -{stats.removed}} + {stats.added === 0 && stats.removed === 0 && ( + Changed + )} + + {tokenCount !== undefined && tokenCount > 0 && ( + + ~{formatTokens(tokenCount)} tokens + + )} +
    + + {/* Diff content */} +
    +
    + {diffLines.map((line, index) => ( + + ))} + {diffLines.length === 0 && ( +
    + No changes detected +
    + )} +
    +
    +
    + ); +}; diff --git a/src/renderer/components/chat/viewers/MarkdownViewer.tsx b/src/renderer/components/chat/viewers/MarkdownViewer.tsx new file mode 100644 index 00000000..80b5e52e --- /dev/null +++ b/src/renderer/components/chat/viewers/MarkdownViewer.tsx @@ -0,0 +1,339 @@ +import React from 'react'; +import ReactMarkdown, { type Components } from 'react-markdown'; + +import { CopyButton } from '@renderer/components/common/CopyButton'; +import { + CODE_BG, + CODE_BORDER, + CODE_HEADER_BG, + COLOR_TEXT, + COLOR_TEXT_MUTED, + COLOR_TEXT_SECONDARY, + PROSE_BLOCKQUOTE_BORDER, + PROSE_BODY, + PROSE_CODE_BG, + PROSE_CODE_TEXT, + PROSE_HEADING, + PROSE_LINK, + PROSE_MUTED, + PROSE_PRE_BG, + PROSE_PRE_BORDER, + PROSE_TABLE_BORDER, + PROSE_TABLE_HEADER_BG, +} from '@renderer/constants/cssVariables'; +import { useStore } from '@renderer/store'; +import { FileText } from 'lucide-react'; +import remarkGfm from 'remark-gfm'; +import { useShallow } from 'zustand/react/shallow'; + +import { + createSearchContext, + highlightSearchInChildren, + type SearchContext, +} from '../searchHighlightUtils'; + +// ============================================================================= +// Types +// ============================================================================= + +interface MarkdownViewerProps { + content: string; + maxHeight?: string; // e.g., "max-h-64" or "max-h-96" + className?: string; + label?: string; // Optional label like "Thinking", "Output", etc. + /** When provided, enables search term highlighting within the markdown */ + itemId?: string; + /** When true, shows a copy button (overlay when no label, inline in header when label exists) */ + copyable?: boolean; +} + +// ============================================================================= +// Component factories +// ============================================================================= + +function createViewerMarkdownComponents(searchCtx: SearchContext | null): Components { + const hl = (children: React.ReactNode): React.ReactNode => + searchCtx ? highlightSearchInChildren(children, searchCtx) : children; + + return { + // Headings + h1: ({ children }) => ( +

    + {hl(children)} +

    + ), + h2: ({ children }) => ( +

    + {hl(children)} +

    + ), + h3: ({ children }) => ( +

    + {hl(children)} +

    + ), + h4: ({ children }) => ( +

    + {hl(children)} +

    + ), + h5: ({ children }) => ( +
    + {hl(children)} +
    + ), + h6: ({ children }) => ( +
    + {hl(children)} +
    + ), + + // Paragraphs + p: ({ children }) => ( +

    + {hl(children)} +

    + ), + + // Links — inline element, no hl(); parent block element's hl() descends here + a: ({ href, children }) => ( + { + e.preventDefault(); + if (href) { + void window.electronAPI.openExternal(href); + } + }} + > + {children} + + ), + + // Strong/Bold — inline element, no hl() + strong: ({ children }) => ( + + {children} + + ), + + // Emphasis/Italic — inline element, no hl() + em: ({ children }) => ( + + {children} + + ), + + // Strikethrough — inline element, no hl() + del: ({ children }) => ( + + {children} + + ), + + // Code: inline vs block detection + code: (props) => { + const { + className: codeClassName, + children, + node, + } = props as { + className?: string; + children?: React.ReactNode; + node?: { position?: { start: { line: number }; end: { line: number } } }; + }; + const hasLanguage = codeClassName?.includes('language-'); + const isMultiLine = + (node?.position && node.position.end.line > node.position.start.line) ?? false; + const isBlock = (hasLanguage ?? false) || isMultiLine; + + if (isBlock) { + return ( + + {hl(children)} + + ); + } + // Inline code — no hl(); parent block element's hl() descends here + return ( + + {children} + + ); + }, + + // Code blocks + pre: ({ children }) => ( +
    +        {children}
    +      
    + ), + + // Blockquotes + blockquote: ({ children }) => ( +
    + {hl(children)} +
    + ), + + // Lists + ul: ({ children }) => ( +
      + {children} +
    + ), + ol: ({ children }) => ( +
      + {children} +
    + ), + li: ({ children }) => ( +
  5. + {hl(children)} +
  6. + ), + + // Tables + table: ({ children }) => ( +
    + + {children} +
    +
    + ), + thead: ({ children }) => ( + {children} + ), + th: ({ children }) => ( + + {hl(children)} + + ), + td: ({ children }) => ( + + {hl(children)} + + ), + + // Horizontal rule + hr: () =>
    , + }; +} + +/** Default components without search highlighting */ +const defaultComponents = createViewerMarkdownComponents(null); + +// ============================================================================= +// Component +// ============================================================================= + +export const MarkdownViewer: React.FC = ({ + content, + maxHeight = 'max-h-96', + className = '', + label, + itemId, + copyable = false, +}) => { + // Only subscribe to search store when itemId is provided + const { searchQuery, searchMatches, currentSearchIndex } = useStore( + useShallow((s) => ({ + searchQuery: itemId ? s.searchQuery : '', + searchMatches: itemId ? s.searchMatches : [], + currentSearchIndex: itemId ? s.currentSearchIndex : -1, + })) + ); + + // Create search context (fresh each render so counter starts at 0) + const searchCtx = + searchQuery && itemId + ? createSearchContext(searchQuery, itemId, searchMatches, currentSearchIndex) + : null; + + // Create markdown components with optional search highlighting + // When search is active, create fresh each render (match counter is stateful and must start at 0) + // useMemo would cache stale closures when parent re-renders without search deps changing + const components = searchCtx ? createViewerMarkdownComponents(searchCtx) : defaultComponents; + + return ( +
    + {/* Copy button overlay (when no label header) */} + {copyable && !label && } + + {/* Optional header - matches CodeBlockViewer style */} + {label && ( +
    + + + {label} + + {copyable && ( + <> + + + + )} +
    + )} + + {/* Markdown content with scroll */} +
    +
    + + {content} + +
    +
    +
    + ); +}; diff --git a/src/renderer/components/chat/viewers/index.ts b/src/renderer/components/chat/viewers/index.ts new file mode 100644 index 00000000..bfc60a1a --- /dev/null +++ b/src/renderer/components/chat/viewers/index.ts @@ -0,0 +1,3 @@ +export { CodeBlockViewer } from './CodeBlockViewer'; +export { DiffViewer } from './DiffViewer'; +export { MarkdownViewer } from './MarkdownViewer'; diff --git a/src/renderer/components/chat/viewers/syntaxHighlighter.ts b/src/renderer/components/chat/viewers/syntaxHighlighter.ts new file mode 100644 index 00000000..2271d08b --- /dev/null +++ b/src/renderer/components/chat/viewers/syntaxHighlighter.ts @@ -0,0 +1,373 @@ +import React from 'react'; + +// ============================================================================= +// Syntax Highlighting (Basic Token-based) +// ============================================================================= + +// Basic keyword sets for common languages +const KEYWORDS: Record> = { + typescript: new Set([ + 'import', + 'export', + 'from', + 'const', + 'let', + 'var', + 'function', + 'class', + 'interface', + 'type', + 'enum', + 'return', + 'if', + 'else', + 'for', + 'while', + 'do', + 'switch', + 'case', + 'break', + 'continue', + 'try', + 'catch', + 'finally', + 'throw', + 'new', + 'this', + 'super', + 'extends', + 'implements', + 'async', + 'await', + 'public', + 'private', + 'protected', + 'static', + 'readonly', + 'abstract', + 'as', + 'typeof', + 'instanceof', + 'in', + 'of', + 'keyof', + 'void', + 'never', + 'unknown', + 'any', + 'null', + 'undefined', + 'true', + 'false', + 'default', + ]), + javascript: new Set([ + 'import', + 'export', + 'from', + 'const', + 'let', + 'var', + 'function', + 'class', + 'return', + 'if', + 'else', + 'for', + 'while', + 'do', + 'switch', + 'case', + 'break', + 'continue', + 'try', + 'catch', + 'finally', + 'throw', + 'new', + 'this', + 'super', + 'extends', + 'async', + 'await', + 'typeof', + 'instanceof', + 'in', + 'of', + 'void', + 'null', + 'undefined', + 'true', + 'false', + 'default', + ]), + python: new Set([ + 'import', + 'from', + 'as', + 'def', + 'class', + 'return', + 'if', + 'elif', + 'else', + 'for', + 'while', + 'break', + 'continue', + 'try', + 'except', + 'finally', + 'raise', + 'with', + 'as', + 'pass', + 'lambda', + 'yield', + 'global', + 'nonlocal', + 'assert', + 'and', + 'or', + 'not', + 'in', + 'is', + 'True', + 'False', + 'None', + 'async', + 'await', + 'self', + 'cls', + ]), + rust: new Set([ + 'fn', + 'let', + 'mut', + 'const', + 'static', + 'struct', + 'enum', + 'impl', + 'trait', + 'pub', + 'mod', + 'use', + 'crate', + 'self', + 'super', + 'where', + 'for', + 'loop', + 'while', + 'if', + 'else', + 'match', + 'return', + 'break', + 'continue', + 'move', + 'ref', + 'as', + 'in', + 'unsafe', + 'async', + 'await', + 'dyn', + 'true', + 'false', + 'type', + 'extern', + ]), + go: new Set([ + 'package', + 'import', + 'func', + 'var', + 'const', + 'type', + 'struct', + 'interface', + 'map', + 'chan', + 'go', + 'defer', + 'return', + 'if', + 'else', + 'for', + 'range', + 'switch', + 'case', + 'default', + 'break', + 'continue', + 'fallthrough', + 'select', + 'nil', + 'true', + 'false', + ]), +}; + +// Extend tsx/jsx to use typescript/javascript keywords +KEYWORDS.tsx = KEYWORDS.typescript; +KEYWORDS.jsx = KEYWORDS.javascript; + +/** + * Very basic tokenization for syntax highlighting. + * This is a simple approach without a full parser. + */ +export function highlightLine(line: string, language: string): React.ReactNode[] { + const keywords = KEYWORDS[language] || new Set(); + + // If no highlighting support, return plain text as single-element array + if (keywords.size === 0 && !['json', 'css', 'html', 'bash', 'markdown'].includes(language)) { + return [line]; + } + + const segments: React.ReactNode[] = []; + let currentPos = 0; + const lineLength = line.length; + + while (currentPos < lineLength) { + const remaining = line.slice(currentPos); + + // Check for string (double quote) + if (remaining.startsWith('"')) { + const endQuote = remaining.indexOf('"', 1); + if (endQuote !== -1) { + const str = remaining.slice(0, endQuote + 1); + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-string)' } }, + str + ) + ); + currentPos += str.length; + continue; + } + } + + // Check for string (single quote) + if (remaining.startsWith("'")) { + const endQuote = remaining.indexOf("'", 1); + if (endQuote !== -1) { + const str = remaining.slice(0, endQuote + 1); + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-string)' } }, + str + ) + ); + currentPos += str.length; + continue; + } + } + + // Check for template literal (backtick) + if (remaining.startsWith('`')) { + const endQuote = remaining.indexOf('`', 1); + if (endQuote !== -1) { + const str = remaining.slice(0, endQuote + 1); + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-string)' } }, + str + ) + ); + currentPos += str.length; + continue; + } + } + + // Check for comment (// style) + if (remaining.startsWith('//')) { + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-comment)', fontStyle: 'italic' } }, + remaining + ) + ); + break; + } + + // Check for comment (# style for Python/Shell) + if ((language === 'python' || language === 'bash') && remaining.startsWith('#')) { + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-comment)', fontStyle: 'italic' } }, + remaining + ) + ); + break; + } + + // Check for numbers + const numberMatch = /^(\d+\.?\d*)/.exec(remaining); + if (numberMatch && (currentPos === 0 || /\W/.test(line[currentPos - 1]))) { + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-number)' } }, + numberMatch[1] + ) + ); + currentPos += numberMatch[1].length; + continue; + } + + // Check for keywords and identifiers + const wordMatch = /^([a-zA-Z_$][a-zA-Z0-9_$]*)/.exec(remaining); + if (wordMatch) { + const word = wordMatch[1]; + if (keywords.has(word)) { + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-keyword)', fontWeight: 500 } }, + word + ) + ); + } else if ((word[0]?.toUpperCase() ?? '') === word[0] && word.length > 1) { + // Likely a type/class name + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-type)' } }, + word + ) + ); + } else { + segments.push(word); + } + currentPos += word.length; + continue; + } + + // Check for operators and punctuation + const opMatch = /^([=<>!+\-*/%&|^~?:;,.{}()[\]])/.exec(remaining); + if (opMatch) { + segments.push( + React.createElement( + 'span', + { key: currentPos, style: { color: 'var(--syntax-operator)' } }, + opMatch[1] + ) + ); + currentPos += 1; + continue; + } + + // Default: just add the character + segments.push(remaining[0]); + currentPos += 1; + } + + return segments; +} diff --git a/src/renderer/components/common/CopyButton.tsx b/src/renderer/components/common/CopyButton.tsx new file mode 100644 index 00000000..63b6917d --- /dev/null +++ b/src/renderer/components/common/CopyButton.tsx @@ -0,0 +1,78 @@ +import React, { useState } from 'react'; + +import { Check, Copy } from 'lucide-react'; + +interface CopyButtonProps { + /** Text to copy to clipboard */ + text: string; + /** Background color the gradient fades into (must match parent surface) */ + bgColor?: string; + /** Render as inline element instead of absolute overlay */ + inline?: boolean; +} + +/** + * Copy-to-clipboard button with two modes: + * + * **Overlay** (default): Absolute-positioned in top-right corner, visible on + * group hover. A horizontal gradient fades from transparent to `bgColor` so + * text behind the button isn't abruptly covered. + * Requires an ancestor with `group` and `relative` classes. + * + * **Inline** (`inline`): Normal-flow button for use inside headers/toolbars. + */ +export const CopyButton: React.FC = ({ + text, + bgColor = 'var(--code-bg)', + inline = false, +}) => { + const [isCopied, setIsCopied] = useState(false); + + const handleCopy = async (): Promise => { + try { + await navigator.clipboard.writeText(text); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch { + // Silently fail — clipboard API may be unavailable + } + }; + + const icon = isCopied ? ( + + ) : ( + + ); + + if (inline) { + return ( + + ); + } + + return ( +
    + {/* Gradient fade from transparent to bgColor so text isn't obscured */} +
    + {/* Solid background holding the button */} +
    + +
    +
    + ); +}; diff --git a/src/renderer/components/common/CopyablePath.tsx b/src/renderer/components/common/CopyablePath.tsx new file mode 100644 index 00000000..b6f1d587 --- /dev/null +++ b/src/renderer/components/common/CopyablePath.tsx @@ -0,0 +1,68 @@ +/** + * CopyablePath - Path display with copy-to-clipboard on hover. + * Click anywhere on the path row to copy the full absolute path. + * A small icon appears on hover as visual affordance. + */ + +import React, { useCallback, useState } from 'react'; + +import { Check, Copy } from 'lucide-react'; + +interface CopyablePathProps { + /** Shortened path for display */ + displayText: string; + /** Full absolute path for clipboard */ + copyText: string; + /** CSS classes for the text span */ + className?: string; + /** Inline style for the text span */ + style?: React.CSSProperties; +} + +export const CopyablePath = ({ + displayText, + copyText, + className = '', + style, +}: Readonly): React.ReactElement => { + const [copied, setCopied] = useState(false); + + const handleCopy = useCallback( + async (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + try { + await navigator.clipboard.writeText(copyText); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch { + // Clipboard API may not be available in all contexts + } + }, + [copyText] + ); + + return ( +
    { + if (e.key === 'Enter' || e.key === ' ') void handleCopy(e as unknown as React.MouseEvent); + }} + > + + {displayText} + + +
    + ); +}; diff --git a/src/renderer/components/common/ErrorBoundary.tsx b/src/renderer/components/common/ErrorBoundary.tsx new file mode 100644 index 00000000..bf885222 --- /dev/null +++ b/src/renderer/components/common/ErrorBoundary.tsx @@ -0,0 +1,109 @@ +import React, { Component, type ErrorInfo, type ReactNode } from 'react'; + +import { createLogger } from '@shared/utils/logger'; +import { AlertTriangle, RefreshCw } from 'lucide-react'; + +const logger = createLogger('Component:ErrorBoundary'); + +interface Props { + children: ReactNode; + fallback?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; + errorInfo: ErrorInfo | null; +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { + hasError: false, + error: null, + errorInfo: null, + }; + } + + static getDerivedStateFromError(error: Error): Partial { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + logger.error('ErrorBoundary caught an error:', error, errorInfo); + this.setState({ errorInfo }); + } + + handleReload = (): void => { + window.location.reload(); + }; + + handleReset = (): void => { + this.setState({ + hasError: false, + error: null, + errorInfo: null, + }); + }; + + // eslint-disable-next-line sonarjs/function-return-type -- Error boundaries inherently return different content based on error state + render(): ReactNode { + const { hasError, error, errorInfo } = this.state; + const { children, fallback } = this.props; + + if (hasError) { + if (fallback) { + return fallback; + } + + return ( +
    +
    + +

    Something went wrong

    +
    + +

    + An unexpected error occurred in the application. You can try reloading the page or + resetting the error state. +

    + + {error && ( +
    +

    {error.message}

    + {errorInfo?.componentStack && ( +
    + + Component Stack + +
    +                    {errorInfo.componentStack}
    +                  
    +
    + )} +
    + )} + +
    + + +
    +
    + ); + } + + return children; + } +} diff --git a/src/renderer/components/common/OngoingIndicator.tsx b/src/renderer/components/common/OngoingIndicator.tsx new file mode 100644 index 00000000..81245738 --- /dev/null +++ b/src/renderer/components/common/OngoingIndicator.tsx @@ -0,0 +1,67 @@ +/** + * OngoingIndicator - Pulsing green dot for sessions/groups in progress. + * Shared across SessionItem (sidebar) and LastOutputDisplay (chat). + */ + +import React from 'react'; + +import { Loader2 } from 'lucide-react'; + +interface OngoingIndicatorProps { + /** Size variant */ + size?: 'sm' | 'md'; + /** Whether to show text label */ + showLabel?: boolean; + /** Custom label text */ + label?: string; +} + +/** + * Pulsing green dot indicator for ongoing sessions. + * Use size="sm" for compact displays (sidebar), size="md" for larger displays (chat). + */ +export const OngoingIndicator = ({ + size = 'sm', + showLabel = false, + label = 'Session in progress...', +}: Readonly): React.JSX.Element => { + const dotSize = size === 'sm' ? 'h-2 w-2' : 'h-2.5 w-2.5'; + + return ( + + + + + + {showLabel && ( + + {label} + + )} + + ); +}; + +/** + * OngoingBanner - Full-width banner variant for the LastOutputDisplay. + * Shows animated spinner and text. + */ +export const OngoingBanner = (): React.JSX.Element => { + return ( +
    + + + Session is in progress... + +
    + ); +}; diff --git a/src/renderer/components/common/RepositoryDropdown.tsx b/src/renderer/components/common/RepositoryDropdown.tsx new file mode 100644 index 00000000..9ac81741 --- /dev/null +++ b/src/renderer/components/common/RepositoryDropdown.tsx @@ -0,0 +1,230 @@ +/** + * RepositoryDropdown - Dropdown for selecting repository groups. + * + * Features: + * - Shows repository groups (not individual worktrees) + * - Displays worktree count and total sessions + * - Click outside to close + * - Keyboard navigation (Escape to close) + * - Filter out already selected items + */ + +import React, { useEffect, useMemo, useRef, useState } from 'react'; + +import { useStore } from '@renderer/store'; +import { ChevronDown, FolderOpen, GitBranch } from 'lucide-react'; + +import type { RepositoryDropdownItem } from '@renderer/components/settings/hooks/useSettingsConfig'; + +interface RepositoryDropdownProps { + /** Callback when a repository is selected */ + onSelect: (item: RepositoryDropdownItem) => void; + /** IDs of items to exclude from the list */ + excludeIds?: string[]; + /** Placeholder text */ + placeholder?: string; + /** Whether the dropdown is disabled */ + disabled?: boolean; + /** Whether to drop up instead of down */ + dropUp?: boolean; + /** Custom class for the container */ + className?: string; +} + +export const RepositoryDropdown = ({ + onSelect, + excludeIds = [], + placeholder = 'Select repository...', + disabled = false, + dropUp = false, + className = '', +}: Readonly): React.JSX.Element => { + const [isOpen, setIsOpen] = useState(false); + const containerRef = useRef(null); + + // Get repository groups from store + const repositoryGroups = useStore((state) => state.repositoryGroups); + const fetchRepositoryGroups = useStore((state) => state.fetchRepositoryGroups); + + // Fetch data if not loaded + useEffect(() => { + if (repositoryGroups.length === 0) { + void fetchRepositoryGroups(); + } + }, [repositoryGroups.length, fetchRepositoryGroups]); + + // Convert repository groups to dropdown items + const allItems = useMemo((): RepositoryDropdownItem[] => { + return repositoryGroups.map((group) => ({ + id: group.id, + name: group.name, + path: group.worktrees[0]?.path ?? '', + worktreeCount: group.worktrees.length, + totalSessions: group.totalSessions, + })); + }, [repositoryGroups]); + + // Filter out excluded items + const availableItems = useMemo(() => { + return allItems.filter((item) => !excludeIds.includes(item.id)); + }, [allItems, excludeIds]); + + // Close dropdown on outside click + useEffect(() => { + const handleClickOutside = (event: MouseEvent): void => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [isOpen]); + + // Close on escape + useEffect(() => { + const handleEscape = (event: KeyboardEvent): void => { + if (event.key === 'Escape') { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + } + }, [isOpen]); + + const handleSelect = (item: RepositoryDropdownItem): void => { + onSelect(item); + setIsOpen(false); + }; + + const isEmpty = availableItems.length === 0; + + return ( +
    + {/* Trigger Button */} + + + {/* Dropdown Menu */} + {isOpen && !isEmpty && ( +
    + {availableItems.map((item) => ( + handleSelect(item)} + /> + ))} +
    + )} +
    + ); +}; + +/** + * Individual item in the dropdown. + */ +const RepositoryDropdownItemComponentInner = ({ + item, + onSelect, +}: Readonly<{ + item: RepositoryDropdownItem; + onSelect: () => void; +}>): React.JSX.Element => { + return ( + + ); +}; + +const RepositoryDropdownItemComponent = React.memo(RepositoryDropdownItemComponentInner); + +/** + * Selected repository item with remove button. + */ +const SelectedRepositoryItemInner = ({ + item, + onRemove, + disabled = false, +}: Readonly<{ + item: RepositoryDropdownItem; + onRemove: () => void; + disabled?: boolean; +}>): React.JSX.Element => { + return ( +
    + +
    +
    + {item.name} + {item.worktreeCount > 1 && ( + + + {item.worktreeCount} + + )} +
    + + {item.path} + +
    + +
    + ); +}; + +export const SelectedRepositoryItem = React.memo(SelectedRepositoryItemInner); diff --git a/src/renderer/components/common/TokenUsageDisplay.tsx b/src/renderer/components/common/TokenUsageDisplay.tsx new file mode 100644 index 00000000..d38690f7 --- /dev/null +++ b/src/renderer/components/common/TokenUsageDisplay.tsx @@ -0,0 +1,572 @@ +/** + * TokenUsageDisplay - Compact token usage display with detailed breakdown on hover. + * Shows total tokens with an info icon that reveals a popover with: + * - Input tokens breakdown + * - Cache read/write tokens + * - Output tokens + * - Optional model information + */ + +import React, { useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; +import { getModelColorClass } from '@shared/utils/modelParser'; +import { + formatTokensCompact as formatTokens, + formatTokensDetailed, +} from '@shared/utils/tokenFormatting'; +import { ChevronRight, Info } from 'lucide-react'; + +import type { ClaudeMdStats } from '@renderer/types/claudeMd'; +import type { ContextStats } from '@renderer/types/contextInjection'; +import type { ModelInfo } from '@shared/utils/modelParser'; + +interface TokenUsageDisplayProps { + /** Input tokens count */ + inputTokens: number; + /** Output tokens count */ + outputTokens: number; + /** Cache read tokens count */ + cacheReadTokens: number; + /** Cache creation/write tokens count */ + cacheCreationTokens: number; + /** Thinking tokens (extended thinking content) - estimated from content */ + thinkingTokens?: number; + /** Text output tokens (Claude's text responses) - estimated from content */ + textOutputTokens?: number; + /** Optional model name for display */ + modelName?: string; + /** Optional model family for color styling */ + modelFamily?: ModelInfo['family']; + /** Size variant - 'sm' for compact, 'md' for slightly larger */ + size?: 'sm' | 'md'; + /** Optional CLAUDE.md injection statistics (deprecated, use contextStats) */ + claudeMdStats?: ClaudeMdStats; + /** Optional unified context statistics */ + contextStats?: ContextStats; + /** Phase number for this AI group */ + phaseNumber?: number; + /** Total number of phases in the session */ + totalPhases?: number; +} + +/** + * Expandable section showing session-wide context breakdown. + * Shows accumulated totals for CLAUDE.md, mentioned files, tool outputs, and thinking+text. + */ +const SessionContextSection = ({ + contextStats, + totalTokens, + thinkingTokens = 0, + textOutputTokens = 0, +}: Readonly<{ + contextStats: ContextStats; + totalTokens: number; + thinkingTokens?: number; + textOutputTokens?: number; +}>): React.JSX.Element => { + const [expanded, setExpanded] = useState(false); + + const { tokensByCategory } = contextStats; + + // Calculate combined thinking+text tokens and include in context total + const thinkingTextTokens = thinkingTokens + textOutputTokens; + const adjustedContextTotal = contextStats.totalEstimatedTokens + thinkingTextTokens; + const contextPercent = + totalTokens > 0 ? Math.min((adjustedContextTotal / totalTokens) * 100, 100).toFixed(1) : '0.0'; + + // Count accumulated injections by category + const claudeMdCount = contextStats.accumulatedInjections.filter( + (inj) => inj.category === 'claude-md' + ).length; + const mentionedFilesCount = contextStats.accumulatedInjections.filter( + (inj) => inj.category === 'mentioned-file' + ).length; + const toolOutputsCount = contextStats.accumulatedInjections.filter( + (inj) => inj.category === 'tool-output' + ).length; + const taskCoordinationCount = contextStats.accumulatedInjections.filter( + (inj) => inj.category === 'task-coordination' + ).length; + const userMessagesCount = contextStats.accumulatedInjections.filter( + (inj) => inj.category === 'user-message' + ).length; + + // Calculate percentages for each category + const claudeMdPercent = + totalTokens > 0 + ? Math.min((tokensByCategory.claudeMd / totalTokens) * 100, 100).toFixed(1) + : '0.0'; + const mentionedFilesPercent = + totalTokens > 0 + ? Math.min((tokensByCategory.mentionedFiles / totalTokens) * 100, 100).toFixed(1) + : '0.0'; + const toolOutputsPercent = + totalTokens > 0 + ? Math.min((tokensByCategory.toolOutputs / totalTokens) * 100, 100).toFixed(1) + : '0.0'; + const thinkingTextPercent = + totalTokens > 0 ? Math.min((thinkingTextTokens / totalTokens) * 100, 100).toFixed(1) : '0.0'; + const taskCoordinationPercent = + totalTokens > 0 + ? Math.min((tokensByCategory.taskCoordination / totalTokens) * 100, 100).toFixed(1) + : '0.0'; + const userMessagesPercent = + totalTokens > 0 + ? Math.min((tokensByCategory.userMessages / totalTokens) * 100, 100).toFixed(1) + : '0.0'; + + return ( +
    + {/* Divider */} +
    + + {/* Header - clickable to expand */} +
    setExpanded(!expanded)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setExpanded(!expanded); + } + }} + > +
    + + Visible Context +
    + + {formatTokens(adjustedContextTotal)} ({contextPercent}%) + +
    + + {/* Expanded details */} + {expanded && ( +
    + {/* CLAUDE.md */} + {tokensByCategory.claudeMd > 0 && ( +
    + + CLAUDE.md ×{claudeMdCount} + + + {formatTokens(tokensByCategory.claudeMd)}{' '} + ({claudeMdPercent}%) + +
    + )} + + {/* Mentioned Files */} + {tokensByCategory.mentionedFiles > 0 && ( +
    + + @files ×{mentionedFilesCount} + + + {formatTokens(tokensByCategory.mentionedFiles)}{' '} + ({mentionedFilesPercent}%) + +
    + )} + + {/* Tool Outputs */} + {tokensByCategory.toolOutputs > 0 && ( +
    + + Tool Outputs ×{toolOutputsCount} + + + {formatTokens(tokensByCategory.toolOutputs)}{' '} + ({toolOutputsPercent}%) + +
    + )} + + {/* Task Coordination */} + {tokensByCategory.taskCoordination > 0 && ( +
    + + Task Coordination ×{taskCoordinationCount} + + + {formatTokens(tokensByCategory.taskCoordination)}{' '} + ({taskCoordinationPercent}%) + +
    + )} + + {/* User Messages */} + {tokensByCategory.userMessages > 0 && ( +
    + + User Messages ×{userMessagesCount} + + + {formatTokens(tokensByCategory.userMessages)}{' '} + ({userMessagesPercent}%) + +
    + )} + + {/* Thinking + Text */} + {thinkingTextTokens > 0 && ( +
    + Thinking + Text + + {formatTokens(thinkingTextTokens)}{' '} + ({thinkingTextPercent}%) + +
    + )} + + {/* Hint about session scope */} +
    + Accumulated across entire session without duplication +
    +
    + )} +
    + ); +}; + +export const TokenUsageDisplay = ({ + inputTokens, + outputTokens, + cacheReadTokens, + cacheCreationTokens, + thinkingTokens = 0, + textOutputTokens = 0, + modelName, + modelFamily, + size = 'sm', + claudeMdStats, + contextStats, + phaseNumber, + totalPhases, +}: Readonly): React.JSX.Element => { + const totalTokens = inputTokens + cacheReadTokens + cacheCreationTokens + outputTokens; + const formattedTotal = formatTokens(totalTokens); + + // Size-based classes + const textSize = size === 'sm' ? 'text-xs' : 'text-sm'; + const iconSize = size === 'sm' ? 'w-3 h-3' : 'w-3.5 h-3.5'; + + // Model color based on family + const modelColorClass = modelFamily ? getModelColorClass(modelFamily) : ''; + + // Use React state for hover instead of CSS group-hover to avoid + // interference with parent components that also use the 'group' class + const [showPopover, setShowPopover] = useState(false); + const [popoverStyle, setPopoverStyle] = useState({}); + const [arrowStyle, setArrowStyle] = useState({}); + const containerRef = useRef(null); + const popoverRef = useRef(null); + const hideTimeoutRef = useRef | null>(null); + const isDraggingRef = useRef(false); + + // Clear timeout helper + const clearHideTimeout = (): void => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current); + hideTimeoutRef.current = null; + } + }; + + // Show popover immediately, clear any pending hide + const handleMouseEnter = (): void => { + clearHideTimeout(); + setShowPopover(true); + }; + + // Hide popover with delay (allows mouse to move to popover) + const handleMouseLeave = (): void => { + // Don't hide while dragging inside the popover + if (isDraggingRef.current) return; + clearHideTimeout(); + hideTimeoutRef.current = setTimeout(() => { + setShowPopover(false); + }, 150); + }; + + // Cleanup timeout on unmount and close on scroll + useEffect(() => { + return () => clearHideTimeout(); + }, []); + + // Close popover on scroll + useEffect(() => { + if (!showPopover) return; + + const handleScroll = (e: Event): void => { + // Don't close if scrolling inside the popover + if (popoverRef.current && e.target instanceof Node && popoverRef.current.contains(e.target)) { + return; + } + setShowPopover(false); + }; + + window.addEventListener('scroll', handleScroll, true); + return () => window.removeEventListener('scroll', handleScroll, true); + }, [showPopover]); + + // Calculate popover position based on trigger element + useEffect(() => { + if (showPopover && containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + const popoverWidth = 220; + const margin = 12; + + // Determine if popover should open left or right + const openLeft = rect.left + popoverWidth > viewportWidth - 20; + + // Determine if popover should open above or below + const spaceBelow = viewportHeight - rect.bottom - margin; + const spaceAbove = rect.top - margin; + const openAbove = spaceBelow < 200 && spaceAbove > spaceBelow; + + const maxHeight = Math.max(openAbove ? spaceAbove : spaceBelow, 120) - 8; + + queueMicrotask(() => { + setPopoverStyle({ + position: 'fixed', + ...(openAbove ? { bottom: viewportHeight - rect.top + 4 } : { top: rect.bottom + 4 }), + left: openLeft ? rect.right - popoverWidth : rect.left, + minWidth: 200, + maxWidth: 280, + maxHeight, + overflowY: 'auto', + zIndex: 99999, + }); + + setArrowStyle({ + position: 'absolute', + ...(openAbove + ? { + bottom: -4, + borderRight: '1px solid var(--color-border)', + borderBottom: '1px solid var(--color-border)', + borderLeft: 'none', + borderTop: 'none', + } + : { + top: -4, + borderLeft: '1px solid var(--color-border)', + borderTop: '1px solid var(--color-border)', + borderRight: 'none', + borderBottom: 'none', + }), + [openLeft ? 'right' : 'left']: 8, + width: 8, + height: 8, + transform: 'rotate(45deg)', + backgroundColor: 'var(--color-surface-raised)', + }); + }); + } + }, [showPopover]); + + return ( +
    + {formattedTotal} + {totalPhases && totalPhases > 1 && phaseNumber && ( + + Phase {phaseNumber}/{totalPhases} + + )} +
    { + // Don't close if focus moved into the popover + if (popoverRef.current?.contains(e.relatedTarget as Node)) return; + handleMouseLeave(); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setShowPopover(!showPopover); + } + }} + aria-expanded={showPopover} + aria-haspopup="true" + > + + {/* Popover - rendered via Portal to escape stacking context */} + {showPopover && + createPortal( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events -- tooltip uses mouse handlers for hover/drag behavior, not interactive +
    { + e.stopPropagation(); + isDraggingRef.current = true; + const handleMouseUp = (): void => { + isDraggingRef.current = false; + document.removeEventListener('mouseup', handleMouseUp); + }; + document.addEventListener('mouseup', handleMouseUp); + }} + onClick={(e) => e.stopPropagation()} + > + {/* Arrow pointer */} +
    + +
    + {/* Input Tokens */} +
    + Input Tokens + + {formatTokensDetailed(inputTokens)} + +
    + + {/* Cache Read */} +
    + Cache Read + + {formatTokensDetailed(cacheReadTokens)} + +
    + + {/* Cache Write/Creation */} +
    + Cache Write + + {formatTokensDetailed(cacheCreationTokens)} + +
    + + {/* Output Tokens */} +
    + Output Tokens + + {formatTokensDetailed(outputTokens)} + +
    + + {/* Divider before Total */} +
    + + {/* Total */} +
    + + Total + + + {formatTokensDetailed(totalTokens)} + +
    + + {/* Visible Context Breakdown - expandable section */} + {contextStats && + (contextStats.totalEstimatedTokens > 0 || + thinkingTokens > 0 || + textOutputTokens > 0) && ( + + )} + + {/* CLAUDE.md Breakdown - fallback when contextStats not provided (deprecated) */} + {!contextStats && claudeMdStats && ( +
    + + incl. CLAUDE.md ×{claudeMdStats.accumulatedCount} + + + {totalTokens > 0 + ? ((claudeMdStats.totalEstimatedTokens / totalTokens) * 100).toFixed(1) + : '0.0'} + % + +
    + )} + + {/* Model Info (optional) */} + {modelName && ( + <> +
    +
    + Model + + {modelName} + +
    + + )} +
    +
    , + document.body + )} +
    +
    + ); +}; diff --git a/src/renderer/components/common/WorktreeBadge.tsx b/src/renderer/components/common/WorktreeBadge.tsx new file mode 100644 index 00000000..ad6febc0 --- /dev/null +++ b/src/renderer/components/common/WorktreeBadge.tsx @@ -0,0 +1,118 @@ +/** + * WorktreeBadge - Displays a compact badge indicating the worktree source. + * Shows subtle, muted colors for each worktree type. + */ + +import { WORKTREE_BADGE_BG, WORKTREE_BADGE_TEXT } from '@renderer/constants/cssVariables'; + +import type { WorktreeSource } from '@renderer/types/data'; + +interface WorktreeBadgeProps { + source: WorktreeSource; + /** Whether this is the main worktree */ + isMain?: boolean; + /** Additional CSS classes */ + className?: string; +} + +/** + * Configuration for each worktree source type. + * Uses muted, subtle colors to avoid being too flashy. + */ +interface SourceConfig { + label: string; + bgColor: string; + textColor: string; +} + +// Muted color palette - all using zinc/neutral tones with subtle tints +const SOURCE_CONFIG: Record = { + 'vibe-kanban': { + label: 'Vibe', + bgColor: WORKTREE_BADGE_BG, // zinc-400 + textColor: WORKTREE_BADGE_TEXT, // zinc-400 + }, + conductor: { + label: 'Conductor', + bgColor: WORKTREE_BADGE_BG, + textColor: WORKTREE_BADGE_TEXT, + }, + 'auto-claude': { + label: 'Auto', + bgColor: WORKTREE_BADGE_BG, + textColor: WORKTREE_BADGE_TEXT, + }, + '21st': { + label: '21st', + bgColor: WORKTREE_BADGE_BG, + textColor: WORKTREE_BADGE_TEXT, + }, + 'claude-desktop': { + label: 'Desktop', + bgColor: WORKTREE_BADGE_BG, + textColor: WORKTREE_BADGE_TEXT, + }, + ccswitch: { + label: 'ccswitch', + bgColor: WORKTREE_BADGE_BG, + textColor: WORKTREE_BADGE_TEXT, + }, + git: { + label: '', + bgColor: 'transparent', + textColor: 'transparent', + }, + unknown: { + label: '', + bgColor: 'transparent', + textColor: 'transparent', + }, +}; + +// Default worktree badge config (not "Main" to avoid confusion with main branch) +const DEFAULT_CONFIG: SourceConfig = { + label: 'Default', + bgColor: 'rgba(82, 82, 91, 0.3)', // zinc-600 + textColor: '#71717a', // zinc-500 +}; + +export const WorktreeBadge = ({ + source, + isMain = false, + className = '', +}: Readonly): React.ReactElement | null => { + // Show Default badge if isMain is true (the default/primary worktree) + if (isMain) { + return ( + + {DEFAULT_CONFIG.label} + + ); + } + + const config = SOURCE_CONFIG[source]; + + // Don't render badge for standard git or unknown sources + if (source === 'git' || source === 'unknown' || !config.label) { + return null; + } + + return ( + + {config.label} + + ); +}; diff --git a/src/renderer/components/dashboard/DashboardView.tsx b/src/renderer/components/dashboard/DashboardView.tsx new file mode 100644 index 00000000..dd5bca27 --- /dev/null +++ b/src/renderer/components/dashboard/DashboardView.tsx @@ -0,0 +1,404 @@ +/** + * DashboardView - Main dashboard with "Productivity Luxury" aesthetic. + * Inspired by Linear, Vercel, and Raycast design patterns. + * Features: + * - Subtle spotlight gradient + * - Centralized command search with inline project filtering + * - Border-first project cards with minimal backgrounds + */ + +import React, { useEffect, useMemo, useState } from 'react'; + +import { useStore } from '@renderer/store'; +import { createLogger } from '@shared/utils/logger'; +import { useShallow } from 'zustand/react/shallow'; + +const logger = createLogger('Component:DashboardView'); +import { formatDistanceToNow } from 'date-fns'; +import { Command, FolderGit2, FolderOpen, GitBranch, Search } from 'lucide-react'; + +import type { RepositoryGroup } from '@renderer/types/data'; + +// ============================================================================= +// Command Search Input +// ============================================================================= + +interface CommandSearchProps { + value: string; + onChange: (value: string) => void; +} + +const CommandSearch = ({ value, onChange }: Readonly): React.JSX.Element => { + const [isFocused, setIsFocused] = useState(false); + const { openCommandPalette, selectedProjectId } = useStore( + useShallow((s) => ({ + openCommandPalette: s.openCommandPalette, + selectedProjectId: s.selectedProjectId, + })) + ); + + // Handle Cmd+K to open full command palette + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent): void => { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + openCommandPalette(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [openCommandPalette]); + + return ( +
    + {/* Search container with glow effect on focus */} +
    + + onChange(e.target.value)} + placeholder="Search projects..." + className="flex-1 bg-transparent text-sm text-text outline-none placeholder:text-text-muted" + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + /> + {/* Keyboard shortcut badge - opens full command palette */} + +
    +
    + ); +}; + +// ============================================================================= +// Repository Card +// ============================================================================= + +interface RepositoryCardProps { + repo: RepositoryGroup; + onClick: () => void; + isHighlighted?: boolean; +} + +/** + * Truncate path to show ~/relative/path format + */ +function formatProjectPath(path: string): string { + const p = path.replace(/\\/g, '/'); + + if (p.startsWith('/Users/') || p.startsWith('/home/')) { + const parts = p.split('/').filter(Boolean); + if (parts.length >= 2) { + const rest = parts.slice(2).join('/'); + return rest ? `~/${rest}` : '~'; + } + } + + if (isWindowsUserPath(path)) { + const parts = p.split('/').filter(Boolean); + if (parts.length >= 3) { + const rest = parts.slice(3).join('/'); + return rest ? `~/${rest}` : '~'; + } + } + + return p; +} + +function isWindowsUserPath(input: string): boolean { + if (input.length < 10) { + return false; + } + + const drive = input.charCodeAt(0); + const hasDriveLetter = + ((drive >= 65 && drive <= 90) || (drive >= 97 && drive <= 122)) && input[1] === ':'; + + return hasDriveLetter && input.startsWith('\\Users\\', 2); +} + +const RepositoryCard = ({ + repo, + onClick, + isHighlighted, +}: Readonly): React.JSX.Element => { + const lastActivity = repo.mostRecentSession + ? formatDistanceToNow(new Date(repo.mostRecentSession), { addSuffix: true }) + : 'No recent activity'; + + const worktreeCount = repo.worktrees.length; + const hasMultipleWorktrees = worktreeCount > 1; + + // Get the path from the first worktree + const projectPath = repo.worktrees[0]?.path || ''; + const formattedPath = formatProjectPath(projectPath); + + return ( + + ); +}; + +// ============================================================================= +// Ghost Card (New Project) +// ============================================================================= + +const NewProjectCard = (): React.JSX.Element => { + const { repositoryGroups, selectRepository } = useStore( + useShallow((s) => ({ + repositoryGroups: s.repositoryGroups, + selectRepository: s.selectRepository, + })) + ); + + const handleClick = async (): Promise => { + try { + const selectedPaths = await window.electronAPI.config.selectFolders(); + if (!selectedPaths || selectedPaths.length === 0) { + return; // User cancelled + } + + const selectedPath = selectedPaths[0]; + + // Match selected path against known repository worktrees + for (const repo of repositoryGroups) { + for (const worktree of repo.worktrees) { + if (worktree.path === selectedPath) { + selectRepository(repo.id); + return; + } + } + } + + // No match found - open the folder in file manager as fallback + const result = await window.electronAPI.openPath(selectedPath); + if (!result.success) { + logger.error('Failed to open folder:', result.error); + } + } catch (error) { + logger.error('Error selecting folder:', error); + } + }; + + return ( + + ); +}; + +// ============================================================================= +// Projects Grid +// ============================================================================= + +interface ProjectsGridProps { + searchQuery: string; + maxProjects?: number; +} + +const ProjectsGrid = ({ + searchQuery, + maxProjects = 12, +}: Readonly): React.JSX.Element => { + const { repositoryGroups, repositoryGroupsLoading, fetchRepositoryGroups, selectRepository } = + useStore( + useShallow((s) => ({ + repositoryGroups: s.repositoryGroups, + repositoryGroupsLoading: s.repositoryGroupsLoading, + fetchRepositoryGroups: s.fetchRepositoryGroups, + selectRepository: s.selectRepository, + })) + ); + + useEffect(() => { + if (repositoryGroups.length === 0) { + void fetchRepositoryGroups(); + } + }, [repositoryGroups.length, fetchRepositoryGroups]); + + // Filter projects based on search query + const filteredRepos = useMemo(() => { + if (!searchQuery.trim()) { + return repositoryGroups.slice(0, maxProjects); + } + + const query = searchQuery.toLowerCase().trim(); + return repositoryGroups + .filter((repo) => { + // Match by name + if (repo.name.toLowerCase().includes(query)) return true; + // Match by path + const path = repo.worktrees[0]?.path || ''; + if (path.toLowerCase().includes(query)) return true; + return false; + }) + .slice(0, maxProjects); + }, [repositoryGroups, searchQuery, maxProjects]); + + if (repositoryGroupsLoading) { + return ( +
    + {Array.from({ length: 8 }).map((_, i) => ( +
    + {/* Icon placeholder */} +
    + {/* Title placeholder */} +
    + {/* Path placeholder */} +
    + {/* Meta row placeholder */} +
    +
    +
    +
    +
    + ))} +
    + ); + } + + if (filteredRepos.length === 0 && searchQuery.trim()) { + return ( +
    +
    + +
    +

    No projects found

    +

    No matches for "{searchQuery}"

    +
    + ); + } + + if (repositoryGroups.length === 0) { + return ( +
    +
    + +
    +

    No projects found

    +

    ~/.claude/projects/

    +
    + ); + } + + return ( +
    + {filteredRepos.map((repo) => ( + selectRepository(repo.id)} + isHighlighted={!!searchQuery.trim()} + /> + ))} + {!searchQuery.trim() && } +
    + ); +}; + +// ============================================================================= +// Dashboard View +// ============================================================================= + +export const DashboardView = (): React.JSX.Element => { + const [searchQuery, setSearchQuery] = useState(''); + + return ( +
    + {/* Spotlight gradient background */} + + ); +}; diff --git a/src/renderer/components/layout/MiddlePanel.tsx b/src/renderer/components/layout/MiddlePanel.tsx new file mode 100644 index 00000000..8579bed3 --- /dev/null +++ b/src/renderer/components/layout/MiddlePanel.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { ChatHistory } from '../chat/ChatHistory'; +import { SearchBar } from '../search/SearchBar'; + +interface MiddlePanelProps { + /** Tab ID for per-tab state isolation (scroll position, etc.) */ + tabId?: string; +} + +export const MiddlePanel: React.FC = ({ tabId }) => { + return ( +
    + + +
    + ); +}; diff --git a/src/renderer/components/layout/PaneContainer.tsx b/src/renderer/components/layout/PaneContainer.tsx new file mode 100644 index 00000000..951c4686 --- /dev/null +++ b/src/renderer/components/layout/PaneContainer.tsx @@ -0,0 +1,152 @@ +/** + * PaneContainer - Horizontal flex container that renders panes side by side. + * Wraps children with @dnd-kit DndContext provider for tab drag-and-drop. + * + * DnD interactions: + * - Drag within same TabBar → reorder tabs (reorderTabInPane) + * - Drag to another pane's TabBar → move tab to target pane (moveTabToPane) + * - Drag to pane edge zone → create new split pane (moveTabToNewPane) + * - Drag last tab out of pane → source pane auto-closes + */ + +import { Fragment, useCallback, useState } from 'react'; + +import { + DndContext, + DragOverlay, + PointerSensor, + pointerWithin, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { useStore } from '@renderer/store'; + +import { PaneResizeHandle } from './PaneResizeHandle'; +import { PaneView } from './PaneView'; +import { DragOverlayTab } from './SortableTab'; + +import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core'; +import type { Tab } from '@renderer/types/tabs'; + +export const PaneContainer = (): React.JSX.Element => { + const panes = useStore((s) => s.paneLayout.panes); + + // Track the currently dragged tab for DragOverlay + const [activeTab, setActiveTab] = useState(null); + + // Configure pointer sensor with activation distance to avoid conflict with clicks + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 8, // 8px drag distance before activating + }, + }) + ); + + const handleDragStart = useCallback( + (event: DragStartEvent) => { + const { active } = event; + const data = active.data.current; + + if (data?.type === 'tab') { + const sourcePaneId = data.paneId as string; + const tabId = data.tabId as string; + + // Find the tab in the source pane + const pane = panes.find((p) => p.id === sourcePaneId); + const tab = pane?.tabs.find((t) => t.id === tabId); + if (tab) { + setActiveTab(tab); + } + } + }, + [panes] + ); + + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + const { active, over } = event; + + setActiveTab(null); + + if (!over || !active.data.current) return; + + const activeData = active.data.current; + const overData = over.data.current; + + if (activeData.type !== 'tab') return; + + const draggedTabId = activeData.tabId as string; + const sourcePaneId = activeData.paneId as string; + const state = useStore.getState(); + + // Case 1: Drop on a split-zone (edge of pane) → create new pane + if (overData?.type === 'split-zone') { + const targetPaneId = overData.paneId as string; + const side = overData.side as 'left' | 'right'; + state.moveTabToNewPane(draggedTabId, sourcePaneId, targetPaneId, side); + return; + } + + // Case 2: Drop on a tabbar (different pane) → move tab to that pane + if (overData?.type === 'tabbar') { + const targetPaneId = overData.paneId as string; + if (sourcePaneId !== targetPaneId) { + state.moveTabToPane(draggedTabId, sourcePaneId, targetPaneId); + } + return; + } + + // Case 3: Drop on another sortable tab + // This can mean either reorder within same pane or move to another pane's tab position + if (overData?.type === 'tab') { + const overTabId = overData.tabId as string; + const overPaneId = overData.paneId as string; + + if (sourcePaneId === overPaneId) { + // Reorder within the same pane + const pane = panes.find((p) => p.id === sourcePaneId); + if (!pane) return; + + const fromIndex = pane.tabs.findIndex((t) => t.id === draggedTabId); + const toIndex = pane.tabs.findIndex((t) => t.id === overTabId); + + if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) { + state.reorderTabInPane(sourcePaneId, fromIndex, toIndex); + } + } else { + // Move to another pane, inserting at the over tab's position + const targetPane = panes.find((p) => p.id === overPaneId); + if (!targetPane) return; + + const insertIndex = targetPane.tabs.findIndex((t) => t.id === overTabId); + state.moveTabToPane(draggedTabId, sourcePaneId, overPaneId, insertIndex); + } + } + }, + [panes] + ); + + return ( + +
    + {panes.map((pane, i) => ( + + {i > 0 && } + + + ))} +
    + + {/* Drag overlay - semi-transparent ghost of the dragged tab */} + + {activeTab ? : null} + +
    + ); +}; diff --git a/src/renderer/components/layout/PaneContent.tsx b/src/renderer/components/layout/PaneContent.tsx new file mode 100644 index 00000000..8abf16d5 --- /dev/null +++ b/src/renderer/components/layout/PaneContent.tsx @@ -0,0 +1,55 @@ +/** + * PaneContent - Renders tab content for a single pane. + * Uses CSS display-toggle to keep all tabs mounted (preserving state). + */ + +import { TabUIProvider } from '@renderer/contexts/TabUIContext'; + +import { DashboardView } from '../dashboard/DashboardView'; +import { NotificationsView } from '../notifications/NotificationsView'; +import { SettingsView } from '../settings/SettingsView'; + +import { SessionTabContent } from './SessionTabContent'; + +import type { Pane } from '@renderer/types/panes'; + +interface PaneContentProps { + pane: Pane; +} + +export const PaneContent = ({ pane }: PaneContentProps): React.JSX.Element => { + const activeTabId = pane.activeTabId; + + // Show default dashboard if no tabs are open in this pane + const showDefaultDashboard = !activeTabId && pane.tabs.length === 0; + + return ( +
    + {showDefaultDashboard && ( +
    + +
    + )} + + {pane.tabs.map((tab) => { + const isActive = tab.id === activeTabId; + return ( +
    + {tab.type === 'dashboard' && } + {tab.type === 'notifications' && } + {tab.type === 'settings' && } + {tab.type === 'session' && ( + + + + )} +
    + ); + })} +
    + ); +}; diff --git a/src/renderer/components/layout/PaneResizeHandle.tsx b/src/renderer/components/layout/PaneResizeHandle.tsx new file mode 100644 index 00000000..848f3e43 --- /dev/null +++ b/src/renderer/components/layout/PaneResizeHandle.tsx @@ -0,0 +1,84 @@ +/** + * PaneResizeHandle - Draggable divider between adjacent panes. + * Uses the same mouse-event pattern as Sidebar.tsx for resize. + */ + +import { useCallback, useEffect, useState } from 'react'; + +import { useStore } from '@renderer/store'; + +interface PaneResizeHandleProps { + leftPaneId: string; + rightPaneId: string; +} + +export const PaneResizeHandle = ({ leftPaneId }: PaneResizeHandleProps): React.JSX.Element => { + const [isResizing, setIsResizing] = useState(false); + const resizePanes = useStore((s) => s.resizePanes); + const paneLayout = useStore((s) => s.paneLayout); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!isResizing) return; + + // Calculate the new width fraction based on mouse position relative to container + const container = document.getElementById('pane-container'); + if (!container) return; + + const containerRect = container.getBoundingClientRect(); + const relativeX = e.clientX - containerRect.left; + const newFraction = relativeX / containerRect.width; + + // Calculate the cumulative width of all panes before the left pane + const leftPaneIndex = paneLayout.panes.findIndex((p) => p.id === leftPaneId); + if (leftPaneIndex === -1) return; + + let cumulativeWidth = 0; + for (let i = 0; i < leftPaneIndex; i++) { + cumulativeWidth += paneLayout.panes[i].widthFraction; + } + + const leftPaneNewWidth = newFraction - cumulativeWidth; + resizePanes(leftPaneId, leftPaneNewWidth); + }, + [isResizing, leftPaneId, paneLayout.panes, resizePanes] + ); + + const handleMouseUp = useCallback(() => { + setIsResizing(false); + }, []); + + useEffect(() => { + if (isResizing) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + }, [isResizing, handleMouseMove, handleMouseUp]); + + const handleMouseDown = (e: React.MouseEvent): void => { + e.preventDefault(); + setIsResizing(true); + }; + + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- resize handle requires mouse interaction +
    + ); +}; diff --git a/src/renderer/components/layout/PaneSplitDropZone.tsx b/src/renderer/components/layout/PaneSplitDropZone.tsx new file mode 100644 index 00000000..7b2c06a5 --- /dev/null +++ b/src/renderer/components/layout/PaneSplitDropZone.tsx @@ -0,0 +1,54 @@ +/** + * PaneSplitDropZone - Half-pane drop zones for creating new panes via tab drag. + * Covers the left or right half of the pane. When a tab is dragged over a half, + * a semi-transparent accent overlay highlights the target area. + */ + +import { useDroppable } from '@dnd-kit/core'; + +interface PaneSplitDropZoneProps { + paneId: string; + side: 'left' | 'right'; + isActive: boolean; +} + +export const PaneSplitDropZone = ({ + paneId, + side, + isActive, +}: PaneSplitDropZoneProps): React.JSX.Element => { + const { setNodeRef, isOver } = useDroppable({ + id: `split-${side}-${paneId}`, + data: { + type: 'split-zone', + paneId, + side, + }, + }); + + return ( +
    + {/* Semi-transparent overlay highlight when hovering */} + {isOver && ( +
    + )} +
    + ); +}; diff --git a/src/renderer/components/layout/PaneView.tsx b/src/renderer/components/layout/PaneView.tsx new file mode 100644 index 00000000..e06bc3cb --- /dev/null +++ b/src/renderer/components/layout/PaneView.tsx @@ -0,0 +1,88 @@ +/** + * PaneView - Single pane wrapper with focus management. + * Handles click-to-focus, visual focus indicator, width, + * and edge split drop zones for DnD. + */ + +import { useDndContext } from '@dnd-kit/core'; +import { useStore } from '@renderer/store'; +import { MAX_PANES } from '@renderer/types/panes'; +import { useShallow } from 'zustand/react/shallow'; + +import { PaneContent } from './PaneContent'; +import { PaneSplitDropZone } from './PaneSplitDropZone'; +import { TabBar } from './TabBar'; + +interface PaneViewProps { + paneId: string; +} + +export const PaneView = ({ paneId }: PaneViewProps): React.JSX.Element => { + const { pane, isFocused, paneCount, focusPane } = useStore( + useShallow((s) => ({ + pane: s.paneLayout.panes.find((p) => p.id === paneId), + isFocused: s.paneLayout.focusedPaneId === paneId, + paneCount: s.paneLayout.panes.length, + focusPane: s.focusPane, + })) + ); + + // Check if a drag is active to show/hide edge drop zones + const { active } = useDndContext(); + const isDragging = active !== null; + const canSplit = paneCount < MAX_PANES; + const showSplitZones = isDragging && canSplit; + + if (!pane) return
    ; + + const handleMouseDown = (): void => { + if (!isFocused) { + focusPane(paneId); + } + }; + + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- pane focus management requires mousedown +
    + {/* Focus indicator - accent border on top of focused pane's TabBar */} +
    1 + ? '2px solid var(--color-accent, #6366f1)' + : '2px solid transparent', + }} + > + +
    + + + + {/* Edge split drop zones - visible only during active drag when under MAX_PANES */} + + + + {/* Max pane indicator - shown during drag when at limit */} + {isDragging && !canSplit && ( +
    +
    + Maximum {MAX_PANES} panes reached +
    +
    + )} +
    + ); +}; diff --git a/src/renderer/components/layout/SessionTabContent.tsx b/src/renderer/components/layout/SessionTabContent.tsx new file mode 100644 index 00000000..3bf89e98 --- /dev/null +++ b/src/renderer/components/layout/SessionTabContent.tsx @@ -0,0 +1,102 @@ +/** + * SessionTabContent - Renders session content with loading/error states. + * Each session tab has its own instance to preserve state. + */ + +import { useEffect } from 'react'; + +import { useStore } from '@renderer/store'; +import { AlertCircle, RefreshCw } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import { MiddlePanel } from './MiddlePanel'; + +import type { Tab } from '@renderer/types/tabs'; + +export const SessionTabContent = ({ + tab, + isActive, +}: Readonly<{ tab: Tab; isActive: boolean }>): React.JSX.Element => { + const { fetchSessionDetail, closeTab, initTabUIState } = useStore( + useShallow((s) => ({ + fetchSessionDetail: s.fetchSessionDetail, + closeTab: s.closeTab, + initTabUIState: s.initTabUIState, + })) + ); + + // Read loading/error from per-tab data, falling back to global state + const { sessionDetailError, sessionDetailLoading } = useStore( + useShallow((s) => { + const td = s.tabSessionData[tab.id]; + return { + sessionDetailError: td?.sessionDetailError ?? s.sessionDetailError, + sessionDetailLoading: td?.sessionDetailLoading ?? s.sessionDetailLoading, + }; + }) + ); + + // Initialize per-tab UI state when this tab is first mounted + useEffect(() => { + initTabUIState(tab.id); + }, [tab.id, initTabUIState]); + + // Only show loading/error states when this tab is active + if (!isActive) { + return ( +
    + +
    + ); + } + + if (sessionDetailError) { + return ( +
    +
    + +

    Failed to load session

    +

    + {sessionDetailError} +

    +
    + + +
    +
    +
    + ); + } + + if (sessionDetailLoading) { + return ( +
    +
    +
    +

    Loading session...

    +
    +
    + ); + } + + return ( +
    + +
    + ); +}; diff --git a/src/renderer/components/layout/Sidebar.tsx b/src/renderer/components/layout/Sidebar.tsx new file mode 100644 index 00000000..6f8f5706 --- /dev/null +++ b/src/renderer/components/layout/Sidebar.tsx @@ -0,0 +1,120 @@ +/** + * Sidebar - Breadcrumb-style navigation with project/worktree hierarchy. + * + * Structure: + * - Fixed Header: Project selector (Row 1) + Worktree selector (Row 2, conditional) + * - Scrollable Body: Date-grouped session list + * - Resizable: Drag right edge to resize + * - Collapsible: Cmd+B to toggle (Notion-style) + * + * Provides clear hierarchy visibility: Project -> Worktree -> Session + */ + +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { useStore } from '@renderer/store'; +import { useShallow } from 'zustand/react/shallow'; + +import { DateGroupedSessions } from '../sidebar/DateGroupedSessions'; + +import { SidebarHeader } from './SidebarHeader'; + +const MIN_WIDTH = 200; +const MAX_WIDTH = 500; +const DEFAULT_WIDTH = 280; + +export const Sidebar = (): React.JSX.Element | null => { + const { projects, projectsLoading, fetchProjects, sidebarCollapsed } = useStore( + useShallow((s) => ({ + projects: s.projects, + projectsLoading: s.projectsLoading, + fetchProjects: s.fetchProjects, + sidebarCollapsed: s.sidebarCollapsed, + })) + ); + const [width, setWidth] = useState(DEFAULT_WIDTH); + const [isResizing, setIsResizing] = useState(false); + const sidebarRef = useRef(null); + + // Fetch projects on mount if not loaded + useEffect(() => { + if (projects.length === 0 && !projectsLoading) { + void fetchProjects(); + } + }, [projects.length, projectsLoading, fetchProjects]); + + // Handle mouse move during resize + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!isResizing) return; + + const newWidth = e.clientX; + if (newWidth >= MIN_WIDTH && newWidth <= MAX_WIDTH) { + setWidth(newWidth); + } + }, + [isResizing] + ); + + // Handle mouse up to stop resizing + const handleMouseUp = useCallback(() => { + setIsResizing(false); + }, []); + + // Add/remove event listeners for resize + useEffect(() => { + if (isResizing) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + }, [isResizing, handleMouseMove, handleMouseUp]); + + const handleResizeStart = (e: React.MouseEvent): void => { + e.preventDefault(); + setIsResizing(true); + }; + + // Collapsed state - sidebar is completely hidden (expand button is in TabBar) + if (sidebarCollapsed) { + return null; + } + + return ( +
    + {/* Sidebar header with project dropdown */} + + + {/* Date-grouped session list */} +
    + +
    + + {/* Resize handle */} +
    + ); +}; diff --git a/src/renderer/components/layout/SidebarHeader.tsx b/src/renderer/components/layout/SidebarHeader.tsx new file mode 100644 index 00000000..d2c12e48 --- /dev/null +++ b/src/renderer/components/layout/SidebarHeader.tsx @@ -0,0 +1,541 @@ +/** + * SidebarHeader - Linear-style header with project name and worktree selector. + * + * Layout (2 stacked horizontal bars): + * - Row 1: Project name (left-aligned after macOS traffic lights) + * - Row 2: Worktree selector (full-width button) + * + * Visual requirements: + * - Row 1 is the drag region for window movement + * - Row 1 reserves left space for macOS traffic lights via shared layout CSS variable + * - Row 2 is a full-width button with no side margins + */ + +import { useEffect, useRef, useState } from 'react'; + +import { HEADER_ROW1_HEIGHT, HEADER_ROW2_HEIGHT } from '@renderer/constants/layout'; +import { useStore } from '@renderer/store'; +import { truncateMiddle } from '@renderer/utils/stringUtils'; +import { Check, ChevronDown, GitBranch, PanelLeft } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import { WorktreeBadge } from '../common/WorktreeBadge'; + +import type { Worktree, WorktreeSource } from '@renderer/types/data'; + +/** + * Group worktrees by source for organized dropdown display. + * Returns: main worktree first, then groups sorted by most recent activity. + */ +interface WorktreeGroup { + source: WorktreeSource; + label: string; + worktrees: Worktree[]; + mostRecent: number; +} + +const SOURCE_LABELS: Record = { + 'vibe-kanban': 'Vibe Kanban', + conductor: 'Conductor', + 'auto-claude': 'Auto Claude', + '21st': '21st', + 'claude-desktop': 'Claude Desktop', + ccswitch: 'ccswitch', + git: 'Git', + unknown: 'Other', +}; + +function groupWorktreesBySource(worktrees: Worktree[]): { + mainWorktree: Worktree | null; + groups: WorktreeGroup[]; +} { + // Find main worktree + const mainWorktree = worktrees.find((w) => w.isMainWorktree) ?? null; + + // Group remaining worktrees by source + const groupMap = new Map(); + + for (const wt of worktrees) { + if (wt.isMainWorktree) continue; // Skip main, handled separately + + const existing = groupMap.get(wt.source) ?? []; + existing.push(wt); + groupMap.set(wt.source, existing); + } + + // Convert to array and sort each group internally by most recent + const groups: WorktreeGroup[] = []; + + for (const [source, wts] of groupMap) { + // Sort worktrees within group by most recent + const sorted = [...wts].sort((a, b) => (b.mostRecentSession ?? 0) - (a.mostRecentSession ?? 0)); + + const mostRecent = Math.max(...sorted.map((w) => w.mostRecentSession ?? 0)); + + groups.push({ + source, + label: SOURCE_LABELS[source] ?? source, + worktrees: sorted, + mostRecent, + }); + } + + // Sort groups by most recent activity + groups.sort((a, b) => b.mostRecent - a.mostRecent); + + return { mainWorktree, groups }; +} + +/** + * Individual worktree item in the dropdown. + */ +interface WorktreeItemProps { + worktree: Worktree; + isSelected: boolean; + onSelect: () => void; +} + +const WorktreeItem = ({ + worktree, + isSelected, + onSelect, +}: Readonly): React.JSX.Element => { + const [isHovered, setIsHovered] = useState(false); + + const buttonStyle: React.CSSProperties = isSelected + ? { backgroundColor: 'var(--color-surface-raised)', color: 'var(--color-text)' } + : { + backgroundColor: isHovered ? 'var(--color-surface-raised)' : 'transparent', + opacity: isHovered ? 0.5 : 1, + }; + + return ( + + ); +}; + +/** + * Individual project/repository item in the dropdown. + */ +interface ProjectDropdownItemProps { + name: string; + path?: string; + sessionCount: number; + isSelected: boolean; + onSelect: () => void; +} + +const ProjectDropdownItem = ({ + name, + path, + sessionCount, + isSelected, + onSelect, +}: Readonly): React.JSX.Element => { + const [isHovered, setIsHovered] = useState(false); + + const buttonStyle: React.CSSProperties = isSelected + ? { backgroundColor: 'var(--color-surface-raised)', color: 'var(--color-text)' } + : { + backgroundColor: isHovered ? 'var(--color-surface-raised)' : 'transparent', + opacity: isHovered ? 0.5 : 1, + }; + + return ( + + ); +}; + +export const SidebarHeader = (): React.JSX.Element => { + const { + repositoryGroups, + selectedRepositoryId, + selectedWorktreeId, + selectWorktree, + selectRepository, + viewMode, + projects, + activeProjectId, + setActiveProject, + fetchRepositoryGroups, + fetchProjects, + toggleSidebar, + } = useStore( + useShallow((s) => ({ + repositoryGroups: s.repositoryGroups, + selectedRepositoryId: s.selectedRepositoryId, + selectedWorktreeId: s.selectedWorktreeId, + selectWorktree: s.selectWorktree, + selectRepository: s.selectRepository, + viewMode: s.viewMode, + projects: s.projects, + activeProjectId: s.activeProjectId, + setActiveProject: s.setActiveProject, + fetchRepositoryGroups: s.fetchRepositoryGroups, + fetchProjects: s.fetchProjects, + toggleSidebar: s.toggleSidebar, + })) + ); + + // Fetch data on mount based on view mode + useEffect(() => { + if (viewMode === 'grouped' && repositoryGroups.length === 0) { + void fetchRepositoryGroups(); + } else if (viewMode === 'flat' && projects.length === 0) { + void fetchProjects(); + } + }, [viewMode, repositoryGroups.length, projects.length, fetchRepositoryGroups, fetchProjects]); + + const [isWorktreeDropdownOpen, setIsWorktreeDropdownOpen] = useState(false); + const [isProjectDropdownOpen, setIsProjectDropdownOpen] = useState(false); + const worktreeDropdownRef = useRef(null); + const projectDropdownRef = useRef(null); + + // Find the active repository and worktree + const activeRepo = repositoryGroups.find((r) => r.id === selectedRepositoryId); + const activeWorktree = activeRepo?.worktrees.find((w) => w.id === selectedWorktreeId); + // Filter worktrees to only show those with sessions + const worktrees = (activeRepo?.worktrees ?? []).filter((w) => w.sessions.length > 0); + const hasMultipleWorktrees = worktrees.length > 1; + + // Group worktrees by source for organized dropdown + const worktreeGroupingResult = groupWorktreesBySource(worktrees); + const mainWorktree = worktreeGroupingResult.mainWorktree; + const worktreeGroups = worktreeGroupingResult.groups; + + // For flat mode + const activeProject = projects.find((p) => p.id === activeProjectId); + + // Get display name + const projectName = + viewMode === 'grouped' + ? (activeRepo?.name ?? 'Select Project') + : (activeProject?.name ?? 'Select Project'); + + const worktreeName = activeWorktree?.name ?? 'main'; + const hasSelection = viewMode === 'grouped' ? !!activeRepo : !!activeProject; + + // Close dropdowns on outside click + useEffect(() => { + function handleClickOutside(event: MouseEvent): void { + if ( + worktreeDropdownRef.current && + !worktreeDropdownRef.current.contains(event.target as Node) + ) { + setIsWorktreeDropdownOpen(false); + } + if ( + projectDropdownRef.current && + !projectDropdownRef.current.contains(event.target as Node) + ) { + setIsProjectDropdownOpen(false); + } + } + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // Close on escape + useEffect(() => { + function handleEscape(event: KeyboardEvent): void { + if (event.key === 'Escape') { + setIsWorktreeDropdownOpen(false); + setIsProjectDropdownOpen(false); + } + } + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, []); + + const handleSelectWorktree = (worktree: Worktree): void => { + selectWorktree(worktree.id); + setIsWorktreeDropdownOpen(false); + }; + + const handleSelectRepo = (repoId: string): void => { + selectRepository(repoId); + setIsProjectDropdownOpen(false); + }; + + const handleSelectProject = (projectId: string): void => { + setActiveProject(projectId); + setIsProjectDropdownOpen(false); + }; + + // Items for project dropdown - filter out repositories/projects with 0 sessions + const projectItems = + viewMode === 'grouped' + ? repositoryGroups.filter((r) => r.totalSessions > 0) + : projects.filter((p) => p.sessions.length > 0); + + const [isCollapseHovered, setIsCollapseHovered] = useState(false); + + return ( +
    + {/* ROW 1: Project Identity (Title Bar / Drag Region) */} +
    + + + {/* Collapse sidebar button */} + + + {/* Project Dropdown */} + {isProjectDropdownOpen && ( + <> +
    setIsProjectDropdownOpen(false)} + /> +
    +
    + Switch {viewMode === 'grouped' ? 'Repository' : 'Project'} +
    + + {projectItems.length === 0 ? ( +
    + No {viewMode === 'grouped' ? 'repositories' : 'projects'} found +
    + ) : ( + projectItems.map((item) => { + const isSelected = + viewMode === 'grouped' + ? item.id === selectedRepositoryId + : item.id === activeProjectId; + const itemSessions = + viewMode === 'grouped' + ? (item as (typeof repositoryGroups)[0]).totalSessions + : (item as (typeof projects)[0]).sessions.length; + // Get path for display + const itemPath = + viewMode === 'grouped' + ? (item as (typeof repositoryGroups)[0]).worktrees[0]?.path + : (item as (typeof projects)[0]).path; + + return ( + + viewMode === 'grouped' + ? handleSelectRepo(item.id) + : handleSelectProject(item.id) + } + /> + ); + }) + )} +
    + + )} +
    + + {/* ROW 2: Worktree Selector (Full Width) */} + {viewMode === 'grouped' && activeRepo && ( +
    + + + {/* Worktree Dropdown */} + {isWorktreeDropdownOpen && hasMultipleWorktrees && ( + <> +
    setIsWorktreeDropdownOpen(false)} + /> +
    +
    + Switch Worktree +
    + + {/* Main worktree first */} + {mainWorktree && ( + handleSelectWorktree(mainWorktree)} + /> + )} + + {/* Grouped worktrees by source */} + {worktreeGroups.map((group) => ( +
    + {/* Group header */} +
    + {group.label} +
    + {/* Worktrees in group */} + {group.worktrees.map((worktree) => ( + handleSelectWorktree(worktree)} + /> + ))} +
    + ))} +
    + + )} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/layout/SortableTab.tsx b/src/renderer/components/layout/SortableTab.tsx new file mode 100644 index 00000000..1a9758c4 --- /dev/null +++ b/src/renderer/components/layout/SortableTab.tsx @@ -0,0 +1,160 @@ +/** + * SortableTab - A draggable tab item used within SortableContext. + * Wraps useSortable from @dnd-kit for tab reordering and cross-pane movement. + */ + +import { useCallback, useState } from 'react'; + +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { useStore } from '@renderer/store'; +import { Bell, FileText, LayoutDashboard, Pin, Search, Settings, X } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import type { Tab } from '@renderer/types/tabs'; + +interface SortableTabProps { + tab: Tab; + paneId: string; + isActive: boolean; + isSelected: boolean; + onTabClick: (tabId: string, e: React.MouseEvent) => void; + onMouseDown: (tabId: string, e: React.MouseEvent) => void; + onContextMenu: (tabId: string, e: React.MouseEvent) => void; + onClose: (tabId: string) => void; + setRef: (tabId: string, el: HTMLDivElement | null) => void; +} + +const TAB_ICONS = { + dashboard: LayoutDashboard, + notifications: Bell, + settings: Settings, + session: FileText, +} as const; + +export const SortableTab = ({ + tab, + paneId, + isActive, + isSelected, + onTabClick, + onMouseDown, + onContextMenu, + onClose, + setRef, +}: SortableTabProps): React.JSX.Element => { + const [isHovered, setIsHovered] = useState(false); + + const isPinned = useStore( + useShallow((s) => + tab.type === 'session' && tab.sessionId ? s.pinnedSessionIds.includes(tab.sessionId) : false + ) + ); + + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id: tab.id, + data: { + type: 'tab', + tabId: tab.id, + paneId, + }, + }); + + const style: React.CSSProperties = { + transform: CSS.Transform.toString(transform), + transition: isDragging ? 'none' : transition, + opacity: isDragging ? 0.3 : 1, + backgroundColor: isActive + ? 'var(--color-surface-raised)' + : isHovered + ? 'var(--color-surface-overlay)' + : 'transparent', + color: isActive || isHovered ? 'var(--color-text)' : 'var(--color-text-muted)', + outline: isSelected ? '1px solid var(--color-border-emphasis)' : 'none', + outlineOffset: '-1px', + }; + + const Icon = TAB_ICONS[tab.type]; + + const handleRef = useCallback( + (el: HTMLDivElement | null) => { + setNodeRef(el); + setRef(tab.id, el); + }, + [setNodeRef, setRef, tab.id] + ); + + return ( +
    onTabClick(tab.id, e)} + onMouseDown={(e) => onMouseDown(tab.id, e)} + onContextMenu={(e) => onContextMenu(tab.id, e)} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onTabClick(tab.id, e as unknown as React.MouseEvent); + } + }} + > + + {tab.fromSearch && ( + + + + )} + {isPinned && ( + + + + )} + {tab.label} + +
    + ); +}; + +/** + * DragOverlayTab - Semi-transparent ghost of a tab shown during drag. + */ +export const DragOverlayTab = ({ tab }: { tab: Tab }): React.JSX.Element => { + const Icon = TAB_ICONS[tab.type]; + + return ( +
    + + {tab.label} +
    + ); +}; diff --git a/src/renderer/components/layout/TabBar.tsx b/src/renderer/components/layout/TabBar.tsx new file mode 100644 index 00000000..fae7e16c --- /dev/null +++ b/src/renderer/components/layout/TabBar.tsx @@ -0,0 +1,430 @@ +/** + * TabBar - Displays open tabs with close buttons and action buttons. + * Accepts a paneId prop to scope to a specific pane's tabs. + * Supports tab switching, closing, horizontal scrolling on overflow, + * right-click context menu, middle-click to close, Shift/Ctrl+click multi-select, + * and drag-and-drop reordering/cross-pane movement via @dnd-kit. + * When sidebar is collapsed, shows expand button on the left with macOS traffic light spacing. + */ + +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { useDroppable } from '@dnd-kit/core'; +import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable'; +import { HEADER_ROW1_HEIGHT } from '@renderer/constants/layout'; +import { useStore } from '@renderer/store'; +import { Bell, PanelLeft, Plus, RefreshCw, Search, Settings } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import { SortableTab } from './SortableTab'; +import { TabContextMenu } from './TabContextMenu'; + +interface TabBarProps { + paneId: string; +} + +export const TabBar = ({ paneId }: TabBarProps): React.JSX.Element => { + const { + pane, + isFocused, + paneCount, + setActiveTab, + closeTab, + closeOtherTabs, + closeAllTabs, + closeTabs, + setSelectedTabIds, + clearTabSelection, + openDashboard, + fetchSessionDetail, + fetchSessions, + openCommandPalette, + unreadCount, + openNotificationsTab, + openSettingsTab, + sidebarCollapsed, + toggleSidebar, + splitPane, + togglePinSession, + pinnedSessionIds, + } = useStore( + useShallow((s) => ({ + pane: s.paneLayout.panes.find((p) => p.id === paneId), + isFocused: s.paneLayout.focusedPaneId === paneId, + paneCount: s.paneLayout.panes.length, + setActiveTab: s.setActiveTab, + closeTab: s.closeTab, + closeOtherTabs: s.closeOtherTabs, + closeAllTabs: s.closeAllTabs, + closeTabs: s.closeTabs, + setSelectedTabIds: s.setSelectedTabIds, + clearTabSelection: s.clearTabSelection, + openDashboard: s.openDashboard, + fetchSessionDetail: s.fetchSessionDetail, + fetchSessions: s.fetchSessions, + openCommandPalette: s.openCommandPalette, + unreadCount: s.unreadCount, + openNotificationsTab: s.openNotificationsTab, + openSettingsTab: s.openSettingsTab, + sidebarCollapsed: s.sidebarCollapsed, + toggleSidebar: s.toggleSidebar, + splitPane: s.splitPane, + togglePinSession: s.togglePinSession, + pinnedSessionIds: s.pinnedSessionIds, + })) + ); + + const openTabs = useMemo(() => pane?.tabs ?? [], [pane?.tabs]); + const activeTabId = pane?.activeTabId ?? null; + const selectedTabIds = useMemo(() => pane?.selectedTabIds ?? [], [pane?.selectedTabIds]); + + // Derive Set for O(1) lookups + const selectedSet = useMemo(() => new Set(selectedTabIds), [selectedTabIds]); + + // Derive stable tab IDs array for SortableContext + const tabIds = useMemo(() => openTabs.map((t) => t.id), [openTabs]); + + // Hover states for buttons + const [expandHover, setExpandHover] = useState(false); + const [refreshHover, setRefreshHover] = useState(false); + const [newTabHover, setNewTabHover] = useState(false); + const [searchHover, setSearchHover] = useState(false); + const [notificationsHover, setNotificationsHover] = useState(false); + const [settingsHover, setSettingsHover] = useState(false); + + // Context menu state + const [contextMenu, setContextMenu] = useState<{ x: number; y: number; tabId: string } | null>( + null + ); + + // Track last clicked tab for Shift range selection + const lastClickedTabIdRef = useRef(null); + + // Get the active tab + const activeTab = openTabs.find((tab) => tab.id === activeTabId); + + // Refs for auto-scrolling to active tab + const tabRefsMap = useRef>(new Map()); + const scrollContainerRef = useRef(null); + + // Make the tab bar area droppable for cross-pane drops + const { setNodeRef: setDroppableRef, isOver: isDroppableOver } = useDroppable({ + id: `tabbar-${paneId}`, + data: { + type: 'tabbar', + paneId, + }, + }); + + // Auto-scroll to active tab when it changes + useEffect(() => { + if (!activeTabId) return; + + const tabElement = tabRefsMap.current.get(activeTabId); + if (tabElement && scrollContainerRef.current) { + tabElement.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'nearest', + }); + } + }, [activeTabId]); + + // Clear selection on Escape + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent): void => { + if (e.key === 'Escape' && selectedTabIds.length > 0) { + clearTabSelection(); + } + }; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [selectedTabIds.length, clearTabSelection]); + + // Handle tab click with multi-select support + const handleTabClick = useCallback( + (tabId: string, e: React.MouseEvent) => { + const isMeta = e.metaKey || e.ctrlKey; + const isShift = e.shiftKey; + + if (isMeta) { + // Ctrl/Cmd+click: toggle tab in selection + if (selectedSet.has(tabId)) { + setSelectedTabIds(selectedTabIds.filter((id) => id !== tabId)); + } else { + setSelectedTabIds([...selectedTabIds, tabId]); + } + lastClickedTabIdRef.current = tabId; + return; + } + + if (isShift && lastClickedTabIdRef.current) { + // Shift+click: range selection from last clicked to current + const lastIndex = openTabs.findIndex((t) => t.id === lastClickedTabIdRef.current); + const currentIndex = openTabs.findIndex((t) => t.id === tabId); + if (lastIndex !== -1 && currentIndex !== -1) { + const start = Math.min(lastIndex, currentIndex); + const end = Math.max(lastIndex, currentIndex); + const rangeIds = openTabs.slice(start, end + 1).map((t) => t.id); + // Merge with existing selection + const merged = new Set([...selectedTabIds, ...rangeIds]); + setSelectedTabIds([...merged]); + } + return; + } + + // Plain click: clear selection, switch tab + clearTabSelection(); + lastClickedTabIdRef.current = tabId; + setActiveTab(tabId); + }, + [openTabs, selectedTabIds, selectedSet, setActiveTab, setSelectedTabIds, clearTabSelection] + ); + + // Middle-click to close + prevent text selection on Shift/Cmd click + const handleMouseDown = useCallback( + (tabId: string, e: React.MouseEvent) => { + if (e.button === 1) { + e.preventDefault(); + closeTab(tabId); + return; + } + // Prevent native text selection when Shift or Cmd/Ctrl clicking tabs + if (e.button === 0 && (e.shiftKey || e.metaKey || e.ctrlKey)) { + e.preventDefault(); + } + }, + [closeTab] + ); + + // Right-click context menu + const handleContextMenu = useCallback((tabId: string, e: React.MouseEvent) => { + e.preventDefault(); + setContextMenu({ x: e.clientX, y: e.clientY, tabId }); + }, []); + + // Handle refresh for active session tab + const handleRefresh = async (): Promise => { + if (activeTab?.type === 'session' && activeTab.projectId && activeTab.sessionId) { + await Promise.all([ + fetchSessionDetail(activeTab.projectId, activeTab.sessionId, activeTabId ?? undefined), + fetchSessions(activeTab.projectId), + ]); + } + }; + + // Ref setter for SortableTab + const setTabRef = useCallback((tabId: string, el: HTMLDivElement | null) => { + if (el) { + tabRefsMap.current.set(tabId, el); + } else { + tabRefsMap.current.delete(tabId); + } + }, []); + + // Context menu helpers + const contextMenuTabId = contextMenu?.tabId ?? null; + const effectiveSelectedCount = + contextMenuTabId && selectedSet.has(contextMenuTabId) ? selectedTabIds.length : 0; + + // Pin state for context menu tab + const contextMenuTab = contextMenuTabId ? openTabs.find((t) => t.id === contextMenuTabId) : null; + const isContextMenuTabSession = contextMenuTab?.type === 'session'; + const isContextMenuTabPinned = + isContextMenuTabSession && contextMenuTab?.sessionId + ? pinnedSessionIds.includes(contextMenuTab.sessionId) + : false; + + // Show sidebar expand button only in the leftmost pane + const isLeftmostPane = useStore( + (s) => s.paneLayout.panes.length === 0 || s.paneLayout.panes[0]?.id === paneId + ); + + return ( +
    + {/* Expand sidebar button - show when collapsed (only in leftmost pane) */} + {sidebarCollapsed && isLeftmostPane && ( + + )} + + {/* Tab list with horizontal scroll, sortable DnD, and droppable area */} +
    { + scrollContainerRef.current = el; + setDroppableRef(el); + }} + className="scrollbar-none flex min-w-0 flex-1 items-center gap-1 overflow-x-auto" + style={ + { + WebkitAppRegion: 'no-drag', + outline: isDroppableOver ? '1px dashed var(--color-accent, #6366f1)' : 'none', + outlineOffset: '-1px', + } as React.CSSProperties + } + > + + {openTabs.map((tab) => ( + + ))} + + + {/* Refresh button - show only for session tabs */} + {activeTab?.type === 'session' && ( + + )} +
    + + {/* Right side actions */} +
    + {/* New tab button */} + + + {/* Search button (icon only) */} + + + {/* Notifications bell icon */} + + + {/* Settings gear icon */} + +
    + + {/* Context menu */} + {contextMenu && contextMenuTabId && ( + setContextMenu(null)} + onCloseTab={() => closeTab(contextMenuTabId)} + onCloseOtherTabs={() => closeOtherTabs(contextMenuTabId)} + onCloseAllTabs={() => closeAllTabs()} + onCloseSelectedTabs={ + effectiveSelectedCount > 1 ? () => closeTabs([...selectedTabIds]) : undefined + } + onSplitRight={() => splitPane(paneId, contextMenuTabId, 'right')} + onSplitLeft={() => splitPane(paneId, contextMenuTabId, 'left')} + disableSplit={paneCount >= 4} + isSessionTab={isContextMenuTabSession} + isPinned={isContextMenuTabPinned} + onTogglePin={ + isContextMenuTabSession && contextMenuTab?.sessionId + ? () => togglePinSession(contextMenuTab.sessionId!) + : undefined + } + /> + )} +
    + ); +}; diff --git a/src/renderer/components/layout/TabContextMenu.tsx b/src/renderer/components/layout/TabContextMenu.tsx new file mode 100644 index 00000000..dd7165ea --- /dev/null +++ b/src/renderer/components/layout/TabContextMenu.tsx @@ -0,0 +1,149 @@ +/** + * TabContextMenu - Right-click context menu for tab actions. + * Supports close, close others, close all, bulk close for multi-select, + * and split left/right for pane management. + * Shows keyboard shortcut hints for actions that have them. + */ + +import { useEffect, useRef } from 'react'; + +interface TabContextMenuProps { + x: number; + y: number; + tabId: string; + paneId: string; + selectedCount: number; + onClose: () => void; + onCloseTab: () => void; + onCloseOtherTabs: () => void; + onCloseAllTabs: () => void; + onCloseSelectedTabs?: () => void; + onSplitRight: () => void; + onSplitLeft: () => void; + disableSplit: boolean; + /** Whether this tab is a session tab (pin only applies to sessions) */ + isSessionTab?: boolean; + /** Whether this session is currently pinned in the sidebar */ + isPinned?: boolean; + /** Callback to toggle pin state */ + onTogglePin?: () => void; +} + +export const TabContextMenu = ({ + x, + y, + selectedCount, + onClose, + onCloseTab, + onCloseOtherTabs, + onCloseAllTabs, + onCloseSelectedTabs, + onSplitRight, + onSplitLeft, + disableSplit, + isSessionTab, + isPinned, + onTogglePin, +}: TabContextMenuProps): React.JSX.Element => { + const menuRef = useRef(null); + + // Close on click-outside and Escape + useEffect(() => { + const handleMouseDown = (e: MouseEvent): void => { + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + onClose(); + } + }; + const handleKeyDown = (e: KeyboardEvent): void => { + if (e.key === 'Escape') onClose(); + }; + document.addEventListener('mousedown', handleMouseDown); + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('mousedown', handleMouseDown); + document.removeEventListener('keydown', handleKeyDown); + }; + }, [onClose]); + + // Viewport clamping + const menuWidth = 240; + const menuHeight = selectedCount > 1 ? 220 : 196; + const clampedX = Math.min(x, window.innerWidth - menuWidth - 8); + const clampedY = Math.min(y, window.innerHeight - menuHeight - 8); + + const handleClick = (action: () => void) => () => { + action(); + onClose(); + }; + + return ( +
    + {selectedCount > 1 && onCloseSelectedTabs ? ( + + ) : ( + + )} + +
    + + + {isSessionTab && onTogglePin && ( + <> +
    + + + )} +
    + +
    + ); +}; + +const MenuItem = ({ + label, + shortcut, + onClick, + disabled, +}: { + label: string; + shortcut?: string; + onClick: () => void; + disabled?: boolean; +}): React.JSX.Element => { + return ( + + ); +}; diff --git a/src/renderer/components/layout/TabbedLayout.tsx b/src/renderer/components/layout/TabbedLayout.tsx new file mode 100644 index 00000000..fcde1397 --- /dev/null +++ b/src/renderer/components/layout/TabbedLayout.tsx @@ -0,0 +1,41 @@ +/** + * TabbedLayout - Main layout with project-centric sidebar and multi-pane tabbed content. + * + * Layout structure: + * - Sidebar (280px): Project dropdown + date-grouped sessions + * - Main content: PaneContainer with one or more panes, each with TabBar + content + */ + +import { getTrafficLightPaddingForZoom } from '@renderer/constants/layout'; +import { useKeyboardShortcuts } from '@renderer/hooks/useKeyboardShortcuts'; +import { useZoomFactor } from '@renderer/hooks/useZoomFactor'; + +import { CommandPalette } from '../search/CommandPalette'; + +import { PaneContainer } from './PaneContainer'; +import { Sidebar } from './Sidebar'; + +export const TabbedLayout = (): React.JSX.Element => { + // Enable keyboard shortcuts + useKeyboardShortcuts(); + const zoomFactor = useZoomFactor(); + const trafficLightPadding = getTrafficLightPaddingForZoom(zoomFactor); + + return ( +
    + {/* Command Palette (Cmd+K) */} + + + {/* Sidebar - Project dropdown + Sessions (280px) */} + + + {/* Multi-pane content area */} + +
    + ); +}; diff --git a/src/renderer/components/notifications/NotificationRow.tsx b/src/renderer/components/notifications/NotificationRow.tsx new file mode 100644 index 00000000..69bf8a4e --- /dev/null +++ b/src/renderer/components/notifications/NotificationRow.tsx @@ -0,0 +1,213 @@ +/** + * NotificationRow - Linear Inbox-style notification row. + * Compact, high-density layout with hover actions. + */ + +import { useState } from 'react'; + +import { getTriggerColorDef } from '@shared/constants/triggerColors'; +import { formatDistanceToNow } from 'date-fns'; +import { ArrowRight, Bot, Check, Trash2 } from 'lucide-react'; + +import type { DetectedError } from '@renderer/types/data'; + +interface NotificationRowProps { + error: DetectedError; + onRowClick: () => void; + onArchive: () => void; + onDelete: () => void; +} + +/** + * Truncates a string to a maximum length, adding ellipsis if truncated. + */ +function truncateMessage(message: string, maxLength: number = 100): string { + if (message.length <= maxLength) return message; + return message.slice(0, maxLength).trim() + '...'; +} + +export const NotificationRow = ({ + error, + onRowClick, + onArchive, + onDelete, +}: Readonly): React.JSX.Element => { + const [isHovered, setIsHovered] = useState(false); + const isUnread = !error.isRead; + const projectName = error.context?.projectName || 'Unknown Project'; + const relativeTime = formatDistanceToNow(new Date(error.timestamp), { + addSuffix: true, + }); + const truncatedMessage = truncateMessage(error.message); + const colorDef = getTriggerColorDef(error.triggerColor); + const displayName = error.triggerName ?? error.source; + + const handleArchiveClick = (e: React.MouseEvent): void => { + e.stopPropagation(); + onArchive(); + }; + + const handleDeleteClick = (e: React.MouseEvent): void => { + e.stopPropagation(); + onDelete(); + }; + + const handleNavigateClick = (e: React.MouseEvent): void => { + e.stopPropagation(); + onRowClick(); + }; + + return ( +
    { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onRowClick(); + } + }} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + className="flex h-full cursor-pointer items-center gap-3 border-b px-4 transition-colors" + style={{ + borderColor: 'var(--color-border)', + backgroundColor: isHovered ? 'var(--color-surface-raised)' : undefined, + }} + > + {/* Color Dot — always visible, opacity indicates read state */} +
    + +
    + + {/* Content */} +
    + {/* Title Row */} +
    + + {displayName} + + · + + {projectName} + + {error.subagentId && ( + + + subagent + + )} +
    + {/* Description */} +

    + {truncatedMessage} +

    +
    + + {/* Right Side: Time or Hover Actions */} +
    + {isHovered ? ( + + ) : ( + + {relativeTime} + + )} +
    +
    + ); +}; + +/** + * HoverActions - Action buttons shown on hover. + */ +interface HoverActionsProps { + isUnread: boolean; + onArchiveClick: (e: React.MouseEvent) => void; + onDeleteClick: (e: React.MouseEvent) => void; + onNavigateClick: (e: React.MouseEvent) => void; +} + +const HoverActions = ({ + isUnread, + onArchiveClick, + onDeleteClick, + onNavigateClick, +}: HoverActionsProps): React.JSX.Element => { + const [hoveredButton, setHoveredButton] = useState(null); + + const getButtonStyle = (buttonId: string, isDelete = false): React.CSSProperties => ({ + color: + hoveredButton === buttonId + ? isDelete + ? 'var(--tool-result-error-text)' + : 'var(--color-text)' + : 'var(--color-text-muted)', + backgroundColor: hoveredButton === buttonId ? 'var(--color-border-emphasis)' : undefined, + }); + + return ( + <> + {/* Archive Button (mark as read) */} + {isUnread && ( + + )} + {/* Delete Button */} + + {/* Navigate Button */} + + + ); +}; diff --git a/src/renderer/components/notifications/NotificationsView.tsx b/src/renderer/components/notifications/NotificationsView.tsx new file mode 100644 index 00000000..e7303e0b --- /dev/null +++ b/src/renderer/components/notifications/NotificationsView.tsx @@ -0,0 +1,351 @@ +/** + * NotificationsView - Linear Inbox-style notifications page. + * Single list showing all notifications with unread indicator. + * Includes a filter chip bar to filter by trigger name. + */ + +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { useStore } from '@renderer/store'; +import { getTriggerColorDef } from '@shared/constants/triggerColors'; +import { useVirtualizer } from '@tanstack/react-virtual'; +import { CheckCheck, Inbox, Loader2, Trash2 } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import { NotificationRow } from './NotificationRow'; + +import type { DetectedError } from '@renderer/types/data'; + +// Virtual list constants +const ROW_HEIGHT = 56; +const OVERSCAN = 5; + +/** Label used for notifications without a triggerName */ +const OTHER_LABEL = 'Other'; + +interface FilterChip { + label: string; + count: number; + colorHex: string; +} + +export const NotificationsView = (): React.JSX.Element => { + const { + notifications, + unreadCount, + fetchNotifications, + markNotificationRead, + markAllNotificationsRead, + deleteNotification, + clearNotifications, + navigateToError, + } = useStore( + useShallow((s) => ({ + notifications: s.notifications, + unreadCount: s.unreadCount, + fetchNotifications: s.fetchNotifications, + markNotificationRead: s.markNotificationRead, + markAllNotificationsRead: s.markAllNotificationsRead, + deleteNotification: s.deleteNotification, + clearNotifications: s.clearNotifications, + navigateToError: s.navigateToError, + })) + ); + + const parentRef = useRef(null); + const [isLoading, setIsLoading] = useState(true); + const [showClearConfirm, setShowClearConfirm] = useState(false); + const [activeFilter, setActiveFilter] = useState(null); + + // Fetch notifications on mount + useEffect(() => { + const loadNotifications = async (): Promise => { + setIsLoading(true); + try { + await fetchNotifications(); + } finally { + setIsLoading(false); + } + }; + void loadNotifications(); + }, [fetchNotifications]); + + // Sort notifications by timestamp (most recent first) + const sortedNotifications = useMemo(() => { + return [...notifications].sort((a, b) => b.timestamp - a.timestamp); + }, [notifications]); + + // Derive filter chips from notifications + const filterChips = useMemo((): FilterChip[] => { + const counts = new Map(); + for (const n of sortedNotifications) { + const label = n.triggerName ?? OTHER_LABEL; + const existing = counts.get(label); + if (existing) { + existing.count++; + } else { + counts.set(label, { + count: 1, + colorHex: getTriggerColorDef(n.triggerColor).hex, + }); + } + } + // Sort by frequency descending + return Array.from(counts.entries()) + .sort((a, b) => b[1].count - a[1].count) + .map(([label, { count, colorHex }]) => ({ label, count, colorHex })); + }, [sortedNotifications]); + + // Reset filter when all notifications are cleared + useEffect(() => { + if (notifications.length === 0) { + setActiveFilter(null); + } + }, [notifications.length]); + + // Apply filter + const filteredNotifications = useMemo(() => { + if (activeFilter === null) return sortedNotifications; + return sortedNotifications.filter((n) => { + const label = n.triggerName ?? OTHER_LABEL; + return label === activeFilter; + }); + }, [sortedNotifications, activeFilter]); + + // Estimate item size + const estimateSize = useCallback(() => ROW_HEIGHT, []); + + // Set up virtualizer + const rowVirtualizer = useVirtualizer({ + count: filteredNotifications.length, + getScrollElement: () => parentRef.current, + estimateSize, + overscan: OVERSCAN, + }); + + // Scroll to top when filter changes + useEffect(() => { + rowVirtualizer.scrollToIndex(0); + }, [activeFilter, rowVirtualizer]); + + // Handle mark all read + const handleMarkAllRead = async (): Promise => { + await markAllNotificationsRead(); + }; + + // Handle clear all with confirmation + const handleClearAll = async (): Promise => { + if (showClearConfirm) { + await clearNotifications(); + setShowClearConfirm(false); + } else { + setShowClearConfirm(true); + // Auto-hide confirmation after 3 seconds + setTimeout(() => setShowClearConfirm(false), 3000); + } + }; + + // Handle archive (mark as read) + const handleArchive = async (id: string): Promise => { + await markNotificationRead(id); + }; + + // Handle delete + const handleDelete = async (id: string): Promise => { + await deleteNotification(id); + }; + + // Handle row click - navigate to error + const handleRowClick = (error: DetectedError): void => { + // Mark as read when navigating + if (!error.isRead) { + void markNotificationRead(error.id); + } + navigateToError(error); + }; + + // Handle filter chip click + const handleFilterClick = (label: string): void => { + setActiveFilter((prev) => (prev === label ? null : label)); + }; + + // Loading state + if (isLoading) { + return ( +
    +
    + + + Loading notifications... + +
    +
    + ); + } + + return ( +
    + {/* Header */} +
    +
    + {/* Title */} +
    + + + Notifications + + {notifications.length > 0 && ( + + {unreadCount > 0 ? `${unreadCount} unread` : `${notifications.length} total`} + + )} +
    + + {/* Action Buttons */} + {notifications.length > 0 && ( +
    + {/* Mark all read */} + {unreadCount > 0 && ( + + )} + {/* Clear all */} + +
    + )} +
    +
    + + {/* Filter Chip Bar */} + {filterChips.length > 1 && ( +
    +
    + {/* All chip */} + + {/* Trigger chips */} + {filterChips.map((chip) => ( + + ))} +
    +
    + )} + + {/* Notifications List */} +
    + {filteredNotifications.length === 0 ? ( +
    + +

    + {activeFilter !== null ? 'No matching notifications' : 'No notifications'} +

    +

    + {activeFilter !== null ? 'Try a different filter' : "You're all caught up!"} +

    +
    + ) : ( +
    + {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const notification = filteredNotifications[virtualRow.index]; + if (!notification) return null; + + return ( +
    + handleRowClick(notification)} + onArchive={() => handleArchive(notification.id)} + onDelete={() => handleDelete(notification.id)} + /> +
    + ); + })} +
    + )} +
    +
    + ); +}; diff --git a/src/renderer/components/search/CommandPalette.tsx b/src/renderer/components/search/CommandPalette.tsx new file mode 100644 index 00000000..056d15c4 --- /dev/null +++ b/src/renderer/components/search/CommandPalette.tsx @@ -0,0 +1,497 @@ +/** + * CommandPalette - Spotlight/Alfred-like search modal. + * Triggered by Cmd+K. + * + * Behavior: + * - When NO project is selected: Searches projects by name/path + * - When a project IS selected: Searches conversations within that project + */ + +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { useStore } from '@renderer/store'; +import { createLogger } from '@shared/utils/logger'; +import { useShallow } from 'zustand/react/shallow'; + +const logger = createLogger('Component:CommandPalette'); +import { formatDistanceToNow } from 'date-fns'; +import { Bot, FileText, FolderGit2, Loader2, MessageSquare, Search, User, X } from 'lucide-react'; + +import type { RepositoryGroup, SearchResult } from '@renderer/types/data'; + +// ============================================================================= +// Search Mode Type +// ============================================================================= + +type SearchMode = 'projects' | 'sessions'; + +// ============================================================================= +// Project Search Result Item +// ============================================================================= + +interface ProjectResultItemProps { + repo: RepositoryGroup; + isSelected: boolean; + onClick: () => void; +} + +const ProjectResultItemInner = ({ + repo, + isSelected, + onClick, +}: Readonly): React.JSX.Element => { + const lastActivity = repo.mostRecentSession + ? formatDistanceToNow(new Date(repo.mostRecentSession), { addSuffix: true }) + : 'No recent activity'; + + return ( + + ); +}; + +const ProjectResultItem = React.memo(ProjectResultItemInner); + +// ============================================================================= +// Session Search Result Item +// ============================================================================= + +interface SessionResultItemProps { + result: SearchResult; + isSelected: boolean; + onClick: () => void; + highlightMatch: (context: string, matchedText: string) => React.ReactNode; +} + +const SessionResultItemInner = ({ + result, + isSelected, + onClick, + highlightMatch, +}: Readonly): React.JSX.Element => { + return ( + + ); +}; + +const SessionResultItem = React.memo(SessionResultItemInner); + +// ============================================================================= +// Main Component +// ============================================================================= + +export const CommandPalette = (): React.JSX.Element | null => { + const { + commandPaletteOpen, + closeCommandPalette, + selectedProjectId, + navigateToSession, + repositoryGroups, + fetchRepositoryGroups, + selectRepository, + } = useStore( + useShallow((s) => ({ + commandPaletteOpen: s.commandPaletteOpen, + closeCommandPalette: s.closeCommandPalette, + selectedProjectId: s.selectedProjectId, + navigateToSession: s.navigateToSession, + repositoryGroups: s.repositoryGroups, + fetchRepositoryGroups: s.fetchRepositoryGroups, + selectRepository: s.selectRepository, + })) + ); + + const inputRef = useRef(null); + const [query, setQuery] = useState(''); + const [sessionResults, setSessionResults] = useState([]); + const [loading, setLoading] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(0); + const [totalMatches, setTotalMatches] = useState(0); + const latestSearchRequestRef = useRef(0); + + // Determine search mode based on whether a project is selected + const searchMode: SearchMode = selectedProjectId ? 'sessions' : 'projects'; + + // Filter projects for project search mode + const filteredProjects = useMemo(() => { + if (searchMode !== 'projects' || query.trim().length < 1) { + return repositoryGroups.slice(0, 10); + } + + const q = query.toLowerCase().trim(); + return repositoryGroups + .filter((repo) => { + if (repo.name.toLowerCase().includes(q)) return true; + const path = repo.worktrees[0]?.path || ''; + if (path.toLowerCase().includes(q)) return true; + return false; + }) + .slice(0, 10); + }, [repositoryGroups, query, searchMode]); + + // Results count for current mode + const resultsCount = searchMode === 'projects' ? filteredProjects.length : sessionResults.length; + + // Fetch repository groups if needed + useEffect(() => { + if (commandPaletteOpen && searchMode === 'projects' && repositoryGroups.length === 0) { + void fetchRepositoryGroups(); + } + }, [commandPaletteOpen, searchMode, repositoryGroups.length, fetchRepositoryGroups]); + + // Focus input when palette opens + useEffect(() => { + if (commandPaletteOpen && inputRef.current) { + inputRef.current.focus(); + setQuery(''); + setSessionResults([]); + setSelectedIndex(0); + setTotalMatches(0); + } + }, [commandPaletteOpen]); + + // Search sessions with debounce (only in session mode) + useEffect(() => { + if ( + !commandPaletteOpen || + searchMode !== 'sessions' || + !selectedProjectId || + query.trim().length < 2 + ) { + setSessionResults([]); + setTotalMatches(0); + return; + } + + const timeoutId = setTimeout(async () => { + const requestId = latestSearchRequestRef.current + 1; + latestSearchRequestRef.current = requestId; + setLoading(true); + try { + const searchResult = await window.electronAPI.searchSessions( + selectedProjectId, + query.trim(), + 50 + ); + if (latestSearchRequestRef.current !== requestId) { + return; + } + setSessionResults(searchResult.results); + setTotalMatches(searchResult.totalMatches); + setSelectedIndex(0); + } catch (error) { + if (latestSearchRequestRef.current !== requestId) { + return; + } + logger.error('Search error:', error); + setSessionResults([]); + setTotalMatches(0); + } finally { + if (latestSearchRequestRef.current === requestId) { + setLoading(false); + } + } + }, 200); + + return () => clearTimeout(timeoutId); + }, [query, selectedProjectId, commandPaletteOpen, searchMode]); + + // Reset selected index when results change + useEffect(() => { + setSelectedIndex(0); + }, [filteredProjects, sessionResults]); + + // Handle project click + const handleProjectClick = useCallback( + (repo: RepositoryGroup) => { + closeCommandPalette(); + selectRepository(repo.id); + }, + [closeCommandPalette, selectRepository] + ); + + // Handle session result click + const handleSessionResultClick = useCallback( + (result: SearchResult) => { + closeCommandPalette(); + navigateToSession(result.projectId, result.sessionId, true, { + query: query.trim(), + messageTimestamp: result.timestamp, + matchedText: result.matchedText, + targetGroupId: result.groupId, + targetMatchIndexInItem: result.matchIndexInItem, + targetMatchStartOffset: result.matchStartOffset, + targetMessageUuid: result.messageUuid, + }); + }, + [closeCommandPalette, navigateToSession, query] + ); + + // Handle backdrop click + const handleBackdropClick = useCallback( + (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + closeCommandPalette(); + } + }, + [closeCommandPalette] + ); + + // Handle keyboard navigation + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + closeCommandPalette(); + return; + } + + if (e.key === 'ArrowDown') { + e.preventDefault(); + setSelectedIndex((prev) => Math.min(prev + 1, resultsCount - 1)); + return; + } + + if (e.key === 'ArrowUp') { + e.preventDefault(); + setSelectedIndex((prev) => Math.max(prev - 1, 0)); + return; + } + + if (e.key === 'Enter' && resultsCount > 0) { + e.preventDefault(); + if (searchMode === 'projects') { + const selected = filteredProjects[selectedIndex]; + if (selected) { + handleProjectClick(selected); + } + } else { + const selected = sessionResults[selectedIndex]; + if (selected) { + handleSessionResultClick(selected); + } + } + } + }, + [ + resultsCount, + selectedIndex, + closeCommandPalette, + searchMode, + filteredProjects, + sessionResults, + handleProjectClick, + handleSessionResultClick, + ] + ); + + // Highlight matched text in context + const highlightMatch = useCallback((context: string, matchedText: string) => { + const lowerContext = context.toLowerCase(); + const lowerMatch = matchedText.toLowerCase(); + const matchIndex = lowerContext.indexOf(lowerMatch); + + if (matchIndex === -1) { + return {context}; + } + + const before = context.slice(0, matchIndex); + const match = context.slice(matchIndex, matchIndex + matchedText.length); + const after = context.slice(matchIndex + matchedText.length); + + return ( + <> + {before} + + {match} + + {after} + + ); + }, []); + + if (!commandPaletteOpen) { + return null; + } + + return ( +
    +
    + {/* Mode indicator */} +
    +
    + {searchMode === 'projects' ? ( + <> + + Search projects + + ) : ( + <> + + Search in projects + · + + {repositoryGroups.find((r) => r.worktrees.some((w) => w.id === selectedProjectId)) + ?.name ?? 'Current project'} + + + )} +
    +
    + + {/* Search input */} +
    + + setQuery(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={ + searchMode === 'projects' ? 'Search projects...' : 'Search conversations...' + } + className="placeholder:text-text-muted/50 flex-1 bg-transparent text-base text-text focus:outline-none" + /> + {loading && } + +
    + + {/* Results */} +
    + {searchMode === 'projects' ? ( + // Project search results + filteredProjects.length === 0 ? ( +
    + {query.trim() ? `No projects found for "${query}"` : 'No projects found'} +
    + ) : ( +
    + {filteredProjects.map((repo, index) => ( + handleProjectClick(repo)} + /> + ))} +
    + ) + ) : // Session search results + query.trim().length < 2 ? ( +
    + Type at least 2 characters to search +
    + ) : sessionResults.length === 0 && !loading ? ( +
    + No results found for "{query}" +
    + ) : ( +
    + {sessionResults.map((result, index) => ( + handleSessionResultClick(result)} + highlightMatch={highlightMatch} + /> + ))} +
    + )} +
    + + {/* Footer */} +
    + + {searchMode === 'projects' + ? `${filteredProjects.length} project${filteredProjects.length !== 1 ? 's' : ''}` + : totalMatches > 0 + ? `${totalMatches} result${totalMatches !== 1 ? 's' : ''}` + : 'Type to search'} + +
    + + ↑↓{' '} + navigate + + + {' '} + {searchMode === 'projects' ? 'select' : 'open'} + + + esc close + +
    +
    +
    +
    + ); +}; diff --git a/src/renderer/components/search/SearchBar.tsx b/src/renderer/components/search/SearchBar.tsx new file mode 100644 index 00000000..d7e6b448 --- /dev/null +++ b/src/renderer/components/search/SearchBar.tsx @@ -0,0 +1,122 @@ +/** + * SearchBar - In-session search interface component. + * Appears at the top of the chat view when Cmd+F is pressed. + */ + +import { useEffect, useRef } from 'react'; + +import { useStore } from '@renderer/store'; +import { ChevronDown, ChevronUp, X } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +interface SearchBarProps { + tabId?: string; +} + +export const SearchBar = ({ tabId }: SearchBarProps): React.JSX.Element | null => { + const { + searchQuery, + searchVisible, + searchResultCount, + currentSearchIndex, + conversation, + setSearchQuery, + hideSearch, + nextSearchResult, + previousSearchResult, + } = useStore( + useShallow((s) => ({ + searchQuery: s.searchQuery, + searchVisible: s.searchVisible, + searchResultCount: s.searchResultCount, + currentSearchIndex: s.currentSearchIndex, + conversation: tabId + ? (s.tabSessionData[tabId]?.conversation ?? s.conversation) + : s.conversation, + setSearchQuery: s.setSearchQuery, + hideSearch: s.hideSearch, + nextSearchResult: s.nextSearchResult, + previousSearchResult: s.previousSearchResult, + })) + ); + + const inputRef = useRef(null); + + // Auto-focus input when search becomes visible + useEffect(() => { + if (searchVisible && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [searchVisible]); + + // Handle keyboard shortcuts within search bar + const handleKeyDown = (e: React.KeyboardEvent): void => { + if (e.key === 'Escape') { + hideSearch(); + } else if (e.key === 'Enter') { + if (e.shiftKey) { + previousSearchResult(); + } else { + nextSearchResult(); + } + } + }; + + if (!searchVisible) { + return null; + } + + return ( +
    + {/* Search input */} + setSearchQuery(e.target.value, conversation)} + onKeyDown={handleKeyDown} + placeholder="Find in conversation..." + className="w-48 rounded border border-border bg-surface-raised px-3 py-1.5 text-sm text-text focus:border-text-secondary focus:outline-none" + /> + + {/* Result count */} + {searchQuery && ( + + {searchResultCount > 0 + ? `${currentSearchIndex + 1} of ${searchResultCount}` + : 'No results'} + + )} + + {/* Navigation buttons */} +
    + + +
    + + {/* Close button */} + +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/AddTriggerForm.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/AddTriggerForm.tsx new file mode 100644 index 00000000..bf824f94 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/AddTriggerForm.tsx @@ -0,0 +1,233 @@ +/** + * AddTriggerForm - Form to add a new custom trigger. + */ + +import { useCallback } from 'react'; + +import { ChevronDown, ChevronUp, Loader2, Plus } from 'lucide-react'; + +import { useAddTriggerFormHandlers } from '../hooks/useAddTriggerFormHandlers'; +import { useAddTriggerFormState } from '../hooks/useAddTriggerFormState'; +import { useRepositoryLookup } from '../hooks/useRepositoryLookup'; +import { useTriggerForm } from '../hooks/useTriggerForm'; +import { generateId } from '../utils/trigger'; + +import { ColorPaletteSelector } from './ColorPaletteSelector'; +import { DynamicConfigSection } from './DynamicConfigSection'; +import { GeneralInfoSection } from './GeneralInfoSection'; +import { IgnorePatternsSection } from './IgnorePatternsSection'; +import { ModeSelector } from './ModeSelector'; +import { RepositoryScopeSection } from './RepositoryScopeSection'; +import { SectionHeader } from './SectionHeader'; +import { TriggerPreview } from './TriggerPreview'; + +import type { NotificationTrigger } from '@renderer/types/data'; + +interface AddTriggerFormProps { + saving: boolean; + onAdd: (trigger: Omit) => Promise; +} + +export const AddTriggerForm = ({ + saving, + onAdd, +}: Readonly): React.JSX.Element => { + // Use form state hook + const formState = useAddTriggerFormState(); + const { + name, + toolName, + mode, + contentType, + matchField, + matchPattern, + tokenThreshold, + tokenType, + ignorePatterns, + repositoryIds, + color, + isExpanded, + setName, + setMatchField, + setTokenType, + setColor, + setIsExpanded, + resetForm, + } = formState; + + // Use shared form hook for validation and preview + const { + patternError, + validatePattern, + previewResult, + handleTestTrigger, + handleViewSession, + clearPreview, + buildTriggerForTest, + } = useTriggerForm({}); + + // Use handlers hook + const handlers = useAddTriggerFormHandlers({ + formState, + validatePattern, + clearPreview, + }); + + // Convert repositoryIds to RepositoryDropdownItem[] for display + const selectedRepositoryItems = useRepositoryLookup(repositoryIds); + + // Test trigger using the shared hook + const handleTest = useCallback(async (): Promise => { + if (mode === 'content_match' && !validatePattern(matchPattern)) return; + + const testTrigger = buildTriggerForTest({ + name, + contentType, + mode, + matchField, + matchPattern, + tokenThreshold, + tokenType, + toolName, + ignorePatterns, + repositoryIds, + }); + + await handleTestTrigger(testTrigger); + }, [ + mode, + matchPattern, + validatePattern, + buildTriggerForTest, + name, + contentType, + matchField, + tokenThreshold, + tokenType, + toolName, + ignorePatterns, + repositoryIds, + handleTestTrigger, + ]); + + const handleSubmit = async (e: React.FormEvent): Promise => { + e.preventDefault(); + if (!name.trim()) return; + if (mode === 'content_match' && !validatePattern(matchPattern)) return; + + const newTrigger = handlers.buildNewTrigger(generateId); + await onAdd(newTrigger); + resetForm(); + clearPreview(); + setIsExpanded(false); + }; + + return ( +
    + {/* Header */} + + + {/* Form */} + {isExpanded && ( +
    + {/* Section 1: General Info */} + + + {/* Dot Color */} +
    + + +
    + + {/* Section 2: Trigger Condition */} +
    + + +
    + + {/* Section 3: Dynamic Configuration */} + + + {/* Section 4: Advanced (Collapsible) */} + + + {/* Section 5: Repository Scope (Collapsible) */} + + + {/* Preview Section */} + + + {/* Submit button */} +
    + + +
    + + )} +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/ColorPaletteSelector.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/ColorPaletteSelector.tsx new file mode 100644 index 00000000..f5d250b3 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/ColorPaletteSelector.tsx @@ -0,0 +1,144 @@ +/** + * ColorPaletteSelector - Color picker with preset palette and custom hex input. + * Renders a row of preset colored circles plus a hex input for custom colors. + * + * Hex input commits on blur/Enter only (not on every keystroke) to avoid + * triggering config saves while the user is still typing. + */ + +import { useCallback, useState } from 'react'; + +import { + isPresetColorKey, + resolveColorHex, + TRIGGER_COLORS, + type TriggerColor, +} from '@shared/constants/triggerColors'; + +interface ColorPaletteSelectorProps { + value: TriggerColor | undefined; + onChange: (color: TriggerColor) => void; + disabled?: boolean; +} + +const HEX_RE = /^#[0-9a-fA-F]{3,8}$/; + +export const ColorPaletteSelector = ({ + value, + onChange, + disabled, +}: Readonly): React.JSX.Element => { + const isCustom = !!value && !isPresetColorKey(value); + const [hexInput, setHexInput] = useState(isCustom ? value : ''); + const [showHexInput, setShowHexInput] = useState(isCustom); + + // Only update local state on each keystroke — do NOT call onChange here. + const handleHexInputChange = useCallback((raw: string) => { + const v = raw.startsWith('#') ? raw : raw.length > 0 ? `#${raw}` : ''; + setHexInput(v); + }, []); + + // Commit hex value on blur or Enter + const commitHex = useCallback(() => { + if (hexInput && HEX_RE.test(hexInput)) { + onChange(hexInput as `#${string}`); + } + }, [hexInput, onChange]); + + const handleHexKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + commitHex(); + } + }, + [commitHex] + ); + + const handlePresetClick = useCallback( + (color: TriggerColor) => { + onChange(color); + setShowHexInput(false); + }, + [onChange] + ); + + const handleCustomClick = useCallback(() => { + setShowHexInput(true); + if (hexInput && HEX_RE.test(hexInput)) { + onChange(hexInput as `#${string}`); + } + }, [hexInput, onChange]); + + // Preview swatch shows live hex input (local state) when typing, otherwise the committed value + const previewHex = + showHexInput && hexInput && HEX_RE.test(hexInput) ? hexInput : resolveColorHex(value); + + return ( +
    + {/* Color preview + presets row */} +
    + {/* Live preview swatch */} + + + {/* Preset palette */} + {TRIGGER_COLORS.map((color) => { + const isSelected = value === color.key || (!value && color.key === 'red'); + return ( + +
    + + {/* Hex input row */} + {showHexInput && ( +
    + handleHexInputChange(e.target.value)} + onBlur={commitHex} + onKeyDown={handleHexKeyDown} + placeholder="#ff6600" + maxLength={9} + disabled={disabled} + className={`w-24 rounded border bg-transparent px-2 py-1 font-mono text-xs text-text placeholder:text-text-muted focus:border-transparent focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ + hexInput && !HEX_RE.test(hexInput) ? 'border-red-500' : 'border-border' + }`} + /> + {hexInput && !HEX_RE.test(hexInput) && ( + Invalid hex + )} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/DynamicConfigSection.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/DynamicConfigSection.tsx new file mode 100644 index 00000000..5ca1cc8a --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/DynamicConfigSection.tsx @@ -0,0 +1,191 @@ +/** + * DynamicConfigSection - Mode-specific configuration for AddTriggerForm. + * Renders different UI based on the selected trigger mode. + */ + +import { + getCursorClass, + SELECT_INPUT_BASE, + SELECT_OPTION_BG, +} from '@renderer/constants/cssVariables'; +import { AlertCircle } from 'lucide-react'; + +import { CONTENT_TYPE_OPTIONS } from '../utils/constants'; +import { getAvailableMatchFields } from '../utils/trigger'; + +import { SectionHeader } from './SectionHeader'; + +import type { TriggerContentType, TriggerMode, TriggerTokenType } from '@renderer/types/data'; + +interface DynamicConfigSectionProps { + mode: TriggerMode; + contentType: TriggerContentType; + toolName: string; + matchField: string; + matchPattern: string; + patternError: string | null; + tokenThreshold: number; + tokenType: TriggerTokenType; + saving: boolean; + onContentTypeChange: (contentType: TriggerContentType) => void; + onMatchFieldChange: (matchField: string) => void; + onMatchPatternChange: (value: string) => void; + onTokenThresholdChange: (value: string) => void; + onTokenTypeChange: (tokenType: TriggerTokenType) => void; +} + +export const DynamicConfigSection = ({ + mode, + contentType, + toolName, + matchField, + matchPattern, + patternError, + tokenThreshold, + tokenType, + saving, + onContentTypeChange, + onMatchFieldChange, + onMatchPatternChange, + onTokenThresholdChange, + onTokenTypeChange, +}: Readonly): React.JSX.Element => { + // Get available match fields based on content type and tool name + const availableMatchFields = getAvailableMatchFields(contentType, toolName || undefined); + + return ( +
    + + + {/* Error Status Mode */} + {mode === 'error_status' && ( +
    +

    + Triggers when a tool execution reports an error (is_error: true). +

    +
    + )} + + {/* Content Match Mode */} + {mode === 'content_match' && ( +
    + {/* Content Type */} +
    + + +
    + + {/* Match Field */} + {availableMatchFields.length > 0 && ( +
    + + +
    + )} + + {/* Match Pattern */} +
    +
    + +
    + onMatchPatternChange(e.target.value)} + placeholder="e.g., error|failed|exception" + disabled={saving} + className={`w-full rounded border bg-transparent px-2 py-1.5 font-mono text-sm text-text placeholder:text-text-muted focus:border-transparent focus:outline-none focus:ring-1 focus:ring-indigo-500 ${patternError ? 'border-red-500' : 'border-border'} ${saving ? 'cursor-not-allowed opacity-50' : ''} `} + /> + {patternError && ( +

    + + {patternError} +

    + )} +

    + Leave empty to match all content. Uses JavaScript regex syntax. +

    +
    +
    + )} + + {/* Token Threshold Mode */} + {mode === 'token_threshold' && ( +
    +
    + + +
    +
    + +
    + Alert if > + onTokenThresholdChange(e.target.value)} + placeholder="0" + disabled={saving} + className={`w-20 rounded border border-border bg-transparent px-2 py-1 text-right text-sm text-text focus:border-transparent focus:outline-none focus:ring-1 focus:ring-indigo-500 ${saving ? 'cursor-not-allowed opacity-50' : ''} `} + /> + tokens +
    +
    +
    + )} +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/GeneralInfoSection.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/GeneralInfoSection.tsx new file mode 100644 index 00000000..facf11af --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/GeneralInfoSection.tsx @@ -0,0 +1,68 @@ +/** + * GeneralInfoSection - Name input and tool select for AddTriggerForm. + */ + +import { TOOL_NAME_OPTIONS } from '../utils/constants'; + +import { SectionHeader } from './SectionHeader'; + +interface GeneralInfoSectionProps { + name: string; + toolName: string; + saving: boolean; + onNameChange: (name: string) => void; + onToolNameChange: (toolName: string) => void; +} + +export const GeneralInfoSection = ({ + name, + toolName, + saving, + onNameChange, + onToolNameChange, +}: Readonly): React.JSX.Element => { + return ( +
    + + + {/* Trigger Name */} +
    +
    + +
    + onNameChange(e.target.value)} + placeholder="e.g., Build Failure Alert" + disabled={saving} + required + className={`w-full rounded border border-border bg-transparent px-2 py-1.5 text-sm text-text placeholder:text-text-muted focus:border-transparent focus:outline-none focus:ring-1 focus:ring-indigo-500 ${saving ? 'cursor-not-allowed opacity-50' : ''} `} + /> +
    + + {/* Scope/Tool Name */} +
    + + +
    +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/IgnorePatternsSection.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/IgnorePatternsSection.tsx new file mode 100644 index 00000000..a15d2a2b --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/IgnorePatternsSection.tsx @@ -0,0 +1,73 @@ +/** + * IgnorePatternsSection - Collapsible section for ignore patterns - Linear style. + */ + +import { X } from 'lucide-react'; + +interface IgnorePatternsSectionProps { + patterns: string[]; + onAdd: (pattern: string) => void; + onRemove: (index: number) => void; + disabled: boolean; +} + +export const IgnorePatternsSection = ({ + patterns, + onAdd, + onRemove, + disabled, +}: Readonly): React.JSX.Element => { + return ( +
    + + Advanced: Exclusion Rules + +
    + + Ignore Patterns (skip if matches) + + {patterns.map((pattern, idx) => ( +
    + + {pattern} + + +
    + ))} +
    + { + if (e.key === 'Enter' && e.currentTarget.value.trim()) { + e.preventDefault(); + try { + const input = e.currentTarget; + const value = input.value.trim(); + new RegExp(value); + onAdd(value); + input.value = ''; + } catch { + // Invalid regex + } + } + }} + /> +
    +

    + Press Enter to add. Notification is skipped if any pattern matches. +

    +
    +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/ModeSelector.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/ModeSelector.tsx new file mode 100644 index 00000000..c8e06f2c --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/ModeSelector.tsx @@ -0,0 +1,45 @@ +/** + * ModeSelector - Segmented control for selecting trigger mode - Linear style. + */ + +import { MODE_OPTIONS } from '../utils/constants'; + +import type { TriggerMode } from '@renderer/types/data'; + +interface ModeSelectorProps { + value: TriggerMode; + onChange: (mode: TriggerMode) => void; + disabled?: boolean; +} + +export const ModeSelector = ({ + value, + onChange, + disabled = false, +}: Readonly): React.JSX.Element => { + return ( +
    + {MODE_OPTIONS.map((mode) => { + const Icon = mode.icon; + const isActive = value === mode.value; + + return ( + + ); + })} +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/RepositoryScopeSection.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/RepositoryScopeSection.tsx new file mode 100644 index 00000000..bd4df655 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/RepositoryScopeSection.tsx @@ -0,0 +1,67 @@ +/** + * RepositoryScopeSection - Section for limiting trigger to specific repositories. + * Uses the shared RepositoryDropdown component. + */ + +import { + RepositoryDropdown, + SelectedRepositoryItem, +} from '@renderer/components/common/RepositoryDropdown'; + +import type { RepositoryDropdownItem } from '@renderer/components/settings/hooks/useSettingsConfig'; + +interface RepositoryScopeSectionProps { + repositoryIds: string[]; + selectedItems: RepositoryDropdownItem[]; + onAdd: (item: RepositoryDropdownItem) => void; + onRemove: (index: number) => void; + disabled: boolean; +} + +export const RepositoryScopeSection = ({ + repositoryIds, + selectedItems, + onAdd, + onRemove, + disabled, +}: Readonly): React.JSX.Element => { + return ( +
    + + Advanced: Repository Scope + +
    + + Limit to Repositories (applies only to selected repositories) + + {selectedItems.length === 0 ? ( +

    + No repositories selected - trigger applies to all repositories +

    + ) : ( + selectedItems.map((item, idx) => ( + onRemove(idx)} + disabled={disabled} + /> + )) + )} + + {/* Repository selector dropdown */} + + +

    + When repositories are selected, this trigger only fires for errors in those repositories. +

    +
    +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/SectionHeader.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/SectionHeader.tsx new file mode 100644 index 00000000..185ca9d7 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/SectionHeader.tsx @@ -0,0 +1,15 @@ +/** + * Section header component - Linear style. + */ + +interface SectionHeaderProps { + title: string; +} + +export const SectionHeader = ({ title }: Readonly): React.JSX.Element => { + return ( +

    + {title} +

    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCard.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCard.tsx new file mode 100644 index 00000000..21acf01d --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCard.tsx @@ -0,0 +1,150 @@ +/** + * TriggerCard - Individual trigger display/edit card component. + * Memoized to prevent unnecessary re-renders when other triggers change. + */ + +import { memo, useCallback } from 'react'; + +import { useRepositoryLookup } from '../hooks/useRepositoryLookup'; +import { useTriggerCardState } from '../hooks/useTriggerCardState'; +import { useTriggerForm } from '../hooks/useTriggerForm'; + +import { IgnorePatternsSection } from './IgnorePatternsSection'; +import { RepositoryScopeSection } from './RepositoryScopeSection'; +import { TriggerCardHeader } from './TriggerCardHeader'; +import { TriggerConfiguration } from './TriggerConfiguration'; +import { TriggerPreview } from './TriggerPreview'; + +import type { NotificationTrigger } from '@renderer/types/data'; + +interface TriggerCardProps { + trigger: NotificationTrigger; + saving: boolean; + onUpdate: (triggerId: string, updates: Partial) => Promise; + onRemove: (triggerId: string) => Promise; +} + +const TriggerCardInner = ({ + trigger, + saving, + onUpdate, + onRemove, +}: Readonly): React.JSX.Element => { + // Wrap callbacks to include trigger.id + const handleUpdate = useCallback( + (updates: Partial) => onUpdate(trigger.id, updates), + [onUpdate, trigger.id] + ); + + const handleRemove = useCallback(() => onRemove(trigger.id), [onRemove, trigger.id]); + + // Use shared form hook for validation and preview + const { patternError, validatePattern, previewResult, handleTestTrigger, handleViewSession } = + useTriggerForm({ trigger, onUpdate: handleUpdate }); + + // Use extracted state and handlers hook + const { + isExpanded, + setIsExpanded, + editingName, + setEditingName, + localName, + setLocalName, + localPattern, + localMode, + localTokenThreshold, + localTokenType, + handleToggleEnabled, + handleNameSave, + handlePatternBlur, + handlePatternChange, + handleContentTypeChange, + handleToolNameChange, + handleMatchFieldChange, + handleModeChange, + handleTokenThresholdChange, + handleTokenThresholdBlur, + handleTokenTypeChange, + handleAddIgnorePattern, + handleRemoveIgnorePattern, + handleAddRepository, + handleRemoveRepository, + handleColorChange, + } = useTriggerCardState({ trigger, onUpdate: handleUpdate, validatePattern }); + + // Convert repositoryIds to RepositoryDropdownItem[] for display + const selectedRepositoryItems = useRepositoryLookup(trigger.repositoryIds ?? []); + + return ( +
    + {/* Header row */} + setIsExpanded(!isExpanded)} + onRemove={handleRemove} + /> + + {/* Expanded details */} + {isExpanded && ( +
    + {/* Configuration sections */} + + + {/* Section 4: Advanced (Collapsible) */} + + + {/* Section 5: Repository Scope (Collapsible) */} + + + {/* Preview Section */} + handleTestTrigger(trigger)} + onViewSession={handleViewSession} + /> +
    + )} +
    + ); +}; + +// Memoize to prevent re-rendering when other triggers change +export const TriggerCard = memo(TriggerCardInner); diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCardHeader.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCardHeader.tsx new file mode 100644 index 00000000..7f122a22 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerCardHeader.tsx @@ -0,0 +1,125 @@ +/** + * TriggerCardHeader - Header row for TriggerCard with name, badges, toggle, and actions. + */ + +import { SettingsToggle } from '@renderer/components/settings/components'; +import { getTriggerColorDef } from '@shared/constants/triggerColors'; +import { ChevronDown, ChevronUp, Pencil, Shield, X } from 'lucide-react'; + +import { CONTENT_TYPE_OPTIONS, MODE_OPTIONS } from '../utils/constants'; + +import type { NotificationTrigger, TriggerMode } from '@renderer/types/data'; + +interface TriggerCardHeaderProps { + trigger: NotificationTrigger; + saving: boolean; + localMode: TriggerMode; + editingName: boolean; + localName: string; + isExpanded: boolean; + onSetEditingName: (value: boolean) => void; + onSetLocalName: (value: string) => void; + onNameSave: () => void; + onToggleEnabled: () => void; + onToggleExpanded: () => void; + onRemove: () => Promise; +} + +export const TriggerCardHeader = ({ + trigger, + saving, + localMode, + editingName, + localName, + isExpanded, + onSetEditingName, + onSetLocalName, + onNameSave, + onToggleEnabled, + onToggleExpanded, + onRemove, +}: Readonly): React.JSX.Element => { + return ( +
    + {/* Left side: Name and badges */} +
    +
    + {editingName && !trigger.isBuiltin ? ( + onSetLocalName(e.target.value)} + onBlur={onNameSave} + onKeyDown={(e) => { + if (e.key === 'Enter') onNameSave(); + if (e.key === 'Escape') { + onSetLocalName(trigger.name); + onSetEditingName(false); + } + }} + autoFocus + className="w-full rounded border border-border bg-transparent px-2 py-1 text-sm text-text focus:outline-none focus:ring-1 focus:ring-indigo-500" + /> + ) : ( +
    + + {trigger.name} + {trigger.isBuiltin && ( + + + Builtin + + )} + {!trigger.isBuiltin && ( + + )} +
    + )} + {/* Description line showing mode and content type */} +
    + {MODE_OPTIONS.find((m) => m.value === localMode)?.label ?? localMode} + - + + {CONTENT_TYPE_OPTIONS.find((o) => o.value === trigger.contentType)?.label ?? + trigger.contentType} + +
    +
    +
    + + {/* Right side: Toggle and actions */} +
    + + + + + {!trigger.isBuiltin && ( + + )} +
    +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerConfiguration.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerConfiguration.tsx new file mode 100644 index 00000000..a05e5027 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerConfiguration.tsx @@ -0,0 +1,342 @@ +/** + * TriggerConfiguration - Mode-specific configuration sections for TriggerCard. + * Handles error status, content match, and token threshold mode configurations. + */ + +import { + getCursorClass, + SELECT_INPUT_BASE, + SELECT_OPTION_BG, +} from '@renderer/constants/cssVariables'; +import { AlertCircle } from 'lucide-react'; + +import { CONTENT_TYPE_OPTIONS, TOOL_NAME_OPTIONS } from '../utils/constants'; +import { getAvailableMatchFields } from '../utils/trigger'; + +import { ColorPaletteSelector } from './ColorPaletteSelector'; +import { ModeSelector } from './ModeSelector'; +import { SectionHeader } from './SectionHeader'; + +import type { + NotificationTrigger, + TriggerContentType, + TriggerMode, + TriggerTokenType, +} from '@renderer/types/data'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +interface TriggerConfigurationProps { + trigger: NotificationTrigger; + saving: boolean; + localMode: TriggerMode; + localPattern: string; + localTokenThreshold: number; + localTokenType: TriggerTokenType; + patternError: string | null; + onModeChange: (mode: TriggerMode) => void; + onContentTypeChange: (value: TriggerContentType) => void; + onToolNameChange: (value: string) => void; + onMatchFieldChange: (value: string) => void; + onPatternChange: (value: string) => void; + onPatternBlur: () => void; + onTokenThresholdChange: (value: number) => void; + onTokenThresholdBlur?: () => void; + onTokenTypeChange: (value: TriggerTokenType) => void; + onColorChange: (color: TriggerColor) => void; +} + +export const TriggerConfiguration = ({ + trigger, + saving, + localMode, + localPattern, + localTokenThreshold, + localTokenType, + patternError, + onModeChange, + onContentTypeChange, + onToolNameChange, + onMatchFieldChange, + onPatternChange, + onPatternBlur, + onTokenThresholdChange, + onTokenThresholdBlur, + onTokenTypeChange, + onColorChange, +}: Readonly): React.JSX.Element => { + const availableMatchFields = getAvailableMatchFields(trigger.contentType, trigger.toolName); + + return ( + <> + {/* Section 1: General Info */} +
    + + + {/* Scope/Tool Name */} + {(trigger.contentType === 'tool_use' || trigger.contentType === 'tool_result') && ( +
    + + +
    + )} +
    + + {/* Dot Color */} +
    + + +
    + + {/* Section 2: Trigger Condition (Mode Selector) */} +
    + + +
    + + {/* Section 3: Dynamic Configuration */} +
    + + + {/* Error Status Mode */} + {localMode === 'error_status' && ( +
    +

    + Triggers when a tool execution reports an error (is_error: true). +

    +
    + )} + + {/* Content Match Mode */} + {localMode === 'content_match' && ( + <> + {/* Content Type */} +
    + + +
    + + + )} + + {/* Token Threshold Mode */} + {localMode === 'token_threshold' && ( + + )} +
    + + ); +}; + +// ============================================================================= +// Content Match Configuration +// ============================================================================= + +interface ContentMatchConfigProps { + triggerId: string; + matchField?: string; + availableMatchFields: { value: string; label: string }[]; + localPattern: string; + patternError: string | null; + saving: boolean; + onMatchFieldChange: (value: string) => void; + onPatternChange: (value: string) => void; + onPatternBlur: () => void; +} + +const ContentMatchConfig = ({ + triggerId, + matchField, + availableMatchFields, + localPattern, + patternError, + saving, + onMatchFieldChange, + onPatternChange, + onPatternBlur, +}: Readonly): React.JSX.Element => { + return ( +
    + {/* Match Field */} + {availableMatchFields.length > 0 && ( +
    + + +
    + )} + + {/* Match Pattern */} +
    +
    + +
    + onPatternChange(e.target.value)} + onBlur={onPatternBlur} + placeholder="e.g., error|failed|exception" + disabled={saving} + className={`w-full rounded border bg-transparent px-2 py-1.5 font-mono text-sm text-text placeholder:text-text-muted focus:border-transparent focus:outline-none focus:ring-1 focus:ring-indigo-500 ${patternError ? 'border-red-500' : 'border-border'} ${saving ? 'cursor-not-allowed opacity-50' : ''} `} + /> + {patternError && ( +

    + + {patternError} +

    + )} +

    + Leave empty to match all content. Uses JavaScript regex syntax. +

    +
    +
    + ); +}; + +// ============================================================================= +// Token Threshold Configuration +// ============================================================================= + +interface TokenThresholdConfigProps { + triggerId: string; + localTokenType: TriggerTokenType; + localTokenThreshold: number; + saving: boolean; + onTokenTypeChange: (value: TriggerTokenType) => void; + onTokenThresholdChange: (value: number) => void; + onTokenThresholdBlur?: () => void; +} + +const TokenThresholdConfig = ({ + triggerId, + localTokenType, + localTokenThreshold, + saving, + onTokenTypeChange, + onTokenThresholdChange, + onTokenThresholdBlur, +}: Readonly): React.JSX.Element => { + return ( +
    +
    + + +
    +
    + +
    + Alert if > + { + const val = e.target.value.replace(/\D/g, ''); + onTokenThresholdChange(parseInt(val) || 0); + }} + onBlur={onTokenThresholdBlur} + placeholder="0" + disabled={saving} + className={`w-20 rounded border border-border bg-transparent px-2 py-1 text-right text-sm text-text focus:border-transparent focus:outline-none focus:ring-1 focus:ring-indigo-500 ${saving ? 'cursor-not-allowed opacity-50' : ''} `} + /> + tokens +
    +
    +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerPreview.tsx b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerPreview.tsx new file mode 100644 index 00000000..8a48ed31 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/components/TriggerPreview.tsx @@ -0,0 +1,103 @@ +/** + * TriggerPreview - Displays test results for a trigger. + * Used by both TriggerCard and AddTriggerForm. + */ + +import { AlertTriangle, Loader2 } from 'lucide-react'; + +import type { PreviewResult } from '../types'; +import type { TriggerTestResult } from '@renderer/types/data'; + +interface TriggerPreviewProps { + previewResult: PreviewResult | null; + loading?: boolean; + onTest: () => void; + onViewSession: (error: TriggerTestResult['errors'][0]) => void; + /** Whether this is inside a form (affects button type) */ + isFormContext?: boolean; +} + +export const TriggerPreview = ({ + previewResult, + loading, + onTest, + onViewSession, + isFormContext = false, +}: Readonly): React.JSX.Element => { + const isLoading = loading ?? previewResult?.loading; + + // Safeguard: ensure count is at least the errors array length (handles edge cases where totalCount is 0 but errors exist) + const effectiveCount = previewResult + ? Math.max(previewResult.totalCount, previewResult.errors.length) + : 0; + + return ( +
    +
    + Preview + +
    + + {previewResult && !previewResult.loading && ( +
    +

    + + {previewResult.truncated && effectiveCount >= 10_000 ? '10,000+' : effectiveCount} + {' '} + errors would have been detected +

    + + {/* Truncation warning - only shown when timeout or count limit hit */} + {previewResult.truncated && ( +
    + + + Search stopped early (timeout or count limit). Actual matches may be higher. + +
    + )} + + {previewResult.errors.slice(0, 10).map((error, idx) => ( +
    +
    + {error.context.projectName} + | + + {error.message.length > 60 ? `${error.message.slice(0, 60)}...` : error.message} + +
    + +
    + ))} + + {effectiveCount > 10 && ( +

    ...and {effectiveCount - 10} more

    + )} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormHandlers.ts b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormHandlers.ts new file mode 100644 index 00000000..72963cd1 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormHandlers.ts @@ -0,0 +1,218 @@ +/** + * Hook for AddTriggerForm event handlers. + * Extracts handler logic from AddTriggerForm for mode changes, content type changes, etc. + */ + +import { useCallback } from 'react'; + +import { getAvailableMatchFields } from '../utils/trigger'; + +import type { AddTriggerFormStateReturn } from './useAddTriggerFormState'; +import type { RepositoryDropdownItem } from '@renderer/components/settings/hooks/useSettingsConfig'; +import type { + NotificationTrigger, + TriggerContentType, + TriggerMatchField, + TriggerMode, +} from '@renderer/types/data'; + +interface UseAddTriggerFormHandlersOptions { + formState: AddTriggerFormStateReturn; + validatePattern: (pattern: string) => boolean; + clearPreview: () => void; +} + +export interface AddTriggerFormHandlersReturn { + handleModeChange: (newMode: TriggerMode) => void; + handleContentTypeChange: (newContentType: TriggerContentType) => void; + handleToolNameChange: (newToolName: string) => void; + handleAddRepository: (item: RepositoryDropdownItem) => void; + handleRemoveIgnorePattern: (idx: number) => void; + handleAddIgnorePattern: (pattern: string) => void; + handleRemoveRepository: (idx: number) => void; + handleMatchPatternChange: (value: string) => void; + handleTokenThresholdChange: (value: string) => void; + handleCancel: () => void; + buildNewTrigger: (generateId: () => string) => Omit; +} + +/** + * Hook for managing AddTriggerForm event handlers. + */ +export function useAddTriggerFormHandlers({ + formState, + validatePattern, + clearPreview, +}: UseAddTriggerFormHandlersOptions): AddTriggerFormHandlersReturn { + const { + name, + toolName, + mode, + contentType, + matchField, + matchPattern, + tokenThreshold, + tokenType, + ignorePatterns, + repositoryIds, + color, + setMode, + setContentType, + setToolName, + setMatchField, + setMatchPattern, + setTokenThreshold, + setIgnorePatterns, + setRepositoryIds, + setIsExpanded, + resetForm, + } = formState; + + // When mode changes, adjust content type defaults + const handleModeChange = useCallback( + (newMode: TriggerMode) => { + setMode(newMode); + if (newMode === 'error_status') { + setContentType('tool_result'); + } + }, + [setMode, setContentType] + ); + + // When content type changes, reset matchField to first available option + const handleContentTypeChange = useCallback( + (newContentType: TriggerContentType) => { + setContentType(newContentType); + const newMatchFields = getAvailableMatchFields(newContentType, toolName || undefined); + setMatchField(newMatchFields[0]?.value || ''); + // Reset tool name if not applicable + if (newContentType !== 'tool_use' && newContentType !== 'tool_result') { + setToolName(''); + } + }, + [toolName, setContentType, setMatchField, setToolName] + ); + + // When tool name changes, reset matchField to first available option + const handleToolNameChange = useCallback( + (newToolName: string) => { + setToolName(newToolName); + const newMatchFields = getAvailableMatchFields(contentType, newToolName || undefined); + setMatchField(newMatchFields[0]?.value || ''); + }, + [contentType, setToolName, setMatchField] + ); + + // Handler for adding repository + const handleAddRepository = useCallback( + (item: RepositoryDropdownItem) => { + if (!repositoryIds.includes(item.id)) { + setRepositoryIds([...repositoryIds, item.id]); + } + }, + [repositoryIds, setRepositoryIds] + ); + + // Handler for removing ignore pattern + const handleRemoveIgnorePattern = useCallback( + (idx: number) => { + const newPatterns = [...ignorePatterns]; + newPatterns.splice(idx, 1); + setIgnorePatterns(newPatterns); + }, + [ignorePatterns, setIgnorePatterns] + ); + + // Handler for adding ignore pattern + const handleAddIgnorePattern = useCallback( + (pattern: string) => { + setIgnorePatterns([...ignorePatterns, pattern]); + }, + [ignorePatterns, setIgnorePatterns] + ); + + // Handler for removing repository + const handleRemoveRepository = useCallback( + (idx: number) => { + const newIds = [...repositoryIds]; + newIds.splice(idx, 1); + setRepositoryIds(newIds); + }, + [repositoryIds, setRepositoryIds] + ); + + // Handler for match pattern change with validation + const handleMatchPatternChange = useCallback( + (value: string) => { + setMatchPattern(value); + validatePattern(value); + }, + [setMatchPattern, validatePattern] + ); + + // Handler for token threshold change + const handleTokenThresholdChange = useCallback( + (value: string) => { + const val = value.replace(/\D/g, ''); + setTokenThreshold(parseInt(val) || 0); + }, + [setTokenThreshold] + ); + + // Handler for cancel button + const handleCancel = useCallback(() => { + resetForm(); + clearPreview(); + setIsExpanded(false); + }, [resetForm, clearPreview, setIsExpanded]); + + // Build new trigger object from form state + const buildNewTrigger = useCallback( + (generateId: () => string): Omit => { + return { + id: `custom-${generateId()}`, + name: name.trim(), + enabled: true, + contentType, + mode, + ...(mode === 'error_status' && { requireError: true }), + ...(mode === 'content_match' && + matchField && { matchField: matchField as TriggerMatchField }), + ...(mode === 'content_match' && matchPattern && { matchPattern }), + ...(mode === 'token_threshold' && { tokenThreshold, tokenType }), + ...((contentType === 'tool_use' || contentType === 'tool_result') && + toolName && { toolName }), + ...(ignorePatterns.length > 0 && { ignorePatterns }), + ...(repositoryIds.length > 0 && { repositoryIds }), + color, + }; + }, + [ + name, + contentType, + mode, + matchField, + matchPattern, + tokenThreshold, + tokenType, + toolName, + ignorePatterns, + repositoryIds, + color, + ] + ); + + return { + handleModeChange, + handleContentTypeChange, + handleToolNameChange, + handleAddRepository, + handleRemoveIgnorePattern, + handleAddIgnorePattern, + handleRemoveRepository, + handleMatchPatternChange, + handleTokenThresholdChange, + handleCancel, + buildNewTrigger, + }; +} diff --git a/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormState.ts b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormState.ts new file mode 100644 index 00000000..a6042107 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useAddTriggerFormState.ts @@ -0,0 +1,135 @@ +/** + * Hook for AddTriggerForm state management. + * Extracts all useState calls and resetForm logic from AddTriggerForm. + */ + +import { useCallback, useState } from 'react'; + +import type { TriggerContentType, TriggerMode, TriggerTokenType } from '@renderer/types/data'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +interface AddTriggerFormState { + // Section 1: General Info + name: string; + toolName: string; + + // Section 2: Trigger Condition + mode: TriggerMode; + + // Section 3: Dynamic Configuration + // Content match settings + contentType: TriggerContentType; + matchField: string; + matchPattern: string; + + // Token threshold settings + tokenThreshold: number; + tokenType: TriggerTokenType; + + // Section 4: Advanced + ignorePatterns: string[]; + + // Section 5: Repository Scope + repositoryIds: string[]; + + // Display + color: TriggerColor; + + // UI state + isExpanded: boolean; +} + +export interface AddTriggerFormStateReturn extends AddTriggerFormState { + setName: (name: string) => void; + setToolName: (toolName: string) => void; + setMode: (mode: TriggerMode) => void; + setContentType: (contentType: TriggerContentType) => void; + setMatchField: (matchField: string) => void; + setMatchPattern: (matchPattern: string) => void; + setTokenThreshold: (threshold: number) => void; + setTokenType: (tokenType: TriggerTokenType) => void; + setIgnorePatterns: (patterns: string[]) => void; + setRepositoryIds: (ids: string[]) => void; + setColor: (color: TriggerColor) => void; + setIsExpanded: (expanded: boolean) => void; + resetForm: () => void; +} + +/** + * Hook for managing AddTriggerForm state. + */ +export function useAddTriggerFormState(): AddTriggerFormStateReturn { + // Section 1: General Info + const [name, setName] = useState(''); + const [toolName, setToolName] = useState(''); + + // Section 2: Trigger Condition + const [mode, setMode] = useState('error_status'); + + // Section 3: Dynamic Configuration + // Content match settings + const [contentType, setContentType] = useState('tool_result'); + const [matchField, setMatchField] = useState('content'); + const [matchPattern, setMatchPattern] = useState(''); + + // Token threshold settings + const [tokenThreshold, setTokenThreshold] = useState(1000); + const [tokenType, setTokenType] = useState('total'); + + // Section 4: Advanced + const [ignorePatterns, setIgnorePatterns] = useState([]); + + // Section 5: Repository Scope + const [repositoryIds, setRepositoryIds] = useState([]); + + // Display + const [color, setColor] = useState('red'); + + // UI state + const [isExpanded, setIsExpanded] = useState(false); + + const resetForm = useCallback(() => { + setName(''); + setToolName(''); + setMode('error_status'); + setContentType('tool_result'); + setMatchField('content'); + setMatchPattern(''); + setTokenThreshold(1000); + setTokenType('total'); + setIgnorePatterns([]); + setRepositoryIds([]); + // Intentionally do NOT reset color — preserve last-used color across triggers + }, []); + + return { + // State values + name, + toolName, + mode, + contentType, + matchField, + matchPattern, + tokenThreshold, + tokenType, + ignorePatterns, + repositoryIds, + color, + isExpanded, + + // Setters + setName, + setToolName, + setMode, + setContentType, + setMatchField, + setMatchPattern, + setTokenThreshold, + setTokenType, + setIgnorePatterns, + setRepositoryIds, + setColor, + setIsExpanded, + resetForm, + }; +} diff --git a/src/renderer/components/settings/NotificationTriggerSettings/hooks/useRepositoryLookup.ts b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useRepositoryLookup.ts new file mode 100644 index 00000000..dbaec754 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useRepositoryLookup.ts @@ -0,0 +1,47 @@ +/** + * Hook to convert repository IDs to RepositoryDropdownItem[] for display. + * Used by TriggerCard and AddTriggerForm to show selected repositories. + */ + +import { useMemo } from 'react'; + +import { useStore } from '@renderer/store'; + +import type { RepositoryDropdownItem } from '@renderer/components/settings/hooks/useSettingsConfig'; + +/** + * Converts an array of repository IDs to RepositoryDropdownItem[] for display. + * Searches repository groups to find matching repositories. + */ +export function useRepositoryLookup(repositoryIds: string[]): RepositoryDropdownItem[] { + const repositoryGroups = useStore((state) => state.repositoryGroups); + + return useMemo((): RepositoryDropdownItem[] => { + const items: RepositoryDropdownItem[] = []; + + for (const repositoryId of repositoryIds) { + // Find repository group by ID + const group = repositoryGroups.find((g) => g.id === repositoryId); + if (group) { + items.push({ + id: group.id, + name: group.name, + path: group.worktrees[0]?.path ?? '', + worktreeCount: group.worktrees.length, + totalSessions: group.totalSessions, + }); + } else { + // If not found, create a placeholder item + items.push({ + id: repositoryId, + name: repositoryId, + path: '', + worktreeCount: 0, + totalSessions: 0, + }); + } + } + + return items; + }, [repositoryIds, repositoryGroups]); +} diff --git a/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerCardState.ts b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerCardState.ts new file mode 100644 index 00000000..3779ec0f --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerCardState.ts @@ -0,0 +1,281 @@ +/** + * Hook for TriggerCard local state and callback handlers. + * Extracts state management logic from TriggerCard component. + */ + +import { useCallback, useState } from 'react'; + +import { deriveMode, getAvailableMatchFields } from '../utils/trigger'; + +import type { RepositoryDropdownItem } from '@renderer/components/settings/hooks/useSettingsConfig'; +import type { + NotificationTrigger, + TriggerContentType, + TriggerMatchField, + TriggerMode, + TriggerTokenType, +} from '@renderer/types/data'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +interface UseTriggerCardStateOptions { + trigger: NotificationTrigger; + onUpdate: (updates: Partial) => Promise; + validatePattern: (pattern: string) => boolean; +} + +interface UseTriggerCardStateReturn { + // UI state + isExpanded: boolean; + setIsExpanded: (value: boolean) => void; + editingName: boolean; + setEditingName: (value: boolean) => void; + + // Local form values + localName: string; + setLocalName: (value: string) => void; + localPattern: string; + localMode: TriggerMode; + localTokenThreshold: number; + localTokenType: TriggerTokenType; + + // Handlers + handleToggleEnabled: () => void; + handleNameSave: () => void; + handlePatternBlur: () => void; + handlePatternChange: (value: string) => void; + handleContentTypeChange: (value: TriggerContentType) => void; + handleToolNameChange: (value: string) => void; + handleMatchFieldChange: (value: string) => void; + handleModeChange: (newMode: TriggerMode) => void; + handleTokenThresholdChange: (value: number) => void; + handleTokenThresholdBlur: () => void; + handleTokenTypeChange: (value: TriggerTokenType) => void; + handleAddIgnorePattern: (pattern: string) => void; + handleRemoveIgnorePattern: (index: number) => void; + handleAddRepository: (item: RepositoryDropdownItem) => void; + handleRemoveRepository: (index: number) => void; + handleColorChange: (color: TriggerColor) => void; +} + +/** + * Manages TriggerCard local state and provides memoized callback handlers. + */ +export function useTriggerCardState({ + trigger, + onUpdate, + validatePattern, +}: UseTriggerCardStateOptions): UseTriggerCardStateReturn { + // UI state + const [isExpanded, setIsExpanded] = useState(false); + const [editingName, setEditingName] = useState(false); + + // Local form values + const [localName, setLocalName] = useState(trigger.name); + const [localPattern, setLocalPattern] = useState(trigger.matchPattern ?? ''); + const [localMode, setLocalMode] = useState(deriveMode(trigger)); + const [localTokenThreshold, setLocalTokenThreshold] = useState( + trigger.tokenThreshold ?? 1000 + ); + const [localTokenType, setLocalTokenType] = useState( + trigger.tokenType ?? 'total' + ); + + // Toggle enabled/disabled + const handleToggleEnabled = useCallback(() => { + void onUpdate({ enabled: !trigger.enabled }); + }, [trigger.enabled, onUpdate]); + + // Save name on blur or Enter + const handleNameSave = useCallback(() => { + if (localName.trim() && localName !== trigger.name) { + void onUpdate({ name: localName.trim() }); + } + setEditingName(false); + }, [localName, trigger.name, onUpdate]); + + // Save pattern on blur + const handlePatternBlur = useCallback(() => { + if (validatePattern(localPattern) && localPattern !== trigger.matchPattern) { + void onUpdate({ matchPattern: localPattern }); + } + }, [localPattern, trigger.matchPattern, onUpdate, validatePattern]); + + // Update local pattern and validate + const handlePatternChange = useCallback( + (value: string) => { + setLocalPattern(value); + validatePattern(value); + }, + [validatePattern] + ); + + // Content type change - reset matchField to first available + const handleContentTypeChange = useCallback( + (value: TriggerContentType) => { + const newMatchFields = getAvailableMatchFields(value, trigger.toolName ?? undefined); + const newMatchField = newMatchFields[0]?.value ?? ''; + const updates: Partial = { + contentType: value, + matchField: (newMatchField as TriggerMatchField) || undefined, + }; + // Reset tool name if not applicable + if (value !== 'tool_use' && value !== 'tool_result') { + updates.toolName = undefined; + } + void onUpdate(updates); + }, + [onUpdate, trigger.toolName] + ); + + // Tool name change - reset matchField to first available + const handleToolNameChange = useCallback( + (value: string) => { + const newMatchFields = getAvailableMatchFields(trigger.contentType, value || undefined); + const newMatchField = newMatchFields[0]?.value ?? ''; + void onUpdate({ + toolName: value || undefined, + matchField: (newMatchField as TriggerMatchField) || undefined, + }); + }, + [onUpdate, trigger.contentType] + ); + + // Match field change + const handleMatchFieldChange = useCallback( + (value: string) => { + void onUpdate({ matchField: value as TriggerMatchField }); + }, + [onUpdate] + ); + + // Mode change with appropriate defaults + const handleModeChange = useCallback( + (newMode: TriggerMode) => { + setLocalMode(newMode); + const updates: Partial = { mode: newMode }; + + if (newMode === 'error_status') { + updates.requireError = true; + updates.contentType = 'tool_result'; + } else if (newMode === 'content_match') { + // Ensure matchField is set for validation + const contentType = trigger.contentType ?? 'tool_result'; + const matchFields = getAvailableMatchFields(contentType, trigger.toolName ?? undefined); + if (!trigger.matchField && matchFields.length > 0) { + updates.matchField = matchFields[0].value as TriggerMatchField; + } + } else if (newMode === 'token_threshold') { + updates.tokenThreshold = localTokenThreshold; + updates.tokenType = localTokenType; + } + + void onUpdate(updates); + }, + [ + onUpdate, + localTokenThreshold, + localTokenType, + trigger.contentType, + trigger.toolName, + trigger.matchField, + ] + ); + + // Token threshold change — local only, commit on blur + const handleTokenThresholdChange = useCallback((value: number) => { + setLocalTokenThreshold(value); + }, []); + + // Commit token threshold to config + const handleTokenThresholdBlur = useCallback(() => { + if (localTokenThreshold !== (trigger.tokenThreshold ?? 1000)) { + void onUpdate({ tokenThreshold: localTokenThreshold }); + } + }, [localTokenThreshold, trigger.tokenThreshold, onUpdate]); + + // Token type change + const handleTokenTypeChange = useCallback( + (value: TriggerTokenType) => { + setLocalTokenType(value); + void onUpdate({ tokenType: value }); + }, + [onUpdate] + ); + + // Add ignore pattern + const handleAddIgnorePattern = useCallback( + (pattern: string) => { + const newPatterns = [...(trigger.ignorePatterns ?? []), pattern]; + void onUpdate({ ignorePatterns: newPatterns }); + }, + [trigger.ignorePatterns, onUpdate] + ); + + // Remove ignore pattern + const handleRemoveIgnorePattern = useCallback( + (index: number) => { + const newPatterns = [...(trigger.ignorePatterns ?? [])]; + newPatterns.splice(index, 1); + void onUpdate({ ignorePatterns: newPatterns }); + }, + [trigger.ignorePatterns, onUpdate] + ); + + // Add repository + const handleAddRepository = useCallback( + (item: RepositoryDropdownItem) => { + const currentIds = trigger.repositoryIds ?? []; + if (!currentIds.includes(item.id)) { + void onUpdate({ repositoryIds: [...currentIds, item.id] }); + } + }, + [trigger.repositoryIds, onUpdate] + ); + + // Remove repository + const handleRemoveRepository = useCallback( + (index: number) => { + const newIds = [...(trigger.repositoryIds ?? [])]; + newIds.splice(index, 1); + void onUpdate({ repositoryIds: newIds }); + }, + [trigger.repositoryIds, onUpdate] + ); + + // Color change + const handleColorChange = useCallback( + (color: TriggerColor) => { + void onUpdate({ color }); + }, + [onUpdate] + ); + + return { + isExpanded, + setIsExpanded, + editingName, + setEditingName, + localName, + setLocalName, + localPattern, + localMode, + localTokenThreshold, + localTokenType, + handleToggleEnabled, + handleNameSave, + handlePatternBlur, + handlePatternChange, + handleContentTypeChange, + handleToolNameChange, + handleMatchFieldChange, + handleModeChange, + handleTokenThresholdChange, + handleTokenThresholdBlur, + handleTokenTypeChange, + handleAddIgnorePattern, + handleRemoveIgnorePattern, + handleAddRepository, + handleRemoveRepository, + handleColorChange, + }; +} diff --git a/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerForm.ts b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerForm.ts new file mode 100644 index 00000000..99c03d8f --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/hooks/useTriggerForm.ts @@ -0,0 +1,184 @@ +/** + * Hook for shared form state and validation logic used by TriggerCard and AddTriggerForm. + */ + +import { useCallback, useState } from 'react'; + +import { useStore } from '@renderer/store'; +import { createLogger } from '@shared/utils/logger'; + +const logger = createLogger('Component:TriggerForm'); + +import { generateId, validateRegexPattern } from '../utils/trigger'; + +import type { PreviewResult } from '../types'; +import type { + NotificationTrigger, + TriggerMatchField, + TriggerMode, + TriggerTestResult, + TriggerTokenType, +} from '@renderer/types/data'; + +interface UseTriggerFormOptions { + /** Initial trigger for editing mode, or undefined for new trigger creation */ + trigger?: NotificationTrigger; + /** Callback when trigger is updated (for edit mode) */ + onUpdate?: (updates: Partial) => Promise; +} + +interface UseTriggerFormReturn { + // Pattern validation + patternError: string | null; + validatePattern: (pattern: string) => boolean; + + // Preview/test functionality + previewResult: PreviewResult | null; + handleTestTrigger: (trigger: NotificationTrigger) => Promise; + handleViewSession: (error: TriggerTestResult['errors'][0]) => void; + clearPreview: () => void; + + // Build trigger for testing (used by AddTriggerForm) + buildTriggerForTest: (formState: { + name: string; + contentType: NotificationTrigger['contentType']; + mode: TriggerMode; + matchField?: string; + matchPattern?: string; + tokenThreshold?: number; + tokenType?: TriggerTokenType; + toolName?: string; + ignorePatterns?: string[]; + repositoryIds?: string[]; + }) => NotificationTrigger; +} + +/** + * Shared form state and validation logic for trigger forms. + */ +export function useTriggerForm(_options: UseTriggerFormOptions = {}): UseTriggerFormReturn { + const [patternError, setPatternError] = useState(null); + const [previewResult, setPreviewResult] = useState(null); + + // Get navigateToError from store for View Session functionality + const navigateToError = useStore((state) => state.navigateToError); + + /** + * Validate a regex pattern. + */ + const validatePattern = useCallback((pattern: string): boolean => { + const error = validateRegexPattern(pattern); + setPatternError(error); + return error === null; + }, []); + + /** + * Clear the preview result. + */ + const clearPreview = useCallback(() => { + setPreviewResult(null); + }, []); + + /** + * Test trigger against historical data. + * Results are automatically limited by the main process to prevent resource exhaustion: + * - Max 50 errors returned + * - Max 10,000 totalCount + * - Max 100 sessions scanned + * - 30 second timeout + */ + const handleTestTrigger = useCallback(async (trigger: NotificationTrigger) => { + setPreviewResult({ loading: true, totalCount: 0, errors: [] }); + + try { + const result = await window.electronAPI.config.testTrigger(trigger); + setPreviewResult({ + loading: false, + totalCount: result.totalCount, + errors: result.errors, + truncated: result.truncated, + }); + } catch (error) { + logger.error('Failed to test trigger:', error); + setPreviewResult(null); + } + }, []); + + /** + * Handle View Session click - navigate to the error location. + */ + const handleViewSession = useCallback( + (error: TriggerTestResult['errors'][0]) => { + navigateToError({ + id: error.id, + sessionId: error.sessionId, + projectId: error.projectId, + message: error.message, + timestamp: error.timestamp, + source: error.source, + filePath: '', + context: error.context, + isRead: true, + createdAt: error.timestamp, + // Deep linking data for exact error position + toolUseId: error.toolUseId, + subagentId: error.subagentId, + lineNumber: error.lineNumber, + }); + }, + [navigateToError] + ); + + /** + * Build a trigger object from form state for testing. + */ + const buildTriggerForTest = useCallback( + (formState: { + name: string; + contentType: NotificationTrigger['contentType']; + mode: TriggerMode; + matchField?: string; + matchPattern?: string; + tokenThreshold?: number; + tokenType?: TriggerTokenType; + toolName?: string; + ignorePatterns?: string[]; + repositoryIds?: string[]; + }): NotificationTrigger => { + return { + id: `test-${generateId()}`, + name: formState.name.trim() || 'Test Trigger', + enabled: true, + contentType: formState.contentType, + mode: formState.mode, + isBuiltin: false, + ...(formState.mode === 'error_status' && { requireError: true }), + ...(formState.mode === 'content_match' && + formState.matchField && { matchField: formState.matchField as TriggerMatchField }), + ...(formState.mode === 'content_match' && + formState.matchPattern && { matchPattern: formState.matchPattern }), + ...(formState.mode === 'token_threshold' && { + tokenThreshold: formState.tokenThreshold, + tokenType: formState.tokenType, + }), + ...((formState.contentType === 'tool_use' || formState.contentType === 'tool_result') && + formState.toolName && { toolName: formState.toolName }), + ...(formState.ignorePatterns && + formState.ignorePatterns.length > 0 && { ignorePatterns: formState.ignorePatterns }), + ...(formState.repositoryIds && + formState.repositoryIds.length > 0 && { repositoryIds: formState.repositoryIds }), + }; + }, + [] + ); + + return { + patternError, + validatePattern, + previewResult, + handleTestTrigger, + handleViewSession, + clearPreview, + buildTriggerForTest, + }; +} diff --git a/src/renderer/components/settings/NotificationTriggerSettings/index.tsx b/src/renderer/components/settings/NotificationTriggerSettings/index.tsx new file mode 100644 index 00000000..c0ba60f3 --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/index.tsx @@ -0,0 +1,88 @@ +/** + * NotificationTriggerSettings - Component for managing notification triggers. + * Allows users to configure when notifications should be generated. + * + * Uses intent-first design pattern with 4 sections: + * 1. General Info (always visible) + * 2. Trigger Condition (mode selector) + * 3. Dynamic Configuration (based on mode) + * 4. Advanced (collapsible) + */ + +import { AddTriggerForm } from './components/AddTriggerForm'; +import { SectionHeader } from './components/SectionHeader'; +import { TriggerCard } from './components/TriggerCard'; + +import type { NotificationTriggerSettingsProps } from './types'; + +// Stable no-op function for builtin triggers that can't be removed +const noopRemove = (_triggerId: string): Promise => Promise.resolve(); + +/** + * Main component for managing notification triggers. + */ +export const NotificationTriggerSettings = ({ + triggers, + saving, + onUpdateTrigger, + onAddTrigger, + onRemoveTrigger, +}: Readonly): React.JSX.Element => { + // Separate builtin and custom triggers + const builtinTriggers = triggers.filter((t) => t.isBuiltin); + const customTriggers = triggers.filter((t) => !t.isBuiltin); + + return ( +
    + {/* Builtin Triggers */} + {builtinTriggers.length > 0 && ( +
    + +

    + Default triggers that come with the application. You can enable/disable them and + customize their patterns. +

    +
    + {builtinTriggers.map((trigger) => ( + + ))} +
    +
    + )} + + {/* Custom Triggers */} +
    + +

    + Create your own triggers to get notified for specific patterns or tool outputs. +

    + + {customTriggers.length > 0 && ( +
    + {customTriggers.map((trigger) => ( + + ))} +
    + )} + + {customTriggers.length === 0 && ( +

    No custom triggers configured yet.

    + )} + + +
    +
    + ); +}; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/types.ts b/src/renderer/components/settings/NotificationTriggerSettings/types.ts new file mode 100644 index 00000000..c927dbbe --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/types.ts @@ -0,0 +1,39 @@ +/** + * Local type definitions for NotificationTriggerSettings components. + */ + +import type { NotificationTrigger, TriggerMode, TriggerTestResult } from '@renderer/types/data'; + +/** + * Preview result state for a trigger test. + */ +export interface PreviewResult { + loading: boolean; + totalCount: number; + errors: TriggerTestResult['errors']; + /** + * True if results were truncated due to safety limits. + * When truncated, totalCount may be capped at 10,000. + */ + truncated?: boolean; +} + +/** + * Mode configuration for the segmented control. + */ +export interface ModeConfig { + value: TriggerMode; + label: string; + icon: React.ComponentType<{ className?: string }>; +} + +/** + * Props for the main NotificationTriggerSettings component. + */ +export interface NotificationTriggerSettingsProps { + triggers: NotificationTrigger[]; + saving: boolean; + onUpdateTrigger: (triggerId: string, updates: Partial) => Promise; + onAddTrigger: (trigger: Omit) => Promise; + onRemoveTrigger: (triggerId: string) => Promise; +} diff --git a/src/renderer/components/settings/NotificationTriggerSettings/utils/constants.ts b/src/renderer/components/settings/NotificationTriggerSettings/utils/constants.ts new file mode 100644 index 00000000..70e6975e --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/utils/constants.ts @@ -0,0 +1,50 @@ +/** + * Constants for NotificationTriggerSettings. + */ + +import { Activity, AlertCircle, Search } from 'lucide-react'; + +import type { ModeConfig } from '../types'; +import type { TriggerContentType, TriggerToolName } from '@renderer/types/data'; + +/** + * Content type options for dropdown. + */ +export const CONTENT_TYPE_OPTIONS: { value: TriggerContentType; label: string }[] = [ + { value: 'tool_result', label: 'Tool Result' }, + { value: 'tool_use', label: 'Tool Use' }, + { value: 'thinking', label: 'Thinking' }, + { value: 'text', label: 'Text Output' }, +]; + +/** + * Tool name options for dropdown. + */ +export const TOOL_NAME_OPTIONS: { value: TriggerToolName; label: string }[] = [ + { value: '', label: 'Any Tool' }, + { value: 'Bash', label: 'Bash' }, + { value: 'Task', label: 'Task' }, + { value: 'Read', label: 'Read' }, + { value: 'Write', label: 'Write' }, + { value: 'Edit', label: 'Edit' }, + { value: 'Grep', label: 'Grep' }, + { value: 'Glob', label: 'Glob' }, + { value: 'WebFetch', label: 'WebFetch' }, + { value: 'WebSearch', label: 'WebSearch' }, + { value: 'LSP', label: 'LSP' }, + { value: 'TodoWrite', label: 'TodoWrite' }, + { value: 'Skill', label: 'Skill' }, + { value: 'NotebookEdit', label: 'NotebookEdit' }, + { value: 'AskUserQuestion', label: 'AskUserQuestion' }, + { value: 'KillShell', label: 'KillShell' }, + { value: 'TaskOutput', label: 'TaskOutput' }, +]; + +/** + * Mode options for the trigger mode selector. + */ +export const MODE_OPTIONS: ModeConfig[] = [ + { value: 'error_status', label: 'Execution Error', icon: AlertCircle }, + { value: 'content_match', label: 'Content Pattern', icon: Search }, + { value: 'token_threshold', label: 'High Token Usage', icon: Activity }, +]; diff --git a/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts b/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts new file mode 100644 index 00000000..fb9803ae --- /dev/null +++ b/src/renderer/components/settings/NotificationTriggerSettings/utils/trigger.ts @@ -0,0 +1,112 @@ +/** + * Utility functions for notification triggers. + */ + +import type { NotificationTrigger, TriggerContentType, TriggerMode } from '@renderer/types/data'; + +/** + * Generates a UUID v4 for new triggers. + */ +export function generateId(): string { + return crypto.randomUUID(); +} + +/** + * Get available match fields based on content type and tool name. + */ +export function getAvailableMatchFields( + contentType: TriggerContentType, + toolName?: string +): { value: string; label: string }[] { + if (contentType === 'tool_result') { + return [{ value: 'content', label: 'Content' }]; + } + + if (contentType === 'thinking') { + return [{ value: 'thinking', label: 'Thinking Content' }]; + } + + if (contentType === 'text') { + return [{ value: 'text', label: 'Text Content' }]; + } + + if (contentType === 'tool_use') { + switch (toolName) { + case 'Bash': + return [ + { value: 'command', label: 'Command' }, + { value: 'description', label: 'Description' }, + ]; + case 'Task': + return [ + { value: 'description', label: 'Description' }, + { value: 'prompt', label: 'Prompt' }, + { value: 'subagent_type', label: 'Subagent Type' }, + ]; + case 'Read': + case 'Write': + return [{ value: 'file_path', label: 'File Path' }]; + case 'Edit': + return [ + { value: 'file_path', label: 'File Path' }, + { value: 'old_string', label: 'Old String' }, + { value: 'new_string', label: 'New String' }, + ]; + case 'Glob': + return [ + { value: 'pattern', label: 'Pattern' }, + { value: 'path', label: 'Path' }, + ]; + case 'Grep': + return [ + { value: 'pattern', label: 'Pattern' }, + { value: 'path', label: 'Path' }, + { value: 'glob', label: 'Glob Filter' }, + ]; + case 'WebFetch': + return [ + { value: 'url', label: 'URL' }, + { value: 'prompt', label: 'Prompt' }, + ]; + case 'WebSearch': + return [{ value: 'query', label: 'Query' }]; + case 'Skill': + return [ + { value: 'skill', label: 'Skill Name' }, + { value: 'args', label: 'Arguments' }, + ]; + default: + return []; + } + } + + return []; +} + +/** + * Derive the effective mode from trigger configuration for backward compatibility. + */ +export function deriveMode(trigger: NotificationTrigger): TriggerMode { + if (trigger.mode) return trigger.mode; + // Backward compatibility: if requireError is true and no mode, default to error_status + if (trigger.requireError && trigger.contentType === 'tool_result') { + return 'error_status'; + } + return 'content_match'; +} + +/** + * Validates a regex pattern. + * @returns null if valid, error message if invalid + */ +export function validateRegexPattern(pattern: string): string | null { + if (!pattern) { + return null; + } + try { + new RegExp(pattern); + return null; + } catch { + return 'Invalid regex pattern'; + } +} diff --git a/src/renderer/components/settings/SettingsTabs.tsx b/src/renderer/components/settings/SettingsTabs.tsx new file mode 100644 index 00000000..1efcd72f --- /dev/null +++ b/src/renderer/components/settings/SettingsTabs.tsx @@ -0,0 +1,64 @@ +import { useState } from 'react'; + +import { Bell, Settings, Wrench } from 'lucide-react'; + +export type SettingsSection = 'general' | 'notifications' | 'advanced'; + +interface SettingsTabsProps { + activeSection: SettingsSection; + onSectionChange: (section: SettingsSection) => void; +} + +interface TabConfig { + id: SettingsSection; + label: string; + icon: React.ComponentType<{ className?: string }>; +} + +const tabs: TabConfig[] = [ + { id: 'general', label: 'General', icon: Settings }, + { id: 'notifications', label: 'Notifications', icon: Bell }, + { id: 'advanced', label: 'Advanced', icon: Wrench }, +]; + +export const SettingsTabs = ({ + activeSection, + onSectionChange, +}: Readonly): React.JSX.Element => { + const [hoveredTab, setHoveredTab] = useState(null); + + return ( +
    + {tabs.map((tab) => { + const Icon = tab.icon; + const isActive = activeSection === tab.id; + const isHovered = hoveredTab === tab.id; + + const getTextColor = (): string => { + if (isActive) return 'var(--color-text)'; + if (isHovered) return 'var(--color-text-secondary)'; + return 'var(--color-text-muted)'; + }; + + return ( + + ); + })} +
    + ); +}; diff --git a/src/renderer/components/settings/SettingsView.tsx b/src/renderer/components/settings/SettingsView.tsx new file mode 100644 index 00000000..3263500d --- /dev/null +++ b/src/renderer/components/settings/SettingsView.tsx @@ -0,0 +1,146 @@ +/** + * SettingsView - Main settings panel with all app configuration options. + * Provides UI for managing notifications, display settings, and advanced options. + */ + +import { useState } from 'react'; + +import { Loader2 } from 'lucide-react'; + +import { useSettingsConfig, useSettingsHandlers } from './hooks'; +import { AdvancedSection, GeneralSection, NotificationsSection } from './sections'; +import { type SettingsSection, SettingsTabs } from './SettingsTabs'; + +export const SettingsView = (): React.JSX.Element | null => { + const [activeSection, setActiveSection] = useState('general'); + + const { + config, + safeConfig, + loading, + saving, + error, + setError, + setSaving, + setConfig, + setOptimisticConfig, + updateConfig, + ignoredRepositoryItems, + excludedRepositoryIds, + isSnoozed, + } = useSettingsConfig(); + + const handlers = useSettingsHandlers({ + config, + setSaving, + setError, + setConfig, + setOptimisticConfig, + updateConfig, + }); + + // Loading state + if (loading) { + return ( +
    +
    + + Loading settings... +
    +
    + ); + } + + // Error state + if (error && !config) { + return ( +
    +
    +

    {error}

    + +
    +
    + ); + } + + if (!config) return null; + + return ( +
    +
    + {/* Header */} +
    +

    + Settings +

    +

    + Manage your app preferences +

    + {error && ( +
    +

    {error}

    +
    + )} +
    + + {/* Tabs */} + + + {/* Content */} +
    + {activeSection === 'general' && ( + + )} + + {activeSection === 'notifications' && ( + + )} + + {activeSection === 'advanced' && ( + + )} +
    +
    +
    + ); +}; diff --git a/src/renderer/components/settings/components/SettingRow.tsx b/src/renderer/components/settings/components/SettingRow.tsx new file mode 100644 index 00000000..d0353e34 --- /dev/null +++ b/src/renderer/components/settings/components/SettingRow.tsx @@ -0,0 +1,35 @@ +/** + * SettingRow - Setting row component for consistent layout. + * Linear-style clean row without icons. + */ + +interface SettingRowProps { + readonly label: string; + readonly description?: string; + readonly children: React.ReactNode; +} + +export const SettingRow = ({ + label, + description, + children, +}: SettingRowProps): React.JSX.Element => { + return ( +
    +
    +
    + {label} +
    + {description && ( +
    + {description} +
    + )} +
    +
    {children}
    +
    + ); +}; diff --git a/src/renderer/components/settings/components/SettingsSectionHeader.tsx b/src/renderer/components/settings/components/SettingsSectionHeader.tsx new file mode 100644 index 00000000..67887fd9 --- /dev/null +++ b/src/renderer/components/settings/components/SettingsSectionHeader.tsx @@ -0,0 +1,19 @@ +/** + * SettingsSectionHeader - Section header component. + * Linear-style subtle label. + */ + +interface SettingsSectionHeaderProps { + readonly title: string; +} + +export const SettingsSectionHeader = ({ title }: SettingsSectionHeaderProps): React.JSX.Element => { + return ( +

    + {title} +

    + ); +}; diff --git a/src/renderer/components/settings/components/SettingsSelect.tsx b/src/renderer/components/settings/components/SettingsSelect.tsx new file mode 100644 index 00000000..f8b53df0 --- /dev/null +++ b/src/renderer/components/settings/components/SettingsSelect.tsx @@ -0,0 +1,97 @@ +/** + * SettingsSelect - Custom dropdown select component with styled dropdown menu. + * Avoids browser default select styling for a consistent dark theme experience. + */ + +import { useEffect, useRef, useState } from 'react'; + +import { Check, ChevronDown } from 'lucide-react'; + +interface SettingsSelectProps { + readonly value: T; + readonly options: readonly { value: T; label: string }[]; + readonly onChange: (value: T) => void; + readonly disabled?: boolean; + readonly dropUp?: boolean; +} + +export const SettingsSelect = ({ + value, + options, + onChange, + disabled = false, + dropUp = false, +}: SettingsSelectProps): React.JSX.Element => { + const [isOpen, setIsOpen] = useState(false); + const containerRef = useRef(null); + + // Find current label + const currentLabel = options.find((opt) => opt.value === value)?.label ?? 'Select...'; + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent): void => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [isOpen]); + + const handleSelect = (optionValue: T): void => { + onChange(optionValue); + setIsOpen(false); + }; + + return ( +
    + {/* Trigger Button */} + + + {/* Dropdown Menu */} + {isOpen && ( +
    + {options.map((option) => ( + + ))} +
    + )} +
    + ); +}; diff --git a/src/renderer/components/settings/components/SettingsToggle.tsx b/src/renderer/components/settings/components/SettingsToggle.tsx new file mode 100644 index 00000000..1af51179 --- /dev/null +++ b/src/renderer/components/settings/components/SettingsToggle.tsx @@ -0,0 +1,45 @@ +/** + * SettingsToggle - Toggle switch component for boolean settings. + * Linear-style design with white thumb and focus ring. + */ + +interface SettingsToggleProps { + readonly enabled: boolean; + readonly onChange: (value: boolean) => void; + readonly disabled?: boolean; +} + +export const SettingsToggle = ({ + enabled, + onChange, + disabled = false, +}: SettingsToggleProps): React.JSX.Element => { + const handleClick = (): void => { + if (!disabled) { + onChange(!enabled); + } + }; + + return ( + + ); +}; diff --git a/src/renderer/components/settings/components/index.ts b/src/renderer/components/settings/components/index.ts new file mode 100644 index 00000000..33dada83 --- /dev/null +++ b/src/renderer/components/settings/components/index.ts @@ -0,0 +1,8 @@ +/** + * Settings shared components barrel export. + */ + +export { SettingRow } from './SettingRow'; +export { SettingsSectionHeader } from './SettingsSectionHeader'; +export { SettingsSelect } from './SettingsSelect'; +export { SettingsToggle } from './SettingsToggle'; diff --git a/src/renderer/components/settings/hooks/index.ts b/src/renderer/components/settings/hooks/index.ts new file mode 100644 index 00000000..97ad3e63 --- /dev/null +++ b/src/renderer/components/settings/hooks/index.ts @@ -0,0 +1,6 @@ +/** + * Settings hooks barrel export. + */ + +export { useSettingsConfig } from './useSettingsConfig'; +export { useSettingsHandlers } from './useSettingsHandlers'; diff --git a/src/renderer/components/settings/hooks/useSettingsConfig.ts b/src/renderer/components/settings/hooks/useSettingsConfig.ts new file mode 100644 index 00000000..064ca78a --- /dev/null +++ b/src/renderer/components/settings/hooks/useSettingsConfig.ts @@ -0,0 +1,228 @@ +/** + * useSettingsConfig - Hook for managing settings configuration state. + * Handles loading, saving, and providing safe defaults for config. + */ + +import { useCallback, useEffect, useMemo, useState } from 'react'; + +import { useStore } from '@renderer/store'; +import { useShallow } from 'zustand/react/shallow'; + +import type { AppConfig } from '@renderer/types/data'; + +// Get the setState function from the store to update appConfig globally +const setStoreState = useStore.setState; + +/** Repository item for ignored repositories list */ +export interface RepositoryDropdownItem { + id: string; + name: string; + path: string; + worktreeCount: number; + totalSessions: number; +} + +export interface SafeConfig { + general: { + launchAtLogin: boolean; + showDockIcon: boolean; + theme: 'dark' | 'light' | 'system'; + defaultTab: 'dashboard' | 'last-session'; + }; + notifications: { + enabled: boolean; + soundEnabled: boolean; + ignoredRegex: string[]; + ignoredRepositories: string[]; + snoozedUntil: number | null; + snoozeMinutes: number; + includeSubagentErrors: boolean; + triggers: AppConfig['notifications']['triggers']; + }; + display: { + showTimestamps: boolean; + compactMode: boolean; + syntaxHighlighting: boolean; + }; +} + +interface UseSettingsConfigReturn { + config: AppConfig | null; + safeConfig: SafeConfig; + loading: boolean; + saving: boolean; + error: string | null; + setError: (error: string | null) => void; + setSaving: (saving: boolean) => void; + setConfig: (config: AppConfig | null) => void; + setOptimisticConfig: React.Dispatch>; + updateConfig: ( + section: keyof AppConfig, + data: Partial + ) => Promise; + ignoredRepositoryItems: RepositoryDropdownItem[]; + excludedRepositoryIds: string[]; + isSnoozed: boolean; +} + +export function useSettingsConfig(): UseSettingsConfigReturn { + const { repositoryGroups, fetchRepositoryGroups } = useStore( + useShallow((s) => ({ + repositoryGroups: s.repositoryGroups, + fetchRepositoryGroups: s.fetchRepositoryGroups, + })) + ); + + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + + // Local optimistic state for immediate visual feedback on toggles + const [optimisticConfig, setOptimisticConfig] = useState(null); + + // Fetch config on mount + useEffect(() => { + const loadConfig = async (): Promise => { + try { + setLoading(true); + setError(null); + const loadedConfig = await window.electronAPI.config.get(); + setConfig(loadedConfig); + setOptimisticConfig(loadedConfig); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load settings'); + } finally { + setLoading(false); + } + }; + + void loadConfig(); + }, []); + + // Fetch repository groups for ignored repositories dropdown + useEffect(() => { + if (repositoryGroups.length === 0) { + void fetchRepositoryGroups(); + } + }, [repositoryGroups.length, fetchRepositoryGroups]); + + // Update a config section with optimistic update for immediate UI feedback + const updateConfig = useCallback( + async (section: keyof AppConfig, data: Partial) => { + // Optimistic update - immediately reflect the change in UI + setOptimisticConfig((prev) => { + if (!prev) return prev; + return { + ...prev, + [section]: { + ...prev[section], + ...data, + }, + }; + }); + + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.update(section, data); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + // Update global store so other components (like useTheme) see the change + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + // Revert optimistic update on error + setOptimisticConfig(config); + setError(err instanceof Error ? err.message : 'Failed to save settings'); + } finally { + setSaving(false); + } + }, + [config] + ); + + // Use optimistic config for UI display (falls back to config if not set) + const displayConfig = optimisticConfig ?? config; + + // Create safe config with defaults to prevent null reference errors + const safeConfig = useMemo( + (): SafeConfig => ({ + general: { + launchAtLogin: displayConfig?.general?.launchAtLogin ?? false, + showDockIcon: displayConfig?.general?.showDockIcon ?? true, + theme: displayConfig?.general?.theme ?? 'dark', + defaultTab: displayConfig?.general?.defaultTab ?? 'dashboard', + }, + notifications: { + enabled: displayConfig?.notifications?.enabled ?? true, + soundEnabled: displayConfig?.notifications?.soundEnabled ?? true, + ignoredRegex: displayConfig?.notifications?.ignoredRegex ?? [], + ignoredRepositories: displayConfig?.notifications?.ignoredRepositories ?? [], + snoozedUntil: displayConfig?.notifications?.snoozedUntil ?? null, + snoozeMinutes: displayConfig?.notifications?.snoozeMinutes ?? 30, + includeSubagentErrors: displayConfig?.notifications?.includeSubagentErrors ?? true, + triggers: displayConfig?.notifications?.triggers ?? [], + }, + display: { + showTimestamps: displayConfig?.display?.showTimestamps ?? true, + compactMode: displayConfig?.display?.compactMode ?? false, + syntaxHighlighting: displayConfig?.display?.syntaxHighlighting ?? true, + }, + }), + [displayConfig] + ); + + // Convert ignored repository IDs to RepositoryDropdownItem[] for display + const ignoredRepositoryItems = useMemo((): RepositoryDropdownItem[] => { + const items: RepositoryDropdownItem[] = []; + const ignoredRepositories = safeConfig.notifications.ignoredRepositories; + + for (const repositoryId of ignoredRepositories) { + // Find repository group by ID + const group = repositoryGroups.find((g) => g.id === repositoryId); + if (group) { + items.push({ + id: group.id, + name: group.name, + path: group.worktrees[0]?.path ?? '', + worktreeCount: group.worktrees.length, + totalSessions: group.totalSessions, + }); + } else { + // If not found, create a placeholder item + items.push({ + id: repositoryId, + name: repositoryId, + path: '', + worktreeCount: 0, + totalSessions: 0, + }); + } + } + + return items; + }, [safeConfig.notifications.ignoredRepositories, repositoryGroups]); + + // Get excluded repository IDs for dropdown + const excludedRepositoryIds = safeConfig.notifications.ignoredRepositories; + + // Check if snoozed + const isSnoozed = + safeConfig.notifications.snoozedUntil !== null && + safeConfig.notifications.snoozedUntil > Date.now(); + + return { + config, + safeConfig, + loading, + saving, + error, + setError, + setSaving, + setConfig, + setOptimisticConfig, + updateConfig, + ignoredRepositoryItems, + excludedRepositoryIds, + isSnoozed, + }; +} diff --git a/src/renderer/components/settings/hooks/useSettingsHandlers.ts b/src/renderer/components/settings/hooks/useSettingsHandlers.ts new file mode 100644 index 00000000..7638e683 --- /dev/null +++ b/src/renderer/components/settings/hooks/useSettingsHandlers.ts @@ -0,0 +1,391 @@ +/** + * useSettingsHandlers - Hook for all settings action handlers. + * Groups handlers by section for better organization. + */ + +import { useCallback, useRef } from 'react'; + +import { useStore } from '@renderer/store'; + +import type { RepositoryDropdownItem } from './useSettingsConfig'; +import type { AppConfig, NotificationTrigger } from '@renderer/types/data'; + +// Get the setState function from the store to update appConfig globally +const setStoreState = useStore.setState; + +interface UseSettingsHandlersProps { + config: AppConfig | null; + setSaving: (saving: boolean) => void; + setError: (error: string | null) => void; + setConfig: (config: AppConfig | null) => void; + setOptimisticConfig: React.Dispatch>; + updateConfig: ( + section: keyof AppConfig, + data: Partial + ) => Promise; +} + +interface SettingsHandlers { + // General handlers + handleGeneralToggle: (key: keyof AppConfig['general'], value: boolean) => void; + handleThemeChange: (value: 'dark' | 'light' | 'system') => void; + handleDefaultTabChange: (value: 'dashboard' | 'last-session') => void; + + // Notification handlers + handleNotificationToggle: (key: keyof AppConfig['notifications'], value: boolean) => void; + handleSnooze: (minutes: number) => Promise; + handleClearSnooze: () => Promise; + handleAddIgnoredRepository: (item: RepositoryDropdownItem) => Promise; + handleRemoveIgnoredRepository: (repositoryId: string) => Promise; + + // Trigger handlers + handleAddTrigger: (trigger: Omit) => Promise; + handleUpdateTrigger: (triggerId: string, updates: Partial) => Promise; + handleRemoveTrigger: (triggerId: string) => Promise; + + // Display handlers + handleDisplayToggle: (key: keyof AppConfig['display'], value: boolean) => void; + + // Advanced handlers + handleResetToDefaults: () => Promise; + handleExportConfig: () => void; + handleImportConfig: () => void; + handleOpenInEditor: () => Promise; +} + +export function useSettingsHandlers({ + config, + setSaving, + setError, + setConfig, + setOptimisticConfig, + updateConfig, +}: UseSettingsHandlersProps): SettingsHandlers { + // Use ref for config to avoid recreating callbacks when config changes + const configRef = useRef(config); + configRef.current = config; + + // General handlers + const handleGeneralToggle = useCallback( + (key: keyof AppConfig['general'], value: boolean) => { + void updateConfig('general', { [key]: value }); + }, + [updateConfig] + ); + + const handleThemeChange = useCallback( + (value: 'dark' | 'light' | 'system') => { + void updateConfig('general', { theme: value }); + }, + [updateConfig] + ); + + const handleDefaultTabChange = useCallback( + (value: 'dashboard' | 'last-session') => { + void updateConfig('general', { defaultTab: value }); + }, + [updateConfig] + ); + + // Notification handlers + const handleNotificationToggle = useCallback( + (key: keyof AppConfig['notifications'], value: boolean) => { + void updateConfig('notifications', { [key]: value }); + }, + [updateConfig] + ); + + const handleSnooze = useCallback( + async (minutes: number) => { + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.snooze(minutes); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to snooze notifications'); + } finally { + setSaving(false); + } + }, + [setSaving, setConfig, setOptimisticConfig, setError] + ); + + const handleClearSnooze = useCallback(async () => { + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.clearSnooze(); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to clear snooze'); + } finally { + setSaving(false); + } + }, [setSaving, setConfig, setOptimisticConfig, setError]); + + const handleAddIgnoredRepository = useCallback( + async (item: RepositoryDropdownItem) => { + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.addIgnoreRepository(item.id); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to add repository'); + } finally { + setSaving(false); + } + }, + [setSaving, setConfig, setOptimisticConfig, setError] + ); + + const handleRemoveIgnoredRepository = useCallback( + async (repositoryId: string) => { + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.removeIgnoreRepository(repositoryId); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to remove repository'); + } finally { + setSaving(false); + } + }, + [setSaving, setConfig, setOptimisticConfig, setError] + ); + + // Trigger handlers + const handleAddTrigger = useCallback( + async (trigger: Omit) => { + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.addTrigger(trigger); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to add trigger'); + } finally { + setSaving(false); + } + }, + [setSaving, setConfig, setOptimisticConfig, setError] + ); + + const handleUpdateTrigger = useCallback( + async (triggerId: string, updates: Partial) => { + // Optimistic update - immediately reflect the change in UI + setOptimisticConfig((prev) => { + if (!prev) return prev; + const updatedTriggers = + prev.notifications.triggers?.map((t) => + t.id === triggerId ? { ...t, ...updates } : t + ) ?? []; + return { + ...prev, + notifications: { + ...prev.notifications, + triggers: updatedTriggers, + }, + }; + }); + + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.updateTrigger(triggerId, updates); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + // Revert optimistic update on error using ref to avoid stale closure + setOptimisticConfig(configRef.current); + setError(err instanceof Error ? err.message : 'Failed to update trigger'); + } finally { + setSaving(false); + } + }, + [setSaving, setConfig, setOptimisticConfig, setError] + ); + + const handleRemoveTrigger = useCallback( + async (triggerId: string) => { + try { + setSaving(true); + const updatedConfig = await window.electronAPI.config.removeTrigger(triggerId); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to remove trigger'); + } finally { + setSaving(false); + } + }, + [setSaving, setConfig, setOptimisticConfig, setError] + ); + + // Display handlers + const handleDisplayToggle = useCallback( + (key: keyof AppConfig['display'], value: boolean) => { + void updateConfig('display', { [key]: value }); + }, + [updateConfig] + ); + + // Advanced handlers + const handleResetToDefaults = useCallback(async () => { + if (!confirm('Are you sure you want to reset all settings to defaults?')) { + return; + } + try { + setSaving(true); + const defaultIgnoredRegex = ["The user doesn't want to proceed with this tool use\\."]; + const defaultTriggers: NotificationTrigger[] = [ + { + id: 'builtin-tool-result-error', + name: 'Tool Result Error', + enabled: true, + contentType: 'tool_result', + mode: 'error_status', + requireError: true, + ignorePatterns: ["The user doesn't want to proceed with this tool use\\."], + isBuiltin: true, + }, + { + id: 'builtin-bash-command', + name: 'Bash Command Alert for .env files', + enabled: true, + contentType: 'tool_use', + toolName: 'Bash', + mode: 'content_match', + matchField: 'command', + matchPattern: '/.env', + isBuiltin: true, + }, + ]; + const defaultConfig: AppConfig = { + notifications: { + enabled: true, + soundEnabled: true, + ignoredRegex: defaultIgnoredRegex, + ignoredRepositories: [], + snoozedUntil: null, + snoozeMinutes: 30, + includeSubagentErrors: true, + triggers: defaultTriggers, + }, + general: { + launchAtLogin: false, + showDockIcon: true, + theme: 'dark', + defaultTab: 'dashboard', + }, + display: { + showTimestamps: true, + compactMode: false, + syntaxHighlighting: true, + }, + sessions: { + pinnedSessions: {}, + }, + }; + + await window.electronAPI.config.update('notifications', defaultConfig.notifications); + await window.electronAPI.config.update('general', defaultConfig.general); + const updatedConfig = await window.electronAPI.config.update( + 'display', + defaultConfig.display + ); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to reset settings'); + } finally { + setSaving(false); + } + }, [setSaving, setConfig, setOptimisticConfig, setError]); + + const handleExportConfig = useCallback(() => { + if (!configRef.current) return; + const dataStr = JSON.stringify(configRef.current, null, 2); + const blob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = 'claude-code-context-config.json'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }, []); + + const handleOpenInEditor = useCallback(async () => { + try { + await window.electronAPI.config.openInEditor(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to open config in editor'); + } + }, [setError]); + + const handleImportConfig = useCallback(() => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + input.onchange = async (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (!file) return; + + try { + setSaving(true); + const text = await file.text(); + const importedConfig = JSON.parse(text) as AppConfig; + + if (importedConfig.notifications) { + await window.electronAPI.config.update('notifications', importedConfig.notifications); + } + if (importedConfig.general) { + await window.electronAPI.config.update('general', importedConfig.general); + } + if (importedConfig.display) { + await window.electronAPI.config.update('display', importedConfig.display); + } + + const updatedConfig = await window.electronAPI.config.get(); + setConfig(updatedConfig); + setOptimisticConfig(updatedConfig); + setStoreState({ appConfig: updatedConfig }); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to import config'); + } finally { + setSaving(false); + } + }; + input.click(); + }, [setSaving, setConfig, setOptimisticConfig, setError]); + + return { + handleGeneralToggle, + handleThemeChange, + handleDefaultTabChange, + handleNotificationToggle, + handleSnooze, + handleClearSnooze, + handleAddIgnoredRepository, + handleRemoveIgnoredRepository, + handleAddTrigger, + handleUpdateTrigger, + handleRemoveTrigger, + handleDisplayToggle, + handleResetToDefaults, + handleExportConfig, + handleImportConfig, + handleOpenInEditor, + }; +} diff --git a/src/renderer/components/settings/sections/AdvancedSection.tsx b/src/renderer/components/settings/sections/AdvancedSection.tsx new file mode 100644 index 00000000..975aff08 --- /dev/null +++ b/src/renderer/components/settings/sections/AdvancedSection.tsx @@ -0,0 +1,104 @@ +/** + * AdvancedSection - Advanced settings including config management and about info. + */ + +import { useEffect, useState } from 'react'; + +import appIcon from '@renderer/favicon.png'; +import { Code2, Download, RefreshCw, Upload } from 'lucide-react'; + +import { SettingsSectionHeader } from '../components'; + +interface AdvancedSectionProps { + readonly saving: boolean; + readonly onResetToDefaults: () => void; + readonly onExportConfig: () => void; + readonly onImportConfig: () => void; + readonly onOpenInEditor: () => void; +} + +export const AdvancedSection = ({ + saving, + onResetToDefaults, + onExportConfig, + onImportConfig, + onOpenInEditor, +}: AdvancedSectionProps): React.JSX.Element => { + const [version, setVersion] = useState(''); + + useEffect(() => { + window.electronAPI.getAppVersion().then(setVersion).catch(console.error); + }, []); + + return ( +
    + +
    + + + + +
    + + +
    + App Icon +
    +

    + Claude Code Context +

    +

    + Version {version || '...'} +

    +

    + Visualize and analyze Claude Code session executions with interactive waterfall charts + and detailed insights. +

    +
    +
    +
    + ); +}; diff --git a/src/renderer/components/settings/sections/GeneralSection.tsx b/src/renderer/components/settings/sections/GeneralSection.tsx new file mode 100644 index 00000000..87194319 --- /dev/null +++ b/src/renderer/components/settings/sections/GeneralSection.tsx @@ -0,0 +1,60 @@ +/** + * GeneralSection - General settings including startup and appearance. + */ + +import { SettingRow, SettingsSectionHeader, SettingsSelect, SettingsToggle } from '../components'; + +import type { SafeConfig } from '../hooks/useSettingsConfig'; + +// Theme options +const THEME_OPTIONS = [ + { value: 'dark', label: 'Dark' }, + { value: 'light', label: 'Light' }, + { value: 'system', label: 'System' }, +] as const; + +interface GeneralSectionProps { + readonly safeConfig: SafeConfig; + readonly saving: boolean; + readonly onGeneralToggle: (key: 'launchAtLogin' | 'showDockIcon', value: boolean) => void; + readonly onThemeChange: (value: 'dark' | 'light' | 'system') => void; +} + +export const GeneralSection = ({ + safeConfig, + saving, + onGeneralToggle, + onThemeChange, +}: GeneralSectionProps): React.JSX.Element => { + return ( +
    + + + onGeneralToggle('launchAtLogin', v)} + disabled={saving} + /> + + {window.navigator.userAgent.includes('Macintosh') && ( + + onGeneralToggle('showDockIcon', v)} + disabled={saving} + /> + + )} + + + + + +
    + ); +}; diff --git a/src/renderer/components/settings/sections/NotificationsSection.tsx b/src/renderer/components/settings/sections/NotificationsSection.tsx new file mode 100644 index 00000000..5a3829b5 --- /dev/null +++ b/src/renderer/components/settings/sections/NotificationsSection.tsx @@ -0,0 +1,166 @@ +/** + * NotificationsSection - Notification settings including triggers and ignored repositories. + */ + +import { + RepositoryDropdown, + SelectedRepositoryItem, +} from '@renderer/components/common/RepositoryDropdown'; + +import { SettingRow, SettingsSectionHeader, SettingsSelect, SettingsToggle } from '../components'; +import { NotificationTriggerSettings } from '../NotificationTriggerSettings'; + +import type { RepositoryDropdownItem, SafeConfig } from '../hooks/useSettingsConfig'; +import type { NotificationTrigger } from '@renderer/types/data'; + +// Snooze duration options +const SNOOZE_OPTIONS = [ + { value: 15, label: '15 minutes' }, + { value: 30, label: '30 minutes' }, + { value: 60, label: '1 hour' }, + { value: 120, label: '2 hours' }, + { value: 240, label: '4 hours' }, + { value: -1, label: 'Until tomorrow' }, +] as const; + +interface NotificationsSectionProps { + readonly safeConfig: SafeConfig; + readonly saving: boolean; + readonly isSnoozed: boolean; + readonly ignoredRepositoryItems: RepositoryDropdownItem[]; + readonly excludedRepositoryIds: string[]; + readonly onNotificationToggle: ( + key: 'enabled' | 'soundEnabled' | 'includeSubagentErrors', + value: boolean + ) => void; + readonly onSnooze: (minutes: number) => Promise; + readonly onClearSnooze: () => Promise; + readonly onAddIgnoredRepository: (item: RepositoryDropdownItem) => Promise; + readonly onRemoveIgnoredRepository: (repositoryId: string) => Promise; + readonly onAddTrigger: (trigger: Omit) => Promise; + readonly onUpdateTrigger: ( + triggerId: string, + updates: Partial + ) => Promise; + readonly onRemoveTrigger: (triggerId: string) => Promise; +} + +export const NotificationsSection = ({ + safeConfig, + saving, + isSnoozed, + ignoredRepositoryItems, + excludedRepositoryIds, + onNotificationToggle, + onSnooze, + onClearSnooze, + onAddIgnoredRepository, + onRemoveIgnoredRepository, + onAddTrigger, + onUpdateTrigger, + onRemoveTrigger, +}: NotificationsSectionProps): React.JSX.Element => { + return ( +
    + {/* Notification Triggers */} + + + {/* Notification Settings */} + + + onNotificationToggle('enabled', v)} + disabled={saving} + /> + + + onNotificationToggle('soundEnabled', v)} + disabled={saving || !safeConfig.notifications.enabled} + /> + + + onNotificationToggle('includeSubagentErrors', v)} + disabled={saving || !safeConfig.notifications.enabled} + /> + + +
    + {isSnoozed ? ( + + ) : ( + v !== 0 && onSnooze(v)} + disabled={saving || !safeConfig.notifications.enabled} + dropUp + /> + )} +
    +
    + + +

    + Notifications from these repositories will be ignored +

    + {ignoredRepositoryItems.length > 0 ? ( +
    + {ignoredRepositoryItems.map((item) => ( + onRemoveIgnoredRepository(item.id)} + disabled={saving} + /> + ))} +
    + ) : ( +
    +

    + No repositories ignored +

    +
    + )} + +
    + ); +}; diff --git a/src/renderer/components/settings/sections/index.ts b/src/renderer/components/settings/sections/index.ts new file mode 100644 index 00000000..d5579c28 --- /dev/null +++ b/src/renderer/components/settings/sections/index.ts @@ -0,0 +1,7 @@ +/** + * Settings section components barrel export. + */ + +export { AdvancedSection } from './AdvancedSection'; +export { GeneralSection } from './GeneralSection'; +export { NotificationsSection } from './NotificationsSection'; diff --git a/src/renderer/components/sidebar/DateGroupedSessions.tsx b/src/renderer/components/sidebar/DateGroupedSessions.tsx new file mode 100644 index 00000000..e46af28c --- /dev/null +++ b/src/renderer/components/sidebar/DateGroupedSessions.tsx @@ -0,0 +1,355 @@ +/** + * DateGroupedSessions - Sessions organized by date categories with virtual scrolling. + * Uses @tanstack/react-virtual for efficient DOM rendering with infinite scroll. + */ + +import { useCallback, useEffect, useMemo, useRef } from 'react'; + +import { useStore } from '@renderer/store'; +import { + getNonEmptyCategories, + groupSessionsByDate, + separatePinnedSessions, +} from '@renderer/utils/dateGrouping'; +import { useVirtualizer } from '@tanstack/react-virtual'; +import { Calendar, Loader2, MessageSquareOff, Pin } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import { SessionItem } from './SessionItem'; + +import type { Session } from '@renderer/types/data'; +import type { DateCategory } from '@renderer/types/tabs'; + +// Virtual list item types +type VirtualItem = + | { type: 'header'; category: DateCategory; id: string } + | { type: 'pinned-header'; id: string } + | { type: 'session'; session: Session; isPinned: boolean; id: string } + | { type: 'loader'; id: string }; + +/** + * Item height constants for virtual scroll positioning. + * CRITICAL: These values MUST match the actual rendered heights of components. + * If SessionItem height changes, update SESSION_HEIGHT here AND add h-[Xpx] to SessionItem. + * Mismatch causes items to overlap! + */ +const HEADER_HEIGHT = 28; +const SESSION_HEIGHT = 48; // Must match h-[48px] in SessionItem.tsx +const LOADER_HEIGHT = 36; +const OVERSCAN = 5; + +export const DateGroupedSessions = (): React.JSX.Element => { + const { + sessions, + selectedSessionId, + selectedProjectId, + sessionsLoading, + sessionsError, + sessionsHasMore, + sessionsLoadingMore, + sessionsTotalCount, + fetchSessionsMore, + pinnedSessionIds, + } = useStore( + useShallow((s) => ({ + sessions: s.sessions, + selectedSessionId: s.selectedSessionId, + selectedProjectId: s.selectedProjectId, + sessionsLoading: s.sessionsLoading, + sessionsError: s.sessionsError, + sessionsHasMore: s.sessionsHasMore, + sessionsLoadingMore: s.sessionsLoadingMore, + sessionsTotalCount: s.sessionsTotalCount, + fetchSessionsMore: s.fetchSessionsMore, + pinnedSessionIds: s.pinnedSessionIds, + })) + ); + + const parentRef = useRef(null); + + // Separate pinned sessions from unpinned + const { pinned: pinnedSessions, unpinned: unpinnedSessions } = useMemo( + () => separatePinnedSessions(sessions, pinnedSessionIds), + [sessions, pinnedSessionIds] + ); + + // Group only unpinned sessions by date + const groupedSessions = useMemo(() => groupSessionsByDate(unpinnedSessions), [unpinnedSessions]); + + // Get non-empty categories in display order + const nonEmptyCategories = useMemo( + () => getNonEmptyCategories(groupedSessions), + [groupedSessions] + ); + + // Flatten sessions with date headers into virtual list items + const virtualItems = useMemo((): VirtualItem[] => { + const items: VirtualItem[] = []; + + // Add pinned section first + if (pinnedSessions.length > 0) { + items.push({ + type: 'pinned-header', + id: 'header-pinned', + }); + + for (const session of pinnedSessions) { + items.push({ + type: 'session', + session, + isPinned: true, + id: `session-${session.id}`, + }); + } + } + + for (const category of nonEmptyCategories) { + // Add header item + items.push({ + type: 'header', + category, + id: `header-${category}`, + }); + + // Add session items + for (const session of groupedSessions[category]) { + items.push({ + type: 'session', + session, + isPinned: false, + id: `session-${session.id}`, + }); + } + } + + // Add loader item if there are more sessions to load + if (sessionsHasMore) { + items.push({ + type: 'loader', + id: 'loader', + }); + } + + return items; + }, [pinnedSessions, nonEmptyCategories, groupedSessions, sessionsHasMore]); + + // Estimate item size based on type + const estimateSize = useCallback( + (index: number) => { + const item = virtualItems[index]; + if (!item) return SESSION_HEIGHT; + + switch (item.type) { + case 'header': + case 'pinned-header': + return HEADER_HEIGHT; + case 'loader': + return LOADER_HEIGHT; + case 'session': + default: + return SESSION_HEIGHT; + } + }, + [virtualItems] + ); + + // Set up virtualizer + // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Virtual API limitation, not fixable in user code + const rowVirtualizer = useVirtualizer({ + count: virtualItems.length, + getScrollElement: () => parentRef.current, + estimateSize, + overscan: OVERSCAN, + }); + + // Get virtual items for dependency tracking + const virtualRows = rowVirtualizer.getVirtualItems(); + const virtualRowsLength = virtualRows.length; + + // Load more when scrolling near end + useEffect(() => { + if (virtualRowsLength === 0) return; + + const lastItem = virtualRows[virtualRowsLength - 1]; + if (!lastItem) return; + + // If we're within 3 items of the end and there's more to load, fetch more + if ( + lastItem.index >= virtualItems.length - 3 && + sessionsHasMore && + !sessionsLoadingMore && + !sessionsLoading + ) { + void fetchSessionsMore(); + } + }, [ + virtualRows, + virtualRowsLength, + virtualItems.length, + sessionsHasMore, + sessionsLoadingMore, + sessionsLoading, + fetchSessionsMore, + ]); + + if (!selectedProjectId) { + return ( +
    +
    +

    Select a project to view sessions

    +
    +
    + ); + } + + if (sessionsLoading && sessions.length === 0) { + return ( +
    +
    + {[...Array(3)].map((_, i) => ( +
    +
    +
    +
    +
    + ))} +
    +
    + ); + } + + if (sessionsError) { + return ( +
    +
    +

    + Error loading sessions +

    +

    {sessionsError}

    +
    +
    + ); + } + + if (sessions.length === 0) { + return ( +
    +
    + +

    No sessions found

    +

    This project has no sessions yet

    +
    +
    + ); + } + + return ( +
    +
    + +

    + Sessions +

    + + ({sessions.length} + {sessionsTotalCount > sessions.length ? ` of ${sessionsTotalCount}` : ''}) + +
    + +
    +
    + {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const item = virtualItems[virtualRow.index]; + if (!item) return null; + + return ( +
    + {item.type === 'pinned-header' ? ( +
    + + Pinned +
    + ) : item.type === 'header' ? ( +
    + {item.category} +
    + ) : item.type === 'loader' ? ( +
    + {sessionsLoadingMore ? ( + <> + + Loading more sessions... + + ) : ( + Scroll to load more + )} +
    + ) : ( + + )} +
    + ); + })} +
    +
    +
    + ); +}; diff --git a/src/renderer/components/sidebar/SessionContextMenu.tsx b/src/renderer/components/sidebar/SessionContextMenu.tsx new file mode 100644 index 00000000..37acea03 --- /dev/null +++ b/src/renderer/components/sidebar/SessionContextMenu.tsx @@ -0,0 +1,130 @@ +/** + * SessionContextMenu - Right-click context menu for sidebar session items. + * Supports opening in current pane, new tab, and split right. + * Shows keyboard shortcut hints for actions that have them. + */ + +import { useEffect, useRef } from 'react'; + +import { MAX_PANES } from '@renderer/types/panes'; +import { Pin, PinOff } from 'lucide-react'; + +interface SessionContextMenuProps { + x: number; + y: number; + sessionId: string; + projectId: string; + sessionLabel: string; + paneCount: number; + isPinned: boolean; + onClose: () => void; + onOpenInCurrentPane: () => void; + onOpenInNewTab: () => void; + onSplitRightAndOpen: () => void; + onTogglePin: () => void; +} + +export const SessionContextMenu = ({ + x, + y, + paneCount, + isPinned, + onClose, + onOpenInCurrentPane, + onOpenInNewTab, + onSplitRightAndOpen, + onTogglePin, +}: SessionContextMenuProps): React.JSX.Element => { + const menuRef = useRef(null); + + useEffect(() => { + const handleMouseDown = (e: MouseEvent): void => { + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + onClose(); + } + }; + const handleKeyDown = (e: KeyboardEvent): void => { + if (e.key === 'Escape') onClose(); + }; + document.addEventListener('mousedown', handleMouseDown); + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('mousedown', handleMouseDown); + document.removeEventListener('keydown', handleKeyDown); + }; + }, [onClose]); + + const menuWidth = 240; + const menuHeight = 180; + const clampedX = Math.min(x, window.innerWidth - menuWidth - 8); + const clampedY = Math.min(y, window.innerHeight - menuHeight - 8); + + const handleClick = (action: () => void) => () => { + action(); + onClose(); + }; + + const atMaxPanes = paneCount >= MAX_PANES; + + return ( +
    + + +
    + +
    + : } + onClick={handleClick(onTogglePin)} + /> +
    + ); +}; + +const MenuItem = ({ + label, + shortcut, + icon, + onClick, + disabled, +}: { + label: string; + shortcut?: string; + icon?: React.ReactNode; + onClick: () => void; + disabled?: boolean; +}): React.JSX.Element => { + return ( + + ); +}; diff --git a/src/renderer/components/sidebar/SessionItem.tsx b/src/renderer/components/sidebar/SessionItem.tsx new file mode 100644 index 00000000..1dbe4cb1 --- /dev/null +++ b/src/renderer/components/sidebar/SessionItem.tsx @@ -0,0 +1,200 @@ +/** + * SessionItem - Compact session row in the session list. + * Shows title, message count, and time ago. + * Supports right-click context menu for pane management. + */ + +import { useCallback, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { useStore } from '@renderer/store'; +import { formatDistanceToNowStrict } from 'date-fns'; +import { MessageSquare, Pin } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; + +import { OngoingIndicator } from '../common/OngoingIndicator'; + +import { SessionContextMenu } from './SessionContextMenu'; + +import type { Session } from '@renderer/types/data'; + +interface SessionItemProps { + session: Session; + isActive?: boolean; + isPinned?: boolean; +} + +/** + * Format time distance in short form (e.g., "4m", "2h", "1d") + */ +function formatShortTime(date: Date): string { + const distance = formatDistanceToNowStrict(date, { addSuffix: false }); + return distance + .replace(' seconds', 's') + .replace(' second', 's') + .replace(' minutes', 'm') + .replace(' minute', 'm') + .replace(' hours', 'h') + .replace(' hour', 'h') + .replace(' days', 'd') + .replace(' day', 'd') + .replace(' weeks', 'w') + .replace(' week', 'w') + .replace(' months', 'mo') + .replace(' month', 'mo') + .replace(' years', 'y') + .replace(' year', 'y'); +} + +export const SessionItem = ({ + session, + isActive, + isPinned, +}: Readonly): React.JSX.Element => { + const { openTab, activeProjectId, selectSession, paneCount, splitPane, togglePinSession } = + useStore( + useShallow((s) => ({ + openTab: s.openTab, + activeProjectId: s.activeProjectId, + selectSession: s.selectSession, + paneCount: s.paneLayout.panes.length, + splitPane: s.splitPane, + togglePinSession: s.togglePinSession, + })) + ); + + const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null); + + const handleClick = (event: React.MouseEvent): void => { + if (!activeProjectId) return; + + // Cmd/Ctrl+click: open in new tab; plain click: replace current tab + const forceNewTab = event.ctrlKey || event.metaKey; + + openTab( + { + type: 'session', + sessionId: session.id, + projectId: activeProjectId, + label: session.firstMessage?.slice(0, 50) ?? 'Session', + }, + forceNewTab ? { forceNewTab } : { replaceActiveTab: true } + ); + + selectSession(session.id); + }; + + const handleContextMenu = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + setContextMenu({ x: e.clientX, y: e.clientY }); + }, []); + + const sessionLabel = session.firstMessage?.slice(0, 50) ?? 'Session'; + + const handleOpenInCurrentPane = useCallback(() => { + if (!activeProjectId) return; + openTab( + { + type: 'session', + sessionId: session.id, + projectId: activeProjectId, + label: sessionLabel, + }, + { replaceActiveTab: true } + ); + selectSession(session.id); + }, [activeProjectId, openTab, selectSession, session.id, sessionLabel]); + + const handleOpenInNewTab = useCallback(() => { + if (!activeProjectId) return; + openTab( + { + type: 'session', + sessionId: session.id, + projectId: activeProjectId, + label: sessionLabel, + }, + { forceNewTab: true } + ); + selectSession(session.id); + }, [activeProjectId, openTab, selectSession, session.id, sessionLabel]); + + const handleSplitRightAndOpen = useCallback(() => { + if (!activeProjectId) return; + // First open the tab in the focused pane + openTab({ + type: 'session', + sessionId: session.id, + projectId: activeProjectId, + label: sessionLabel, + }); + selectSession(session.id); + // Then split it to the right + const state = useStore.getState(); + const focusedPaneId = state.paneLayout.focusedPaneId; + const activeTabId = state.activeTabId; + if (activeTabId) { + splitPane(focusedPaneId, activeTabId, 'right'); + } + }, [activeProjectId, openTab, selectSession, session.id, sessionLabel, splitPane]); + + // Height must match SESSION_HEIGHT (48px) in DateGroupedSessions.tsx for virtual scroll + return ( + <> + + + {contextMenu && + activeProjectId && + createPortal( + setContextMenu(null)} + onOpenInCurrentPane={handleOpenInCurrentPane} + onOpenInNewTab={handleOpenInNewTab} + onSplitRightAndOpen={handleSplitRightAndOpen} + onTogglePin={() => void togglePinSession(session.id)} + />, + document.body + )} + + ); +}; diff --git a/src/renderer/constants/cssVariables.ts b/src/renderer/constants/cssVariables.ts new file mode 100644 index 00000000..eec8bf61 --- /dev/null +++ b/src/renderer/constants/cssVariables.ts @@ -0,0 +1,223 @@ +/** + * CSS Variable Constants + * + * Centralized CSS variable strings to avoid duplication across components. + * These are used with inline styles for theme-aware styling. + */ + +// ============================================================================= +// Text Colors +// ============================================================================= + +/** Muted text color for less important content */ +export const COLOR_TEXT_MUTED = 'var(--color-text-muted)'; + +/** Secondary text color for supporting content */ +export const COLOR_TEXT_SECONDARY = 'var(--color-text-secondary)'; + +/** Primary text color */ +export const COLOR_TEXT = 'var(--color-text)'; + +// ============================================================================= +// Prose/Typography Colors (for markdown rendering) +// ============================================================================= + +/** Prose body text color */ +export const PROSE_BODY = 'var(--prose-body)'; + +/** Prose heading color */ +export const PROSE_HEADING = 'var(--prose-heading)'; + +/** Prose muted text color */ +export const PROSE_MUTED = 'var(--prose-muted)'; + +/** Prose link color */ +export const PROSE_LINK = 'var(--prose-link)'; + +/** Prose inline code background */ +export const PROSE_CODE_BG = 'var(--prose-code-bg)'; + +/** Prose inline code text color */ +export const PROSE_CODE_TEXT = 'var(--prose-code-text)'; + +/** Prose code block background */ +export const PROSE_PRE_BG = 'var(--prose-pre-bg)'; + +/** Prose code block border */ +export const PROSE_PRE_BORDER = 'var(--prose-pre-border)'; + +/** Prose blockquote border color */ +export const PROSE_BLOCKQUOTE_BORDER = 'var(--prose-blockquote-border)'; + +/** Prose table border color */ +export const PROSE_TABLE_BORDER = 'var(--prose-table-border)'; + +/** Prose table header background */ +export const PROSE_TABLE_HEADER_BG = 'var(--prose-table-header-bg)'; + +// ============================================================================= +// Surface Colors +// ============================================================================= + +/** Raised surface background */ +export const COLOR_SURFACE_RAISED = 'var(--color-surface-raised)'; + +/** Overlay surface background */ +export const COLOR_SURFACE_OVERLAY = 'var(--color-surface-overlay)'; + +/** Base surface background */ +export const COLOR_SURFACE = 'var(--color-surface)'; + +// ============================================================================= +// Border Colors +// ============================================================================= + +/** Standard border color */ +export const COLOR_BORDER = 'var(--color-border)'; + +/** Subtle border color */ +export const COLOR_BORDER_SUBTLE = 'var(--color-border-subtle)'; + +// ============================================================================= +// Tool Item Colors (for expandable items in chat) +// ============================================================================= + +/** Tool item muted color */ +export const TOOL_ITEM_MUTED = 'var(--tool-item-muted)'; + +// ============================================================================= +// Code Block Colors +// ============================================================================= + +/** Code block background */ +export const CODE_BG = 'var(--code-bg)'; + +/** Code block border */ +export const CODE_BORDER = 'var(--code-border)'; + +/** Code block header background */ +export const CODE_HEADER_BG = 'var(--code-header-bg)'; + +/** Code filename color */ +export const CODE_FILENAME = 'var(--code-filename)'; + +/** Code line number color */ +export const CODE_LINE_NUMBER = 'var(--code-line-number)'; + +// ============================================================================= +// Diff Colors +// ============================================================================= + +/** Diff removed line background */ +export const DIFF_REMOVED_BG = 'var(--diff-removed-bg)'; + +/** Diff removed line text color */ +export const DIFF_REMOVED_TEXT = 'var(--diff-removed-text)'; + +/** Diff removed line border */ +export const DIFF_REMOVED_BORDER = 'var(--diff-removed-border)'; + +/** Diff added line background */ +export const DIFF_ADDED_BG = 'var(--diff-added-bg)'; + +/** Diff added line text color */ +export const DIFF_ADDED_TEXT = 'var(--diff-added-text)'; + +/** Diff added line border */ +export const DIFF_ADDED_BORDER = 'var(--diff-added-border)'; + +// ============================================================================= +// Tool Call/Result Colors +// ============================================================================= + +/** Tool call background */ +export const TOOL_CALL_BG = 'var(--tool-call-bg)'; + +/** Tool call border */ +export const TOOL_CALL_BORDER = 'var(--tool-call-border)'; + +/** Tool call text color */ +export const TOOL_CALL_TEXT = 'var(--tool-call-text)'; + +// ============================================================================= +// Tag/Badge Colors +// ============================================================================= + +/** Tag background */ +export const TAG_BG = 'var(--tag-bg)'; + +/** Tag text color */ +export const TAG_TEXT = 'var(--tag-text)'; + +/** Tag border */ +export const TAG_BORDER = 'var(--tag-border)'; + +// ============================================================================= +// Worktree Badge Colors (hardcoded hex values) +// ============================================================================= + +/** Muted zinc badge background */ +export const WORKTREE_BADGE_BG = 'rgba(161, 161, 170, 0.15)'; + +/** Muted zinc badge text color */ +export const WORKTREE_BADGE_TEXT = '#a1a1aa'; + +// ============================================================================= +// Card/Subagent Styling (theme-aware) +// ============================================================================= + +/** Card background */ +export const CARD_BG = 'var(--card-bg)'; + +/** Card border color */ +const CARD_BORDER = 'var(--card-border)'; + +/** Card border style */ +export const CARD_BORDER_STYLE = `1px solid ${CARD_BORDER}`; + +/** Card header background */ +export const CARD_HEADER_BG = 'var(--card-header-bg)'; + +/** Card header hover background */ +export const CARD_HEADER_HOVER = 'var(--card-header-hover)'; + +/** Card muted icon color */ +export const CARD_ICON_MUTED = 'var(--card-icon-muted)'; + +/** Card light text */ +export const CARD_TEXT_LIGHT = 'var(--card-text-light)'; + +/** Card lighter text */ +export const CARD_TEXT_LIGHTER = 'var(--card-text-lighter)'; + +/** Card separator color */ +export const CARD_SEPARATOR = 'var(--card-separator)'; + +// ============================================================================= +// Form/Input Colors (Tailwind classes for select/input options) +// ============================================================================= + +/** Background for select options (theme-aware) */ +export const SELECT_OPTION_BG = 'bg-surface'; + +// ============================================================================= +// Form State Classes (Tailwind classes for form states) +// ============================================================================= + +/** Cursor pointer class */ +const CURSOR_POINTER = 'cursor-pointer'; + +/** Cursor not allowed with opacity for disabled state */ +const CURSOR_DISABLED = 'cursor-not-allowed opacity-50'; + +/** + * Helper to get cursor class based on disabled state + */ +export const getCursorClass = (disabled: boolean): string => + disabled ? CURSOR_DISABLED : CURSOR_POINTER; + +/** + * Base className for select inputs in settings forms (theme-aware) + */ +export const SELECT_INPUT_BASE = + 'rounded border border-border bg-transparent px-2 py-1 text-sm text-text focus:border-transparent focus:outline-none focus:ring-1 focus:ring-indigo-500'; diff --git a/src/renderer/constants/layout.ts b/src/renderer/constants/layout.ts new file mode 100644 index 00000000..367ffe5f --- /dev/null +++ b/src/renderer/constants/layout.ts @@ -0,0 +1,9 @@ +/** + * Shared layout constants for consistent header heights. + * Used by both SidebarHeader and TabBar to ensure alignment. + */ + +export { getTrafficLightPaddingForZoom, HEADER_ROW1_HEIGHT } from '@shared/constants'; + +/** Height of the secondary header row (worktree selector) */ +export const HEADER_ROW2_HEIGHT = 30; // px diff --git a/src/renderer/constants/teamColors.ts b/src/renderer/constants/teamColors.ts new file mode 100644 index 00000000..96c4177c --- /dev/null +++ b/src/renderer/constants/teamColors.ts @@ -0,0 +1,51 @@ +/** + * Team Color Constants + * + * Shared color definitions for team member visualization. + * Used by TeammateMessageItem and SubagentItem when displaying team members. + */ + +export interface TeamColorSet { + /** Border accent color */ + border: string; + /** Badge background (semi-transparent) */ + badge: string; + /** Text color for labels */ + text: string; +} + +const TEAMMATE_COLORS: Record = { + blue: { border: '#3b82f6', badge: 'rgba(59, 130, 246, 0.15)', text: '#60a5fa' }, + green: { border: '#22c55e', badge: 'rgba(34, 197, 94, 0.15)', text: '#4ade80' }, + red: { border: '#ef4444', badge: 'rgba(239, 68, 68, 0.15)', text: '#f87171' }, + yellow: { border: '#eab308', badge: 'rgba(234, 179, 8, 0.15)', text: '#facc15' }, + purple: { border: '#a855f7', badge: 'rgba(168, 85, 247, 0.15)', text: '#c084fc' }, + cyan: { border: '#06b6d4', badge: 'rgba(6, 182, 212, 0.15)', text: '#22d3ee' }, + orange: { border: '#f97316', badge: 'rgba(249, 115, 22, 0.15)', text: '#fb923c' }, + pink: { border: '#ec4899', badge: 'rgba(236, 72, 153, 0.15)', text: '#f472b6' }, +}; + +const DEFAULT_COLOR: TeamColorSet = TEAMMATE_COLORS.blue; + +/** + * Get a TeamColorSet from a color name or hex string. + * Falls back to blue if unrecognized. + */ +export function getTeamColorSet(colorName: string): TeamColorSet { + if (!colorName) return DEFAULT_COLOR; + + // Check named colors + const named = TEAMMATE_COLORS[colorName.toLowerCase()]; + if (named) return named; + + // If it's a hex color, generate a set from it + if (colorName.startsWith('#')) { + return { + border: colorName, + badge: `${colorName}26`, + text: colorName, + }; + } + + return DEFAULT_COLOR; +} diff --git a/src/renderer/contexts/TabUIContext.tsx b/src/renderer/contexts/TabUIContext.tsx new file mode 100644 index 00000000..b99aa4ea --- /dev/null +++ b/src/renderer/contexts/TabUIContext.tsx @@ -0,0 +1,51 @@ +/** + * TabUIContext - Provides the current tab's ID to all descendant components. + * + * This context enables per-tab UI state isolation. Components use the tabId + * from this context to access their tab-specific state from the store. + * + * Usage: + * ```tsx + * // In TabbedLayout (provider): + * + * + * + * + * // In any descendant component (consumer): + * const tabId = useTabId(); + * const { expandedIds, toggleExpansion } = useTabUI(); + * ``` + */ + +import { createContext, type ReactNode } from 'react'; + +// ============================================================================= +// Context Definition +// ============================================================================= + +interface TabUIContextValue { + /** The unique ID of the current tab */ + tabId: string; +} + +const TabUIContext = createContext(null); + +export { TabUIContext }; + +// ============================================================================= +// Provider Component +// ============================================================================= + +interface TabUIProviderProps { + /** The tab ID to provide to descendants */ + tabId: string; + children: ReactNode; +} + +/** + * Provides the tab ID to all descendant components. + * Wrap each tab's content with this provider. + */ +export const TabUIProvider = ({ tabId, children }: TabUIProviderProps): JSX.Element => { + return {children}; +}; diff --git a/src/renderer/contexts/useTabUIContext.ts b/src/renderer/contexts/useTabUIContext.ts new file mode 100644 index 00000000..102adba2 --- /dev/null +++ b/src/renderer/contexts/useTabUIContext.ts @@ -0,0 +1,18 @@ +/** + * Hooks for accessing the TabUIContext. + * + * These hooks are in a separate file from the provider to support React Fast Refresh. + */ + +import { useContext } from 'react'; + +import { TabUIContext } from './TabUIContext'; + +/** + * Returns the current tab's ID, or null if not within a TabUIProvider. + * Use this for components that may be rendered outside of a tab context. + */ +export function useTabIdOptional(): string | null { + const context = useContext(TabUIContext); + return context?.tabId ?? null; +} diff --git a/src/renderer/favicon.png b/src/renderer/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc2bf508eb274e840f431e757e9de22239f17dc GIT binary patch literal 15770 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 I#_`1e17yQZf&c&j literal 0 HcmV?d00001 diff --git a/src/renderer/hooks/navigation/utils.ts b/src/renderer/hooks/navigation/utils.ts new file mode 100644 index 00000000..c5ce035f --- /dev/null +++ b/src/renderer/hooks/navigation/utils.ts @@ -0,0 +1,263 @@ +/** + * Shared navigation utilities for scroll/highlight orchestration. + * + * These helpers are used by useTabNavigationController and can be + * reused by other navigation-related hooks. + */ + +import type { ChatItem } from '@renderer/types/groups'; + +// ============================================================================= +// Target Resolution +// ============================================================================= + +/** + * Find the AI group that contains or is closest to the given error timestamp. + */ +export function findAIGroupByTimestamp(items: ChatItem[], errorTimestamp: number): string | null { + if (items.length === 0) return null; + + let bestGroupId: string | null = null; + let bestTimeDiff = Infinity; + + for (const item of items) { + if (item.type !== 'ai') continue; + + const group = item.group; + const startMs = group.startTime.getTime(); + const endMs = group.endTime.getTime(); + + // Check if error timestamp is within this group's time range + if (errorTimestamp >= startMs && errorTimestamp <= endMs) { + return group.id; // Exact match + } + + // Track closest group for fallback + const startDiff = Math.abs(errorTimestamp - startMs); + const endDiff = Math.abs(errorTimestamp - endMs); + const minDiff = Math.min(startDiff, endDiff); + + if (minDiff < bestTimeDiff) { + bestTimeDiff = minDiff; + bestGroupId = group.id; + } + } + + return bestGroupId; +} + +/** + * Find the chat item (any type) that contains or is closest to the given timestamp. + * Returns the item's group ID and type. + */ +export function findChatItemByTimestamp( + items: ChatItem[], + targetTimestamp: number +): { groupId: string; type: 'user' | 'system' | 'ai' | 'compact' } | null { + if (items.length === 0) return null; + + let bestMatch: { groupId: string; type: 'user' | 'system' | 'ai' | 'compact' } | null = null; + let bestTimeDiff = Infinity; + + for (const item of items) { + let itemTimestamp: number; + + if (item.type === 'user') { + itemTimestamp = item.group.timestamp.getTime(); + } else if (item.type === 'system') { + itemTimestamp = item.group.timestamp.getTime(); + } else if (item.type === 'ai') { + const startMs = item.group.startTime.getTime(); + const endMs = item.group.endTime.getTime(); + if (targetTimestamp >= startMs && targetTimestamp <= endMs) { + return { groupId: item.group.id, type: 'ai' }; + } + itemTimestamp = startMs; + } else if (item.type === 'compact') { + itemTimestamp = item.group.timestamp.getTime(); + } else { + continue; + } + + const timeDiff = Math.abs(targetTimestamp - itemTimestamp); + if (timeDiff < bestTimeDiff) { + bestTimeDiff = timeDiff; + bestMatch = { groupId: item.group.id, type: item.type }; + } + } + + return bestMatch; +} + +// ============================================================================= +// Subagent Group Resolution +// ============================================================================= + +/** + * Find the AI group that contains a subagent with the given ID. + * Looks through each AI group's processes array for a matching process ID. + */ +export function findAIGroupBySubagentId(items: ChatItem[], subagentId: string): string | null { + for (const item of items) { + if (item.type !== 'ai') continue; + if (item.group.processes.some((p) => p.id === subagentId)) { + return item.group.id; + } + } + return null; +} + +// ============================================================================= +// DOM Readiness Helpers +// ============================================================================= + +/** + * Wait for element size to stabilize using ResizeObserver. + * More reliable than timer-based approaches because it detects actual DOM changes. + */ +export function waitForElementStability( + element: HTMLElement, + timeoutMs = 250, + stableFrames = 2 +): Promise { + return new Promise((resolve) => { + let lastSize = { width: 0, height: 0 }; + let stableCount = 0; + let resolved = false; + + const observer = new ResizeObserver((entries) => { + if (resolved) return; + const entry = entries[0]; + if (!entry) return; + + const currentSize = { + width: Math.round(entry.contentRect.width), + height: Math.round(entry.contentRect.height), + }; + + if (currentSize.width === lastSize.width && currentSize.height === lastSize.height) { + stableCount++; + if (stableCount >= stableFrames) { + resolved = true; + observer.disconnect(); + resolve(); + } + } else { + stableCount = 0; + lastSize = currentSize; + } + }); + + observer.observe(element); + + // Initial size reading to bootstrap comparison + const rect = element.getBoundingClientRect(); + lastSize = { width: Math.round(rect.width), height: Math.round(rect.height) }; + + // Timeout fallback to prevent infinite waiting + setTimeout(() => { + if (!resolved) { + resolved = true; + observer.disconnect(); + resolve(); + } + }, timeoutMs); + }); +} + +/** + * Wait for scroll animation to complete. + * Detects completion by monitoring when scrollTop stops changing. + */ +export function waitForScrollEnd(container: HTMLElement, timeoutMs = 400): Promise { + return new Promise((resolve) => { + let lastScrollTop = container.scrollTop; + let stableCount = 0; + let rafId: number | undefined; + let resolved = false; + + const checkScroll = (): void => { + if (resolved) return; + + const currentScrollTop = container.scrollTop; + + if (Math.abs(currentScrollTop - lastScrollTop) < 1) { + stableCount++; + if (stableCount >= 3) { + resolved = true; + if (rafId !== undefined) cancelAnimationFrame(rafId); + resolve(); + return; + } + } else { + stableCount = 0; + lastScrollTop = currentScrollTop; + } + + rafId = requestAnimationFrame(checkScroll); + }; + + rafId = requestAnimationFrame(checkScroll); + + setTimeout(() => { + if (!resolved) { + resolved = true; + if (rafId !== undefined) cancelAnimationFrame(rafId); + resolve(); + } + }, timeoutMs); + }); +} + +// ============================================================================= +// Visibility and Scroll Calculation +// ============================================================================= + +/** + * Calculate the scrollTop value to center an element in the visible area + * of a scroll container, accounting for sticky offset. + */ +export function calculateCenteredScrollTop( + element: HTMLElement, + container: HTMLElement, + stickyOffset: number +): number { + const containerRect = container.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + + const visibleHeight = containerRect.height - stickyOffset; + const elementCenterRelativeToContainer = + elementRect.top - containerRect.top + container.scrollTop + elementRect.height / 2; + const targetScrollTop = elementCenterRelativeToContainer - visibleHeight / 2 - stickyOffset; + + return Math.max(0, targetScrollTop); +} + +/** + * Find the current search result element within a container. + * When item identity is provided, resolves the exact current match for that item/index. + */ +export function findCurrentSearchResultInContainer( + container: HTMLElement | null | undefined, + itemId?: string, + matchIndexInItem?: number +): Element | null { + if (!container) return null; + + const currentResults = container.querySelectorAll('[data-search-result="current"]'); + if (itemId !== undefined && matchIndexInItem !== undefined) { + for (const candidate of currentResults) { + if (!(candidate instanceof HTMLElement)) { + continue; + } + if ( + candidate.dataset.searchItemId === itemId && + candidate.dataset.searchMatchIndex === String(matchIndexInItem) + ) { + return candidate; + } + } + } + + return currentResults[0] ?? null; +} diff --git a/src/renderer/hooks/useAutoScrollBottom.ts b/src/renderer/hooks/useAutoScrollBottom.ts new file mode 100644 index 00000000..6d303931 --- /dev/null +++ b/src/renderer/hooks/useAutoScrollBottom.ts @@ -0,0 +1,263 @@ +import { useCallback, useEffect, useRef } from 'react'; + +/** + * Options for the auto-scroll hook. + */ +interface UseAutoScrollBottomOptions { + /** + * Threshold in pixels from bottom to consider "at bottom". + * Default: 100px (generous threshold for better UX) + */ + threshold?: number; + + /** + * Smooth scroll duration in milliseconds. + * Default: 300ms + */ + smoothDuration?: number; + + /** + * Whether auto-scroll is enabled. + * Default: true + */ + enabled?: boolean; + + /** + * Whether auto-scroll is temporarily disabled (e.g., during navigation). + * Unlike enabled, this is for transient disabling during specific operations. + * Default: false + */ + disabled?: boolean; + + /** + * Optional external scroll container ref. If provided, the hook will use this + * ref instead of creating its own. Useful when the ref needs to be shared + * with other hooks (e.g., navigation coordinator). + */ + externalRef?: React.RefObject; + + /** + * When this value changes, reset isAtBottom state to true. + * Use for tab/session changes to ensure new content scrolls to bottom. + */ + resetKey?: string | null; +} + +/** + * Return type for the auto-scroll hook. + */ +interface UseAutoScrollBottomReturn { + /** + * Ref to attach to the scroll container element. + */ + scrollContainerRef: React.RefObject; + + /** + * Get whether the user is currently at the bottom of the scroll container. + * Returns a function to avoid accessing ref.current during render. + */ + getIsAtBottom: () => boolean; + + /** + * Manually scroll to bottom with smooth animation. + */ + scrollToBottom: (behavior?: ScrollBehavior) => void; + + /** + * Check and update the isAtBottom state. + * Call this after content changes if needed. + */ + checkIsAtBottom: () => boolean; +} + +export function isNearBottom( + scrollTop: number, + scrollHeight: number, + clientHeight: number, + threshold: number +): boolean { + const distanceFromBottom = scrollHeight - scrollTop - clientHeight; + return distanceFromBottom <= threshold; +} + +/** + * Custom hook for managing auto-scroll-to-bottom behavior in chat-like interfaces. + * + * Features: + * - Tracks whether user is at the bottom of the scroll container + * - Automatically scrolls to bottom when content changes (if user was at bottom) + * - Smooth scrolling animation + * - Respects user's scroll position (doesn't force scroll if user scrolled up) + * + * @param dependencies - Array of dependencies that trigger scroll check (e.g., conversation items) + * @param options - Configuration options + * @returns Scroll management utilities + * + * @example + * ```tsx + * const { scrollContainerRef, isAtBottom, scrollToBottom } = useAutoScrollBottom( + * [conversation?.items.length], + * { threshold: 100 } + * ); + * + * return ( + *
    + * {items.map(renderItem)} + *
    + * ); + * ``` + */ +export function useAutoScrollBottom( + dependencies: unknown[], + options: UseAutoScrollBottomOptions = {} +): UseAutoScrollBottomReturn { + const { + threshold = 100, + smoothDuration = 300, + enabled = true, + disabled = false, + externalRef, + resetKey, + } = options; + + // Use external ref if provided, otherwise create our own + const internalRef = useRef(null); + const scrollContainerRef = externalRef ?? internalRef; + + const isAtBottomRef = useRef(true); // Start assuming at bottom + const wasAtBottomBeforeUpdateRef = useRef(true); + const isScrollingRef = useRef(false); + // Track disabled state in ref for checking inside RAF callbacks + const disabledRef = useRef(disabled); + // Track resetKey to detect changes + const prevResetKeyRef = useRef(resetKey); + + /** + * Check if the scroll container is at the bottom. + */ + const checkIsAtBottom = useCallback((): boolean => { + const container = scrollContainerRef.current; + if (!container) return true; + + const { scrollTop, scrollHeight, clientHeight } = container; + return isNearBottom(scrollTop, scrollHeight, clientHeight, threshold); + // eslint-disable-next-line react-hooks/exhaustive-deps -- scrollContainerRef is a ref, stable across renders + }, [threshold]); + + /** + * Scroll to bottom with smooth animation. + */ + const scrollToBottom = useCallback( + (behavior: ScrollBehavior = 'smooth') => { + const container = scrollContainerRef.current; + if (!container) return; + + // Prevent scroll event handler from updating isAtBottom during programmatic scroll + isScrollingRef.current = true; + + const targetScrollTop = container.scrollHeight - container.clientHeight; + + if (behavior === 'smooth') { + // Use native smooth scrolling + container.scrollTo({ + top: targetScrollTop, + behavior: 'smooth', + }); + + // Reset flag after animation completes + setTimeout(() => { + isScrollingRef.current = false; + isAtBottomRef.current = true; + }, smoothDuration); + } else { + container.scrollTop = targetScrollTop; + isScrollingRef.current = false; + isAtBottomRef.current = true; + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- scrollContainerRef is a ref, stable across renders + [smoothDuration] + ); + + /** + * Handle scroll events to track isAtBottom state. + */ + const handleScroll = useCallback(() => { + // Ignore scroll events during programmatic scrolling + if (isScrollingRef.current) return; + + isAtBottomRef.current = checkIsAtBottom(); + }, [checkIsAtBottom]); + + /** + * Set up scroll event listener. + */ + useEffect(() => { + const container = scrollContainerRef.current; + if (!container) return; + + container.addEventListener('scroll', handleScroll, { passive: true }); + + return () => { + container.removeEventListener('scroll', handleScroll); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- scrollContainerRef is a ref, stable across renders + }, [handleScroll]); + + /** + * Before content updates, remember if we were at bottom. + */ + useEffect(() => { + wasAtBottomBeforeUpdateRef.current = isAtBottomRef.current; + }); + + // Keep disabledRef in sync with disabled prop + useEffect(() => { + disabledRef.current = disabled; + }, [disabled]); + + // Reset isAtBottom state when resetKey changes (e.g., tab/session switch) + // This ensures new content will auto-scroll to bottom + useEffect(() => { + if (resetKey !== prevResetKeyRef.current) { + isAtBottomRef.current = true; + wasAtBottomBeforeUpdateRef.current = true; + prevResetKeyRef.current = resetKey; + } + }, [resetKey]); + + /** + * After content updates (dependencies change), scroll to bottom if we were at bottom. + */ + useEffect(() => { + // Skip if disabled (e.g., during navigation) or not enabled + if (!enabled || disabled) return; + + // Use requestAnimationFrame to ensure DOM has updated + requestAnimationFrame(() => { + // Re-check disabled state inside RAF - it might have changed between effect and callback + // This prevents auto-scroll from firing if navigation started after the effect ran + if (disabledRef.current) return; + + // Only auto-scroll if user was at bottom before the update + if (wasAtBottomBeforeUpdateRef.current) { + scrollToBottom('smooth'); + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps -- Dynamic dependencies array is intentional design + }, [...dependencies, enabled, disabled, scrollToBottom]); + + /** + * Getter function for isAtBottom to avoid accessing ref.current during render. + */ + const getIsAtBottom = useCallback((): boolean => { + return isAtBottomRef.current; + }, []); + + return { + scrollContainerRef, + getIsAtBottom, + scrollToBottom, + checkIsAtBottom, + }; +} diff --git a/src/renderer/hooks/useKeyboardShortcuts.ts b/src/renderer/hooks/useKeyboardShortcuts.ts new file mode 100644 index 00000000..a095aebe --- /dev/null +++ b/src/renderer/hooks/useKeyboardShortcuts.ts @@ -0,0 +1,284 @@ +/** + * useKeyboardShortcuts - Global keyboard shortcut handler + * Handles app-wide keyboard shortcuts for tab management, navigation, and pane management. + * + * Pane-scoped: Tab cycling (Ctrl+Tab, Cmd+1-9, Cmd+Shift+[/]) operates within the focused pane. + * Pane shortcuts: Cmd+Option+1-4 (focus pane), Cmd+\ (split right), Cmd+Option+W (close pane). + */ + +import { useEffect } from 'react'; + +import { createLogger } from '@shared/utils/logger'; +import { useShallow } from 'zustand/react/shallow'; + +import { useStore } from '../store'; + +const logger = createLogger('Hook:KeyboardShortcuts'); + +export function useKeyboardShortcuts(): void { + const { + openTabs, + activeTabId, + selectedTabIds, + openDashboard, + closeTab, + closeAllTabs, + closeTabs, + setActiveTab, + showSearch, + getActiveTab, + selectedProjectId, + selectedSessionId, + fetchSessionDetail, + fetchSessions, + openCommandPalette, + openSettingsTab, + toggleSidebar, + paneLayout, + focusPane, + splitPane, + closePane, + } = useStore( + useShallow((s) => ({ + openTabs: s.openTabs, + activeTabId: s.activeTabId, + selectedTabIds: s.selectedTabIds, + openDashboard: s.openDashboard, + closeTab: s.closeTab, + closeAllTabs: s.closeAllTabs, + closeTabs: s.closeTabs, + setActiveTab: s.setActiveTab, + showSearch: s.showSearch, + getActiveTab: s.getActiveTab, + selectedProjectId: s.selectedProjectId, + selectedSessionId: s.selectedSessionId, + fetchSessionDetail: s.fetchSessionDetail, + fetchSessions: s.fetchSessions, + openCommandPalette: s.openCommandPalette, + openSettingsTab: s.openSettingsTab, + toggleSidebar: s.toggleSidebar, + paneLayout: s.paneLayout, + focusPane: s.focusPane, + splitPane: s.splitPane, + closePane: s.closePane, + })) + ); + + useEffect(() => { + function handleKeyDown(event: KeyboardEvent): void { + // Check if Cmd (macOS) or Ctrl (Windows/Linux) is pressed + const isMod = event.metaKey || event.ctrlKey; + + // Ctrl+Tab / Ctrl+Shift+Tab: Switch tabs within focused pane (universal shortcut) + if (event.ctrlKey && event.key === 'Tab') { + event.preventDefault(); + const currentIndex = openTabs.findIndex((t) => t.id === activeTabId); + + if (event.shiftKey) { + // Ctrl+Shift+Tab: Previous tab (with wrap-around) + if (currentIndex > 0) { + setActiveTab(openTabs[currentIndex - 1].id); + } else if (openTabs.length > 0) { + // Wrap to last tab + setActiveTab(openTabs[openTabs.length - 1].id); + } + } else { + // Ctrl+Tab: Next tab (with wrap-around) + if (currentIndex !== -1 && currentIndex < openTabs.length - 1) { + setActiveTab(openTabs[currentIndex + 1].id); + } else if (openTabs.length > 0) { + // Wrap to first tab + setActiveTab(openTabs[0].id); + } + } + return; + } + + if (!isMod) return; + + // --- Pane management shortcuts (Cmd+Option) --- + + // Cmd+Option+1-4: Focus pane by index + if (event.altKey && !event.shiftKey) { + const numKey = parseInt(event.key); + if (numKey >= 1 && numKey <= 4) { + event.preventDefault(); + const targetPane = paneLayout.panes[numKey - 1]; + if (targetPane) { + focusPane(targetPane.id); + } + return; + } + + // Cmd+Option+W: Close current pane + if (event.key === 'w') { + event.preventDefault(); + if (paneLayout.panes.length > 1) { + closePane(paneLayout.focusedPaneId); + } + return; + } + } + + // Cmd+\: Split right with current tab + if (event.key === '\\' && !event.altKey && !event.shiftKey) { + event.preventDefault(); + if (activeTabId) { + splitPane(paneLayout.focusedPaneId, activeTabId, 'right'); + } + return; + } + + // Cmd+T: New tab (Dashboard) + if (event.key === 't') { + event.preventDefault(); + openDashboard(); + return; + } + + // Cmd+Shift+W: Close all tabs + if (event.key === 'w' && event.shiftKey && !event.altKey) { + event.preventDefault(); + closeAllTabs(); + return; + } + + // Cmd+W: Close selected tabs (if multi-selected) or active tab + if (event.key === 'w' && !event.altKey) { + event.preventDefault(); + if (selectedTabIds.length > 0) { + closeTabs(selectedTabIds); + } else if (activeTabId) { + closeTab(activeTabId); + } + return; + } + + // Cmd+[1-9]: Switch to tab by index within focused pane + const numKey = parseInt(event.key); + if (numKey >= 1 && numKey <= 9 && !event.altKey) { + event.preventDefault(); + const targetTab = openTabs[numKey - 1]; + if (targetTab) { + setActiveTab(targetTab.id); + } + return; + } + + // Cmd+Shift+]: Next tab within focused pane + if (event.key === ']' && event.shiftKey) { + event.preventDefault(); + const currentIndex = openTabs.findIndex((t) => t.id === activeTabId); + if (currentIndex !== -1 && currentIndex < openTabs.length - 1) { + setActiveTab(openTabs[currentIndex + 1].id); + } + return; + } + + // Cmd+Shift+[: Previous tab within focused pane + if (event.key === '[' && event.shiftKey) { + event.preventDefault(); + const currentIndex = openTabs.findIndex((t) => t.id === activeTabId); + if (currentIndex > 0) { + setActiveTab(openTabs[currentIndex - 1].id); + } + return; + } + + // Cmd+Option+Right: Next tab (browser-style) within focused pane + if (event.key === 'ArrowRight' && event.altKey) { + event.preventDefault(); + const currentIndex = openTabs.findIndex((t) => t.id === activeTabId); + if (currentIndex !== -1 && currentIndex < openTabs.length - 1) { + setActiveTab(openTabs[currentIndex + 1].id); + } + return; + } + + // Cmd+Option+Left: Previous tab (browser-style) within focused pane + if (event.key === 'ArrowLeft' && event.altKey) { + event.preventDefault(); + const currentIndex = openTabs.findIndex((t) => t.id === activeTabId); + if (currentIndex > 0) { + setActiveTab(openTabs[currentIndex - 1].id); + } + return; + } + + // Cmd+K: Open command palette for global search + if (event.key === 'k') { + event.preventDefault(); + openCommandPalette(); + return; + } + + // Cmd+,: Open settings (standard macOS shortcut) + if (event.key === ',') { + event.preventDefault(); + openSettingsTab(); + return; + } + + // Cmd+F: Find in session + if (event.key === 'f') { + event.preventDefault(); + const activeTab = getActiveTab(); + // Only enable search in session views, not dashboard + if (activeTab?.type === 'session') { + showSearch(); + } + return; + } + + // Cmd+O: Open project (placeholder for future implementation) + if (event.key === 'o') { + event.preventDefault(); + logger.debug('Open project shortcut triggered (not yet implemented)'); + return; + } + + // Cmd+R: Refresh current session and sidebar session list + if (event.key === 'r') { + event.preventDefault(); + if (selectedProjectId && selectedSessionId) { + void Promise.all([ + fetchSessionDetail(selectedProjectId, selectedSessionId), + fetchSessions(selectedProjectId), + ]); + } + return; + } + + // Cmd+B: Toggle sidebar + if (event.key === 'b') { + event.preventDefault(); + toggleSidebar(); + } + } + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [ + openTabs, + activeTabId, + selectedTabIds, + openDashboard, + closeTab, + closeAllTabs, + closeTabs, + setActiveTab, + showSearch, + getActiveTab, + selectedProjectId, + selectedSessionId, + fetchSessionDetail, + fetchSessions, + openCommandPalette, + openSettingsTab, + toggleSidebar, + paneLayout, + focusPane, + splitPane, + closePane, + ]); +} diff --git a/src/renderer/hooks/useTabNavigationController.ts b/src/renderer/hooks/useTabNavigationController.ts new file mode 100644 index 00000000..65274fea5 --- /dev/null +++ b/src/renderer/hooks/useTabNavigationController.ts @@ -0,0 +1,524 @@ +/** + * Unified Tab Navigation Controller + * + * Single active-tab controller that replaces useNavigationCoordinator + useSearchContextNavigation. + * Manages the complete lifecycle of navigation requests with proper sequencing: + * + * 1. Receive pending navigation request from tab state + * 2. Ignore if tab is not active (prevents cross-tab races) + * 3. Wait for content to load + * 4. Expand target group and item + * 5. Wait for DOM to stabilize + * 6. Scroll to target + * 7. Set highlight (red for error, yellow for search) + * 8. Clear highlight after timeout + * 9. Consume the navigation request (mark as processed) + * + * The nonce-based request model ensures: + * - Repeated clicks create new navigations + * - Tab switches don't re-trigger stale requests + * - Auto-scroll is suppressed during navigation + */ + +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { isErrorPayload, isSearchPayload } from '@renderer/types/tabs'; + +import { + calculateCenteredScrollTop, + findAIGroupBySubagentId, + findAIGroupByTimestamp, + findChatItemByTimestamp, + findCurrentSearchResultInContainer, + waitForElementStability, + waitForScrollEnd, +} from './navigation/utils'; + +import type { SessionConversation } from '@renderer/types/groups'; +import type { TabNavigationRequest } from '@renderer/types/tabs'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +// ============================================================================= +// Types +// ============================================================================= + +export type NavigationPhase = + | 'idle' // No navigation in progress + | 'pending' // Navigation requested, waiting for content + | 'expanding' // Expanding target group/item + | 'scrolling' // Scrolling to target + | 'highlighting' // Showing highlight ring + | 'complete'; // Navigation done, waiting to clear highlight + +interface UseTabNavigationControllerOptions { + /** Whether this tab instance is currently the active tab */ + isActiveTab: boolean; + /** Pending navigation request from tab state (undefined = no request) */ + pendingNavigation?: TabNavigationRequest; + /** Conversation data (null while loading) */ + conversation: SessionConversation | null; + /** Whether conversation is currently loading */ + conversationLoading: boolean; + /** Function to consume (mark as processed) a navigation request */ + consumeTabNavigation: (tabId: string, requestId: string) => void; + /** Tab ID for consuming navigation */ + tabId: string; + /** Refs to AI group DOM elements */ + aiGroupRefs: React.MutableRefObject>; + /** Refs to individual chat item DOM elements */ + chatItemRefs: React.MutableRefObject>; + /** Refs to individual tool item DOM elements */ + toolItemRefs: React.MutableRefObject>; + /** Function to expand an AI group (per-tab state) */ + expandAIGroup: (groupId: string) => void; + /** Ref to scroll container */ + scrollContainerRef: React.RefObject; + /** Height of sticky elements at top of scroll container */ + stickyOffset?: number; + /** Optional helper to ensure a target group is mounted (e.g., virtualized lists) */ + ensureGroupVisible?: (groupId: string) => Promise | void; + /** Function to expand a subagent trace (persists in per-tab state) */ + expandSubagentTrace: (subagentId: string) => void; + /** Function to set search query in the search bar */ + setSearchQuery: (query: string) => void; + /** Function to select an exact search match by item identity */ + selectSearchMatch: (itemId: string, matchIndexInItem: number) => boolean; + /** Highlight duration in ms (default: 3000) */ + highlightDuration?: number; +} + +interface UseTabNavigationControllerReturn { + /** Current navigation phase */ + phase: NavigationPhase; + /** Currently highlighted group ID */ + highlightedGroupId: string | null; + /** Tool use ID to highlight */ + highlightToolUseId: string | null; + /** Whether this is a search-based highlight (yellow) */ + isSearchHighlight: boolean; + /** Custom highlight color from trigger (undefined = default red) */ + highlightColor: TriggerColor | undefined; + /** Whether auto-scroll should be disabled */ + shouldDisableAutoScroll: boolean; + /** Set highlighted group (for external control, e.g., turn navigation) */ + setHighlightedGroupId: (id: string | null) => void; + /** Handle highlight end (clear highlight) */ + handleHighlightEnd: () => void; +} + +// ============================================================================= +// Hook Implementation +// ============================================================================= + +export function useTabNavigationController( + options: UseTabNavigationControllerOptions +): UseTabNavigationControllerReturn { + const { + isActiveTab, + pendingNavigation, + conversation, + conversationLoading, + consumeTabNavigation, + tabId, + aiGroupRefs, + chatItemRefs, + toolItemRefs, + expandAIGroup, + scrollContainerRef, + stickyOffset = 0, + ensureGroupVisible, + expandSubagentTrace, + setSearchQuery, + selectSearchMatch, + highlightDuration = 3000, + } = options; + + // State + const [phase, setPhase] = useState('idle'); + const [highlightedGroupId, setHighlightedGroupId] = useState(null); + const [currentToolUseId, setCurrentToolUseId] = useState(null); + const [isSearchHighlight, setIsSearchHighlight] = useState(false); + const [highlightColor, setHighlightColor] = useState(undefined); + + // Refs for tracking + const activeRequestIdRef = useRef(null); + const highlightTimerRef = useRef | null>(null); + const abortControllerRef = useRef(null); + const lastFailureAtRef = useRef(0); + + // Clear highlight and reset state + const handleHighlightEnd = useCallback(() => { + setHighlightedGroupId(null); + setCurrentToolUseId(null); + setIsSearchHighlight(false); + setHighlightColor(undefined); + setPhase('idle'); + activeRequestIdRef.current = null; + + if (highlightTimerRef.current) { + clearTimeout(highlightTimerRef.current); + highlightTimerRef.current = null; + } + }, []); + + // Abort any in-progress navigation + const abortNavigation = useCallback(() => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + if (highlightTimerRef.current) { + clearTimeout(highlightTimerRef.current); + highlightTimerRef.current = null; + } + }, []); + + // Execute error navigation sequence + const executeErrorNavigation = useCallback( + async (request: TabNavigationRequest, abortSignal: AbortSignal): Promise => { + if (!isErrorPayload(request) || !conversation) return false; + const { errorTimestamp, toolUseId, subagentId } = request.payload; + + const checkAborted = (): boolean => abortSignal.aborted; + + // Find target AI group (subagent-aware lookup first, then timestamp fallback) + let targetGroupId: string | null = null; + if (subagentId) { + targetGroupId = findAIGroupBySubagentId(conversation.items, subagentId); + } + if (!targetGroupId && errorTimestamp > 0) { + targetGroupId = findAIGroupByTimestamp(conversation.items, errorTimestamp); + } + if (!targetGroupId) { + // Fallback: last AI group + const aiItems = conversation.items.filter((item) => item.type === 'ai'); + if (aiItems.length > 0) { + targetGroupId = aiItems[aiItems.length - 1].group.id; + } + } + if (!targetGroupId) return false; + + // Phase 1: Expanding + setPhase('expanding'); + expandAIGroup(targetGroupId); + // Persist subagent trace expansion so it survives highlight clearing + if (subagentId) { + expandSubagentTrace(subagentId); + } + await ensureGroupVisible?.(targetGroupId); + if (checkAborted()) return false; + + // Set highlight early so it's visible even if scroll is imperfect + setHighlightedGroupId(targetGroupId); + setIsSearchHighlight(false); + // Error navigation uses a TriggerColor (preset key or custom hex, defaulting to 'red') + setHighlightColor(request.highlight === 'none' ? undefined : request.highlight); + if (toolUseId) setCurrentToolUseId(toolUseId); + + // Wait for element to exist and stabilize + let element: HTMLElement | undefined; + const elementLookupStart = Date.now(); + while (Date.now() - elementLookupStart < 600) { + element = aiGroupRefs.current.get(targetGroupId); + if (element) break; + await new Promise((resolve) => setTimeout(resolve, 50)); + if (checkAborted()) return false; + await ensureGroupVisible?.(targetGroupId); + } + // If element not found, highlight is already set — return success + if (!element) return true; + await waitForElementStability(element, 250, 2); + if (checkAborted()) return false; + + // Phase 2: Scrolling (best-effort — highlight already set) + setPhase('scrolling'); + + // Wait for tool item ref if needed (longer timeout for subagent cascading expansion) + let toolElement: HTMLElement | undefined; + if (toolUseId) { + // Subagents need more time: AI group expand → display item expand → trace expand → tool render + const toolLookupTimeout = subagentId ? 1200 : 300; + const startTime = Date.now(); + while (Date.now() - startTime < toolLookupTimeout) { + toolElement = toolItemRefs.current.get(toolUseId); + if (toolElement) break; + await new Promise((resolve) => setTimeout(resolve, 50)); + if (checkAborted()) return true; // Highlight already set + } + if (toolElement) { + await waitForElementStability(toolElement, 300, 2); + if (checkAborted()) return true; // Highlight already set + } + } + + // Scroll to target (best-effort) + const targetElement = toolElement ?? element; + const container = scrollContainerRef.current; + if (targetElement && container) { + const targetScrollTop = calculateCenteredScrollTop(targetElement, container, stickyOffset); + container.scrollTo({ top: targetScrollTop, behavior: 'smooth' }); + await waitForScrollEnd(container, 400); + } + if (checkAborted()) return false; + + // Phase 3: Highlight was set early, just update phase + setPhase('highlighting'); + return true; + }, + [ + conversation, + expandAIGroup, + expandSubagentTrace, + aiGroupRefs, + toolItemRefs, + scrollContainerRef, + stickyOffset, + ensureGroupVisible, + ] + ); + + // Execute search navigation sequence + const executeSearchNavigation = useCallback( + async (request: TabNavigationRequest, abortSignal: AbortSignal): Promise => { + if (!isSearchPayload(request) || !conversation) return false; + const { query, messageTimestamp, targetGroupId, targetMatchIndexInItem } = request.payload; + + const checkAborted = (): boolean => abortSignal.aborted; + + // Find target chat item (prefer exact group ID when provided) + const exactTargetItem = + targetGroupId !== undefined + ? conversation.items.find((item) => item.group.id === targetGroupId) + : undefined; + const targetItem = + exactTargetItem && + (exactTargetItem.type === 'user' || + exactTargetItem.type === 'system' || + exactTargetItem.type === 'ai' || + exactTargetItem.type === 'compact') + ? { groupId: exactTargetItem.group.id, type: exactTargetItem.type } + : findChatItemByTimestamp(conversation.items, messageTimestamp); + if (!targetItem) return false; + + // Phase 1: Expanding + setPhase('expanding'); + setSearchQuery(query); + if (targetGroupId !== undefined && targetMatchIndexInItem !== undefined) { + selectSearchMatch(targetGroupId, targetMatchIndexInItem); + } + setHighlightedGroupId(targetItem.groupId); + setIsSearchHighlight(true); + await ensureGroupVisible?.(targetItem.groupId); + if (checkAborted()) return false; + + // Wait for element to appear + const startedAt = Date.now(); + let targetEl: Element | null = null; + + while (!checkAborted() && Date.now() - startedAt < 600) { + targetEl = findCurrentSearchResultInContainer( + scrollContainerRef.current, + targetGroupId, + targetMatchIndexInItem + ); + if (!targetEl) { + targetEl = + chatItemRefs.current.get(targetItem.groupId) ?? + aiGroupRefs.current.get(targetItem.groupId) ?? + null; + } + if (targetEl) break; + await new Promise((resolve) => setTimeout(resolve, 50)); + await ensureGroupVisible?.(targetItem.groupId); + } + + if (checkAborted()) return false; + // If element not found, highlight is already set — return success + if (!targetEl) return true; + + // Phase 2: Scrolling (best-effort — highlight already set) + setPhase('scrolling'); + const container = scrollContainerRef.current; + if (container && targetEl instanceof HTMLElement) { + const targetScrollTop = calculateCenteredScrollTop(targetEl, container, stickyOffset); + container.scrollTo({ top: targetScrollTop, behavior: 'smooth' }); + await waitForScrollEnd(container, 400); + } else if (targetEl instanceof HTMLElement) { + targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); + await new Promise((resolve) => setTimeout(resolve, 350)); + } + + if (checkAborted()) return false; + + // Phase 3: Highlighting (yellow for search) + setPhase('highlighting'); + // highlightedGroupId and isSearchHighlight already set above + + return true; + }, + [ + conversation, + scrollContainerRef, + chatItemRefs, + aiGroupRefs, + stickyOffset, + ensureGroupVisible, + setSearchQuery, + selectSearchMatch, + ] + ); + + // Main navigation executor + const executeNavigation = useCallback( + async (request: TabNavigationRequest): Promise => { + abortNavigation(); + const abortController = new AbortController(); + abortControllerRef.current = abortController; + + try { + let success = false; + + if (request.kind === 'error') { + success = await executeErrorNavigation(request, abortController.signal); + } else if (request.kind === 'search') { + success = await executeSearchNavigation(request, abortController.signal); + } else if (request.kind === 'autoBottom') { + // autoBottom is handled by useAutoScrollBottom naturally + // Just consume the request and stay idle + consumeTabNavigation(tabId, request.id); + return; + } + + if (abortController.signal.aborted) return; + + if (success) { + // Schedule highlight end + highlightTimerRef.current = setTimeout(() => { + if (!abortController.signal.aborted) { + // Clear search state if it was a search navigation + if (request.kind === 'search') { + setSearchQuery(''); + } + handleHighlightEnd(); + } + }, highlightDuration); + + setPhase('complete'); + } else { + // Navigation failed - reset + setPhase('idle'); + setHighlightedGroupId(null); + setCurrentToolUseId(null); + setIsSearchHighlight(false); + setHighlightColor(undefined); + activeRequestIdRef.current = null; + lastFailureAtRef.current = Date.now(); + } + + // Consume the request regardless of success/failure to prevent re-processing + consumeTabNavigation(tabId, request.id); + } catch { + if (!abortController.signal.aborted) { + setPhase('idle'); + activeRequestIdRef.current = null; + lastFailureAtRef.current = Date.now(); + consumeTabNavigation(tabId, request.id); + } + } + }, + [ + abortNavigation, + executeErrorNavigation, + executeSearchNavigation, + consumeTabNavigation, + tabId, + highlightDuration, + handleHighlightEnd, + setSearchQuery, + ] + ); + + // Effect: Detect and process new navigation requests + useEffect(() => { + // Ignore if not active tab (prevents cross-tab races) + if (!isActiveTab) return; + + // No pending request + if (!pendingNavigation) return; + + // Already processing this request + if (activeRequestIdRef.current === pendingNavigation.id) return; + + // Recently failed - debounce + if (Date.now() - lastFailureAtRef.current < 500) return; + + // Record this request + activeRequestIdRef.current = pendingNavigation.id; + + // If content is loading, wait in pending state + if (conversationLoading || !conversation) { + queueMicrotask(() => setPhase('pending')); + return; + } + + // Execute navigation (deferred to avoid synchronous setState in effect) + queueMicrotask(() => { + void executeNavigation(pendingNavigation); + }); + }, [isActiveTab, pendingNavigation, conversationLoading, conversation, executeNavigation]); + + // Effect: When content finishes loading and we're pending, start navigation + useEffect(() => { + if (phase !== 'pending') return; + if (!isActiveTab) return; + if (conversationLoading || !conversation) return; + if (!pendingNavigation) return; + + queueMicrotask(() => { + void executeNavigation(pendingNavigation); + }); + }, [phase, isActiveTab, conversationLoading, conversation, pendingNavigation, executeNavigation]); + + // Effect: Reset when tab becomes inactive + useEffect(() => { + if (!isActiveTab && phase !== 'idle') { + abortNavigation(); + queueMicrotask(() => { + setPhase('idle'); + setHighlightedGroupId(null); + setCurrentToolUseId(null); + setIsSearchHighlight(false); + setHighlightColor(undefined); + }); + activeRequestIdRef.current = null; + } + }, [isActiveTab, phase, abortNavigation]); + + // Cleanup on unmount + useEffect(() => { + return () => { + abortNavigation(); + }; + }, [abortNavigation]); + + // Computed: should disable auto-scroll + const shouldDisableAutoScroll = + phase === 'pending' || + phase === 'expanding' || + phase === 'scrolling' || + phase === 'highlighting' || + phase === 'complete' || + // Also disable while any pendingNavigation exists (even before processing starts) + (isActiveTab && pendingNavigation !== undefined); + + return { + phase, + highlightedGroupId, + highlightToolUseId: currentToolUseId, + isSearchHighlight, + highlightColor, + shouldDisableAutoScroll, + setHighlightedGroupId, + handleHighlightEnd, + }; +} diff --git a/src/renderer/hooks/useTabUI.ts b/src/renderer/hooks/useTabUI.ts new file mode 100644 index 00000000..91a06a1c --- /dev/null +++ b/src/renderer/hooks/useTabUI.ts @@ -0,0 +1,252 @@ +/** + * useTabUI - Hook for accessing per-tab UI state. + * + * This hook combines the TabUIContext (for tabId) with the tabUISlice (for state/actions). + * It provides a simple interface for components to access their tab-specific UI state. + * + * IMPORTANT: This hook subscribes to `tabUIStates` directly to ensure proper reactivity. + * Using getter functions (like isContextPanelVisibleForTab) in useMemo doesn't work + * because the function reference doesn't change when the underlying state changes. + * + * Usage: + * ```tsx + * const { isAIGroupExpanded, toggleAIGroupExpansion } = useTabUI(); + * + * // Check if a group is expanded in THIS tab + * if (isAIGroupExpanded(groupId)) { ... } + * + * // Toggle expansion in THIS tab only + * toggleAIGroupExpansion(groupId); + * ``` + */ + +import { useCallback, useMemo } from 'react'; + +import { useTabIdOptional } from '@renderer/contexts/useTabUIContext'; +import { useStore } from '@renderer/store'; +import { useShallow } from 'zustand/react/shallow'; + +// ============================================================================= +// Types +// ============================================================================= + +interface UseTabUIReturn { + tabId: string | null; + isAIGroupExpanded: (aiGroupId: string) => boolean; + toggleAIGroupExpansion: (aiGroupId: string) => void; + expandAIGroup: (aiGroupId: string) => void; + getExpandedDisplayItemIds: (aiGroupId: string) => Set; + toggleDisplayItemExpansion: (aiGroupId: string, itemId: string) => void; + expandDisplayItem: (aiGroupId: string, itemId: string) => void; + isSubagentTraceExpanded: (subagentId: string) => boolean; + toggleSubagentTraceExpansion: (subagentId: string) => void; + expandSubagentTrace: (subagentId: string) => void; + isContextPanelVisible: boolean; + setContextPanelVisible: (visible: boolean) => void; + selectedContextPhase: number | null; + setSelectedContextPhase: (phase: number | null) => void; + savedScrollTop: number | undefined; + saveScrollPosition: (scrollTop: number) => void; + initializeTabUI: () => void; +} + +// ============================================================================= +// Main Hook +// ============================================================================= + +/** + * Hook for accessing per-tab UI state and actions. + * + * @returns Object containing per-tab state getters and actions + */ +export function useTabUI(): UseTabUIReturn { + // Get tabId from context (null if not in a tab) + const tabId = useTabIdOptional(); + + // Subscribe to tabUIStates MAP directly for reactivity + // This ensures re-renders when any tab state changes + const tabUIStates = useStore((s) => s.tabUIStates); + + // Get the current tab's state (derived from subscribed state) + const tabState = useMemo(() => { + if (!tabId) return null; + return tabUIStates.get(tabId) ?? null; + }, [tabId, tabUIStates]); + + // Get all tab UI actions from store (these are stable function references) + const { + toggleAIGroupExpansionForTab, + expandAIGroupForTab, + toggleDisplayItemExpansionForTab, + expandDisplayItemForTab, + toggleSubagentTraceExpansionForTab, + expandSubagentTraceForTab, + setContextPanelVisibleForTab, + setSelectedContextPhaseForTab, + saveScrollPositionForTab, + initTabUIState, + } = useStore( + useShallow((s) => ({ + toggleAIGroupExpansionForTab: s.toggleAIGroupExpansionForTab, + expandAIGroupForTab: s.expandAIGroupForTab, + toggleDisplayItemExpansionForTab: s.toggleDisplayItemExpansionForTab, + expandDisplayItemForTab: s.expandDisplayItemForTab, + toggleSubagentTraceExpansionForTab: s.toggleSubagentTraceExpansionForTab, + expandSubagentTraceForTab: s.expandSubagentTraceForTab, + setContextPanelVisibleForTab: s.setContextPanelVisibleForTab, + setSelectedContextPhaseForTab: s.setSelectedContextPhaseForTab, + saveScrollPositionForTab: s.saveScrollPositionForTab, + initTabUIState: s.initTabUIState, + })) + ); + + // ========================================================================== + // Derived state from tabState (reactive!) + // ========================================================================== + + // AI Group expansion - check directly from tabState + const isAIGroupExpanded = useCallback( + (aiGroupId: string): boolean => { + return tabState?.expandedAIGroupIds.has(aiGroupId) ?? false; + }, + [tabState] + ); + + const toggleAIGroupExpansion = useCallback( + (aiGroupId: string): void => { + if (!tabId) return; + toggleAIGroupExpansionForTab(tabId, aiGroupId); + }, + [tabId, toggleAIGroupExpansionForTab] + ); + + const expandAIGroup = useCallback( + (aiGroupId: string): void => { + if (!tabId) return; + expandAIGroupForTab(tabId, aiGroupId); + }, + [tabId, expandAIGroupForTab] + ); + + // Display item expansion - derive from tabState + const getExpandedDisplayItemIds = useCallback( + (aiGroupId: string): Set => { + return tabState?.expandedDisplayItemIds.get(aiGroupId) ?? new Set(); + }, + [tabState] + ); + + const toggleDisplayItemExpansion = useCallback( + (aiGroupId: string, itemId: string): void => { + if (!tabId) return; + toggleDisplayItemExpansionForTab(tabId, aiGroupId, itemId); + }, + [tabId, toggleDisplayItemExpansionForTab] + ); + + const expandDisplayItem = useCallback( + (aiGroupId: string, itemId: string): void => { + if (!tabId) return; + expandDisplayItemForTab(tabId, aiGroupId, itemId); + }, + [tabId, expandDisplayItemForTab] + ); + + // Subagent trace expansion - derive from tabState + const isSubagentTraceExpanded = useCallback( + (subagentId: string): boolean => { + return tabState?.expandedSubagentTraceIds.has(subagentId) ?? false; + }, + [tabState] + ); + + const toggleSubagentTraceExpansion = useCallback( + (subagentId: string): void => { + if (!tabId) return; + toggleSubagentTraceExpansionForTab(tabId, subagentId); + }, + [tabId, toggleSubagentTraceExpansionForTab] + ); + + const expandSubagentTrace = useCallback( + (subagentId: string): void => { + if (!tabId) return; + expandSubagentTraceForTab(tabId, subagentId); + }, + [tabId, expandSubagentTraceForTab] + ); + + // Context panel - derive directly from tabState (reactive!) + const isContextPanelVisible = tabState?.showContextPanel ?? false; + + const setContextPanelVisible = useCallback( + (visible: boolean): void => { + if (!tabId) return; + setContextPanelVisibleForTab(tabId, visible); + }, + [tabId, setContextPanelVisibleForTab] + ); + + // Context phase selection - derive from tabState + const selectedContextPhase = tabState?.selectedContextPhase ?? null; + + const setSelectedContextPhase = useCallback( + (phase: number | null): void => { + if (!tabId) return; + setSelectedContextPhaseForTab(tabId, phase); + }, + [tabId, setSelectedContextPhaseForTab] + ); + + // Scroll position - derive from tabState + const savedScrollTop = tabState?.savedScrollTop; + + const saveScrollPosition = useCallback( + (scrollTop: number): void => { + if (!tabId) return; + saveScrollPositionForTab(tabId, scrollTop); + }, + [tabId, saveScrollPositionForTab] + ); + + // Initialize tab UI state (call once when tab is mounted) + const initializeTabUI = useCallback((): void => { + if (!tabId) return; + initTabUIState(tabId); + }, [tabId, initTabUIState]); + + return { + // Current tab ID + tabId, + + // AI Group expansion + isAIGroupExpanded, + toggleAIGroupExpansion, + expandAIGroup, + + // Display item expansion + getExpandedDisplayItemIds, + toggleDisplayItemExpansion, + expandDisplayItem, + + // Subagent trace expansion + isSubagentTraceExpanded, + toggleSubagentTraceExpansion, + expandSubagentTrace, + + // Context panel + isContextPanelVisible, + setContextPanelVisible, + + // Context phase selection + selectedContextPhase, + setSelectedContextPhase, + + // Scroll position + savedScrollTop, + saveScrollPosition, + + // Initialization + initializeTabUI, + }; +} diff --git a/src/renderer/hooks/useTheme.ts b/src/renderer/hooks/useTheme.ts new file mode 100644 index 00000000..1ee91c42 --- /dev/null +++ b/src/renderer/hooks/useTheme.ts @@ -0,0 +1,102 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; + +import { useStore } from '../store'; + +type Theme = 'dark' | 'light' | 'system'; +type ResolvedTheme = 'dark' | 'light'; + +const THEME_CACHE_KEY = 'claude-code-context-theme-cache'; + +/** + * Hook to manage theme state and application. + * - Fetches theme preference from config on mount + * - Listens to system theme changes when set to 'system' + * - Applies theme class to document root + * - Caches theme in localStorage for flash prevention + */ +export function useTheme(): { + theme: Theme; + resolvedTheme: ResolvedTheme; + isDark: boolean; + isLight: boolean; +} { + const { appConfig, fetchConfig } = useStore( + useShallow((s) => ({ + appConfig: s.appConfig, + fetchConfig: s.fetchConfig, + })) + ); + const [resolvedTheme, setResolvedTheme] = useState(() => { + // Initialize from cache to prevent flash + try { + const cached = localStorage.getItem(THEME_CACHE_KEY); + if (cached === 'light') return 'light'; + } catch { + // localStorage may not be available + } + return 'dark'; + }); + + // Fetch config on mount if not loaded + useEffect(() => { + if (!appConfig) { + void fetchConfig(); + } + }, [appConfig, fetchConfig]); + + // Get configured theme + const configuredTheme: Theme = appConfig?.general?.theme ?? 'dark'; + + // Get system theme preference + const getSystemTheme = useCallback((): ResolvedTheme => { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + }, []); + + // Resolve 'system' theme and listen for changes + useEffect(() => { + const updateTheme = (): void => { + const resolved = configuredTheme === 'system' ? getSystemTheme() : configuredTheme; + setResolvedTheme(resolved); + + // Cache for flash prevention + try { + localStorage.setItem(THEME_CACHE_KEY, resolved); + } catch { + // localStorage may not be available + } + }; + + updateTheme(); + + // Listen to system theme changes when in 'system' mode + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = (): void => { + if (configuredTheme === 'system') { + updateTheme(); + } + }; + + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + }, [configuredTheme, getSystemTheme]); + + // Apply theme class to document root + useEffect(() => { + const root = document.documentElement; + + // Remove existing theme classes + root.classList.remove('dark', 'light'); + + // Add new theme class + root.classList.add(resolvedTheme); + }, [resolvedTheme]); + + return { + theme: configuredTheme, + resolvedTheme, + isDark: resolvedTheme === 'dark', + isLight: resolvedTheme === 'light', + }; +} diff --git a/src/renderer/hooks/useVisibleAIGroup.ts b/src/renderer/hooks/useVisibleAIGroup.ts new file mode 100644 index 00000000..74a0187b --- /dev/null +++ b/src/renderer/hooks/useVisibleAIGroup.ts @@ -0,0 +1,122 @@ +import { type RefObject, useCallback, useEffect, useRef } from 'react'; + +interface UseVisibleAIGroupOptions { + onVisibleChange: (aiGroupId: string) => void; + threshold?: number; // Default 0.5 + /** Optional scroll container to observe against (important for nested scroll areas). */ + rootRef?: RefObject; +} + +interface UseVisibleAIGroupReturn { + registerAIGroupRef: (aiGroupId: string) => (element: HTMLElement | null) => void; +} + +export function useVisibleAIGroup(options: UseVisibleAIGroupOptions): UseVisibleAIGroupReturn { + const { onVisibleChange, threshold = 0.5, rootRef } = options; + + // Track which AI Groups are currently visible (above threshold) + const visibleAIGroupIds = useRef>(new Set()); + + // Track element references by AI Group ID + const elementRefs = useRef>(new Map()); + + // IntersectionObserver instance + const observerRef = useRef(null); + + // Calculate and report the topmost visible AI Group + const updateTopmostVisible = useCallback(() => { + if (visibleAIGroupIds.current.size === 0) { + return; + } + + let topmostId: string | null = null; + let minTop = Infinity; + + // Find the AI Group with the smallest top position (closest to top of viewport) + visibleAIGroupIds.current.forEach((id) => { + const element = elementRefs.current.get(id); + if (element) { + const rect = element.getBoundingClientRect(); + if (rect.top < minTop) { + minTop = rect.top; + topmostId = id; + } + } + }); + + if (topmostId) { + onVisibleChange(topmostId); + } + }, [onVisibleChange]); + + // Set up IntersectionObserver + useEffect(() => { + observerRef.current = new IntersectionObserver( + (entries) => { + let changed = false; + + entries.forEach((entry) => { + const aiGroupId = entry.target.getAttribute('data-aigroup-id'); + if (!aiGroupId) return; + + if (entry.isIntersecting && entry.intersectionRatio >= threshold) { + // Element is visible above threshold + if (!visibleAIGroupIds.current.has(aiGroupId)) { + visibleAIGroupIds.current.add(aiGroupId); + changed = true; + } + } else { + // Element is not visible or below threshold + if (visibleAIGroupIds.current.has(aiGroupId)) { + visibleAIGroupIds.current.delete(aiGroupId); + changed = true; + } + } + }); + + // Recalculate topmost visible AI Group if visibility changed + if (changed) { + updateTopmostVisible(); + } + }, + { + root: rootRef?.current ?? null, + threshold, + // Use root margin to start detection slightly before element enters viewport + rootMargin: '0px', + } + ); + + return () => { + observerRef.current?.disconnect(); + observerRef.current = null; + }; + }, [threshold, updateTopmostVisible, rootRef]); + + // Register an AI Group element for observation + const registerAIGroupRef = useCallback((aiGroupId: string) => { + return (element: HTMLElement | null) => { + const observer = observerRef.current; + if (!observer) return; + + // Clean up previous element if it exists + const prevElement = elementRefs.current.get(aiGroupId); + if (prevElement) { + observer.unobserve(prevElement); + elementRefs.current.delete(aiGroupId); + visibleAIGroupIds.current.delete(aiGroupId); + } + + // Register new element + if (element) { + element.setAttribute('data-aigroup-id', aiGroupId); + elementRefs.current.set(aiGroupId, element); + observer.observe(element); + } + }; + }, []); + + return { + registerAIGroupRef, + }; +} diff --git a/src/renderer/hooks/useZoomFactor.ts b/src/renderer/hooks/useZoomFactor.ts new file mode 100644 index 00000000..0602ae9c --- /dev/null +++ b/src/renderer/hooks/useZoomFactor.ts @@ -0,0 +1,34 @@ +import { useEffect, useState } from 'react'; + +/** + * Reads current zoom factor and stays subscribed to zoom updates from main. + */ +export function useZoomFactor(): number { + const [zoomFactor, setZoomFactor] = useState(1); + + useEffect(() => { + let isMounted = true; + + void window.electronAPI + .getZoomFactor() + .then((value) => { + if (isMounted) { + setZoomFactor(value); + } + }) + .catch(() => { + // Keep default 1 if zoom factor cannot be read. + }); + + const unsubscribe = window.electronAPI.onZoomFactorChanged((value) => { + setZoomFactor(value); + }); + + return () => { + isMounted = false; + unsubscribe(); + }; + }, []); + + return zoomFactor; +} diff --git a/src/renderer/index.css b/src/renderer/index.css new file mode 100644 index 00000000..a9125363 --- /dev/null +++ b/src/renderer/index.css @@ -0,0 +1,505 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Theme CSS Custom Properties */ +:root { + /* Dark theme (default) - Soft Charcoal palette */ + --color-surface: #141416; /* Main Chat Area Background (Soft Matte Charcoal) */ + --color-surface-raised: #27272a; /* Active/Selected items (Zinc-800) */ + --color-surface-overlay: #27272a; /* Overlay surfaces */ + --color-surface-sidebar: #0f0f11; /* Sidebar Background (slightly darker) */ + --color-border: rgba(255, 255, 255, 0.05); /* Borders/Dividers (5% white) */ + --color-border-subtle: rgba(255, 255, 255, 0.05); /* Subtle borders (5% white) */ + --color-border-emphasis: rgba(255, 255, 255, 0.1); /* Emphasis borders (10% white) */ + --color-text: #fafafa; + --color-text-secondary: #a1a1aa; + --color-text-muted: #71717a; + + /* Scrollbar colors */ + --scrollbar-thumb: rgba(255, 255, 255, 0.15); + --scrollbar-thumb-hover: rgba(255, 255, 255, 0.25); + --scrollbar-thumb-active: rgba(255, 255, 255, 0.35); + + /* Search highlights */ + --highlight-bg: rgba(202, 138, 4, 0.7); + --highlight-bg-inactive: rgba(113, 63, 18, 0.5); + --highlight-text: #fef9c3; + --highlight-text-inactive: #fef08a; + --highlight-ring: #facc15; + + /* User chat bubble - Soft Charcoal theme (softer text for visual hierarchy) */ + --chat-user-bg: #27272a; + --chat-user-text: #a1a1aa; + --chat-user-border: rgba(255, 255, 255, 0.08); + --chat-user-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.03); + + /* User bubble inline tags - Linear-style neutral */ + --chat-user-tag-bg: rgba(255, 255, 255, 0.08); + --chat-user-tag-text: #e4e4e7; + --chat-user-tag-border: rgba(255, 255, 255, 0.12); + + /* Tool items */ + --tool-item-name: #e4e4e7; + --tool-item-summary: #a1a1aa; + --tool-item-muted: #71717a; + --tool-item-hover-bg: rgba(39, 39, 42, 0.5); + + /* System chat bubble */ + --chat-system-bg: rgba(39, 39, 42, 0.5); + --chat-system-text: #d4d4d8; + + /* AI message styling */ + --chat-ai-border: rgba(255, 255, 255, 0.05); + --chat-ai-icon: #71717a; + + /* Code blocks - Soft Charcoal theme */ + --code-bg: #1c1c1e; + --code-header-bg: #1c1c1e; + --code-border: rgba(255, 255, 255, 0.1); + --code-line-number: #52525b; + --code-filename: #60a5fa; + + /* Syntax highlighting - Dark theme */ + --syntax-string: #4ade80; + --syntax-comment: #71717a; + --syntax-number: #fb923c; + --syntax-keyword: #c084fc; + --syntax-type: #facc15; + --syntax-operator: #a1a1aa; + --syntax-function: #60a5fa; + + /* Inline code - Linear-style neutral */ + --inline-code-bg: rgba(255, 255, 255, 0.08); + --inline-code-text: #e4e4e7; + + /* Diff viewer */ + --diff-added-bg: rgba(34, 197, 94, 0.15); + --diff-added-text: #4ade80; + --diff-added-border: #22c55e; + --diff-removed-bg: rgba(239, 68, 68, 0.15); + --diff-removed-text: #f87171; + --diff-removed-border: #ef4444; + + /* Markdown prose - Brighter body text for AI responses (visual hierarchy) */ + --prose-heading: #ffffff; + --prose-body: #f4f4f5; + --prose-muted: #a1a1aa; + --prose-link: #60a5fa; + --prose-code-bg: rgba(255, 255, 255, 0.08); + --prose-code-text: #e4e4e7; + --prose-pre-bg: #1c1c1e; + --prose-pre-border: rgba(255, 255, 255, 0.1); + --prose-blockquote-border: rgba(255, 255, 255, 0.1); + --prose-table-border: rgba(255, 255, 255, 0.05); + --prose-table-header-bg: #27272a; + + /* Thinking blocks */ + --thinking-bg: rgba(88, 28, 135, 0.2); + --thinking-border: rgba(107, 33, 168, 0.4); + --thinking-text: #d8b4fe; + --thinking-text-muted: #e9d5ff; + --thinking-content-border: rgba(107, 33, 168, 0.3); + --thinking-content-text: #f3e8ff; + + /* Tool call blocks */ + --tool-call-bg: rgba(120, 53, 15, 0.2); + --tool-call-border: rgba(146, 64, 14, 0.4); + --tool-call-text: #fcd34d; + --tool-call-code-bg: rgba(69, 26, 3, 0.5); + --tool-call-content-border: rgba(146, 64, 14, 0.3); + + /* Tool result blocks */ + --tool-result-success-bg: rgba(20, 83, 45, 0.2); + --tool-result-success-border: rgba(22, 101, 52, 0.4); + --tool-result-success-text: #86efac; + --tool-result-error-bg: rgba(127, 29, 29, 0.2); + --tool-result-error-border: rgba(153, 27, 27, 0.4); + --tool-result-error-text: #fca5a5; + + /* Output blocks */ + --output-bg: rgba(31, 41, 55, 0.3); + --output-border: #374151; + --output-text: #e5e7eb; + --output-content-border: rgba(55, 65, 81, 0.5); + + /* Badges */ + --badge-error-bg: #dc2626; + --badge-error-text: #ffffff; + --badge-warning-bg: #ca8a04; + --badge-warning-text: #ffffff; + --badge-success-bg: #16a34a; + --badge-success-text: #ffffff; + --badge-info-bg: #2563eb; + --badge-info-text: #ffffff; + --badge-neutral-bg: #3f3f46; + --badge-neutral-text: #e4e4e7; + + /* Language/tag badges */ + --tag-bg: #27272a; + --tag-text: #a1a1aa; + --tag-border: rgba(255, 255, 255, 0.05); + + /* Highlighted text (skills, paths) - Linear-style neutral (same as inline code) */ + --skill-highlight-bg: rgba(255, 255, 255, 0.08); + --skill-highlight-text: #e4e4e7; + --path-highlight-bg: rgba(255, 255, 255, 0.08); + --path-highlight-text: #e4e4e7; + + /* Interruption badge */ + --interruption-bg: rgba(127, 29, 29, 0.3); + --interruption-border: #b91c1c; + --interruption-text: #fca5a5; + + /* Warning/Amber colors (for interruption banner) */ + --warning-bg: rgba(245, 158, 11, 0.15); + --warning-border: rgba(245, 158, 11, 0.4); + --warning-text: #fbbf24; + + /* Plan exit/Green colors (for ExitPlanMode) */ + --plan-exit-bg: rgba(34, 197, 94, 0.05); + --plan-exit-header-bg: rgba(34, 197, 94, 0.1); + --plan-exit-border: rgba(34, 197, 94, 0.25); + --plan-exit-text: #4ade80; + + /* Error highlight (pulsing) */ + --error-highlight-ring: #ef4444; + --error-highlight-bg: rgba(239, 68, 68, 0.2); + + /* Keyboard hints */ + --kbd-bg: #27272a; + --kbd-border: rgba(255, 255, 255, 0.1); + --kbd-text: #d4d4d8; + + /* Subagent/Card styling */ + --card-bg: #121212; + --card-border: #27272a; + --card-header-bg: #18181b; + --card-header-hover: #1f1f23; + --card-icon-muted: #52525b; + --card-text-light: #d4d4d8; + --card-text-lighter: #e4e4e7; + --card-separator: #3f3f46; + + /* Sticky Context button - transparent glass */ + --context-btn-bg: rgba(255, 255, 255, 0.08); + --context-btn-bg-hover: rgba(255, 255, 255, 0.14); + --context-btn-active-bg: rgba(99, 102, 241, 0.45); + --context-btn-active-text: #e0e7ff; +} + +/* Light theme overrides - Warm neutral palette for eye comfort */ +:root.light { + --color-surface: #f9f9f7; /* Warm off-white (not pure white) */ + --color-surface-raised: #f0efed; /* Warm raised surface, clearly distinct */ + --color-surface-overlay: #e8e7e4; /* Warm overlay */ + --color-surface-sidebar: #f1f0ee; /* Warm sidebar, distinct from main */ + --color-border: #d5d3cf; /* Warm neutral border */ + --color-border-subtle: #e3e1dd; /* Warm subtle border */ + --color-border-emphasis: #a8a5a0; /* Warm emphasis border */ + --color-text: #1c1b19; /* Warm near-black text */ + --color-text-secondary: #4d4b46; /* Warm secondary text */ + --color-text-muted: #6d6b65; /* Warm muted text */ + + /* Scrollbar colors for light mode */ + --scrollbar-thumb: rgba(0, 0, 0, 0.15); + --scrollbar-thumb-hover: rgba(0, 0, 0, 0.28); + --scrollbar-thumb-active: rgba(0, 0, 0, 0.4); + + /* Search highlights - High saturation yellow */ + --highlight-bg: #facc15; + --highlight-bg-inactive: #fef08a; + --highlight-text: #1c1917; + --highlight-text-inactive: #422006; + --highlight-ring: #ca8a04; + + /* User chat bubble - Warm neutral, clearly visible */ + --chat-user-bg: #eae9e6; + --chat-user-text: #5a5955; + --chat-user-border: #d5d3cf; + --chat-user-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.04); + + /* User bubble inline tags - Warm neutral */ + --chat-user-tag-bg: rgba(0, 0, 0, 0.05); + --chat-user-tag-text: #3a3935; + --chat-user-tag-border: rgba(0, 0, 0, 0.08); + + /* Tool items - Warm high contrast */ + --tool-item-name: #1c1b19; + --tool-item-summary: #4d4b46; + --tool-item-muted: #6d6b65; + --tool-item-hover-bg: #eae9e6; + + /* System chat bubble */ + --chat-system-bg: #eae9e6; + --chat-system-text: #3a3935; + + /* AI message styling */ + --chat-ai-border: #d5d3cf; + --chat-ai-icon: #6d6b65; + + /* Code blocks - Warm light theme */ + --code-bg: #f0efed; + --code-header-bg: #eae9e6; + --code-border: #d5d3cf; + --code-line-number: #a8a5a0; + --code-filename: #2563eb; + + /* Syntax highlighting - GitHub Light inspired */ + --syntax-string: #0a3069; + --syntax-comment: #6e7781; + --syntax-number: #0550ae; + --syntax-keyword: #cf222e; + --syntax-type: #8250df; + --syntax-operator: #24292f; + --syntax-function: #8250df; + + /* Inline code - Warm neutral */ + --inline-code-bg: rgba(0, 0, 0, 0.05); + --inline-code-text: #3a3935; + + /* Diff viewer - Light mode */ + --diff-added-bg: rgba(34, 197, 94, 0.1); + --diff-added-text: #166534; + --diff-added-border: #22c55e; + --diff-removed-bg: rgba(239, 68, 68, 0.1); + --diff-removed-text: #991b1b; + --diff-removed-border: #ef4444; + + /* Markdown prose - Warm tones */ + --prose-heading: #1c1b19; + --prose-body: #2a2925; + --prose-muted: #6d6b65; + --prose-link: #2563eb; + --prose-code-bg: rgba(0, 0, 0, 0.05); + --prose-code-text: #3a3935; + --prose-pre-bg: #f0efed; + --prose-pre-border: #d5d3cf; + --prose-blockquote-border: #d5d3cf; + --prose-table-border: #e3e1dd; + --prose-table-header-bg: #eae9e6; + + /* Thinking blocks - Warm purple */ + --thinking-bg: #f9f5fe; + --thinking-border: #d8b4fe; + --thinking-text: #6b21a8; + --thinking-text-muted: #7c3aed; + --thinking-content-border: #e9d5ff; + --thinking-content-text: #581c87; + + /* Tool call blocks - Warm amber */ + --tool-call-bg: #fefbf0; + --tool-call-border: #f0d070; + --tool-call-text: #92400e; + --tool-call-code-bg: #fdf3d0; + --tool-call-content-border: #f0d888; + + /* Tool result blocks */ + --tool-result-success-bg: #f0fdf4; + --tool-result-success-border: #86efac; + --tool-result-success-text: #166534; + --tool-result-error-bg: #fef2f2; + --tool-result-error-border: #fca5a5; + --tool-result-error-text: #991b1b; + + /* Output blocks */ + --output-bg: #f0efed; + --output-border: #d5d3cf; + --output-text: #2a2925; + --output-content-border: #d5d3cf; + + /* Badges - High contrast */ + --badge-error-bg: #dc2626; + --badge-error-text: #ffffff; + --badge-warning-bg: #d97706; + --badge-warning-text: #ffffff; + --badge-success-bg: #16a34a; + --badge-success-text: #ffffff; + --badge-info-bg: #2563eb; + --badge-info-text: #ffffff; + --badge-neutral-bg: #e3e1dd; + --badge-neutral-text: #3a3935; + + /* Language/tag badges - Warm neutral */ + --tag-bg: #eae9e6; + --tag-text: #4d4b46; + --tag-border: #d5d3cf; + + /* Highlighted text (skills, paths) - Warm neutral */ + --skill-highlight-bg: rgba(0, 0, 0, 0.05); + --skill-highlight-text: #3a3935; + --path-highlight-bg: rgba(0, 0, 0, 0.05); + --path-highlight-text: #3a3935; + + /* Interruption badge */ + --interruption-bg: #fef2f2; + --interruption-border: #f87171; + --interruption-text: #b91c1c; + + /* Warning/Amber colors (for interruption banner) */ + --warning-bg: #fef3c7; + --warning-border: #f59e0b; + --warning-text: #b45309; + + /* Plan exit/Green colors (for ExitPlanMode) */ + --plan-exit-bg: #f0fdf4; + --plan-exit-header-bg: #dcfce7; + --plan-exit-border: #86efac; + --plan-exit-text: #15803d; + + /* Error highlight (pulsing) */ + --error-highlight-ring: #dc2626; + --error-highlight-bg: rgba(220, 38, 38, 0.15); + + /* Keyboard hints */ + --kbd-bg: #eae9e6; + --kbd-border: #a8a5a0; + --kbd-text: #3a3935; + + /* Subagent/Card styling - Warm light mode */ + --card-bg: #f9f9f7; + --card-border: #d5d3cf; + --card-header-bg: #f0efed; + --card-header-hover: #eae9e6; + --card-icon-muted: #a8a5a0; + --card-text-light: #3a3935; + --card-text-lighter: #2a2925; + --card-separator: #d5d3cf; + + /* Sticky Context button - transparent glass */ + --context-btn-bg: rgba(0, 0, 0, 0.06); + --context-btn-bg-hover: rgba(0, 0, 0, 0.1); + --context-btn-active-bg: rgba(99, 102, 241, 0.35); + --context-btn-active-text: #3730a3; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', + 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: var(--color-surface); + color: var(--color-text); + transition: + background-color 0.2s ease, + color 0.2s ease; +} + +#root { + width: 100vw; + height: 100vh; + overflow: hidden; +} + +/* macOS window integration */ +.sidebar-header { + -webkit-app-region: drag; + -webkit-user-select: none; +} + +.sidebar-header button, +.sidebar-header input, +.sidebar-header .no-drag { + -webkit-app-region: no-drag; +} + +/* Hide horizontal scrollbar in tab bar */ +.scrollbar-none { + scrollbar-width: none; + -ms-overflow-style: none; +} + +.scrollbar-none::-webkit-scrollbar { + display: none; +} + +/* Custom scrollbar styling for dark theme */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 4px; + transition: background 0.2s ease; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--scrollbar-thumb-hover); +} + +::-webkit-scrollbar-thumb:active { + background: var(--scrollbar-thumb-active); +} + +/* Firefox scrollbar */ +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) transparent; +} + +/* Dashboard skeleton shimmer animation */ +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} + +@keyframes skeleton-fade-in { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.skeleton-card { + animation: skeleton-fade-in 0.4s ease-out both; + position: relative; + overflow: hidden; +} + +.skeleton-card::after { + content: ''; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.04) 40%, + rgba(255, 255, 255, 0.06) 50%, + rgba(255, 255, 255, 0.04) 60%, + transparent 100% + ); + animation: shimmer 1.8s ease-in-out infinite; +} + +:root.light .skeleton-card::after { + background: linear-gradient( + 90deg, + transparent 0%, + rgba(0, 0, 0, 0.03) 40%, + rgba(0, 0, 0, 0.05) 50%, + rgba(0, 0, 0, 0.03) 60%, + transparent 100% + ); +} diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 00000000..d32d2531 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,63 @@ + + + + + + + Claude Code Context + + + + +
    + +
    Claude Code Context
    +
    +
    +
    +
    +
    + + + diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx new file mode 100644 index 00000000..b0a79fac --- /dev/null +++ b/src/renderer/main.tsx @@ -0,0 +1,12 @@ +import './index.css'; + +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import { App } from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/src/renderer/store/CLAUDE.md b/src/renderer/store/CLAUDE.md new file mode 100644 index 00000000..640f470f --- /dev/null +++ b/src/renderer/store/CLAUDE.md @@ -0,0 +1,52 @@ +# Store (Zustand) + +State management with slices pattern for domain organization. + +## Structure +- `index.ts` - Store creation, combines all slices +- `types.ts` - AppState type definition +- `slices/` - Individual domain slices +- `utils/` - Store utilities (`paneHelpers.ts`, `pathResolution.ts`) + +## Slices (12 total) +| Slice | Purpose | +|-------|---------| +| `projectSlice` | Projects list, selectedProjectId | +| `repositorySlice` | Repository grouping, worktrees | +| `sessionSlice` | Sessions list, pagination, selectedSessionId | +| `sessionDetailSlice` | Session detail, chunks, metrics | +| `subagentSlice` | Subagent data, selectedSubagentId | +| `conversationSlice` | Messages, conversation metadata | +| `tabSlice` | Tabs list, activeTabId, tab ordering | +| `tabUISlice` | Per-tab UI state (expansions, scroll) | +| `paneSlice` | Pane layout, split views | +| `uiSlice` | UI flags (sidebar visible, etc.) | +| `notificationSlice` | Notifications, unreadCount | +| `configSlice` | App config, triggers | + +## Slice Pattern +Each slice follows: +```typescript +data: T[] +selectedId: string | null +loading: boolean +error: string | null +``` + +## Key Pattern: Per-Tab UI Isolation +`tabUISlice` maintains independent UI state per tab using tabId: +- `expandedAIGroupIds`, `expandedDisplayItemIds`, `expandedSubagentTraceIds` +- Ensures expanding a group in tab A doesn't affect tab B + +## Store Initialization +Call `initializeNotificationListeners()` once in App.tsx useEffect: +- Subscribes to file change events +- Auto-refreshes sessions on new files +- Updates session detail on content change +- Uses `refreshSessionInPlace` to prevent flickering + +## Adding a Slice +1. Create `slices/{domain}Slice.ts` +2. Export `create{Domain}Slice` function +3. Add to store composition in `index.ts` +4. Update `AppState` type in `types.ts` diff --git a/src/renderer/store/index.ts b/src/renderer/store/index.ts new file mode 100644 index 00000000..42762c39 --- /dev/null +++ b/src/renderer/store/index.ts @@ -0,0 +1,241 @@ +/** + * Store index - combines all slices and exports the unified store. + */ + +import { create } from 'zustand'; + +import { createConfigSlice } from './slices/configSlice'; +import { createConversationSlice } from './slices/conversationSlice'; +import { createNotificationSlice } from './slices/notificationSlice'; +import { createPaneSlice } from './slices/paneSlice'; +import { createProjectSlice } from './slices/projectSlice'; +import { createRepositorySlice } from './slices/repositorySlice'; +import { createSessionDetailSlice } from './slices/sessionDetailSlice'; +import { createSessionSlice } from './slices/sessionSlice'; +import { createSubagentSlice } from './slices/subagentSlice'; +import { createTabSlice } from './slices/tabSlice'; +import { createTabUISlice } from './slices/tabUISlice'; +import { createUISlice } from './slices/uiSlice'; + +import type { DetectedError } from '../types/data'; +import type { AppState } from './types'; + +// ============================================================================= +// Store Creation +// ============================================================================= + +export const useStore = create()((...args) => ({ + ...createProjectSlice(...args), + ...createRepositorySlice(...args), + ...createSessionSlice(...args), + ...createSessionDetailSlice(...args), + ...createSubagentSlice(...args), + ...createConversationSlice(...args), + ...createTabSlice(...args), + ...createTabUISlice(...args), + ...createPaneSlice(...args), + ...createUISlice(...args), + ...createNotificationSlice(...args), + ...createConfigSlice(...args), +})); + +// ============================================================================= +// Re-exports +// ============================================================================= + +// ============================================================================= +// Store Initialization - Subscribe to IPC Events +// ============================================================================= + +/** + * Initialize notification event listeners and fetch initial notification count. + * Call this once when the app starts (e.g., in App.tsx useEffect). + */ +export function initializeNotificationListeners(): () => void { + const cleanupFns: (() => void)[] = []; + const pendingSessionRefreshTimers = new Map>(); + const pendingProjectRefreshTimers = new Map>(); + const SESSION_REFRESH_DEBOUNCE_MS = 150; + const PROJECT_REFRESH_DEBOUNCE_MS = 300; + + const scheduleSessionRefresh = (projectId: string, sessionId: string): void => { + const key = `${projectId}/${sessionId}`; + const existingTimer = pendingSessionRefreshTimers.get(key); + if (existingTimer) { + clearTimeout(existingTimer); + } + const timer = setTimeout(() => { + pendingSessionRefreshTimers.delete(key); + const state = useStore.getState(); + void state.refreshSessionInPlace(projectId, sessionId); + }, SESSION_REFRESH_DEBOUNCE_MS); + pendingSessionRefreshTimers.set(key, timer); + }; + + const scheduleProjectRefresh = (projectId: string): void => { + const existingTimer = pendingProjectRefreshTimers.get(projectId); + if (existingTimer) { + clearTimeout(existingTimer); + } + const timer = setTimeout(() => { + pendingProjectRefreshTimers.delete(projectId); + const state = useStore.getState(); + void state.refreshSessionsInPlace(projectId); + }, PROJECT_REFRESH_DEBOUNCE_MS); + pendingProjectRefreshTimers.set(projectId, timer); + }; + + // Listen for new notifications from main process + if (window.electronAPI.notifications?.onNew) { + const cleanup = window.electronAPI.notifications.onNew((_event: unknown, error: unknown) => { + // Cast the error to DetectedError type + const notification = error as DetectedError; + if (notification?.id) { + // Keep list in sync immediately; unread count is synced via notification:updated/fetch. + useStore.setState((state) => { + if (state.notifications.some((n) => n.id === notification.id)) { + return {}; + } + return { notifications: [notification, ...state.notifications].slice(0, 200) }; + }); + } + }); + if (typeof cleanup === 'function') { + cleanupFns.push(cleanup); + } + } + + // Listen for notification updates from main process + if (window.electronAPI.notifications?.onUpdated) { + const cleanup = window.electronAPI.notifications.onUpdated( + (_event: unknown, payload: { total: number; unreadCount: number }) => { + const unreadCount = + typeof payload.unreadCount === 'number' && Number.isFinite(payload.unreadCount) + ? Math.max(0, Math.floor(payload.unreadCount)) + : 0; + useStore.setState({ unreadCount }); + } + ); + if (typeof cleanup === 'function') { + cleanupFns.push(cleanup); + } + } + + // Navigate to error when user clicks a native OS notification + if (window.electronAPI.notifications?.onClicked) { + const cleanup = window.electronAPI.notifications.onClicked((_event: unknown, data: unknown) => { + const error = data as DetectedError; + if (error?.id && error?.sessionId && error?.projectId) { + useStore.getState().navigateToError(error); + } + }); + if (typeof cleanup === 'function') { + cleanupFns.push(cleanup); + } + } + + // Fetch after listeners are attached so startup events do not get overwritten by a stale response. + void useStore.getState().fetchNotifications(); + + /** + * Check if a session is visible in any pane (not just the focused pane's active tab). + * This ensures file change and task-list listeners refresh sessions shown in any split pane. + */ + const isSessionVisibleInAnyPane = (sessionId: string): boolean => { + const { paneLayout } = useStore.getState(); + return paneLayout.panes.some( + (pane) => + pane.activeTabId != null && + pane.tabs.some( + (tab) => + tab.id === pane.activeTabId && tab.type === 'session' && tab.sessionId === sessionId + ) + ); + }; + + // Listen for task-list file changes to refresh currently viewed session metadata + if (window.electronAPI.onTodoChange) { + const cleanup = window.electronAPI.onTodoChange((event) => { + if (!event.sessionId || event.type === 'unlink') { + return; + } + + const state = useStore.getState(); + const isViewingSession = + state.selectedSessionId === event.sessionId || isSessionVisibleInAnyPane(event.sessionId); + + if (isViewingSession) { + // Find the project ID from any pane's tab that shows this session + const allTabs = state.getAllPaneTabs(); + const sessionTab = allTabs.find( + (t) => t.type === 'session' && t.sessionId === event.sessionId + ); + if (sessionTab?.projectId) { + scheduleSessionRefresh(sessionTab.projectId, event.sessionId); + } + } + + // Refresh project sessions list if applicable + const activeTab = state.getActiveTab(); + const activeProjectId = + activeTab?.type === 'session' && typeof activeTab.projectId === 'string' + ? activeTab.projectId + : null; + if (activeProjectId && activeProjectId === state.selectedProjectId) { + scheduleProjectRefresh(activeProjectId); + } + }); + if (typeof cleanup === 'function') { + cleanupFns.push(cleanup); + } + } + + // Listen for file changes to auto-refresh current session and detect new sessions + if (window.electronAPI.onFileChange) { + const cleanup = window.electronAPI.onFileChange((event) => { + // Skip unlink events + if (event.type === 'unlink') { + return; + } + + const state = useStore.getState(); + + // Handle new session added to a project (main session files only) + if (event.type === 'add' && !event.isSubagent && event.projectId) { + // Refresh sessions list if viewing this project (without loading state) + if (state.selectedProjectId === event.projectId) { + scheduleProjectRefresh(event.projectId); + } + return; + } + + // Handle session or subagent content change + if (event.type === 'change' && event.projectId && event.sessionId) { + // Check if the changed session is visible in ANY pane (not just focused) + const isViewingSession = + state.selectedSessionId === event.sessionId || isSessionVisibleInAnyPane(event.sessionId); + + if (isViewingSession) { + // Use refreshSessionInPlace to avoid flickering and preserve UI state + scheduleSessionRefresh(event.projectId, event.sessionId); + } + } + }); + if (typeof cleanup === 'function') { + cleanupFns.push(cleanup); + } + } + + // Return cleanup function + return () => { + for (const timer of pendingSessionRefreshTimers.values()) { + clearTimeout(timer); + } + pendingSessionRefreshTimers.clear(); + for (const timer of pendingProjectRefreshTimers.values()) { + clearTimeout(timer); + } + pendingProjectRefreshTimers.clear(); + cleanupFns.forEach((fn) => fn()); + }; +} diff --git a/src/renderer/store/slices/configSlice.ts b/src/renderer/store/slices/configSlice.ts new file mode 100644 index 00000000..17e1a91d --- /dev/null +++ b/src/renderer/store/slices/configSlice.ts @@ -0,0 +1,89 @@ +/** + * Config slice - manages app configuration state and actions. + */ + +import { createLogger } from '@shared/utils/logger'; + +import type { AppState } from '../types'; +import type { AppConfig } from '@renderer/types/data'; +import type { StateCreator } from 'zustand'; + +const logger = createLogger('Store:config'); + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface ConfigSlice { + // State + appConfig: AppConfig | null; + configLoading: boolean; + configError: string | null; + + // Actions + fetchConfig: () => Promise; + updateConfig: (section: string, data: Record) => Promise; + openSettingsTab: () => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createConfigSlice: StateCreator = (set, get) => ({ + // Initial state + appConfig: null, + configLoading: false, + configError: null, + + // Fetch app configuration from main process + fetchConfig: async () => { + set({ configLoading: true, configError: null }); + try { + const config = await window.electronAPI.config.get(); + set({ + appConfig: config, + configLoading: false, + }); + } catch (error) { + set({ + configError: error instanceof Error ? error.message : 'Failed to fetch config', + configLoading: false, + }); + } + }, + + // Update a section of the app configuration + updateConfig: async (section: string, data: Record) => { + try { + await window.electronAPI.config.update(section, data); + // Refresh config after update + const config = await window.electronAPI.config.get(); + set({ appConfig: config }); + } catch (error) { + logger.error('Failed to update config:', error); + set({ + configError: error instanceof Error ? error.message : 'Failed to update config', + }); + } + }, + + // Open or focus the settings tab (per-pane singleton) + openSettingsTab: () => { + const state = get(); + + // Check if settings tab exists in focused pane + const focusedPane = state.paneLayout.panes.find((p) => p.id === state.paneLayout.focusedPaneId); + const settingsTab = focusedPane?.tabs.find((t) => t.type === 'settings'); + if (settingsTab) { + state.setActiveTab(settingsTab.id); + return; + } + + // Create new settings tab via openTab (which adds to focused pane) + state.openTab({ + type: 'settings', + label: 'Settings', + }); + }, +}); diff --git a/src/renderer/store/slices/conversationSlice.ts b/src/renderer/store/slices/conversationSlice.ts new file mode 100644 index 00000000..10374aae --- /dev/null +++ b/src/renderer/store/slices/conversationSlice.ts @@ -0,0 +1,476 @@ +/** + * Conversation slice - manages expansion states, chart mode, search, and detail popover. + */ + +import { findLastOutput } from '@renderer/utils/aiGroupEnhancer'; +import { findMarkdownSearchMatches } from '@shared/utils/markdownTextSearch'; + +import type { AppState, SearchMatch } from '../types'; +import type { AIGroupExpansionLevel } from '@renderer/types/groups'; +import type { SessionConversation } from '@renderer/types/groups'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Types +// ============================================================================= + +type DetailItemType = 'thinking' | 'text' | 'linked-tool' | 'subagent'; + +const isSearchDebugEnabled = (): boolean => { + if (typeof window === 'undefined') return false; + try { + return ( + window.localStorage.getItem('search-debug') === '1' || + (window as { __searchDebug?: boolean }).__searchDebug === true + ); + } catch { + return false; + } +}; + +export interface ActiveDetailItem { + aiGroupId: string; + itemId: string; + type: DetailItemType; +} + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface ConversationSlice { + // Expansion states + aiGroupExpansionLevels: Map; + expandedStepIds: Set; + /** Display item expansion state per AI group - persists across refreshes */ + expandedDisplayItemIds: Map>; + /** AI group expanded/collapsed state - persists across refreshes */ + expandedAIGroupIds: Set; + + // Detail popover state + activeDetailItem: ActiveDetailItem | null; + + // Search state + searchQuery: string; + searchVisible: boolean; + searchResultCount: number; + currentSearchIndex: number; + searchMatches: SearchMatch[]; + + // Auto-expand state for search results + /** AI group IDs that should be expanded to show search results */ + searchExpandedAIGroupIds: Set; + /** Subagent IDs within AI groups that should show their execution trace */ + searchExpandedSubagentIds: Set; + /** Current search result's display item ID for precise expansion (e.g., "thinking-0") */ + searchCurrentDisplayItemId: string | null; + /** Current search result's item ID within subagent trace (e.g., "subagent-thinking-0") */ + searchCurrentSubagentItemId: string | null; + + // Actions + setAIGroupExpansion: (aiGroupId: string, level: AIGroupExpansionLevel) => void; + toggleStepExpansion: (stepId: string) => void; + /** Toggle expansion of a display item within an AI group */ + toggleDisplayItemExpansion: (aiGroupId: string, itemId: string) => void; + /** Get expanded display item IDs for an AI group */ + getExpandedDisplayItemIds: (aiGroupId: string) => Set; + /** Toggle AI group expanded/collapsed state */ + toggleAIGroupExpansion: (aiGroupId: string) => void; + + // Detail popover actions + showDetailPopover: ( + aiGroupId: string, + itemId: string, + type: 'thinking' | 'text' | 'linked-tool' | 'subagent' + ) => void; + hideDetailPopover: () => void; + + // Search actions + setSearchQuery: (query: string, conversationOverride?: SessionConversation | null) => void; + /** Canonicalize search matches from currently rendered mark elements (DOM order) */ + syncSearchMatchesWithRendered: ( + renderedMatches: { itemId: string; matchIndexInItem: number }[] + ) => void; + /** Select a specific search match by item ID and in-item match index */ + selectSearchMatch: (itemId: string, matchIndexInItem: number) => boolean; + showSearch: () => void; + hideSearch: () => void; + nextSearchResult: () => void; + previousSearchResult: () => void; + /** Expand AI groups and subagents needed to show the current search result */ + expandForCurrentSearchResult: () => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createConversationSlice: StateCreator = ( + set, + get +) => ({ + // Initial state + aiGroupExpansionLevels: new Map(), + expandedStepIds: new Set(), + expandedDisplayItemIds: new Map(), + expandedAIGroupIds: new Set(), + + ganttChartMode: 'timeline', + + activeDetailItem: null, + + // Search state (initial values) + searchQuery: '', + searchVisible: false, + searchResultCount: 0, + currentSearchIndex: -1, + searchMatches: [], + + // Auto-expand state for search results (initial values) + searchExpandedAIGroupIds: new Set(), + searchExpandedSubagentIds: new Set(), + searchCurrentDisplayItemId: null, + searchCurrentSubagentItemId: null, + + // Set expansion level for a specific AI Group + setAIGroupExpansion: (aiGroupId: string, level: AIGroupExpansionLevel) => { + const state = get(); + const newLevels = new Map(state.aiGroupExpansionLevels); + newLevels.set(aiGroupId, level); + set({ aiGroupExpansionLevels: newLevels }); + }, + + // Toggle expansion state for a semantic step + toggleStepExpansion: (stepId: string) => { + const state = get(); + const newExpandedStepIds = new Set(state.expandedStepIds); + if (newExpandedStepIds.has(stepId)) { + newExpandedStepIds.delete(stepId); + } else { + newExpandedStepIds.add(stepId); + } + set({ expandedStepIds: newExpandedStepIds }); + }, + + // Toggle expansion of a display item within an AI group + toggleDisplayItemExpansion: (aiGroupId: string, itemId: string) => { + const state = get(); + const newMap = new Map(state.expandedDisplayItemIds); + const currentSet = newMap.get(aiGroupId) ?? new Set(); + const newSet = new Set(currentSet); + + if (newSet.has(itemId)) { + newSet.delete(itemId); + } else { + newSet.add(itemId); + } + + newMap.set(aiGroupId, newSet); + set({ expandedDisplayItemIds: newMap }); + }, + + // Get expanded display item IDs for an AI group + getExpandedDisplayItemIds: (aiGroupId: string) => { + const state = get(); + return state.expandedDisplayItemIds.get(aiGroupId) ?? new Set(); + }, + + // Toggle AI group expanded/collapsed state + toggleAIGroupExpansion: (aiGroupId: string) => { + const state = get(); + const newSet = new Set(state.expandedAIGroupIds); + if (newSet.has(aiGroupId)) { + newSet.delete(aiGroupId); + } else { + newSet.add(aiGroupId); + } + set({ expandedAIGroupIds: newSet }); + }, + + // Show detail popover + showDetailPopover: ( + aiGroupId: string, + itemId: string, + type: 'thinking' | 'text' | 'linked-tool' | 'subagent' + ) => { + set({ + activeDetailItem: { + aiGroupId, + itemId, + type, + }, + }); + }, + + // Hide detail popover + hideDetailPopover: () => { + set({ activeDetailItem: null }); + }, + + // Search actions + + setSearchQuery: (query: string, conversationOverride?: SessionConversation | null) => { + const conversation = conversationOverride ?? get().conversation; + + if (!query.trim() || !conversation) { + if (isSearchDebugEnabled()) { + console.info('[search] clear', { query }); + } + set({ + searchQuery: query, + searchResultCount: 0, + currentSearchIndex: -1, + searchMatches: [], + searchCurrentDisplayItemId: null, + searchCurrentSubagentItemId: null, + }); + return; + } + + // Build search matches by scanning conversation + // ONLY searches: user message text and AI lastOutput text (not tool results) + // Uses remark-based markdown parsing to extract visible text segments, + // ensuring match counts align with what ReactMarkdown renders. + const matches: SearchMatch[] = []; + const lowerQuery = query.toLowerCase(); + let globalIndex = 0; + + // Helper to find markdown-aware matches and add to matches array + const addMarkdownMatches = ( + text: string, + itemId: string, + itemType: 'user' | 'ai', + displayItemId?: string + ): void => { + const mdMatches = findMarkdownSearchMatches(text, lowerQuery); + for (const mdMatch of mdMatches) { + matches.push({ + itemId, + itemType, + matchIndexInItem: mdMatch.matchIndexInItem, + globalIndex, + displayItemId, + }); + globalIndex++; + } + }; + + for (const item of conversation.items) { + if (item.type === 'user') { + // Search user message text + const text = item.group.content.rawText ?? item.group.content.text ?? ''; + addMarkdownMatches(text, item.group.id, 'user'); + } else if (item.type === 'ai') { + // For AI items: ONLY search lastOutput text (not tool results, thinking, or subagents) + const aiGroup = item.group; + const itemId = aiGroup.id; + const lastOutput = findLastOutput(aiGroup.steps, aiGroup.isOngoing ?? false); + + if (lastOutput?.type === 'text' && lastOutput.text) { + // Last output text - displayItemId indicates this is lastOutput content + addMarkdownMatches(lastOutput.text, itemId, 'ai', 'lastOutput'); + } + // Skip tool_result type - only searching text output + } + // Skip system items entirely + } + + if (isSearchDebugEnabled()) { + const sample = matches.slice(0, 10).map((match) => ({ + itemId: match.itemId, + itemType: match.itemType, + matchIndexInItem: match.matchIndexInItem, + globalIndex: match.globalIndex, + })); + const counts = matches.reduce>((acc, match) => { + acc[`${match.itemType}:${match.itemId}`] = + (acc[`${match.itemType}:${match.itemId}`] ?? 0) + 1; + return acc; + }, {}); + console.info('[search] query', query, 'matches', matches.length); + console.info('[search] counts', counts); + console.info('[search] sample', sample); + } + + set({ + searchQuery: query, + searchResultCount: matches.length, + currentSearchIndex: matches.length > 0 ? 0 : -1, + searchMatches: matches, + }); + }, + + syncSearchMatchesWithRendered: (renderedMatches) => { + const state = get(); + if (!state.searchQuery.trim()) return; + + const dedupedRendered: { itemId: string; matchIndexInItem: number }[] = []; + const seen = new Set(); + for (const rendered of renderedMatches) { + const key = `${rendered.itemId}:${rendered.matchIndexInItem}`; + if (seen.has(key)) continue; + seen.add(key); + dedupedRendered.push(rendered); + } + + const oldMatches = state.searchMatches; + const sameLength = oldMatches.length === dedupedRendered.length; + const sameContent = + sameLength && + oldMatches.every( + (match, index) => + match.itemId === dedupedRendered[index]?.itemId && + match.matchIndexInItem === dedupedRendered[index]?.matchIndexInItem + ); + if (sameContent) return; + + const oldMatchMap = new Map(); + for (const match of oldMatches) { + oldMatchMap.set(`${match.itemId}:${match.matchIndexInItem}`, match); + } + + const nextMatches: SearchMatch[] = dedupedRendered.map((rendered, index) => { + const key = `${rendered.itemId}:${rendered.matchIndexInItem}`; + const previous = oldMatchMap.get(key); + const inferredItemType = rendered.itemId.startsWith('user-') ? 'user' : 'ai'; + return { + itemId: rendered.itemId, + itemType: previous?.itemType ?? inferredItemType, + matchIndexInItem: rendered.matchIndexInItem, + globalIndex: index, + displayItemId: previous?.displayItemId, + }; + }); + + const oldCurrentMatch = + state.currentSearchIndex >= 0 ? oldMatches[state.currentSearchIndex] : undefined; + let newCurrentIndex = -1; + if (oldCurrentMatch) { + newCurrentIndex = nextMatches.findIndex( + (match) => + match.itemId === oldCurrentMatch.itemId && + match.matchIndexInItem === oldCurrentMatch.matchIndexInItem + ); + } + + if (newCurrentIndex < 0) { + if (nextMatches.length === 0) { + newCurrentIndex = -1; + } else if (state.currentSearchIndex < 0) { + newCurrentIndex = 0; + } else { + newCurrentIndex = Math.min(state.currentSearchIndex, nextMatches.length - 1); + } + } + + if (isSearchDebugEnabled()) { + console.info('[search] sync-rendered', { + parsedCount: oldMatches.length, + renderedCount: nextMatches.length, + currentBefore: state.currentSearchIndex, + currentAfter: newCurrentIndex, + }); + } + + set({ + searchMatches: nextMatches, + searchResultCount: nextMatches.length, + currentSearchIndex: newCurrentIndex, + }); + }, + + showSearch: () => { + set({ searchVisible: true }); + }, + + selectSearchMatch: (itemId: string, matchIndexInItem: number) => { + const state = get(); + const targetIndex = state.searchMatches.findIndex( + (match) => match.itemId === itemId && match.matchIndexInItem === matchIndexInItem + ); + + if (targetIndex < 0) { + return false; + } + + set({ currentSearchIndex: targetIndex }); + get().expandForCurrentSearchResult(); + return true; + }, + + hideSearch: () => { + set({ + searchVisible: false, + searchQuery: '', + searchResultCount: 0, + currentSearchIndex: -1, + searchMatches: [], + searchExpandedAIGroupIds: new Set(), + searchExpandedSubagentIds: new Set(), + searchCurrentDisplayItemId: null, + searchCurrentSubagentItemId: null, + }); + }, + + nextSearchResult: () => { + const state = get(); + if (state.searchResultCount > 0) { + const nextIndex = (state.currentSearchIndex + 1) % state.searchResultCount; + set({ currentSearchIndex: nextIndex }); + // Auto-expand any collapsed sections containing the result + get().expandForCurrentSearchResult(); + if (isSearchDebugEnabled()) { + const match = get().searchMatches[nextIndex]; + console.info('[search] next', { + index: nextIndex, + itemId: match?.itemId, + matchIndexInItem: match?.matchIndexInItem, + }); + } + } + }, + + previousSearchResult: () => { + const state = get(); + if (state.searchResultCount > 0) { + const prevIndex = state.currentSearchIndex - 1; + const newIndex = prevIndex < 0 ? state.searchResultCount - 1 : prevIndex; + set({ currentSearchIndex: newIndex }); + // Auto-expand any collapsed sections containing the result + get().expandForCurrentSearchResult(); + if (isSearchDebugEnabled()) { + const match = get().searchMatches[newIndex]; + console.info('[search] prev', { + index: newIndex, + itemId: match?.itemId, + matchIndexInItem: match?.matchIndexInItem, + }); + } + } + }, + + expandForCurrentSearchResult: () => { + const state = get(); + const { currentSearchIndex, searchMatches } = state; + + if (currentSearchIndex < 0 || searchMatches.length === 0) return; + + const currentMatch = searchMatches[currentSearchIndex]; + if (!currentMatch) return; + + // For AI group matches, track the display item ID for highlighting + // Since we only search lastOutput text (always visible), no expansion needed + if (currentMatch.itemType === 'ai') { + set({ + searchCurrentDisplayItemId: currentMatch.displayItemId ?? null, + searchCurrentSubagentItemId: null, + }); + } else { + // For user matches, clear display item IDs + set({ + searchCurrentDisplayItemId: null, + searchCurrentSubagentItemId: null, + }); + } + }, +}); diff --git a/src/renderer/store/slices/notificationSlice.ts b/src/renderer/store/slices/notificationSlice.ts new file mode 100644 index 00000000..58d095b8 --- /dev/null +++ b/src/renderer/store/slices/notificationSlice.ts @@ -0,0 +1,223 @@ +/** + * Notification slice - manages notifications state and actions. + */ + +import { createErrorNavigationRequest, findTabBySessionAndProject } from '@renderer/types/tabs'; +import { createLogger } from '@shared/utils/logger'; + +import { getAllTabs } from '../utils/paneHelpers'; + +import type { AppState } from '../types'; +import type { DetectedError } from '@renderer/types/data'; +import type { StateCreator } from 'zustand'; + +const logger = createLogger('Store:notification'); +const NOTIFICATIONS_FETCH_LIMIT = 200; + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface NotificationSlice { + // State + notifications: DetectedError[]; + unreadCount: number; + notificationsLoading: boolean; + notificationsError: string | null; + + // Actions + fetchNotifications: () => Promise; + markNotificationRead: (id: string) => Promise; + markAllNotificationsRead: () => Promise; + deleteNotification: (id: string) => Promise; + clearNotifications: () => Promise; + navigateToError: (error: DetectedError) => void; + openNotificationsTab: () => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createNotificationSlice: StateCreator = ( + set, + get +) => ({ + // Initial state + notifications: [], + unreadCount: 0, + notificationsLoading: false, + notificationsError: null, + + // Fetch all notifications from main process + fetchNotifications: async () => { + set({ notificationsLoading: true, notificationsError: null }); + try { + // Fetch the full stored history (manager currently caps storage at 100). + const result = await window.electronAPI.notifications.get({ + limit: NOTIFICATIONS_FETCH_LIMIT, + offset: 0, + }); + const notifications = result.notifications || []; + const unreadCount = + typeof result.unreadCount === 'number' && Number.isFinite(result.unreadCount) + ? Math.max(0, Math.floor(result.unreadCount)) + : notifications.filter((n: { isRead: boolean }) => !n.isRead).length; + set({ + notifications, + unreadCount, + notificationsLoading: false, + }); + } catch (error) { + set({ + notificationsError: + error instanceof Error ? error.message : 'Failed to fetch notifications', + notificationsLoading: false, + }); + } + }, + + // Mark a single notification as read + markNotificationRead: async (id: string) => { + try { + const success = await window.electronAPI.notifications.markRead(id); + if (!success) { + await get().fetchNotifications(); + return; + } + // Optimistically update local state + set((state) => { + const notifications = state.notifications.map((n) => + n.id === id ? { ...n, isRead: true } : n + ); + const unreadCount = notifications.filter((n) => !n.isRead).length; + return { notifications, unreadCount }; + }); + } catch (error) { + logger.error('Failed to mark notification as read:', error); + } + }, + + // Mark all notifications as read + markAllNotificationsRead: async () => { + try { + const success = await window.electronAPI.notifications.markAllRead(); + if (!success) { + await get().fetchNotifications(); + return; + } + // Optimistically update local state + set((state) => ({ + notifications: state.notifications.map((n) => ({ ...n, isRead: true })), + unreadCount: 0, + })); + } catch (error) { + logger.error('Failed to mark all notifications as read:', error); + } + }, + + // Delete a single notification + deleteNotification: async (id: string) => { + try { + const success = await window.electronAPI.notifications.delete(id); + if (!success) { + await get().fetchNotifications(); + return; + } + // Optimistically update local state + set((state) => { + const notifications = state.notifications.filter((n) => n.id !== id); + const unreadCount = notifications.filter((n) => !n.isRead).length; + return { notifications, unreadCount }; + }); + } catch (error) { + logger.error('Failed to delete notification:', error); + } + }, + + // Clear all notifications + clearNotifications: async () => { + try { + const success = await window.electronAPI.notifications.clear(); + if (!success) { + await get().fetchNotifications(); + return; + } + set({ + notifications: [], + unreadCount: 0, + }); + } catch (error) { + logger.error('Failed to clear notifications:', error); + } + }, + + // Navigate to error location in session (deep linking) + navigateToError: (error: DetectedError) => { + const state = get(); + + // Mark the notification as read + void state.markNotificationRead(error.id); + + // Create the navigation request with a fresh nonce + const navRequest = createErrorNavigationRequest( + { + errorId: error.id, + errorTimestamp: error.timestamp, + toolUseId: error.toolUseId, + subagentId: error.subagentId, + lineNumber: error.lineNumber, + }, + 'notification', + error.triggerColor + ); + + // Check if session tab is already open across all panes + const allTabs = getAllTabs(state.paneLayout); + const existingTab = findTabBySessionAndProject(allTabs, error.sessionId, error.projectId); + + if (existingTab) { + // Focus existing tab via setActiveTab for proper sidebar sync + state.setActiveTab(existingTab.id); + // Enqueue navigation request with fresh nonce + state.enqueueTabNavigation(existingTab.id, navRequest); + } else { + // Open new session tab via openTab (properly adds to focused pane) + state.openTab({ + type: 'session', + label: 'Loading...', + projectId: error.projectId, + sessionId: error.sessionId, + }); + + // Enqueue navigation on the newly created tab, then trigger sidebar + // sync + session data fetch via setActiveTab + const newTabId = get().activeTabId; + if (newTabId) { + state.enqueueTabNavigation(newTabId, navRequest); + get().setActiveTab(newTabId); + } + } + }, + + // Open or focus the notifications tab (per-pane singleton) + openNotificationsTab: () => { + const state = get(); + + // Check if notifications tab exists in focused pane + const focusedPane = state.paneLayout.panes.find((p) => p.id === state.paneLayout.focusedPaneId); + const notificationsTab = focusedPane?.tabs.find((t) => t.type === 'notifications'); + if (notificationsTab) { + state.setActiveTab(notificationsTab.id); + // Re-sync in case updates happened while tab was inactive. + void state.fetchNotifications(); + return; + } + + // Create new notifications tab via openTab (which adds to focused pane) + state.openTab({ + type: 'notifications', + label: 'Notifications', + }); + }, +}); diff --git a/src/renderer/store/slices/paneSlice.ts b/src/renderer/store/slices/paneSlice.ts new file mode 100644 index 00000000..85d6628e --- /dev/null +++ b/src/renderer/store/slices/paneSlice.ts @@ -0,0 +1,356 @@ +/** + * Pane slice - manages multi-pane split layout state and actions. + * Each pane has its own tab bar, active tab, and selected tabs. + */ + +import { MAX_PANES } from '@renderer/types/panes'; + +import { + createEmptyPane, + findPane, + findPaneByTabId, + getAllTabs, + insertPane, + removePane, + syncFocusedPaneState, + updatePane, +} from '../utils/paneHelpers'; + +import type { AppState } from '../types'; +import type { PaneLayout } from '@renderer/types/panes'; +import type { Tab } from '@renderer/types/tabs'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface PaneSlice { + // State + paneLayout: PaneLayout; + + // Pane lifecycle + focusPane: (paneId: string) => void; + splitPane: (sourcePaneId: string, tabId: string, direction: 'left' | 'right') => void; + closePane: (paneId: string) => void; + + // Tab movement + moveTabToPane: ( + tabId: string, + sourcePaneId: string, + targetPaneId: string, + insertIndex?: number + ) => void; + moveTabToNewPane: ( + tabId: string, + sourcePaneId: string, + adjacentPaneId: string, + direction: 'left' | 'right' + ) => void; + reorderTabInPane: (paneId: string, fromIndex: number, toIndex: number) => void; + + // Resize + resizePanes: (paneId: string, newWidthFraction: number) => void; + + // Queries + getPaneForTab: (tabId: string) => string | null; + getAllPaneTabs: () => Tab[]; +} + +// ============================================================================= +// Helpers +// ============================================================================= + +/** + * Sync root-level openTabs/activeTabId/selectedTabIds from the focused pane. + * This maintains backward compatibility for consumers that read root-level state. + */ +function syncRootState(layout: PaneLayout): Record { + const synced = syncFocusedPaneState(layout); + return { + paneLayout: layout, + openTabs: synced.openTabs, + activeTabId: synced.activeTabId, + selectedTabIds: synced.selectedTabIds, + }; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createPaneSlice: StateCreator = (set, get) => ({ + // Initial state: single pane (populated by tabSlice init or first openTab) + paneLayout: { + panes: [ + { + id: 'pane-default', + tabs: [], + activeTabId: null, + selectedTabIds: [], + widthFraction: 1, + }, + ], + focusedPaneId: 'pane-default', + }, + + focusPane: (paneId: string) => { + const state = get(); + const { paneLayout } = state; + if (paneLayout.focusedPaneId === paneId) return; + + const pane = findPane(paneLayout, paneId); + if (!pane) return; + + const newLayout: PaneLayout = { ...paneLayout, focusedPaneId: paneId }; + set(syncRootState(newLayout)); + + // Trigger sidebar sync for the focused pane's active tab + if (pane.activeTabId) { + get().setActiveTab(pane.activeTabId); + } + }, + + splitPane: (sourcePaneId: string, tabId: string, direction: 'left' | 'right') => { + const state = get(); + const { paneLayout } = state; + + if (paneLayout.panes.length >= MAX_PANES) return; + + const sourcePane = findPane(paneLayout, sourcePaneId); + if (!sourcePane) return; + + const tab = sourcePane.tabs.find((t) => t.id === tabId); + if (!tab) return; + + // Remove tab from source pane + const newSourceTabs = sourcePane.tabs.filter((t) => t.id !== tabId); + let newSourceActiveTabId = sourcePane.activeTabId; + if (sourcePane.activeTabId === tabId) { + // Focus adjacent tab in source + const oldIndex = sourcePane.tabs.findIndex((t) => t.id === tabId); + newSourceActiveTabId = newSourceTabs[oldIndex]?.id ?? newSourceTabs[oldIndex - 1]?.id ?? null; + } + + const updatedSource = { + ...sourcePane, + tabs: newSourceTabs, + activeTabId: newSourceActiveTabId, + selectedTabIds: sourcePane.selectedTabIds.filter((id) => id !== tabId), + }; + + // Create new pane with the tab + const newPaneId = crypto.randomUUID(); + const newPane = { + ...createEmptyPane(newPaneId), + tabs: [tab], + activeTabId: tab.id, + }; + + // Update layout + let newLayout = updatePane(paneLayout, updatedSource); + + // If source pane is now empty, remove it + if (newSourceTabs.length === 0 && paneLayout.panes.length > 1) { + newLayout = removePane(newLayout, sourcePaneId); + } + + newLayout = insertPane( + newLayout, + updatedSource.id !== sourcePaneId ? paneLayout.panes[0].id : sourcePaneId, + newPane, + direction + ); + newLayout = { ...newLayout, focusedPaneId: newPaneId }; + + set(syncRootState(newLayout)); + + // Sync sidebar for the new pane's active tab + if (tab.type === 'session') { + get().setActiveTab(tab.id); + } + }, + + closePane: (paneId: string) => { + const state = get(); + const { paneLayout } = state; + + if (paneLayout.panes.length <= 1) return; // Can't close the last pane + + const pane = findPane(paneLayout, paneId); + if (!pane) return; + + // Cleanup tab UI state for all tabs in the pane + for (const tab of pane.tabs) { + state.cleanupTabUIState(tab.id); + } + + const newLayout = removePane(paneLayout, paneId); + set(syncRootState(newLayout)); + + // Sync sidebar for the newly focused pane + const focusedPane = findPane(newLayout, newLayout.focusedPaneId); + if (focusedPane?.activeTabId) { + get().setActiveTab(focusedPane.activeTabId); + } + }, + + moveTabToPane: ( + tabId: string, + sourcePaneId: string, + targetPaneId: string, + insertIndex?: number + ) => { + const state = get(); + const { paneLayout } = state; + + if (sourcePaneId === targetPaneId) return; + + const sourcePane = findPane(paneLayout, sourcePaneId); + const targetPane = findPane(paneLayout, targetPaneId); + if (!sourcePane || !targetPane) return; + + const tab = sourcePane.tabs.find((t) => t.id === tabId); + if (!tab) return; + + // Remove from source + const newSourceTabs = sourcePane.tabs.filter((t) => t.id !== tabId); + let newSourceActiveTabId = sourcePane.activeTabId; + if (sourcePane.activeTabId === tabId) { + const oldIndex = sourcePane.tabs.findIndex((t) => t.id === tabId); + newSourceActiveTabId = newSourceTabs[oldIndex]?.id ?? newSourceTabs[oldIndex - 1]?.id ?? null; + } + + // Add to target at insertion index + const newTargetTabs = [...targetPane.tabs]; + if (insertIndex !== undefined && insertIndex >= 0 && insertIndex <= newTargetTabs.length) { + newTargetTabs.splice(insertIndex, 0, tab); + } else { + newTargetTabs.push(tab); + } + + let newLayout = updatePane(paneLayout, { + ...sourcePane, + tabs: newSourceTabs, + activeTabId: newSourceActiveTabId, + selectedTabIds: sourcePane.selectedTabIds.filter((id) => id !== tabId), + }); + newLayout = updatePane(newLayout, { + ...targetPane, + tabs: newTargetTabs, + activeTabId: tab.id, + }); + + // Auto-close source pane if it's empty and not the sole pane + if (newSourceTabs.length === 0 && newLayout.panes.length > 1) { + newLayout = removePane(newLayout, sourcePaneId); + } + + // Focus the target pane + newLayout = { ...newLayout, focusedPaneId: targetPaneId }; + + set(syncRootState(newLayout)); + }, + + moveTabToNewPane: ( + tabId: string, + sourcePaneId: string, + adjacentPaneId: string, + direction: 'left' | 'right' + ) => { + const state = get(); + const { paneLayout } = state; + + if (paneLayout.panes.length >= MAX_PANES) return; + + const sourcePane = findPane(paneLayout, sourcePaneId); + if (!sourcePane) return; + + const tab = sourcePane.tabs.find((t) => t.id === tabId); + if (!tab) return; + + // Remove from source + const newSourceTabs = sourcePane.tabs.filter((t) => t.id !== tabId); + let newSourceActiveTabId = sourcePane.activeTabId; + if (sourcePane.activeTabId === tabId) { + const oldIndex = sourcePane.tabs.findIndex((t) => t.id === tabId); + newSourceActiveTabId = newSourceTabs[oldIndex]?.id ?? newSourceTabs[oldIndex - 1]?.id ?? null; + } + + const newPaneId = crypto.randomUUID(); + const newPane = { + ...createEmptyPane(newPaneId), + tabs: [tab], + activeTabId: tab.id, + }; + + let newLayout = updatePane(paneLayout, { + ...sourcePane, + tabs: newSourceTabs, + activeTabId: newSourceActiveTabId, + selectedTabIds: sourcePane.selectedTabIds.filter((id) => id !== tabId), + }); + + // Auto-close source pane if it's empty and not the sole pane + if (newSourceTabs.length === 0 && newLayout.panes.length > 1) { + newLayout = removePane(newLayout, sourcePaneId); + } + + newLayout = insertPane(newLayout, adjacentPaneId, newPane, direction); + newLayout = { ...newLayout, focusedPaneId: newPaneId }; + + set(syncRootState(newLayout)); + }, + + reorderTabInPane: (paneId: string, fromIndex: number, toIndex: number) => { + const { paneLayout } = get(); + const pane = findPane(paneLayout, paneId); + if (!pane) return; + + if (fromIndex < 0 || fromIndex >= pane.tabs.length) return; + if (toIndex < 0 || toIndex >= pane.tabs.length) return; + if (fromIndex === toIndex) return; + + const newTabs = [...pane.tabs]; + const [moved] = newTabs.splice(fromIndex, 1); + newTabs.splice(toIndex, 0, moved); + + const newLayout = updatePane(paneLayout, { ...pane, tabs: newTabs }); + set(syncRootState(newLayout)); + }, + + resizePanes: (paneId: string, newWidthFraction: number) => { + const { paneLayout } = get(); + const paneIndex = paneLayout.panes.findIndex((p) => p.id === paneId); + if (paneIndex === -1 || paneIndex >= paneLayout.panes.length - 1) return; + + const MIN_FRACTION = 0.1; + const clamped = Math.max( + MIN_FRACTION, + Math.min(1 - MIN_FRACTION * (paneLayout.panes.length - 1), newWidthFraction) + ); + const currentPane = paneLayout.panes[paneIndex]; + const nextPane = paneLayout.panes[paneIndex + 1]; + const combinedWidth = currentPane.widthFraction + nextPane.widthFraction; + const nextWidth = combinedWidth - clamped; + + if (nextWidth < MIN_FRACTION) return; + + const newPanes = paneLayout.panes.map((p, i) => { + if (i === paneIndex) return { ...p, widthFraction: clamped }; + if (i === paneIndex + 1) return { ...p, widthFraction: nextWidth }; + return p; + }); + + set({ paneLayout: { ...paneLayout, panes: newPanes } }); + }, + + getPaneForTab: (tabId: string) => { + const pane = findPaneByTabId(get().paneLayout, tabId); + return pane?.id ?? null; + }, + + getAllPaneTabs: () => { + return getAllTabs(get().paneLayout); + }, +}); diff --git a/src/renderer/store/slices/projectSlice.ts b/src/renderer/store/slices/projectSlice.ts new file mode 100644 index 00000000..989679f2 --- /dev/null +++ b/src/renderer/store/slices/projectSlice.ts @@ -0,0 +1,66 @@ +/** + * Project slice - manages project list state and selection. + */ + +import { getSessionResetState } from '../utils/stateResetHelpers'; + +import type { AppState } from '../types'; +import type { Project } from '@renderer/types/data'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface ProjectSlice { + // State + projects: Project[]; + selectedProjectId: string | null; + projectsLoading: boolean; + projectsError: string | null; + + // Actions + fetchProjects: () => Promise; + selectProject: (id: string) => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createProjectSlice: StateCreator = (set, get) => ({ + // Initial state + projects: [], + selectedProjectId: null, + projectsLoading: false, + projectsError: null, + + // Fetch all projects from main process + fetchProjects: async () => { + set({ projectsLoading: true, projectsError: null }); + try { + const projects = await window.electronAPI.getProjects(); + // Sort by most recent session (descending) + const sorted = [...projects].sort( + (a, b) => (b.mostRecentSession ?? 0) - (a.mostRecentSession ?? 0) + ); + set({ projects: sorted, projectsLoading: false }); + } catch (error) { + set({ + projectsError: error instanceof Error ? error.message : 'Failed to fetch projects', + projectsLoading: false, + }); + } + }, + + // Select a project and fetch its sessions (paginated) + selectProject: (id: string) => { + set({ + selectedProjectId: id, + ...getSessionResetState(), + }); + + // Fetch sessions for this project (paginated) + void get().fetchSessionsInitial(id); + }, +}); diff --git a/src/renderer/store/slices/repositorySlice.ts b/src/renderer/store/slices/repositorySlice.ts new file mode 100644 index 00000000..14476c84 --- /dev/null +++ b/src/renderer/store/slices/repositorySlice.ts @@ -0,0 +1,133 @@ +/** + * Repository slice - manages repository grouping state (worktree support). + */ + +import { createLogger } from '@shared/utils/logger'; + +import { getSessionResetState } from '../utils/stateResetHelpers'; + +import type { AppState } from '../types'; +import type { RepositoryGroup } from '@renderer/types/data'; +import type { StateCreator } from 'zustand'; + +const logger = createLogger('Store:repository'); + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface RepositorySlice { + // State + repositoryGroups: RepositoryGroup[]; + selectedRepositoryId: string | null; + selectedWorktreeId: string | null; + repositoryGroupsLoading: boolean; + repositoryGroupsError: string | null; + viewMode: 'flat' | 'grouped'; + + // Actions + fetchRepositoryGroups: () => Promise; + selectRepository: (repositoryId: string) => void; + selectWorktree: (worktreeId: string) => void; + setViewMode: (mode: 'flat' | 'grouped') => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createRepositorySlice: StateCreator = ( + set, + get +) => ({ + // Initial state + repositoryGroups: [], + selectedRepositoryId: null, + selectedWorktreeId: null, + repositoryGroupsLoading: false, + repositoryGroupsError: null, + viewMode: 'grouped', // Default to grouped view + + // Fetch all repository groups (projects grouped by git repo) + fetchRepositoryGroups: async () => { + set({ repositoryGroupsLoading: true, repositoryGroupsError: null }); + try { + const groups = await window.electronAPI.getRepositoryGroups(); + // Already sorted by most recent session in the scanner + set({ repositoryGroups: groups, repositoryGroupsLoading: false }); + } catch (error) { + set({ + repositoryGroupsError: + error instanceof Error ? error.message : 'Failed to fetch repository groups', + repositoryGroupsLoading: false, + }); + } + }, + + // Select a repository group and auto-select a worktree + selectRepository: (repositoryId: string) => { + const { repositoryGroups } = get(); + const repo = repositoryGroups.find((r) => r.id === repositoryId); + + if (!repo) { + logger.warn('Repository not found:', repositoryId); + return; + } + + // Auto-select worktree: + // 1. Prefer the "Default" worktree (isMainWorktree = true) + // 2. Otherwise, select the first worktree (already sorted by most recent) + const defaultWorktree = repo.worktrees.find((w) => w.isMainWorktree); + const worktreeToSelect = defaultWorktree ?? repo.worktrees[0]; + + if (worktreeToSelect) { + set({ + selectedRepositoryId: repositoryId, + selectedWorktreeId: worktreeToSelect.id, + selectedProjectId: worktreeToSelect.id, + activeProjectId: worktreeToSelect.id, + ...getSessionResetState(), + }); + // Fetch sessions for this worktree + void get().fetchSessionsInitial(worktreeToSelect.id); + } else { + // No worktrees available (shouldn't happen normally) + set({ + selectedRepositoryId: repositoryId, + selectedWorktreeId: null, + ...getSessionResetState(), + }); + } + }, + + // Select a worktree within a repository group + selectWorktree: (worktreeId: string) => { + set({ + selectedWorktreeId: worktreeId, + selectedProjectId: worktreeId, + activeProjectId: worktreeId, + ...getSessionResetState(), + }); + + // Fetch sessions for this worktree + void get().fetchSessionsInitial(worktreeId); + }, + + // Toggle between flat and grouped view modes + setViewMode: (mode: 'flat' | 'grouped') => { + set({ + viewMode: mode, + selectedRepositoryId: null, + selectedWorktreeId: null, + selectedProjectId: null, + ...getSessionResetState(), + }); + + // Fetch the appropriate data for the new mode + if (mode === 'grouped') { + void get().fetchRepositoryGroups(); + } else { + void get().fetchProjects(); + } + }, +}); diff --git a/src/renderer/store/slices/sessionDetailSlice.ts b/src/renderer/store/slices/sessionDetailSlice.ts new file mode 100644 index 00000000..f4542a82 --- /dev/null +++ b/src/renderer/store/slices/sessionDetailSlice.ts @@ -0,0 +1,658 @@ +/** + * Session detail slice - manages session detail, conversation, and stats. + */ + +import { asEnhancedChunkArray } from '@renderer/types/data'; +import { findTabBySession, truncateLabel } from '@renderer/types/tabs'; +import { processSessionClaudeMd } from '@renderer/utils/claudeMdTracker'; +import { processSessionContextWithPhases } from '@renderer/utils/contextTracker'; +import { + extractFileReferences, + transformChunksToConversation, +} from '@renderer/utils/groupTransformer'; +import { createLogger } from '@shared/utils/logger'; + +import { resolveFilePath } from '../utils/pathResolution'; + +const logger = createLogger('Store:sessionDetail'); + +/** + * Tracks latest refresh generation per session to avoid stale overwrites when + * many file-change events trigger concurrent in-place refreshes. + */ +const sessionRefreshGeneration = new Map(); +const sessionRefreshInFlight = new Set(); +const sessionRefreshQueued = new Set(); +let sessionDetailFetchGeneration = 0; + +import { getAllTabs } from '../utils/paneHelpers'; + +import type { AppState } from '../types'; +import type { ClaudeMdStats } from '@renderer/types/claudeMd'; +import type { + ContextPhaseInfo, + ContextStats, + MentionedFileInfo, +} from '@renderer/types/contextInjection'; +import type { ClaudeMdFileInfo, SessionDetail } from '@renderer/types/data'; +import type { AIGroup, SessionConversation } from '@renderer/types/groups'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Per-tab session data type +// ============================================================================= + +export interface TabSessionData { + sessionDetail: SessionDetail | null; + conversation: SessionConversation | null; + conversationLoading: boolean; + sessionDetailLoading: boolean; + sessionDetailError: string | null; + sessionClaudeMdStats: Map | null; + sessionContextStats: Map | null; + sessionPhaseInfo: ContextPhaseInfo | null; + visibleAIGroupId: string | null; + selectedAIGroup: AIGroup | null; +} + +function createEmptyTabSessionData(): TabSessionData { + return { + sessionDetail: null, + conversation: null, + conversationLoading: false, + sessionDetailLoading: false, + sessionDetailError: null, + sessionClaudeMdStats: null, + sessionContextStats: null, + sessionPhaseInfo: null, + visibleAIGroupId: null, + selectedAIGroup: null, + }; +} + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface SessionDetailSlice { + // State + sessionDetail: SessionDetail | null; + sessionDetailLoading: boolean; + sessionDetailError: string | null; + + // Conversation state + conversation: SessionConversation | null; + conversationLoading: boolean; + + // CLAUDE.md stats (injection tracking per AI group) + sessionClaudeMdStats: Map | null; + // Unified context stats (CLAUDE.md + mentioned files + tool outputs) + sessionContextStats: Map | null; + // Context phase info (compaction boundaries) + sessionPhaseInfo: ContextPhaseInfo | null; + + // Visible AI Group + visibleAIGroupId: string | null; + selectedAIGroup: AIGroup | null; + + // Per-tab session data (keyed by tabId) + tabSessionData: Record; + + // Actions + fetchSessionDetail: (projectId: string, sessionId: string, tabId?: string) => Promise; + /** Refresh session without loading states or UI resets - for real-time updates */ + refreshSessionInPlace: (projectId: string, sessionId: string) => Promise; + setVisibleAIGroup: (aiGroupId: string | null) => void; + /** Set visible AI group for a specific tab */ + setTabVisibleAIGroup: (tabId: string, aiGroupId: string | null) => void; + /** Clean up per-tab session data when tab is closed */ + cleanupTabSessionData: (tabId: string) => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createSessionDetailSlice: StateCreator = ( + set, + get +) => ({ + // Initial state + sessionDetail: null, + sessionDetailLoading: false, + sessionDetailError: null, + + conversation: null, + conversationLoading: false, + + // CLAUDE.md stats (injection tracking per AI group) + sessionClaudeMdStats: null, + // Unified context stats (CLAUDE.md + mentioned files + tool outputs) + sessionContextStats: null, + // Context phase info (compaction boundaries) + sessionPhaseInfo: null, + + visibleAIGroupId: null, + selectedAIGroup: null, + + // Per-tab session data + tabSessionData: {}, + + // Fetch full session detail with chunks and subagents + fetchSessionDetail: async (projectId: string, sessionId: string, tabId?: string) => { + const requestGeneration = ++sessionDetailFetchGeneration; + set({ + sessionDetailLoading: true, + sessionDetailError: null, + conversationLoading: true, + }); + + // Also set per-tab loading state + if (tabId) { + const prev = get().tabSessionData; + set({ + tabSessionData: { + ...prev, + [tabId]: { + ...(prev[tabId] ?? createEmptyTabSessionData()), + sessionDetailLoading: true, + sessionDetailError: null, + conversationLoading: true, + }, + }, + }); + } + try { + const detail = await window.electronAPI.getSessionDetail(projectId, sessionId); + if (requestGeneration !== sessionDetailFetchGeneration) { + return; + } + + // Transform chunks to conversation + // Chunks are EnhancedChunk[] at runtime - validate with type guard + // Pass isOngoing to mark the last AI group when session is still in progress + const isOngoing = detail?.session?.isOngoing ?? false; + const enhancedChunks = detail ? asEnhancedChunkArray(detail.chunks) : null; + const conversation: SessionConversation | null = + detail && enhancedChunks + ? transformChunksToConversation(enhancedChunks, detail.processes, isOngoing) + : null; + + // Initialize visibleAIGroupId to first AI Group if available + const firstAIItem = conversation?.items?.find((item) => item.type === 'ai'); + const firstAIGroupId = firstAIItem?.type === 'ai' ? firstAIItem.group.id : null; + const firstAIGroup = firstAIItem?.type === 'ai' ? firstAIItem.group : null; + + // Compute CLAUDE.md stats for the session + const projectRoot = detail?.session?.projectPath ?? ''; + let claudeMdStats: Map | null = null; + let contextStats: Map | null = null; + let phaseInfo: ContextPhaseInfo | null = null; + if (conversation?.items) { + // Fetch real CLAUDE.md token data + let claudeMdTokenData: Record = {}; + try { + claudeMdTokenData = await window.electronAPI.readClaudeMdFiles(projectRoot); + if (requestGeneration !== sessionDetailFetchGeneration) { + return; + } + } catch (err) { + logger.error('Failed to read CLAUDE.md files:', err); + } + + claudeMdStats = processSessionClaudeMd(conversation.items, projectRoot, claudeMdTokenData); + + // Fetch real tokens for directory CLAUDE.md files + // Directory injections are detected dynamically from Read tool paths and aren't in pre-fetched tokenData + // We need to validate these BEFORE calling processSessionContext so both trackers have consistent data + const directoryTokenData: Record = {}; // Validated directory token data + + if (claudeMdStats && claudeMdStats.size > 0) { + // Collect all unique directory injection paths + const directoryPaths = new Set(); + for (const stats of claudeMdStats.values()) { + for (const injection of stats.accumulatedInjections) { + if (injection.source === 'directory') { + directoryPaths.add(injection.path); + } + } + } + + // Fetch real tokens for each directory path (parallel IPC calls) + if (directoryPaths.size > 0) { + const directoryTokens = new Map(); + const nonExistentPaths = new Set(); + + const directoryResults = await Promise.all( + Array.from(directoryPaths).map(async (fullPath) => { + try { + const dirPath = fullPath.replace(/[\\/]CLAUDE\.md$/, ''); + const fileInfo = await window.electronAPI.readDirectoryClaudeMd(dirPath); + return { fullPath, fileInfo, error: false }; + } catch (err) { + logger.error('Failed to read directory CLAUDE.md:', fullPath, err); + return { fullPath, fileInfo: null, error: true }; + } + }) + ); + if (requestGeneration !== sessionDetailFetchGeneration) { + return; + } + + for (const { fullPath, fileInfo, error } of directoryResults) { + if (error || !fileInfo) { + nonExistentPaths.add(fullPath); + } else if (fileInfo.exists && fileInfo.estimatedTokens > 0) { + directoryTokens.set(fullPath, fileInfo.estimatedTokens); + directoryTokenData[fullPath] = fileInfo; + } else { + nonExistentPaths.add(fullPath); + } + } + + // Update stats: set real tokens and REMOVE non-existent files + for (const [, stats] of claudeMdStats.entries()) { + // Filter out non-existent paths + stats.accumulatedInjections = stats.accumulatedInjections.filter( + (inj) => inj.source !== 'directory' || !nonExistentPaths.has(inj.path) + ); + stats.newInjections = stats.newInjections.filter( + (inj) => inj.source !== 'directory' || !nonExistentPaths.has(inj.path) + ); + + // Update tokens for existing files + for (const injection of stats.accumulatedInjections) { + if (injection.source === 'directory' && directoryTokens.has(injection.path)) { + injection.estimatedTokens = directoryTokens.get(injection.path)!; + } + } + for (const injection of stats.newInjections) { + if (injection.source === 'directory' && directoryTokens.has(injection.path)) { + injection.estimatedTokens = directoryTokens.get(injection.path)!; + } + } + + // Recalculate totals and counts + stats.totalEstimatedTokens = stats.accumulatedInjections.reduce( + (sum, inj) => sum + inj.estimatedTokens, + 0 + ); + stats.accumulatedCount = stats.accumulatedInjections.length; + stats.newCount = stats.newInjections.length; + } + } + } + + // Compute unified context stats (CLAUDE.md + mentioned files + tool outputs) + // Extract all mentioned file paths from user groups + const mentionedFilePaths = new Set(); + for (const item of conversation.items) { + if (item.type === 'user' && item.group.content.fileReferences) { + for (const ref of item.group.content.fileReferences) { + // Use resolveFilePath to properly handle ./ and ../ prefixes + const absolutePath = resolveFilePath(projectRoot, ref.path); + mentionedFilePaths.add(absolutePath); + } + } + } + + // Also collect @-mentions from isMeta:true user messages in AI responses + for (const item of conversation.items) { + if (item.type === 'ai') { + for (const msg of item.group.responses) { + if (msg.type !== 'user') continue; + let text = ''; + if (typeof msg.content === 'string') { + text = msg.content; + } else if (Array.isArray(msg.content)) { + for (const block of msg.content) { + if (block.type === 'text' && block.text) text += block.text; + } + } + if (text) { + for (const ref of extractFileReferences(text)) { + const absolutePath = resolveFilePath(projectRoot, ref.path); + mentionedFilePaths.add(absolutePath); + } + } + } + } + } + + // Fetch token data for each mentioned file (parallel IPC calls) + const mentionedFileTokenData = new Map(); + const mentionedFileResults = await Promise.all( + Array.from(mentionedFilePaths).map(async (filePath) => { + try { + const fileInfo = await window.electronAPI.readMentionedFile(filePath, projectRoot); + return { filePath, fileInfo }; + } catch (err) { + logger.error('Failed to read mentioned file:', filePath, err); + return { filePath, fileInfo: null }; + } + }) + ); + if (requestGeneration !== sessionDetailFetchGeneration) { + return; + } + for (const { filePath, fileInfo } of mentionedFileResults) { + if (fileInfo) { + mentionedFileTokenData.set(filePath, fileInfo); + } + } + + // Process Visible Context with all token data + // Pass validated directory token data so contextTracker can filter non-existent files + const phaseResult = processSessionContextWithPhases( + conversation.items, + projectRoot, + claudeMdTokenData, + mentionedFileTokenData, + directoryTokenData + ); + contextStats = phaseResult.statsMap; + phaseInfo = phaseResult.phaseInfo; + } + + // Update tab label if this session is open in a tab + const currentState = get(); + if (requestGeneration !== sessionDetailFetchGeneration) { + return; + } + const activeTab = currentState.getActiveTab(); + const stillViewingSession = + currentState.selectedSessionId === sessionId || + (activeTab?.type === 'session' && + activeTab.sessionId === sessionId && + activeTab.projectId === projectId); + if (!stillViewingSession) { + set({ + sessionDetailLoading: false, + conversationLoading: false, + }); + return; + } + const existingTab = findTabBySession(currentState.openTabs, sessionId); + if (existingTab && detail) { + const newLabel = detail.session.firstMessage + ? truncateLabel(detail.session.firstMessage) + : `Session ${sessionId.slice(0, 8)}`; + currentState.updateTabLabel(existingTab.id, newLabel); + } + + set({ + sessionDetail: detail, + sessionDetailLoading: false, + conversation, + conversationLoading: false, + visibleAIGroupId: firstAIGroupId, + selectedAIGroup: firstAIGroup, + sessionClaudeMdStats: claudeMdStats, + sessionContextStats: contextStats, + sessionPhaseInfo: phaseInfo, + }); + + // Store per-tab session data + if (tabId) { + const prev = get().tabSessionData; + set({ + tabSessionData: { + ...prev, + [tabId]: { + sessionDetail: detail, + conversation, + conversationLoading: false, + sessionDetailLoading: false, + sessionDetailError: null, + sessionClaudeMdStats: claudeMdStats, + sessionContextStats: contextStats, + sessionPhaseInfo: phaseInfo, + visibleAIGroupId: firstAIGroupId, + selectedAIGroup: firstAIGroup, + }, + }, + }); + } + } catch (error) { + logger.error('fetchSessionDetail error:', error); + if (requestGeneration !== sessionDetailFetchGeneration) { + return; + } + const errorMsg = error instanceof Error ? error.message : 'Failed to fetch session detail'; + set({ + sessionDetailError: errorMsg, + sessionDetailLoading: false, + conversationLoading: false, + }); + + // Store per-tab error state + if (tabId) { + const prev = get().tabSessionData; + set({ + tabSessionData: { + ...prev, + [tabId]: { + ...(prev[tabId] ?? createEmptyTabSessionData()), + sessionDetailError: errorMsg, + sessionDetailLoading: false, + conversationLoading: false, + }, + }, + }); + } + } + }, + + // Refresh session in place without loading states or UI resets + // Used for real-time file change updates to avoid flickering + refreshSessionInPlace: async (projectId: string, sessionId: string) => { + const currentState = get(); + + // Check if any tab is viewing this session (across all panes) + const allTabs = getAllTabs(currentState.paneLayout); + const tabsViewingSession = allTabs.filter( + (t) => t.type === 'session' && t.sessionId === sessionId + ); + + // Only refresh if we're actually viewing this session + if (currentState.selectedSessionId !== sessionId && tabsViewingSession.length === 0) { + return; + } + + const refreshKey = `${projectId}/${sessionId}`; + const generation = (sessionRefreshGeneration.get(refreshKey) ?? 0) + 1; + sessionRefreshGeneration.set(refreshKey, generation); + + // Coalesce duplicate in-flight refreshes for the same session. + if (sessionRefreshInFlight.has(refreshKey)) { + sessionRefreshQueued.add(refreshKey); + return; + } + sessionRefreshInFlight.add(refreshKey); + + try { + const detail = await window.electronAPI.getSessionDetail(projectId, sessionId); + + // Drop stale responses if a newer refresh started while this one was in flight. + if (sessionRefreshGeneration.get(refreshKey) !== generation) { + return; + } + + if (!detail) { + return; + } + + // Transform chunks to conversation - validate with type guard + const isOngoing = detail.session?.isOngoing ?? false; + const enhancedChunks = asEnhancedChunkArray(detail.chunks); + if (!enhancedChunks) { + return; + } + const newConversation = transformChunksToConversation( + enhancedChunks, + detail.processes, + isOngoing + ); + + if (!newConversation) { + return; + } + + const latestState = get(); + const latestActiveTab = latestState.getActiveTab(); + const stillViewingSession = + latestState.selectedSessionId === sessionId || + (latestActiveTab?.type === 'session' && latestActiveTab.sessionId === sessionId); + if (!stillViewingSession) { + return; + } + + // Preserve current visibleAIGroupId if it still exists in new conversation + // Otherwise keep it (it might be scrolled to an item that still exists) + const currentVisibleId = currentState.visibleAIGroupId; + const currentSelectedGroup = currentState.selectedAIGroup; + + // Check if current visible group still exists + const visibleGroupStillExists = + currentVisibleId && + newConversation.items.some( + (item) => item.type === 'ai' && item.group.id === currentVisibleId + ); + + // Find the updated group if it exists + let updatedSelectedGroup = currentSelectedGroup; + if (visibleGroupStillExists && currentVisibleId) { + const foundItem = newConversation.items.find( + (item) => item.type === 'ai' && item.group.id === currentVisibleId + ); + if (foundItem?.type === 'ai') { + updatedSelectedGroup = foundItem.group; + } + } + + // Also update the session's isOngoing in the sessions array + // This keeps the sidebar in sync with the chat view + const updatedSessions = currentState.sessions.map((s) => + s.id === sessionId ? { ...s, isOngoing: detail.session?.isOngoing ?? false } : s + ); + + // Update only the data, preserve UI states + set({ + sessionDetail: detail, + conversation: newConversation, + sessions: updatedSessions, + // Preserve visible group if it still exists, otherwise keep current + ...(visibleGroupStillExists + ? { + selectedAIGroup: updatedSelectedGroup, + } + : {}), + // Note: aiGroupExpansionLevels and expandedStepIds are NOT touched + // so expansion states are preserved + }); + + // Also update per-tab session data for all tabs viewing this session + const latestTabSessionData = { ...get().tabSessionData }; + const latestAllTabs = getAllTabs(get().paneLayout); + for (const tab of latestAllTabs) { + if (tab.type === 'session' && tab.sessionId === sessionId && latestTabSessionData[tab.id]) { + const tabData = latestTabSessionData[tab.id]; + // Preserve per-tab visibleAIGroupId + const tabVisibleId = tabData.visibleAIGroupId; + const tabGroupStillExists = + tabVisibleId && + newConversation.items.some( + (item) => item.type === 'ai' && item.group.id === tabVisibleId + ); + let tabSelectedGroup = tabData.selectedAIGroup; + if (tabGroupStillExists && tabVisibleId) { + const found = newConversation.items.find( + (item) => item.type === 'ai' && item.group.id === tabVisibleId + ); + if (found?.type === 'ai') tabSelectedGroup = found.group; + } + + latestTabSessionData[tab.id] = { + ...tabData, + sessionDetail: detail, + conversation: newConversation, + ...(tabGroupStillExists ? { selectedAIGroup: tabSelectedGroup } : {}), + }; + } + } + set({ tabSessionData: latestTabSessionData }); + } catch (error) { + logger.error('refreshSessionInPlace error:', error); + // Don't set error state - this is a background refresh + } finally { + sessionRefreshInFlight.delete(refreshKey); + if (sessionRefreshQueued.has(refreshKey)) { + sessionRefreshQueued.delete(refreshKey); + void get().refreshSessionInPlace(projectId, sessionId); + } + } + }, + + // Set visible AI Group (called by scroll observer) + setVisibleAIGroup: (aiGroupId: string | null) => { + const state = get(); + + if (aiGroupId === state.visibleAIGroupId) return; + + // Find the AIGroup in the conversation + let selectedAIGroup: AIGroup | null = null; + if (aiGroupId && state.conversation) { + for (const item of state.conversation.items) { + if (item.type === 'ai' && item.group.id === aiGroupId) { + selectedAIGroup = item.group; + break; + } + } + } + + set({ + visibleAIGroupId: aiGroupId, + selectedAIGroup, + }); + }, + + // Set visible AI Group for a specific tab + setTabVisibleAIGroup: (tabId: string, aiGroupId: string | null) => { + const state = get(); + const tabData = state.tabSessionData[tabId]; + if (!tabData) return; + + if (aiGroupId === tabData.visibleAIGroupId) return; + + // Find the AIGroup in the tab's conversation + let selectedAIGroup: AIGroup | null = null; + if (aiGroupId && tabData.conversation) { + for (const item of tabData.conversation.items) { + if (item.type === 'ai' && item.group.id === aiGroupId) { + selectedAIGroup = item.group; + break; + } + } + } + + set({ + tabSessionData: { + ...state.tabSessionData, + [tabId]: { + ...tabData, + visibleAIGroupId: aiGroupId, + selectedAIGroup, + }, + }, + }); + }, + + // Clean up per-tab session data when tab is closed + cleanupTabSessionData: (tabId: string) => { + const prev = get().tabSessionData; + if (!(tabId in prev)) return; + const next = { ...prev }; + delete next[tabId]; + set({ tabSessionData: next }); + }, +}); diff --git a/src/renderer/store/slices/sessionSlice.ts b/src/renderer/store/slices/sessionSlice.ts new file mode 100644 index 00000000..3eb285bf --- /dev/null +++ b/src/renderer/store/slices/sessionSlice.ts @@ -0,0 +1,274 @@ +/** + * Session slice - manages session list state and pagination. + */ + +import { createLogger } from '@shared/utils/logger'; + +import type { AppState } from '../types'; +import type { Session } from '@renderer/types/data'; +import type { StateCreator } from 'zustand'; + +const logger = createLogger('Store:session'); + +/** + * Tracks the latest in-place refresh generation per project. + * Used to guarantee last-write-wins under rapid file change events. + */ +const projectRefreshGeneration = new Map(); + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface SessionSlice { + // State + sessions: Session[]; + selectedSessionId: string | null; + sessionsLoading: boolean; + sessionsError: string | null; + // Pagination state + sessionsCursor: string | null; + sessionsHasMore: boolean; + sessionsTotalCount: number; + sessionsLoadingMore: boolean; + // Pinned sessions + pinnedSessionIds: string[]; + + // Actions + fetchSessions: (projectId: string) => Promise; + fetchSessionsInitial: (projectId: string) => Promise; + fetchSessionsMore: () => Promise; + resetSessionsPagination: () => void; + selectSession: (id: string) => void; + clearSelection: () => void; + /** Refresh sessions list without loading states - for real-time updates */ + refreshSessionsInPlace: (projectId: string) => Promise; + /** Toggle pin/unpin for a session */ + togglePinSession: (sessionId: string) => Promise; + /** Load pinned sessions from config for current project */ + loadPinnedSessions: () => Promise; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createSessionSlice: StateCreator = (set, get) => ({ + // Initial state + sessions: [], + selectedSessionId: null, + sessionsLoading: false, + sessionsError: null, + // Pagination state + sessionsCursor: null, + sessionsHasMore: false, + sessionsTotalCount: 0, + sessionsLoadingMore: false, + // Pinned sessions + pinnedSessionIds: [], + + // Fetch sessions for a specific project (legacy - not paginated) + fetchSessions: async (projectId: string) => { + set({ sessionsLoading: true, sessionsError: null }); + try { + const sessions = await window.electronAPI.getSessions(projectId); + // Sort by createdAt (descending) + const sorted = [...sessions].sort((a, b) => b.createdAt - a.createdAt); + set({ sessions: sorted, sessionsLoading: false }); + } catch (error) { + set({ + sessionsError: error instanceof Error ? error.message : 'Failed to fetch sessions', + sessionsLoading: false, + }); + } + }, + + // Fetch initial page of sessions (paginated) + fetchSessionsInitial: async (projectId: string) => { + set({ + sessionsLoading: true, + sessionsError: null, + sessions: [], + sessionsCursor: null, + sessionsHasMore: false, + sessionsTotalCount: 0, + }); + try { + const result = await window.electronAPI.getSessionsPaginated(projectId, null, 20, { + includeTotalCount: false, + prefilterAll: false, + }); + set({ + sessions: result.sessions, + sessionsCursor: result.nextCursor, + sessionsHasMore: result.hasMore, + sessionsTotalCount: result.totalCount, + sessionsLoading: false, + }); + + // Load pinned sessions after fetching session list + void get().loadPinnedSessions(); + } catch (error) { + set({ + sessionsError: error instanceof Error ? error.message : 'Failed to fetch sessions', + sessionsLoading: false, + }); + } + }, + + // Fetch more sessions (next page) + fetchSessionsMore: async () => { + const state = get(); + const { selectedProjectId, sessionsCursor, sessionsHasMore, sessionsLoadingMore } = state; + + // Guard: don't fetch if already loading, no more pages, or no project + if (!selectedProjectId || !sessionsHasMore || sessionsLoadingMore || !sessionsCursor) { + return; + } + + set({ sessionsLoadingMore: true }); + try { + const result = await window.electronAPI.getSessionsPaginated( + selectedProjectId, + sessionsCursor, + 20, + { + includeTotalCount: false, + prefilterAll: false, + } + ); + set((prevState) => ({ + sessions: [...prevState.sessions, ...result.sessions], + sessionsCursor: result.nextCursor, + sessionsHasMore: result.hasMore, + sessionsLoadingMore: false, + })); + } catch (error) { + set({ + sessionsError: error instanceof Error ? error.message : 'Failed to fetch more sessions', + sessionsLoadingMore: false, + }); + } + }, + + // Reset pagination state + resetSessionsPagination: () => { + set({ + sessions: [], + sessionsCursor: null, + sessionsHasMore: false, + sessionsTotalCount: 0, + sessionsLoadingMore: false, + sessionsError: null, + }); + }, + + // Select a session and fetch its detail + selectSession: (id: string) => { + set({ + selectedSessionId: id, + sessionDetail: null, + sessionContextStats: null, + sessionDetailError: null, + }); + + // Fetch detail for this session, passing the active tabId for per-tab data + const state = get(); + const projectId = state.selectedProjectId; + if (projectId) { + const activeTabId = state.activeTabId ?? undefined; + void state.fetchSessionDetail(projectId, id, activeTabId); + } else { + logger.warn('Cannot fetch session detail: no project selected'); + } + }, + + // Clear all selections + clearSelection: () => { + set({ + selectedProjectId: null, + selectedSessionId: null, + sessions: [], + sessionDetail: null, + sessionContextStats: null, + }); + }, + + // Refresh sessions list in place without loading states + // Used for real-time updates when new sessions are added + refreshSessionsInPlace: async (projectId: string) => { + const currentState = get(); + + // Only refresh if viewing this project + if (currentState.selectedProjectId !== projectId) { + return; + } + + const generation = (projectRefreshGeneration.get(projectId) ?? 0) + 1; + projectRefreshGeneration.set(projectId, generation); + + try { + const result = await window.electronAPI.getSessionsPaginated(projectId, null, 20, { + includeTotalCount: false, + prefilterAll: false, + }); + + // Drop stale responses from older in-flight refreshes + if (projectRefreshGeneration.get(projectId) !== generation) { + return; + } + + // Update sessions without loading state + set({ + sessions: result.sessions, + sessionsCursor: result.nextCursor, + sessionsHasMore: result.hasMore, + sessionsTotalCount: result.totalCount, + // Don't touch sessionsLoading - keep it as-is + }); + } catch (error) { + logger.error('refreshSessionsInPlace error:', error); + // Don't set error state - this is a background refresh + } + }, + + // Toggle pin/unpin for a session + togglePinSession: async (sessionId: string) => { + const state = get(); + const projectId = state.selectedProjectId; + if (!projectId) return; + + const isPinned = state.pinnedSessionIds.includes(sessionId); + + try { + if (isPinned) { + await window.electronAPI.config.unpinSession(projectId, sessionId); + set({ pinnedSessionIds: state.pinnedSessionIds.filter((id) => id !== sessionId) }); + } else { + await window.electronAPI.config.pinSession(projectId, sessionId); + set({ pinnedSessionIds: [sessionId, ...state.pinnedSessionIds] }); + } + } catch (error) { + logger.error('togglePinSession error:', error); + } + }, + + // Load pinned sessions from config for current project + loadPinnedSessions: async () => { + const state = get(); + const projectId = state.selectedProjectId; + if (!projectId) { + set({ pinnedSessionIds: [] }); + return; + } + + try { + const config = await window.electronAPI.config.get(); + const pins = config.sessions?.pinnedSessions?.[projectId] ?? []; + set({ pinnedSessionIds: pins.map((p) => p.sessionId) }); + } catch (error) { + logger.error('loadPinnedSessions error:', error); + set({ pinnedSessionIds: [] }); + } + }, +}); diff --git a/src/renderer/store/slices/subagentSlice.ts b/src/renderer/store/slices/subagentSlice.ts new file mode 100644 index 00000000..06c5dbc6 --- /dev/null +++ b/src/renderer/store/slices/subagentSlice.ts @@ -0,0 +1,143 @@ +/** + * Subagent slice - manages subagent drill-down state. + */ + +import type { AppState, BreadcrumbItem } from '../types'; +import type { SubagentDetail } from '@renderer/types/data'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface SubagentSlice { + // State + drillDownStack: BreadcrumbItem[]; + currentSubagentDetail: SubagentDetail | null; + subagentDetailLoading: boolean; + subagentDetailError: string | null; + + // Actions + drillDownSubagent: ( + projectId: string, + sessionId: string, + subagentId: string, + description: string + ) => Promise; + navigateToBreadcrumb: (index: number) => void; + closeSubagentModal: () => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createSubagentSlice: StateCreator = (set, get) => ({ + // Initial state + drillDownStack: [], + currentSubagentDetail: null, + subagentDetailLoading: false, + subagentDetailError: null, + + // Drill down into a subagent + drillDownSubagent: async ( + projectId: string, + sessionId: string, + subagentId: string, + description: string + ) => { + set({ subagentDetailLoading: true, subagentDetailError: null }); + try { + const detail = await window.electronAPI.getSubagentDetail(projectId, sessionId, subagentId); + + if (!detail) { + set({ + subagentDetailError: 'Failed to load subagent details', + subagentDetailLoading: false, + }); + return; + } + + // Add to breadcrumb stack + const currentStack = get().drillDownStack; + set({ + drillDownStack: [...currentStack, { id: subagentId, description }], + currentSubagentDetail: detail, + subagentDetailLoading: false, + }); + } catch (error) { + set({ + subagentDetailError: error instanceof Error ? error.message : 'Failed to load subagent', + subagentDetailLoading: false, + }); + } + }, + + // Navigate to a specific breadcrumb (pop stack to that level) + navigateToBreadcrumb: (index: number) => { + const state = get(); + + // If navigating to index 0 or negative, close modal + if (index <= 0) { + set({ + drillDownStack: [], + currentSubagentDetail: null, + subagentDetailError: null, + }); + return; + } + + // Pop stack to the specified index + const newStack = state.drillDownStack.slice(0, index); + + if (newStack.length === 0) { + set({ + drillDownStack: [], + currentSubagentDetail: null, + subagentDetailError: null, + }); + return; + } + + // Reload detail for the target level + const targetItem = newStack[newStack.length - 1]; + const projectId = state.selectedProjectId; + const sessionId = state.selectedSessionId; + + if (!projectId || !sessionId) return; + + set({ subagentDetailLoading: true, subagentDetailError: null }); + + window.electronAPI + .getSubagentDetail(projectId, sessionId, targetItem.id) + .then((detail) => { + if (detail) { + set({ + drillDownStack: newStack, + currentSubagentDetail: detail, + subagentDetailLoading: false, + }); + } else { + set({ + subagentDetailError: 'Failed to load subagent details', + subagentDetailLoading: false, + }); + } + }) + .catch((error) => { + set({ + subagentDetailError: error instanceof Error ? error.message : 'Failed to load subagent', + subagentDetailLoading: false, + }); + }); + }, + + // Close the subagent modal + closeSubagentModal: () => { + set({ + drillDownStack: [], + currentSubagentDetail: null, + subagentDetailError: null, + }); + }, +}); diff --git a/src/renderer/store/slices/tabSlice.ts b/src/renderer/store/slices/tabSlice.ts new file mode 100644 index 00000000..a6da3673 --- /dev/null +++ b/src/renderer/store/slices/tabSlice.ts @@ -0,0 +1,740 @@ +/** + * Tab slice - manages tab state and actions. + * + * Facade pattern: All tab mutations operate on the paneLayout and sync + * root-level openTabs/activeTabId/selectedTabIds from the focused pane + * for backward compatibility. + */ + +import { + createSearchNavigationRequest, + findTabBySession, + findTabBySessionAndProject, + truncateLabel, +} from '@renderer/types/tabs'; + +import { + findPane, + findPaneByTabId, + getAllTabs, + removePane as removePaneHelper, + syncFocusedPaneState, + updatePane, +} from '../utils/paneHelpers'; +import { getFullResetState } from '../utils/stateResetHelpers'; + +import type { AppState, SearchNavigationContext } from '../types'; +import type { PaneLayout } from '@renderer/types/panes'; +import type { OpenTabOptions, Tab, TabInput, TabNavigationRequest } from '@renderer/types/tabs'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface TabSlice { + // State (synced from focused pane for backward compat) + openTabs: Tab[]; + activeTabId: string | null; + selectedTabIds: string[]; + + // Project context state + activeProjectId: string | null; + + // Actions + openTab: (tab: TabInput, options?: OpenTabOptions) => void; + closeTab: (tabId: string) => void; + setActiveTab: (tabId: string) => void; + openDashboard: () => void; + getActiveTab: () => Tab | null; + isSessionOpen: (sessionId: string) => boolean; + enqueueTabNavigation: (tabId: string, request: TabNavigationRequest) => void; + consumeTabNavigation: (tabId: string, requestId: string) => void; + saveTabScrollPosition: (tabId: string, scrollTop: number) => void; + + // Project context actions + setActiveProject: (projectId: string) => void; + + // Per-tab UI state actions + setTabContextPanelVisible: (tabId: string, visible: boolean) => void; + updateTabLabel: (tabId: string, label: string) => void; + + // Multi-select actions + setSelectedTabIds: (ids: string[]) => void; + clearTabSelection: () => void; + + // Bulk close actions + closeOtherTabs: (tabId: string) => void; + closeTabsToRight: (tabId: string) => void; + closeAllTabs: () => void; + closeTabs: (tabIds: string[]) => void; + + // Navigation actions + navigateToSession: ( + projectId: string, + sessionId: string, + fromSearch?: boolean, + searchContext?: SearchNavigationContext + ) => void; +} + +// ============================================================================= +// Helpers +// ============================================================================= + +/** + * Sync root-level state from the focused pane. + */ +function syncFromLayout(layout: PaneLayout): Record { + const synced = syncFocusedPaneState(layout); + return { + paneLayout: layout, + openTabs: synced.openTabs, + activeTabId: synced.activeTabId, + selectedTabIds: synced.selectedTabIds, + }; +} + +/** + * Update a tab in whichever pane contains it, returning the new layout. + */ +function updateTabInLayout( + layout: PaneLayout, + tabId: string, + updater: (tab: Tab) => Tab +): PaneLayout { + const pane = findPaneByTabId(layout, tabId); + if (!pane) return layout; + return updatePane(layout, { + ...pane, + tabs: pane.tabs.map((t) => (t.id === tabId ? updater(t) : t)), + }); +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createTabSlice: StateCreator = (set, get) => ({ + // Initial state (synced from focused pane) + openTabs: [], + activeTabId: null, + selectedTabIds: [], + + // Project context state + activeProjectId: null, + + // Open a tab in the focused pane, or focus existing if sessionId matches (within focused pane) + openTab: (tab: TabInput, options?: OpenTabOptions) => { + const state = get(); + const { paneLayout } = state; + const focusedPane = findPane(paneLayout, paneLayout.focusedPaneId); + if (!focusedPane) return; + + // If opening a session tab, check for duplicates first (unless forceNewTab) + if (tab.type === 'session' && tab.sessionId && !options?.forceNewTab) { + // Check across ALL panes for dedup + const allTabs = getAllTabs(paneLayout); + const existing = findTabBySession(allTabs, tab.sessionId); + if (existing) { + // Focus existing tab (which will also focus its pane) + state.setActiveTab(existing.id); + return; + } + + // Replace active tab if replaceActiveTab option is set or active tab is a dashboard + const activeTab = focusedPane.tabs.find((t) => t.id === focusedPane.activeTabId); + if (activeTab && (options?.replaceActiveTab || activeTab.type === 'dashboard')) { + // Cleanup old tab's state if it was a session tab + if (activeTab.type === 'session') { + state.cleanupTabUIState(activeTab.id); + state.cleanupTabSessionData(activeTab.id); + } + + const replacementTab: Tab = { + ...tab, + id: activeTab.id, + label: truncateLabel(tab.label), + createdAt: Date.now(), + }; + + const updatedPane = { + ...focusedPane, + tabs: focusedPane.tabs.map((t) => (t.id === activeTab.id ? replacementTab : t)), + activeTabId: replacementTab.id, + }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + return; + } + } + + // Create new tab with generated id and timestamp + const newTab: Tab = { + ...tab, + id: crypto.randomUUID(), + label: truncateLabel(tab.label), + createdAt: Date.now(), + }; + + const updatedPane = { + ...focusedPane, + tabs: [...focusedPane.tabs, newTab], + activeTabId: newTab.id, + }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + }, + + // Close a tab by ID in whichever pane contains it + closeTab: (tabId: string) => { + const state = get(); + const { paneLayout } = state; + const pane = findPaneByTabId(paneLayout, tabId); + if (!pane) return; + + const index = pane.tabs.findIndex((t) => t.id === tabId); + if (index === -1) return; + + // Cleanup per-tab UI state and session data + state.cleanupTabUIState(tabId); + state.cleanupTabSessionData(tabId); + + const newTabs = pane.tabs.filter((t) => t.id !== tabId); + + // Determine new active tab within this pane + let newActiveId = pane.activeTabId; + if (pane.activeTabId === tabId) { + newActiveId = newTabs[index]?.id ?? newTabs[index - 1]?.id ?? null; + } + + // If pane becomes empty and it's not the only pane, close the pane + if (newTabs.length === 0 && paneLayout.panes.length > 1) { + state.closePane(pane.id); + return; + } + + // If all tabs across all panes are gone, reset to initial state + const allOtherTabs = paneLayout.panes.filter((p) => p.id !== pane.id).flatMap((p) => p.tabs); + if (newTabs.length === 0 && allOtherTabs.length === 0) { + const updatedPane = { ...pane, tabs: [], activeTabId: null, selectedTabIds: [] }; + const newLayout = updatePane(paneLayout, updatedPane); + set({ + ...syncFromLayout(newLayout), + ...getFullResetState(), + }); + return; + } + + const updatedPane = { + ...pane, + tabs: newTabs, + activeTabId: newActiveId, + selectedTabIds: pane.selectedTabIds.filter((id) => id !== tabId), + }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + + // Sync sidebar state for the newly active tab (project, repository, sessions) + if (newActiveId) { + get().setActiveTab(newActiveId); + } + }, + + // Switch focus to an existing tab + // Also syncs sidebar state for session tabs to match the tab's project/session + setActiveTab: (tabId: string) => { + const state = get(); + const { paneLayout } = state; + + // Find which pane contains this tab + const pane = findPaneByTabId(paneLayout, tabId); + if (!pane) return; + + const tab = pane.tabs.find((t) => t.id === tabId); + if (!tab) return; + + // Update pane's activeTabId and focus the pane + const updatedPane = { ...pane, activeTabId: tabId }; + let newLayout = updatePane(paneLayout, updatedPane); + newLayout = { ...newLayout, focusedPaneId: pane.id }; + set(syncFromLayout(newLayout)); + + // For session tabs, sync sidebar state to match + if (tab.type === 'session' && tab.sessionId && tab.projectId) { + const sessionId = tab.sessionId; + const projectId = tab.projectId; + const sessionChanged = state.selectedSessionId !== sessionId; + + // Check if per-tab data is already cached + const cachedTabData = state.tabSessionData[tabId]; + const hasCachedData = cachedTabData?.conversation != null; + + // Find the repository and worktree containing this session + let foundRepo: string | null = null; + let foundWorktree: string | null = null; + + for (const repo of state.repositoryGroups) { + for (const wt of repo.worktrees) { + if (wt.sessions.includes(sessionId)) { + foundRepo = repo.id; + foundWorktree = wt.id; + break; + } + } + if (foundRepo) break; + } + + if (foundRepo && foundWorktree) { + const worktreeChanged = state.selectedWorktreeId !== foundWorktree; + set({ + selectedRepositoryId: foundRepo, + selectedWorktreeId: foundWorktree, + selectedSessionId: sessionId, + activeProjectId: foundWorktree, + selectedProjectId: foundWorktree, + }); + if (worktreeChanged) { + void get().fetchSessionsInitial(foundWorktree); + } + if (sessionChanged) { + if (hasCachedData) { + // Swap global state from per-tab cache (no re-fetch) + set({ + sessionDetail: cachedTabData.sessionDetail, + conversation: cachedTabData.conversation, + conversationLoading: false, + sessionDetailLoading: false, + sessionDetailError: null, + sessionClaudeMdStats: cachedTabData.sessionClaudeMdStats, + sessionContextStats: cachedTabData.sessionContextStats, + sessionPhaseInfo: cachedTabData.sessionPhaseInfo, + visibleAIGroupId: cachedTabData.visibleAIGroupId, + selectedAIGroup: cachedTabData.selectedAIGroup, + }); + } else { + void get().fetchSessionDetail(foundWorktree, sessionId, tabId); + } + } + return; + } + + // Fallback: search in flat projects + const project = state.projects.find( + (p) => p.id === projectId || p.sessions.includes(sessionId) + ); + if (project) { + const projectChanged = state.selectedProjectId !== project.id; + set({ + activeProjectId: project.id, + selectedProjectId: project.id, + selectedSessionId: sessionId, + }); + if (projectChanged) { + void get().fetchSessionsInitial(project.id); + } + if (sessionChanged) { + if (hasCachedData) { + // Swap global state from per-tab cache (no re-fetch) + set({ + sessionDetail: cachedTabData.sessionDetail, + conversation: cachedTabData.conversation, + conversationLoading: false, + sessionDetailLoading: false, + sessionDetailError: null, + sessionClaudeMdStats: cachedTabData.sessionClaudeMdStats, + sessionContextStats: cachedTabData.sessionContextStats, + sessionPhaseInfo: cachedTabData.sessionPhaseInfo, + visibleAIGroupId: cachedTabData.visibleAIGroupId, + selectedAIGroup: cachedTabData.selectedAIGroup, + }); + } else { + void get().fetchSessionDetail(project.id, sessionId, tabId); + } + } + return; + } + } + }, + + // Open a new dashboard tab in the focused pane + openDashboard: () => { + const state = get(); + const { paneLayout } = state; + const focusedPane = findPane(paneLayout, paneLayout.focusedPaneId); + if (!focusedPane) return; + + const newTab: Tab = { + id: crypto.randomUUID(), + type: 'dashboard', + label: 'Dashboard', + createdAt: Date.now(), + }; + + const updatedPane = { + ...focusedPane, + tabs: [...focusedPane.tabs, newTab], + activeTabId: newTab.id, + }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + }, + + // Get the currently active tab (from the focused pane) + getActiveTab: () => { + const state = get(); + const focusedPane = findPane(state.paneLayout, state.paneLayout.focusedPaneId); + if (!focusedPane?.activeTabId) return null; + return focusedPane.tabs.find((t) => t.id === focusedPane.activeTabId) ?? null; + }, + + // Check if a session is already open in any pane + isSessionOpen: (sessionId: string) => { + const allTabs = getAllTabs(get().paneLayout); + return allTabs.some((t) => t.type === 'session' && t.sessionId === sessionId); + }, + + // Enqueue a navigation request on a tab (in whichever pane contains it) + enqueueTabNavigation: (tabId: string, request: TabNavigationRequest) => { + const { paneLayout } = get(); + const newLayout = updateTabInLayout(paneLayout, tabId, (tab) => ({ + ...tab, + pendingNavigation: request, + })); + set(syncFromLayout(newLayout)); + }, + + // Mark a navigation request as consumed + consumeTabNavigation: (tabId: string, requestId: string) => { + const { paneLayout } = get(); + const newLayout = updateTabInLayout(paneLayout, tabId, (tab) => + tab.pendingNavigation?.id === requestId + ? { ...tab, pendingNavigation: undefined, lastConsumedNavigationId: requestId } + : tab + ); + set(syncFromLayout(newLayout)); + }, + + // Save scroll position for a tab + saveTabScrollPosition: (tabId: string, scrollTop: number) => { + const { paneLayout } = get(); + const newLayout = updateTabInLayout(paneLayout, tabId, (tab) => ({ + ...tab, + savedScrollTop: scrollTop, + })); + set(syncFromLayout(newLayout)); + }, + + // Update a tab's label (used by sessionDetailSlice after fetching session data) + updateTabLabel: (tabId: string, label: string) => { + const { paneLayout } = get(); + const newLayout = updateTabInLayout(paneLayout, tabId, (tab) => ({ + ...tab, + label, + })); + set(syncFromLayout(newLayout)); + }, + + // Set context panel visibility for a specific tab + setTabContextPanelVisible: (tabId: string, visible: boolean) => { + const { paneLayout } = get(); + const newLayout = updateTabInLayout(paneLayout, tabId, (tab) => ({ + ...tab, + showContextPanel: visible, + })); + set(syncFromLayout(newLayout)); + }, + + // Set multi-selected tab IDs (within the focused pane) + setSelectedTabIds: (ids: string[]) => { + const { paneLayout } = get(); + const focusedPane = findPane(paneLayout, paneLayout.focusedPaneId); + if (!focusedPane) return; + + const updatedPane = { ...focusedPane, selectedTabIds: ids }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + }, + + // Clear multi-selection in the focused pane + clearTabSelection: () => { + const { paneLayout } = get(); + const focusedPane = findPane(paneLayout, paneLayout.focusedPaneId); + if (!focusedPane) return; + + const updatedPane = { ...focusedPane, selectedTabIds: [] }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + }, + + // Close all tabs except the specified one (within the pane containing the tab) + closeOtherTabs: (tabId: string) => { + const state = get(); + const { paneLayout } = state; + const pane = findPaneByTabId(paneLayout, tabId); + if (!pane) return; + + const tabsToClose = pane.tabs.filter((t) => t.id !== tabId); + for (const tab of tabsToClose) { + state.cleanupTabUIState(tab.id); + } + + const keepTab = pane.tabs.find((t) => t.id === tabId); + if (!keepTab) return; + + const updatedPane = { + ...pane, + tabs: [keepTab], + activeTabId: tabId, + selectedTabIds: [], + }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + + // Sync sidebar state for the remaining tab + get().setActiveTab(tabId); + }, + + // Close all tabs to the right (within the pane containing the tab) + closeTabsToRight: (tabId: string) => { + const state = get(); + const { paneLayout } = state; + const pane = findPaneByTabId(paneLayout, tabId); + if (!pane) return; + + const index = pane.tabs.findIndex((t) => t.id === tabId); + if (index === -1) return; + + const tabsToClose = pane.tabs.slice(index + 1); + for (const tab of tabsToClose) { + state.cleanupTabUIState(tab.id); + } + + const newTabs = pane.tabs.slice(0, index + 1); + const activeStillExists = newTabs.some((t) => t.id === pane.activeTabId); + const newActiveId = activeStillExists ? pane.activeTabId : tabId; + const updatedPane = { + ...pane, + tabs: newTabs, + activeTabId: newActiveId, + selectedTabIds: [], + }; + const newLayout = updatePane(paneLayout, updatedPane); + set(syncFromLayout(newLayout)); + + // Sync sidebar state for the active tab + if (newActiveId) { + get().setActiveTab(newActiveId); + } + }, + + // Close all tabs across all panes, reset to initial state + closeAllTabs: () => { + const state = get(); + const allTabs = getAllTabs(state.paneLayout); + for (const tab of allTabs) { + state.cleanupTabUIState(tab.id); + state.cleanupTabSessionData(tab.id); + } + + // Reset to single empty pane + const defaultPaneId = state.paneLayout.panes[0]?.id ?? 'pane-default'; + const newLayout: PaneLayout = { + panes: [ + { + id: defaultPaneId, + tabs: [], + activeTabId: null, + selectedTabIds: [], + widthFraction: 1, + }, + ], + focusedPaneId: defaultPaneId, + }; + + set({ + ...syncFromLayout(newLayout), + ...getFullResetState(), + }); + }, + + // Close multiple tabs by ID (within the pane containing them) + closeTabs: (tabIds: string[]) => { + const state = get(); + const idSet = new Set(tabIds); + + // Cleanup UI state and session data + for (const id of idSet) { + state.cleanupTabUIState(id); + state.cleanupTabSessionData(id); + } + + // Group tabs by pane for batch removal + let { paneLayout } = state; + const panesToRemove: string[] = []; + + for (const pane of paneLayout.panes) { + const remainingTabs = pane.tabs.filter((t) => !idSet.has(t.id)); + + if (remainingTabs.length === pane.tabs.length) continue; // No tabs removed from this pane + + if (remainingTabs.length === 0 && paneLayout.panes.length > 1) { + panesToRemove.push(pane.id); + continue; + } + + // Determine new active tab + let newActiveId = pane.activeTabId; + if (newActiveId && idSet.has(newActiveId)) { + const oldIndex = pane.tabs.findIndex((t) => t.id === newActiveId); + newActiveId = null; + for (let i = oldIndex; i < pane.tabs.length; i++) { + if (!idSet.has(pane.tabs[i].id)) { + newActiveId = pane.tabs[i].id; + break; + } + } + if (!newActiveId) { + for (let i = oldIndex - 1; i >= 0; i--) { + if (!idSet.has(pane.tabs[i].id)) { + newActiveId = pane.tabs[i].id; + break; + } + } + } + newActiveId = newActiveId ?? remainingTabs[0]?.id ?? null; + } + + paneLayout = updatePane(paneLayout, { + ...pane, + tabs: remainingTabs, + activeTabId: newActiveId, + selectedTabIds: pane.selectedTabIds.filter((id) => !idSet.has(id)), + }); + } + + // Check if ALL tabs are now gone + const allRemainingTabs = getAllTabs(paneLayout); + if (allRemainingTabs.length === 0) { + state.closeAllTabs(); + return; + } + + // Remove empty panes + for (const paneId of panesToRemove) { + paneLayout = removePaneHelper(paneLayout, paneId); + } + + set(syncFromLayout(paneLayout)); + + // Sync sidebar state for the new active tab + const newActiveTabId = get().activeTabId; + if (newActiveTabId) { + get().setActiveTab(newActiveTabId); + } + }, + + // Set active project and fetch its sessions + setActiveProject: (projectId: string) => { + set({ activeProjectId: projectId }); + get().selectProject(projectId); + }, + + // Navigate to a session (from search or other sources) + navigateToSession: ( + projectId: string, + sessionId: string, + fromSearch = false, + searchContext?: SearchNavigationContext + ) => { + const state = get(); + + // If different project, select it first + if (state.selectedProjectId !== projectId) { + state.selectProject(projectId); + } + + // Check if session tab is already open in any pane + const allTabs = getAllTabs(state.paneLayout); + const existingTab = + findTabBySessionAndProject(allTabs, sessionId, projectId) ?? + findTabBySession(allTabs, sessionId); + + if (existingTab) { + // Focus existing tab via setActiveTab for proper sidebar sync + state.setActiveTab(existingTab.id); + + // Enqueue search navigation if search context provided + if (searchContext) { + const searchPayload = { + query: searchContext.query, + messageTimestamp: searchContext.messageTimestamp, + matchedText: searchContext.matchedText, + ...(searchContext.targetGroupId !== undefined + ? { targetGroupId: searchContext.targetGroupId } + : {}), + ...(searchContext.targetMatchIndexInItem !== undefined + ? { targetMatchIndexInItem: searchContext.targetMatchIndexInItem } + : {}), + ...(searchContext.targetMatchStartOffset !== undefined + ? { targetMatchStartOffset: searchContext.targetMatchStartOffset } + : {}), + ...(searchContext.targetMessageUuid !== undefined + ? { targetMessageUuid: searchContext.targetMessageUuid } + : {}), + }; + const navRequest = createSearchNavigationRequest({ + ...searchPayload, + }); + state.enqueueTabNavigation(existingTab.id, navRequest); + } + } else { + // Open the session in a new tab + state.openTab({ + type: 'session', + label: 'Loading...', + projectId, + sessionId, + fromSearch, + }); + + // Enqueue search navigation on the newly created tab + if (searchContext) { + const newState = get(); + const newTabId = newState.activeTabId; + if (newTabId) { + const searchPayload = { + query: searchContext.query, + messageTimestamp: searchContext.messageTimestamp, + matchedText: searchContext.matchedText, + ...(searchContext.targetGroupId !== undefined + ? { targetGroupId: searchContext.targetGroupId } + : {}), + ...(searchContext.targetMatchIndexInItem !== undefined + ? { targetMatchIndexInItem: searchContext.targetMatchIndexInItem } + : {}), + ...(searchContext.targetMatchStartOffset !== undefined + ? { targetMatchStartOffset: searchContext.targetMatchStartOffset } + : {}), + ...(searchContext.targetMessageUuid !== undefined + ? { targetMessageUuid: searchContext.targetMessageUuid } + : {}), + }; + const navRequest = createSearchNavigationRequest({ + ...searchPayload, + }); + state.enqueueTabNavigation(newTabId, navRequest); + } + } + + // Fetch session detail for the new tab (with tabId for per-tab data) + const newTabIdForFetch = get().activeTabId ?? undefined; + void state.fetchSessionDetail(projectId, sessionId, newTabIdForFetch); + } + + // If opened from search, clear sidebar selection to deselect + if (fromSearch) { + set({ selectedSessionId: null }); + } + }, +}); diff --git a/src/renderer/store/slices/tabUISlice.ts b/src/renderer/store/slices/tabUISlice.ts new file mode 100644 index 00000000..0f43588d --- /dev/null +++ b/src/renderer/store/slices/tabUISlice.ts @@ -0,0 +1,319 @@ +/** + * Tab UI slice - manages per-tab UI state (expansion states, scroll positions, etc.) + * + * This slice provides COMPLETE isolation of UI state between tabs. Each tab has its + * own independent state for: + * - AI group expansion (collapsed/expanded) + * - Display item expansion within AI groups + * - Subagent trace expansion + * - Context panel visibility + * - Scroll position + * + * The state is keyed by tabId, so opening the same session in two tabs gives each + * tab its own independent UI state. + */ + +import type { AppState } from '../types'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Types +// ============================================================================= + +/** + * UI state for a single tab. + * All values are optional - defaults are applied when reading. + */ +export interface TabUIState { + /** Which AI groups are expanded (by aiGroupId) */ + expandedAIGroupIds: Set; + + /** Which display items within AI groups are expanded: Map> */ + expandedDisplayItemIds: Map>; + + /** Which subagent traces are manually expanded (by subagentId) */ + expandedSubagentTraceIds: Set; + + /** Whether the context panel is visible */ + showContextPanel: boolean; + + /** Selected context phase for filtering (null = current/latest phase) */ + selectedContextPhase: number | null; + + /** Saved scroll position for restoring when switching back to this tab */ + savedScrollTop?: number; +} + +/** + * Creates a default/empty TabUIState. + */ +function createDefaultTabUIState(): TabUIState { + return { + expandedAIGroupIds: new Set(), + expandedDisplayItemIds: new Map(), + expandedSubagentTraceIds: new Set(), + showContextPanel: false, + selectedContextPhase: null, + savedScrollTop: undefined, + }; +} + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface TabUISlice { + /** Per-tab UI states: Map */ + tabUIStates: Map; + + // Initialization & cleanup + /** Initialize UI state for a new tab */ + initTabUIState: (tabId: string) => void; + /** Clean up UI state when a tab is closed */ + cleanupTabUIState: (tabId: string) => void; + + // AI Group expansion (per-tab) + /** Toggle AI group expansion for a specific tab */ + toggleAIGroupExpansionForTab: (tabId: string, aiGroupId: string) => void; + /** Check if AI group is expanded for a specific tab */ + isAIGroupExpandedForTab: (tabId: string, aiGroupId: string) => boolean; + /** Expand AI group for a specific tab (for auto-expand scenarios) */ + expandAIGroupForTab: (tabId: string, aiGroupId: string) => void; + + // Display item expansion (per-tab) + /** Toggle display item expansion within an AI group for a specific tab */ + toggleDisplayItemExpansionForTab: (tabId: string, aiGroupId: string, itemId: string) => void; + /** Get expanded display item IDs for an AI group in a specific tab */ + getExpandedDisplayItemIdsForTab: (tabId: string, aiGroupId: string) => Set; + /** Expand a display item for a specific tab (for auto-expand scenarios) */ + expandDisplayItemForTab: (tabId: string, aiGroupId: string, itemId: string) => void; + + // Subagent trace expansion (per-tab) + /** Toggle subagent trace expansion for a specific tab */ + toggleSubagentTraceExpansionForTab: (tabId: string, subagentId: string) => void; + /** Expand subagent trace for a specific tab (no-op if already expanded) */ + expandSubagentTraceForTab: (tabId: string, subagentId: string) => void; + /** Check if subagent trace is expanded for a specific tab */ + isSubagentTraceExpandedForTab: (tabId: string, subagentId: string) => boolean; + + // Context panel (per-tab) + /** Set context panel visibility for a specific tab */ + setContextPanelVisibleForTab: (tabId: string, visible: boolean) => void; + /** Get context panel visibility for a specific tab */ + isContextPanelVisibleForTab: (tabId: string) => boolean; + + // Context phase selection (per-tab) + /** Set the selected context phase for a specific tab */ + setSelectedContextPhaseForTab: (tabId: string, phase: number | null) => void; + + // Scroll position (per-tab) + /** Save scroll position for a specific tab */ + saveScrollPositionForTab: (tabId: string, scrollTop: number) => void; + /** Get saved scroll position for a specific tab */ + getScrollPositionForTab: (tabId: string) => number | undefined; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createTabUISlice: StateCreator = (set, get) => ({ + tabUIStates: new Map(), + + // ========================================================================== + // Initialization & Cleanup + // ========================================================================== + + initTabUIState: (tabId: string) => { + const state = get(); + if (state.tabUIStates.has(tabId)) return; // Already initialized + + const newMap = new Map(state.tabUIStates); + newMap.set(tabId, createDefaultTabUIState()); + set({ tabUIStates: newMap }); + }, + + cleanupTabUIState: (tabId: string) => { + const state = get(); + if (!state.tabUIStates.has(tabId)) return; + + const newMap = new Map(state.tabUIStates); + newMap.delete(tabId); + set({ tabUIStates: newMap }); + }, + + // ========================================================================== + // AI Group Expansion + // ========================================================================== + + toggleAIGroupExpansionForTab: (tabId: string, aiGroupId: string) => { + const state = get(); + const newMap = new Map(state.tabUIStates); + const tabState = newMap.get(tabId) ?? createDefaultTabUIState(); + + const newExpandedIds = new Set(tabState.expandedAIGroupIds); + if (newExpandedIds.has(aiGroupId)) { + newExpandedIds.delete(aiGroupId); + } else { + newExpandedIds.add(aiGroupId); + } + + newMap.set(tabId, { ...tabState, expandedAIGroupIds: newExpandedIds }); + set({ tabUIStates: newMap }); + }, + + isAIGroupExpandedForTab: (tabId: string, aiGroupId: string) => { + const tabState = get().tabUIStates.get(tabId); + return tabState?.expandedAIGroupIds.has(aiGroupId) ?? false; + }, + + expandAIGroupForTab: (tabId: string, aiGroupId: string) => { + const state = get(); + const tabState = state.tabUIStates.get(tabId); + if (tabState?.expandedAIGroupIds.has(aiGroupId)) return; // Already expanded + + const newMap = new Map(state.tabUIStates); + const currentTabState = newMap.get(tabId) ?? createDefaultTabUIState(); + + const newExpandedIds = new Set(currentTabState.expandedAIGroupIds); + newExpandedIds.add(aiGroupId); + + newMap.set(tabId, { ...currentTabState, expandedAIGroupIds: newExpandedIds }); + set({ tabUIStates: newMap }); + }, + + // ========================================================================== + // Display Item Expansion + // ========================================================================== + + toggleDisplayItemExpansionForTab: (tabId: string, aiGroupId: string, itemId: string) => { + const state = get(); + const newMap = new Map(state.tabUIStates); + const tabState = newMap.get(tabId) ?? createDefaultTabUIState(); + + const newDisplayItemMap = new Map(tabState.expandedDisplayItemIds); + const currentSet = newDisplayItemMap.get(aiGroupId) ?? new Set(); + const newSet = new Set(currentSet); + + if (newSet.has(itemId)) { + newSet.delete(itemId); + } else { + newSet.add(itemId); + } + + newDisplayItemMap.set(aiGroupId, newSet); + newMap.set(tabId, { ...tabState, expandedDisplayItemIds: newDisplayItemMap }); + set({ tabUIStates: newMap }); + }, + + getExpandedDisplayItemIdsForTab: (tabId: string, aiGroupId: string) => { + const tabState = get().tabUIStates.get(tabId); + return tabState?.expandedDisplayItemIds.get(aiGroupId) ?? new Set(); + }, + + expandDisplayItemForTab: (tabId: string, aiGroupId: string, itemId: string) => { + const state = get(); + const tabState = state.tabUIStates.get(tabId); + const currentSet = tabState?.expandedDisplayItemIds.get(aiGroupId); + if (currentSet?.has(itemId)) return; // Already expanded + + const newMap = new Map(state.tabUIStates); + const currentTabState = newMap.get(tabId) ?? createDefaultTabUIState(); + + const newDisplayItemMap = new Map(currentTabState.expandedDisplayItemIds); + const newSet = new Set(newDisplayItemMap.get(aiGroupId) ?? new Set()); + newSet.add(itemId); + newDisplayItemMap.set(aiGroupId, newSet); + + newMap.set(tabId, { ...currentTabState, expandedDisplayItemIds: newDisplayItemMap }); + set({ tabUIStates: newMap }); + }, + + // ========================================================================== + // Subagent Trace Expansion + // ========================================================================== + + toggleSubagentTraceExpansionForTab: (tabId: string, subagentId: string) => { + const state = get(); + const newMap = new Map(state.tabUIStates); + const tabState = newMap.get(tabId) ?? createDefaultTabUIState(); + + const newExpandedIds = new Set(tabState.expandedSubagentTraceIds); + if (newExpandedIds.has(subagentId)) { + newExpandedIds.delete(subagentId); + } else { + newExpandedIds.add(subagentId); + } + + newMap.set(tabId, { ...tabState, expandedSubagentTraceIds: newExpandedIds }); + set({ tabUIStates: newMap }); + }, + + expandSubagentTraceForTab: (tabId: string, subagentId: string) => { + const state = get(); + const tabState = state.tabUIStates.get(tabId) ?? createDefaultTabUIState(); + + // No-op if already expanded + if (tabState.expandedSubagentTraceIds.has(subagentId)) return; + + const newExpandedIds = new Set(tabState.expandedSubagentTraceIds); + newExpandedIds.add(subagentId); + + const newMap = new Map(state.tabUIStates); + newMap.set(tabId, { ...tabState, expandedSubagentTraceIds: newExpandedIds }); + set({ tabUIStates: newMap }); + }, + + isSubagentTraceExpandedForTab: (tabId: string, subagentId: string) => { + const tabState = get().tabUIStates.get(tabId); + return tabState?.expandedSubagentTraceIds.has(subagentId) ?? false; + }, + + // ========================================================================== + // Context Panel + // ========================================================================== + + setContextPanelVisibleForTab: (tabId: string, visible: boolean) => { + const state = get(); + const newMap = new Map(state.tabUIStates); + const tabState = newMap.get(tabId) ?? createDefaultTabUIState(); + + newMap.set(tabId, { ...tabState, showContextPanel: visible }); + set({ tabUIStates: newMap }); + }, + + isContextPanelVisibleForTab: (tabId: string) => { + const tabState = get().tabUIStates.get(tabId); + return tabState?.showContextPanel ?? false; + }, + + // ========================================================================== + // Context Phase Selection + // ========================================================================== + + setSelectedContextPhaseForTab: (tabId: string, phase: number | null) => { + const state = get(); + const newMap = new Map(state.tabUIStates); + const tabState = newMap.get(tabId) ?? createDefaultTabUIState(); + newMap.set(tabId, { ...tabState, selectedContextPhase: phase }); + set({ tabUIStates: newMap }); + }, + + // ========================================================================== + // Scroll Position + // ========================================================================== + + saveScrollPositionForTab: (tabId: string, scrollTop: number) => { + const state = get(); + const newMap = new Map(state.tabUIStates); + const tabState = newMap.get(tabId) ?? createDefaultTabUIState(); + + newMap.set(tabId, { ...tabState, savedScrollTop: scrollTop }); + set({ tabUIStates: newMap }); + }, + + getScrollPositionForTab: (tabId: string) => { + const tabState = get().tabUIStates.get(tabId); + return tabState?.savedScrollTop; + }, +}); diff --git a/src/renderer/store/slices/uiSlice.ts b/src/renderer/store/slices/uiSlice.ts new file mode 100644 index 00000000..8543b507 --- /dev/null +++ b/src/renderer/store/slices/uiSlice.ts @@ -0,0 +1,45 @@ +/** + * UI slice - manages command palette and sidebar state. + */ + +import type { AppState } from '../types'; +import type { StateCreator } from 'zustand'; + +// ============================================================================= +// Slice Interface +// ============================================================================= + +export interface UISlice { + // State + commandPaletteOpen: boolean; + sidebarCollapsed: boolean; + + // Actions + openCommandPalette: () => void; + closeCommandPalette: () => void; + toggleSidebar: () => void; +} + +// ============================================================================= +// Slice Creator +// ============================================================================= + +export const createUISlice: StateCreator = (set) => ({ + // Initial state + commandPaletteOpen: false, + sidebarCollapsed: false, + + // Command palette actions + openCommandPalette: () => { + set({ commandPaletteOpen: true }); + }, + + closeCommandPalette: () => { + set({ commandPaletteOpen: false }); + }, + + // Sidebar actions + toggleSidebar: () => { + set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })); + }, +}); diff --git a/src/renderer/store/types.ts b/src/renderer/store/types.ts new file mode 100644 index 00000000..de0bb833 --- /dev/null +++ b/src/renderer/store/types.ts @@ -0,0 +1,87 @@ +/** + * Store type definitions. + * Contains the combined AppState interface and shared types used across slices. + */ + +import type { ConfigSlice } from './slices/configSlice'; +import type { ConversationSlice } from './slices/conversationSlice'; +import type { NotificationSlice } from './slices/notificationSlice'; +import type { PaneSlice } from './slices/paneSlice'; +import type { ProjectSlice } from './slices/projectSlice'; +import type { RepositorySlice } from './slices/repositorySlice'; +import type { SessionDetailSlice } from './slices/sessionDetailSlice'; +import type { SessionSlice } from './slices/sessionSlice'; +import type { SubagentSlice } from './slices/subagentSlice'; +import type { TabSlice } from './slices/tabSlice'; +import type { TabUISlice } from './slices/tabUISlice'; +import type { UISlice } from './slices/uiSlice'; + +// ============================================================================= +// Shared Types +// ============================================================================= + +/** + * Breadcrumb item for subagent drill-down navigation. + */ +export interface BreadcrumbItem { + id: string; + description: string; +} + +/** + * Represents a single search match in the conversation. + * Only searches: user message text and AI lastOutput text (not tool results, thinking, or subagents) + */ +export interface SearchMatch { + /** ID of the chat item containing this match */ + itemId: string; + /** Type of item ('user' | 'ai') - system items are not searched */ + itemType: 'user' | 'ai'; + /** Which match within this item (0-based) */ + matchIndexInItem: number; + /** Global index across all matches */ + globalIndex: number; + /** Display item ID within the AI group (e.g., "lastOutput") */ + displayItemId?: string; +} + +/** + * Search context for navigating from Command Palette results. + */ +export interface SearchNavigationContext { + /** The search query */ + query: string; + /** Timestamp of the message containing the search match */ + messageTimestamp: number; + /** The matched text */ + matchedText: string; + /** Optional exact target group ID (e.g., "user-..." or "ai-...") */ + targetGroupId?: string; + /** Optional exact match index within the target group's searchable text */ + targetMatchIndexInItem?: number; + /** Optional character offset of the match in the searchable text */ + targetMatchStartOffset?: number; + /** Optional source message UUID for diagnostics/fallback mapping */ + targetMessageUuid?: string; +} + +// ============================================================================= +// Combined AppState Type +// ============================================================================= + +/** + * Combined application state type. + * Combines all slice interfaces into a single unified state type. + */ +export type AppState = ProjectSlice & + RepositorySlice & + SessionSlice & + SessionDetailSlice & + SubagentSlice & + ConversationSlice & + TabSlice & + TabUISlice & + PaneSlice & + UISlice & + NotificationSlice & + ConfigSlice; diff --git a/src/renderer/store/utils/paneHelpers.ts b/src/renderer/store/utils/paneHelpers.ts new file mode 100644 index 00000000..e6e2164b --- /dev/null +++ b/src/renderer/store/utils/paneHelpers.ts @@ -0,0 +1,134 @@ +/** + * Pure utility functions for immutable pane manipulation. + * All functions return new objects (no mutation). + */ + +import type { Pane, PaneLayout } from '@renderer/types/panes'; +import type { Tab } from '@renderer/types/tabs'; + +/** + * Find a pane by its ID. + */ +export function findPane(layout: PaneLayout, paneId: string): Pane | undefined { + return layout.panes.find((p) => p.id === paneId); +} + +/** + * Find which pane contains a given tab. + */ +export function findPaneByTabId(layout: PaneLayout, tabId: string): Pane | undefined { + return layout.panes.find((p) => p.tabs.some((t) => t.id === tabId)); +} + +/** + * Replace a pane immutably in the layout. + */ +export function updatePane(layout: PaneLayout, updatedPane: Pane): PaneLayout { + return { + ...layout, + panes: layout.panes.map((p) => (p.id === updatedPane.id ? updatedPane : p)), + }; +} + +/** + * Remove a pane and redistribute its width to a neighbor. + * If removing the focused pane, focus shifts to the nearest neighbor. + */ +export function removePane(layout: PaneLayout, paneId: string): PaneLayout { + const index = layout.panes.findIndex((p) => p.id === paneId); + if (index === -1 || layout.panes.length <= 1) return layout; + + const removedPane = layout.panes[index]; + const newPanes = layout.panes.filter((p) => p.id !== paneId); + + // Redistribute width to the nearest neighbor + const neighborIndex = index > 0 ? index - 1 : 0; + const redistributed = newPanes.map((p, i) => + i === neighborIndex ? { ...p, widthFraction: p.widthFraction + removedPane.widthFraction } : p + ); + + // Equalize to avoid floating point drift + const equalized = redistributeWidths(redistributed); + + // Update focus if the removed pane was focused + let newFocusedId = layout.focusedPaneId; + if (layout.focusedPaneId === paneId) { + const focusTarget = equalized[Math.min(index, equalized.length - 1)]; + newFocusedId = focusTarget.id; + } + + return { + panes: equalized, + focusedPaneId: newFocusedId, + }; +} + +/** + * Insert a new pane adjacent to an existing pane. + */ +export function insertPane( + layout: PaneLayout, + adjacentPaneId: string, + newPane: Pane, + direction: 'left' | 'right' +): PaneLayout { + const index = layout.panes.findIndex((p) => p.id === adjacentPaneId); + if (index === -1) return layout; + + const insertAt = direction === 'right' ? index + 1 : index; + const newPanes = [...layout.panes]; + newPanes.splice(insertAt, 0, newPane); + + return { + ...layout, + panes: redistributeWidths(newPanes), + }; +} + +/** + * Equalize widths across all panes so they sum to 1. + */ +function redistributeWidths(panes: Pane[]): Pane[] { + if (panes.length === 0) return panes; + const fraction = 1 / panes.length; + return panes.map((p) => ({ ...p, widthFraction: fraction })); +} + +/** + * Extract the focused pane's tab state for root-level sync. + */ +export function syncFocusedPaneState(layout: PaneLayout): { + openTabs: Tab[]; + activeTabId: string | null; + selectedTabIds: string[]; +} { + const focused = findPane(layout, layout.focusedPaneId); + if (!focused) { + return { openTabs: [], activeTabId: null, selectedTabIds: [] }; + } + return { + openTabs: focused.tabs, + activeTabId: focused.activeTabId, + selectedTabIds: focused.selectedTabIds, + }; +} + +/** + * Get all tabs across all panes (flat list). + */ +export function getAllTabs(layout: PaneLayout): Tab[] { + return layout.panes.flatMap((p) => p.tabs); +} + +/** + * Create a new empty pane with a unique ID. + */ +export function createEmptyPane(id: string): Pane { + return { + id, + tabs: [], + activeTabId: null, + selectedTabIds: [], + widthFraction: 0, + }; +} diff --git a/src/renderer/store/utils/pathResolution.ts b/src/renderer/store/utils/pathResolution.ts new file mode 100644 index 00000000..7d520af8 --- /dev/null +++ b/src/renderer/store/utils/pathResolution.ts @@ -0,0 +1,121 @@ +/** + * Path resolution utilities for the store. + */ + +/** + * Resolves a relative path against a base path, handling various path formats. + * Handles: + * - Absolute paths: /full/path/file.tsx (returned as-is) + * - Relative paths with ./: ./apps/foo/bar.tsx (strips ./) + * - Parent paths with ../: ../other/file.tsx (walks up directories) + * - Plain paths: apps/foo/bar.tsx (joins with base) + * - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins) + */ +export function resolveFilePath(base: string, relativePath: string): string { + // If already absolute, return as-is + if (isAbsolutePath(relativePath)) { + return relativePath; + } + + const cleanBase = trimTrailingSeparator(base); + + // Handle @ prefix (file mention marker) - strip it if present + let cleanRelative = relativePath; + if (cleanRelative.startsWith('@')) { + cleanRelative = cleanRelative.slice(1); + } + + // Tilde paths (~/) are home-relative absolute paths - pass through as-is + // The main process will expand ~ to the actual home directory + if (cleanRelative.startsWith('~/') || cleanRelative.startsWith('~\\') || cleanRelative === '~') { + return cleanRelative; + } + + // Handle ./ prefix (current directory) + if (cleanRelative.startsWith('./')) { + cleanRelative = cleanRelative.slice(2); + } + + // Handle ../ prefixes (parent directory) + const separator = cleanBase.includes('\\') ? '\\' : '/'; + const hasUnixRoot = cleanBase.startsWith('/'); + const hasUncRoot = cleanBase.startsWith('\\\\'); + const normalizedRelative = normalizeSeparators(cleanRelative, separator); + const baseParts = splitPath(cleanBase); + let remainingRelative = normalizedRelative; + + while (remainingRelative.startsWith(`..${separator}`)) { + remainingRelative = remainingRelative.slice(3); + if (baseParts.length > 1) { + baseParts.pop(); + } + } + + // Join the normalized paths + let normalizedBase = baseParts.join(separator); + if (hasUnixRoot && !normalizedBase.startsWith('/')) { + normalizedBase = `/${normalizedBase}`; + } + if (hasUncRoot && !normalizedBase.startsWith('\\\\')) { + normalizedBase = `\\\\${normalizedBase}`; + } + return remainingRelative ? `${normalizedBase}${separator}${remainingRelative}` : normalizedBase; +} + +function isAbsolutePath(input: string): boolean { + return input.startsWith('/') || input.startsWith('\\\\') || /^[a-zA-Z]:[\\/]/.test(input); +} + +function trimTrailingSeparator(input: string): string { + let end = input.length; + while (end > 0) { + const char = input[end - 1]; + if (char !== '/' && char !== '\\') { + break; + } + end--; + } + return input.slice(0, end); +} + +function normalizeSeparators(input: string, separator: '/' | '\\'): string { + let output = ''; + let prevWasSeparator = false; + + for (const char of input) { + const isSeparator = char === '/' || char === '\\'; + if (isSeparator) { + if (!prevWasSeparator) { + output += separator; + } + prevWasSeparator = true; + } else { + output += char; + prevWasSeparator = false; + } + } + + return output; +} + +function splitPath(input: string): string[] { + const parts: string[] = []; + let current = ''; + + for (const char of input) { + if (char === '/' || char === '\\') { + if (current.length > 0) { + parts.push(current); + current = ''; + } + } else { + current += char; + } + } + + if (current.length > 0) { + parts.push(current); + } + + return parts; +} diff --git a/src/renderer/store/utils/stateResetHelpers.ts b/src/renderer/store/utils/stateResetHelpers.ts new file mode 100644 index 00000000..7fdc4a0b --- /dev/null +++ b/src/renderer/store/utils/stateResetHelpers.ts @@ -0,0 +1,43 @@ +/** + * Shared state reset helpers to eliminate duplicated reset blocks across slices. + * + * These return partial state objects that can be spread into Zustand `set()` calls. + */ + +import type { AppState } from '../types'; + +/** + * Reset session-related state (sessions list, detail, pagination, context stats). + * Used when switching projects, worktrees, or repositories. + */ +export function getSessionResetState(): Partial { + return { + selectedSessionId: null, + sessionDetail: null, + sessionContextStats: null, + sessions: [], + sessionsError: null, + sessionsCursor: null, + sessionsHasMore: false, + sessionsTotalCount: 0, + sessionsLoadingMore: false, + }; +} + +/** + * Full state reset (session + project + repository + conversation). + * Used when closing all tabs or resetting to initial state. + */ +export function getFullResetState(): Partial { + return { + ...getSessionResetState(), + selectedRepositoryId: null, + selectedWorktreeId: null, + selectedProjectId: null, + activeProjectId: null, + conversation: null, + visibleAIGroupId: null, + selectedAIGroup: null, + sessionClaudeMdStats: null, + }; +} diff --git a/src/renderer/types/api.ts b/src/renderer/types/api.ts new file mode 100644 index 00000000..82d3f0e0 --- /dev/null +++ b/src/renderer/types/api.ts @@ -0,0 +1,8 @@ +/** + * IPC API type definitions for Electron preload bridge. + * + * Re-exports types from shared for backwards compatibility. + * The canonical definitions are in @shared/types/api. + */ + +export { type ClaudeMdFileInfo } from '@shared/types'; diff --git a/src/renderer/types/claudeMd.ts b/src/renderer/types/claudeMd.ts new file mode 100644 index 00000000..9af32b7d --- /dev/null +++ b/src/renderer/types/claudeMd.ts @@ -0,0 +1,74 @@ +/** + * Type definitions for CLAUDE.md injection tracking. + * Tracks system context injections from various sources throughout the session. + */ + +// ============================================================================= +// Source Types +// ============================================================================= + +/** + * Source types for CLAUDE.md injections. + * - enterprise: Enterprise-level configuration + * - user-memory: User's global memory settings (~/.claude/CLAUDE.md) + * - project-memory: Project-level memory + * - project-rules: Project rules configuration + * - project-local: Local project CLAUDE.md (checked into codebase) + * - directory: Directory-specific CLAUDE.md files + */ +export type ClaudeMdSource = + | 'enterprise' + | 'user-memory' + | 'user-rules' + | 'auto-memory' + | 'project-memory' + | 'project-rules' + | 'project-local' + | 'directory'; + +// ============================================================================= +// Injection Types +// ============================================================================= + +/** + * Represents a single CLAUDE.md injection detected in the session. + */ +export interface ClaudeMdInjection { + /** Unique identifier for this injection */ + id: string; + /** File path of the CLAUDE.md source */ + path: string; + /** Source type categorization */ + source: ClaudeMdSource; + /** Human-readable display name */ + displayName: string; + /** Whether this is a global (user-level) injection */ + isGlobal: boolean; + /** Estimated token count (chars / 4) */ + estimatedTokens: number; + /** ID of the AI group where this injection was first seen */ + firstSeenInGroup: string; +} + +// ============================================================================= +// Statistics Types +// ============================================================================= + +/** + * Statistics about CLAUDE.md injections for an AI group. + * Tracks both new injections in the current group and accumulated totals. + */ +export interface ClaudeMdStats { + /** Injections that are new in THIS group */ + newInjections: ClaudeMdInjection[]; + /** All injections accumulated up to and including this group */ + accumulatedInjections: ClaudeMdInjection[]; + /** Total estimated tokens from all accumulated injections */ + totalEstimatedTokens: number; + /** Percentage of context window used (vs input tokens) */ + percentageOfContext: number; + /** Count of new injections in this group */ + newCount: number; + /** Total count of accumulated injections */ + accumulatedCount: number; +} diff --git a/src/renderer/types/contextInjection.ts b/src/renderer/types/contextInjection.ts new file mode 100644 index 00000000..3ad3be56 --- /dev/null +++ b/src/renderer/types/contextInjection.ts @@ -0,0 +1,307 @@ +/** + * Type definitions for unified context injection tracking. + * Extends CLAUDE.md tracking to include mentioned files (@mentions) and tool outputs. + * This provides a comprehensive view of all context sources injected into the conversation. + */ + +import type { ClaudeMdInjection } from './claudeMd'; + +// ============================================================================= +// Constants +// ============================================================================= + +/** + * Maximum tokens to estimate for a mentioned file. + * Files larger than this are capped to prevent unrealistic token estimates. + */ +export const MAX_MENTIONED_FILE_TOKENS = 25000; + +// ============================================================================= +// Mentioned File Types +// ============================================================================= + +/** + * Represents a file mentioned via @-mention that was injected into context. + * Tracks the file path, token estimate, and where it first appeared in the session. + */ +export interface MentionedFileInjection { + /** Unique identifier for this injection */ + id: string; + /** Discriminator for type narrowing */ + category: 'mentioned-file'; + /** Absolute file path of the mentioned file */ + path: string; + /** Relative path or filename for display purposes */ + displayName: string; + /** Estimated token count for this file's content */ + estimatedTokens: number; + /** Turn index where this file was first mentioned */ + firstSeenTurnIndex: number; + /** AI group ID (e.g., "ai-0") where this file was first seen, for navigation */ + firstSeenInGroup: string; + /** Whether the file exists on disk */ + exists: boolean; +} + +/** + * Information about a mentioned file returned from IPC. + * Used to get file metadata before creating a MentionedFileInjection. + */ +export interface MentionedFileInfo { + /** Absolute file path */ + path: string; + /** Whether the file exists on disk */ + exists: boolean; + /** Character count of file content */ + charCount: number; + /** Estimated token count (typically charCount / 4) */ + estimatedTokens: number; +} + +// ============================================================================= +// Tool Output Types +// ============================================================================= + +/** + * Breakdown of tokens contributed by a single tool in a turn. + */ +export interface ToolTokenBreakdown { + /** Name of the tool (e.g., "Read", "Grep", "Bash") */ + toolName: string; + /** Number of tokens in the tool's output */ + tokenCount: number; + /** Whether the tool execution resulted in an error */ + isError: boolean; +} + +/** + * Represents aggregated tool output context for a single AI turn. + * Multiple tools may execute in one turn; this aggregates their token contributions. + */ +export interface ToolOutputInjection { + /** Unique identifier (e.g., "tool-output-ai-0") */ + id: string; + /** Discriminator for type narrowing */ + category: 'tool-output'; + /** Turn index where these tool outputs occurred */ + turnIndex: number; + /** AI group ID for navigation (e.g., "ai-0") */ + aiGroupId: string; + /** Total estimated tokens from all tools in this turn */ + estimatedTokens: number; + /** Number of tools that contributed output */ + toolCount: number; + /** Detailed breakdown of tokens by individual tool */ + toolBreakdown: ToolTokenBreakdown[]; +} + +// ============================================================================= +// Thinking/Text Output Types +// ============================================================================= + +/** + * Breakdown of thinking vs text tokens within a turn. + */ +export interface ThinkingTextBreakdown { + /** Type of content */ + type: 'thinking' | 'text'; + /** Estimated token count */ + tokenCount: number; +} + +/** + * Thinking and Text output token injection for a single turn. + * Aggregates all thinking blocks and text outputs within one AI response turn. + */ +export interface ThinkingTextInjection { + /** Unique identifier (e.g., "thinking-text-ai-0") */ + id: string; + /** Discriminator for type narrowing */ + category: 'thinking-text'; + /** Turn index where this content occurred */ + turnIndex: number; + /** AI group ID for navigation (e.g., "ai-0") */ + aiGroupId: string; + /** Total estimated tokens from thinking + text in this turn */ + estimatedTokens: number; + /** Detailed breakdown of thinking vs text tokens */ + breakdown: ThinkingTextBreakdown[]; +} + +// ============================================================================= +// User Message Types +// ============================================================================= + +/** + * Represents a user message injected into context for a single turn. + * User prompts are a real part of the context window — tracking them + * provides a more complete picture of what consumes tokens. + */ +export interface UserMessageInjection { + /** Unique identifier (e.g., "user-msg-ai-0") */ + id: string; + /** Discriminator for type narrowing */ + category: 'user-message'; + /** Turn index where this user message occurred */ + turnIndex: number; + /** AI group ID for navigation (e.g., "ai-0") */ + aiGroupId: string; + /** Estimated token count for the user message content */ + estimatedTokens: number; + /** First ~80 characters of the message for preview */ + textPreview: string; +} + +// ============================================================================= +// Task Coordination Types +// ============================================================================= + +/** + * Breakdown of tokens contributed by a single task coordination item. + */ +export interface TaskCoordinationBreakdown { + /** Type of task coordination item */ + type: 'teammate-message' | 'send-message' | 'task-tool'; + /** Tool name (e.g., "TeamCreate", "TaskCreate", "SendMessage") */ + toolName?: string; + /** Estimated token count */ + tokenCount: number; + /** Display label (e.g., teammate name, "TaskCreate #3") */ + label: string; +} + +/** + * Represents aggregated task coordination context for a single AI turn. + * Tracks SendMessage, TeamCreate, TaskCreate, and other task tools separately + * from generic tool outputs. + */ +export interface TaskCoordinationInjection { + /** Unique identifier (e.g., "task-coord-ai-0") */ + id: string; + /** Discriminator for type narrowing */ + category: 'task-coordination'; + /** Turn index where these task coordination items occurred */ + turnIndex: number; + /** AI group ID for navigation (e.g., "ai-0") */ + aiGroupId: string; + /** Total estimated tokens from all task coordination items in this turn */ + estimatedTokens: number; + /** Detailed breakdown of tokens by individual item */ + breakdown: TaskCoordinationBreakdown[]; +} + +// ============================================================================= +// Union Types +// ============================================================================= + +/** + * Extended ClaudeMdInjection with category discriminator for union compatibility. + */ +export type ClaudeMdContextInjection = ClaudeMdInjection & { category: 'claude-md' }; + +/** + * Discriminated union of all context injection types. + * Use the `category` field to narrow the type: + * - 'claude-md': CLAUDE.md configuration injections + * - 'mentioned-file': User @-mentioned file injections + * - 'tool-output': Tool execution output injections + * - 'thinking-text': Thinking and text output token injections + * - 'task-coordination': Task coordination tool and message injections + * - 'user-message': User message prompt injections + */ +export type ContextInjection = + | ClaudeMdContextInjection + | MentionedFileInjection + | ToolOutputInjection + | ThinkingTextInjection + | TaskCoordinationInjection + | UserMessageInjection; + +// ============================================================================= +// Statistics Types +// ============================================================================= + +/** + * Token counts broken down by context source category. + */ +export interface TokensByCategory { + /** Tokens from CLAUDE.md injections */ + claudeMd: number; + /** Tokens from mentioned files */ + mentionedFiles: number; + /** Tokens from tool outputs */ + toolOutputs: number; + /** Tokens from thinking blocks and text outputs */ + thinkingText: number; + /** Tokens from task coordination (SendMessage, TeamCreate, TaskCreate, etc.) */ + taskCoordination: number; + /** Tokens from user messages */ + userMessages: number; +} + +/** + * Counts of new injections broken down by context source category. + */ +export interface NewCountsByCategory { + /** Count of new CLAUDE.md injections */ + claudeMd: number; + /** Count of new mentioned file injections */ + mentionedFiles: number; + /** Count of new tool output injections */ + toolOutputs: number; + /** Count of new thinking/text injections */ + thinkingText: number; + /** Count of new task coordination injections */ + taskCoordination: number; + /** Count of new user message injections */ + userMessages: number; +} + +/** + * Comprehensive statistics about context injections for an AI group. + * Tracks both new injections in the current group and accumulated totals, + * with breakdowns by category. + */ +export interface ContextStats { + /** Injections that are new in THIS group */ + newInjections: ContextInjection[]; + /** All injections accumulated up to and including this group */ + accumulatedInjections: ContextInjection[]; + /** Total estimated tokens from all accumulated injections */ + totalEstimatedTokens: number; + /** Token counts broken down by category */ + tokensByCategory: TokensByCategory; + /** Counts of new injections in this group, by category */ + newCounts: NewCountsByCategory; + /** Which context phase this stats belongs to (1-based) */ + phaseNumber?: number; +} + +// ============================================================================= +// Context Phase Types +// ============================================================================= + +/** Token change at a compaction boundary */ +export interface CompactionTokenDelta { + preCompactionTokens: number; + postCompactionTokens: number; + delta: number; // negative = context freed +} + +/** Metadata about a single context phase */ +export interface ContextPhase { + phaseNumber: number; // 1-based + firstAIGroupId: string; + lastAIGroupId: string; + compactGroupId: string | null; // null for phase 1 + startTokens?: number; + endTokens?: number; +} + +/** Session-wide phase information */ +export interface ContextPhaseInfo { + phases: ContextPhase[]; + compactionCount: number; + aiGroupPhaseMap: Map; // aiGroupId → phaseNumber + compactionTokenDeltas: Map; // compactGroupId → delta +} diff --git a/src/renderer/types/data.ts b/src/renderer/types/data.ts new file mode 100644 index 00000000..79f5268a --- /dev/null +++ b/src/renderer/types/data.ts @@ -0,0 +1,136 @@ +/** + * Type definitions for the renderer process. + * + * This module re-exports types from the main process types and adds + * renderer-specific types and utilities. For most uses, import from + * the index.ts barrel file instead. + * + * Import hierarchy: + * - Main types: Domain models, JSONL format, parsed messages, chunks + * - Renderer types: API interfaces, notifications, visualization + */ + +// ============================================================================= +// Re-exports from Main Process Types +// ============================================================================= + +// Domain types +export type { + Project, + RepositoryGroup, + SearchResult, + Session, + SessionMetrics, + Worktree, + WorktreeSource, +} from '@shared/types'; + +// Message types +export type { ParsedMessage } from '@shared/types'; + +// Chunk types +export type { + Chunk, + EnhancedAIChunk, + EnhancedChunk, + EnhancedCompactChunk, + EnhancedSystemChunk, + EnhancedUserChunk, + Process, + SemanticStep, + SessionDetail, + SubagentDetail, +} from '@shared/types'; + +// Chunk type guards +export { isEnhancedAIChunk } from '@shared/types'; + +// JSONL types (for components that need content block types) +export type { ToolUseResultData } from '@shared/types'; + +// ============================================================================= +// Re-exports from Renderer-Specific Types +// ============================================================================= + +// API types +export type { ClaudeMdFileInfo } from './api'; + +// Notification types +export type { + AppConfig, + DetectedError, + NotificationTrigger, + TriggerContentType, + TriggerMatchField, + TriggerMode, + TriggerTestResult, + TriggerTokenType, + TriggerToolName, +} from './notifications'; + +// ============================================================================= +// Renderer-Specific Type Guards +// ============================================================================= + +import type { + Chunk, + EnhancedChunk, + EnhancedCompactChunk, + EnhancedSystemChunk, + EnhancedUserChunk, + ParsedMessage, +} from '@shared/types'; + +/** + * Type guard: Check if message is an assistant message. + */ +export function isAssistantMessage(msg: ParsedMessage): boolean { + return msg.type === 'assistant'; +} + +/** + * Type guard to check if a chunk is an EnhancedUserChunk. + */ +export function isEnhancedUserChunk(chunk: Chunk | EnhancedChunk): chunk is EnhancedUserChunk { + return 'chunkType' in chunk && chunk.chunkType === 'user' && 'rawMessages' in chunk; +} + +/** + * Type guard to check if a chunk is an EnhancedSystemChunk. + */ +export function isEnhancedSystemChunk(chunk: Chunk | EnhancedChunk): chunk is EnhancedSystemChunk { + return 'chunkType' in chunk && chunk.chunkType === 'system' && 'rawMessages' in chunk; +} + +/** + * Type guard to check if a chunk is an EnhancedCompactChunk. + */ +export function isEnhancedCompactChunk( + chunk: Chunk | EnhancedChunk +): chunk is EnhancedCompactChunk { + return 'chunkType' in chunk && chunk.chunkType === 'compact' && 'rawMessages' in chunk; +} + +/** + * Type guard to check if a single chunk is an EnhancedChunk. + * Enhanced chunks have 'chunkType' and 'rawMessages' properties. + */ +function isEnhancedChunk(chunk: Chunk | EnhancedChunk): chunk is EnhancedChunk { + return 'chunkType' in chunk && 'rawMessages' in chunk; +} + +/** + * Type guard to check if an array of chunks are all EnhancedChunks. + * Returns the array typed as EnhancedChunk[] if valid. + */ +export function asEnhancedChunkArray(chunks: Chunk[]): EnhancedChunk[] | null { + if (chunks.length === 0) { + return []; + } + // Check first chunk - if it has enhanced properties, assume all do + // (they come from the same builder) + if (isEnhancedChunk(chunks[0])) { + return chunks as EnhancedChunk[]; + } + return null; +} diff --git a/src/renderer/types/groups.ts b/src/renderer/types/groups.ts new file mode 100644 index 00000000..938af9a7 --- /dev/null +++ b/src/renderer/types/groups.ts @@ -0,0 +1,398 @@ +/** + * Type definitions for the new chat history architecture. + * These types separate user input from AI responses for a chat-style display. + */ + +import type { + ParsedMessage, + Process, + SemanticStep, + SessionMetrics, + ToolUseResultData, +} from './data'; +export type { SemanticStep }; +import type { ClaudeMdStats } from './claudeMd'; +import type { CompactionTokenDelta } from './contextInjection'; +import type { ModelInfo } from '@shared/utils/modelParser'; + +// ============================================================================= +// Expansion Levels +// ============================================================================= + +/** + * AI Group expansion levels for the collapsible UI. + * - collapsed: Show only summary line "1 tool call, 3 messages" + * - items: Show list of items (thinking, tool calls, etc.) + * - full: Show full content of each item + */ +export type AIGroupExpansionLevel = 'collapsed' | 'items' | 'full'; + +// ============================================================================= +// User Group Types +// ============================================================================= + +/** + * Command reference extracted from user input (e.g., /isolate-context, /context). + */ +export interface CommandInfo { + /** Command name without slash (e.g., "isolate-context") */ + name: string; + /** Optional arguments after the command */ + args?: string; + /** Full raw text including slash */ + raw: string; + /** Position in the text where command starts */ + startIndex: number; + /** Position in the text where command ends */ + endIndex: number; +} + +/** + * Image data from user message. + */ +export interface ImageData { + /** Unique identifier */ + id: string; + /** MIME type */ + mediaType: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp'; + /** Base64 encoded data for display */ + data?: string; +} + +/** + * File reference mentioned in user message (e.g., @file.ts). + */ +export interface FileReference { + /** File path */ + path: string; + /** Optional line range */ + lineRange?: { + start: number; + end?: number; + }; + /** Raw text as written */ + raw: string; +} + +/** + * Parsed content from a user message. + */ +export interface UserGroupContent { + /** Plain text content (with commands removed for display) */ + text?: string; + /** Raw text content (original) */ + rawText?: string; + /** Extracted commands */ + commands: CommandInfo[]; + /** Extracted images */ + images: ImageData[]; + /** Extracted file references */ + fileReferences: FileReference[]; +} + +/** + * User Group - represents a user's complete input. + * This is one side of a conversation turn. + */ +export interface UserGroup { + /** Unique identifier */ + id: string; + /** Original ParsedMessage */ + message: ParsedMessage; + /** Timestamp of the message */ + timestamp: Date; + /** Parsed content */ + content: UserGroupContent; + /** Index within the session (for ordering) */ + index: number; +} + +/** + * System Group - represents command output rendered like AI. + */ +export interface SystemGroup { + id: string; + message: ParsedMessage; + timestamp: Date; + commandOutput: string; // Raw output text + commandName?: string; // Optional: extracted command name +} + +// ============================================================================= +// AI Group Types +// ============================================================================= + +/** + * Summary statistics for the collapsed AI Group view. + */ +export interface AIGroupSummary { + /** Preview of thinking content (first ~100 chars) */ + thinkingPreview?: string; + /** Number of tool calls in this group */ + toolCallCount: number; + /** Number of output messages */ + outputMessageCount: number; + /** Number of subagent executions */ + subagentCount: number; + /** Total duration in milliseconds */ + totalDurationMs: number; + /** Total tokens used */ + totalTokens: number; + /** Output tokens */ + outputTokens: number; + /** Cached tokens */ + cachedTokens: number; +} + +/** + * Linked tool item pairing a tool call with its result. + * Includes preview text for display in collapsed/item views. + */ +export interface LinkedToolItem { + /** Tool call ID */ + id: string; + /** Tool name */ + name: string; + /** Tool input parameters */ + input: Record; + /** + * Token count for the tool CALL (what Claude generated). + * From message.usage.output_tokens, proportioned if multiple tools in message. + * For Write: includes file content. For Edit: includes old_string + new_string. + */ + callTokens?: number; + /** Tool result if received */ + result?: { + content: string | unknown[]; + isError: boolean; + toolUseResult?: ToolUseResultData; + /** Pre-computed token count for the result content */ + tokenCount?: number; + }; + /** Preview of input (first 100 chars) */ + inputPreview: string; + /** Preview of output (first 200 chars) */ + outputPreview?: string; + /** When the tool was called */ + startTime: Date; + /** When the result was received */ + endTime?: Date; + /** Duration in milliseconds */ + durationMs?: number; + /** Whether this is an orphaned call (no result) */ + isOrphaned: boolean; + /** Model used for the assistant message containing this tool call */ + sourceModel?: string; + /** + * Skill instructions content for Skill tool calls. + * Contains the follow-up text message starting with "Base directory for this skill:". + * This is captured from isMeta:true user messages with matching sourceToolUseID. + */ + skillInstructions?: string; + /** Pre-computed token count for skill instructions */ + skillInstructionsTokenCount?: number; +} + +/** + * Unified slash item - represents any slash command invocation. + * All slash commands follow the same format: + * /xxx + * xxx + * optional + * + * This includes: + * - Skills (e.g., /isolate-context) + * - Built-in commands (e.g., /model, /context) + * - Plugin commands + * - MCP commands + * - User-defined commands + */ +export interface SlashItem { + /** Unique ID (generated from command message uuid) */ + id: string; + /** Slash name extracted from /xxx */ + name: string; + /** Message content from */ + message?: string; + /** Optional arguments from */ + args?: string; + /** The command message uuid */ + commandMessageUuid: string; + /** Instructions/output content (from follow-up isMeta:true message with parentUuid) */ + instructions?: string; + /** Pre-computed token count for instructions */ + instructionsTokenCount?: number; + /** Timestamp of the command message */ + timestamp: Date; +} + +/** + * Teammate message received from a team member agent. + */ +export interface TeammateMessage { + id: string; + teammateId: string; + color: string; + summary: string; + content: string; + timestamp: Date; + tokenCount?: number; + /** Summary of the SendMessage that triggered this response (reply context) */ + replyToSummary?: string; + /** Tool call ID of the SendMessage that triggered this response */ + replyToToolId?: string; +} + +/** + * Display item for the AI Group - union of possible items to show. + * These are flattened and shown in chronological order. + */ +export type AIGroupDisplayItem = + | { type: 'thinking'; content: string; timestamp: Date; tokenCount?: number } + | { type: 'tool'; tool: LinkedToolItem } + | { type: 'subagent'; subagent: Process } + | { type: 'output'; content: string; timestamp: Date; tokenCount?: number } + | { type: 'slash'; slash: SlashItem } + | { type: 'teammate_message'; teammateMessage: TeammateMessage }; + +/** + * The last output in an AI Group - what user sees as "the answer". + * Either text output, the last tool result, an interruption, ongoing (still in progress), + * or plan_exit (ExitPlanMode tool call with plan content). + */ +export interface AIGroupLastOutput { + /** Output type */ + type: 'text' | 'tool_result' | 'interruption' | 'ongoing' | 'plan_exit'; + /** Text content if type === 'text' */ + text?: string; + /** Tool name if type === 'tool_result' */ + toolName?: string; + /** Tool result content if type === 'tool_result' */ + toolResult?: string; + /** Whether the tool result was an error */ + isError?: boolean; + /** Interruption message text if type === 'interruption' */ + interruptionMessage?: string; + /** Plan content if type === 'plan_exit' (from ExitPlanMode tool input) */ + planContent?: string; + /** Preamble text before plan exit (e.g., "The plan is complete. Let me exit plan mode...") */ + planPreamble?: string; + /** Timestamp of this output */ + timestamp: Date; +} + +/** + * Enhanced AI Group with display-ready data for the new UI. + * Extends the base AIGroup with computed properties for rendering. + */ +export interface EnhancedAIGroup extends AIGroup { + /** The last visible output (text or tool result) */ + lastOutput: AIGroupLastOutput | null; + /** Flattened display items in chronological order */ + displayItems: AIGroupDisplayItem[]; + /** Map of tool call IDs to linked tool items */ + linkedTools: Map; + /** Human-readable summary of items (e.g., "2 thinking, 4 tool calls, 3 subagents") */ + itemsSummary: string; + /** Model used by main agent (most common if mixed) */ + mainModel: ModelInfo | null; + /** Unique models used by subagents (if different from main) */ + subagentModels: ModelInfo[]; + /** CLAUDE.md injection statistics for this group */ + claudeMdStats: ClaudeMdStats | null; +} + +/** + * Status of an AI Group. + */ +export type AIGroupStatus = 'complete' | 'interrupted' | 'error' | 'in_progress'; + +/** + * Token metrics for an AI Group. + */ +export interface AIGroupTokens { + input: number; + output: number; + cached: number; + thinking?: number; +} + +/** + * AI Group - represents a single assistant response cycle. + * AI Groups are independent items in the flat conversation list. + */ +export interface AIGroup { + /** Unique identifier */ + id: string; + /** 0-based index of this AI group within the session (for turn navigation) */ + turnIndex: number; + /** Start timestamp */ + startTime: Date; + /** End timestamp */ + endTime: Date; + /** Duration in milliseconds */ + durationMs: number; + /** Semantic steps within this response */ + steps: SemanticStep[]; + /** Token metrics */ + tokens: AIGroupTokens; + /** Summary for collapsed view */ + summary: AIGroupSummary; + /** Completion status */ + status: AIGroupStatus; + /** Associated processes */ + processes: Process[]; + /** Source chunk ID */ + chunkId: string; + /** Metrics for this AI response (summed across all messages) */ + metrics: SessionMetrics; + /** All response messages (assistant + internal user messages) for accessing raw usage data */ + responses: ParsedMessage[]; + /** Whether this is the last AI group in an ongoing session */ + isOngoing?: boolean; +} + +// ============================================================================= +// Conversation Types +// ============================================================================= + +/** + * Compact Group - marks where conversation was compacted. + * Contains the compact summary message with the conversation summary. + */ +export interface CompactGroup { + id: string; + timestamp: Date; + message: ParsedMessage; // Contains compact summary in message.content + tokenDelta?: CompactionTokenDelta; + startingPhaseNumber?: number; +} + +/** + * Chat item - can be user, system, ai, or compact. + * These are INDEPENDENT items in a flat list, not paired turns. + */ +export type ChatItem = + | { type: 'user'; group: UserGroup } + | { type: 'system'; group: SystemGroup } + | { type: 'ai'; group: AIGroup } + | { type: 'compact'; group: CompactGroup }; + +/** + * Session conversation as a flat list of independent chat items. + * NO LONGER uses turns - each item stands alone. + */ +export interface SessionConversation { + /** Session ID */ + sessionId: string; + /** All chat items in chronological order */ + items: ChatItem[]; + /** Total count of user groups */ + totalUserGroups: number; + /** Total count of system groups */ + totalSystemGroups: number; + /** Total count of AI groups */ + totalAIGroups: number; + /** Total count of compact groups */ + totalCompactGroups: number; +} diff --git a/src/renderer/types/notifications.ts b/src/renderer/types/notifications.ts new file mode 100644 index 00000000..ad9aadaf --- /dev/null +++ b/src/renderer/types/notifications.ts @@ -0,0 +1,18 @@ +/** + * Notification and configuration types for Claude Code Context. + * + * Re-exports types from shared for backwards compatibility. + * The canonical definitions are in @shared/types/notifications. + */ + +export { + type AppConfig, + type DetectedError, + type NotificationTrigger, + type TriggerContentType, + type TriggerMatchField, + type TriggerMode, + type TriggerTestResult, + type TriggerTokenType, + type TriggerToolName, +} from '@shared/types'; diff --git a/src/renderer/types/panes.ts b/src/renderer/types/panes.ts new file mode 100644 index 00000000..6f061a4c --- /dev/null +++ b/src/renderer/types/panes.ts @@ -0,0 +1,35 @@ +/** + * Pane type definitions for the multi-pane split layout feature. + * Supports up to MAX_PANES horizontal panes, each with its own TabBar and tab state. + */ + +import type { Tab } from './tabs'; + +export const MAX_PANES = 4; + +/** + * Represents a single pane in the split layout. + * Each pane has its own set of tabs and active tab. + */ +export interface Pane { + /** Unique identifier (UUID) */ + id: string; + /** Tabs in this pane */ + tabs: Tab[]; + /** Active tab within this pane */ + activeTabId: string | null; + /** Multi-selected tabs within this pane */ + selectedTabIds: string[]; + /** Width as fraction of total (0-1, sum of all panes = 1) */ + widthFraction: number; +} + +/** + * The overall pane layout state. + */ +export interface PaneLayout { + /** Ordered left-to-right panes */ + panes: Pane[]; + /** Which pane receives keyboard/sidebar actions */ + focusedPaneId: string; +} diff --git a/src/renderer/types/tabs.ts b/src/renderer/types/tabs.ts new file mode 100644 index 00000000..4e60603e --- /dev/null +++ b/src/renderer/types/tabs.ts @@ -0,0 +1,236 @@ +/** + * Tab type definitions for the tabbed layout feature. + * Based on specs/001-tabbed-layout-dashboard/contracts/tab-state.ts + */ + +import type { Session } from './data'; +import type { TriggerColor } from '@shared/constants/triggerColors'; + +// ============================================================================= +// Navigation Request Types +// ============================================================================= + +/** + * Payload for error-based navigation (from notifications or trigger preview). + */ +export interface ErrorNavigationPayload { + /** Error ID for tracking */ + errorId: string; + /** Error timestamp for finding the correct AI group */ + errorTimestamp: number; + /** Tool use ID for precise tool item highlighting */ + toolUseId?: string; + /** Subagent ID for subagent-aware group lookup */ + subagentId?: string; + /** Line number (fallback) */ + lineNumber?: number; +} + +/** + * Payload for search-based navigation (from Command Palette). + */ +export interface SearchNavigationPayload { + /** The search query */ + query: string; + /** Timestamp of the message containing the search match */ + messageTimestamp: number; + /** The matched text */ + matchedText: string; + /** Optional exact target group ID (e.g., "user-..." or "ai-...") */ + targetGroupId?: string; + /** Optional exact match index within the target group's searchable text */ + targetMatchIndexInItem?: number; + /** Optional character offset of the match in the searchable text */ + targetMatchStartOffset?: number; + /** Optional source message UUID for diagnostics/fallback mapping */ + targetMessageUuid?: string; +} + +/** + * Unified tab navigation request. + * Each click/action creates a new request with a unique nonce (id). + * The nonce ensures repeated clicks produce new navigations. + */ +export interface TabNavigationRequest { + /** Unique nonce per click/action (crypto.randomUUID) */ + id: string; + /** Kind of navigation */ + kind: 'error' | 'search' | 'autoBottom'; + /** Source of the navigation action */ + source: 'notification' | 'triggerPreview' | 'commandPalette' | 'sessionOpen'; + /** Highlight color to use */ + highlight: TriggerColor | 'yellow' | 'none'; + /** Navigation payload (depends on kind) */ + payload: ErrorNavigationPayload | SearchNavigationPayload | Record; +} + +// ============================================================================= +// Core Types +// ============================================================================= + +/** + * Represents a single open tab in the main content area + */ +export interface Tab { + /** Unique identifier (UUID v4) */ + id: string; + + /** Type of content displayed in this tab */ + type: 'session' | 'dashboard' | 'notifications' | 'settings'; + + /** Session ID (required when type === 'session') */ + sessionId?: string; + + /** Project ID (required when type === 'session') */ + projectId?: string; + + /** Display name for the tab (max 50 chars) */ + label: string; + + /** Unix timestamp when tab was opened */ + createdAt: number; + + /** Whether this tab was opened from CommandPalette search */ + fromSearch?: boolean; + + /** Pending navigation request (replaces legacy deep-link fields) */ + pendingNavigation?: TabNavigationRequest; + + /** ID of the last consumed navigation request (prevents re-processing) */ + lastConsumedNavigationId?: string; + + /** Saved scroll position for restoring when tab becomes active again */ + savedScrollTop?: number; + + /** Whether the Context panel is shown (per-tab UI state) */ + showContextPanel?: boolean; +} + +/** + * Options for opening a tab + */ +export interface OpenTabOptions { + /** Force open in new tab even if session already exists (e.g., for Ctrl+click) */ + forceNewTab?: boolean; + /** Replace the current active tab instead of creating a new one */ + replaceActiveTab?: boolean; +} + +/** + * Input type for creating a new tab (id and createdAt are auto-generated) + */ +export type TabInput = Omit; + +/** + * Categories for date-based session grouping + */ +export type DateCategory = 'Today' | 'Yesterday' | 'Previous 7 Days' | 'Older'; + +/** + * Sessions grouped by relative date category + */ +export type DateGroupedSessions = Record; + +// ============================================================================= +// Constants +// ============================================================================= + +/** Maximum characters for tab label before truncation */ +const TAB_LABEL_MAX_LENGTH = 50; + +/** Date category order for rendering */ +export const DATE_CATEGORY_ORDER: DateCategory[] = [ + 'Today', + 'Yesterday', + 'Previous 7 Days', + 'Older', +]; + +// ============================================================================= +// Validation Helpers +// ============================================================================= + +/** + * Find tab by session ID (for backwards compatibility) + * NOTE: Prefer findTabBySessionAndProject when projectId is available + */ +export function findTabBySession(tabs: Tab[], sessionId: string): Tab | undefined { + return tabs.find((t) => t.type === 'session' && t.sessionId === sessionId); +} + +/** + * Find tab by both session ID AND project ID + * This prevents finding a tab with the same sessionId but different project + * (e.g., same filename in different repositories) + */ +export function findTabBySessionAndProject( + tabs: Tab[], + sessionId: string, + projectId: string +): Tab | undefined { + return tabs.find( + (t) => t.type === 'session' && t.sessionId === sessionId && t.projectId === projectId + ); +} + +/** + * Truncate label to max length with ellipsis + */ +export function truncateLabel(label: string): string { + if (label.length <= TAB_LABEL_MAX_LENGTH) return label; + return label.slice(0, TAB_LABEL_MAX_LENGTH - 1) + '…'; +} + +// ============================================================================= +// Navigation Request Helpers +// ============================================================================= + +/** + * Create an error navigation request (from notification click or trigger preview). + */ +export function createErrorNavigationRequest( + payload: ErrorNavigationPayload, + source: 'notification' | 'triggerPreview' = 'notification', + highlightColor?: TriggerColor +): TabNavigationRequest { + return { + id: crypto.randomUUID(), + kind: 'error', + source, + highlight: highlightColor ?? 'red', + payload, + }; +} + +/** + * Create a search navigation request (from Command Palette). + */ +export function createSearchNavigationRequest( + payload: SearchNavigationPayload +): TabNavigationRequest { + return { + id: crypto.randomUUID(), + kind: 'search', + source: 'commandPalette', + highlight: 'yellow', + payload, + }; +} + +/** + * Type guard for error navigation payload. + */ +export function isErrorPayload( + request: TabNavigationRequest +): request is TabNavigationRequest & { payload: ErrorNavigationPayload } { + return request.kind === 'error'; +} + +/** + * Type guard for search navigation payload. + */ +export function isSearchPayload( + request: TabNavigationRequest +): request is TabNavigationRequest & { payload: SearchNavigationPayload } { + return request.kind === 'search'; +} diff --git a/src/renderer/utils/aiGroupEnhancer.ts b/src/renderer/utils/aiGroupEnhancer.ts new file mode 100644 index 00000000..a5c1dd51 --- /dev/null +++ b/src/renderer/utils/aiGroupEnhancer.ts @@ -0,0 +1,78 @@ +/** + * AI Group Enhancer - Orchestrator for AI Group enhancement + * + * This module transforms raw AIGroup data into EnhancedAIGroup with display-ready + * properties for the chat-style UI. It coordinates between specialized utility modules: + * - lastOutputDetector: Find the last visible output + * - slashCommandExtractor: Handle slash command extraction + * - toolLinkingEngine: Link tool calls to their results + * - displayItemBuilder: Build display items from steps/messages + * - modelExtractor: Extract model information + * - displaySummary: Build human-readable summaries + * - aiGroupHelpers: Small utility functions + */ + +// Import from specialized modules +import { attachMainSessionImpact } from './aiGroupHelpers'; +import { buildDisplayItems } from './displayItemBuilder'; +import { buildSummary } from './displaySummary'; +import { findLastOutput } from './lastOutputDetector'; +import { extractMainModel, extractSubagentModels } from './modelExtractor'; +import { type PrecedingSlashInfo } from './slashCommandExtractor'; +import { linkToolCallsToResults } from './toolLinkingEngine'; + +import type { ClaudeMdStats } from '../types/claudeMd'; +import type { AIGroup, EnhancedAIGroup } from '../types/groups'; + +// Re-export types and functions that are part of the public API +export { truncateText } from './aiGroupHelpers'; +export { buildDisplayItems, buildDisplayItemsFromMessages } from './displayItemBuilder'; +export { buildSummary } from './displaySummary'; +export { findLastOutput } from './lastOutputDetector'; +export { type PrecedingSlashInfo } from './slashCommandExtractor'; +export { linkToolCallsToResults } from './toolLinkingEngine'; + +/** + * Main enhancement function - transforms AIGroup into EnhancedAIGroup. + * + * This is the primary entry point that ties together all the helper functions + * to produce a display-ready enhanced group. + * + * @param aiGroup - Base AI Group to enhance + * @param claudeMdStats - Optional CLAUDE.md injection stats for this group + * @param precedingSlash - Optional slash info from the preceding UserGroup + * @returns Enhanced AI Group with display data + */ +export function enhanceAIGroup( + aiGroup: AIGroup, + claudeMdStats?: ClaudeMdStats, + precedingSlash?: PrecedingSlashInfo +): EnhancedAIGroup { + // Pass isOngoing to findLastOutput - if ongoing, it returns 'ongoing' type instead of forcing a last output + const lastOutput = findLastOutput(aiGroup.steps, aiGroup.isOngoing ?? false); + // Pass responses to linkToolCallsToResults for slash instruction extraction + const linkedTools = linkToolCallsToResults(aiGroup.steps, aiGroup.responses); + // Attach main session impact tokens to subagents (Task tool call/result tokens) + attachMainSessionImpact(aiGroup.processes, linkedTools); + const displayItems = buildDisplayItems( + aiGroup.steps, + lastOutput, + aiGroup.processes, + aiGroup.responses, + precedingSlash + ); + const summary = buildSummary(displayItems); + const mainModel = extractMainModel(aiGroup.steps); + const subagentModels = extractSubagentModels(aiGroup.processes, mainModel); + + return { + ...aiGroup, + lastOutput, + linkedTools, + displayItems, + itemsSummary: summary, + mainModel, + subagentModels, + claudeMdStats: claudeMdStats ?? null, + }; +} diff --git a/src/renderer/utils/aiGroupHelpers.ts b/src/renderer/utils/aiGroupHelpers.ts new file mode 100644 index 00000000..f1448ba3 --- /dev/null +++ b/src/renderer/utils/aiGroupHelpers.ts @@ -0,0 +1,100 @@ +/** + * AI Group Helpers - Utility functions for AI Group enhancement + * + * Small, focused utility functions used across the AI Group enhancement modules. + */ + +import { createLogger } from '@shared/utils/logger'; +import { estimateTokens } from '@shared/utils/tokenFormatting'; + +import type { Process } from '../types/data'; +import type { LinkedToolItem } from '../types/groups'; + +const logger = createLogger('Util:aiGroupHelpers'); + +// Re-export for backwards compatibility +export { estimateTokens }; + +/** + * Safely converts a timestamp to a Date object. + * Handles both Date objects and ISO string timestamps (from IPC serialization). + */ +export function toDate(timestamp: Date | string | number): Date { + if (timestamp instanceof Date) { + return timestamp; + } + return new Date(timestamp); +} + +/** + * Truncates text to a maximum length and adds ellipsis if needed. + */ +export function truncateText(text: string, maxLength: number): string { + if (text.length <= maxLength) { + return text; + } + return text.substring(0, maxLength) + '...'; +} + +/** + * Converts tool input object to a preview string. + */ +export function formatToolInput(input: Record): string { + try { + const json = JSON.stringify(input, null, 2); + return truncateText(json, 100); + } catch (error) { + logger.debug('formatToolInput failed:', error); + return '[Invalid JSON]'; + } +} + +/** + * Converts tool result content to a preview string. + */ +export function formatToolResult(content: string | unknown[]): string { + try { + if (typeof content === 'string') { + return truncateText(content, 200); + } + const json = JSON.stringify(content, null, 2); + return truncateText(json, 200); + } catch (error) { + logger.debug('formatToolResult failed:', error); + return '[Invalid content]'; + } +} + +/** + * Attaches main session impact tokens to subagents. + * For each subagent with a parentTaskId, finds the matching Task tool + * and extracts the callTokens and resultTokens that affect the main session. + * + * This allows SubagentItem to display both: + * - Main session impact: tokens consumed by the Task tool_call + tool_result in the parent session + * - Subagent isolated context: the subagent's internal token usage + * + * @param subagents - Array of subagents to enhance + * @param linkedTools - Map of tool IDs to LinkedToolItem (includes Task tools) + * @returns The same subagents array with mainSessionImpact populated + */ +export function attachMainSessionImpact( + subagents: Process[], + linkedTools: Map +): Process[] { + for (const subagent of subagents) { + if (subagent.parentTaskId) { + const taskTool = linkedTools.get(subagent.parentTaskId); + if (taskTool) { + const callTokens = taskTool.callTokens ?? 0; + const resultTokens = taskTool.result?.tokenCount ?? 0; + subagent.mainSessionImpact = { + callTokens, + resultTokens, + totalTokens: callTokens + resultTokens, + }; + } + } + } + return subagents; +} diff --git a/src/renderer/utils/claudeMdTracker.ts b/src/renderer/utils/claudeMdTracker.ts new file mode 100644 index 00000000..90a64b13 --- /dev/null +++ b/src/renderer/utils/claudeMdTracker.ts @@ -0,0 +1,656 @@ +/** + * CLAUDE.md Injection Tracker + * + * Tracks system context injections from various CLAUDE.md sources throughout a session. + * Detects injections based on: + * - Global sources (enterprise, user-memory, project-memory, project-rules, project-local) + * - Directory-specific CLAUDE.md files (detected from Read tool calls and @ mentions) + */ + +import { extractFileReferences } from './groupTransformer'; + +import type { ClaudeMdInjection, ClaudeMdSource, ClaudeMdStats } from '../types/claudeMd'; +import type { ClaudeMdFileInfo, ParsedMessage, SemanticStep } from '../types/data'; +import type { AIGroup, ChatItem, FileReference, UserGroup } from '../types/groups'; + +// ============================================================================= +// Constants +// ============================================================================= + +/** Default estimated tokens for global CLAUDE.md sources */ +const DEFAULT_ESTIMATED_TOKENS = 500; + +/** CLAUDE.md filename to search for */ +const CLAUDE_MD_FILENAME = 'CLAUDE.md'; + +/** Source identifier for project memory CLAUDE.md files */ +const SOURCE_PROJECT_MEMORY: ClaudeMdSource = 'project-memory'; + +// ============================================================================= +// Helper Functions +// ============================================================================= + +/** + * Generate a unique ID for an injection based on its path. + * Uses a simple hash-like approach for readability. + */ +export function generateInjectionId(path: string): string { + // Create a simple hash from the path + let hash = 0; + for (let i = 0; i < path.length; i++) { + const char = path.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; // Convert to 32bit integer + } + // Convert to positive hex string + const positiveHash = Math.abs(hash).toString(16); + return `cmd-${positiveHash}`; +} + +/** + * Create a display name for a CLAUDE.md injection. + * Returns the raw path for transparency. + */ +export function getDisplayName(path: string, _source: ClaudeMdSource): string { + return path; +} + +/** + * Check if a path is absolute (starts with /). + */ +function isAbsolutePath(path: string): boolean { + return path.startsWith('/') || path.startsWith('\\\\') || /^[a-zA-Z]:[\\/]/.test(path); +} + +/** + * Join paths, handling various path formats properly. + * Handles: + * - Absolute paths: /full/path/file.tsx (returned as-is) + * - Relative paths with ./: ./apps/foo/bar.tsx (strips ./) + * - Parent paths with ../: ../other/file.tsx (walks up directories) + * - Plain paths: apps/foo/bar.tsx (joins with base) + * - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins) + */ +function joinPaths(base: string, relative: string): string { + if (isAbsolutePath(relative)) { + return relative; + } + + // Remove trailing slash from base if present + const cleanBase = trimTrailingSeparator(base); + + // Handle @ prefix (file mention marker) - strip it if present + let cleanRelative = relative; + if (cleanRelative.startsWith('@')) { + cleanRelative = cleanRelative.slice(1); + } + + // Handle ./ prefix (current directory) + if (cleanRelative.startsWith('./')) { + cleanRelative = cleanRelative.slice(2); + } + + // Handle ../ prefixes (parent directory) + const separator = cleanBase.includes('\\') ? '\\' : '/'; + const hasUnixRoot = cleanBase.startsWith('/'); + const hasUncRoot = cleanBase.startsWith('\\\\'); + const normalizedRelative = normalizeSeparators(cleanRelative, separator); + const baseParts = splitPath(cleanBase); + let remainingRelative = normalizedRelative; + while (remainingRelative.startsWith(`..${separator}`)) { + remainingRelative = remainingRelative.slice(3); + if (baseParts.length > 1) { + baseParts.pop(); + } + } + + // Join the normalized paths + let normalizedBase = baseParts.join(separator); + if (hasUnixRoot && !normalizedBase.startsWith('/')) { + normalizedBase = `/${normalizedBase}`; + } + if (hasUncRoot && !normalizedBase.startsWith('\\\\')) { + normalizedBase = `\\\\${normalizedBase}`; + } + return remainingRelative ? `${normalizedBase}${separator}${remainingRelative}` : normalizedBase; +} + +function trimTrailingSeparator(input: string): string { + let end = input.length; + while (end > 0) { + const char = input[end - 1]; + if (char !== '/' && char !== '\\') { + break; + } + end--; + } + return input.slice(0, end); +} + +function normalizeSeparators(input: string, separator: '/' | '\\'): string { + let output = ''; + let prevWasSeparator = false; + + for (const char of input) { + const isSeparator = char === '/' || char === '\\'; + if (isSeparator) { + if (!prevWasSeparator) { + output += separator; + } + prevWasSeparator = true; + } else { + output += char; + prevWasSeparator = false; + } + } + + return output; +} + +function splitPath(input: string): string[] { + const parts: string[] = []; + let current = ''; + + for (const char of input) { + if (char === '/' || char === '\\') { + if (current.length > 0) { + parts.push(current); + current = ''; + } + } else { + current += char; + } + } + + if (current.length > 0) { + parts.push(current); + } + + return parts; +} + +function normalizeForComparison(input: string): string { + return input.replace(/\\/g, '/'); +} + +/** + * Get the directory containing a file. + */ +export function getDirectory(filePath: string): string { + const lastSep = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\')); + if (lastSep === -1) return ''; + return filePath.slice(0, lastSep); +} + +/** + * Get the parent directory of a path. + */ +export function getParentDirectory(dirPath: string): string | null { + const lastSep = Math.max(dirPath.lastIndexOf('/'), dirPath.lastIndexOf('\\')); + if (lastSep <= 0) return null; // At root or invalid + return dirPath.slice(0, lastSep); +} + +/** + * Check if dirPath is at or above stopPath in the directory tree. + */ +function isAtOrAbove(dirPath: string, stopPath: string): boolean { + const normDir = normalizeForComparison(dirPath).replace(/\/$/, ''); + const normStop = normalizeForComparison(stopPath).replace(/\/$/, ''); + + // dirPath is at or above stopPath if stopPath starts with dirPath + return normStop === normDir || normStop.startsWith(normDir + '/'); +} + +// ============================================================================= +// Path Extraction Functions +// ============================================================================= + +/** + * Extract file paths from Read tool calls in semantic steps. + */ +export function extractReadToolPaths(steps: SemanticStep[]): string[] { + const paths: string[] = []; + + for (const step of steps) { + // Check if this is a Read tool call + if (step.type === 'tool_call' && step.content.toolName === 'Read') { + const toolInput = step.content.toolInput as Record | undefined; + if (toolInput && typeof toolInput.file_path === 'string') { + paths.push(toolInput.file_path); + } + } + } + + return paths; +} + +/** + * Extract file paths from user @ mentions. + * Converts relative paths to absolute using projectRoot. + */ +export function extractUserMentionPaths( + userGroup: UserGroup | null, + projectRoot: string +): string[] { + if (!userGroup) return []; + + const fileReferences = userGroup.content.fileReferences || []; + const paths: string[] = []; + + for (const ref of fileReferences) { + if (ref.path) { + // Convert to absolute if relative + const absolutePath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); + paths.push(absolutePath); + } + } + + return paths; +} + +/** + * Extracts file references from isMeta:true user messages within AI group responses. + * These are user-type messages generated by slash commands and other internal mechanisms + * that contain @-mentioned file paths. + */ +export function extractFileRefsFromResponses(responses: ParsedMessage[]): FileReference[] { + const refs: FileReference[] = []; + for (const msg of responses) { + if (msg.type !== 'user') continue; + let text = ''; + if (typeof msg.content === 'string') { + text = msg.content; + } else if (Array.isArray(msg.content)) { + for (const block of msg.content) { + if (block.type === 'text' && block.text) text += block.text; + } + } + if (text) refs.push(...extractFileReferences(text)); + } + return refs; +} + +// ============================================================================= +// CLAUDE.md Detection Functions +// ============================================================================= + +/** + * Detect potential CLAUDE.md files by walking up from a file's directory to project root. + * Returns paths to CLAUDE.md files that would be injected based on the file path. + */ +export function detectClaudeMdFromFilePath(filePath: string, projectRoot: string): string[] { + const claudeMdPaths: string[] = []; + const sep = filePath.includes('\\') ? '\\' : '/'; + + // Get the directory containing the file + let currentDir = getDirectory(filePath); + + // Walk up to project root (inclusive) + while (currentDir && isAtOrAbove(projectRoot, currentDir)) { + // Add potential CLAUDE.md path for this directory + const claudeMdPath = `${currentDir}${sep}${CLAUDE_MD_FILENAME}`; + claudeMdPaths.push(claudeMdPath); + + // Move to parent directory + const parentDir = getParentDirectory(currentDir); + if (!parentDir || parentDir === currentDir) { + break; + } + currentDir = parentDir; + } + + return claudeMdPaths; +} + +// ============================================================================= +// Injection Creation Functions +// ============================================================================= + +/** + * Create injection entries for global CLAUDE.md sources. + * These are injected at the start of every session. + * Only includes files that actually exist (tokens > 0). + */ +export function createGlobalInjections( + projectRoot: string, + aiGroupId: string, + tokenData?: Record +): ClaudeMdInjection[] { + const injections: ClaudeMdInjection[] = []; + + // Helper to get token count from tokenData or fallback to default + const getTokens = (key: string): number => { + return tokenData?.[key]?.estimatedTokens ?? DEFAULT_ESTIMATED_TOKENS; + }; + + // 1. Enterprise config + const enterprisePath = + tokenData?.enterprise?.path ?? '/Library/Application Support/ClaudeCode/CLAUDE.md'; + const enterpriseTokens = getTokens('enterprise'); + if (enterpriseTokens > 0) { + injections.push({ + id: generateInjectionId(enterprisePath), + path: enterprisePath, + source: 'enterprise', + displayName: getDisplayName(enterprisePath, 'enterprise'), + isGlobal: true, + estimatedTokens: enterpriseTokens, + firstSeenInGroup: aiGroupId, + }); + } + + // 2. User memory (~/.claude/CLAUDE.md) + // Use ~ for display purposes (renderer cannot access Node.js process.env) + const userMemoryPath = '~/.claude/CLAUDE.md'; + const userTokens = getTokens('user'); + if (userTokens > 0) { + injections.push({ + id: generateInjectionId(userMemoryPath), + path: userMemoryPath, + source: 'user-memory', + displayName: getDisplayName(userMemoryPath, 'user-memory'), + isGlobal: true, + estimatedTokens: userTokens, + firstSeenInGroup: aiGroupId, + }); + } + + // 3. Project memory - could be at root or in .claude folder + const projectMemoryPath = joinPaths(projectRoot, 'CLAUDE.md'); + const projectMemoryAltPath = joinPaths(projectRoot, '.claude/CLAUDE.md'); + // Add the main project CLAUDE.md + const projectTokens = getTokens('project'); + if (projectTokens > 0) { + injections.push({ + id: generateInjectionId(projectMemoryPath), + path: projectMemoryPath, + source: SOURCE_PROJECT_MEMORY, + displayName: getDisplayName(projectMemoryPath, SOURCE_PROJECT_MEMORY), + isGlobal: true, + estimatedTokens: projectTokens, + firstSeenInGroup: aiGroupId, + }); + } + // Also add the .claude folder variant + const projectAltTokens = getTokens('project-alt'); + if (projectAltTokens > 0) { + injections.push({ + id: generateInjectionId(projectMemoryAltPath), + path: projectMemoryAltPath, + source: SOURCE_PROJECT_MEMORY, + displayName: getDisplayName(projectMemoryAltPath, SOURCE_PROJECT_MEMORY), + isGlobal: true, + estimatedTokens: projectAltTokens, + firstSeenInGroup: aiGroupId, + }); + } + + // 4. Project rules (*.md files in .claude/rules/) + const projectRulesPath = joinPaths(projectRoot, '.claude/rules/*.md'); + const projectRulesTokens = getTokens('project-rules'); + if (projectRulesTokens > 0) { + injections.push({ + id: generateInjectionId(projectRulesPath), + path: projectRulesPath, + source: 'project-rules', + displayName: getDisplayName(projectRulesPath, 'project-rules'), + isGlobal: true, + estimatedTokens: projectRulesTokens, + firstSeenInGroup: aiGroupId, + }); + } + + // 5. Project local + const projectLocalPath = joinPaths(projectRoot, 'CLAUDE.local.md'); + const projectLocalTokens = getTokens('project-local'); + if (projectLocalTokens > 0) { + injections.push({ + id: generateInjectionId(projectLocalPath), + path: projectLocalPath, + source: 'project-local', + displayName: getDisplayName(projectLocalPath, 'project-local'), + isGlobal: true, + estimatedTokens: projectLocalTokens, + firstSeenInGroup: aiGroupId, + }); + } + + // 6. User rules (~/.claude/rules/**/*.md) + const userRulesPath = '~/.claude/rules/**/*.md'; + const userRulesTokens = getTokens('user-rules'); + if (userRulesTokens > 0) { + injections.push({ + id: generateInjectionId(userRulesPath), + path: userRulesPath, + source: 'user-rules', + displayName: getDisplayName(userRulesPath, 'user-rules'), + isGlobal: true, + estimatedTokens: userRulesTokens, + firstSeenInGroup: aiGroupId, + }); + } + + // 7. Auto memory (~/.claude/projects//memory/MEMORY.md) + const autoMemoryPath = + tokenData?.['auto-memory']?.path ?? '~/.claude/projects/.../memory/MEMORY.md'; + const autoMemoryTokens = getTokens('auto-memory'); + if (autoMemoryTokens > 0) { + injections.push({ + id: generateInjectionId(autoMemoryPath), + path: autoMemoryPath, + source: 'auto-memory', + displayName: getDisplayName(autoMemoryPath, 'auto-memory'), + isGlobal: true, + estimatedTokens: autoMemoryTokens, + firstSeenInGroup: aiGroupId, + }); + } + + return injections; +} + +/** + * Create an injection entry for a directory-specific CLAUDE.md. + */ +function createDirectoryInjection(path: string, aiGroupId: string): ClaudeMdInjection { + return { + id: generateInjectionId(path), + path, + source: 'directory', + displayName: getDisplayName(path, 'directory'), + isGlobal: false, + estimatedTokens: DEFAULT_ESTIMATED_TOKENS, + firstSeenInGroup: aiGroupId, + }; +} + +// ============================================================================= +// Stats Computation +// ============================================================================= + +/** + * Parameters for computing CLAUDE.md stats for an AI group. + */ +interface ComputeClaudeMdStatsParams { + aiGroup: AIGroup; + userGroup: UserGroup | null; + isFirstGroup: boolean; + previousInjections: ClaudeMdInjection[]; + projectRoot: string; + contextTokens: number; + tokenData?: Record; +} + +/** + * Compute CLAUDE.md injection statistics for an AI group. + */ +function computeClaudeMdStats(params: ComputeClaudeMdStatsParams): ClaudeMdStats { + const { + aiGroup, + userGroup, + isFirstGroup, + previousInjections, + projectRoot, + contextTokens, + tokenData, + } = params; + + const newInjections: ClaudeMdInjection[] = []; + const previousPaths = new Set(previousInjections.map((inj) => inj.path)); + + // For the first group, add global injections + // Use "ai-N" format for firstSeenInGroup to enable turn navigation in SessionClaudeMdPanel + const turnGroupId = `ai-${aiGroup.turnIndex}`; + if (isFirstGroup) { + const globalInjections = createGlobalInjections(projectRoot, turnGroupId, tokenData); + for (const injection of globalInjections) { + if (!previousPaths.has(injection.path)) { + newInjections.push(injection); + previousPaths.add(injection.path); + } + } + } + + // Collect all file paths from Read tools and user @ mentions + const allFilePaths: string[] = []; + + // Extract from Read tool calls in semantic steps + const readPaths = extractReadToolPaths(aiGroup.steps); + allFilePaths.push(...readPaths); + + // Extract from user @ mentions + const mentionPaths = extractUserMentionPaths(userGroup, projectRoot); + allFilePaths.push(...mentionPaths); + + // Extract from isMeta:true user messages in AI responses (slash command follow-ups) + const responseRefs = extractFileRefsFromResponses(aiGroup.responses); + for (const ref of responseRefs) { + if (ref.path) { + const absPath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); + allFilePaths.push(absPath); + } + } + + // For each file path, detect potential CLAUDE.md files + for (const filePath of allFilePaths) { + const claudeMdPaths = detectClaudeMdFromFilePath(filePath, projectRoot); + + for (const claudeMdPath of claudeMdPaths) { + // Skip if already seen + if (previousPaths.has(claudeMdPath)) { + continue; + } + + // Skip if this is a global path (already handled) + const isGlobalPath = + normalizeForComparison(claudeMdPath) === + `${normalizeForComparison(projectRoot)}/CLAUDE.md` || + normalizeForComparison(claudeMdPath) === + `${normalizeForComparison(projectRoot)}/.claude/CLAUDE.md` || + normalizeForComparison(claudeMdPath) === + `${normalizeForComparison(projectRoot)}/CLAUDE.local.md`; + + if (isGlobalPath) { + continue; + } + + // Create directory injection + const injection = createDirectoryInjection(claudeMdPath, turnGroupId); + newInjections.push(injection); + previousPaths.add(claudeMdPath); + } + } + + // Build accumulated injections + const accumulatedInjections = [...previousInjections, ...newInjections]; + + // Calculate totals + const totalEstimatedTokens = accumulatedInjections.reduce( + (sum, inj) => sum + inj.estimatedTokens, + 0 + ); + + // Calculate percentage of context + const percentageOfContext = contextTokens > 0 ? (totalEstimatedTokens / contextTokens) * 100 : 0; + + return { + newInjections, + accumulatedInjections, + totalEstimatedTokens, + percentageOfContext, + newCount: newInjections.length, + accumulatedCount: accumulatedInjections.length, + }; +} + +// ============================================================================= +// Session Processing +// ============================================================================= + +/** + * Process all chat items in a session and compute CLAUDE.md stats for each AI group. + * Returns a map of aiGroupId -> ClaudeMdStats. + */ +export function processSessionClaudeMd( + items: ChatItem[], + projectRoot: string, + tokenData?: Record +): Map { + const statsMap = new Map(); + let accumulatedInjections: ClaudeMdInjection[] = []; + let isFirstAiGroup = true; + let previousUserGroup: UserGroup | null = null; + + for (const item of items) { + // Track user groups for pairing with subsequent AI groups + if (item.type === 'user') { + previousUserGroup = item.group; + continue; + } + + // Handle compact items: reset accumulated state across compaction boundaries + if (item.type === 'compact') { + accumulatedInjections = []; + isFirstAiGroup = true; + previousUserGroup = null; + continue; + } + + // Process AI groups + if (item.type === 'ai') { + const aiGroup = item.group; + + // Get context tokens from the AI group's metrics + // Use input tokens as a proxy for context window usage + const contextTokens = aiGroup.tokens.input || 0; + + // Compute stats for this group + const stats = computeClaudeMdStats({ + aiGroup, + userGroup: previousUserGroup, + isFirstGroup: isFirstAiGroup, + previousInjections: accumulatedInjections, + projectRoot, + contextTokens, + tokenData, + }); + + // Store stats + statsMap.set(aiGroup.id, stats); + + // Update accumulated state for next iteration + accumulatedInjections = stats.accumulatedInjections; + isFirstAiGroup = false; + + // Clear the user group pairing after processing + previousUserGroup = null; + } + } + + return statsMap; +} + +// ============================================================================= +// Utility Exports +// ============================================================================= diff --git a/src/renderer/utils/contextTracker.ts b/src/renderer/utils/contextTracker.ts new file mode 100644 index 00000000..d1b54aef --- /dev/null +++ b/src/renderer/utils/contextTracker.ts @@ -0,0 +1,1099 @@ +/** + * Unified Context Tracker + * + * Provides comprehensive context tracking for all sources of context injection: + * - CLAUDE.md files (enterprise, user, project, directory) + * - Mentioned files (@mentions) + * - Tool outputs + * + * This builds on claudeMdTracker.ts and extends it to track all context sources. + */ + +import { estimateTokens } from '@shared/utils/tokenFormatting'; + +import { MAX_MENTIONED_FILE_TOKENS } from '../types/contextInjection'; + +import { buildDisplayItems, findLastOutput, linkToolCallsToResults } from './aiGroupEnhancer'; +import { + createGlobalInjections, + detectClaudeMdFromFilePath, + extractFileRefsFromResponses, + extractReadToolPaths, + extractUserMentionPaths, + generateInjectionId, + getDisplayName, +} from './claudeMdTracker'; + +import type { ClaudeMdInjection, ClaudeMdSource } from '../types/claudeMd'; +import type { + ClaudeMdContextInjection, + CompactionTokenDelta, + ContextInjection, + ContextPhase, + ContextPhaseInfo, + ContextStats, + MentionedFileInfo, + MentionedFileInjection, + NewCountsByCategory, + TaskCoordinationBreakdown, + TaskCoordinationInjection, + ThinkingTextBreakdown, + ThinkingTextInjection, + TokensByCategory, + ToolOutputInjection, + ToolTokenBreakdown, + UserMessageInjection, +} from '../types/contextInjection'; +import type { ClaudeMdFileInfo } from '../types/data'; +import type { + AIGroup, + AIGroupDisplayItem, + ChatItem, + LinkedToolItem, + UserGroup, +} from '../types/groups'; + +// ============================================================================= +// Constants +// ============================================================================= + +/** Category identifier for mentioned file injections */ +const CATEGORY_MENTIONED_FILE = 'mentioned-file' as const; + +/** Tool names that constitute task coordination overhead */ +const TASK_COORDINATION_TOOL_NAMES = new Set([ + 'SendMessage', + 'TeamCreate', + 'TeamDelete', + 'TaskCreate', + 'TaskUpdate', + 'TaskList', + 'TaskGet', +]); + +// ============================================================================= +// ID Generation Functions +// ============================================================================= + +/** + * Generate a unique ID for a mentioned file injection. + * Uses a similar approach to generateInjectionId but with 'mf-' prefix. + */ +function generateMentionedFileId(path: string): string { + let hash = 0; + for (let i = 0; i < path.length; i++) { + const char = path.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; // Convert to 32bit integer + } + const positiveHash = Math.abs(hash).toString(16); + return `mf-${positiveHash}`; +} + +/** + * Generate a unique ID for a tool output injection. + */ +function generateToolOutputId(turnIndex: number): string { + return `tool-output-ai-${turnIndex}`; +} + +/** + * Generate unique ID for thinking-text injection. + */ +function generateThinkingTextId(turnIndex: number): string { + return `thinking-text-ai-${turnIndex}`; +} + +/** + * Generate unique ID for task coordination injection. + */ +function generateTaskCoordinationId(turnIndex: number): string { + return `task-coord-ai-${turnIndex}`; +} + +/** + * Generate unique ID for user message injection. + */ +function generateUserMessageId(turnIndex: number): string { + return `user-msg-ai-${turnIndex}`; +} + +// ============================================================================= +// Injection Wrapping Functions +// ============================================================================= + +/** + * Wrap a ClaudeMdInjection with the 'claude-md' category for union compatibility. + */ +function wrapClaudeMdInjection(injection: ClaudeMdInjection): ClaudeMdContextInjection { + return { + ...injection, + category: 'claude-md' as const, + }; +} + +// ============================================================================= +// Mentioned File Injection Creation +// ============================================================================= + +/** + * Parameters for creating a mentioned file injection. + */ +interface CreateMentionedFileInjectionParams { + /** Absolute file path */ + path: string; + /** Display name (relative path or filename) */ + displayName: string; + /** Estimated token count for this file */ + estimatedTokens: number; + /** Turn index where this file was first mentioned */ + turnIndex: number; + /** AI group ID for navigation */ + aiGroupId: string; + /** Whether the file exists on disk */ + exists?: boolean; +} + +/** + * Create a MentionedFileInjection object. + */ +function createMentionedFileInjection( + params: CreateMentionedFileInjectionParams +): MentionedFileInjection { + return { + id: generateMentionedFileId(params.path), + category: CATEGORY_MENTIONED_FILE, + path: params.path, + displayName: params.displayName, + estimatedTokens: params.estimatedTokens, + firstSeenTurnIndex: params.turnIndex, + firstSeenInGroup: params.aiGroupId, + exists: params.exists ?? true, + }; +} + +// ============================================================================= +// Tool Output Aggregation +// ============================================================================= + +/** + * Aggregate tool outputs from all linked tools in a turn. + * Also includes tokens from user-invoked skills (via /skill-name commands). + * Returns a ToolOutputInjection if there are any tool outputs with tokens. + */ +function aggregateToolOutputs( + linkedTools: Map, + turnIndex: number, + aiGroupId: string, + displayItems?: AIGroupDisplayItem[] +): ToolOutputInjection | null { + const toolBreakdown: ToolTokenBreakdown[] = []; + let totalTokens = 0; + + for (const linkedTool of linkedTools.values()) { + // Skip task coordination tools - they are tracked separately + if (TASK_COORDINATION_TOOL_NAMES.has(linkedTool.name)) { + continue; + } + + // Calculate total context tokens for the tool operation + // This matches getToolContextTokens in LinkedToolItem.tsx and ErrorDetector + // + // callTokens: What Claude generated (Write file content, Edit old/new strings) + // resultTokens: What Claude reads back (success message, Read file content) + // skillTokens: Additional context for Skill tools + const callTokens = linkedTool.callTokens ?? 0; + const resultTokens = linkedTool.result?.tokenCount ?? 0; + const skillTokens = linkedTool.skillInstructionsTokenCount ?? 0; + const toolTokenCount = callTokens + resultTokens + skillTokens; + + if (toolTokenCount > 0) { + // Rename "Task" to "Task (Subagent)" for clarity in the UI + const displayName = linkedTool.name === 'Task' ? 'Task (Subagent)' : linkedTool.name; + toolBreakdown.push({ + toolName: displayName, + tokenCount: toolTokenCount, + isError: linkedTool.result?.isError ?? false, + }); + totalTokens += toolTokenCount; + } + } + + // Include user-invoked slash tokens from display items + // These are slashes invoked via /xxx commands + if (displayItems) { + for (const item of displayItems) { + if (item.type === 'slash' && item.slash.instructionsTokenCount) { + toolBreakdown.push({ + toolName: `/${item.slash.name}`, + tokenCount: item.slash.instructionsTokenCount, + isError: false, + }); + totalTokens += item.slash.instructionsTokenCount; + } + } + } + + // Return null if no tokens from tools + if (totalTokens === 0) { + return null; + } + + return { + id: generateToolOutputId(turnIndex), + category: 'tool-output', + turnIndex, + aiGroupId, + estimatedTokens: totalTokens, + toolCount: toolBreakdown.length, + toolBreakdown, + }; +} + +// ============================================================================= +// Task Coordination Aggregation +// ============================================================================= + +/** + * Aggregate task coordination tokens from linked tools and display items. + * Tracks SendMessage, TeamCreate, TaskCreate, and other task tools, + * plus teammate_message items injected into the session. + */ +function aggregateTaskCoordination( + linkedTools: Map, + turnIndex: number, + aiGroupId: string, + displayItems?: AIGroupDisplayItem[] +): TaskCoordinationInjection | null { + const breakdown: TaskCoordinationBreakdown[] = []; + let totalTokens = 0; + + // Scan linked tools for task coordination tools + for (const linkedTool of linkedTools.values()) { + if (!TASK_COORDINATION_TOOL_NAMES.has(linkedTool.name)) { + continue; + } + + const callTokens = linkedTool.callTokens ?? 0; + const resultTokens = linkedTool.result?.tokenCount ?? 0; + const skillTokens = linkedTool.skillInstructionsTokenCount ?? 0; + const toolTokenCount = callTokens + resultTokens + skillTokens; + + if (toolTokenCount > 0) { + // Extract a label from tool input for SendMessage (recipient name) + let label = linkedTool.name; + if (linkedTool.name === 'SendMessage' && linkedTool.input) { + const recipient = linkedTool.input.recipient as string | undefined; + if (recipient) { + label = `SendMessage → ${recipient}`; + } + } + + breakdown.push({ + type: linkedTool.name === 'SendMessage' ? 'send-message' : 'task-tool', + toolName: linkedTool.name, + tokenCount: toolTokenCount, + label, + }); + totalTokens += toolTokenCount; + } + } + + // Scan display items for teammate messages + if (displayItems) { + for (const item of displayItems) { + if (item.type === 'teammate_message' && item.teammateMessage.tokenCount) { + breakdown.push({ + type: 'teammate-message', + tokenCount: item.teammateMessage.tokenCount, + label: item.teammateMessage.teammateId, + }); + totalTokens += item.teammateMessage.tokenCount; + } + } + } + + if (totalTokens === 0) { + return null; + } + + return { + id: generateTaskCoordinationId(turnIndex), + category: 'task-coordination', + turnIndex, + aiGroupId, + estimatedTokens: totalTokens, + breakdown, + }; +} + +// ============================================================================= +// User Message Injection Creation +// ============================================================================= + +/** + * Create a UserMessageInjection from a user group. + * Uses rawText (includes commands and @mentions) for token estimation + * since that's what's actually sent to the API. + * + * @returns UserMessageInjection or null if empty text or 0 tokens + */ +function createUserMessageInjection( + userGroup: UserGroup, + turnIndex: number, + aiGroupId: string +): UserMessageInjection | null { + const text = userGroup.content.rawText ?? userGroup.content.text ?? ''; + if (!text) return null; + + const tokens = estimateTokens(text); + if (tokens === 0) return null; + + const textPreview = text.length > 80 ? text.slice(0, 80) + '…' : text; + + return { + id: generateUserMessageId(turnIndex), + category: 'user-message', + turnIndex, + aiGroupId, + estimatedTokens: tokens, + textPreview, + }; +} + +// ============================================================================= +// Thinking/Text Output Aggregation +// ============================================================================= + +/** + * Aggregates thinking and text output tokens for a single turn. + * Creates a ThinkingTextInjection that tracks all thinking blocks and text outputs. + * + * @param displayItems - Display items from the AI group + * @param turnIndex - The turn index (0-based) + * @param aiGroupId - The AI group ID for navigation + * @returns ThinkingTextInjection or null if no tokens + */ +function aggregateThinkingText( + displayItems: AIGroupDisplayItem[], + turnIndex: number, + aiGroupId: string +): ThinkingTextInjection | null { + const breakdown: ThinkingTextBreakdown[] = []; + let totalTokens = 0; + let thinkingTokens = 0; + let textTokens = 0; + + for (const item of displayItems) { + if (item.type === 'thinking' && item.tokenCount && item.tokenCount > 0) { + thinkingTokens += item.tokenCount; + totalTokens += item.tokenCount; + } else if (item.type === 'output' && item.tokenCount && item.tokenCount > 0) { + textTokens += item.tokenCount; + totalTokens += item.tokenCount; + } + } + + if (thinkingTokens > 0) { + breakdown.push({ type: 'thinking', tokenCount: thinkingTokens }); + } + if (textTokens > 0) { + breakdown.push({ type: 'text', tokenCount: textTokens }); + } + + if (totalTokens === 0) { + return null; + } + + return { + id: generateThinkingTextId(turnIndex), + category: 'thinking-text', + turnIndex, + aiGroupId, + estimatedTokens: totalTokens, + breakdown, + }; +} + +// ============================================================================= +// Stats Computation +// ============================================================================= + +/** + * Parameters for computing context stats for an AI group. + */ +interface ComputeContextStatsParams { + /** The AI group being processed */ + aiGroup: AIGroup; + /** The preceding user group (if any) */ + userGroup: UserGroup | null; + /** Linked tools map from the enhanced AI group */ + linkedTools: Map; + /** Display items from enhanced AI group (includes user skills) */ + displayItems?: AIGroupDisplayItem[]; + /** Whether this is the first AI group in the session */ + isFirstGroup: boolean; + /** Accumulated injections from previous groups */ + previousInjections: ContextInjection[]; + /** Project root path for resolving relative paths */ + projectRoot: string; + /** Token data for CLAUDE.md files (global sources) */ + claudeMdTokenData?: Record; + /** Token data for mentioned files */ + mentionedFileTokenData?: Map; + /** Token data for validated directory CLAUDE.md files (keyed by full path) */ + directoryTokenData?: Record; +} + +/** + * Helper to check if a path is absolute. + */ +function isAbsolutePath(path: string): boolean { + return ( + path.startsWith('/') || + path.startsWith('~/') || + path.startsWith('~\\') || + path === '~' || + path.startsWith('\\\\') || + /^[a-zA-Z]:[\\/]/.test(path) + ); +} + +/** + * Helper to join paths, handling various path formats properly. + * Handles: + * - Absolute paths: /full/path/file.tsx (returned as-is) + * - Relative paths with ./: ./apps/foo/bar.tsx (strips ./) + * - Parent paths with ../: ../other/file.tsx (walks up directories) + * - Plain paths: apps/foo/bar.tsx (joins with base) + * - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins) + */ +function joinPaths(base: string, relative: string): string { + if (isAbsolutePath(relative)) { + return relative; + } + + const cleanBase = trimTrailingSeparator(base); + + // Handle @ prefix (file mention marker) - strip it if present + let cleanRelative = relative; + if (cleanRelative.startsWith('@')) { + cleanRelative = cleanRelative.slice(1); + } + + // Handle ./ prefix (current directory) + if (cleanRelative.startsWith('./')) { + cleanRelative = cleanRelative.slice(2); + } + + // Handle ../ prefixes (parent directory) + const separator = cleanBase.includes('\\') ? '\\' : '/'; + const hasUnixRoot = cleanBase.startsWith('/'); + const hasUncRoot = cleanBase.startsWith('\\\\'); + const normalizedRelative = normalizeSeparators(cleanRelative, separator); + const baseParts = splitPath(cleanBase); + let remainingRelative = normalizedRelative; + while (remainingRelative.startsWith(`..${separator}`)) { + remainingRelative = remainingRelative.slice(3); + if (baseParts.length > 1) { + baseParts.pop(); + } + } + + // Join the normalized paths + let normalizedBase = baseParts.join(separator); + if (hasUnixRoot && !normalizedBase.startsWith('/')) { + normalizedBase = `/${normalizedBase}`; + } + if (hasUncRoot && !normalizedBase.startsWith('\\\\')) { + normalizedBase = `\\\\${normalizedBase}`; + } + return remainingRelative ? `${normalizedBase}${separator}${remainingRelative}` : normalizedBase; +} + +function trimTrailingSeparator(input: string): string { + let end = input.length; + while (end > 0) { + const char = input[end - 1]; + if (char !== '/' && char !== '\\') { + break; + } + end--; + } + return input.slice(0, end); +} + +function normalizeSeparators(input: string, separator: '/' | '\\'): string { + let output = ''; + let prevWasSeparator = false; + + for (const char of input) { + const isSeparator = char === '/' || char === '\\'; + if (isSeparator) { + if (!prevWasSeparator) { + output += separator; + } + prevWasSeparator = true; + } else { + output += char; + prevWasSeparator = false; + } + } + + return output; +} + +function splitPath(input: string): string[] { + const parts: string[] = []; + let current = ''; + + for (const char of input) { + if (char === '/' || char === '\\') { + if (current.length > 0) { + parts.push(current); + current = ''; + } + } else { + current += char; + } + } + + if (current.length > 0) { + parts.push(current); + } + + return parts; +} + +function normalizeForComparison(input: string): string { + return input.replace(/\\/g, '/'); +} + +/** + * Create a directory injection for a CLAUDE.md file discovered via file paths. + */ +function createDirectoryInjection(path: string, aiGroupId: string): ClaudeMdInjection { + return { + id: generateInjectionId(path), + path, + source: 'directory' as ClaudeMdSource, + displayName: getDisplayName(path, 'directory'), + isGlobal: false, + estimatedTokens: 500, // Default estimated tokens + firstSeenInGroup: aiGroupId, + }; +} + +/** + * Compute context stats for an AI group. + * Tracks CLAUDE.md injections, mentioned files, and tool outputs. + */ +function computeContextStats(params: ComputeContextStatsParams): ContextStats { + const { + aiGroup, + userGroup, + linkedTools, + displayItems, + isFirstGroup, + previousInjections, + projectRoot, + claudeMdTokenData, + mentionedFileTokenData, + directoryTokenData, + } = params; + + const newInjections: ContextInjection[] = []; + const previousPaths = new Set( + previousInjections + .filter( + (inj): inj is ClaudeMdContextInjection | MentionedFileInjection => + inj.category === 'claude-md' || inj.category === CATEGORY_MENTIONED_FILE + ) + .map((inj) => inj.path) + ); + + // Use "ai-N" format for firstSeenInGroup to enable turn navigation + const turnGroupId = `ai-${aiGroup.turnIndex}`; + + // a) For FIRST group only: Add CLAUDE.md global injections + if (isFirstGroup) { + const globalInjections = createGlobalInjections(projectRoot, turnGroupId, claudeMdTokenData); + for (const injection of globalInjections) { + if (!previousPaths.has(injection.path)) { + newInjections.push(wrapClaudeMdInjection(injection)); + previousPaths.add(injection.path); + } + } + } + + // b) Detect directory CLAUDE.md from file paths + // Only include directory CLAUDE.md files that have been validated to exist + const allFilePaths: string[] = []; + + // Extract from Read tool calls in semantic steps + const readPaths = extractReadToolPaths(aiGroup.steps); + allFilePaths.push(...readPaths); + + // Extract from user @ mentions + const mentionPaths = extractUserMentionPaths(userGroup, projectRoot); + allFilePaths.push(...mentionPaths); + + // Extract from isMeta:true user messages in AI responses (slash command follow-ups) + const responseRefs = extractFileRefsFromResponses(aiGroup.responses); + for (const ref of responseRefs) { + if (ref.path) { + const absPath = isAbsolutePath(ref.path) ? ref.path : joinPaths(projectRoot, ref.path); + allFilePaths.push(absPath); + } + } + + // For each file path, detect potential CLAUDE.md files + for (const filePath of allFilePaths) { + const claudeMdPaths = detectClaudeMdFromFilePath(filePath, projectRoot); + + for (const claudeMdPath of claudeMdPaths) { + // Skip if already seen + if (previousPaths.has(claudeMdPath)) { + continue; + } + + // Skip if this is a global path (already handled) + const isGlobalPath = + normalizeForComparison(claudeMdPath) === + `${normalizeForComparison(projectRoot)}/CLAUDE.md` || + normalizeForComparison(claudeMdPath) === + `${normalizeForComparison(projectRoot)}/.claude/CLAUDE.md` || + normalizeForComparison(claudeMdPath) === + `${normalizeForComparison(projectRoot)}/CLAUDE.local.md`; + + if (isGlobalPath) { + continue; + } + + // Only include directory CLAUDE.md files that exist (validated via directoryTokenData) + // If directoryTokenData is provided and doesn't contain this path, the file doesn't exist + if (directoryTokenData) { + const fileInfo = directoryTokenData[claudeMdPath]; + if (!fileInfo || !fileInfo.exists || fileInfo.estimatedTokens <= 0) { + // File doesn't exist or has no content - skip it + continue; + } + // Use validated token count from directoryTokenData + const injection = createDirectoryInjection(claudeMdPath, turnGroupId); + injection.estimatedTokens = fileInfo.estimatedTokens; + newInjections.push(wrapClaudeMdInjection(injection)); + previousPaths.add(claudeMdPath); + } else { + // Fallback: if no directoryTokenData provided, create with default tokens (legacy behavior) + const injection = createDirectoryInjection(claudeMdPath, turnGroupId); + newInjections.push(wrapClaudeMdInjection(injection)); + previousPaths.add(claudeMdPath); + } + } + } + + // c) Process mentioned files (NEW LOGIC) + if (userGroup?.content.fileReferences) { + for (const fileRef of userGroup.content.fileReferences) { + if (!fileRef.path) continue; + + // Convert to absolute path if needed + const absolutePath = isAbsolutePath(fileRef.path) + ? fileRef.path + : joinPaths(projectRoot, fileRef.path); + + // Skip if already seen + if (previousPaths.has(absolutePath)) { + continue; + } + + // Check if we have token data for this file + const fileInfo = mentionedFileTokenData?.get(absolutePath); + + // Only include files that exist and are under the token limit + if (fileInfo && fileInfo.exists && fileInfo.estimatedTokens <= MAX_MENTIONED_FILE_TOKENS) { + const mentionedFileInjection = createMentionedFileInjection({ + path: absolutePath, + displayName: fileRef.path, // Use original path for display + estimatedTokens: fileInfo.estimatedTokens, + turnIndex: aiGroup.turnIndex, + aiGroupId: turnGroupId, + exists: fileInfo.exists, + }); + + newInjections.push(mentionedFileInjection); + previousPaths.add(absolutePath); + } + } + } + + // c2) Process @-mentions from isMeta:true user messages in AI responses + for (const fileRef of responseRefs) { + if (!fileRef.path) continue; + + const absolutePath = isAbsolutePath(fileRef.path) + ? fileRef.path + : joinPaths(projectRoot, fileRef.path); + + if (previousPaths.has(absolutePath)) { + continue; + } + + const fileInfo = mentionedFileTokenData?.get(absolutePath); + + if (fileInfo && fileInfo.exists && fileInfo.estimatedTokens <= MAX_MENTIONED_FILE_TOKENS) { + const mentionedFileInjection = createMentionedFileInjection({ + path: absolutePath, + displayName: fileRef.path, + estimatedTokens: fileInfo.estimatedTokens, + turnIndex: aiGroup.turnIndex, + aiGroupId: turnGroupId, + exists: fileInfo.exists, + }); + + newInjections.push(mentionedFileInjection); + previousPaths.add(absolutePath); + } + } + + // d) Aggregate tool outputs (includes user-invoked skill tokens from displayItems) + // Task coordination tools are excluded here (tracked separately in step d2) + const toolOutputInjection = aggregateToolOutputs( + linkedTools, + aiGroup.turnIndex, + turnGroupId, + displayItems + ); + if (toolOutputInjection) { + newInjections.push(toolOutputInjection); + } + + // d2) Aggregate task coordination tokens (SendMessage, TeamCreate, TaskCreate, etc.) + const taskCoordinationInjection = aggregateTaskCoordination( + linkedTools, + aiGroup.turnIndex, + turnGroupId, + displayItems + ); + if (taskCoordinationInjection) { + newInjections.push(taskCoordinationInjection); + } + + // d3) Create user message injection + if (userGroup) { + const userMessageInjection = createUserMessageInjection( + userGroup, + aiGroup.turnIndex, + turnGroupId + ); + if (userMessageInjection) { + newInjections.push(userMessageInjection); + } + } + + // e) Aggregate thinking and text output tokens + if (displayItems) { + const thinkingTextInjection = aggregateThinkingText( + displayItems, + aiGroup.turnIndex, + turnGroupId + ); + if (thinkingTextInjection) { + newInjections.push(thinkingTextInjection); + } + } + + // f) Build accumulated injections + const accumulatedInjections = [...previousInjections, ...newInjections]; + + // g) Calculate totals and category breakdowns + const tokensByCategory: TokensByCategory = { + claudeMd: 0, + mentionedFiles: 0, + toolOutputs: 0, + thinkingText: 0, + taskCoordination: 0, + userMessages: 0, + }; + + const newCounts: NewCountsByCategory = { + claudeMd: 0, + mentionedFiles: 0, + toolOutputs: 0, + thinkingText: 0, + taskCoordination: 0, + userMessages: 0, + }; + + // Count new injections by category + for (const injection of newInjections) { + switch (injection.category) { + case 'claude-md': + newCounts.claudeMd++; + break; + case CATEGORY_MENTIONED_FILE: + newCounts.mentionedFiles++; + break; + case 'tool-output': + newCounts.toolOutputs++; + break; + case 'thinking-text': + newCounts.thinkingText++; + break; + case 'task-coordination': + newCounts.taskCoordination++; + break; + case 'user-message': + newCounts.userMessages++; + break; + } + } + + // Sum tokens by category from accumulated injections + for (const injection of accumulatedInjections) { + switch (injection.category) { + case 'claude-md': + tokensByCategory.claudeMd += injection.estimatedTokens; + break; + case CATEGORY_MENTIONED_FILE: + tokensByCategory.mentionedFiles += injection.estimatedTokens; + break; + case 'tool-output': + tokensByCategory.toolOutputs += injection.estimatedTokens; + break; + case 'thinking-text': + tokensByCategory.thinkingText += injection.estimatedTokens; + break; + case 'task-coordination': + tokensByCategory.taskCoordination += injection.estimatedTokens; + break; + case 'user-message': + tokensByCategory.userMessages += injection.estimatedTokens; + break; + } + } + + const totalEstimatedTokens = + tokensByCategory.claudeMd + + tokensByCategory.mentionedFiles + + tokensByCategory.toolOutputs + + tokensByCategory.thinkingText + + tokensByCategory.taskCoordination + + tokensByCategory.userMessages; + + return { + newInjections, + accumulatedInjections, + totalEstimatedTokens, + tokensByCategory, + newCounts, + }; +} + +// ============================================================================= +// Session Processing +// ============================================================================= + +/** + * Get total tokens from the last assistant message in an AI group. + * Sums input_tokens, output_tokens, cache_read_input_tokens, and cache_creation_input_tokens. + */ +function getLastAssistantTotalTokens(aiGroup: AIGroup): number | undefined { + const responses = aiGroup.responses || []; + for (let i = responses.length - 1; i >= 0; i--) { + const msg = responses[i]; + if (msg.type === 'assistant' && msg.usage) { + return ( + (msg.usage.input_tokens ?? 0) + + (msg.usage.output_tokens ?? 0) + + (msg.usage.cache_read_input_tokens ?? 0) + + (msg.usage.cache_creation_input_tokens ?? 0) + ); + } + } + return undefined; +} + +/** + * Get total tokens from the FIRST assistant message in an AI group. + * Used for post-compaction token measurement: the first response after compaction + * reflects the actual compacted context size before the AI generates more content. + */ +function getFirstAssistantTotalTokens(aiGroup: AIGroup): number | undefined { + const responses = aiGroup.responses || []; + for (const msg of responses) { + if (msg.type === 'assistant' && msg.usage) { + return ( + (msg.usage.input_tokens ?? 0) + + (msg.usage.output_tokens ?? 0) + + (msg.usage.cache_read_input_tokens ?? 0) + + (msg.usage.cache_creation_input_tokens ?? 0) + ); + } + } + return undefined; +} + +/** + * Process all chat items in a session and compute context stats with phase information. + * Returns both the stats map and session-wide phase info. + */ +export function processSessionContextWithPhases( + items: ChatItem[], + projectRoot: string, + claudeMdTokenData?: Record, + mentionedFileTokenData?: Map, + directoryTokenData?: Record +): { statsMap: Map; phaseInfo: ContextPhaseInfo } { + const statsMap = new Map(); + let accumulatedInjections: ContextInjection[] = []; + let isFirstAiGroup = true; + let previousUserGroup: UserGroup | null = null; + + // Phase tracking state + let currentPhaseNumber = 1; + const phases: ContextPhase[] = []; + const aiGroupPhaseMap = new Map(); + const compactionTokenDeltas = new Map(); + + // Track phase boundaries + let currentPhaseFirstAIGroupId: string | null = null; + let currentPhaseLastAIGroupId: string | null = null; + let currentPhaseCompactGroupId: string | null = null; + let lastAIGroupBeforeCompact: AIGroup | null = null; + + for (const item of items) { + // Track user groups for pairing with subsequent AI groups + if (item.type === 'user') { + previousUserGroup = item.group; + continue; + } + + // Handle compact items: reset accumulated state and start new phase + if (item.type === 'compact') { + // Finalize the current phase before starting a new one + if (currentPhaseFirstAIGroupId && currentPhaseLastAIGroupId) { + phases.push({ + phaseNumber: currentPhaseNumber, + firstAIGroupId: currentPhaseFirstAIGroupId, + lastAIGroupId: currentPhaseLastAIGroupId, + compactGroupId: currentPhaseCompactGroupId, + }); + } + + // Reset context tracking state + accumulatedInjections = []; + isFirstAiGroup = true; + previousUserGroup = null; + + // Start new phase + currentPhaseNumber++; + currentPhaseCompactGroupId = item.group.id; + currentPhaseFirstAIGroupId = null; + currentPhaseLastAIGroupId = null; + // Note: lastAIGroupBeforeCompact is intentionally NOT reset here. + // It retains the last AI group from the previous phase so we can + // compute compaction token deltas when the first AI group of the + // new phase is encountered. + + continue; + } + + // Process AI groups + if (item.type === 'ai') { + const aiGroup = item.group; + + // Compute linked tools for this AI group + interface EnhancedAIGroupProps { + linkedTools?: Map; + displayItems?: AIGroupDisplayItem[]; + } + let linkedTools = (aiGroup as AIGroup & EnhancedAIGroupProps).linkedTools; + if (!linkedTools || linkedTools.size === 0) { + linkedTools = linkToolCallsToResults(aiGroup.steps, aiGroup.responses); + } + + let displayItems = (aiGroup as AIGroup & EnhancedAIGroupProps).displayItems; + if (!displayItems && aiGroup.steps && aiGroup.steps.length > 0) { + const lastOutput = findLastOutput(aiGroup.steps, aiGroup.isOngoing ?? false); + displayItems = buildDisplayItems( + aiGroup.steps, + lastOutput, + aiGroup.processes || [], + aiGroup.responses + ); + } + + // Compute stats for this group + const stats = computeContextStats({ + aiGroup, + userGroup: previousUserGroup, + linkedTools, + displayItems, + isFirstGroup: isFirstAiGroup, + previousInjections: accumulatedInjections, + projectRoot, + claudeMdTokenData, + mentionedFileTokenData, + directoryTokenData, + }); + + // Tag with phase number + stats.phaseNumber = currentPhaseNumber; + + // Build compaction token delta for this phase's first AI group + if (isFirstAiGroup && currentPhaseCompactGroupId && lastAIGroupBeforeCompact) { + const preTokens = getLastAssistantTotalTokens(lastAIGroupBeforeCompact); + // Use FIRST assistant message after compaction — it reflects the actual + // compacted context size before the AI generates more content. + const postTokens = getFirstAssistantTotalTokens(aiGroup); + if (preTokens !== undefined && postTokens !== undefined) { + compactionTokenDeltas.set(currentPhaseCompactGroupId, { + preCompactionTokens: preTokens, + postCompactionTokens: postTokens, + delta: postTokens - preTokens, + }); + } + } + + // Store stats + statsMap.set(aiGroup.id, stats); + + // Track phase boundaries + aiGroupPhaseMap.set(aiGroup.id, currentPhaseNumber); + if (!currentPhaseFirstAIGroupId) { + currentPhaseFirstAIGroupId = aiGroup.id; + } + currentPhaseLastAIGroupId = aiGroup.id; + lastAIGroupBeforeCompact = aiGroup; + + // Update accumulated state for next iteration + accumulatedInjections = stats.accumulatedInjections; + isFirstAiGroup = false; + previousUserGroup = null; + } + } + + // Finalize the last phase + if (currentPhaseFirstAIGroupId && currentPhaseLastAIGroupId) { + phases.push({ + phaseNumber: currentPhaseNumber, + firstAIGroupId: currentPhaseFirstAIGroupId, + lastAIGroupId: currentPhaseLastAIGroupId, + compactGroupId: currentPhaseCompactGroupId, + }); + } + + const phaseInfo: ContextPhaseInfo = { + phases, + compactionCount: currentPhaseNumber - 1, + aiGroupPhaseMap, + compactionTokenDeltas, + }; + + return { statsMap, phaseInfo }; +} + +// ============================================================================= +// Utility Functions +// ============================================================================= diff --git a/src/renderer/utils/dateGrouping.ts b/src/renderer/utils/dateGrouping.ts new file mode 100644 index 00000000..718c0351 --- /dev/null +++ b/src/renderer/utils/dateGrouping.ts @@ -0,0 +1,91 @@ +/** + * Date-based session grouping utility. + * Groups sessions by relative date categories: Today, Yesterday, Previous 7 Days, Older. + */ + +import { differenceInDays, isToday, isYesterday } from 'date-fns'; + +import { DATE_CATEGORY_ORDER } from '../types/tabs'; + +import type { Session } from '../types/data'; +import type { DateCategory, DateGroupedSessions } from '../types/tabs'; + +/** + * Groups sessions by relative date category. + * Sessions are categorized based on their createdAt timestamp: + * - Today: Sessions created today + * - Yesterday: Sessions created yesterday + * - Previous 7 Days: Sessions created 2-7 days ago + * - Older: Sessions created more than 7 days ago + * + * Within each category, sessions maintain their original sort order. + * + * @param sessions Array of sessions to group + * @returns Object with sessions grouped by date category + */ +export function groupSessionsByDate(sessions: Session[]): DateGroupedSessions { + const now = new Date(); + + return sessions.reduce( + (acc, session) => { + const sessionDate = new Date(session.createdAt); + + if (isToday(sessionDate)) { + acc.Today.push(session); + } else if (isYesterday(sessionDate)) { + acc.Yesterday.push(session); + } else if (differenceInDays(now, sessionDate) <= 7) { + acc['Previous 7 Days'].push(session); + } else { + acc.Older.push(session); + } + + return acc; + }, + { Today: [], Yesterday: [], 'Previous 7 Days': [], Older: [] } + ); +} + +/** + * Get non-empty date categories in display order. + * Useful for rendering only categories that have sessions. + * + * @param grouped The grouped sessions object + * @returns Array of non-empty category names in display order + */ +export function getNonEmptyCategories(grouped: DateGroupedSessions): DateCategory[] { + return DATE_CATEGORY_ORDER.filter((category) => grouped[category].length > 0); +} + +/** + * Separates sessions into pinned and unpinned groups. + * Pinned sessions are ordered by pin order (from pinnedSessionIds iteration order). + * + * @param sessions All sessions + * @param pinnedSessionIds Ordered array of pinned session IDs (most recently pinned first) + * @returns Object with pinned and unpinned session arrays + */ +export function separatePinnedSessions( + sessions: Session[], + pinnedSessionIds: string[] +): { pinned: Session[]; unpinned: Session[] } { + if (pinnedSessionIds.length === 0) { + return { pinned: [], unpinned: sessions }; + } + + const pinnedSet = new Set(pinnedSessionIds); + const sessionMap = new Map(sessions.map((s) => [s.id, s])); + + // Preserve pin order from pinnedSessionIds + const pinned: Session[] = []; + for (const id of pinnedSessionIds) { + const session = sessionMap.get(id); + if (session) { + pinned.push(session); + } + } + + const unpinned = sessions.filter((s) => !pinnedSet.has(s.id)); + + return { pinned, unpinned }; +} diff --git a/src/renderer/utils/displayItemBuilder.ts b/src/renderer/utils/displayItemBuilder.ts new file mode 100644 index 00000000..b0681e70 --- /dev/null +++ b/src/renderer/utils/displayItemBuilder.ts @@ -0,0 +1,499 @@ +/** + * Display Item Builder - Build display items from semantic steps or messages + * + * Creates a flat chronological list of display items for the AI Group UI. + */ + +import { parseAllTeammateMessages } from '@shared/utils/teammateMessageParser'; + +import { estimateTokens, formatToolInput, formatToolResult, toDate } from './aiGroupHelpers'; +import { extractSlashes, type PrecedingSlashInfo } from './slashCommandExtractor'; +import { linkToolCallsToResults } from './toolLinkingEngine'; + +import type { ParsedMessage, Process, SemanticStep } from '../types/data'; +import type { AIGroupDisplayItem, AIGroupLastOutput, LinkedToolItem } from '../types/groups'; + +/** + * Get the timestamp from a display item for sorting. + */ +function getDisplayItemTimestamp(item: AIGroupDisplayItem): Date { + switch (item.type) { + case 'thinking': + case 'output': + return toDate(item.timestamp); + case 'tool': + return toDate(item.tool.startTime); + case 'subagent': + return toDate(item.subagent.startTime); + case 'slash': + return toDate(item.slash.timestamp); + case 'teammate_message': + return toDate(item.teammateMessage.timestamp); + } +} + +/** + * Sort display items chronologically. + */ +function sortDisplayItemsChronologically(items: AIGroupDisplayItem[]): void { + items.sort((a, b) => getDisplayItemTimestamp(a).getTime() - getDisplayItemTimestamp(b).getTime()); +} + +/** + * Link TeammateMessages to their triggering SendMessage calls. + * For each TeammateMessage, scans backwards through chronologically sorted items + * to find the most recent SendMessage to that teammate. + * Only matches type: "message" or "broadcast" (not shutdown_request/shutdown_response). + * Proactive messages (no preceding SendMessage) get no badge. + */ +function linkTeammateReplies(items: AIGroupDisplayItem[]): void { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.type !== 'teammate_message') continue; + const tmMsg = item.teammateMessage; + + // Scan backwards for the most recent SendMessage to this teammate + for (let j = i - 1; j >= 0; j--) { + const prev = items[j]; + if (prev.type !== 'tool') continue; + if (prev.tool.name !== 'SendMessage') continue; + const input = prev.tool.input; + // Only match outbound messages (not shutdown_request, shutdown_response, etc.) + if (input.type !== 'message' && input.type !== 'broadcast') continue; + // Match by recipient (broadcast goes to all, so always matches) + if (input.type === 'message' && input.recipient !== tmMsg.teammateId) continue; + + tmMsg.replyToSummary = (input.summary as string) || 'message'; + tmMsg.replyToToolId = prev.tool.id; + break; + } + } +} + +/** + * Build a flat chronological list of display items for the AI Group. + * + * Strategy: + * 1. Skip the step that represents lastOutput (to avoid duplication) + * 2. For tool_call steps, use the LinkedToolItem (which includes the result) + * 3. Skip standalone tool_result steps (already linked to calls) + * 4. Skip Task tool_call steps that have associated subagents (avoid duplication) + * 5. Include thinking, subagent, and output steps + * 6. Return items in chronological order + * + * @param steps - Semantic steps from the AI Group + * @param lastOutput - The last output to skip + * @param subagents - Subagents associated with this group + * @param responses - Optional raw messages for extracting slash instructions + * @param precedingSlash - Optional slash info from the preceding UserGroup + * @returns Flat array of display items + */ +export function buildDisplayItems( + steps: SemanticStep[], + lastOutput: AIGroupLastOutput | null, + subagents: Process[], + responses?: ParsedMessage[], + precedingSlash?: PrecedingSlashInfo +): AIGroupDisplayItem[] { + const displayItems: AIGroupDisplayItem[] = []; + const linkedTools = linkToolCallsToResults(steps, responses); + + // Build set of Task IDs that have associated subagents + // This prevents duplicate display of Task tool calls when subagents are shown + const taskIdsWithSubagents = new Set( + subagents.map((s) => s.parentTaskId).filter((id): id is string => !!id) + ); + + // Find the step ID of lastOutput to skip it + let lastOutputStepId: string | undefined; + if (lastOutput) { + for (let i = steps.length - 1; i >= 0; i--) { + const step = steps[i]; + if ( + lastOutput.type === 'text' && + step.type === 'output' && + step.content.outputText === lastOutput.text + ) { + lastOutputStepId = step.id; + break; + } + if ( + lastOutput.type === 'tool_result' && + step.type === 'tool_result' && + step.content.toolResultContent === lastOutput.toolResult + ) { + lastOutputStepId = step.id; + break; + } + if ( + lastOutput.type === 'interruption' && + step.type === 'interruption' && + step.content.interruptionText === lastOutput.interruptionMessage + ) { + lastOutputStepId = step.id; + break; + } + } + } + + // Build display items + for (const step of steps) { + // Skip the last output step + if (lastOutputStepId && step.id === lastOutputStepId) { + continue; + } + + switch (step.type) { + case 'thinking': + if (step.content.thinkingText) { + displayItems.push({ + type: 'thinking', + content: step.content.thinkingText, + timestamp: step.startTime, + tokenCount: estimateTokens(step.content.thinkingText), + }); + } + break; + + case 'tool_call': { + const linkedTool = linkedTools.get(step.id); + if (linkedTool) { + // Skip Task tool calls that have associated subagents + // The subagent will be shown separately, so showing the Task call is redundant + const isTaskWithSubagent = + linkedTool.name === 'Task' && taskIdsWithSubagents.has(step.id); + if (!isTaskWithSubagent) { + displayItems.push({ + type: 'tool', + tool: linkedTool, + }); + } + } + break; + } + + case 'tool_result': + // Skip - these are already included in LinkedToolItem + break; + + case 'subagent': { + const subagentId = step.content.subagentId; + const subagent = subagents.find((s) => s.id === subagentId); + if (subagent) { + displayItems.push({ + type: 'subagent', + subagent: subagent, + }); + } + break; + } + + case 'output': + if (step.content.outputText) { + displayItems.push({ + type: 'output', + content: step.content.outputText, + timestamp: step.startTime, + tokenCount: estimateTokens(step.content.outputText), + }); + } + break; + + case 'interruption': + if (step.content.interruptionText) { + displayItems.push({ + type: 'output', + content: step.content.interruptionText, + timestamp: step.startTime, + tokenCount: estimateTokens(step.content.interruptionText), + }); + } + break; + } + } + + // Add slashes as display items + if (responses) { + const slashes = extractSlashes(responses, precedingSlash); + for (const slash of slashes) { + displayItems.push({ + type: 'slash', + slash, + }); + } + } + + // Add teammate messages from responses (one user message may contain multiple blocks) + if (responses) { + for (const msg of responses) { + if (msg.type !== 'user' || msg.isMeta) continue; + const rawText = + typeof msg.content === 'string' + ? msg.content + : Array.isArray(msg.content) + ? msg.content + .filter((b) => b.type === 'text') + .map((b) => b.text) + .join('') + : ''; + const parsedBlocks = parseAllTeammateMessages(rawText); + for (const parsed of parsedBlocks) { + displayItems.push({ + type: 'teammate_message', + teammateMessage: { + id: `${msg.uuid}-${parsed.teammateId}-${displayItems.length}`, + teammateId: parsed.teammateId, + color: parsed.color, + summary: parsed.summary, + content: parsed.content, + timestamp: toDate(msg.timestamp), + tokenCount: estimateTokens(parsed.content), + }, + }); + } + } + } + + // Sort all items chronologically to ensure slashes appear in correct order + sortDisplayItemsChronologically(displayItems); + + // Link TeammateMessages to their triggering SendMessage calls + linkTeammateReplies(displayItems); + + return displayItems; +} + +/** + * Build display items from raw ParsedMessages (used by subagents). + * This mirrors the logic of buildDisplayItems but works with messages instead of SemanticSteps. + * + * Strategy: + * 1. Extract thinking blocks from assistant messages + * 2. Extract tool_use blocks from assistant messages -> collect in a Map by ID + * 3. Extract text output blocks from assistant messages + * 4. Extract tool_result blocks from user messages (isMeta or toolResults exist) + * 5. Link tool calls with their results using LinkedToolItem structure + * 6. Filter Task tool calls that have matching subagents + * 7. Include subagents as separate items + * 8. Sort all items chronologically + * + * @param messages - Raw ParsedMessages to process + * @param subagents - Subagents associated with these messages + * @returns Flat array of display items + */ +export function buildDisplayItemsFromMessages( + messages: ParsedMessage[], + subagents: Process[] = [] +): AIGroupDisplayItem[] { + const displayItems: AIGroupDisplayItem[] = []; + + // Maps for tool call/result linking + const toolCallsById = new Map< + string, + { + id: string; + name: string; + input: Record; + timestamp: Date; + sourceMessageId: string; + sourceModel?: string; + } + >(); + + const toolResultsById = new Map< + string, + { + content: string | unknown[]; + isError: boolean; + toolUseResult?: Record; + timestamp: Date; + } + >(); + + // Map to collect skill instructions by source tool use ID + // Skill tools have follow-up isMeta:true messages with instructions starting with "Base directory for this skill:" + const skillInstructionsById = new Map(); + + // Build set of Task IDs that have associated subagents + // This prevents duplicate display of Task tool calls when subagents are shown + const taskIdsWithSubagents = new Set( + subagents.map((s) => s.parentTaskId).filter((id): id is string => !!id) + ); + + // First pass: collect tool calls and tool results from messages + for (const msg of messages) { + const msgTimestamp = toDate(msg.timestamp); + + // Check for teammate messages (non-meta user messages with content) + // One user message may contain multiple blocks + if (msg.type === 'user' && !msg.isMeta) { + const rawText = + typeof msg.content === 'string' + ? msg.content + : Array.isArray(msg.content) + ? msg.content + .filter((b) => b.type === 'text') + .map((b) => b.text) + .join('') + : ''; + const parsedBlocks = parseAllTeammateMessages(rawText); + if (parsedBlocks.length > 0) { + for (const parsed of parsedBlocks) { + displayItems.push({ + type: 'teammate_message', + teammateMessage: { + id: `${msg.uuid}-${parsed.teammateId}-${displayItems.length}`, + teammateId: parsed.teammateId, + color: parsed.color, + summary: parsed.summary, + content: parsed.content, + timestamp: msgTimestamp, + tokenCount: estimateTokens(parsed.content), + }, + }); + } + continue; + } + } + + if (msg.type === 'assistant' && Array.isArray(msg.content)) { + // Process assistant message content blocks + for (const block of msg.content) { + if (block.type === 'thinking' && block.thinking) { + // Add thinking block + displayItems.push({ + type: 'thinking', + content: block.thinking, + timestamp: msgTimestamp, + tokenCount: estimateTokens(block.thinking), + }); + } else if (block.type === 'tool_use' && block.id && block.name) { + // Collect tool call for later linking + toolCallsById.set(block.id, { + id: block.id, + name: block.name, + input: block.input ?? {}, + timestamp: msgTimestamp, + sourceMessageId: msg.uuid, + sourceModel: msg.model, + }); + } else if (block.type === 'text' && block.text) { + // Add text output + displayItems.push({ + type: 'output', + content: block.text, + timestamp: msgTimestamp, + tokenCount: estimateTokens(block.text), + }); + } + } + } else if (msg.type === 'user' && (msg.isMeta || msg.toolResults.length > 0)) { + // Process tool results from internal user messages + if (Array.isArray(msg.content)) { + for (const block of msg.content) { + if (block.type === 'tool_result' && block.tool_use_id) { + // Collect tool result for linking + toolResultsById.set(block.tool_use_id, { + content: block.content ?? '', + isError: block.is_error ?? false, + toolUseResult: msg.toolUseResult, + timestamp: msgTimestamp, + }); + } + + // Check for skill instructions: isMeta:true messages with sourceToolUseID + // containing text starting with "Base directory for this skill:" + if (block.type === 'text' && block.text && msg.sourceToolUseID) { + const text = block.text; + if (text.startsWith('Base directory for this skill:')) { + skillInstructionsById.set(msg.sourceToolUseID, text); + } + } + } + } + + // Also check msg.toolResults array (pre-extracted results) + for (const result of msg.toolResults) { + if (!toolResultsById.has(result.toolUseId)) { + toolResultsById.set(result.toolUseId, { + content: result.content, + isError: result.isError, + toolUseResult: msg.toolUseResult, + timestamp: msgTimestamp, + }); + } + } + } + } + + // Second pass: Build LinkedToolItems by matching calls with results + for (const [toolId, call] of toolCallsById.entries()) { + const result = toolResultsById.get(toolId); + + // Skip Task tool calls that have associated subagents + // The subagent will be shown separately, so showing the Task call is redundant + const isTaskWithSubagent = call.name === 'Task' && taskIdsWithSubagents.has(toolId); + if (isTaskWithSubagent) { + continue; + } + + // Get skill instructions for Skill tool calls + const skillInstructions = call.name === 'Skill' ? skillInstructionsById.get(toolId) : undefined; + + const linkedItem: LinkedToolItem = { + id: toolId, + name: call.name, + input: call.input, + result: result + ? { + content: result.content, + isError: result.isError, + toolUseResult: result.toolUseResult, + } + : undefined, + inputPreview: formatToolInput(call.input), + outputPreview: result ? formatToolResult(result.content) : undefined, + startTime: call.timestamp, + endTime: result?.timestamp, + durationMs: result?.timestamp + ? result.timestamp.getTime() - call.timestamp.getTime() + : undefined, + isOrphaned: !result, + sourceModel: call.sourceModel, + skillInstructions, + skillInstructionsTokenCount: skillInstructions + ? estimateTokens(skillInstructions) + : undefined, + }; + + displayItems.push({ + type: 'tool', + tool: linkedItem, + }); + } + + // Add subagents as display items + for (const subagent of subagents) { + displayItems.push({ + type: 'subagent', + subagent: subagent, + }); + } + + // Add slashes as display items + const slashes = extractSlashes(messages); + for (const slash of slashes) { + displayItems.push({ + type: 'slash', + slash, + }); + } + + // Sort all items chronologically + sortDisplayItemsChronologically(displayItems); + + // Link TeammateMessages to their triggering SendMessage calls + linkTeammateReplies(displayItems); + + return displayItems; +} diff --git a/src/renderer/utils/displaySummary.ts b/src/renderer/utils/displaySummary.ts new file mode 100644 index 00000000..28c6c31d --- /dev/null +++ b/src/renderer/utils/displaySummary.ts @@ -0,0 +1,67 @@ +/** + * Display Summary - Build human-readable summaries of display items + * + * Creates formatted summary strings for AI Group display item counts. + */ + +import type { AIGroupDisplayItem } from '../types/groups'; + +/** + * Build a human-readable summary of display items. + * + * Strategy: + * 1. Count items by type (thinking, tool, output, subagent, slash) + * 2. Format as "X thinking, Y tool calls, Z messages, N subagents, M slashes" + * 3. Skip counts that are zero + * 4. Return formatted string + * + * @param items - Display items to summarize + * @returns Formatted summary string + */ +export function buildSummary(items: AIGroupDisplayItem[]): string { + const counts = { + thinking: 0, + tool: 0, + output: 0, + subagent: 0, + slash: 0, + teammate_message: 0, + }; + const teammateNames = new Set(); + + for (const item of items) { + if (item.type === 'subagent' && item.subagent.team) { + teammateNames.add(item.subagent.team.memberName); + } else { + counts[item.type]++; + } + } + + const parts: string[] = []; + + if (counts.thinking > 0) { + parts.push(`${counts.thinking} thinking`); + } + if (counts.tool > 0) { + parts.push(`${counts.tool} tool ${counts.tool === 1 ? 'call' : 'calls'}`); + } + if (counts.output > 0) { + parts.push(`${counts.output} ${counts.output === 1 ? 'message' : 'messages'}`); + } + if (teammateNames.size > 0) { + parts.push(`${teammateNames.size} ${teammateNames.size === 1 ? 'teammate' : 'teammates'}`); + } + if (counts.subagent > 0) { + parts.push(`${counts.subagent} ${counts.subagent === 1 ? 'subagent' : 'subagents'}`); + } + if (counts.slash > 0) { + parts.push(`${counts.slash} ${counts.slash === 1 ? 'slash' : 'slashes'}`); + } + if (counts.teammate_message > 0) { + parts.push( + `${counts.teammate_message} teammate ${counts.teammate_message === 1 ? 'message' : 'messages'}` + ); + } + + return parts.length > 0 ? parts.join(', ') : 'No items'; +} diff --git a/src/renderer/utils/formatters.ts b/src/renderer/utils/formatters.ts new file mode 100644 index 00000000..1ebc9f16 --- /dev/null +++ b/src/renderer/utils/formatters.ts @@ -0,0 +1,22 @@ +/** + * Formatting utility functions for display values. + */ + +// Re-export token formatting from shared module +export { formatTokensCompact } from '@shared/utils/tokenFormatting'; + +/** + * Formats duration in milliseconds to a human-readable string. + */ +export function formatDuration(ms: number): string { + if (ms < 1000) { + return `${Math.round(ms)}ms`; + } + const seconds = ms / 1000; + if (seconds < 60) { + return `${seconds.toFixed(1)}s`; + } + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.round(seconds % 60); + return `${minutes}m ${remainingSeconds}s`; +} diff --git a/src/renderer/utils/groupTransformer.ts b/src/renderer/utils/groupTransformer.ts new file mode 100644 index 00000000..45a68a83 --- /dev/null +++ b/src/renderer/utils/groupTransformer.ts @@ -0,0 +1,700 @@ +/** + * Transforms EnhancedChunk[] into SessionConversation structure. + * + * This module converts chunk-based data into a flat list of ChatItems + * (UserGroups, SystemGroups, AIGroups) for a chat-style display. + * Each item is independent - no pairing between user and AI chunks. + */ + +import { + isAssistantMessage, + isEnhancedAIChunk, + isEnhancedCompactChunk, + isEnhancedSystemChunk, + isEnhancedUserChunk, +} from '@renderer/types/data'; +import { getFirstSegment, hasPathSeparator, isRelativePath } from '@renderer/utils/pathUtils'; +import { isCommandContent, sanitizeDisplayContent } from '@shared/utils/contentSanitizer'; +import { createLogger } from '@shared/utils/logger'; + +import type { + EnhancedAIChunk, + EnhancedChunk, + EnhancedCompactChunk, + EnhancedSystemChunk, + EnhancedUserChunk, + ParsedMessage, + Process, + SemanticStep, +} from '@renderer/types/data'; +import type { + AIGroup, + AIGroupStatus, + AIGroupSummary, + AIGroupTokens, + ChatItem, + CommandInfo, + CompactGroup, + FileReference, + ImageData, + SessionConversation, + SystemGroup, + UserGroup, + UserGroupContent, +} from '@renderer/types/groups'; + +const logger = createLogger('Util:groupTransformer'); + +// ============================================================================= +// Constants +// ============================================================================= + +/** + * Regex pattern for detecting slash commands. + * Matches: /command-name [optional args] + * Uses non-greedy matching and limited repetition to prevent ReDoS. + */ +// eslint-disable-next-line security/detect-unsafe-regex -- Pattern is safe: limited to 1000 chars and used on bounded user input +const COMMAND_PATTERN = /\/([a-z][a-z-]{0,50})(?:\s+(\S[^\n]{0,1000}))?$/gim; + +/** + * Maximum characters to extract for thinking preview. + */ +const THINKING_PREVIEW_LENGTH = 100; + +// ============================================================================= +// Main Transformation Function +// ============================================================================= + +/** + * Transforms EnhancedChunk[] into SessionConversation. + * + * Produces a flat list of independent ChatItems (user, system, AI). + * Each chunk type becomes its own item - no pairing or grouping. + * + * @param chunks - Array of enhanced chunks with semantic steps + * @param _subagents - Array of all subagents in the session (unused, processes come from chunks) + * @param isOngoing - Whether the session is still in progress (marks last AI group) + * @returns SessionConversation structure for chat-style rendering + */ +export function transformChunksToConversation( + chunks: EnhancedChunk[], + _subagents: Process[], + isOngoing: boolean = false +): SessionConversation { + if (!chunks || chunks.length === 0) { + return { + sessionId: '', + items: [], + totalUserGroups: 0, + totalSystemGroups: 0, + totalAIGroups: 0, + totalCompactGroups: 0, + }; + } + + const items: ChatItem[] = []; + let userCount = 0; + let systemCount = 0; + let aiCount = 0; + let compactCount = 0; + + for (const chunk of chunks) { + if (isEnhancedUserChunk(chunk)) { + items.push({ + type: 'user', + group: createUserGroupFromChunk(chunk, userCount++), + }); + } else if (isEnhancedSystemChunk(chunk)) { + items.push({ + type: 'system', + group: createSystemGroup(chunk), + }); + systemCount++; + } else if (isEnhancedAIChunk(chunk)) { + items.push({ + type: 'ai', + group: createAIGroupFromChunk(chunk, aiCount), + }); + aiCount++; + } else if (isEnhancedCompactChunk(chunk)) { + items.push({ + type: 'compact', + group: createCompactGroup(chunk), + }); + compactCount++; + } else { + const unhandledChunkType = + 'chunkType' in chunk ? (chunk as EnhancedChunk).chunkType : 'unknown'; + logger.warn('Unhandled chunk type:', unhandledChunkType); + } + } + + // Post-pass: enrich CompactGroups with token deltas + let phaseCounter = 1; + for (let i = 0; i < items.length; i++) { + if (items[i].type === 'compact') { + phaseCounter++; + const compactItem = items[i] as { type: 'compact'; group: CompactGroup }; + compactItem.group.startingPhaseNumber = phaseCounter; + + // Find last AI group before and first AI group after + const preAi = findLastAiBefore(items, i); + const postAi = findFirstAiAfter(items, i); + if (preAi && postAi) { + const pre = getLastAssistantTotalTokens(preAi); + // Use FIRST assistant message after compaction — it reflects the actual + // compacted context size before the AI generates more content. + const post = getFirstAssistantTotalTokens(postAi); + if (pre !== undefined && post !== undefined) { + compactItem.group.tokenDelta = { + preCompactionTokens: pre, + postCompactionTokens: post, + delta: post - pre, + }; + } + } + } + } + + // If session is ongoing, mark the last AI group (but don't override interrupted status) + if (isOngoing && aiCount > 0) { + // Find the last AI item and mark it as ongoing + for (let i = items.length - 1; i >= 0; i--) { + const item = items[i]; + if (item.type === 'ai') { + const currentStatus = item.group.status; + // Don't override 'interrupted' status - interruption takes precedence over ongoing + if (currentStatus !== 'interrupted') { + (item.group as AIGroup & { isOngoing?: boolean }).isOngoing = true; + (item.group as AIGroup & { status?: AIGroupStatus }).status = 'in_progress'; + } + break; + } + } + } + + return { + sessionId: chunks[0]?.id ?? 'unknown', + items, + totalUserGroups: userCount, + totalSystemGroups: systemCount, + totalAIGroups: aiCount, + totalCompactGroups: compactCount, + }; +} + +// ============================================================================= +// UserGroup Creation +// ============================================================================= + +/** + * Creates a UserGroup from an EnhancedUserChunk. + * + * @param chunk - The user chunk to transform + * @param index - Index within the session (for ordering) + * @returns UserGroup with parsed content + */ +function createUserGroupFromChunk(chunk: EnhancedUserChunk, index: number): UserGroup { + return createUserGroup(chunk.userMessage, index); +} + +/** + * Creates a UserGroup from a ParsedMessage. + * + * @param message - The user's input message + * @param index - Index within the session (for ordering) + * @returns UserGroup with parsed content + */ +function createUserGroup(message: ParsedMessage, index: number): UserGroup { + const content = extractUserGroupContent(message); + + return { + id: `user-${message.uuid}`, + message, + timestamp: message.timestamp, + content, + index, + }; +} + +/** + * Extracts and parses content from a user message. + * + * @param message - The user message to parse + * @returns Parsed UserGroupContent + */ +function extractUserGroupContent(message: ParsedMessage): UserGroupContent { + let rawText = ''; + const images: ImageData[] = []; + const fileReferences: FileReference[] = []; + + // Extract text from content + // Note: Image handling not yet implemented - images are not part of ContentBlock type + if (typeof message.content === 'string') { + rawText = message.content; + } else if (Array.isArray(message.content)) { + for (const block of message.content) { + if (block.type === 'text' && block.text) { + rawText += block.text; + } + } + } + + // Sanitize content for display (handles XML tags from command messages) + // This converts /model to "/model" + const sanitizedText = sanitizeDisplayContent(rawText); + + // Check if this is a command message (for special handling) + const isCommand = isCommandContent(rawText); + + // Extract commands from the sanitized text (for inline /commands in regular messages) + // For command messages, the command is already extracted as sanitizedText + const commands = isCommand ? [] : extractCommands(sanitizedText); + + // Extract file references (@file.ts) from sanitized text + fileReferences.push(...extractFileReferences(sanitizedText)); + + // For command messages, use the sanitized command as display text + // For regular messages, remove inline commands from display + let displayText = sanitizedText; + if (!isCommand) { + for (const cmd of commands) { + displayText = displayText.replace(cmd.raw, '').trim(); + } + } + + return { + text: displayText || undefined, + rawText: sanitizedText, // Use sanitized version as rawText for display + commands, + images, + fileReferences, + }; +} + +/** + * Extracts commands from text using regex. + * + * @param text - Text to parse for commands + * @returns Array of CommandInfo objects + */ +function extractCommands(text: string): CommandInfo[] { + if (!text) return []; + + const commands: CommandInfo[] = []; + let match: RegExpExecArray | null; + + // Reset regex state + COMMAND_PATTERN.lastIndex = 0; + + while ((match = COMMAND_PATTERN.exec(text)) !== null) { + const [fullMatch, commandName, args] = match; + commands.push({ + name: commandName, + args: args?.trim(), + raw: fullMatch, + startIndex: match.index, + endIndex: match.index + fullMatch.length, + }); + } + + return commands; +} + +/** + * Known directory prefixes that identify file references. + */ +const KNOWN_DIRS = new Set([ + 'src', + 'apps', + 'app', + 'lib', + 'types', + 'packages', + 'components', + 'utils', + 'services', + 'hooks', + 'store', + 'renderer', + 'main', + 'preload', + 'public', + 'assets', + 'config', + 'tests', + 'test', + 'specs', + 'spec', + 'e2e', + 'docs', + 'scripts', + 'screens', + 'features', + 'pages', + 'views', + 'models', + 'controllers', + 'routes', + 'middleware', + 'api', + 'common', + 'shared', + 'core', + 'modules', + 'client', + 'server', + 'web', + 'mobile', + 'native', + 'electron', + 'node_modules', +]); + +/** + * Simple pattern for detecting @ mentions that could be file paths. + * The filtering logic in extractFileReferences determines validity. + */ +const FILE_REF_PATTERN = /@([~a-zA-Z0-9._/-]+)/g; + +/** + * Checks if a path looks like a valid file reference. + * Must start with known dir, contain /, or start with ./ or ../ + */ +function isValidFileRef(path: string): boolean { + // Check for relative path indicators + if (isRelativePath(path)) { + return true; + } + // Check if starts with known directory + const first = getFirstSegment(path); + if (KNOWN_DIRS.has(first)) { + return true; + } + // Check if contains a path separator (indicates directory structure) + if (hasPathSeparator(path) && path.length > 2) { + return true; + } + return false; +} + +/** + * Extracts file references (@file.ts) from text. + * + * @param text - Text to parse for file references + * @returns Array of FileReference objects + */ +export function extractFileReferences(text: string): FileReference[] { + if (!text) return []; + + const references: FileReference[] = []; + // Reset regex state before use + FILE_REF_PATTERN.lastIndex = 0; + let match: RegExpExecArray | null; + + while ((match = FILE_REF_PATTERN.exec(text)) !== null) { + const [fullMatch, path] = match; + // Only include if it looks like a valid file reference + if (isValidFileRef(path)) { + references.push({ + path, + raw: fullMatch, + }); + } + } + + return references; +} + +// ============================================================================= +// SystemGroup Creation +// ============================================================================= + +/** + * Creates a SystemGroup from an EnhancedSystemChunk. + * + * @param chunk - The system chunk to transform + * @returns SystemGroup with command output + */ +function createSystemGroup(chunk: EnhancedSystemChunk): SystemGroup { + return { + id: chunk.id, // Use stable chunk ID instead of array index + message: chunk.message, + timestamp: chunk.startTime, + commandOutput: chunk.commandOutput, + }; +} + +// ============================================================================= +// CompactGroup Creation +// ============================================================================= + +/** + * Creates a CompactGroup from an EnhancedCompactChunk. + * + * @param chunk - The compact chunk to transform + * @returns CompactGroup marking where conversation was compacted, with message content + */ +function createCompactGroup(chunk: EnhancedCompactChunk): CompactGroup { + return { + id: chunk.id, // Use stable chunk ID instead of array index + timestamp: chunk.startTime, + message: chunk.message, // Pass through the compact summary message + }; +} + +// ============================================================================= +// AIGroup Creation +// ============================================================================= + +/** + * Creates an AIGroup from an EnhancedAIChunk. + * + * @param chunk - The AI chunk to transform + * @param turnIndex - 0-based index of this AI group within the session + * @returns AIGroup with semantic steps and metrics + */ +function createAIGroupFromChunk(chunk: EnhancedAIChunk, turnIndex: number): AIGroup { + const steps = chunk.semanticSteps; + + // Calculate timing from all steps + const startTime = steps.length > 0 ? steps[0].startTime : chunk.startTime; + const endTime = + steps.length > 0 + ? (steps[steps.length - 1].endTime ?? steps[steps.length - 1].startTime) + : chunk.endTime; + const durationMs = endTime.getTime() - startTime.getTime(); + + // Find any source assistant message for token calculation + const sourceMessage = chunk.responses.find((msg) => isAssistantMessage(msg)) ?? null; + + // Calculate tokens from all steps + const tokens = calculateTokensFromSteps(steps, sourceMessage); + + // Generate summary from all steps + const summary = computeAIGroupSummary(steps); + + // Determine status from all steps + const status = determineAIGroupStatus(steps); + + return { + id: chunk.id, // Use stable chunk ID instead of array index + turnIndex, + startTime, + endTime, + durationMs, + steps, + tokens, + summary, + status, + processes: chunk.processes, + chunkId: chunk.id, + metrics: chunk.metrics, + responses: chunk.responses, + }; +} + +/** + * Calculates token metrics from semantic steps and source message. + * + * @param steps - Semantic steps in this AI Group + * @param sourceMessage - Source assistant message (if available) + * @returns Token metrics + */ +function calculateTokensFromSteps( + steps: SemanticStep[], + sourceMessage: ParsedMessage | null | undefined +): AIGroupTokens { + let input = 0; + let output = 0; + let cached = 0; + let thinking = 0; + + // Sum from steps + for (const step of steps) { + if (step.tokens) { + input += step.tokens.input ?? 0; + output += step.tokens.output ?? 0; + cached += step.tokens.cached ?? 0; + } + if (step.tokenBreakdown) { + input += step.tokenBreakdown.input ?? 0; + output += step.tokenBreakdown.output ?? 0; + cached += step.tokenBreakdown.cacheRead ?? 0; + } + if (step.type === 'thinking' && step.tokens?.output) { + thinking += step.tokens.output; + } + } + + // Override with source message usage if available (more accurate) + if (sourceMessage?.usage) { + input = sourceMessage.usage.input_tokens ?? 0; + output = sourceMessage.usage.output_tokens ?? 0; + cached = sourceMessage.usage.cache_read_input_tokens ?? 0; + } + + return { + input, + output, + cached, + thinking, + }; +} + +// ============================================================================= +// AIGroup Summary & Status Computation +// ============================================================================= + +/** + * Computes summary statistics for an AIGroup's collapsed view. + * + * @param steps - Semantic steps in the AI Group + * @returns Summary statistics + */ +function computeAIGroupSummary(steps: SemanticStep[]): AIGroupSummary { + let thinkingPreview: string | undefined; + let toolCallCount = 0; + let outputMessageCount = 0; + let subagentCount = 0; + let totalDurationMs = 0; + let totalTokens = 0; + let outputTokens = 0; + let cachedTokens = 0; + + for (const step of steps) { + // Extract thinking preview from first thinking step + if (!thinkingPreview && step.type === 'thinking' && step.content.thinkingText) { + const fullText = step.content.thinkingText; + thinkingPreview = + fullText.length > THINKING_PREVIEW_LENGTH + ? fullText.slice(0, THINKING_PREVIEW_LENGTH) + '...' + : fullText; + } + + // Count step types + if (step.type === 'tool_call') toolCallCount++; + if (step.type === 'output') outputMessageCount++; + if (step.type === 'subagent') subagentCount++; + + // Sum duration + totalDurationMs += step.durationMs ?? 0; + + // Sum tokens + if (step.tokens) { + totalTokens += (step.tokens.input ?? 0) + (step.tokens.output ?? 0); + outputTokens += step.tokens.output ?? 0; + cachedTokens += step.tokens.cached ?? 0; + } + if (step.tokenBreakdown) { + totalTokens += step.tokenBreakdown.input + step.tokenBreakdown.output; + outputTokens += step.tokenBreakdown.output; + cachedTokens += step.tokenBreakdown.cacheRead; + } + } + + return { + thinkingPreview, + toolCallCount, + outputMessageCount, + subagentCount, + totalDurationMs, + totalTokens, + outputTokens, + cachedTokens, + }; +} + +/** + * Determines the status of an AIGroup based on its steps. + * + * @param steps - Semantic steps in the AI Group + * @returns AIGroupStatus + */ +function determineAIGroupStatus(steps: SemanticStep[]): AIGroupStatus { + if (steps.length === 0) return 'error'; + + // Check for interruption + const hasInterruption = steps.some((step) => step.type === 'interruption'); + if (hasInterruption) return 'interrupted'; + + // Check for errors + const hasError = steps.some((step) => step.type === 'tool_result' && step.content.isError); + if (hasError) return 'error'; + + // Check if any step is incomplete (no endTime) + const hasIncomplete = steps.some((step) => !step.endTime); + if (hasIncomplete) return 'in_progress'; + + // Otherwise, complete + return 'complete'; +} + +// ============================================================================= +// CompactGroup Enrichment Helpers +// ============================================================================= + +/** + * Find the last AI group before a given index in the items array. + */ +function findLastAiBefore(items: ChatItem[], index: number): AIGroup | null { + for (let i = index - 1; i >= 0; i--) { + if (items[i].type === 'ai') return items[i].group as AIGroup; + } + return null; +} + +/** + * Find the first AI group after a given index in the items array. + */ +function findFirstAiAfter(items: ChatItem[], index: number): AIGroup | null { + for (let i = index + 1; i < items.length; i++) { + if (items[i].type === 'ai') return items[i].group as AIGroup; + } + return null; +} + +/** + * Get total tokens from the last assistant message in an AI group. + * Sums input_tokens, output_tokens, cache_read_input_tokens, and cache_creation_input_tokens. + */ +function getLastAssistantTotalTokens(aiGroup: AIGroup): number | undefined { + const responses = aiGroup.responses || []; + for (let i = responses.length - 1; i >= 0; i--) { + const msg = responses[i]; + if (msg.type === 'assistant' && msg.usage) { + return ( + (msg.usage.input_tokens ?? 0) + + (msg.usage.output_tokens ?? 0) + + (msg.usage.cache_read_input_tokens ?? 0) + + (msg.usage.cache_creation_input_tokens ?? 0) + ); + } + } + return undefined; +} + +/** + * Get total tokens from the FIRST assistant message in an AI group. + * Used for post-compaction token measurement: the first response after compaction + * reflects the actual compacted context size before the AI generates more content. + */ +function getFirstAssistantTotalTokens(aiGroup: AIGroup): number | undefined { + const responses = aiGroup.responses || []; + for (const msg of responses) { + if (msg.type === 'assistant' && msg.usage) { + return ( + (msg.usage.input_tokens ?? 0) + + (msg.usage.output_tokens ?? 0) + + (msg.usage.cache_read_input_tokens ?? 0) + + (msg.usage.cache_creation_input_tokens ?? 0) + ); + } + } + return undefined; +} + +// ============================================================================= +// Helper Functions +// ============================================================================= diff --git a/src/renderer/utils/lastOutputDetector.ts b/src/renderer/utils/lastOutputDetector.ts new file mode 100644 index 00000000..032a8037 --- /dev/null +++ b/src/renderer/utils/lastOutputDetector.ts @@ -0,0 +1,150 @@ +/** + * Last Output Detector - Find the last visible output in an AI Group + * + * Uses a state machine approach to find the last meaningful output + * for display in the chat UI. + */ + +import { toDate } from './aiGroupHelpers'; + +import type { SemanticStep } from '../types/data'; +import type { AIGroupLastOutput } from '../types/groups'; + +/** + * Find the last visible output in the AI Group. + * + * Strategy: + * 1. If isOngoing is true, return 'ongoing' type (session still in progress) + * 2. Check for ExitPlanMode tool_call as special 'plan_exit' type + * 3. Iterate through steps in reverse order + * 4. Find the last 'output' step with outputText + * 5. If no output found, find the last 'tool_result' step + * 6. If no tool_result found, find the last 'interruption' step + * 7. Return null if none exists + * + * Special case: ExitPlanMode + * When the last tool_call is ExitPlanMode, return 'plan_exit' type with the plan content. + * The preamble text (if any) is captured from the preceding output step. + * + * @param steps - Semantic steps from the AI Group + * @param isOngoing - Whether this AI group is still in progress + * @returns The last output or null + */ +export function findLastOutput( + steps: SemanticStep[], + isOngoing: boolean = false +): AIGroupLastOutput | null { + // Check for interruption first - interruption takes precedence over ongoing status + // This ensures user interruptions are always visible even if session appears ongoing + for (let i = steps.length - 1; i >= 0; i--) { + const step = steps[i]; + if (step.type === 'interruption') { + return { + type: 'interruption', + timestamp: step.startTime, + }; + } + } + + // If session is ongoing (and no interruption), return 'ongoing' type + if (isOngoing) { + return { + type: 'ongoing', + timestamp: steps.length > 0 ? toDate(steps[steps.length - 1].startTime) : new Date(), + }; + } + + // Check for ExitPlanMode as the last significant activity + // ExitPlanMode is a special ending tool that signals plan completion + let lastExitPlanModeStep: SemanticStep | null = null; + let lastOutputBeforeExitPlanMode: SemanticStep | null = null; + + for (let i = steps.length - 1; i >= 0; i--) { + const step = steps[i]; + if (step.type === 'tool_call' && step.content.toolName === 'ExitPlanMode') { + lastExitPlanModeStep = step; + // Look for the preceding output step (preamble text) + for (let j = i - 1; j >= 0; j--) { + if (steps[j].type === 'output' && steps[j].content.outputText) { + lastOutputBeforeExitPlanMode = steps[j]; + break; + } + } + break; + } + } + + // If ExitPlanMode is found, check if it's the "last" activity + // (no other output or tool_result comes after it) + if (lastExitPlanModeStep) { + const exitPlanModeIndex = steps.indexOf(lastExitPlanModeStep); + let hasLaterEnding = false; + + for (let i = exitPlanModeIndex + 1; i < steps.length; i++) { + const step = steps[i]; + if (step.type === 'output' && step.content.outputText) { + hasLaterEnding = true; + break; + } + if (step.type === 'tool_result' && step.content.toolResultContent) { + hasLaterEnding = true; + break; + } + } + + if (!hasLaterEnding) { + // ExitPlanMode is the last significant activity - return plan_exit + const toolInput = lastExitPlanModeStep.content.toolInput as + | Record + | undefined; + const planContent = toolInput?.plan as string | undefined; + + return { + type: 'plan_exit', + planContent: planContent ?? '', + planPreamble: lastOutputBeforeExitPlanMode?.content.outputText, + timestamp: lastExitPlanModeStep.startTime, + }; + } + } + + // First pass: look for last 'output' step with outputText + for (let i = steps.length - 1; i >= 0; i--) { + const step = steps[i]; + if (step.type === 'output' && step.content.outputText) { + return { + type: 'text', + text: step.content.outputText, + timestamp: step.startTime, + }; + } + } + + // Second pass: look for last 'tool_result' step + for (let i = steps.length - 1; i >= 0; i--) { + const step = steps[i]; + if (step.type === 'tool_result' && step.content.toolResultContent) { + return { + type: 'tool_result', + toolName: step.content.toolName, + toolResult: step.content.toolResultContent, + isError: step.content.isError ?? false, + timestamp: step.startTime, + }; + } + } + + // Third pass: look for last 'interruption' step + for (let i = steps.length - 1; i >= 0; i--) { + const step = steps[i]; + if (step.type === 'interruption' && step.content.interruptionText) { + return { + type: 'interruption', + interruptionMessage: step.content.interruptionText, + timestamp: step.startTime, + }; + } + } + + return null; +} diff --git a/src/renderer/utils/modelExtractor.ts b/src/renderer/utils/modelExtractor.ts new file mode 100644 index 00000000..355b5f61 --- /dev/null +++ b/src/renderer/utils/modelExtractor.ts @@ -0,0 +1,90 @@ +/** + * Model Extractor - Extract model information from AI Group data + * + * Parses and extracts model information from semantic steps and subagent processes. + */ + +import { type ModelInfo, parseModelString } from '@shared/utils/modelParser'; + +import type { Process, SemanticStep } from '@renderer/types/data'; + +/** + * Extract the main model used in an AI Group. + * + * Strategy: + * 1. Look through semantic steps to find tool_call steps with sourceModel + * 2. Count occurrences of each model + * 3. Return the most common model (in case of mixed usage) + * + * @param steps - Semantic steps from the AI Group + * @returns The most common model info, or null if no models found + */ +export function extractMainModel(steps: SemanticStep[]): ModelInfo | null { + const modelCounts = new Map(); + + for (const step of steps) { + // Tool call steps have sourceModel set from the assistant message + if (step.type === 'tool_call' && step.content.sourceModel) { + const model = step.content.sourceModel; + if (model && model !== '') { + const info = parseModelString(model); + if (info) { + const existing = modelCounts.get(info.name); + if (existing) { + existing.count++; + } else { + modelCounts.set(info.name, { count: 1, info }); + } + } + } + } + } + + // Find most common model + let maxCount = 0; + let mainModel: ModelInfo | null = null; + + for (const { count, info } of modelCounts.values()) { + if (count > maxCount) { + maxCount = count; + mainModel = info; + } + } + + return mainModel; +} + +/** + * Extract unique models used by subagents that differ from the main model. + * + * Strategy: + * 1. Iterate through all processes (subagents) + * 2. Find the first assistant message with a valid model in each process + * 3. Parse and collect unique models that differ from mainModel + * + * @param processes - Subagent processes from the AI Group + * @param mainModel - The main agent's model (to filter out) + * @returns Array of unique model infos used by subagents + */ +export function extractSubagentModels( + processes: Process[], + mainModel: ModelInfo | null +): ModelInfo[] { + const uniqueModels = new Map(); + + for (const process of processes) { + // Find first assistant message with a valid model + const assistantMsg = process.messages?.find( + (m) => m.type === 'assistant' && m.model && m.model !== '' + ); + + if (assistantMsg?.model) { + const modelInfo = parseModelString(assistantMsg.model); + if (modelInfo && modelInfo.name !== mainModel?.name) { + uniqueModels.set(modelInfo.name, modelInfo); + } + } + } + + return Array.from(uniqueModels.values()); +} diff --git a/src/renderer/utils/pathDisplay.ts b/src/renderer/utils/pathDisplay.ts new file mode 100644 index 00000000..7adf60e3 --- /dev/null +++ b/src/renderer/utils/pathDisplay.ts @@ -0,0 +1,94 @@ +/** + * Path display utilities for shortening file paths in tight UI spaces. + * + * Strategy: + * 1. Strip project root to make relative + * 2. Replace home directory with ~ + * 3. Middle-truncate if still too long, preserving first and last segments + * + * Also provides resolveAbsolutePath() for clipboard copy (~ → real home, relative → absolute). + */ + +/** + * Shorten a file path for display in compact UI elements. + * Full path should still be available via tooltip (title attribute). + * + * Examples: + * - `/Users/name/.claude/projects/-Users-name-project/memory/MEMORY.md` → `~/.claude/…/memory/MEMORY.md` + * - `/Users/name/project/.claude/rules/tailwind.md` (with projectRoot) → `.claude/rules/tailwind.md` + * - `~/.claude/CLAUDE.md` → `~/.claude/CLAUDE.md` (already short) + */ +export function shortenDisplayPath(fullPath: string, projectRoot?: string, maxLength = 40): string { + let p = fullPath; + + // 1. Make relative to project root + if (projectRoot) { + const root = projectRoot.replace(/[/\\]$/, ''); + if (p.startsWith(root + '/') || p.startsWith(root + '\\')) { + p = p.slice(root.length + 1); + } + } + + // 2. Replace home directory with ~ + p = p + .replace(/^\/Users\/[^/]+/, '~') + .replace(/^\/home\/[^/]+/, '~') + .replace(/^[A-Z]:\\Users\\[^\\]+/, '~'); + + // 3. If short enough, return as-is + if (p.length <= maxLength) return p; + + // 4. Middle-truncate: keep first meaningful segments + … + last 2 segments + const sep = p.includes('\\') ? '\\' : '/'; + const segments = p.split(sep); + + // Determine where content starts (skip leading empty segment from absolute paths or ~) + let startIdx = 0; + if (segments[0] === '' || segments[0] === '~') startIdx = 1; + + // Need at least 4 content segments to truncate the middle + if (segments.length - startIdx <= 3) return p; + + const head = segments.slice(0, startIdx + 1).join(sep); + const tail = segments.slice(-2).join(sep); + + return `${head}${sep}\u2026${sep}${tail}`; +} + +/** + * Infer the user's home directory from a known absolute project path. + * Works for macOS (/Users/x), Linux (/home/x), and Windows (C:\Users\x). + */ +function inferHomeDir(projectRoot: string): string | null { + const match = + /^(\/Users\/[^/]+)/.exec(projectRoot) ?? + /^(\/home\/[^/]+)/.exec(projectRoot) ?? + /^([A-Z]:\\Users\\[^\\]+)/.exec(projectRoot); + return match?.[1] ?? null; +} + +/** + * Resolve a possibly-shortened path to its full absolute form for clipboard copy. + * + * - `~/...` → `/Users/username/...` (home dir inferred from projectRoot) + * - `src/foo/bar` → `{projectRoot}/src/foo/bar` + * - Already absolute → returned as-is + */ +export function resolveAbsolutePath(filePath: string, projectRoot?: string): string { + let p = filePath; + + // Resolve ~ using home dir inferred from projectRoot + if (p.startsWith('~/') && projectRoot) { + const homeDir = inferHomeDir(projectRoot); + if (homeDir) { + p = homeDir + p.slice(1); + } + } + + // Make relative paths absolute by prepending projectRoot + if (projectRoot && !p.startsWith('/') && !p.startsWith('~') && !/^[A-Z]:[/\\]/.test(p)) { + p = projectRoot.replace(/[/\\]$/, '') + '/' + p; + } + + return p; +} diff --git a/src/renderer/utils/pathUtils.ts b/src/renderer/utils/pathUtils.ts new file mode 100644 index 00000000..fbbe578d --- /dev/null +++ b/src/renderer/utils/pathUtils.ts @@ -0,0 +1,47 @@ +/** + * Cross-platform path utilities for the renderer process. + * + * The renderer has no access to Node's `path` module, and session data + * may originate from any OS, so all helpers handle both `/` and `\`. + */ + +const SEP_RE = /[\\/]/; + +/** + * Returns the last segment of a path (the file or directory name). + * Equivalent to `path.basename()` but handles both separators. + */ +export function getBaseName(filePath: string): string { + const parts = filePath.split(SEP_RE); + return parts[parts.length - 1] || ''; +} + +/** + * Returns the first meaningful segment of a path. + * Leading empty segments (from absolute paths like `/foo`) are skipped. + */ +export function getFirstSegment(filePath: string): string { + const parts = filePath.split(SEP_RE).filter(Boolean); + return parts[0] ?? ''; +} + +/** + * Splits a path into non-empty segments. + */ +export function splitPathSegments(filePath: string): string[] { + return filePath.split(SEP_RE).filter(Boolean); +} + +/** + * Returns true if the string contains a path separator (`/` or `\`). + */ +export function hasPathSeparator(filePath: string): boolean { + return SEP_RE.test(filePath); +} + +/** + * Returns true if the path starts with `./`, `.\`, `../`, or `..\`. + */ +export function isRelativePath(filePath: string): boolean { + return /^\.\.?[\\/]/.test(filePath); +} diff --git a/src/renderer/utils/slashCommandExtractor.ts b/src/renderer/utils/slashCommandExtractor.ts new file mode 100644 index 00000000..be50c18b --- /dev/null +++ b/src/renderer/utils/slashCommandExtractor.ts @@ -0,0 +1,154 @@ +/** + * Slash Command Extractor - Handle slash command extraction from AI Group responses + * + * Extracts and processes slash command invocations and their follow-up instructions. + */ + +import { extractSlashInfo, isCommandContent } from '@shared/utils/contentSanitizer'; + +import { estimateTokens, toDate } from './aiGroupHelpers'; + +import type { ParsedMessage } from '@renderer/types/data'; +import type { SlashItem } from '@renderer/types/groups'; + +/** + * Info about the preceding user message's slash invocation. + * This is passed from the UserGroup to help link slash outputs to slash names. + */ +export interface PrecedingSlashInfo { + /** Slash name (e.g., "claude-hud:setup", "isolate-context", "model") */ + name: string; + /** Message content from */ + message?: string; + /** Arguments from */ + args?: string; + /** UUID of the slash command message */ + commandMessageUuid: string; + /** Timestamp of the slash command message */ + timestamp: Date; +} + +/** + * Extract slash items from AI group responses. + * + * All slash invocations follow the same format: + * /xxx + * xxx + * optional + * + * Strategy: + * 1. Build a map of follow-up messages (isMeta:true with parentUuid) by their parentUuid + * 2. If precedingSlash is provided, create a SlashItem with its follow-up instructions + * 3. Also check for any slash invocations in responses (fallback) + * + * @param responses - All response messages in the AI group + * @param precedingSlash - Optional slash info from the preceding UserGroup + * @returns Array of SlashItem objects ready for display + */ +export function extractSlashes( + responses: ParsedMessage[], + precedingSlash?: PrecedingSlashInfo +): SlashItem[] { + const slashes: SlashItem[] = []; + + // Build a map of follow-up messages by their parentUuid + // These are isMeta:true messages that contain slash instructions/output + const followUpsByParentUuid = new Map< + string, + { + text: string; + timestamp: Date; + } + >(); + + // Also build a map of potential slash messages from responses (fallback) + const slashMessagesById = new Map< + string, + { + uuid: string; + name: string; + message?: string; + args?: string; + timestamp: Date; + } + >(); + + for (const msg of responses) { + // Look for slash messages (user messages with string content containing ) + // This is a fallback in case the slash invocation is somehow in responses + if (msg.type === 'user' && typeof msg.content === 'string' && isCommandContent(msg.content)) { + const slashInfo = extractSlashInfo(msg.content); + if (slashInfo) { + slashMessagesById.set(msg.uuid, { + uuid: msg.uuid, + name: slashInfo.name, + message: slashInfo.message, + args: slashInfo.args, + timestamp: toDate(msg.timestamp), + }); + } + } + + // Look for follow-up isMeta messages with parentUuid + if ( + msg.type === 'user' && + msg.isMeta === true && + msg.parentUuid && + !msg.sourceToolUseID && // Exclude tool-call related messages + Array.isArray(msg.content) + ) { + // Extract text from the message + for (const block of msg.content) { + if (block.type === 'text' && block.text) { + const text = block.text; + followUpsByParentUuid.set(msg.parentUuid, { + text, + timestamp: toDate(msg.timestamp), + }); + break; // Only need the first text block + } + } + } + } + + // Strategy 1: If we have precedingSlash info, create a SlashItem with its follow-up + if (precedingSlash) { + const followUp = followUpsByParentUuid.get(precedingSlash.commandMessageUuid); + + slashes.push({ + id: `slash-${precedingSlash.commandMessageUuid}`, + name: precedingSlash.name, + message: precedingSlash.message, + args: precedingSlash.args, + commandMessageUuid: precedingSlash.commandMessageUuid, + instructions: followUp?.text, + instructionsTokenCount: followUp ? estimateTokens(followUp.text) : undefined, + // Use follow-up timestamp if available (sorts with other AI items), + // otherwise fall back to slash invocation timestamp + timestamp: followUp?.timestamp ?? precedingSlash.timestamp, + }); + } + + // Strategy 2: Fallback - match slash messages found in responses to their follow-ups + for (const [uuid, slashMsg] of slashMessagesById.entries()) { + // Skip if we already added this slash via precedingSlash + if (uuid === precedingSlash?.commandMessageUuid) { + continue; + } + + const followUp = followUpsByParentUuid.get(uuid); + + slashes.push({ + id: `slash-${uuid}`, + name: slashMsg.name, + message: slashMsg.message, + args: slashMsg.args, + commandMessageUuid: uuid, + instructions: followUp?.text, + instructionsTokenCount: followUp ? estimateTokens(followUp.text) : undefined, + timestamp: slashMsg.timestamp, + }); + } + + return slashes; +} diff --git a/src/renderer/utils/stringUtils.ts b/src/renderer/utils/stringUtils.ts new file mode 100644 index 00000000..6c84441b --- /dev/null +++ b/src/renderer/utils/stringUtils.ts @@ -0,0 +1,29 @@ +/** + * String utilities for display formatting. + */ + +/** + * Truncates a string in the middle to preserve both the beginning and end. + * Useful for branch names where the unique identifier is often at the end. + * + * @example + * truncateMiddle("feature/very-long-branch-name-with-ticket-12345", 25) + * // Returns: "feature/ver...ticket-12345" + * + * @param text - The string to truncate + * @param maxLen - Maximum length of the resulting string (default: 25) + * @returns The truncated string with "..." in the middle, or original if short enough + */ +export function truncateMiddle(text: string, maxLen: number = 25): string { + if (!text || text.length <= maxLen) return text; + + // Account for the 3-character ellipsis + const availableChars = maxLen - 3; + const startLen = Math.ceil(availableChars / 2); + const endLen = Math.floor(availableChars / 2); + + const start = text.slice(0, startLen); + const end = text.slice(-endLen); + + return `${start}...${end}`; +} diff --git a/src/renderer/utils/toolLinkingEngine.ts b/src/renderer/utils/toolLinkingEngine.ts new file mode 100644 index 00000000..dfe1ee7d --- /dev/null +++ b/src/renderer/utils/toolLinkingEngine.ts @@ -0,0 +1,117 @@ +/** + * Tool Linking Engine - Link tool calls to their results + * + * Matches tool_call steps with their corresponding tool_result steps + * and builds LinkedToolItem structures for display. + */ + +import { estimateTokens, formatToolInput, formatToolResult, toDate } from './aiGroupHelpers'; + +import type { ParsedMessage, SemanticStep } from '../types/data'; +import type { LinkedToolItem } from '../types/groups'; + +/** + * Link tool calls to their results and build a map of LinkedToolItems. + * + * Strategy: + * 1. Iterate through steps to find all tool_call steps + * 2. For each tool call, search for matching tool_result by ID + * - Tool result step IDs are set to the tool_use_id, matching the call's ID + * 3. Build LinkedToolItem with preview text + * 4. Include orphaned calls (calls without results) + * 5. For Skill tool calls, extract skill instructions from responses + * + * @param steps - Semantic steps from the AI Group + * @param responses - Optional raw messages for extracting skill instructions + * @returns Map of tool call ID to LinkedToolItem + */ +export function linkToolCallsToResults( + steps: SemanticStep[], + responses?: ParsedMessage[] +): Map { + const linkedTools = new Map(); + + // First pass: collect all tool calls + const toolCalls = steps.filter((step) => step.type === 'tool_call'); + + // Build a map of result steps by their ID for fast lookup + const resultStepsById = new Map(); + for (const step of steps) { + if (step.type === 'tool_result') { + resultStepsById.set(step.id, step); + } + } + + // Build a map of skill instructions by source tool use ID + // Skill tools have follow-up isMeta:true messages with instructions starting with "Base directory for this skill:" + const skillInstructionsById = new Map(); + + if (responses) { + for (const msg of responses) { + // Extract skill instructions + if (msg.type === 'user' && msg.isMeta && msg.sourceToolUseID && Array.isArray(msg.content)) { + for (const block of msg.content) { + if (block.type === 'text' && block.text) { + const text = block.text; + if (text.startsWith('Base directory for this skill:')) { + skillInstructionsById.set(msg.sourceToolUseID, text); + } + } + } + } + } + } + + for (const callStep of toolCalls) { + const toolCallId = callStep.id; + const toolName = callStep.content.toolName ?? 'Unknown'; + const toolInput = callStep.content.toolInput ?? {}; + + // Search for matching tool result by ID + // Tool result steps have their ID set to the tool_use_id (same as call ID) + const resultStep = resultStepsById.get(toolCallId); + + // Convert timestamps to proper Date objects (handles IPC serialization) + const callStartTime = toDate(callStep.startTime); + const resultStartTime = resultStep ? toDate(resultStep.startTime) : undefined; + + // Get skill instructions for Skill tool calls + const skillInstructions = + toolName === 'Skill' ? skillInstructionsById.get(toolCallId) : undefined; + + // Calculate callTokens directly from tool name + input + // This reflects what actually enters the context window (not proportioned output_tokens) + const callTokens = estimateTokens(toolName + JSON.stringify(toolInput)); + + const linkedItem: LinkedToolItem = { + id: toolCallId, + name: toolName, + input: toolInput as Record, + callTokens, // Token count for tool call (what Claude generated) + result: resultStep + ? { + content: resultStep.content.toolResultContent ?? '', + isError: resultStep.content.isError ?? false, + toolUseResult: resultStep.content.toolUseResult, + tokenCount: resultStep.content.tokenCount, // Pre-computed token count for result + } + : undefined, + inputPreview: formatToolInput(toolInput as Record), + outputPreview: resultStep + ? formatToolResult(resultStep.content.toolResultContent ?? '') + : undefined, + startTime: callStartTime, + endTime: resultStartTime, + durationMs: resultStartTime ? resultStartTime.getTime() - callStartTime.getTime() : undefined, + isOrphaned: !resultStep, + skillInstructions, + skillInstructionsTokenCount: skillInstructions + ? estimateTokens(skillInstructions) + : undefined, + }; + + linkedTools.set(toolCallId, linkedItem); + } + + return linkedTools; +} diff --git a/src/renderer/utils/toolRendering/index.ts b/src/renderer/utils/toolRendering/index.ts new file mode 100644 index 00000000..b2936950 --- /dev/null +++ b/src/renderer/utils/toolRendering/index.ts @@ -0,0 +1,14 @@ +/** + * Tool Rendering Utilities + * + * Exports all tool rendering helper functions. + */ + +export { + hasEditContent, + hasReadContent, + hasSkillInstructions, + hasWriteContent, +} from './toolContentChecks'; +export { getToolSummary } from './toolSummaryHelpers'; +export { getToolContextTokens, getToolStatus } from './toolTokens'; diff --git a/src/renderer/utils/toolRendering/toolContentChecks.ts b/src/renderer/utils/toolRendering/toolContentChecks.ts new file mode 100644 index 00000000..090cf546 --- /dev/null +++ b/src/renderer/utils/toolRendering/toolContentChecks.ts @@ -0,0 +1,58 @@ +/** + * Tool Content Check Helpers + * + * Utilities for checking if tool items have specific types of content. + */ + +import type { LinkedToolItem } from '@renderer/types/groups'; + +/** + * Checks if a Skill tool has skill instructions. + */ +export function hasSkillInstructions(linkedTool: LinkedToolItem): boolean { + return !!linkedTool.skillInstructions; +} + +/** + * Checks if a Read tool has content to display. + */ +export function hasReadContent(linkedTool: LinkedToolItem): boolean { + if (!linkedTool.result) return false; + + const toolUseResult = linkedTool.result.toolUseResult as Record | undefined; + const fileData = toolUseResult?.file as { content?: string } | undefined; + if (fileData?.content) return true; + + if (linkedTool.result.content != null) { + if (typeof linkedTool.result.content === 'string' && linkedTool.result.content.length > 0) + return true; + if (Array.isArray(linkedTool.result.content) && linkedTool.result.content.length > 0) + return true; + } + + return false; +} + +/** + * Checks if an Edit tool has content to display. + */ +export function hasEditContent(linkedTool: LinkedToolItem): boolean { + if (linkedTool.input.old_string != null) return true; + + const toolUseResult = linkedTool.result?.toolUseResult as Record | undefined; + if (toolUseResult?.oldString != null || toolUseResult?.newString != null) return true; + + return false; +} + +/** + * Checks if a Write tool has content to display. + */ +export function hasWriteContent(linkedTool: LinkedToolItem): boolean { + if (linkedTool.input.content != null || linkedTool.input.file_path != null) return true; + + const toolUseResult = linkedTool.result?.toolUseResult as Record | undefined; + if (toolUseResult?.content != null || toolUseResult?.filePath != null) return true; + + return false; +} diff --git a/src/renderer/utils/toolRendering/toolSummaryHelpers.ts b/src/renderer/utils/toolRendering/toolSummaryHelpers.ts new file mode 100644 index 00000000..a130e44e --- /dev/null +++ b/src/renderer/utils/toolRendering/toolSummaryHelpers.ts @@ -0,0 +1,271 @@ +/** + * Tool Summary Helpers + * + * Utilities for generating human-readable summaries for tool calls. + */ + +import { getBaseName } from '@renderer/utils/pathUtils'; + +/** + * Truncates a string to a maximum length with ellipsis. + */ +function truncate(str: string, maxLength: number): string { + if (str.length <= maxLength) return str; + return str.slice(0, maxLength) + '...'; +} + +/** + * Generates a human-readable summary for a tool call. + */ +export function getToolSummary(toolName: string, input: Record): string { + switch (toolName) { + case 'Edit': { + const filePath = input.file_path as string | undefined; + const oldString = input.old_string as string | undefined; + const newString = input.new_string as string | undefined; + + if (!filePath) return 'Edit'; + + const fileName = getBaseName(filePath); + + // Count line changes if we have old/new strings + if (oldString && newString) { + const oldLines = oldString.split('\n').length; + const newLines = newString.split('\n').length; + if (oldLines === newLines) { + return `${fileName} - ${oldLines} line${oldLines > 1 ? 's' : ''}`; + } + return `${fileName} - ${oldLines} -> ${newLines} lines`; + } + + return fileName; + } + + case 'Read': { + const filePath = input.file_path as string | undefined; + const limit = input.limit as number | undefined; + const offset = input.offset as number | undefined; + + if (!filePath) return 'Read'; + + const fileName = getBaseName(filePath); + + if (limit) { + const start = offset ?? 1; + return `${fileName} - lines ${start}-${start + limit - 1}`; + } + + return fileName; + } + + case 'Write': { + const filePath = input.file_path as string | undefined; + const content = input.content as string | undefined; + + if (!filePath) return 'Write'; + + const fileName = getBaseName(filePath); + + if (content) { + const lineCount = content.split('\n').length; + return `${fileName} - ${lineCount} lines`; + } + + return fileName; + } + + case 'Bash': { + const command = input.command as string | undefined; + const description = input.description as string | undefined; + + // Prefer description if available + if (description) { + return truncate(description, 50); + } + + if (command) { + return truncate(command, 50); + } + + return 'Bash'; + } + + case 'Grep': { + const pattern = input.pattern as string | undefined; + const path = input.path as string | undefined; + const glob = input.glob as string | undefined; + + if (!pattern) return 'Grep'; + + const patternStr = `"${truncate(pattern, 30)}"`; + + if (glob) { + return `${patternStr} in ${glob}`; + } + if (path) { + return `${patternStr} in ${getBaseName(path)}`; + } + + return patternStr; + } + + case 'Glob': { + const pattern = input.pattern as string | undefined; + const path = input.path as string | undefined; + + if (!pattern) return 'Glob'; + + const patternStr = `"${truncate(pattern, 30)}"`; + + if (path) { + return `${patternStr} in ${getBaseName(path)}`; + } + + return patternStr; + } + + case 'Task': { + const prompt = input.prompt as string | undefined; + const subagentType = input.subagentType as string | undefined; + const description = input.description as string | undefined; + + const desc = description ?? prompt; + const typeStr = subagentType ? `${subagentType} - ` : ''; + + if (desc) { + return `${typeStr}${truncate(desc, 40)}`; + } + + return subagentType ?? 'Task'; + } + + case 'LSP': { + const operation = input.operation as string | undefined; + const filePath = input.filePath as string | undefined; + + if (!operation) return 'LSP'; + + if (filePath) { + return `${operation} - ${getBaseName(filePath)}`; + } + + return operation; + } + + case 'WebFetch': { + const url = input.url as string | undefined; + + if (url) { + try { + const urlObj = new URL(url); + return truncate(urlObj.hostname + urlObj.pathname, 50); + } catch { + return truncate(url, 50); + } + } + + return 'WebFetch'; + } + + case 'WebSearch': { + const query = input.query as string | undefined; + + if (query) { + return `"${truncate(query, 40)}"`; + } + + return 'WebSearch'; + } + + case 'TodoWrite': { + const todos = input.todos as unknown[] | undefined; + + if (todos && Array.isArray(todos)) { + return `${todos.length} item${todos.length !== 1 ? 's' : ''}`; + } + + return 'TodoWrite'; + } + + case 'NotebookEdit': { + const notebookPath = input.notebook_path as string | undefined; + const editMode = input.edit_mode as string | undefined; + + if (notebookPath) { + const fileName = getBaseName(notebookPath); + return editMode ? `${editMode} - ${fileName}` : fileName; + } + + return 'NotebookEdit'; + } + + // ========================================================================= + // Team Tools + // ========================================================================= + + case 'TeamCreate': { + const teamName = input.team_name as string | undefined; + const desc = input.description as string | undefined; + if (teamName) return `${teamName}${desc ? ' - ' + truncate(desc, 30) : ''}`; + return 'Create team'; + } + + case 'TaskCreate': { + const subject = input.subject as string | undefined; + return subject ? truncate(subject, 50) : 'Create task'; + } + + case 'TaskUpdate': { + const taskId = input.taskId as string | undefined; + const status = input.status as string | undefined; + const owner = input.owner as string | undefined; + const parts: string[] = []; + if (taskId) parts.push(`#${taskId}`); + if (status) parts.push(status); + if (owner) parts.push(`-> ${owner}`); + return parts.length > 0 ? parts.join(' ') : 'Update task'; + } + + case 'TaskList': + return 'List tasks'; + + case 'TaskGet': { + const taskId = input.taskId as string | undefined; + return taskId ? `Get task #${taskId}` : 'Get task'; + } + + case 'SendMessage': { + const msgType = input.type as string | undefined; + const recipient = input.recipient as string | undefined; + const summary = input.summary as string | undefined; + if (msgType === 'shutdown_request' && recipient) return `Shutdown ${recipient}`; + if (msgType === 'shutdown_response') return 'Shutdown response'; + if (msgType === 'broadcast') return `Broadcast: ${truncate(summary ?? '', 30)}`; + if (recipient) return `To ${recipient}: ${truncate(summary ?? '', 30)}`; + return 'Send message'; + } + + case 'TeamDelete': + return 'Delete team'; + + default: { + // For unknown tools, try to extract a meaningful summary + const keys = Object.keys(input); + if (keys.length === 0) return toolName; + + // Try common parameter names + const nameField = input.name ?? input.path ?? input.file ?? input.query ?? input.command; + if (typeof nameField === 'string') { + return truncate(nameField, 50); + } + + // Fallback to showing first parameter + const firstValue = input[keys[0]]; + if (typeof firstValue === 'string') { + return truncate(firstValue, 40); + } + + return toolName; + } + } +} diff --git a/src/renderer/utils/toolRendering/toolTokens.ts b/src/renderer/utils/toolRendering/toolTokens.ts new file mode 100644 index 00000000..97b47d8c --- /dev/null +++ b/src/renderer/utils/toolRendering/toolTokens.ts @@ -0,0 +1,57 @@ +/** + * Tool Token Utilities + * + * Functions for estimating and calculating token counts for tool operations. + */ + +import { estimateTokens } from '@shared/utils/tokenFormatting'; + +import type { ItemStatus } from '@renderer/components/chat/items/BaseItem'; +import type { LinkedToolItem } from '@renderer/types/groups'; + +/** + * Calculates total context tokens consumed by a tool operation. + */ +export function getToolContextTokens(linkedTool: LinkedToolItem): number { + let totalTokens = 0; + + // Tool CALL tokens (what Claude generated) + if (linkedTool.callTokens !== undefined) { + totalTokens += linkedTool.callTokens; + } else { + // Fallback: estimate from input + totalTokens += estimateTokens(JSON.stringify(linkedTool.input)); + } + + // Tool RESULT tokens (what Claude reads back) + if (linkedTool.result?.tokenCount !== undefined) { + totalTokens += linkedTool.result.tokenCount; + } else if (linkedTool.result?.content) { + const content = linkedTool.result.content; + if (typeof content === 'string') { + totalTokens += estimateTokens(content); + } else if (Array.isArray(content)) { + totalTokens += estimateTokens(JSON.stringify(content)); + } + } + + // For Skill tools, also add skill instructions tokens + if (linkedTool.name === 'Skill') { + if (linkedTool.skillInstructionsTokenCount !== undefined) { + totalTokens += linkedTool.skillInstructionsTokenCount; + } else if (linkedTool.skillInstructions) { + totalTokens += estimateTokens(linkedTool.skillInstructions); + } + } + + return totalTokens; +} + +/** + * Gets the status of a tool execution. + */ +export function getToolStatus(linkedTool: LinkedToolItem): ItemStatus { + if (linkedTool.isOrphaned) return 'orphaned'; + if (linkedTool.result?.isError) return 'error'; + return 'ok'; +} diff --git a/src/renderer/vite-env.d.ts b/src/renderer/vite-env.d.ts new file mode 100644 index 00000000..827db198 --- /dev/null +++ b/src/renderer/vite-env.d.ts @@ -0,0 +1,19 @@ +/// + +declare module '*.png' { + const src: string; + // eslint-disable-next-line import/no-default-export -- Vite asset modules require default exports + export default src; +} + +declare module '*.jpg' { + const src: string; + // eslint-disable-next-line import/no-default-export -- Vite asset modules require default exports + export default src; +} + +declare module '*.svg' { + const src: string; + // eslint-disable-next-line import/no-default-export -- Vite asset modules require default exports + export default src; +} diff --git a/src/shared/CLAUDE.md b/src/shared/CLAUDE.md new file mode 100644 index 00000000..fdfcc0f6 --- /dev/null +++ b/src/shared/CLAUDE.md @@ -0,0 +1,35 @@ +# Shared + +Cross-process code used by main and renderer. + +## What Goes Here +- Types shared between processes +- Pure utility functions (no Node/DOM APIs) +- Constants used across processes + +## What Doesn't Go Here +- Node.js APIs → main/ +- DOM/React APIs → renderer/ +- Process-specific logic + +## Structure +- `types/` - Shared type definitions (`api.ts`, `notifications.ts`, `visualization.ts`) +- `utils/` - Pure utility functions + - `tokenFormatting.ts` - Token formatting and estimation (`estimateTokens`, `formatTokensCompact`) + - `modelParser.ts` - Model name/family parsing + - `teammateMessageParser.ts` - `` XML parsing + - `markdownTextSearch.ts` - Markdown-aware text search + - `contentSanitizer.ts` - Content sanitization + - `errorHandling.ts` - Error helpers + - `logger.ts` - Logging utility +- `constants/` - Shared constants + - `cache.ts` - Cache configuration + - `trafficLights.ts` - macOS traffic light constants + - `triggerColors.ts` - Trigger color palette + - `window.ts` - Window configuration + +## Import +```typescript +import { SomeType } from '@shared/types'; +import { estimateTokens } from '@shared/utils/tokenFormatting'; +``` diff --git a/src/shared/constants/cache.ts b/src/shared/constants/cache.ts new file mode 100644 index 00000000..a61dc69a --- /dev/null +++ b/src/shared/constants/cache.ts @@ -0,0 +1,12 @@ +/** + * Cache-related constants. + */ + +/** Maximum number of sessions to cache */ +export const MAX_CACHE_SESSIONS = 50; + +/** Cache TTL in minutes */ +export const CACHE_TTL_MINUTES = 10; + +/** Cleanup interval in minutes */ +export const CACHE_CLEANUP_INTERVAL_MINUTES = 5; diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts new file mode 100644 index 00000000..85d41c2f --- /dev/null +++ b/src/shared/constants/index.ts @@ -0,0 +1,8 @@ +/** + * Shared constants barrel export. + */ + +export * from './cache'; +export * from './trafficLights'; +export * from './triggerColors'; +export * from './window'; diff --git a/src/shared/constants/trafficLights.ts b/src/shared/constants/trafficLights.ts new file mode 100644 index 00000000..6c523c76 --- /dev/null +++ b/src/shared/constants/trafficLights.ts @@ -0,0 +1,60 @@ +/** + * Shared macOS traffic-light geometry. + * + * Keep this as the single source of truth for both: + * - main process native button positioning + * - renderer process left padding reservation + */ + +/** IPC event channel emitted by main when zoom changes */ +export const WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL = 'window:zoom-factor-changed'; + +/** Base traffic-light origin at 100% zoom (native coordinates) */ +const MACOS_TRAFFIC_LIGHT_BASE_POSITION = { x: 12, y: 12 } as const; + +/** Header row height used by SidebarHeader and TabBar */ +export const HEADER_ROW1_HEIGHT = 40; + +/** Native button-group frame height (used to vertically center in header row) */ +const MACOS_TRAFFIC_LIGHT_GROUP_HEIGHT = 16; + +/** Approximate total width of the 3 traffic lights group in native px */ +const MACOS_TRAFFIC_LIGHT_GROUP_WIDTH = 52; + +/** Visual gap between traffic lights and first left-aligned content */ +const MACOS_TRAFFIC_LIGHT_CONTENT_GAP = 8; + +const MIN_ZOOM_FACTOR = 0.25; + +function sanitizeZoomFactor(zoomFactor: number): number { + if (!Number.isFinite(zoomFactor) || zoomFactor <= 0) { + return 1; + } + return Math.max(zoomFactor, MIN_ZOOM_FACTOR); +} + +/** + * Native traffic-light position for the given zoom. + * Uses linear scaling to keep vertical alignment with zoomed title rows. + */ +export function getTrafficLightPositionForZoom( + zoomFactor: number +): Readonly<{ x: number; y: number }> { + const zoom = sanitizeZoomFactor(zoomFactor); + return { + x: Math.round(MACOS_TRAFFIC_LIGHT_BASE_POSITION.x * zoom), + y: Math.round((HEADER_ROW1_HEIGHT * zoom - MACOS_TRAFFIC_LIGHT_GROUP_HEIGHT) / 2), + }; +} + +/** + * CSS left padding (in CSS px) needed to avoid overlap with native buttons. + * Produces a stable physical gap between traffic lights and content at any zoom. + */ +export function getTrafficLightPaddingForZoom(zoomFactor: number): number { + const zoom = sanitizeZoomFactor(zoomFactor); + return Math.ceil( + MACOS_TRAFFIC_LIGHT_BASE_POSITION.x + + (MACOS_TRAFFIC_LIGHT_GROUP_WIDTH + MACOS_TRAFFIC_LIGHT_CONTENT_GAP) / zoom + ); +} diff --git a/src/shared/constants/triggerColors.ts b/src/shared/constants/triggerColors.ts new file mode 100644 index 00000000..c8706b30 --- /dev/null +++ b/src/shared/constants/triggerColors.ts @@ -0,0 +1,126 @@ +/** + * Preset color palette for notification triggers. + * Shared between main and renderer processes. + * + * Supports both preset color keys and custom hex strings (e.g., '#ff6600'). + */ + +export type TriggerColorKey = + | 'red' + | 'orange' + | 'yellow' + | 'green' + | 'blue' + | 'purple' + | 'pink' + | 'cyan'; + +/** Color value: either a preset key or a custom hex string like '#ff6600'. */ +export type TriggerColor = TriggerColorKey | `#${string}`; + +export interface TriggerColorDef { + key: string; + label: string; + hex: string; +} + +export const TRIGGER_COLORS: TriggerColorDef[] = [ + { key: 'red', label: 'Red', hex: '#ef4444' }, + { key: 'orange', label: 'Orange', hex: '#f97316' }, + { key: 'yellow', label: 'Yellow', hex: '#eab308' }, + { key: 'green', label: 'Green', hex: '#22c55e' }, + { key: 'blue', label: 'Blue', hex: '#3b82f6' }, + { key: 'purple', label: 'Purple', hex: '#a855f7' }, + { key: 'pink', label: 'Pink', hex: '#ec4899' }, + { key: 'cyan', label: 'Cyan', hex: '#06b6d4' }, +]; + +const DEFAULT_TRIGGER_COLOR: TriggerColorKey = 'red'; + +const TRIGGER_COLOR_MAP = new Map(TRIGGER_COLORS.map((c) => [c.key, c])); + +const HEX_COLOR_RE = /^#[0-9a-fA-F]{3,8}$/; + +/** Check if value is a preset color key. */ +export function isPresetColorKey(value: string | undefined): value is TriggerColorKey { + return TRIGGER_COLOR_MAP.has(value ?? ''); +} + +/** + * Resolve a color value (preset key or hex string) to a TriggerColorDef. + * Custom hex strings return a synthetic def with key and label set to the hex value. + */ +export function getTriggerColorDef(color: TriggerColor | undefined): TriggerColorDef { + if (!color) return TRIGGER_COLOR_MAP.get(DEFAULT_TRIGGER_COLOR) ?? TRIGGER_COLORS[0]; + const preset = TRIGGER_COLOR_MAP.get(color); + if (preset) return preset; + // Treat as custom hex + if (HEX_COLOR_RE.test(color)) return { key: color, label: color, hex: color }; + return TRIGGER_COLOR_MAP.get(DEFAULT_TRIGGER_COLOR) ?? TRIGGER_COLORS[0]; +} + +/** Resolve any TriggerColor to its hex value. */ +export function resolveColorHex(color: TriggerColor | undefined): string { + return getTriggerColorDef(color).hex; +} + +/** + * Tailwind highlight classes for chat group rings (error navigation). + */ +export const HIGHLIGHT_CLASSES: Record = { + red: 'ring-2 ring-red-500/30 bg-red-500/5', + orange: 'ring-2 ring-orange-500/30 bg-orange-500/5', + yellow: 'ring-2 ring-yellow-500/30 bg-yellow-500/5', + green: 'ring-2 ring-green-500/30 bg-green-500/5', + blue: 'ring-2 ring-blue-500/30 bg-blue-500/5', + purple: 'ring-2 ring-purple-500/30 bg-purple-500/5', + pink: 'ring-2 ring-pink-500/30 bg-pink-500/5', + cyan: 'ring-2 ring-cyan-500/30 bg-cyan-500/5', +}; + +/** + * Get highlight classes for a color, supporting custom hex. + * Returns { className, style } — use className for presets, style for custom hex. + */ +export function getHighlightProps(color: TriggerColor | undefined): { + className: string; + style?: React.CSSProperties; +} { + const key = color ?? DEFAULT_TRIGGER_COLOR; + if (isPresetColorKey(key)) return { className: HIGHLIGHT_CLASSES[key] }; + const hex = resolveColorHex(key); + return { + className: 'ring-2', + style: { boxShadow: `0 0 0 2px ${hex}4D`, backgroundColor: `${hex}0D` }, + }; +} + +/** + * Tailwind highlight classes for tool item rings (pulsing highlight). + */ +export const TOOL_HIGHLIGHT_CLASSES: Record = { + red: 'ring-2 ring-red-500 bg-red-500/10 animate-pulse', + orange: 'ring-2 ring-orange-500 bg-orange-500/10 animate-pulse', + yellow: 'ring-2 ring-yellow-500 bg-yellow-500/10 animate-pulse', + green: 'ring-2 ring-green-500 bg-green-500/10 animate-pulse', + blue: 'ring-2 ring-blue-500 bg-blue-500/10 animate-pulse', + purple: 'ring-2 ring-purple-500 bg-purple-500/10 animate-pulse', + pink: 'ring-2 ring-pink-500 bg-pink-500/10 animate-pulse', + cyan: 'ring-2 ring-cyan-500 bg-cyan-500/10 animate-pulse', +}; + +/** + * Get tool highlight classes for a color, supporting custom hex. + */ +export function getToolHighlightProps(color: TriggerColor | undefined): { + className: string; + style?: React.CSSProperties; +} { + const key = color ?? DEFAULT_TRIGGER_COLOR; + if (isPresetColorKey(key)) return { className: TOOL_HIGHLIGHT_CLASSES[key] }; + const hex = resolveColorHex(key); + return { + className: 'ring-2 animate-pulse', + style: { boxShadow: `0 0 0 2px ${hex}`, backgroundColor: `${hex}1A` }, + }; +} diff --git a/src/shared/constants/window.ts b/src/shared/constants/window.ts new file mode 100644 index 00000000..7de23840 --- /dev/null +++ b/src/shared/constants/window.ts @@ -0,0 +1,12 @@ +/** + * Window-related constants. + */ + +/** Default main window width in pixels */ +export const DEFAULT_WINDOW_WIDTH = 1400; + +/** Default main window height in pixels */ +export const DEFAULT_WINDOW_HEIGHT = 900; + +/** Development server port */ +export const DEV_SERVER_PORT = 5173; diff --git a/src/shared/types/api.ts b/src/shared/types/api.ts new file mode 100644 index 00000000..c60727ed --- /dev/null +++ b/src/shared/types/api.ts @@ -0,0 +1,210 @@ +/** + * IPC API type definitions for Electron preload bridge. + * + * These types define the interface exposed to the renderer process + * via contextBridge. The actual implementation lives in src/preload/index.ts. + * + * Shared between preload and renderer processes. + */ + +import type { + AppConfig, + DetectedError, + NotificationTrigger, + TriggerTestResult, +} from './notifications'; +import type { WaterfallData } from './visualization'; +import type { + ConversationGroup, + FileChangeEvent, + PaginatedSessionsResult, + Project, + RepositoryGroup, + SearchSessionsResult, + Session, + SessionDetail, + SessionMetrics, + SessionsPaginationOptions, + SubagentDetail, +} from '@main/types'; + +// ============================================================================= +// Notifications API +// ============================================================================= + +/** + * Result of notifications:get with pagination. + */ +interface NotificationsResult { + notifications: DetectedError[]; + total: number; + totalCount: number; + unreadCount: number; + hasMore: boolean; +} + +/** + * Notifications API exposed via preload. + * Note: Event callbacks use `unknown` types because IPC data cannot be typed at the preload layer. + * Consumers should cast to DetectedError or NotificationClickData as appropriate. + */ +export interface NotificationsAPI { + get: (options?: { limit?: number; offset?: number }) => Promise; + markRead: (id: string) => Promise; + markAllRead: () => Promise; + delete: (id: string) => Promise; + clear: () => Promise; + getUnreadCount: () => Promise; + onNew: (callback: (event: unknown, error: unknown) => void) => () => void; + onUpdated: ( + callback: (event: unknown, payload: { total: number; unreadCount: number }) => void + ) => () => void; + onClicked: (callback: (event: unknown, data: unknown) => void) => () => void; +} + +// ============================================================================= +// Config API +// ============================================================================= + +/** + * Config API exposed via preload. + */ +export interface ConfigAPI { + get: () => Promise; + update: (section: string, data: object) => Promise; + addIgnoreRegex: (pattern: string) => Promise; + removeIgnoreRegex: (pattern: string) => Promise; + addIgnoreRepository: (repositoryId: string) => Promise; + removeIgnoreRepository: (repositoryId: string) => Promise; + snooze: (minutes: number) => Promise; + clearSnooze: () => Promise; + // Trigger management methods + addTrigger: (trigger: Omit) => Promise; + updateTrigger: (triggerId: string, updates: Partial) => Promise; + removeTrigger: (triggerId: string) => Promise; + getTriggers: () => Promise; + testTrigger: (trigger: NotificationTrigger) => Promise; + /** Opens native folder selection dialog and returns selected paths */ + selectFolders: () => Promise; + /** Opens the config JSON file in an external editor */ + openInEditor: () => Promise; + /** Pin a session for a project */ + pinSession: (projectId: string, sessionId: string) => Promise; + /** Unpin a session for a project */ + unpinSession: (projectId: string, sessionId: string) => Promise; +} + +// ============================================================================= +// Session API +// ============================================================================= + +/** + * Session navigation API exposed via preload. + */ +export interface SessionAPI { + scrollToLine: (sessionId: string, lineNumber: number) => Promise; +} + +// ============================================================================= +// CLAUDE.md File Info +// ============================================================================= + +/** + * CLAUDE.md file information returned from reading operations. + */ +export interface ClaudeMdFileInfo { + path: string; + exists: boolean; + charCount: number; + estimatedTokens: number; +} + +// ============================================================================= +// Main Electron API +// ============================================================================= + +/** + * Complete Electron API exposed to the renderer process via preload script. + */ +export interface ElectronAPI { + getAppVersion: () => Promise; + getProjects: () => Promise; + getSessions: (projectId: string) => Promise; + getSessionsPaginated: ( + projectId: string, + cursor: string | null, + limit?: number, + options?: SessionsPaginationOptions + ) => Promise; + searchSessions: ( + projectId: string, + query: string, + maxResults?: number + ) => Promise; + getSessionDetail: (projectId: string, sessionId: string) => Promise; + getSessionMetrics: (projectId: string, sessionId: string) => Promise; + getWaterfallData: (projectId: string, sessionId: string) => Promise; + getSubagentDetail: ( + projectId: string, + sessionId: string, + subagentId: string + ) => Promise; + getSessionGroups: (projectId: string, sessionId: string) => Promise; + + // Repository grouping (worktree support) + getRepositoryGroups: () => Promise; + getWorktreeSessions: (worktreeId: string) => Promise; + + // Validation methods + validatePath: ( + relativePath: string, + projectPath: string + ) => Promise<{ exists: boolean; isDirectory?: boolean }>; + validateMentions: ( + mentions: { type: 'path'; value: string }[], + projectPath: string + ) => Promise>; + + // CLAUDE.md reading methods + readClaudeMdFiles: (projectRoot: string) => Promise>; + readDirectoryClaudeMd: (dirPath: string) => Promise; + readMentionedFile: ( + absolutePath: string, + projectRoot: string, + maxTokens?: number + ) => Promise; + + // Notifications API + notifications: NotificationsAPI; + + // Config API + config: ConfigAPI; + + // Deep link navigation + session: SessionAPI; + + // Window zoom sync (for traffic-light-safe layout) + getZoomFactor: () => Promise; + onZoomFactorChanged: (callback: (zoomFactor: number) => void) => () => void; + + // File change events (real-time updates) + onFileChange: (callback: (event: FileChangeEvent) => void) => () => void; + onTodoChange: (callback: (event: FileChangeEvent) => void) => () => void; + + // Shell operations + openPath: ( + targetPath: string, + projectRoot?: string + ) => Promise<{ success: boolean; error?: string }>; + openExternal: (url: string) => Promise<{ success: boolean; error?: string }>; +} + +// ============================================================================= +// Window Type Extension +// ============================================================================= + +declare global { + interface Window { + electronAPI: ElectronAPI; + } +} diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts new file mode 100644 index 00000000..41e53ef1 --- /dev/null +++ b/src/shared/types/index.ts @@ -0,0 +1,22 @@ +/** + * Shared type definitions - re-exports types from main process for use in renderer. + * + * This module provides a stable import path (@shared/types) for types that + * are shared between main and renderer processes, allowing proper boundary + * separation while maintaining type safety. + * + * Usage: + * import type { Session, Chunk, ParsedMessage } from '@shared/types'; + */ + +// Re-export all types from main process types +export * from '@main/types'; + +// Re-export notification and config types +export * from './notifications'; + +// Re-export visualization types (WaterfallData, WaterfallItem) +export type * from './visualization'; + +// Re-export API types (ElectronAPI, ConfigAPI, etc.) +export type * from './api'; diff --git a/src/shared/types/notifications.ts b/src/shared/types/notifications.ts new file mode 100644 index 00000000..7f1fb5b4 --- /dev/null +++ b/src/shared/types/notifications.ts @@ -0,0 +1,278 @@ +/** + * Notification and configuration types for Claude Code Context. + * + * These types define: + * - Detected errors from session files + * - Notification triggers (rules for when to notify) + * - Application configuration settings + * + * Shared between preload and renderer processes. + */ + +import type { TriggerColor } from '@shared/constants/triggerColors'; + +// ============================================================================= +// Detected Error Types +// ============================================================================= + +/** + * Detected error from session JSONL files. + * Used for notification display and deep linking to error locations. + */ +export interface DetectedError { + /** UUID for unique identification */ + id: string; + /** Unix timestamp when error occurred */ + timestamp: number; + /** Session ID where error occurred */ + sessionId: string; + /** Project ID (encoded project path) */ + projectId: string; + /** Path to the JSONL file */ + filePath: string; + /** Tool name or 'assistant' */ + source: string; + /** Error message text */ + message: string; + /** Line number in JSONL for deep linking */ + lineNumber?: number; + /** Tool use ID for precise deep linking to the specific tool item */ + toolUseId?: string; + /** Subagent ID when error originates from a subagent session */ + subagentId?: string; + /** Whether the notification has been read */ + isRead: boolean; + /** When the notification was created */ + createdAt: number; + /** Trigger color key for notification dot and highlight */ + triggerColor?: TriggerColor; + /** ID of the trigger that produced this notification */ + triggerId?: string; + /** Human-readable name of the trigger that produced this notification */ + triggerName?: string; + /** Additional context */ + context: { + /** Display name of the project */ + projectName: string; + /** Current working directory when error occurred */ + cwd?: string; + }; +} + +// ============================================================================= +// Notification Trigger Types +// ============================================================================= + +/** + * Content types that can trigger notifications. + */ +export type TriggerContentType = 'tool_result' | 'tool_use' | 'thinking' | 'text'; + +/** + * Known tool names that can be filtered for tool_use triggers. + */ +export const KNOWN_TOOL_NAMES = [ + 'Bash', + 'Task', + 'TodoWrite', + 'Read', + 'Write', + 'Edit', + 'Grep', + 'Glob', + 'WebFetch', + 'WebSearch', + 'LSP', + 'Skill', + 'NotebookEdit', + 'AskUserQuestion', + 'KillShell', + 'TaskOutput', +] as const; + +/** + * Tool names that can be filtered for tool_use triggers. + * Accepts known tool names or any custom tool name. + */ +export type TriggerToolName = (typeof KNOWN_TOOL_NAMES)[number] | (string & Record); + +/** + * Match fields available for different content types and tools. + */ +export type MatchFieldForToolResult = 'content'; +export type MatchFieldForBash = 'command' | 'description'; +export type MatchFieldForTask = 'description' | 'prompt' | 'subagent_type'; +export type MatchFieldForRead = 'file_path'; +export type MatchFieldForWrite = 'file_path' | 'content'; +export type MatchFieldForEdit = 'file_path' | 'old_string' | 'new_string'; +export type MatchFieldForGlob = 'pattern' | 'path'; +export type MatchFieldForGrep = 'pattern' | 'path' | 'glob'; +export type MatchFieldForWebFetch = 'url' | 'prompt'; +export type MatchFieldForWebSearch = 'query'; +export type MatchFieldForSkill = 'skill' | 'args'; +export type MatchFieldForThinking = 'thinking'; +export type MatchFieldForText = 'text'; + +/** + * Combined type for all possible match fields. + */ +export type TriggerMatchField = + | MatchFieldForToolResult + | MatchFieldForBash + | MatchFieldForTask + | MatchFieldForRead + | MatchFieldForWrite + | MatchFieldForEdit + | MatchFieldForGlob + | MatchFieldForGrep + | MatchFieldForWebFetch + | MatchFieldForWebSearch + | MatchFieldForSkill + | MatchFieldForThinking + | MatchFieldForText; + +/** + * Trigger mode determines how the trigger evaluates conditions. + * - 'error_status': Triggers when is_error is true (simple boolean check) + * - 'content_match': Triggers when content matches a regex pattern + * - 'token_threshold': Triggers when token count exceeds threshold + */ +export type TriggerMode = 'error_status' | 'content_match' | 'token_threshold'; + +/** + * Token type for threshold triggers. + */ +export type TriggerTokenType = 'input' | 'output' | 'total'; + +/** + * Notification trigger configuration. + * Defines when notifications should be generated. + */ +export interface NotificationTrigger { + /** Unique identifier for this trigger */ + id: string; + /** Human-readable name for this trigger */ + name: string; + /** Whether this trigger is enabled */ + enabled: boolean; + /** Content type to match */ + contentType: TriggerContentType; + /** For tool_use/tool_result: specific tool name to match */ + toolName?: TriggerToolName; + /** Whether this is a built-in trigger (cannot be deleted) */ + isBuiltin?: boolean; + /** Regex patterns to IGNORE (skip notification if content matches any of these) */ + ignorePatterns?: string[]; + + // === Discriminated Union Mode === + /** Trigger evaluation mode */ + mode: TriggerMode; + + // === Mode: error_status === + /** For error_status mode: always triggers on is_error=true */ + requireError?: boolean; + + // === Mode: content_match === + /** For content_match mode: field to match against */ + matchField?: TriggerMatchField; + /** For content_match mode: regex pattern to match */ + matchPattern?: string; + + // === Mode: token_threshold === + /** For token_threshold mode: minimum token count to trigger */ + tokenThreshold?: number; + /** For token_threshold mode: which token type to check */ + tokenType?: TriggerTokenType; + + // === Repository Scope === + /** If set, this trigger only applies to these repository group IDs */ + repositoryIds?: string[]; + + // === Display === + /** Color for notification dot and navigation highlight (preset key or hex string) */ + color?: TriggerColor; +} + +/** + * Result of testing a trigger against historical data. + */ +export interface TriggerTestResult { + totalCount: number; + errors: { + id: string; + sessionId: string; + projectId: string; + message: string; + timestamp: number; + source: string; + /** Tool use ID for precise deep linking to the specific tool item */ + toolUseId?: string; + /** Subagent ID when error originates from or targets a subagent */ + subagentId?: string; + /** Line number in JSONL for deep linking */ + lineNumber?: number; + context: { projectName: string }; + }[]; + /** + * True if results were truncated due to safety limits: + * - totalCount capped at 10,000 + * - Max 100 sessions scanned + * - 30 second timeout + */ + truncated?: boolean; +} + +// ============================================================================= +// Application Configuration Types +// ============================================================================= + +/** + * Application configuration settings. + * Persisted to disk and loaded on app startup. + */ +export interface AppConfig { + /** Notification-related settings */ + notifications: { + /** Whether notifications are enabled globally */ + enabled: boolean; + /** Whether to play sound with notifications */ + soundEnabled: boolean; + /** Regex patterns for errors to ignore */ + ignoredRegex: string[]; + /** Repository group IDs to ignore for notifications */ + ignoredRepositories: string[]; + /** Unix timestamp until which notifications are snoozed (null if not snoozed) */ + snoozedUntil: number | null; + /** Default snooze duration in minutes */ + snoozeMinutes: number; + /** Whether to include errors from subagent sessions */ + includeSubagentErrors: boolean; + /** Notification triggers - define when to generate notifications */ + triggers: NotificationTrigger[]; + }; + /** General application settings */ + general: { + /** Whether to launch app at system login */ + launchAtLogin: boolean; + /** Whether to show icon in dock (macOS) */ + showDockIcon: boolean; + /** Application theme */ + theme: 'dark' | 'light' | 'system'; + /** Default tab to show on app launch */ + defaultTab: 'dashboard' | 'last-session'; + }; + /** Display and UI settings */ + display: { + /** Whether to show timestamps in message views */ + showTimestamps: boolean; + /** Whether to use compact display mode */ + compactMode: boolean; + /** Whether to enable syntax highlighting in code blocks */ + syntaxHighlighting: boolean; + }; + /** Session-related settings */ + sessions: { + /** Pinned sessions per project. Key is projectId, value is array of pinned sessions */ + pinnedSessions: Record; + }; +} diff --git a/src/shared/types/visualization.ts b/src/shared/types/visualization.ts new file mode 100644 index 00000000..827e3a95 --- /dev/null +++ b/src/shared/types/visualization.ts @@ -0,0 +1,60 @@ +/** + * Visualization-specific types for Claude Code Context. + * + * These types are used for waterfall chart visualization + * and are shared between main and renderer processes. + */ + +import type { TokenUsage } from '@main/types'; + +// ============================================================================= +// Waterfall Chart Types +// ============================================================================= + +/** + * Waterfall item for visualization. + */ +export interface WaterfallItem { + /** Unique item identifier */ + id: string; + /** Display label */ + label: string; + /** Item start time */ + startTime: Date; + /** Item end time */ + endTime: Date; + /** Duration in milliseconds */ + durationMs: number; + /** Token usage for this item */ + tokenUsage: TokenUsage; + /** Hierarchy depth (0 = main session) */ + level: number; + /** Item type */ + type: 'chunk' | 'subagent' | 'tool'; + /** Whether executed in parallel */ + isParallel: boolean; + /** Parent item ID */ + parentId?: string; + /** Group ID for parallel items */ + groupId?: string; + /** Additional metadata for display */ + metadata?: { + subagentType?: string; + toolName?: string; + messageCount?: number; + }; +} + +/** + * Complete waterfall chart data. + */ +export interface WaterfallData { + /** All waterfall items */ + items: WaterfallItem[]; + /** Earliest timestamp in the session */ + minTime: Date; + /** Latest timestamp in the session */ + maxTime: Date; + /** Total session duration in milliseconds */ + totalDurationMs: number; +} diff --git a/src/shared/utils/contentSanitizer.ts b/src/shared/utils/contentSanitizer.ts new file mode 100644 index 00000000..e85c145d --- /dev/null +++ b/src/shared/utils/contentSanitizer.ts @@ -0,0 +1,151 @@ +/** + * Content sanitization utilities for display. + * + * SHARED MODULE: Used by both main and renderer processes. + * - Main process: Used in jsonl.ts for initial parsing + * - Renderer process: Used in groupTransformer.ts for display formatting + * + * This module handles conversion of raw JSONL content (with XML tags) into + * human-readable format for the UI. + * + * NOTE: This file was previously duplicated in both main/utils and renderer/utils. + * Consolidated to src/shared/utils to maintain DRY principle while serving both processes. + */ + +/** + * Patterns for noise tags that should be completely removed. + * These are system-generated metadata that provide no value in display. + */ +const NOISE_TAG_PATTERNS = [ + /[\s\S]*?<\/local-command-caveat>/gi, + /[\s\S]*?<\/system-reminder>/gi, +]; + +/** + * Extract content from tags. + * Returns the command output without the wrapper tags. + */ +function extractCommandOutput(content: string): string | null { + const match = /([\s\S]*?)<\/local-command-stdout>/i.exec(content); + const matchStderr = /([\s\S]*?)<\/local-command-stderr>/i.exec(content); + if (match) { + return match[1].trim(); + } + if (matchStderr) { + return matchStderr[1].trim(); + } + return null; +} + +/** + * Extract command info from command XML tags. + * Returns the slash command in readable format (e.g., "/model sonnet") + */ +function extractCommandDisplay(content: string): string | null { + const commandNameMatch = /\/([^<]+)<\/command-name>/.exec(content); + const commandArgsMatch = /([^<]*)<\/command-args>/.exec(content); + + if (commandNameMatch) { + const commandName = `/${commandNameMatch[1].trim()}`; + const args = commandArgsMatch?.[1]?.trim(); + return args ? `${commandName} ${args}` : commandName; + } + + return null; +} + +/** + * Check if content is primarily a command message. + * Handles both orderings: + * - Built-in commands: comes first + * - Skill commands: comes first, followed by + */ +export function isCommandContent(content: string): boolean { + return content.startsWith('') || content.startsWith(''); +} + +/** + * Check if content is a command output message. + */ +export function isCommandOutputContent(content: string): boolean { + return ( + content.startsWith('') || content.startsWith('') + ); +} + +/** + * Sanitize content for display. + * + * - Command messages: Converted to readable format (e.g., "/model sonnet") + * - Command output: Extracted from tags + * - Noise tags: Completely removed + * - Regular content: Returned as-is + */ +export function sanitizeDisplayContent(content: string): string { + // If it's a command output message, extract the output content + if (isCommandOutputContent(content)) { + const commandOutput = extractCommandOutput(content); + if (commandOutput) { + return commandOutput; + } + } + + // If it's a command message, extract the command for display + if (isCommandContent(content)) { + const commandDisplay = extractCommandDisplay(content); + if (commandDisplay) { + return commandDisplay; + } + } + + // Remove noise tags + let sanitized = content; + for (const pattern of NOISE_TAG_PATTERNS) { + sanitized = sanitized.replace(pattern, ''); + } + + // Also remove any remaining command tags (in case of mixed content) + sanitized = sanitized + .replace(/[\s\S]*?<\/command-name>/gi, '') + .replace(/[\s\S]*?<\/command-message>/gi, '') + .replace(/[\s\S]*?<\/command-args>/gi, ''); + + return sanitized.trim(); +} + +/** + * Slash info extracted from command XML tags. + * All slash commands have the same format: + * /xxx + * xxx + * optional + */ +export interface SlashInfo { + /** Slash name without the leading slash (e.g., "model", "isolate-context") */ + name: string; + /** Message content from */ + message?: string; + /** Optional arguments from */ + args?: string; +} + +/** + * Extract slash information from command XML tags. + * Works for all slash types: skills, built-in commands, plugins, MCP, user commands. + * Returns null if not a slash command format. + */ +export function extractSlashInfo(content: string): SlashInfo | null { + const nameMatch = /\/([^<]+)<\/command-name>/.exec(content); + if (!nameMatch) return null; + + const name = nameMatch[1].trim(); + + const messageMatch = /([^<]*)<\/command-message>/.exec(content); + const argsMatch = /([^<]*)<\/command-args>/.exec(content); + + return { + name, + message: messageMatch?.[1]?.trim() ?? undefined, + args: argsMatch?.[1]?.trim() ?? undefined, + }; +} diff --git a/src/shared/utils/errorHandling.ts b/src/shared/utils/errorHandling.ts new file mode 100644 index 00000000..da7211c3 --- /dev/null +++ b/src/shared/utils/errorHandling.ts @@ -0,0 +1,26 @@ +/** + * Shared error handling utilities. + * + * Provides type-safe error message extraction and formatting + * for use across both main and renderer processes. + */ + +/** + * Extracts a human-readable error message from an unknown error value. + * Handles Error instances, strings, and other types safely. + * + * @param error - The error value (could be Error, string, or unknown) + * @returns A string error message + */ +export function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + if (typeof error === 'string') { + return error; + } + if (error && typeof error === 'object' && 'message' in error) { + return String((error as { message: unknown }).message); + } + return String(error); +} diff --git a/src/shared/utils/logger.ts b/src/shared/utils/logger.ts new file mode 100644 index 00000000..a361b20b --- /dev/null +++ b/src/shared/utils/logger.ts @@ -0,0 +1,69 @@ +/** + * Centralized logging utility for the application. + * + * Provides namespace-prefixed logging with environment-based filtering: + * - Development: All log levels (DEBUG, INFO, WARN, ERROR) + * - Production: Only ERROR logs are shown + * + * Usage: + * ```typescript + * import { createLogger } from '@shared/utils/logger'; + * const logger = createLogger('IPC:config'); + * logger.info('Config loaded'); + * logger.error('Failed to load config', error); + * ``` + */ + +enum LogLevel { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, + NONE = 4, +} + +class Logger { + private static level: LogLevel = + process.env.NODE_ENV === 'production' ? LogLevel.ERROR : LogLevel.WARN; + + constructor(private namespace: string) {} + + debug(...args: unknown[]): void { + if (Logger.level <= LogLevel.DEBUG) { + console.debug(`[${this.namespace}]`, ...args); + } + } + + info(...args: unknown[]): void { + if (Logger.level <= LogLevel.INFO) { + console.log(`[${this.namespace}]`, ...args); + } + } + + warn(...args: unknown[]): void { + if (Logger.level <= LogLevel.WARN) { + console.warn(`[${this.namespace}]`, ...args); + } + } + + error(...args: unknown[]): void { + if (Logger.level <= LogLevel.ERROR) { + console.error(`[${this.namespace}]`, ...args); + } + } + + /** Allow runtime level changes (for testing/debugging) */ + static setLevel(level: LogLevel): void { + Logger.level = level; + } + + static getLevel(): LogLevel { + return Logger.level; + } +} + +export function createLogger(namespace: string): Logger { + return new Logger(namespace); +} + +export type { Logger }; diff --git a/src/shared/utils/markdownTextSearch.ts b/src/shared/utils/markdownTextSearch.ts new file mode 100644 index 00000000..d33f13f3 --- /dev/null +++ b/src/shared/utils/markdownTextSearch.ts @@ -0,0 +1,204 @@ +/** + * Markdown-aware text search utility. + * + * Converts markdown through the **same pipeline** as react-markdown: + * remark-parse → remarkGfm → mdast-util-to-hast → HAST tree + * + * Then collects text nodes only from HAST elements whose corresponding + * React components call `hl(children)` (highlightSearchInChildren). + * This ensures match counts align exactly with what the renderer produces. + * + * Key design: segments are collected per-text-node, NOT concatenated. + * `highlightSearchText` operates per-React-string-child, so a match + * spanning two elements is not valid in either layer. + */ + +import { toHast } from 'mdast-util-to-hast'; +import remarkGfm from 'remark-gfm'; +import remarkParse from 'remark-parse'; +import { unified } from 'unified'; + +import type { Nodes as HastNodes } from 'hast'; +import type { Root as MdastRoot } from 'mdast'; + +// --------------------------------------------------------------------------- +// Parser singleton +// --------------------------------------------------------------------------- + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -- inferred type used by MarkdownParser alias +function createParser() { + return unified().use(remarkParse).use(remarkGfm); +} + +type MarkdownParser = ReturnType; + +let _parser: MarkdownParser | null = null; + +function getParser(): MarkdownParser { + if (!_parser) { + _parser = createParser(); + } + return _parser; +} + +function parseMarkdown(text: string): MdastRoot { + return getParser().parse(text); +} + +// --------------------------------------------------------------------------- +// Segment cache (parse once, search many times per query keystroke) +// --------------------------------------------------------------------------- + +const MAX_CACHE_SIZE = 200; +const segmentCache = new Map(); + +function getCachedSegments(markdown: string): string[] { + const cached = segmentCache.get(markdown); + if (cached) return cached; + + const segments = collectTextSegments(markdown); + + // Evict oldest entries when cache is full + if (segmentCache.size >= MAX_CACHE_SIZE) { + const firstKey = segmentCache.keys().next().value; + if (firstKey !== undefined) segmentCache.delete(firstKey); + } + segmentCache.set(markdown, segments); + return segments; +} + +// --------------------------------------------------------------------------- +// HAST → text segments +// --------------------------------------------------------------------------- + +/** + * HTML element tag names whose React component counterparts call + * `hl(children)` (highlightSearchInChildren). + * + * Block-level elements call hl(): p, h1-h6, blockquote, li, th, td, code (block only) + * Inline elements do NOT call hl(): strong, em, a, del, code (inline) + * The block element's hl() recursively descends into inline children, + * processing text in document order — matching this walker's traversal. + * + * Inline tags are omitted from this set because they are always nested + * inside a block-level HL element in standard markdown, so their text + * is collected via the inherited `inHlElement` flag. + * + * Must stay in sync with createMarkdownComponents() in markdownComponents.tsx, + * createUserMarkdownComponents() in UserChatGroup.tsx, and + * createViewerMarkdownComponents() in MarkdownViewer.tsx. + */ +const HL_TAGS = new Set([ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'p', + 'code', + 'blockquote', + 'li', + 'th', + 'td', +]); + +/** + * Parse markdown → mdast → HAST, then collect text nodes from elements + * whose React components call `hl()`. This produces the exact same + * text segments that `highlightSearchInChildren` processes at render time. + */ +export function collectTextSegments(markdown: string): string[] { + const mdast = parseMarkdown(markdown); + const hast = toHast(mdast); + if (!hast) return []; + + const segments: string[] = []; + walkHast(hast, segments, false); + return segments; +} + +function walkHast(node: HastNodes, segments: string[], inHlElement: boolean): void { + // Raw HTML nodes (e.g. ...) are dropped by ReactMarkdown + // without rehype-raw, so we must skip them to keep match counts aligned. + if (node.type === 'raw') return; + + if (node.type === 'text') { + if (inHlElement && node.value) { + segments.push(node.value); + } + return; + } + + if (node.type === 'element' || node.type === 'root') { + const isHl = node.type === 'element' && HL_TAGS.has(node.tagName); + for (const child of node.children) { + walkHast(child as HastNodes, segments, inHlElement || isHl); + } + } + // skip comments, doctypes +} + +// --------------------------------------------------------------------------- +// Search functions +// --------------------------------------------------------------------------- + +export interface MarkdownSearchMatch { + matchIndexInItem: number; +} + +/** + * Parse markdown into segments and search each segment individually. + * Returns per-item match indices that align with what the renderer produces. + */ +export function findMarkdownSearchMatches(markdown: string, query: string): MarkdownSearchMatch[] { + if (!query || !markdown) return []; + + const segments = getCachedSegments(markdown); + const lowerQuery = query.toLowerCase(); + const matches: MarkdownSearchMatch[] = []; + let matchIndex = 0; + + for (const segment of segments) { + const lowerSegment = segment.toLowerCase(); + let pos = 0; + while ((pos = lowerSegment.indexOf(lowerQuery, pos)) !== -1) { + matches.push({ matchIndexInItem: matchIndex }); + matchIndex++; + pos += lowerQuery.length; + } + } + + return matches; +} + +/** + * Count matches (cheaper than allocating match objects when only the count is needed). + */ +export function countMarkdownSearchMatches(markdown: string, query: string): number { + if (!query || !markdown) return 0; + + const segments = getCachedSegments(markdown); + const lowerQuery = query.toLowerCase(); + let count = 0; + + for (const segment of segments) { + const lowerSegment = segment.toLowerCase(); + let pos = 0; + while ((pos = lowerSegment.indexOf(lowerQuery, pos)) !== -1) { + count++; + pos += lowerQuery.length; + } + } + + return count; +} + +/** + * Join all visible text segments with spaces for use in context snippets. + */ +export function extractMarkdownPlainText(markdown: string): string { + if (!markdown) return ''; + const segments = getCachedSegments(markdown); + return segments.join(' '); +} diff --git a/src/shared/utils/modelParser.ts b/src/shared/utils/modelParser.ts new file mode 100644 index 00000000..8d9fbf37 --- /dev/null +++ b/src/shared/utils/modelParser.ts @@ -0,0 +1,155 @@ +/** + * Claude model string parser utility. + * Parses model identifiers into friendly display names and metadata. + */ + +/** Known model families with specific styling */ +export type KnownModelFamily = 'sonnet' | 'opus' | 'haiku'; + +/** Model family can be a known family or any arbitrary string for new/unknown models */ +export type ModelFamily = KnownModelFamily | (string & Record); + +export interface ModelInfo { + /** Friendly name like "sonnet4.5" */ + name: string; + /** Model family: sonnet, opus, haiku, or any other string for unknown families */ + family: ModelFamily; + /** Major version like 4 or 3 */ + majorVersion: number; + /** Minor version like 5 or 1 (null if not present) */ + minorVersion: number | null; +} + +const KNOWN_FAMILIES: KnownModelFamily[] = ['sonnet', 'opus', 'haiku']; + +/** + * Parses a Claude model string into friendly display info. + * Returns null if model string is invalid, synthetic, or empty. + * + * Supported formats: + * - New format: claude-{family}-{major}-{minor}-{date} (e.g., "claude-sonnet-4-5-20250929") + * - Old format: claude-{major}-{family}-{date} (e.g., "claude-3-opus-20240229") + * - Old format with minor: claude-{major}-{minor}-{family}-{date} (e.g., "claude-3-5-sonnet-20241022") + */ +export function parseModelString(model: string | undefined): ModelInfo | null { + // Handle null, undefined, empty, or synthetic models + if (!model || model.trim() === '' || model === '') { + return null; + } + + const normalized = model.toLowerCase().trim(); + + // Must start with "claude" + if (!normalized.startsWith('claude')) { + return null; + } + + // Split into parts (e.g., ["claude", "sonnet", "4", "5", "20250929"]) + const parts = normalized.split('-'); + + if (parts.length < 3) { + return null; + } + + // Detect model family - first check known families, then accept any non-numeric string + let family: ModelFamily | null = null; + let familyIndex = -1; + + // First pass: look for known families + for (let i = 1; i < parts.length; i++) { + const part = parts[i]; + if (KNOWN_FAMILIES.includes(part as KnownModelFamily)) { + family = part as KnownModelFamily; + familyIndex = i; + break; + } + } + + // Second pass: if no known family found, look for any non-numeric, non-date string as family + if (family === null) { + for (let i = 1; i < parts.length; i++) { + const part = parts[i]; + // Skip numeric parts and date-like parts (8 digits) + if (!/^\d+$/.test(part) && !/^\d{8}$/.test(part) && part.length > 1) { + family = part; + familyIndex = i; + break; + } + } + } + + if (family === null || familyIndex === -1) { + return null; + } + + let majorVersion: number; + let minorVersion: number | null = null; + + // Determine format based on family position + if (familyIndex === 1) { + // New format: claude-{family}-{major}-{minor}-{date} + // e.g., claude-sonnet-4-5-20250929 -> ["claude", "sonnet", "4", "5", "20250929"] + if (parts.length < 4) { + return null; + } + + majorVersion = parseInt(parts[2], 10); + if (isNaN(majorVersion)) { + return null; + } + + // Check if there's a minor version (next part is a number and not a date) + if (parts.length >= 4 && parts[3].length <= 2) { + const potentialMinor = parseInt(parts[3], 10); + if (!isNaN(potentialMinor)) { + minorVersion = potentialMinor; + } + } + } else { + // Old format: claude-{major}[-{minor}]-{family}-{date} + // e.g., claude-3-opus-20240229 -> ["claude", "3", "opus", "20240229"] + // e.g., claude-3-5-sonnet-20241022 -> ["claude", "3", "5", "sonnet", "20241022"] + + majorVersion = parseInt(parts[1], 10); + if (isNaN(majorVersion)) { + return null; + } + + // Check if there's a minor version between major and family + if (familyIndex > 2) { + const potentialMinor = parseInt(parts[2], 10); + if (!isNaN(potentialMinor)) { + minorVersion = potentialMinor; + } + } + } + + // Build friendly name + const versionString = + minorVersion !== null ? `${majorVersion}.${minorVersion}` : `${majorVersion}`; + const name = `${family}${versionString}`; + + return { + name, + family, + majorVersion, + minorVersion, + }; +} + +/** + * Gets the color class for a model family (for Tailwind). + * Uses consistent neutral gray styling for a clean, Linear-like design. + * All models use the same muted color for visual consistency. + */ +export function getModelColorClass(family: ModelFamily): string { + // All families use consistent neutral gray for clean design + switch (family) { + case 'opus': + case 'sonnet': + case 'haiku': + return 'text-zinc-400'; + default: + return 'text-zinc-500'; + } +} diff --git a/src/shared/utils/teammateMessageParser.ts b/src/shared/utils/teammateMessageParser.ts new file mode 100644 index 00000000..574b747e --- /dev/null +++ b/src/shared/utils/teammateMessageParser.ts @@ -0,0 +1,52 @@ +/** + * Teammate Message Parser + * + * Parses XML content into structured data. + * Handles single or multiple blocks in one message. + * Pure function for cross-process use (renderer needs it in displayItemBuilder). + */ + +export interface ParsedTeammateContent { + teammateId: string; + color: string; + summary: string; + content: string; +} + +/** + * Regex to match a single block (non-greedy content). + * Captures: [1] teammate_id, [2] remaining attributes string, [3] inner content + */ +const TEAMMATE_BLOCK_RE = + /]*)>([\s\S]*?)<\/teammate-message>/g; + +const COLOR_RE = /color="([^"]*)"/; +const SUMMARY_RE = /summary="([^"]*)"/; + +/** + * Parse all blocks from raw content. + * Returns an array of parsed blocks (may be 0, 1, or many). + */ +export function parseAllTeammateMessages(rawContent: string): ParsedTeammateContent[] { + const results: ParsedTeammateContent[] = []; + const regex = new RegExp(TEAMMATE_BLOCK_RE.source, TEAMMATE_BLOCK_RE.flags); + + let match: RegExpExecArray | null; + while ((match = regex.exec(rawContent)) !== null) { + const teammateId = match[1]; + const attrs = match[2]; + const content = match[3].trim(); + + const colorMatch = COLOR_RE.exec(attrs); + const summaryMatch = SUMMARY_RE.exec(attrs); + + results.push({ + teammateId, + color: colorMatch?.[1] ?? '', + summary: summaryMatch?.[1] ?? '', + content, + }); + } + + return results; +} diff --git a/src/shared/utils/tokenFormatting.ts b/src/shared/utils/tokenFormatting.ts new file mode 100644 index 00000000..aafed8fe --- /dev/null +++ b/src/shared/utils/tokenFormatting.ts @@ -0,0 +1,91 @@ +/** + * Shared token formatting utilities. + * + * This module consolidates all token-related formatting functions across the codebase. + * Use these functions instead of implementing token formatting inline. + */ + +/** + * Formats token count for compact display. + * Shows full number under 1k, uses 'k' suffix for thousands, 'M' suffix for millions. + * + * Examples: + * - 500 -> "500" + * - 1500 -> "1.5k" + * - 50000 -> "50.0k" + * - 1500000 -> "1.5M" + */ +export function formatTokensCompact(tokens: number): string { + if (tokens >= 1000000) { + return `${(tokens / 1000000).toFixed(1)}M`; + } + if (tokens >= 1000) { + return `${(tokens / 1000).toFixed(1)}k`; + } + return tokens.toString(); +} + +/** + * Formats token count with smart precision. + * Uses one decimal for 1k-10k range, whole numbers above 10k. + * + * Examples: + * - 500 -> "500" + * - 1500 -> "1.5k" + * - 15000 -> "15k" + */ +export function formatTokens(tokens: number): string { + if (tokens < 1000) { + return `${tokens}`; + } + if (tokens < 10000) { + return `${(tokens / 1000).toFixed(1)}k`; + } + return `${Math.round(tokens / 1000)}k`; +} + +/** + * Formats token count with locale-aware separators. + * Used for detailed views where exact numbers matter. + * + * Examples: + * - 1500 -> "1,500" (in en-US locale) + * - 1000000 -> "1,000,000" + */ +export function formatTokensDetailed(tokens: number): string { + return tokens.toLocaleString(); +} + +/** + * Estimates token count from text content. + * Uses the rough heuristic of ~4 characters per token, which is a + * reasonable average for English text and code. + * + * This is faster than using a real tokenizer and accurate enough + * for display purposes. + */ +export function estimateTokens(text: string | undefined | null): number { + if (!text || text.length === 0) { + return 0; + } + return Math.ceil(text.length / 4); +} + +/** + * Estimates tokens for content that may be a string, array, or object. + * Arrays and objects are stringified before counting. + */ +export function estimateContentTokens( + content: string | unknown[] | Record | undefined | null +): number { + if (!content) { + return 0; + } + + if (typeof content === 'string') { + return estimateTokens(content); + } + + // For array/object content, stringify and count + return estimateTokens(JSON.stringify(content)); +} diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 00000000..08e51f8e --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,53 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './src/renderer/index.html', + './src/renderer/**/*.{js,ts,jsx,tsx}', + './src/shared/**/*.{js,ts,jsx,tsx}' + ], + theme: { + extend: { + colors: { + // Theme-aware surface colors (use CSS variables) + surface: { + DEFAULT: 'var(--color-surface)', + raised: 'var(--color-surface-raised)', + overlay: 'var(--color-surface-overlay)', + sidebar: 'var(--color-surface-sidebar)', + code: 'var(--code-bg)', // Deep black for code blocks + }, + // Theme-aware border colors (use CSS variables) + border: { + DEFAULT: 'var(--color-border)', + subtle: 'var(--color-border-subtle)', + emphasis: 'var(--color-border-emphasis)', + }, + // Theme-aware text colors (use CSS variables) + text: { + DEFAULT: 'var(--color-text)', + secondary: 'var(--color-text-secondary)', + muted: 'var(--color-text-muted)', + }, + // Semantic colors (only for status, not containers) + semantic: { + success: '#22c55e', // green-500 + error: '#ef4444', // red-500 + warning: '#f59e0b', // amber-500 + info: '#3b82f6', // blue-500 + }, + // Theme-aware colors using CSS variables + // These aliases enable all existing components to automatically support light/dark mode + 'claude-dark': { + bg: 'var(--color-surface)', + surface: 'var(--color-surface-raised)', + border: 'var(--color-border)', + text: 'var(--color-text)', + 'text-secondary': 'var(--color-text-secondary)' + } + } + } + }, + plugins: [ + require('@tailwindcss/typography') + ] +} diff --git a/test/main/ipc/configValidation.test.ts b/test/main/ipc/configValidation.test.ts new file mode 100644 index 00000000..75cf42f2 --- /dev/null +++ b/test/main/ipc/configValidation.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, it } from 'vitest'; + +import { validateConfigUpdatePayload } from '../../../src/main/ipc/configValidation'; + +describe('configValidation', () => { + it('accepts valid general updates', () => { + const result = validateConfigUpdatePayload('general', { + theme: 'system', + launchAtLogin: true, + }); + + expect(result.valid).toBe(true); + if (result.valid) { + expect(result.section).toBe('general'); + expect(result.data).toEqual({ + theme: 'system', + launchAtLogin: true, + }); + } + }); + + it('rejects invalid section names', () => { + const result = validateConfigUpdatePayload('invalid-section', { theme: 'dark' }); + expect(result.valid).toBe(false); + if (!result.valid) { + expect(result.error).toContain('Section must be one of'); + } + }); + + it('rejects unknown notification keys', () => { + const result = validateConfigUpdatePayload('notifications', { unknownField: true }); + expect(result.valid).toBe(false); + if (!result.valid) { + expect(result.error).toContain('not supported'); + } + }); + + it('accepts valid notifications.triggers payload', () => { + const result = validateConfigUpdatePayload('notifications', { + triggers: [ + { + id: 'trigger-1', + name: 'test', + enabled: true, + contentType: 'tool_result', + mode: 'error_status', + requireError: true, + }, + ], + }); + expect(result.valid).toBe(true); + }); + + it('rejects invalid notifications.triggers payload', () => { + const result = validateConfigUpdatePayload('notifications', { + triggers: [{ id: 'missing-required-fields' }], + }); + expect(result.valid).toBe(false); + if (!result.valid) { + expect(result.error).toContain('valid trigger'); + } + }); + + it('rejects out-of-range snoozeMinutes', () => { + const result = validateConfigUpdatePayload('notifications', { snoozeMinutes: 0 }); + expect(result.valid).toBe(false); + if (!result.valid) { + expect(result.error).toContain('between 1 and'); + } + }); + + it('accepts valid display updates', () => { + const result = validateConfigUpdatePayload('display', { + compactMode: true, + syntaxHighlighting: false, + }); + + expect(result.valid).toBe(true); + if (result.valid) { + expect(result.section).toBe('display'); + expect(result.data).toEqual({ + compactMode: true, + syntaxHighlighting: false, + }); + } + }); +}); diff --git a/test/main/ipc/guards.test.ts b/test/main/ipc/guards.test.ts new file mode 100644 index 00000000..6af43df5 --- /dev/null +++ b/test/main/ipc/guards.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; + +import { + coercePageLimit, + coerceSearchMaxResults, + validateProjectId, + validateSearchQuery, + validateSessionId, +} from '../../../src/main/ipc/guards'; + +describe('ipc guards', () => { + it('accepts valid encoded project IDs', () => { + const result = validateProjectId('-Users-test-project'); + expect(result.valid).toBe(true); + expect(result.value).toBe('-Users-test-project'); + }); + + it('accepts valid Windows-style encoded project IDs', () => { + const result = validateProjectId('-C:-Users-test-project'); + expect(result.valid).toBe(true); + expect(result.value).toBe('-C:-Users-test-project'); + }); + + it('rejects invalid project IDs', () => { + const result = validateProjectId('../escape'); + expect(result.valid).toBe(false); + }); + + it('accepts valid session IDs', () => { + const result = validateSessionId('abc123-session_id'); + expect(result.valid).toBe(true); + }); + + it('rejects empty search queries', () => { + const result = validateSearchQuery(' '); + expect(result.valid).toBe(false); + }); + + it('caps search max results', () => { + expect(coerceSearchMaxResults(9999, 50)).toBe(200); + expect(coerceSearchMaxResults(-1, 50)).toBe(50); + }); + + it('caps pagination limits', () => { + expect(coercePageLimit(500, 20)).toBe(200); + expect(coercePageLimit(0, 20)).toBe(20); + }); +}); diff --git a/test/main/services/analysis/ChunkBuilder.test.ts b/test/main/services/analysis/ChunkBuilder.test.ts new file mode 100644 index 00000000..97aa7053 --- /dev/null +++ b/test/main/services/analysis/ChunkBuilder.test.ts @@ -0,0 +1,449 @@ +/** + * Tests for ChunkBuilder service. + * + * Tests chunk building from parsed messages: + * - UserChunk creation from user messages + * - AIChunk creation from assistant messages (with tool grouping) + * - SystemChunk creation from command output + * - Subagent linking to AIChunks + */ + +import { describe, expect, it } from 'vitest'; + +import { ChunkBuilder } from '../../../../src/main/services/analysis/ChunkBuilder'; +import { isAIChunk, isCompactChunk, isSystemChunk, isUserChunk } from '../../../../src/main/types'; +import type { ParsedMessage, Process } from '../../../../src/main/types'; + +// ============================================================================= +// Test Helpers +// ============================================================================= + +/** + * Creates a minimal ParsedMessage for testing. + */ +function createMessage(overrides: Partial): ParsedMessage { + return { + uuid: `msg-${Math.random().toString(36).slice(2, 11)}`, + parentUuid: null, + type: 'user', + timestamp: new Date(), + content: '', + isSidechain: false, + isMeta: false, + toolCalls: [], + toolResults: [], + ...overrides, + }; +} + +/** + * Creates a minimal Process (subagent) for testing. + */ +function createSubagent(overrides: Partial): Process { + return { + id: `agent-${Math.random().toString(36).slice(2, 11)}`, + filePath: '/path/to/agent.jsonl', + parentTaskId: 'task-1', + description: 'Test subagent', + startTime: new Date(), + endTime: new Date(), + durationMs: 1000, + isOngoing: false, + messages: [], + metrics: { + inputTokens: 100, + outputTokens: 50, + cacheReadTokens: 0, + cacheCreationTokens: 0, + totalTokens: 150, + messageCount: 2, + durationMs: 1000, + }, + ...overrides, + }; +} + +// ============================================================================= +// Tests +// ============================================================================= + +describe('ChunkBuilder', () => { + const builder = new ChunkBuilder(); + + describe('buildChunks', () => { + it('should return empty array for empty input', () => { + const chunks = builder.buildChunks([]); + expect(chunks).toEqual([]); + }); + + it('should filter out sidechain messages', () => { + const messages = [ + createMessage({ + type: 'user', + content: 'Main thread message', + isMeta: false, + isSidechain: false, + }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Sidechain response' }], + isSidechain: true, + }), + ]; + + const chunks = builder.buildChunks(messages); + // Only the main thread user message should create a chunk + expect(chunks).toHaveLength(1); + expect(isUserChunk(chunks[0])).toBe(true); + }); + + describe('UserChunk creation', () => { + it('should create UserChunk from real user message', () => { + const messages = [ + createMessage({ + type: 'user', + content: 'Help me debug this', + isMeta: false, + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(1); + expect(isUserChunk(chunks[0])).toBe(true); + + if (isUserChunk(chunks[0])) { + expect(chunks[0].userMessage.content).toBe('Help me debug this'); + } + }); + + it('should create UserChunk with array content', () => { + const messages = [ + createMessage({ + type: 'user', + content: [{ type: 'text', text: 'Hello world' }], + isMeta: false, + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(1); + expect(isUserChunk(chunks[0])).toBe(true); + }); + }); + + describe('AIChunk creation', () => { + it('should create AIChunk from assistant message', () => { + const messages = [ + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: "Here's how to fix it" }], + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(1); + expect(isAIChunk(chunks[0])).toBe(true); + + if (isAIChunk(chunks[0])) { + expect(chunks[0].responses).toHaveLength(1); + } + }); + + it('should group consecutive assistant messages into one AIChunk', () => { + const messages = [ + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'First response' }], + }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Second response' }], + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(1); + expect(isAIChunk(chunks[0])).toBe(true); + + if (isAIChunk(chunks[0])) { + expect(chunks[0].responses).toHaveLength(2); + } + }); + + it('should include tool results in AIChunk', () => { + const messages = [ + createMessage({ + type: 'assistant', + content: [ + { type: 'text', text: 'Reading file' }, + { type: 'tool_use', id: 't1', name: 'Read', input: { file_path: 'test.ts' } }, + ], + toolCalls: [{ id: 't1', name: 'Read', input: { file_path: 'test.ts' }, isTask: false }], + }), + createMessage({ + type: 'user', + content: [{ type: 'tool_result', tool_use_id: 't1', content: 'file contents' }], + isMeta: true, + }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Found the issue' }], + }), + ]; + + const chunks = builder.buildChunks(messages); + // All should be in one AIChunk + expect(chunks).toHaveLength(1); + expect(isAIChunk(chunks[0])).toBe(true); + + if (isAIChunk(chunks[0])) { + // 2 assistant messages + 1 tool result + expect(chunks[0].responses.length).toBeGreaterThanOrEqual(2); + } + }); + }); + + describe('SystemChunk creation', () => { + it('should create SystemChunk from command output', () => { + const messages = [ + createMessage({ + type: 'user', + content: 'Model set to sonnet', + isMeta: false, + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(1); + expect(isSystemChunk(chunks[0])).toBe(true); + + if (isSystemChunk(chunks[0])) { + expect(chunks[0].commandOutput).toContain('Model set to sonnet'); + } + }); + }); + + describe('CompactChunk creation', () => { + it('should create CompactChunk from compact summary', () => { + const messages = [ + createMessage({ + type: 'user', + content: 'Summary of conversation...', + isCompactSummary: true, + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(1); + expect(isCompactChunk(chunks[0])).toBe(true); + }); + }); + + describe('hardNoise filtering', () => { + it('should filter out system messages', () => { + const messages = [ + createMessage({ + type: 'system', + content: 'System prompt', + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(0); + }); + + it('should filter out synthetic assistant messages', () => { + const messages = [ + createMessage({ + type: 'assistant', + content: '', + model: '', + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(0); + }); + + it('should filter out caveat messages', () => { + const messages = [ + createMessage({ + type: 'user', + content: 'This is a caveat', + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(0); + }); + }); + + describe('AIChunk flushing', () => { + it('should flush AIChunk buffer when user message arrives', () => { + const messages = [ + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Response 1' }], + }), + createMessage({ + type: 'user', + content: 'New question', + isMeta: false, + }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Response 2' }], + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(3); + expect(isAIChunk(chunks[0])).toBe(true); + expect(isUserChunk(chunks[1])).toBe(true); + expect(isAIChunk(chunks[2])).toBe(true); + }); + + it('should flush AIChunk buffer when system message arrives', () => { + const messages = [ + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Response' }], + }), + createMessage({ + type: 'user', + content: 'Output', + isMeta: false, + }), + ]; + + const chunks = builder.buildChunks(messages); + expect(chunks).toHaveLength(2); + expect(isAIChunk(chunks[0])).toBe(true); + expect(isSystemChunk(chunks[1])).toBe(true); + }); + }); + + describe('subagent linking', () => { + it('should link subagent to AIChunk containing Task call', () => { + const taskId = 'task-123'; + const messages = [ + createMessage({ + type: 'assistant', + content: [ + { type: 'text', text: 'Spawning agent' }, + { + type: 'tool_use', + id: taskId, + name: 'Task', + input: { prompt: 'Do something', subagent_type: 'explore' }, + }, + ], + toolCalls: [ + { + id: taskId, + name: 'Task', + input: { prompt: 'Do something', subagent_type: 'explore' }, + isTask: true, + taskDescription: 'Do something', + taskSubagentType: 'explore', + }, + ], + }), + ]; + + const subagent = createSubagent({ + parentTaskId: taskId, + }); + + const chunks = builder.buildChunks(messages, [subagent]); + expect(chunks).toHaveLength(1); + expect(isAIChunk(chunks[0])).toBe(true); + + if (isAIChunk(chunks[0])) { + expect(chunks[0].processes).toHaveLength(1); + expect(chunks[0].processes[0].id).toBe(subagent.id); + } + }); + }); + }); + + describe('getTotalChunkMetrics', () => { + it('should return empty metrics for empty chunks', () => { + const metrics = builder.getTotalChunkMetrics([]); + expect(metrics.totalTokens).toBe(0); + expect(metrics.durationMs).toBe(0); + expect(metrics.messageCount).toBe(0); + }); + }); + + describe('buildSessionDetail', () => { + it('should build complete session detail', () => { + const session = { + id: 'session-1', + projectId: 'project-1', + projectPath: '/path/to/project', + filePath: '/path/to/session.jsonl', + timestamp: new Date(), + lastModified: new Date(), + isOngoing: false, + }; + + const messages = [ + createMessage({ + type: 'user', + content: 'Hello', + isMeta: false, + }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Hi' }], + }), + ]; + + const detail = builder.buildSessionDetail(session, messages, []); + + expect(detail.session).toBe(session); + expect(detail.messages).toBe(messages); + expect(detail.chunks.length).toBeGreaterThan(0); + expect(detail.processes).toEqual([]); + expect(detail.metrics).toBeDefined(); + }); + }); + + describe('buildWaterfallData', () => { + it('should build sorted waterfall items from chunks and subagents', () => { + const start = new Date('2026-01-01T00:00:00.000Z'); + const end = new Date('2026-01-01T00:00:10.000Z'); + + const messages = [ + createMessage({ + type: 'assistant', + timestamp: start, + content: [{ type: 'text', text: 'Running tools' }], + toolCalls: [{ id: 'tool-1', name: 'Read', input: {}, isTask: false }], + }), + createMessage({ + type: 'user', + timestamp: end, + isMeta: true, + content: [{ type: 'tool_result', tool_use_id: 'tool-1', content: 'done' }], + }), + ]; + + const subagent = createSubagent({ + id: 'agent-1', + startTime: new Date('2026-01-01T00:00:03.000Z'), + endTime: new Date('2026-01-01T00:00:08.000Z'), + durationMs: 5000, + }); + + const chunks = builder.buildChunks(messages, [subagent]); + const waterfall = builder.buildWaterfallData(chunks, [subagent]); + + expect(waterfall.items.length).toBeGreaterThan(0); + expect(waterfall.totalDurationMs).toBeGreaterThanOrEqual(0); + expect(waterfall.minTime.getTime()).toBeLessThanOrEqual(waterfall.maxTime.getTime()); + expect(waterfall.items.some((item) => item.type === 'subagent')).toBe(true); + }); + }); +}); diff --git a/test/main/services/discovery/ProjectPathResolver.test.ts b/test/main/services/discovery/ProjectPathResolver.test.ts new file mode 100644 index 00000000..3bdb27b7 --- /dev/null +++ b/test/main/services/discovery/ProjectPathResolver.test.ts @@ -0,0 +1,88 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { ProjectPathResolver } from '../../../../src/main/services/discovery/ProjectPathResolver'; + +function createSessionLine(cwd: string): string { + return JSON.stringify({ + uuid: 'test-uuid', + type: 'user', + cwd, + message: { role: 'user', content: 'hello' }, + timestamp: new Date().toISOString(), + }); +} + +describe('ProjectPathResolver', () => { + const tempDirs: string[] = []; + + afterEach(() => { + for (const tempDir of tempDirs) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + tempDirs.length = 0; + }); + + it('prefers absolute cwd hint', async () => { + const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); + tempDirs.push(projectsDir); + + const resolver = new ProjectPathResolver(projectsDir); + const resolved = await resolver.resolveProjectPath('-Users-test-proj', { + cwdHint: '/Users/test/proj', + }); + + expect(resolved).toBe('/Users/test/proj'); + }); + + it('extracts cwd from session file when available', async () => { + const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); + tempDirs.push(projectsDir); + + const projectId = '-Users-test-my-repo'; + const projectDir = path.join(projectsDir, projectId); + fs.mkdirSync(projectDir, { recursive: true }); + + const sessionPath = path.join(projectDir, 'session-1.jsonl'); + fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo')}\n`, 'utf8'); + + const resolver = new ProjectPathResolver(projectsDir); + const resolved = await resolver.resolveProjectPath(projectId); + + expect(resolved).toBe('/Users/test/my-repo'); + }); + + it('falls back to decoded project ID when no cwd is available', async () => { + const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); + tempDirs.push(projectsDir); + + const resolver = new ProjectPathResolver(projectsDir); + const resolved = await resolver.resolveProjectPath('-C:-Users-test-my-repo'); + + expect(resolved).toBe('C:/Users/test/my/repo'); + }); + + it('invalidates cached paths by project', async () => { + const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolver-projects-')); + tempDirs.push(projectsDir); + + const projectId = '-Users-test-my-repo'; + const projectDir = path.join(projectsDir, projectId); + fs.mkdirSync(projectDir, { recursive: true }); + + const sessionPath = path.join(projectDir, 'session-1.jsonl'); + fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo-v1')}\n`, 'utf8'); + + const resolver = new ProjectPathResolver(projectsDir); + const firstResolved = await resolver.resolveProjectPath(projectId); + expect(firstResolved).toBe('/Users/test/my-repo-v1'); + + fs.writeFileSync(sessionPath, `${createSessionLine('/Users/test/my-repo-v2')}\n`, 'utf8'); + resolver.invalidateProject(projectId); + + const secondResolved = await resolver.resolveProjectPath(projectId); + expect(secondResolved).toBe('/Users/test/my-repo-v2'); + }); +}); diff --git a/test/main/services/discovery/SessionSearcher.test.ts b/test/main/services/discovery/SessionSearcher.test.ts new file mode 100644 index 00000000..fa3dc845 --- /dev/null +++ b/test/main/services/discovery/SessionSearcher.test.ts @@ -0,0 +1,122 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { SessionSearcher } from '../../../../src/main/services/discovery/SessionSearcher'; + +describe('SessionSearcher', () => { + const tempDirs: string[] = []; + + afterEach(() => { + for (const dir of tempDirs) { + fs.rmSync(dir, { recursive: true, force: true }); + } + tempDirs.length = 0; + }); + + it('searches only user text and AI last text output, returning every match occurrence', async () => { + const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'session-searcher-')); + tempDirs.push(projectsDir); + + const projectId = 'project-1'; + const sessionId = 'session-1'; + const projectPath = path.join(projectsDir, projectId); + fs.mkdirSync(projectPath, { recursive: true }); + + const sessionPath = path.join(projectPath, `${sessionId}.jsonl`); + const lines = [ + JSON.stringify({ + uuid: 'user-1', + type: 'user', + timestamp: '2026-01-01T00:00:00.000Z', + message: { role: 'user', content: 'alpha intro alpha' }, + isMeta: false, + }), + JSON.stringify({ + uuid: 'asst-1', + type: 'assistant', + timestamp: '2026-01-01T00:00:01.000Z', + message: { + role: 'assistant', + content: [{ type: 'text', text: 'older alpha that should be ignored' }], + }, + }), + JSON.stringify({ + uuid: 'asst-2', + type: 'assistant', + timestamp: '2026-01-01T00:00:02.000Z', + message: { + role: 'assistant', + content: [ + { type: 'thinking', thinking: 'alpha in thinking should not be matched' }, + { type: 'text', text: 'latest alpha alpha output' }, + ], + }, + }), + ]; + fs.writeFileSync(sessionPath, `${lines.join('\n')}\n`, 'utf8'); + + const searcher = new SessionSearcher(projectsDir); + const result = await searcher.searchSessions(projectId, 'alpha', 50); + + expect(result.totalMatches).toBe(4); + expect(result.results).toHaveLength(4); + + const userResults = result.results.filter((entry) => entry.groupId === 'user-user-1'); + const aiResults = result.results.filter((entry) => entry.groupId === 'ai-asst-1'); + + expect(userResults).toHaveLength(2); + expect(aiResults).toHaveLength(2); + expect(userResults.map((entry) => entry.matchIndexInItem)).toEqual([0, 1]); + expect(aiResults.map((entry) => entry.matchIndexInItem)).toEqual([0, 1]); + expect(result.results.some((entry) => entry.context.includes('ignored'))).toBe(false); + expect( + result.results.every((entry) => entry.itemType === 'user' || entry.itemType === 'ai') + ).toBe(true); + }); + + it('does not produce phantom matches for code fence language identifiers', async () => { + const projectsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'session-searcher-md-')); + tempDirs.push(projectsDir); + + const projectId = 'project-2'; + const sessionId = 'session-2'; + const projectPath = path.join(projectsDir, projectId); + fs.mkdirSync(projectPath, { recursive: true }); + + const sessionPath = path.join(projectPath, `${sessionId}.jsonl`); + const codeBlock = '```tsx\nconst x = 1;\n```'; + const lines = [ + JSON.stringify({ + uuid: 'user-md-1', + type: 'user', + timestamp: '2026-01-01T00:00:00.000Z', + message: { role: 'user', content: 'Show me tsx code' }, + isMeta: false, + }), + JSON.stringify({ + uuid: 'asst-md-1', + type: 'assistant', + timestamp: '2026-01-01T00:00:01.000Z', + message: { + role: 'assistant', + content: [{ type: 'text', text: `Here is a code block:\n\n${codeBlock}` }], + }, + }), + ]; + fs.writeFileSync(sessionPath, `${lines.join('\n')}\n`, 'utf8'); + + const searcher = new SessionSearcher(projectsDir); + const result = await searcher.searchSessions(projectId, 'tsx', 50); + + // "tsx" should match in user text ("Show me tsx code") but NOT in the + // code fence language identifier (```tsx). It should also not match in + // the code block content since "const x = 1;" doesn't contain "tsx". + const userResults = result.results.filter((r) => r.itemType === 'user'); + const aiResults = result.results.filter((r) => r.itemType === 'ai'); + + expect(userResults).toHaveLength(1); + expect(aiResults).toHaveLength(0); + }); +}); diff --git a/test/main/services/infrastructure/FileWatcher.test.ts b/test/main/services/infrastructure/FileWatcher.test.ts new file mode 100644 index 00000000..3be1d487 --- /dev/null +++ b/test/main/services/infrastructure/FileWatcher.test.ts @@ -0,0 +1,571 @@ +import { EventEmitter } from 'events'; +import type * as FsType from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('@shared/utils/logger', () => ({ + createLogger: () => ({ + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }), +})); + +vi.mock('fs', async () => { + const actual = await vi.importActual('fs'); + return { + ...actual, + existsSync: vi.fn(), + watch: vi.fn(), + // Stash the real existsSync so tests can delegate to it for real file I/O + __realExistsSync: actual.existsSync, + }; +}); + +vi.mock('../../../../src/main/services/error/ErrorDetector', () => ({ + errorDetector: { + detectErrors: vi.fn().mockResolvedValue([]), + }, +})); + +vi.mock('../../../../src/main/services/infrastructure/ConfigManager', () => ({ + ConfigManager: { + getInstance: () => ({ + getConfig: () => ({ + notifications: { includeSubagentErrors: true, triggers: [] }, + }), + }), + }, +})); + +vi.mock('../../../../src/main/services/discovery/ProjectPathResolver', () => ({ + projectPathResolver: { + invalidateProject: vi.fn(), + }, +})); + +import * as fs from 'fs'; + +import { errorDetector } from '../../../../src/main/services/error/ErrorDetector'; +import { DataCache } from '../../../../src/main/services/infrastructure/DataCache'; +import { FileWatcher } from '../../../../src/main/services/infrastructure/FileWatcher'; + +function createFakeWatcher(): FsType.FSWatcher { + const emitter = new EventEmitter() as EventEmitter & { close: () => void }; + emitter.close = vi.fn(() => { + emitter.emit('close'); + }); + return emitter as unknown as FsType.FSWatcher; +} + +/** Make existsSync delegate to the real implementation (needed for tests with real temp files) */ +function useRealExistsSync() { + const realFn = (fs as unknown as { __realExistsSync: typeof fs.existsSync }).__realExistsSync; + vi.mocked(fs.existsSync).mockImplementation((p) => realFn(p)); +} + +function createMockNotificationManager() { + return { + addError: vi.fn().mockResolvedValue(null), + } as unknown as Parameters[0]; +} + +/** Helper to write a valid JSONL line */ +function jsonlLine(uuid: string, text: string): string { + return ( + JSON.stringify({ + type: 'assistant', + uuid, + timestamp: '2026-01-01T00:00:00.000Z', + message: { + role: 'assistant', + content: [{ type: 'text', text }], + }, + }) + '\n' + ); +} + +describe('FileWatcher', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + it('retries and starts watchers when directories appear later', () => { + const dataCache = new DataCache(50, 10, false); + let dirsAvailable = false; + + const existsSyncMock = vi.mocked(fs.existsSync); + existsSyncMock.mockImplementation((targetPath) => { + if (targetPath === '/tmp/projects' || targetPath === '/tmp/todos') { + return dirsAvailable; + } + return false; + }); + + const watchMock = vi.mocked(fs.watch); + watchMock.mockImplementation(() => createFakeWatcher()); + + const watcher = new FileWatcher(dataCache, '/tmp/projects', '/tmp/todos'); + watcher.start(); + + expect(watchMock).toHaveBeenCalledTimes(0); + + dirsAvailable = true; + vi.advanceTimersByTime(2000); + + expect(watchMock).toHaveBeenCalledTimes(2); + watcher.stop(); + }); + + it('recovers from watcher errors by re-registering affected watcher', () => { + const dataCache = new DataCache(50, 10, false); + const projectWatcher = createFakeWatcher(); + const todoWatcher = createFakeWatcher(); + const replacementProjectWatcher = createFakeWatcher(); + + const existsSyncMock = vi.mocked(fs.existsSync); + existsSyncMock.mockImplementation((targetPath) => { + return targetPath === '/tmp/projects' || targetPath === '/tmp/todos'; + }); + + const watchMock = vi.mocked(fs.watch); + watchMock + .mockImplementationOnce(() => projectWatcher) + .mockImplementationOnce(() => todoWatcher) + .mockImplementationOnce(() => replacementProjectWatcher); + + const watcher = new FileWatcher(dataCache, '/tmp/projects', '/tmp/todos'); + watcher.start(); + expect(watchMock).toHaveBeenCalledTimes(2); + + (projectWatcher as unknown as EventEmitter).emit('error', new Error('watch failed')); + vi.advanceTimersByTime(2000); + + expect(watchMock).toHaveBeenCalledTimes(3); + watcher.stop(); + }); + + it('keeps append offset pinned for partial trailing lines until completed', async () => { + vi.useRealTimers(); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filewatcher-')); + const filePath = path.join(tempDir, 'session.jsonl'); + const firstLine = jsonlLine('a1', 'hi'); + fs.writeFileSync(filePath, firstLine, 'utf8'); + + const dataCache = new DataCache(50, 10, false); + const watcher = new FileWatcher(dataCache, '/tmp/projects', '/tmp/todos'); + + const firstPass = await ( + watcher as unknown as { + parseAppendedMessages: ( + targetPath: string, + startOffset: number + ) => Promise<{ parsedLineCount: number; consumedBytes: number }>; + } + ).parseAppendedMessages(filePath, 0); + expect(firstPass.parsedLineCount).toBe(1); + expect(firstPass.consumedBytes).toBe(Buffer.byteLength(firstLine, 'utf8')); + + const partialSuffix = + '{"type":"assistant","uuid":"a2","timestamp":"2026-01-01T00:00:01.000Z","message":{"role":"assistant","content":[{"type":"text","text":"partial"'; + fs.appendFileSync(filePath, partialSuffix, 'utf8'); + + const partialPass = await ( + watcher as unknown as { + parseAppendedMessages: ( + targetPath: string, + startOffset: number + ) => Promise<{ parsedLineCount: number; consumedBytes: number }>; + } + ).parseAppendedMessages(filePath, firstPass.consumedBytes); + expect(partialPass.parsedLineCount).toBe(0); + expect(partialPass.consumedBytes).toBe(0); + + const completion = '}]}}\n'; + fs.appendFileSync(filePath, completion, 'utf8'); + + const completedPass = await ( + watcher as unknown as { + parseAppendedMessages: ( + targetPath: string, + startOffset: number + ) => Promise<{ parsedLineCount: number; consumedBytes: number }>; + } + ).parseAppendedMessages(filePath, firstPass.consumedBytes); + expect(completedPass.parsedLineCount).toBe(1); + expect(completedPass.consumedBytes).toBeGreaterThan(0); + + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + // =========================================================================== + // Catch-Up Scan Tests + // =========================================================================== + + describe('catch-up scan', () => { + it('detects file growth missed by fs.watch', async () => { + vi.useRealTimers(); + useRealExistsSync(); + vi.mocked(errorDetector.detectErrors).mockResolvedValue([]); + + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filewatcher-catchup-')); + const projectsDir = path.join(tempDir, 'projects'); + const projectDir = path.join(projectsDir, 'test-project'); + fs.mkdirSync(projectDir, { recursive: true }); + + const filePath = path.join(projectDir, 'session-1.jsonl'); + const line1 = jsonlLine('u1', 'hello'); + fs.writeFileSync(filePath, line1, 'utf8'); + + const dataCache = new DataCache(50, 10, false); + const notificationManager = createMockNotificationManager(); + const watcher = new FileWatcher(dataCache, projectsDir, path.join(tempDir, 'todos')); + watcher.setNotificationManager(notificationManager); + + // Simulate having previously processed the file by directly setting tracking state + const watcherAny = watcher as unknown as { + lastProcessedLineCount: Map; + lastProcessedSize: Map; + activeSessionFiles: Map; + runCatchUpScan: () => Promise; + }; + const initialSize = fs.statSync(filePath).size; + watcherAny.lastProcessedLineCount.set(filePath, 1); + watcherAny.lastProcessedSize.set(filePath, initialSize); + watcherAny.activeSessionFiles.set(filePath, { + projectId: 'test-project', + sessionId: 'session-1', + }); + + // Append new data WITHOUT triggering fs.watch (simulating a missed event) + const line2 = jsonlLine('u2', 'world'); + fs.appendFileSync(filePath, line2, 'utf8'); + + // Run catch-up scan manually + await watcherAny.runCatchUpScan(); + + // The error detector should have been called with the new message + expect(errorDetector.detectErrors).toHaveBeenCalled(); + const calls = vi.mocked(errorDetector.detectErrors).mock.calls; + const lastCall = calls[calls.length - 1]; + expect(lastCall[1]).toBe('session-1'); + expect(lastCall[2]).toBe('test-project'); + + // Verify tracking state was updated + expect(watcherAny.lastProcessedLineCount.get(filePath)).toBe(2); + expect(watcherAny.lastProcessedSize.get(filePath)).toBeGreaterThan(initialSize); + + watcher.stop(); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + it('skips files with no size change', async () => { + vi.useRealTimers(); + useRealExistsSync(); + + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filewatcher-noop-')); + const projectsDir = path.join(tempDir, 'projects'); + const projectDir = path.join(projectsDir, 'test-project'); + fs.mkdirSync(projectDir, { recursive: true }); + + const filePath = path.join(projectDir, 'session-1.jsonl'); + const line1 = jsonlLine('u1', 'hello'); + fs.writeFileSync(filePath, line1, 'utf8'); + + const dataCache = new DataCache(50, 10, false); + const notificationManager = createMockNotificationManager(); + const watcher = new FileWatcher(dataCache, projectsDir, path.join(tempDir, 'todos')); + watcher.setNotificationManager(notificationManager); + + const watcherAny = watcher as unknown as { + lastProcessedLineCount: Map; + lastProcessedSize: Map; + activeSessionFiles: Map; + runCatchUpScan: () => Promise; + }; + const currentSize = fs.statSync(filePath).size; + watcherAny.lastProcessedLineCount.set(filePath, 1); + watcherAny.lastProcessedSize.set(filePath, currentSize); + watcherAny.activeSessionFiles.set(filePath, { + projectId: 'test-project', + sessionId: 'session-1', + }); + + vi.mocked(errorDetector.detectErrors).mockClear(); + + // Run catch-up scan without any file changes + await watcherAny.runCatchUpScan(); + + // Error detector should NOT have been called since file hasn't changed + expect(errorDetector.detectErrors).not.toHaveBeenCalled(); + + watcher.stop(); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + it('removes stale files older than 1 hour from active tracking', async () => { + vi.useRealTimers(); + useRealExistsSync(); + + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filewatcher-stale-')); + const projectsDir = path.join(tempDir, 'projects'); + const projectDir = path.join(projectsDir, 'test-project'); + fs.mkdirSync(projectDir, { recursive: true }); + + const filePath = path.join(projectDir, 'old-session.jsonl'); + fs.writeFileSync(filePath, jsonlLine('u1', 'old'), 'utf8'); + + // Set file mtime to 2 hours ago + const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000); + fs.utimesSync(filePath, twoHoursAgo, twoHoursAgo); + + const dataCache = new DataCache(50, 10, false); + const notificationManager = createMockNotificationManager(); + const watcher = new FileWatcher(dataCache, projectsDir, path.join(tempDir, 'todos')); + watcher.setNotificationManager(notificationManager); + + const watcherAny = watcher as unknown as { + activeSessionFiles: Map; + lastProcessedSize: Map; + runCatchUpScan: () => Promise; + }; + watcherAny.activeSessionFiles.set(filePath, { + projectId: 'test-project', + sessionId: 'old-session', + }); + watcherAny.lastProcessedSize.set(filePath, 0); + + await watcherAny.runCatchUpScan(); + + // Stale file should be removed from active tracking + expect(watcherAny.activeSessionFiles.has(filePath)).toBe(false); + + watcher.stop(); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + it('handles deleted files gracefully during catch-up scan', async () => { + vi.useRealTimers(); + + const dataCache = new DataCache(50, 10, false); + const notificationManager = createMockNotificationManager(); + const watcher = new FileWatcher(dataCache, '/tmp/projects', '/tmp/todos'); + watcher.setNotificationManager(notificationManager); + + const filePath = '/tmp/projects/test-project/nonexistent.jsonl'; + + const watcherAny = watcher as unknown as { + activeSessionFiles: Map; + lastProcessedSize: Map; + lastProcessedLineCount: Map; + runCatchUpScan: () => Promise; + }; + watcherAny.activeSessionFiles.set(filePath, { + projectId: 'test-project', + sessionId: 'nonexistent', + }); + watcherAny.lastProcessedSize.set(filePath, 100); + watcherAny.lastProcessedLineCount.set(filePath, 5); + + // Should not throw + await watcherAny.runCatchUpScan(); + + // Deleted file should be cleaned up + expect(watcherAny.activeSessionFiles.has(filePath)).toBe(false); + expect(watcherAny.lastProcessedSize.has(filePath)).toBe(false); + expect(watcherAny.lastProcessedLineCount.has(filePath)).toBe(false); + + watcher.stop(); + }); + }); + + // =========================================================================== + // Concurrency Guard Tests + // =========================================================================== + + describe('concurrency guard', () => { + it('prevents concurrent processing of the same file', async () => { + vi.useRealTimers(); + useRealExistsSync(); + + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filewatcher-concurrent-')); + const projectsDir = path.join(tempDir, 'projects'); + const projectDir = path.join(projectsDir, 'test-project'); + fs.mkdirSync(projectDir, { recursive: true }); + + const filePath = path.join(projectDir, 'session-1.jsonl'); + fs.writeFileSync(filePath, jsonlLine('u1', 'hello'), 'utf8'); + + const dataCache = new DataCache(50, 10, false); + const notificationManager = createMockNotificationManager(); + const watcher = new FileWatcher(dataCache, projectsDir, path.join(tempDir, 'todos')); + watcher.setNotificationManager(notificationManager); + + // Make detectErrors slow to simulate long processing + let detectResolve: () => void; + const detectPromise = new Promise((resolve) => { + detectResolve = resolve; + }); + vi.mocked(errorDetector.detectErrors).mockImplementation( + () => + new Promise((resolve) => { + detectPromise.then(() => resolve([])); + }) + ); + + const watcherAny = watcher as unknown as { + detectErrorsInSessionFile: ( + projectId: string, + sessionId: string, + filePath: string + ) => Promise; + processingInProgress: Set; + pendingReprocess: Set; + }; + + // Start first call (will block on detectErrors) + const first = watcherAny.detectErrorsInSessionFile('test-project', 'session-1', filePath); + + // Wait a tick so the first call enters the processing block and reaches detectErrors + await new Promise((r) => setTimeout(r, 50)); + + // Verify the file is marked as processing + expect(watcherAny.processingInProgress.has(filePath)).toBe(true); + + // Second call should be deferred (returns immediately) + const second = watcherAny.detectErrorsInSessionFile('test-project', 'session-1', filePath); + await second; + + // Verify pending reprocess was set + expect(watcherAny.pendingReprocess.has(filePath)).toBe(true); + + // Resolve the slow detectErrors + detectResolve!(); + await first; + + // After first completes, pending reprocess triggers a re-run + // Wait for the re-run to complete + await new Promise((r) => setTimeout(r, 100)); + + // pendingReprocess should be cleared after reprocessing + expect(watcherAny.pendingReprocess.has(filePath)).toBe(false); + expect(watcherAny.processingInProgress.has(filePath)).toBe(false); + + watcher.stop(); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + }); + + // =========================================================================== + // Fallback Size Tracking Tests + // =========================================================================== + + describe('lastProcessedSize in fallback path', () => { + it('re-stats file after full parse to capture concurrent writes', async () => { + vi.useRealTimers(); + useRealExistsSync(); + + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filewatcher-size-')); + const projectsDir = path.join(tempDir, 'projects'); + const projectDir = path.join(projectsDir, 'test-project'); + fs.mkdirSync(projectDir, { recursive: true }); + + const filePath = path.join(projectDir, 'session-1.jsonl'); + const line1 = jsonlLine('u1', 'hello'); + fs.writeFileSync(filePath, line1, 'utf8'); + + const dataCache = new DataCache(50, 10, false); + const notificationManager = createMockNotificationManager(); + const watcher = new FileWatcher(dataCache, projectsDir, path.join(tempDir, 'todos')); + watcher.setNotificationManager(notificationManager); + + vi.mocked(errorDetector.detectErrors).mockResolvedValue([]); + + const watcherAny = watcher as unknown as { + detectErrorsInSessionFile: ( + projectId: string, + sessionId: string, + filePath: string + ) => Promise; + lastProcessedSize: Map; + lastProcessedLineCount: Map; + }; + + // First call - fallback path (no lastProcessedLineCount) + await watcherAny.detectErrorsInSessionFile('test-project', 'session-1', filePath); + + // The lastProcessedSize should match the actual file size on disk + const actualSize = fs.statSync(filePath).size; + expect(watcherAny.lastProcessedSize.get(filePath)).toBe(actualSize); + expect(watcherAny.lastProcessedLineCount.get(filePath)).toBe(1); + + watcher.stop(); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + }); + + // =========================================================================== + // Timer Lifecycle Tests + // =========================================================================== + + describe('timer lifecycle', () => { + it('starts catch-up timer on start() and clears on stop()', () => { + const dataCache = new DataCache(50, 10, false); + + vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(fs.watch).mockImplementation(() => createFakeWatcher()); + + const watcher = new FileWatcher(dataCache, '/tmp/projects', '/tmp/todos'); + + const watcherAny = watcher as unknown as { + catchUpTimer: NodeJS.Timeout | null; + }; + + expect(watcherAny.catchUpTimer).toBeNull(); + + watcher.start(); + expect(watcherAny.catchUpTimer).not.toBeNull(); + + watcher.stop(); + expect(watcherAny.catchUpTimer).toBeNull(); + }); + + it('clears all tracking state on stop()', () => { + const dataCache = new DataCache(50, 10, false); + + vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(fs.watch).mockImplementation(() => createFakeWatcher()); + + const watcher = new FileWatcher(dataCache, '/tmp/projects', '/tmp/todos'); + + const watcherAny = watcher as unknown as { + activeSessionFiles: Map; + processingInProgress: Set; + pendingReprocess: Set; + }; + + watcher.start(); + + // Add some tracking state + watcherAny.activeSessionFiles.set('/tmp/file.jsonl', { + projectId: 'p', + sessionId: 's', + }); + watcherAny.processingInProgress.add('/tmp/file.jsonl'); + watcherAny.pendingReprocess.add('/tmp/file.jsonl'); + + watcher.stop(); + + expect(watcherAny.activeSessionFiles.size).toBe(0); + expect(watcherAny.processingInProgress.size).toBe(0); + expect(watcherAny.pendingReprocess.size).toBe(0); + }); + }); +}); diff --git a/test/main/services/parsing/MessageClassifier.test.ts b/test/main/services/parsing/MessageClassifier.test.ts new file mode 100644 index 00000000..fe071de6 --- /dev/null +++ b/test/main/services/parsing/MessageClassifier.test.ts @@ -0,0 +1,303 @@ +/** + * Tests for MessageClassifier service. + * + * Tests the 5-category message classification: + * - user: Real user input (creates UserChunk) + * - system: Command output (creates SystemChunk) + * - compact: Summary messages from conversation compaction + * - hardNoise: Filtered out (system metadata, caveats, reminders) + * - ai: All other messages (creates AIChunk) + */ + +import { describe, expect, it } from 'vitest'; + +import { classifyMessages } from '../../../../src/main/services/parsing/MessageClassifier'; +import type { ParsedMessage } from '../../../../src/main/types'; + +// ============================================================================= +// Test Helpers +// ============================================================================= + +/** + * Creates a minimal ParsedMessage for testing. + */ +function createMessage(overrides: Partial): ParsedMessage { + return { + uuid: 'test-uuid', + parentUuid: null, + type: 'user', + timestamp: new Date(), + content: '', + isSidechain: false, + isMeta: false, + toolCalls: [], + toolResults: [], + ...overrides, + }; +} + +// ============================================================================= +// Tests +// ============================================================================= + +describe('MessageClassifier', () => { + describe('classifyMessages', () => { + it('should return empty array for empty input', () => { + const result = classifyMessages([]); + expect(result).toEqual([]); + }); + + it('should classify all messages', () => { + const messages = [ + createMessage({ type: 'user', content: 'Hello', isMeta: false }), + createMessage({ type: 'assistant', content: 'Hi there!' }), + ]; + const result = classifyMessages(messages); + expect(result).toHaveLength(2); + expect(result[0].message).toBe(messages[0]); + expect(result[1].message).toBe(messages[1]); + }); + }); + + describe('user category', () => { + it('should classify real user message with string content', () => { + const message = createMessage({ + type: 'user', + content: 'Help me debug this code', + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('user'); + }); + + it('should classify real user message with array content (text block)', () => { + const message = createMessage({ + type: 'user', + content: [{ type: 'text', text: 'Help me debug this code' }], + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('user'); + }); + + it('should classify user message with image as user', () => { + const message = createMessage({ + type: 'user', + content: [ + { type: 'text', text: 'What is in this image?' }, + { type: 'image', source: { type: 'base64', media_type: 'image/png', data: 'abc' } }, + ], + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('user'); + }); + + it('should classify slash command as user input', () => { + const message = createMessage({ + type: 'user', + content: '/model Switch to sonnet', + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('user'); + }); + }); + + describe('system category', () => { + it('should classify local-command-stdout as system', () => { + const message = createMessage({ + type: 'user', + content: + 'Set model to claude-sonnet-4-20250514', + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('system'); + }); + + it('should classify local-command-stderr as system', () => { + const message = createMessage({ + type: 'user', + content: 'Error: command failed', + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('system'); + }); + + it('should classify array content with stdout as system', () => { + const message = createMessage({ + type: 'user', + content: [{ type: 'text', text: 'output' }], + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('system'); + }); + }); + + describe('compact category', () => { + it('should classify compact summary message', () => { + const message = createMessage({ + type: 'user', + content: 'Summary of previous conversation...', + isCompactSummary: true, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('compact'); + }); + }); + + describe('hardNoise category', () => { + it('should classify system type as hardNoise', () => { + const message = createMessage({ + type: 'system', + content: 'System prompt', + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + + it('should classify summary type as hardNoise', () => { + const message = createMessage({ + type: 'summary' as ParsedMessage['type'], + content: 'Summary', + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + + it('should classify synthetic assistant message as hardNoise', () => { + const message = createMessage({ + type: 'assistant', + content: '', + model: '', + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + + it('should classify local-command-caveat as hardNoise', () => { + const message = createMessage({ + type: 'user', + content: 'This is a caveat', + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + + it('should classify system-reminder as hardNoise', () => { + const message = createMessage({ + type: 'user', + content: 'Remember to do X', + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + + it('should classify empty stdout as hardNoise', () => { + const message = createMessage({ + type: 'user', + content: '', + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + + it('should classify file-history-snapshot as hardNoise', () => { + const message = createMessage({ + type: 'file-history-snapshot' as ParsedMessage['type'], + content: '', + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + }); + + describe('ai category', () => { + it('should classify assistant message as ai', () => { + const message = createMessage({ + type: 'assistant', + content: [{ type: 'text', text: "Here's how to fix your code..." }], + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('ai'); + }); + + it('should classify assistant message with tool use as ai', () => { + const message = createMessage({ + type: 'assistant', + content: [ + { type: 'text', text: 'Let me read that file' }, + { type: 'tool_use', id: 'tool-1', name: 'Read', input: { file_path: '/test.ts' } }, + ], + toolCalls: [ + { id: 'tool-1', name: 'Read', input: { file_path: '/test.ts' }, isTask: false }, + ], + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('ai'); + }); + + it('should classify internal user message (tool result) as ai', () => { + const message = createMessage({ + type: 'user', + content: [{ type: 'tool_result', tool_use_id: 'tool-1', content: 'file contents' }], + isMeta: true, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('ai'); + }); + + it('should classify user interruption message as hardNoise', () => { + const message = createMessage({ + type: 'user', + content: [{ type: 'text', text: '[Request interrupted by user]' }], + isMeta: false, + }); + const [result] = classifyMessages([message]); + expect(result.category).toBe('hardNoise'); + }); + }); + + describe('mixed message sequence', () => { + it('should correctly classify a typical conversation flow', () => { + const messages = [ + createMessage({ + type: 'user', + content: 'Fix the bug in app.ts', + isMeta: false, + }), + createMessage({ + type: 'assistant', + content: [ + { type: 'text', text: 'Let me read the file' }, + { type: 'tool_use', id: 't1', name: 'Read', input: { file_path: 'app.ts' } }, + ], + }), + createMessage({ + type: 'user', + content: [{ type: 'tool_result', tool_use_id: 't1', content: 'const x = 1;' }], + isMeta: true, + }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'I found the issue. Let me fix it.' }], + }), + createMessage({ + type: 'system', + content: 'System message', + }), + ]; + + const results = classifyMessages(messages); + + expect(results[0].category).toBe('user'); // User input + expect(results[1].category).toBe('ai'); // Assistant with tool use + expect(results[2].category).toBe('ai'); // Tool result (internal user) + expect(results[3].category).toBe('ai'); // Assistant response + expect(results[4].category).toBe('hardNoise'); // System message + }); + }); +}); diff --git a/test/main/services/parsing/SessionParser.test.ts b/test/main/services/parsing/SessionParser.test.ts new file mode 100644 index 00000000..c80ec8dd --- /dev/null +++ b/test/main/services/parsing/SessionParser.test.ts @@ -0,0 +1,415 @@ +/** + * Tests for SessionParser service. + * + * Tests parsing functionality: + * - Message type grouping + * - Sidechain vs main thread separation + * - Task call extraction + * - Tool result linking + * - Time range calculation + */ + +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { + SessionParser, + type ParsedSession, +} from '../../../../src/main/services/parsing/SessionParser'; +import type { ParsedMessage } from '../../../../src/main/types'; + +// ============================================================================= +// Mock ProjectScanner +// ============================================================================= + +const mockProjectScanner = { + scan: vi.fn(), + getSessionPath: vi.fn(), + listSessionsPaginated: vi.fn(), + listSessions: vi.fn(), + listSubagentFiles: vi.fn(), + getSession: vi.fn(), + listWorktreeSessions: vi.fn(), + scanWithWorktreeGrouping: vi.fn(), +}; + +// ============================================================================= +// Test Helpers +// ============================================================================= + +/** + * Creates a minimal ParsedMessage for testing. + */ +function createMessage(overrides: Partial): ParsedMessage { + return { + uuid: `msg-${Math.random().toString(36).slice(2, 11)}`, + parentUuid: null, + type: 'user', + timestamp: new Date(), + content: '', + isSidechain: false, + isMeta: false, + toolCalls: [], + toolResults: [], + ...overrides, + }; +} + +// ============================================================================= +// Tests +// ============================================================================= + +describe('SessionParser', () => { + let parser: SessionParser; + + beforeEach(() => { + vi.clearAllMocks(); + // @ts-expect-error - Using partial mock + parser = new SessionParser(mockProjectScanner); + }); + + describe('processMessages (via public methods)', () => { + // Since processMessages is private, we test its behavior through the query methods + + describe('message type grouping', () => { + it('should group user messages correctly', () => { + const messages = [ + createMessage({ type: 'user', content: 'User message 1' }), + createMessage({ type: 'assistant', content: [{ type: 'text', text: 'Response' }] }), + createMessage({ type: 'user', content: 'User message 2' }), + ]; + + // Access processMessages result through getUserMessages + const processedResult = { + messages, + metrics: { + durationMs: 0, + totalTokens: 0, + inputTokens: 0, + outputTokens: 0, + cacheReadTokens: 0, + cacheCreationTokens: 0, + messageCount: messages.length, + }, + taskCalls: [], + byType: { + user: messages.filter((m) => m.type === 'user'), + realUser: messages.filter((m) => m.type === 'user' && !m.isMeta), + internalUser: messages.filter((m) => m.type === 'user' && m.isMeta), + assistant: messages.filter((m) => m.type === 'assistant'), + system: [], + other: [], + }, + sidechainMessages: [], + mainMessages: messages, + }; + + const userMessages = parser.getUserMessages(processedResult); + expect(userMessages).toHaveLength(2); + }); + + it('should separate real user vs internal user messages', () => { + const messages = [ + createMessage({ type: 'user', content: 'Real user input', isMeta: false }), + createMessage({ + type: 'user', + content: [{ type: 'tool_result', tool_use_id: 't1', content: 'result' }], + isMeta: true, + }), + ]; + + const processedResult: ParsedSession = { + messages, + metrics: { + durationMs: 0, + totalTokens: 0, + inputTokens: 0, + outputTokens: 0, + cacheReadTokens: 0, + cacheCreationTokens: 0, + messageCount: messages.length, + }, + taskCalls: [], + byType: { + user: messages.filter((m) => m.type === 'user'), + realUser: messages.filter((m) => m.type === 'user' && !m.isMeta), + internalUser: messages.filter((m) => m.type === 'user' && m.isMeta), + assistant: [], + system: [], + other: [], + }, + sidechainMessages: [], + mainMessages: messages, + }; + + expect(processedResult.byType.realUser).toHaveLength(1); + expect(processedResult.byType.internalUser).toHaveLength(1); + }); + }); + + describe('sidechain separation', () => { + it('should separate sidechain from main thread messages', () => { + const messages = [ + createMessage({ type: 'user', content: 'Main', isSidechain: false }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Sidechain' }], + isSidechain: true, + }), + createMessage({ + type: 'assistant', + content: [{ type: 'text', text: 'Main' }], + isSidechain: false, + }), + ]; + + const sidechainMessages = messages.filter((m) => m.isSidechain); + const mainMessages = messages.filter((m) => !m.isSidechain); + + expect(sidechainMessages).toHaveLength(1); + expect(mainMessages).toHaveLength(2); + }); + }); + }); + + describe('getResponses', () => { + it('should get assistant responses after user message', () => { + const userMsgUuid = 'user-1'; + const messages = [ + createMessage({ uuid: userMsgUuid, type: 'user', content: 'Question' }), + createMessage({ + uuid: 'asst-1', + type: 'assistant', + content: [{ type: 'text', text: 'Answer 1' }], + }), + createMessage({ + uuid: 'asst-2', + type: 'assistant', + content: [{ type: 'text', text: 'Answer 2' }], + }), + createMessage({ uuid: 'user-2', type: 'user', content: 'Next question' }), + ]; + + const responses = parser.getResponses(messages, userMsgUuid); + expect(responses).toHaveLength(2); + expect(responses[0].uuid).toBe('asst-1'); + expect(responses[1].uuid).toBe('asst-2'); + }); + + it('should stop at next user message', () => { + const userMsgUuid = 'user-1'; + const messages = [ + createMessage({ uuid: userMsgUuid, type: 'user', content: 'Q1' }), + createMessage({ + uuid: 'asst-1', + type: 'assistant', + content: [{ type: 'text', text: 'A1' }], + }), + createMessage({ uuid: 'user-2', type: 'user', content: 'Q2' }), + createMessage({ + uuid: 'asst-2', + type: 'assistant', + content: [{ type: 'text', text: 'A2' }], + }), + ]; + + const responses = parser.getResponses(messages, userMsgUuid); + expect(responses).toHaveLength(1); + expect(responses[0].uuid).toBe('asst-1'); + }); + + it('should return empty for non-existent message', () => { + const messages = [createMessage({ uuid: 'user-1', type: 'user', content: 'Q' })]; + + const responses = parser.getResponses(messages, 'non-existent'); + expect(responses).toEqual([]); + }); + }); + + describe('getTaskCalls', () => { + it('should extract Task tool calls from messages', () => { + const messages = [ + createMessage({ + type: 'assistant', + content: [ + { type: 'text', text: 'Spawning agent' }, + { + type: 'tool_use', + id: 'task-1', + name: 'Task', + input: { prompt: 'Do something', subagent_type: 'explore' }, + }, + ], + toolCalls: [ + { + id: 'task-1', + name: 'Task', + input: { prompt: 'Do something', subagent_type: 'explore' }, + isTask: true, + taskDescription: 'Do something', + taskSubagentType: 'explore', + }, + ], + }), + createMessage({ + type: 'assistant', + content: [ + { type: 'tool_use', id: 'read-1', name: 'Read', input: { file_path: 'test.ts' } }, + ], + toolCalls: [ + { id: 'read-1', name: 'Read', input: { file_path: 'test.ts' }, isTask: false }, + ], + }), + ]; + + const taskCalls = parser.getTaskCalls(messages); + expect(taskCalls).toHaveLength(1); + expect(taskCalls[0].name).toBe('Task'); + expect(taskCalls[0].isTask).toBe(true); + }); + }); + + describe('getToolCallsByName', () => { + it('should get tool calls by name', () => { + const messages = [ + createMessage({ + type: 'assistant', + toolCalls: [ + { id: 'read-1', name: 'Read', input: { file_path: 'a.ts' }, isTask: false }, + { + id: 'write-1', + name: 'Write', + input: { file_path: 'b.ts', content: '' }, + isTask: false, + }, + { id: 'read-2', name: 'Read', input: { file_path: 'c.ts' }, isTask: false }, + ], + }), + ]; + + const readCalls = parser.getToolCallsByName(messages, 'Read'); + expect(readCalls).toHaveLength(2); + expect(readCalls[0].id).toBe('read-1'); + expect(readCalls[1].id).toBe('read-2'); + }); + }); + + describe('findToolResult', () => { + it('should find tool result by tool call ID', () => { + const toolCallId = 'tool-1'; + const messages = [ + createMessage({ + type: 'user', + isMeta: true, + toolResults: [{ toolUseId: toolCallId, content: 'result content', isError: false }], + }), + ]; + + const found = parser.findToolResult(messages, toolCallId); + expect(found).not.toBeNull(); + expect(found?.result.toolUseId).toBe(toolCallId); + expect(found?.result.content).toBe('result content'); + }); + + it('should return null for non-existent tool call', () => { + const messages = [ + createMessage({ + type: 'user', + isMeta: true, + toolResults: [{ toolUseId: 'other-id', content: '', isError: false }], + }), + ]; + + const found = parser.findToolResult(messages, 'non-existent'); + expect(found).toBeNull(); + }); + }); + + describe('getTimeRange', () => { + it('should calculate time range correctly', () => { + const start = new Date('2024-01-01T10:00:00Z'); + const end = new Date('2024-01-01T10:05:00Z'); + const messages = [ + createMessage({ timestamp: start }), + createMessage({ timestamp: new Date('2024-01-01T10:02:00Z') }), + createMessage({ timestamp: end }), + ]; + + const range = parser.getTimeRange(messages); + expect(range.start.getTime()).toBe(start.getTime()); + expect(range.end.getTime()).toBe(end.getTime()); + expect(range.durationMs).toBe(5 * 60 * 1000); // 5 minutes + }); + + it('should handle empty messages', () => { + const range = parser.getTimeRange([]); + expect(range.durationMs).toBe(0); + }); + + it('should handle single message', () => { + const timestamp = new Date('2024-01-01T10:00:00Z'); + const messages = [createMessage({ timestamp })]; + + const range = parser.getTimeRange(messages); + expect(range.start.getTime()).toBe(timestamp.getTime()); + expect(range.end.getTime()).toBe(timestamp.getTime()); + expect(range.durationMs).toBe(0); + }); + }); + + describe('buildMessageTree', () => { + it('should build parent-child tree', () => { + const messages = [ + createMessage({ uuid: 'root', parentUuid: null }), + createMessage({ uuid: 'child1', parentUuid: 'root' }), + createMessage({ uuid: 'child2', parentUuid: 'root' }), + createMessage({ uuid: 'grandchild', parentUuid: 'child1' }), + ]; + + const tree = parser.buildMessageTree(messages); + + expect(tree.get('root')?.map((m) => m.uuid)).toContain('child1'); + expect(tree.get('root')?.map((m) => m.uuid)).toContain('child2'); + expect(tree.get('child1')?.map((m) => m.uuid)).toContain('grandchild'); + }); + }); + + describe('getChildMessages', () => { + it('should get direct children', () => { + const messages = [ + createMessage({ uuid: 'parent', parentUuid: null }), + createMessage({ uuid: 'child1', parentUuid: 'parent' }), + createMessage({ uuid: 'child2', parentUuid: 'parent' }), + createMessage({ uuid: 'other', parentUuid: 'other-parent' }), + ]; + + const children = parser.getChildMessages(messages, 'parent'); + expect(children).toHaveLength(2); + expect(children.map((m) => m.uuid)).toContain('child1'); + expect(children.map((m) => m.uuid)).toContain('child2'); + }); + }); + + describe('extractText', () => { + it('should extract text from string content', () => { + const message = createMessage({ content: 'Hello world' }); + expect(parser.extractText(message)).toBe('Hello world'); + }); + }); + + describe('getMessagePreview', () => { + it('should truncate long messages', () => { + const longText = 'A'.repeat(200); + const message = createMessage({ content: longText }); + + const preview = parser.getMessagePreview(message, 50); + expect(preview.length).toBe(53); // 50 chars + '...' + expect(preview.endsWith('...')).toBe(true); + }); + + it('should not truncate short messages', () => { + const message = createMessage({ content: 'Short' }); + const preview = parser.getMessagePreview(message, 50); + expect(preview).toBe('Short'); + }); + }); +}); diff --git a/test/main/utils/jsonl.test.ts b/test/main/utils/jsonl.test.ts new file mode 100644 index 00000000..55b35f5e --- /dev/null +++ b/test/main/utils/jsonl.test.ts @@ -0,0 +1,175 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { describe, expect, it } from 'vitest'; + +import { analyzeSessionFileMetadata, calculateMetrics } from '../../../src/main/utils/jsonl'; +import type { ParsedMessage } from '../../../src/main/types'; + +// Helper to create a minimal ParsedMessage +function createMessage(overrides: Partial = {}): ParsedMessage { + return { + uuid: 'test-uuid', + parentUuid: null, + type: 'assistant', + timestamp: new Date('2024-01-01T10:00:00Z'), + content: '', + isSidechain: false, + isMeta: false, + isCompactSummary: false, + toolCalls: [], + toolResults: [], + ...overrides, + }; +} + +describe('jsonl', () => { + describe('calculateMetrics', () => { + it('should return empty metrics for empty messages array', () => { + const result = calculateMetrics([]); + expect(result.durationMs).toBe(0); + expect(result.totalTokens).toBe(0); + expect(result.inputTokens).toBe(0); + expect(result.outputTokens).toBe(0); + expect(result.messageCount).toBe(0); + }); + + it('should calculate total tokens from usage', () => { + const messages = [ + createMessage({ + usage: { + input_tokens: 100, + output_tokens: 50, + }, + }), + ]; + + const result = calculateMetrics(messages); + expect(result.inputTokens).toBe(100); + expect(result.outputTokens).toBe(50); + expect(result.totalTokens).toBe(150); + }); + + it('should sum tokens across multiple messages', () => { + const messages = [ + createMessage({ + usage: { input_tokens: 100, output_tokens: 50 }, + }), + createMessage({ + usage: { input_tokens: 200, output_tokens: 100 }, + }), + ]; + + const result = calculateMetrics(messages); + expect(result.inputTokens).toBe(300); + expect(result.outputTokens).toBe(150); + expect(result.totalTokens).toBe(450); + }); + + it('should handle cache tokens', () => { + const messages = [ + createMessage({ + usage: { + input_tokens: 100, + output_tokens: 50, + cache_read_input_tokens: 25, + cache_creation_input_tokens: 10, + }, + }), + ]; + + const result = calculateMetrics(messages); + expect(result.cacheReadTokens).toBe(25); + expect(result.cacheCreationTokens).toBe(10); + expect(result.totalTokens).toBe(185); // 100 + 50 + 25 + 10 + }); + + it('should calculate duration from timestamps', () => { + const messages = [ + createMessage({ timestamp: new Date('2024-01-01T10:00:00Z') }), + createMessage({ timestamp: new Date('2024-01-01T10:01:00Z') }), + createMessage({ timestamp: new Date('2024-01-01T10:02:00Z') }), + ]; + + const result = calculateMetrics(messages); + expect(result.durationMs).toBe(120000); // 2 minutes in ms + }); + + it('should count messages', () => { + const messages = [createMessage(), createMessage(), createMessage()]; + + const result = calculateMetrics(messages); + expect(result.messageCount).toBe(3); + }); + + it('should handle messages without usage', () => { + const messages = [ + createMessage({ type: 'user', content: 'Hello' }), + createMessage({ type: 'system' }), + ]; + + const result = calculateMetrics(messages); + expect(result.totalTokens).toBe(0); + expect(result.messageCount).toBe(2); + }); + + it('should handle single message duration', () => { + const messages = [createMessage({ timestamp: new Date('2024-01-01T10:00:00Z') })]; + + const result = calculateMetrics(messages); + expect(result.durationMs).toBe(0); // min === max + }); + + it('should handle undefined token values', () => { + const messages = [ + createMessage({ + usage: { + input_tokens: undefined as unknown as number, + output_tokens: 50, + }, + }), + ]; + + const result = calculateMetrics(messages); + expect(result.inputTokens).toBe(0); + expect(result.outputTokens).toBe(50); + }); + }); + + describe('analyzeSessionFileMetadata', () => { + it('should extract first message, count, ongoing state, and git branch in one pass', async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsonl-meta-')); + const filePath = path.join(tempDir, 'session.jsonl'); + const lines = [ + JSON.stringify({ + type: 'user', + uuid: 'u1', + timestamp: '2026-01-01T00:00:00.000Z', + gitBranch: 'feature/test', + message: { role: 'user', content: 'hello world' }, + isMeta: false, + }), + JSON.stringify({ + type: 'assistant', + uuid: 'a1', + timestamp: '2026-01-01T00:00:01.000Z', + message: { + role: 'assistant', + content: [{ type: 'thinking', thinking: 'thinking...' }], + }, + }), + ]; + fs.writeFileSync(filePath, `${lines.join('\n')}\n`, 'utf8'); + + const result = await analyzeSessionFileMetadata(filePath); + + expect(result.firstUserMessage?.text).toBe('hello world'); + expect(result.firstUserMessage?.timestamp).toBe('2026-01-01T00:00:00.000Z'); + expect(result.messageCount).toBe(1); + expect(result.isOngoing).toBe(true); + expect(result.gitBranch).toBe('feature/test'); + + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + }); +}); diff --git a/test/main/utils/pathDecoder.test.ts b/test/main/utils/pathDecoder.test.ts new file mode 100644 index 00000000..d3e4d178 --- /dev/null +++ b/test/main/utils/pathDecoder.test.ts @@ -0,0 +1,201 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildSessionPath, + buildSubagentsPath, + buildTodoPath, + decodePath, + encodePath, + extractProjectName, + extractSessionId, + getProjectsBasePath, + getTodosBasePath, + isValidEncodedPath, +} from '../../../src/main/utils/pathDecoder'; + +describe('pathDecoder', () => { + describe('encodePath', () => { + it('should encode a macOS-style absolute path', () => { + expect(encodePath('/Users/username/projectname')).toBe('-Users-username-projectname'); + }); + + it('should encode a Windows-style absolute path', () => { + expect(encodePath('C:\\Users\\username\\projectname')).toBe('-C:-Users-username-projectname'); + }); + + it('should handle empty string', () => { + expect(encodePath('')).toBe(''); + }); + + it('should round-trip with decodePath for POSIX paths', () => { + const original = '/Users/username/projectname'; + expect(decodePath(encodePath(original))).toBe(original); + }); + + it('should round-trip with decodePath for Windows paths', () => { + const original = 'C:/Users/username/projectname'; + expect(decodePath(encodePath(original))).toBe(original); + }); + + it('should encode a Linux-style path', () => { + expect(encodePath('/home/user/projects/myapp')).toBe('-home-user-projects-myapp'); + }); + }); + + describe('decodePath', () => { + it('should decode a simple encoded path', () => { + expect(decodePath('-Users-username-projectname')).toBe('/Users/username/projectname'); + }); + + it('should handle empty string', () => { + expect(decodePath('')).toBe(''); + }); + + it('should ensure leading slash for absolute paths', () => { + expect(decodePath('Users-username-projectname')).toBe('/Users/username/projectname'); + }); + + it('should decode path with multiple segments', () => { + expect(decodePath('-home-user-projects-myapp-src')).toBe('/home/user/projects/myapp/src'); + }); + + it('should handle single segment path', () => { + expect(decodePath('-project')).toBe('/project'); + }); + + it('should handle path with underscores', () => { + expect(decodePath('-Users-username-my_projectname')).toBe('/Users/username/my_projectname'); + }); + + it('should handle path with dots', () => { + expect(decodePath('-Users-username-.config')).toBe('/Users/username/.config'); + }); + + it('should decode Windows-style encoded path without adding leading slash', () => { + expect(decodePath('-C:-Users-username-projectname')).toBe('C:/Users/username/projectname'); + }); + }); + + describe('extractProjectName', () => { + it('should extract project name from encoded path', () => { + expect(extractProjectName('-Users-username-projectname')).toBe('projectname'); + }); + + it('should handle deeply nested paths', () => { + expect(extractProjectName('-home-user-dev-projects-appname')).toBe('appname'); + }); + + it('should return encoded name if decoding fails', () => { + expect(extractProjectName('')).toBe(''); + }); + + it('should handle single segment', () => { + expect(extractProjectName('-projectname')).toBe('projectname'); + }); + + it('should handle path with underscore in project name', () => { + expect(extractProjectName('-Users-username-my_cool_projectname')).toBe('my_cool_projectname'); + }); + }); + + describe('isValidEncodedPath', () => { + it('should return true for valid encoded path', () => { + expect(isValidEncodedPath('-Users-username-projectname')).toBe(true); + }); + + it('should return false for empty string', () => { + expect(isValidEncodedPath('')).toBe(false); + }); + + it('should return false for path without leading dash', () => { + expect(isValidEncodedPath('Users-username-projectname')).toBe(false); + }); + + it('should return true for path with underscores', () => { + expect(isValidEncodedPath('-Users-username-my_projectname')).toBe(true); + }); + + it('should return true for path with dots', () => { + expect(isValidEncodedPath('-Users-username-.config')).toBe(true); + }); + + it('should return true for path with numbers', () => { + expect(isValidEncodedPath('-Users-username-projectname123')).toBe(true); + }); + + it('should return true for path with spaces', () => { + expect(isValidEncodedPath('-Users-username-My Projectname')).toBe(true); + }); + + it('should return true for valid Windows-style encoded path', () => { + expect(isValidEncodedPath('-C:-Users-username-projectname')).toBe(true); + }); + + it('should return false for misplaced colons', () => { + expect(isValidEncodedPath('-Users-username:project')).toBe(false); + expect(isValidEncodedPath('-C:-Users-name-project:extra')).toBe(false); + }); + }); + + describe('extractSessionId', () => { + it('should extract session ID from JSONL filename', () => { + expect(extractSessionId('abc123.jsonl')).toBe('abc123'); + }); + + it('should handle UUID-style session IDs', () => { + expect(extractSessionId('550e8400-e29b-41d4-a716-446655440000.jsonl')).toBe( + '550e8400-e29b-41d4-a716-446655440000' + ); + }); + + it('should handle filename without extension', () => { + expect(extractSessionId('session123')).toBe('session123'); + }); + + it('should handle empty string', () => { + expect(extractSessionId('')).toBe(''); + }); + }); + + describe('buildSessionPath', () => { + it('should construct correct session path', () => { + expect(buildSessionPath('/base', 'project-id', 'session-123')).toBe( + '/base/project-id/session-123.jsonl' + ); + }); + + it('should handle paths with special characters', () => { + expect(buildSessionPath('/home/user/.claude/projects', '-Users-name', 'abc123')).toBe( + '/home/user/.claude/projects/-Users-name/abc123.jsonl' + ); + }); + }); + + describe('buildSubagentsPath', () => { + it('should construct correct subagents path', () => { + expect(buildSubagentsPath('/base', 'project-id', 'session-123')).toBe( + '/base/project-id/session-123/subagents' + ); + }); + }); + + describe('buildTodoPath', () => { + it('should construct correct todo path', () => { + expect(buildTodoPath('/home/user/.claude', 'session-123')).toBe( + '/home/user/.claude/todos/session-123.json' + ); + }); + }); + + describe('getProjectsBasePath', () => { + it('should return projects base path', () => { + expect(getProjectsBasePath()).toBe('/home/testuser/.claude/projects'); + }); + }); + + describe('getTodosBasePath', () => { + it('should return todos base path', () => { + expect(getTodosBasePath()).toBe('/home/testuser/.claude/todos'); + }); + }); +}); diff --git a/test/main/utils/pathValidation.test.ts b/test/main/utils/pathValidation.test.ts new file mode 100644 index 00000000..bb3e1ef9 --- /dev/null +++ b/test/main/utils/pathValidation.test.ts @@ -0,0 +1,292 @@ +/** + * Tests for path validation utilities. + */ + +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { describe, expect, it } from 'vitest'; + +import { + isPathWithinAllowedDirectories, + validateFilePath, + validateOpenPath, +} from '../../../src/main/utils/pathValidation'; + +describe('pathValidation', () => { + const homeDir = os.homedir(); + const claudeDir = path.join(homeDir, '.claude'); + const testProjectPath = '/home/user/my-project'; + + describe('isPathWithinAllowedDirectories', () => { + it('should allow paths within ~/.claude', () => { + expect( + isPathWithinAllowedDirectories(path.join(claudeDir, 'projects', 'test.jsonl'), null) + ).toBe(true); + }); + + it('should allow paths within project directory', () => { + expect( + isPathWithinAllowedDirectories( + path.join(testProjectPath, 'src', 'index.ts'), + testProjectPath + ) + ).toBe(true); + }); + + it('should reject paths outside allowed directories', () => { + expect(isPathWithinAllowedDirectories('/etc/passwd', testProjectPath)).toBe(false); + }); + + it('should reject home directory itself without project context', () => { + expect(isPathWithinAllowedDirectories(homeDir, null)).toBe(false); + }); + + it('should allow exact ~/.claude path', () => { + expect(isPathWithinAllowedDirectories(claudeDir, null)).toBe(true); + }); + + it('should allow exact project path', () => { + expect(isPathWithinAllowedDirectories(testProjectPath, testProjectPath)).toBe(true); + }); + }); + + describe('validateFilePath', () => { + describe('basic validation', () => { + it('should reject empty path', () => { + const result = validateFilePath('', testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Invalid file path'); + }); + + it('should reject relative paths', () => { + const result = validateFilePath('src/index.ts', testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Path must be absolute'); + }); + + it('should accept valid absolute paths within project', () => { + const result = validateFilePath( + path.join(testProjectPath, 'src', 'index.ts'), + testProjectPath + ); + expect(result.valid).toBe(true); + expect(result.normalizedPath).toBeDefined(); + }); + }); + + describe('sensitive file patterns', () => { + it('should reject ~/.ssh paths', () => { + const result = validateFilePath(path.join(homeDir, '.ssh', 'id_rsa'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject ~/.aws paths', () => { + const result = validateFilePath(path.join(homeDir, '.aws', 'credentials'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject .env files in project', () => { + const result = validateFilePath(path.join(testProjectPath, '.env'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject .env.local files', () => { + const result = validateFilePath(path.join(testProjectPath, '.env.local'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject credentials.json files', () => { + const result = validateFilePath( + path.join(testProjectPath, 'credentials.json'), + testProjectPath + ); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject .pem files', () => { + const result = validateFilePath(path.join(testProjectPath, 'server.pem'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject .key files', () => { + const result = validateFilePath(path.join(testProjectPath, 'private.key'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject ~/.kube/config', () => { + const result = validateFilePath(path.join(homeDir, '.kube', 'config'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject ~/.docker/config.json', () => { + const result = validateFilePath( + path.join(homeDir, '.docker', 'config.json'), + testProjectPath + ); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject secrets.json files', () => { + const result = validateFilePath( + path.join(testProjectPath, 'config', 'secrets.json'), + testProjectPath + ); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + }); + + describe('path traversal prevention', () => { + it('should handle normalized paths with ..', () => { + // This path resolves correctly but starts outside project + const result = validateFilePath( + '/home/user/my-project/../other-project/file.ts', + testProjectPath + ); + // Should be rejected because final path is outside project + expect(result.valid).toBe(false); + }); + + it('should reject symlink targets that escape project directory', () => { + if (process.platform === 'win32') { + // Symlink creation may require elevated privileges on Windows CI. + return; + } + + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'path-validation-')); + const projectRoot = path.join(tempRoot, 'project'); + const outsideRoot = path.join(tempRoot, 'outside'); + fs.mkdirSync(projectRoot, { recursive: true }); + fs.mkdirSync(outsideRoot, { recursive: true }); + + const outsideFile = path.join(outsideRoot, 'secret.txt'); + fs.writeFileSync(outsideFile, 'secret', 'utf8'); + + const linkedPath = path.join(projectRoot, 'linked-secret.txt'); + fs.symlinkSync(outsideFile, linkedPath); + + const result = validateFilePath(linkedPath, projectRoot); + expect(result.valid).toBe(false); + + fs.rmSync(tempRoot, { recursive: true, force: true }); + }); + }); + + describe('allowed paths', () => { + it('should allow regular source files in project', () => { + const result = validateFilePath( + path.join(testProjectPath, 'src', 'components', 'App.tsx'), + testProjectPath + ); + expect(result.valid).toBe(true); + }); + + it('should allow JSON config files (non-sensitive)', () => { + const result = validateFilePath( + path.join(testProjectPath, 'package.json'), + testProjectPath + ); + expect(result.valid).toBe(true); + }); + + it('should allow JSONL files in ~/.claude', () => { + const result = validateFilePath( + path.join(claudeDir, 'projects', '-home-user-project', 'session.jsonl'), + null + ); + expect(result.valid).toBe(true); + }); + }); + + describe('tilde expansion', () => { + it('should expand ~ to home directory for paths within ~/.claude', () => { + const result = validateFilePath('~/.claude/projects/test.jsonl', null); + expect(result.valid).toBe(true); + expect(result.normalizedPath).toBe(path.join(homeDir, '.claude', 'projects', 'test.jsonl')); + }); + + it('should expand ~ to home directory for project paths', () => { + const projectInHome = path.join(homeDir, 'my-project'); + const result = validateFilePath('~/my-project/src/index.ts', projectInHome); + expect(result.valid).toBe(true); + expect(result.normalizedPath).toBe(path.join(projectInHome, 'src', 'index.ts')); + }); + + it('should reject tilde paths to sensitive files', () => { + const result = validateFilePath('~/.ssh/id_rsa', testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Access to sensitive files is not allowed'); + }); + + it('should reject tilde paths outside allowed directories', () => { + const result = validateFilePath('~/random-dir/file.txt', testProjectPath); + expect(result.valid).toBe(false); + }); + }); + }); + + describe('validateOpenPath', () => { + it('should expand tilde in paths', () => { + const result = validateOpenPath('~/.claude', null); + expect(result.valid).toBe(true); + expect(result.normalizedPath).toBe(path.normalize(claudeDir)); + }); + + it('should reject sensitive files', () => { + const result = validateOpenPath(path.join(homeDir, '.ssh', 'id_rsa'), testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Cannot open sensitive files'); + }); + + it('should reject empty path', () => { + const result = validateOpenPath('', testProjectPath); + expect(result.valid).toBe(false); + expect(result.error).toBe('Invalid path'); + }); + + it('should allow project directory', () => { + const result = validateOpenPath(testProjectPath, testProjectPath); + expect(result.valid).toBe(true); + }); + + it('should allow ~/.claude directory', () => { + const result = validateOpenPath(claudeDir, null); + expect(result.valid).toBe(true); + }); + + it('should reject paths outside allowed directories', () => { + const result = validateOpenPath('/etc', testProjectPath); + expect(result.valid).toBe(false); + }); + + it('should reject symlink paths that escape project directory', () => { + if (process.platform === 'win32') { + return; + } + + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'open-path-validation-')); + const projectRoot = path.join(tempRoot, 'project'); + const outsideRoot = path.join(tempRoot, 'outside'); + fs.mkdirSync(projectRoot, { recursive: true }); + fs.mkdirSync(outsideRoot, { recursive: true }); + + const linkedDir = path.join(projectRoot, 'linked-outside'); + fs.symlinkSync(outsideRoot, linkedDir); + + const result = validateOpenPath(linkedDir, projectRoot); + expect(result.valid).toBe(false); + + fs.rmSync(tempRoot, { recursive: true, force: true }); + }); + }); +}); diff --git a/test/main/utils/regexValidation.test.ts b/test/main/utils/regexValidation.test.ts new file mode 100644 index 00000000..9778c3e1 --- /dev/null +++ b/test/main/utils/regexValidation.test.ts @@ -0,0 +1,146 @@ +/** + * Tests for regex validation utilities (ReDoS protection). + */ + +import { describe, expect, it } from 'vitest'; + +import { createSafeRegExp, validateRegexPattern } from '../../../src/main/utils/regexValidation'; + +describe('regexValidation', () => { + describe('validateRegexPattern', () => { + describe('basic validation', () => { + it('should reject empty pattern', () => { + const result = validateRegexPattern(''); + expect(result.valid).toBe(false); + expect(result.error).toContain('non-empty string'); + }); + + it('should accept valid simple patterns', () => { + expect(validateRegexPattern('hello')).toEqual({ valid: true }); + expect(validateRegexPattern('error')).toEqual({ valid: true }); + expect(validateRegexPattern('[a-z]+')).toEqual({ valid: true }); + }); + + it('should accept valid patterns with special chars', () => { + expect(validateRegexPattern('foo\\.bar')).toEqual({ valid: true }); + expect(validateRegexPattern('\\d+\\.\\d+')).toEqual({ valid: true }); + expect(validateRegexPattern('^test$')).toEqual({ valid: true }); + }); + }); + + describe('length validation', () => { + it('should reject patterns over 100 chars', () => { + const longPattern = 'a'.repeat(101); + const result = validateRegexPattern(longPattern); + expect(result.valid).toBe(false); + expect(result.error).toContain('too long'); + }); + + it('should accept patterns at 100 chars', () => { + const maxPattern = 'a'.repeat(100); + expect(validateRegexPattern(maxPattern).valid).toBe(true); + }); + }); + + describe('ReDoS protection', () => { + it('should reject nested quantifiers (a+)+', () => { + const result = validateRegexPattern('(a+)+'); + expect(result.valid).toBe(false); + expect(result.error).toContain('performance issues'); + }); + + it('should reject nested quantifiers (a*)+', () => { + const result = validateRegexPattern('(a*)+'); + expect(result.valid).toBe(false); + }); + + it('should reject nested quantifiers (a+)*', () => { + const result = validateRegexPattern('(a+)*'); + expect(result.valid).toBe(false); + }); + + it('should reject overlapping alternation with quantifiers', () => { + const result = validateRegexPattern('(a|a)+'); + expect(result.valid).toBe(false); + }); + + it('should reject backreferences with quantifiers', () => { + const result = validateRegexPattern('(.)\\1+'); + expect(result.valid).toBe(false); + }); + + it('should accept safe quantifier patterns', () => { + expect(validateRegexPattern('a+')).toEqual({ valid: true }); + expect(validateRegexPattern('a*b+')).toEqual({ valid: true }); + expect(validateRegexPattern('[a-z]+')).toEqual({ valid: true }); + expect(validateRegexPattern('\\d{1,3}')).toEqual({ valid: true }); + }); + }); + + describe('bracket balance', () => { + it('should reject unbalanced parentheses', () => { + const result = validateRegexPattern('(abc'); + expect(result.valid).toBe(false); + expect(result.error).toContain('unbalanced'); + }); + + it('should reject unbalanced brackets', () => { + const result = validateRegexPattern('[abc'); + expect(result.valid).toBe(false); + expect(result.error).toContain('unbalanced'); + }); + + it('should accept balanced patterns', () => { + expect(validateRegexPattern('(abc)')).toEqual({ valid: true }); + expect(validateRegexPattern('[a-z]')).toEqual({ valid: true }); + expect(validateRegexPattern('((a)(b))')).toEqual({ valid: true }); + }); + + it('should handle escaped brackets', () => { + expect(validateRegexPattern('\\(abc\\)')).toEqual({ valid: true }); + expect(validateRegexPattern('\\[test\\]')).toEqual({ valid: true }); + }); + }); + + describe('syntax validation', () => { + it('should reject invalid regex syntax', () => { + const result = validateRegexPattern('*invalid'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid regex syntax'); + }); + + it('should reject invalid quantifier syntax', () => { + // Note: 'a{abc}' is valid JS regex (matches 'a' followed by literal '{abc}') + // We test actual invalid syntax + const result = validateRegexPattern('a{2,1}'); // min > max is invalid + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid regex syntax'); + }); + }); + }); + + describe('createSafeRegExp', () => { + it('should return RegExp for valid pattern', () => { + const regex = createSafeRegExp('test'); + expect(regex).toBeInstanceOf(RegExp); + expect(regex?.test('test')).toBe(true); + }); + + it('should return null for invalid pattern', () => { + expect(createSafeRegExp('')).toBeNull(); + expect(createSafeRegExp('(a+)+')).toBeNull(); + expect(createSafeRegExp('*invalid')).toBeNull(); + }); + + it('should use default case-insensitive flag', () => { + const regex = createSafeRegExp('test'); + expect(regex?.flags).toContain('i'); + expect(regex?.test('TEST')).toBe(true); + }); + + it('should use provided flags', () => { + const regex = createSafeRegExp('test', 'g'); + expect(regex?.flags).toBe('g'); + }); + }); +}); diff --git a/test/main/utils/tokenizer.test.ts b/test/main/utils/tokenizer.test.ts new file mode 100644 index 00000000..f10c133e --- /dev/null +++ b/test/main/utils/tokenizer.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; + +import { countContentTokens, countTokens } from '../../../src/main/utils/tokenizer'; + +describe('tokenizer', () => { + describe('countTokens', () => { + it('should return 0 for empty string', () => { + expect(countTokens('')).toBe(0); + }); + + it('should return 0 for null', () => { + expect(countTokens(null)).toBe(0); + }); + + it('should return 0 for undefined', () => { + expect(countTokens(undefined)).toBe(0); + }); + + it('should estimate tokens by dividing length by 4', () => { + // 12 chars / 4 = 3 tokens + expect(countTokens('Hello World!')).toBe(3); + }); + + it('should ceil the result', () => { + // 5 chars / 4 = 1.25, ceil to 2 + expect(countTokens('Hello')).toBe(2); + }); + + it('should handle long text', () => { + const longText = 'a'.repeat(1000); + expect(countTokens(longText)).toBe(250); // 1000 / 4 + }); + + it('should handle single character', () => { + expect(countTokens('a')).toBe(1); // 1 / 4 = 0.25, ceil to 1 + }); + }); + + describe('countContentTokens', () => { + it('should handle string content', () => { + expect(countContentTokens('Hello World!')).toBe(3); + }); + + it('should handle array content by stringifying', () => { + const content = [{ type: 'text', text: 'Hello' }]; + const stringified = JSON.stringify(content); + expect(countContentTokens(content)).toBe(Math.ceil(stringified.length / 4)); + }); + + it('should return 0 for null', () => { + expect(countContentTokens(null)).toBe(0); + }); + + it('should return 0 for undefined', () => { + expect(countContentTokens(undefined)).toBe(0); + }); + + it('should handle empty array', () => { + const content: unknown[] = []; + expect(countContentTokens(content)).toBe(1); // "[]" is 2 chars, ceil(2/4) = 1 + }); + }); +}); diff --git a/test/mocks/electronAPI.ts b/test/mocks/electronAPI.ts new file mode 100644 index 00000000..f16e9390 --- /dev/null +++ b/test/mocks/electronAPI.ts @@ -0,0 +1,174 @@ +/** + * Mock for window.electronAPI used in tests. + * Provides typed mocks for all IPC methods. + */ + +import { vi } from 'vitest'; + +import type { Project, Session, SessionDetail } from '../../src/renderer/types/data'; + +export interface MockElectronAPI { + getProjects: ReturnType Promise>>; + getSessions: ReturnType Promise>>; + getSessionsPaginated: ReturnType< + typeof vi.fn< + ( + projectId: string, + cursor: string | null, + limit?: number, + options?: { includeTotalCount?: boolean; prefilterAll?: boolean } + ) => Promise<{ + sessions: Session[]; + nextCursor: string | null; + hasMore: boolean; + totalCount: number; + }> + > + >; + getSessionDetail: ReturnType< + typeof vi.fn<(projectId: string, sessionId: string) => Promise> + >; + getRepositoryGroups: ReturnType; + getWorktreeSessions: ReturnType; + getSubagentDetail: ReturnType; + searchSessions: ReturnType; + readClaudeMdFiles: ReturnType; + readDirectoryClaudeMd: ReturnType; + readMentionedFile: ReturnType; + validateMentions: ReturnType; + openPath: ReturnType; + openExternal: ReturnType; + notifications: { + onNew: ReturnType; + onUpdated: ReturnType; + getUnread: ReturnType; + markAsRead: ReturnType; + markAllAsRead: ReturnType; + // Methods used by notificationSlice + get: ReturnType; + markRead: ReturnType; + markAllRead: ReturnType; + delete: ReturnType; + clear: ReturnType; + }; + onFileChange: ReturnType; + onTodoChange: ReturnType; + config: { + get: ReturnType; + update: ReturnType; + addIgnoreRegex: ReturnType; + removeIgnoreRegex: ReturnType; + addIgnoreRepository: ReturnType; + removeIgnoreRepository: ReturnType; + snooze: ReturnType; + clearSnooze: ReturnType; + addTrigger: ReturnType; + updateTrigger: ReturnType; + removeTrigger: ReturnType; + getTriggers: ReturnType; + testTrigger: ReturnType; + selectFolders: ReturnType; + }; +} + +/** + * Create a fresh mock electronAPI instance. + */ +export function createMockElectronAPI(): MockElectronAPI { + return { + getProjects: vi.fn().mockResolvedValue([]), + getSessions: vi.fn().mockResolvedValue([]), + getSessionsPaginated: vi.fn().mockResolvedValue({ + sessions: [], + nextCursor: null, + hasMore: false, + totalCount: 0, + }), + getSessionDetail: vi.fn().mockResolvedValue(null), + getRepositoryGroups: vi.fn().mockResolvedValue([]), + getWorktreeSessions: vi.fn().mockResolvedValue([]), + getSubagentDetail: vi.fn().mockResolvedValue(null), + searchSessions: vi.fn().mockResolvedValue({ + results: [], + totalMatches: 0, + sessionsSearched: 0, + query: '', + }), + readClaudeMdFiles: vi.fn().mockResolvedValue({}), + readDirectoryClaudeMd: vi.fn().mockResolvedValue({ + path: '', + exists: false, + charCount: 0, + estimatedTokens: 0, + }), + readMentionedFile: vi.fn().mockResolvedValue(null), + validateMentions: vi.fn().mockResolvedValue({}), + openPath: vi.fn().mockResolvedValue({ success: true }), + openExternal: vi.fn().mockResolvedValue({ success: true }), + notifications: { + onNew: vi.fn().mockReturnValue(() => {}), + onUpdated: vi.fn().mockReturnValue(() => {}), + getUnread: vi.fn().mockResolvedValue([]), + markAsRead: vi.fn().mockResolvedValue(undefined), + markAllAsRead: vi.fn().mockResolvedValue(undefined), + // Methods used by notificationSlice + get: vi.fn().mockResolvedValue({ notifications: [] }), + markRead: vi.fn().mockResolvedValue(true), + markAllRead: vi.fn().mockResolvedValue(true), + delete: vi.fn().mockResolvedValue(true), + clear: vi.fn().mockResolvedValue(true), + }, + onFileChange: vi.fn().mockReturnValue(() => {}), + onTodoChange: vi.fn().mockReturnValue(() => {}), + config: { + get: vi.fn().mockResolvedValue({ + notifications: { + enabled: true, + soundEnabled: true, + ignoredRegex: [], + ignoredRepositories: [], + snoozedUntil: null, + snoozeMinutes: 30, + triggers: [], + }, + general: { + launchAtLogin: false, + showDockIcon: true, + theme: 'dark', + defaultTab: 'dashboard', + }, + display: { + showTimestamps: true, + compactMode: false, + syntaxHighlighting: true, + }, + }), + update: vi.fn(), + addIgnoreRegex: vi.fn(), + removeIgnoreRegex: vi.fn(), + addIgnoreRepository: vi.fn(), + removeIgnoreRepository: vi.fn(), + snooze: vi.fn(), + clearSnooze: vi.fn(), + addTrigger: vi.fn(), + updateTrigger: vi.fn(), + removeTrigger: vi.fn(), + getTriggers: vi.fn().mockResolvedValue([]), + testTrigger: vi.fn(), + selectFolders: vi.fn().mockResolvedValue([]), + }, + }; +} + +/** + * Install mock electronAPI on window object. + * Returns the mock instance for assertions. + */ +export function installMockElectronAPI(): MockElectronAPI { + const mock = createMockElectronAPI(); + vi.stubGlobal('window', { + ...window, + electronAPI: mock, + }); + return mock; +} diff --git a/test/renderer/hooks/navigationUtils.test.ts b/test/renderer/hooks/navigationUtils.test.ts new file mode 100644 index 00000000..c01505e4 --- /dev/null +++ b/test/renderer/hooks/navigationUtils.test.ts @@ -0,0 +1,68 @@ +/** + * Tests for navigation/utils.ts — specifically findAIGroupBySubagentId. + */ + +import { describe, expect, it } from 'vitest'; + +import { findAIGroupBySubagentId } from '@renderer/hooks/navigation/utils'; + +import type { ChatItem } from '@renderer/types/groups'; +import type { Process } from '@main/types'; + +/** Minimal AI chat item factory for testing. */ +function makeAIChatItem(groupId: string, processes: Partial[] = []): ChatItem { + return { + type: 'ai', + group: { + id: groupId, + startTime: new Date(0), + endTime: new Date(1000), + processes: processes.map((p) => ({ + id: p.id ?? 'unknown', + filePath: p.filePath ?? '', + messages: [], + startTime: new Date(0), + endTime: new Date(1000), + ...p, + })) as Process[], + }, + } as ChatItem; +} + +describe('findAIGroupBySubagentId', () => { + it('returns null for empty items', () => { + expect(findAIGroupBySubagentId([], 'agent-123')).toBeNull(); + }); + + it('returns null when no AI group contains the subagent', () => { + const items: ChatItem[] = [ + makeAIChatItem('ai-1', [{ id: 'agent-aaa' }]), + makeAIChatItem('ai-2', [{ id: 'agent-bbb' }]), + ]; + expect(findAIGroupBySubagentId(items, 'agent-ccc')).toBeNull(); + }); + + it('finds the AI group containing the subagent', () => { + const items: ChatItem[] = [ + makeAIChatItem('ai-1', [{ id: 'agent-aaa' }]), + makeAIChatItem('ai-2', [{ id: 'agent-bbb' }, { id: 'agent-ccc' }]), + ]; + expect(findAIGroupBySubagentId(items, 'agent-ccc')).toBe('ai-2'); + }); + + it('returns first match when subagent appears in multiple groups', () => { + const items: ChatItem[] = [ + makeAIChatItem('ai-1', [{ id: 'agent-same' }]), + makeAIChatItem('ai-2', [{ id: 'agent-same' }]), + ]; + expect(findAIGroupBySubagentId(items, 'agent-same')).toBe('ai-1'); + }); + + it('skips non-AI items', () => { + const items: ChatItem[] = [ + { type: 'user', group: { id: 'user-1' } } as ChatItem, + makeAIChatItem('ai-1', [{ id: 'agent-target' }]), + ]; + expect(findAIGroupBySubagentId(items, 'agent-target')).toBe('ai-1'); + }); +}); diff --git a/test/renderer/hooks/useAutoScrollBottom.test.ts b/test/renderer/hooks/useAutoScrollBottom.test.ts new file mode 100644 index 00000000..c8eae798 --- /dev/null +++ b/test/renderer/hooks/useAutoScrollBottom.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest'; + +import { isNearBottom } from '../../../src/renderer/hooks/useAutoScrollBottom'; + +describe('useAutoScrollBottom helpers', () => { + it('returns true when distance from bottom is within threshold', () => { + expect(isNearBottom(850, 1000, 100, 50)).toBe(true); + }); + + it('returns false when distance from bottom exceeds threshold', () => { + expect(isNearBottom(700, 1000, 100, 50)).toBe(false); + }); +}); diff --git a/test/renderer/hooks/useSearchContextNavigation.test.ts b/test/renderer/hooks/useSearchContextNavigation.test.ts new file mode 100644 index 00000000..351fe8c4 --- /dev/null +++ b/test/renderer/hooks/useSearchContextNavigation.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; + +import { findCurrentSearchResultInContainer } from '../../../src/renderer/hooks/navigation/utils'; + +describe('useSearchContextNavigation helpers', () => { + it('finds current search result only within the provided container', () => { + const activeContainer = document.createElement('div'); + activeContainer.innerHTML = ` +
    + `; + + const inactiveContainer = document.createElement('div'); + inactiveContainer.innerHTML = ` +
    + `; + + document.body.appendChild(inactiveContainer); + document.body.appendChild(activeContainer); + + const result = findCurrentSearchResultInContainer(activeContainer); + expect(result?.id).toBe('active-result'); + }); + + it('returns null when container is missing', () => { + expect(findCurrentSearchResultInContainer(null)).toBeNull(); + }); + + it('finds the exact current result using item identity metadata', () => { + const container = document.createElement('div'); + container.innerHTML = ` + + + `; + + const result = findCurrentSearchResultInContainer(container, 'ai-1', 1); + expect(result?.id).toBe('second'); + }); +}); diff --git a/test/renderer/hooks/useVisibleAIGroup.test.ts b/test/renderer/hooks/useVisibleAIGroup.test.ts new file mode 100644 index 00000000..f40725a9 --- /dev/null +++ b/test/renderer/hooks/useVisibleAIGroup.test.ts @@ -0,0 +1,54 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { act } from 'react-dom/test-utils'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { useVisibleAIGroup } from '../../../src/renderer/hooks/useVisibleAIGroup'; + +class FakeIntersectionObserver { + constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {} + + observe(): void {} + unobserve(): void {} + disconnect(): void {} + takeRecords(): IntersectionObserverEntry[] { + return []; + } +} + +describe('useVisibleAIGroup', () => { + afterEach(() => { + vi.restoreAllMocks(); + document.body.innerHTML = ''; + }); + + it('uses provided rootRef as IntersectionObserver root', async () => { + const observerSpy = vi.fn((cb: IntersectionObserverCallback, opts?: IntersectionObserverInit) => + new FakeIntersectionObserver(cb, opts) + ); + + vi.stubGlobal('IntersectionObserver', observerSpy as unknown as typeof IntersectionObserver); + + const host = document.createElement('div'); + document.body.appendChild(host); + const rootEl = document.createElement('div'); + + function Harness(): React.JSX.Element { + const rootRef = React.useRef(rootEl); + useVisibleAIGroup({ onVisibleChange: () => undefined, rootRef }); + return React.createElement('div'); + } + + const root = createRoot(host); + await act(async () => { + root.render(React.createElement(Harness)); + await Promise.resolve(); + }); + + expect(observerSpy).toHaveBeenCalled(); + const lastCall = observerSpy.mock.calls[observerSpy.mock.calls.length - 1]; + expect(lastCall?.[1]?.root).toBe(rootEl); + + root.unmount(); + }); +}); diff --git a/test/renderer/store/notificationSlice.test.ts b/test/renderer/store/notificationSlice.test.ts new file mode 100644 index 00000000..f96f06e0 --- /dev/null +++ b/test/renderer/store/notificationSlice.test.ts @@ -0,0 +1,450 @@ +/** + * Notification slice unit tests. + * Tests navigateToError behavior for sidebar session highlighting. + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { installMockElectronAPI, type MockElectronAPI } from '../../mocks/electronAPI'; + +import { createTestStore, type TestStore } from './storeTestUtils'; + +import type { DetectedError } from '../../../src/renderer/types/data'; + +describe('notificationSlice', () => { + let store: TestStore; + let mockAPI: MockElectronAPI; + + beforeEach(() => { + vi.useFakeTimers(); + mockAPI = installMockElectronAPI(); + store = createTestStore(); + + // Mock crypto.randomUUID for predictable tab IDs + let uuidCounter = 0; + vi.stubGlobal('crypto', { + randomUUID: () => `test-uuid-${++uuidCounter}`, + }); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + describe('notification mutation fallbacks', () => { + it('re-fetches notifications when markRead returns false', async () => { + store.setState({ + notifications: [ + { + id: 'n1', + message: 'msg', + isRead: false, + }, + ] as never[], + }); + + mockAPI.notifications.markRead.mockResolvedValue(false); + mockAPI.notifications.get.mockResolvedValue({ + notifications: [{ id: 'n1', message: 'msg', isRead: false }], + }); + + await store.getState().markNotificationRead('n1'); + + expect(mockAPI.notifications.get).toHaveBeenCalled(); + }); + + it('re-fetches notifications when clear returns false', async () => { + store.setState({ + notifications: [{ id: 'n1', message: 'msg', isRead: true }] as never[], + }); + + mockAPI.notifications.clear.mockResolvedValue(false); + mockAPI.notifications.get.mockResolvedValue({ + notifications: [{ id: 'n1', message: 'msg', isRead: true }], + }); + + await store.getState().clearNotifications(); + + expect(mockAPI.notifications.get).toHaveBeenCalled(); + }); + }); + + describe('navigateToError', () => { + const createMockError = (overrides?: Partial): DetectedError => ({ + id: 'error-1', + sessionId: 'session-target', + projectId: 'project-1', + lineNumber: 42, + timestamp: Date.now(), + toolUseId: 'tool-1', + triggerName: 'test-trigger', + severity: 'error', + message: 'Test error message', + isRead: false, + ...overrides, + }); + + describe('flat mode (viewMode !== grouped)', () => { + beforeEach(() => { + store.setState({ + viewMode: 'flat', + projects: [ + { + id: 'project-1', + name: 'Project 1', + path: '/path/1', + sessions: ['session-1', 'session-target'], + }, + ] as never[], + }); + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: [{ id: 'session-1' }] as never[], + nextCursor: null, + hasMore: false, + totalCount: 1, + }); + + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-target' }, + chunks: [], + } as never); + }); + + it('should set selectedSessionId when navigating to error', () => { + const error = createMockError(); + + store.getState().navigateToError(error); + + // selectedSessionId should be set to the target session + expect(store.getState().selectedSessionId).toBe('session-target'); + }); + + it('should create new tab with correct sessionId and pendingNavigation', () => { + const error = createMockError(); + + store.getState().navigateToError(error); + + expect(store.getState().openTabs).toHaveLength(1); + expect(store.getState().openTabs[0].sessionId).toBe('session-target'); + expect(store.getState().openTabs[0].projectId).toBe('project-1'); + expect(store.getState().openTabs[0].pendingNavigation?.kind).toBe('error'); + }); + + it('should set selectedSessionId even when switching from different project', () => { + // Start with a different project selected + store.setState({ + selectedProjectId: 'project-other', + selectedSessionId: 'session-other', + }); + + const error = createMockError(); + + store.getState().navigateToError(error); + + // Should update to target session + expect(store.getState().selectedSessionId).toBe('session-target'); + expect(store.getState().selectedProjectId).toBe('project-1'); + }); + + it('should not highlight wrong session from previous tab state', () => { + // Setup: Have an old session selected + store.setState({ + selectedProjectId: 'project-1', + selectedSessionId: 'session-old', + }); + + const error = createMockError(); + + store.getState().navigateToError(error); + + // Should NOT retain old session, should be updated to target + expect(store.getState().selectedSessionId).not.toBe('session-old'); + expect(store.getState().selectedSessionId).toBe('session-target'); + }); + }); + + describe('grouped mode (viewMode === grouped)', () => { + beforeEach(() => { + store.setState({ + viewMode: 'grouped', + repositoryGroups: [ + { + id: 'repo-1', + name: 'Repo 1', + worktrees: [ + { + id: 'project-1', + name: 'Worktree 1', + path: '/path/1', + sessions: ['session-1', 'session-target'], + }, + ], + }, + ] as never[], + }); + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: [{ id: 'session-1' }] as never[], + nextCursor: null, + hasMore: false, + totalCount: 1, + }); + + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-target' }, + chunks: [], + } as never); + }); + + it('should set selectedSessionId when navigating to error in grouped mode', () => { + const error = createMockError(); + + store.getState().navigateToError(error); + + // selectedSessionId should be set to the target session + expect(store.getState().selectedSessionId).toBe('session-target'); + }); + + it('should set repository and worktree selection', () => { + const error = createMockError(); + + store.getState().navigateToError(error); + + expect(store.getState().selectedRepositoryId).toBe('repo-1'); + expect(store.getState().selectedWorktreeId).toBe('project-1'); + }); + + it('should not highlight wrong session from previous state in grouped mode', () => { + // Setup: Have an old session selected + store.setState({ + selectedRepositoryId: 'repo-1', + selectedWorktreeId: 'project-1', + selectedSessionId: 'session-old', + }); + + const error = createMockError(); + + store.getState().navigateToError(error); + + // Should NOT retain old session + expect(store.getState().selectedSessionId).not.toBe('session-old'); + expect(store.getState().selectedSessionId).toBe('session-target'); + }); + }); + + describe('existing tab behavior', () => { + it('should focus existing tab if session is already open', () => { + // Open target session tab first + store.getState().openTab({ + type: 'session', + sessionId: 'session-target', + projectId: 'project-1', + label: 'Target Session', + }); + const existingTabId = store.getState().activeTabId; + + // Open another tab + store.getState().openDashboard(); + + const error = createMockError(); + + store.getState().navigateToError(error); + + // Should focus existing tab, not create new + expect(store.getState().openTabs).toHaveLength(2); + expect(store.getState().activeTabId).toBe(existingTabId); + }); + + it('should enqueue error navigation request on existing tab', () => { + // Open target session tab first + store.getState().openTab({ + type: 'session', + sessionId: 'session-target', + projectId: 'project-1', + label: 'Target Session', + }); + + const error = createMockError({ + lineNumber: 100, + }); + + store.getState().navigateToError(error); + + const tab = store.getState().openTabs[0]; + expect(tab.pendingNavigation).toBeDefined(); + expect(tab.pendingNavigation?.kind).toBe('error'); + expect(tab.pendingNavigation?.highlight).toBe('red'); + expect(tab.pendingNavigation?.payload).toMatchObject({ + errorId: 'error-1', + lineNumber: 100, + toolUseId: 'tool-1', + }); + }); + + it('should create new nonce on repeated clicks', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-target', + projectId: 'project-1', + label: 'Target Session', + }); + + const error = createMockError(); + + store.getState().navigateToError(error); + const firstId = store.getState().openTabs[0].pendingNavigation?.id; + + store.getState().navigateToError(error); + const secondId = store.getState().openTabs[0].pendingNavigation?.id; + + expect(firstId).toBeDefined(); + expect(secondId).toBeDefined(); + expect(firstId).not.toBe(secondId); + }); + }); + + describe('sidebar highlighting with pagination', () => { + /** + * Test scenario: Session exists but is not in the first page (pagination). + * + * The sidebar only renders sessions that are in the `sessions` array. + * If selectedSessionId is set to a session not in the loaded list, + * nothing will be highlighted (correct behavior). + * + * The fix ensures selectedSessionId is always set to the target session, + * rather than retaining a stale value that might match a loaded session. + */ + it('should set selectedSessionId to target even if not in loaded sessions list', () => { + store.setState({ + viewMode: 'flat', + projects: [ + { + id: 'project-1', + name: 'Project 1', + path: '/path/1', + sessions: ['session-1', 'session-target'], + }, + ] as never[], + // Simulating: first page loaded, target session not included + sessions: [{ id: 'session-1', createdAt: '2024-01-15' }] as never[], + }); + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: [{ id: 'session-1' }] as never[], + nextCursor: 'cursor-1', + hasMore: true, + totalCount: 100, + }); + + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-target' }, + chunks: [], + } as never); + + const error = createMockError(); + + store.getState().navigateToError(error); + + // selectedSessionId should be set to target, even if not in loaded sessions + expect(store.getState().selectedSessionId).toBe('session-target'); + + // Verify the session is NOT in the current loaded list (simulating pagination) + const loadedSessionIds = store.getState().sessions.map((s) => s.id); + expect(loadedSessionIds).not.toContain('session-target'); + + // Sidebar behavior: isActive = selectedSessionId === item.session.id + // Since 'session-target' is not in sessions array, it won't be rendered + // and therefore won't be highlighted. Only 'session-1' is rendered, + // but selectedSessionId doesn't match it, so nothing is highlighted. + // This is the correct behavior. + }); + + it('should correctly highlight when target session IS in loaded list', async () => { + store.setState({ + viewMode: 'flat', + projects: [ + { + id: 'project-1', + name: 'Project 1', + path: '/path/1', + sessions: ['session-1', 'session-target'], + }, + ] as never[], + }); + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: [{ id: 'session-1' }, { id: 'session-target' }] as never[], + nextCursor: null, + hasMore: false, + totalCount: 2, + }); + + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-target' }, + chunks: [], + } as never); + + const error = createMockError(); + + store.getState().navigateToError(error); + + // selectedSessionId should match target immediately + expect(store.getState().selectedSessionId).toBe('session-target'); + + // Wait for async fetch to complete + await vi.runAllTimersAsync(); + + // Verify the session IS in the loaded list after fetch + const loadedSessionIds = store.getState().sessions.map((s) => s.id); + expect(loadedSessionIds).toContain('session-target'); + + // Sidebar behavior: isActive = selectedSessionId === item.session.id + // Since 'session-target' is in sessions array and selectedSessionId matches, + // it will be highlighted correctly. + }); + + it('should not highlight unrelated session when target is not loaded', () => { + store.setState({ + viewMode: 'flat', + projects: [ + { + id: 'project-1', + name: 'Project 1', + path: '/path/1', + sessions: ['session-1', 'session-target'], + }, + ] as never[], + // Only session-1 is loaded, and it was previously selected + sessions: [{ id: 'session-1', createdAt: '2024-01-15' }] as never[], + selectedSessionId: 'session-1', // Previous selection that might cause wrong highlight + }); + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: [{ id: 'session-1' }] as never[], + nextCursor: 'cursor-1', + hasMore: true, + totalCount: 100, + }); + + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-target' }, + chunks: [], + } as never); + + const error = createMockError(); + + // Before fix: selectedSessionId would remain 'session-1' (from selectProject reset) + // causing session-1 to be highlighted incorrectly + + store.getState().navigateToError(error); + + // After fix: selectedSessionId is updated to 'session-target' + expect(store.getState().selectedSessionId).toBe('session-target'); + // Since 'session-target' is not in sessions array, nothing will be highlighted + // (session-1 is in the array but doesn't match selectedSessionId anymore) + }); + }); + }); +}); diff --git a/test/renderer/store/paneSlice.test.ts b/test/renderer/store/paneSlice.test.ts new file mode 100644 index 00000000..110a2c0f --- /dev/null +++ b/test/renderer/store/paneSlice.test.ts @@ -0,0 +1,487 @@ +/** + * Tests for the paneSlice - multi-pane split layout state management. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; + +import { MAX_PANES } from '../../../src/renderer/types/panes'; + +import { createTestStore } from './storeTestUtils'; + +import type { TestStore } from './storeTestUtils'; + +let store: TestStore; + +beforeEach(() => { + store = createTestStore(); +}); + +describe('paneSlice', () => { + describe('initial state', () => { + it('starts with a single default pane', () => { + const { paneLayout } = store.getState(); + expect(paneLayout.panes).toHaveLength(1); + expect(paneLayout.panes[0].id).toBe('pane-default'); + expect(paneLayout.panes[0].widthFraction).toBe(1); + expect(paneLayout.panes[0].tabs).toEqual([]); + expect(paneLayout.focusedPaneId).toBe('pane-default'); + }); + }); + + describe('focusPane', () => { + it('changes focusedPaneId', () => { + const state = store.getState(); + // Open a tab first and split to create a second pane + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + const { paneLayout } = store.getState(); + expect(paneLayout.panes).toHaveLength(2); + + // New pane should be focused after split + const newPaneId = paneLayout.focusedPaneId; + expect(newPaneId).not.toBe('pane-default'); + + // Focus back to default pane + store.getState().focusPane('pane-default'); + expect(store.getState().paneLayout.focusedPaneId).toBe('pane-default'); + }); + + it('no-ops when already focused', () => { + const before = store.getState().paneLayout; + store.getState().focusPane('pane-default'); + expect(store.getState().paneLayout).toBe(before); + }); + + it('no-ops for non-existent pane', () => { + const before = store.getState().paneLayout; + store.getState().focusPane('non-existent'); + expect(store.getState().paneLayout).toBe(before); + }); + }); + + describe('splitPane', () => { + it('creates a new pane with the specified tab to the right', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tabs = store.getState().paneLayout.panes[0].tabs; + expect(tabs).toHaveLength(2); + const tab1Id = tabs[0].id; + + state.splitPane('pane-default', tab1Id, 'right'); + + const { paneLayout } = store.getState(); + expect(paneLayout.panes).toHaveLength(2); + + // Source pane should have lost the tab + const sourcePane = paneLayout.panes.find((p) => p.id === 'pane-default'); + expect(sourcePane?.tabs).toHaveLength(1); + expect(sourcePane?.tabs[0].sessionId).toBe('s2'); + + // New pane should have the split tab + const newPane = paneLayout.panes.find((p) => p.id !== 'pane-default'); + expect(newPane?.tabs).toHaveLength(1); + expect(newPane?.tabs[0].sessionId).toBe('s1'); + + // New pane should be focused + expect(paneLayout.focusedPaneId).toBe(newPane?.id); + + // Widths should be equal + expect(sourcePane?.widthFraction).toBeCloseTo(0.5); + expect(newPane?.widthFraction).toBeCloseTo(0.5); + }); + + it('creates a new pane to the left', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab2Id = store.getState().paneLayout.panes[0].tabs[1].id; + state.splitPane('pane-default', tab2Id, 'left'); + + const { paneLayout } = store.getState(); + expect(paneLayout.panes).toHaveLength(2); + + // New pane should be to the left (index 0) + const leftPane = paneLayout.panes[0]; + expect(leftPane.tabs[0].sessionId).toBe('s2'); + expect(leftPane.id).toBe(paneLayout.focusedPaneId); + }); + + it('does not exceed MAX_PANES', () => { + const state = store.getState(); + // Create MAX_PANES tabs + for (let i = 0; i < MAX_PANES + 1; i++) { + state.openTab({ + type: 'session', + sessionId: `s${i}`, + projectId: 'p1', + label: `Session ${i}`, + }); + } + + // Split until we reach MAX_PANES + for (let i = 0; i < MAX_PANES - 1; i++) { + const currentState = store.getState(); + const focusedPane = currentState.paneLayout.panes.find( + (p) => p.id === currentState.paneLayout.focusedPaneId + ); + if (focusedPane && focusedPane.tabs.length > 1) { + currentState.splitPane(focusedPane.id, focusedPane.tabs[0].id, 'right'); + } + } + + const paneCount = store.getState().paneLayout.panes.length; + expect(paneCount).toBeLessThanOrEqual(MAX_PANES); + + // Attempting to split again should be no-op if at MAX_PANES + if (paneCount === MAX_PANES) { + const beforeLayout = store.getState().paneLayout; + const focusedPane = beforeLayout.panes.find((p) => p.id === beforeLayout.focusedPaneId); + if (focusedPane && focusedPane.tabs.length > 0) { + store.getState().splitPane(focusedPane.id, focusedPane.tabs[0].id, 'right'); + expect(store.getState().paneLayout.panes).toHaveLength(MAX_PANES); + } + } + }); + }); + + describe('closePane', () => { + it('removes a pane and redistributes width', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + const newPaneId = store.getState().paneLayout.panes.find((p) => p.id !== 'pane-default')?.id; + expect(newPaneId).toBeDefined(); + + store.getState().closePane(newPaneId!); + + const { paneLayout } = store.getState(); + expect(paneLayout.panes).toHaveLength(1); + expect(paneLayout.panes[0].widthFraction).toBe(1); + }); + + it('cannot close the last pane', () => { + store.getState().closePane('pane-default'); + expect(store.getState().paneLayout.panes).toHaveLength(1); + }); + + it('shifts focus to neighbor when closing focused pane', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + // New pane is focused + const focusedId = store.getState().paneLayout.focusedPaneId; + expect(focusedId).not.toBe('pane-default'); + + // Close the focused pane + store.getState().closePane(focusedId); + + // Focus should shift to remaining pane + expect(store.getState().paneLayout.focusedPaneId).toBe('pane-default'); + }); + }); + + describe('moveTabToPane', () => { + it('moves a tab from one pane to another', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + state.openTab({ type: 'session', sessionId: 's3', projectId: 'p1', label: 'Session 3' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + const panes = store.getState().paneLayout.panes; + const newPaneId = panes.find((p) => p.id !== 'pane-default')!.id; + + // Move s2 from pane-default to the new pane + const tab2Id = panes.find((p) => p.id === 'pane-default')!.tabs[0].id; + store.getState().moveTabToPane(tab2Id, 'pane-default', newPaneId); + + const updatedPanes = store.getState().paneLayout.panes; + const sourcePane = updatedPanes.find((p) => p.id === 'pane-default')!; + const targetPane = updatedPanes.find((p) => p.id === newPaneId)!; + + expect(sourcePane.tabs).toHaveLength(1); // s3 left + expect(targetPane.tabs).toHaveLength(2); // s1 + s2 + }); + + it('auto-closes source pane when last tab is moved out', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + // Now pane-default has s2, new pane has s1 + const newPaneId = store.getState().paneLayout.panes.find((p) => p.id !== 'pane-default')!.id; + + // Move s2 (last tab in pane-default) to new pane + const tab2Id = store.getState().paneLayout.panes.find((p) => p.id === 'pane-default')!.tabs[0] + .id; + store.getState().moveTabToPane(tab2Id, 'pane-default', newPaneId); + + // pane-default should be auto-closed + const panes = store.getState().paneLayout.panes; + expect(panes).toHaveLength(1); + expect(panes[0].id).toBe(newPaneId); + expect(panes[0].tabs).toHaveLength(2); + }); + + it('no-ops when source and target are the same', () => { + store.getState().openTab({ + type: 'session', + sessionId: 's1', + projectId: 'p1', + label: 'Session 1', + }); + const tabId = store.getState().paneLayout.panes[0].tabs[0].id; + const before = store.getState().paneLayout; + store.getState().moveTabToPane(tabId, 'pane-default', 'pane-default'); + expect(store.getState().paneLayout).toBe(before); + }); + }); + + describe('reorderTabInPane', () => { + it('reorders tabs within a pane', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + state.openTab({ type: 'session', sessionId: 's3', projectId: 'p1', label: 'Session 3' }); + + const tabs = store.getState().paneLayout.panes[0].tabs; + expect(tabs[0].sessionId).toBe('s1'); + expect(tabs[2].sessionId).toBe('s3'); + + // Move first tab to last position + store.getState().reorderTabInPane('pane-default', 0, 2); + + const reordered = store.getState().paneLayout.panes[0].tabs; + expect(reordered[0].sessionId).toBe('s2'); + expect(reordered[1].sessionId).toBe('s3'); + expect(reordered[2].sessionId).toBe('s1'); + }); + + it('no-ops for same index', () => { + store.getState().openTab({ + type: 'session', + sessionId: 's1', + projectId: 'p1', + label: 'Session 1', + }); + const before = store.getState().paneLayout; + store.getState().reorderTabInPane('pane-default', 0, 0); + expect(store.getState().paneLayout).toBe(before); + }); + + it('no-ops for out-of-bounds index', () => { + store.getState().openTab({ + type: 'session', + sessionId: 's1', + projectId: 'p1', + label: 'Session 1', + }); + const before = store.getState().paneLayout; + store.getState().reorderTabInPane('pane-default', 0, 5); + expect(store.getState().paneLayout).toBe(before); + }); + }); + + describe('resizePanes', () => { + it('adjusts width fractions of adjacent panes', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + // Resize pane-default to 60% + store.getState().resizePanes('pane-default', 0.6); + + const panes = store.getState().paneLayout.panes; + const defaultPane = panes.find((p) => p.id === 'pane-default')!; + const otherPane = panes.find((p) => p.id !== 'pane-default')!; + + expect(defaultPane.widthFraction).toBeCloseTo(0.6); + expect(otherPane.widthFraction).toBeCloseTo(0.4); + }); + + it('clamps to minimum fraction', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + // Try to make pane-default almost 100% (leaving too little for neighbor) + store.getState().resizePanes('pane-default', 0.95); + + const panes = store.getState().paneLayout.panes; + for (const pane of panes) { + expect(pane.widthFraction).toBeGreaterThanOrEqual(0.1); + } + }); + }); + + describe('getPaneForTab', () => { + it('returns the pane ID containing the tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 's1', + projectId: 'p1', + label: 'Session 1', + }); + const tabId = store.getState().paneLayout.panes[0].tabs[0].id; + expect(store.getState().getPaneForTab(tabId)).toBe('pane-default'); + }); + + it('returns null for non-existent tab', () => { + expect(store.getState().getPaneForTab('non-existent')).toBeNull(); + }); + }); + + describe('getAllPaneTabs', () => { + it('returns all tabs across all panes', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + const allTabs = store.getState().getAllPaneTabs(); + expect(allTabs).toHaveLength(2); + const sessionIds = allTabs.map((t) => t.sessionId); + expect(sessionIds).toContain('s1'); + expect(sessionIds).toContain('s2'); + }); + }); + + describe('moveTabToNewPane', () => { + it('creates a new pane and moves the tab there', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.moveTabToNewPane(tab1Id, 'pane-default', 'pane-default', 'right'); + + const { paneLayout } = store.getState(); + expect(paneLayout.panes).toHaveLength(2); + + const sourcePane = paneLayout.panes.find((p) => p.id === 'pane-default')!; + const newPane = paneLayout.panes.find((p) => p.id !== 'pane-default')!; + + expect(sourcePane.tabs).toHaveLength(1); + expect(sourcePane.tabs[0].sessionId).toBe('s2'); + expect(newPane.tabs).toHaveLength(1); + expect(newPane.tabs[0].sessionId).toBe('s1'); + }); + + it('respects MAX_PANES limit', () => { + const state = store.getState(); + for (let i = 0; i < MAX_PANES + 1; i++) { + state.openTab({ + type: 'session', + sessionId: `s${i}`, + projectId: 'p1', + label: `Session ${i}`, + }); + } + + // Split until MAX_PANES + for (let i = 0; i < MAX_PANES - 1; i++) { + const currentState = store.getState(); + const focusedPane = currentState.paneLayout.panes.find( + (p) => p.id === currentState.paneLayout.focusedPaneId + ); + if (focusedPane && focusedPane.tabs.length > 1) { + currentState.splitPane(focusedPane.id, focusedPane.tabs[0].id, 'right'); + } + } + + const paneCountBefore = store.getState().paneLayout.panes.length; + if (paneCountBefore >= MAX_PANES) { + // Attempt should be no-op + const focusedPane = store + .getState() + .paneLayout.panes.find((p) => p.id === store.getState().paneLayout.focusedPaneId); + if (focusedPane && focusedPane.tabs.length > 0) { + store + .getState() + .moveTabToNewPane(focusedPane.tabs[0].id, focusedPane.id, focusedPane.id, 'right'); + expect(store.getState().paneLayout.panes.length).toBe(paneCountBefore); + } + } + }); + }); + + describe('integration with tabSlice', () => { + it('openTab adds to focused pane', () => { + store.getState().openTab({ + type: 'session', + sessionId: 's1', + projectId: 'p1', + label: 'Session 1', + }); + + const pane = store.getState().paneLayout.panes[0]; + expect(pane.tabs).toHaveLength(1); + expect(pane.tabs[0].sessionId).toBe('s1'); + + // Root-level state should be synced + expect(store.getState().openTabs).toHaveLength(1); + expect(store.getState().activeTabId).toBe(pane.tabs[0].id); + }); + + it('closeTab removes from the containing pane', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + const tabToClose = store.getState().paneLayout.panes[0].tabs[0].id; + store.getState().closeTab(tabToClose); + + const pane = store.getState().paneLayout.panes[0]; + expect(pane.tabs).toHaveLength(1); + expect(pane.tabs[0].sessionId).toBe('s2'); + }); + + it('setActiveTab focuses the pane containing the tab', () => { + const state = store.getState(); + state.openTab({ type: 'session', sessionId: 's1', projectId: 'p1', label: 'Session 1' }); + state.openTab({ type: 'session', sessionId: 's2', projectId: 'p1', label: 'Session 2' }); + + // Split to create two panes + const tab1Id = store.getState().paneLayout.panes[0].tabs[0].id; + state.splitPane('pane-default', tab1Id, 'right'); + + // Now s2 is in pane-default, s1 is in new pane + // Focus pane-default + store.getState().focusPane('pane-default'); + expect(store.getState().paneLayout.focusedPaneId).toBe('pane-default'); + + // Set active tab to s1 (in other pane) - should focus that pane + store.getState().setActiveTab(tab1Id); + const newPaneId = store.getState().paneLayout.panes.find((p) => p.id !== 'pane-default')?.id; + expect(store.getState().paneLayout.focusedPaneId).toBe(newPaneId); + }); + }); +}); diff --git a/test/renderer/store/pathResolution.test.ts b/test/renderer/store/pathResolution.test.ts new file mode 100644 index 00000000..19de8fad --- /dev/null +++ b/test/renderer/store/pathResolution.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; + +import { resolveFilePath } from '../../../src/renderer/store/utils/pathResolution'; + +describe('resolveFilePath', () => { + it('returns unix absolute paths as-is', () => { + expect(resolveFilePath('/repo', '/repo/src/index.ts')).toBe('/repo/src/index.ts'); + }); + + it('returns windows absolute paths as-is', () => { + expect(resolveFilePath('C:\\repo', 'C:\\repo\\src\\index.ts')).toBe('C:\\repo\\src\\index.ts'); + }); + + it('resolves dot-prefixed relative paths', () => { + expect(resolveFilePath('/repo', './src/app.ts')).toBe('/repo/src/app.ts'); + }); + + it('resolves parent relative paths on unix', () => { + expect(resolveFilePath('/repo/apps/web', '../shared/file.ts')).toBe( + '/repo/apps/shared/file.ts' + ); + }); + + it('resolves parent relative paths on windows', () => { + expect(resolveFilePath('C:\\repo\\apps\\web', '..\\shared\\file.ts')).toBe( + 'C:\\repo\\apps\\shared\\file.ts' + ); + }); + + it('passes through tilde paths as-is', () => { + expect(resolveFilePath('/repo', '~/some/directory')).toBe('~/some/directory'); + }); + + it('passes through tilde paths with @ prefix as-is', () => { + expect(resolveFilePath('/repo', '@~/some/file.ts')).toBe('~/some/file.ts'); + }); + + it('passes through bare tilde as-is', () => { + expect(resolveFilePath('/repo', '~')).toBe('~'); + }); + + it('passes through tilde paths with backslash separator (Windows)', () => { + expect(resolveFilePath('C:\\repo', '~\\.claude\\agents\\file.md')).toBe( + '~\\.claude\\agents\\file.md' + ); + }); + + it('does not treat tilde in the middle as special', () => { + expect(resolveFilePath('/repo', 'foo~/bar')).toBe('/repo/foo~/bar'); + }); +}); diff --git a/test/renderer/store/sessionSlice.test.ts b/test/renderer/store/sessionSlice.test.ts new file mode 100644 index 00000000..3389d947 --- /dev/null +++ b/test/renderer/store/sessionSlice.test.ts @@ -0,0 +1,314 @@ +/** + * Session slice unit tests. + * Tests session state management including fetching, pagination, and selection. + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { installMockElectronAPI, type MockElectronAPI } from '../../mocks/electronAPI'; + +import { createTestStore, type TestStore } from './storeTestUtils'; + +describe('sessionSlice', () => { + let store: TestStore; + let mockAPI: MockElectronAPI; + + beforeEach(() => { + mockAPI = installMockElectronAPI(); + store = createTestStore(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('fetchSessionsInitial', () => { + it('should fetch first page of sessions', async () => { + const mockSessions = [ + { id: 'session-1', createdAt: '2024-01-15T10:00:00Z' }, + { id: 'session-2', createdAt: '2024-01-14T10:00:00Z' }, + ]; + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: mockSessions as never[], + nextCursor: 'cursor-1', + hasMore: true, + totalCount: 50, + }); + + await store.getState().fetchSessionsInitial('project-1'); + + expect(mockAPI.getSessionsPaginated).toHaveBeenCalledWith('project-1', null, 20, { + includeTotalCount: false, + prefilterAll: false, + }); + expect(store.getState().sessions).toHaveLength(2); + expect(store.getState().sessionsCursor).toBe('cursor-1'); + expect(store.getState().sessionsHasMore).toBe(true); + expect(store.getState().sessionsTotalCount).toBe(50); + expect(store.getState().sessionsLoading).toBe(false); + }); + + it('should set loading state during fetch', async () => { + mockAPI.getSessionsPaginated.mockImplementation( + () => + new Promise((resolve) => { + setTimeout( + () => + resolve({ + sessions: [], + nextCursor: null, + hasMore: false, + totalCount: 0, + }), + 100 + ); + }) + ); + + const fetchPromise = store.getState().fetchSessionsInitial('project-1'); + expect(store.getState().sessionsLoading).toBe(true); + + vi.useFakeTimers(); + vi.advanceTimersByTime(100); + await fetchPromise; + vi.useRealTimers(); + + expect(store.getState().sessionsLoading).toBe(false); + }); + + it('should handle fetch error', async () => { + mockAPI.getSessionsPaginated.mockRejectedValue(new Error('Network error')); + + await store.getState().fetchSessionsInitial('project-1'); + + expect(store.getState().sessionsError).toBe('Network error'); + expect(store.getState().sessionsLoading).toBe(false); + }); + }); + + describe('fetchSessionsMore', () => { + it('should append sessions to existing list', async () => { + // Setup initial state + store.setState({ + selectedProjectId: 'project-1', + sessions: [{ id: 'session-1' }] as never[], + sessionsCursor: 'cursor-1', + sessionsHasMore: true, + sessionsLoadingMore: false, + }); + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: [{ id: 'session-2' }] as never[], + nextCursor: 'cursor-2', + hasMore: true, + totalCount: 50, + }); + + await store.getState().fetchSessionsMore(); + + expect(store.getState().sessions).toHaveLength(2); + expect(store.getState().sessionsCursor).toBe('cursor-2'); + }); + + it('should not fetch if no more pages', async () => { + store.setState({ + selectedProjectId: 'project-1', + sessionsHasMore: false, + sessionsCursor: null, + }); + + await store.getState().fetchSessionsMore(); + + expect(mockAPI.getSessionsPaginated).not.toHaveBeenCalled(); + }); + + it('should not fetch if already loading', async () => { + store.setState({ + selectedProjectId: 'project-1', + sessionsHasMore: true, + sessionsCursor: 'cursor-1', + sessionsLoadingMore: true, + }); + + await store.getState().fetchSessionsMore(); + + expect(mockAPI.getSessionsPaginated).not.toHaveBeenCalled(); + }); + }); + + describe('selectSession', () => { + it('should update selected session ID', () => { + store.setState({ + selectedProjectId: 'project-1', + }); + + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-1' }, + chunks: [], + } as never); + + store.getState().selectSession('session-1'); + + expect(store.getState().selectedSessionId).toBe('session-1'); + }); + + it('should clear previous session detail', () => { + store.setState({ + selectedProjectId: 'project-1', + sessionDetail: { session: { id: 'old-session' } } as never, + sessionContextStats: new Map() as never, + }); + + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-2' }, + chunks: [], + } as never); + + store.getState().selectSession('session-2'); + + expect(store.getState().sessionDetail).toBeNull(); + expect(store.getState().sessionContextStats).toBeNull(); + }); + }); + + describe('clearSelection', () => { + it('should clear all selection state', () => { + store.setState({ + selectedProjectId: 'project-1', + selectedSessionId: 'session-1', + sessions: [{ id: 'session-1' }] as never[], + sessionDetail: { session: { id: 'session-1' } } as never, + }); + + store.getState().clearSelection(); + + expect(store.getState().selectedProjectId).toBeNull(); + expect(store.getState().selectedSessionId).toBeNull(); + expect(store.getState().sessions).toHaveLength(0); + expect(store.getState().sessionDetail).toBeNull(); + }); + }); + + describe('refreshSessionsInPlace', () => { + it('should refresh sessions without loading state', async () => { + store.setState({ + selectedProjectId: 'project-1', + sessions: [{ id: 'session-1' }] as never[], + sessionsLoading: false, + }); + + mockAPI.getSessionsPaginated.mockResolvedValue({ + sessions: [{ id: 'session-1' }, { id: 'session-2' }] as never[], + nextCursor: null, + hasMore: false, + totalCount: 2, + }); + + await store.getState().refreshSessionsInPlace('project-1'); + + expect(store.getState().sessions).toHaveLength(2); + expect(mockAPI.getSessionsPaginated).toHaveBeenCalledWith('project-1', null, 20, { + includeTotalCount: false, + prefilterAll: false, + }); + // Should not have set loading state + expect(store.getState().sessionsLoading).toBe(false); + }); + + it('should skip refresh if different project selected', async () => { + store.setState({ + selectedProjectId: 'project-1', + }); + + await store.getState().refreshSessionsInPlace('project-2'); + + expect(mockAPI.getSessionsPaginated).not.toHaveBeenCalled(); + }); + + it('should ignore stale refresh responses and keep latest result', async () => { + store.setState({ + selectedProjectId: 'project-1', + sessions: [{ id: 'seed' }] as never[], + }); + + let resolveFirst: ((value: unknown) => void) | undefined; + let resolveSecond: ((value: unknown) => void) | undefined; + + mockAPI.getSessionsPaginated + .mockImplementationOnce( + () => + new Promise((resolve) => { + resolveFirst = resolve; + }) + ) + .mockImplementationOnce( + () => + new Promise((resolve) => { + resolveSecond = resolve; + }) + ); + + const first = store.getState().refreshSessionsInPlace('project-1'); + const second = store.getState().refreshSessionsInPlace('project-1'); + + resolveSecond?.({ + sessions: [{ id: 'newest' }] as never[], + nextCursor: null, + hasMore: false, + totalCount: 1, + }); + resolveFirst?.({ + sessions: [{ id: 'stale' }] as never[], + nextCursor: null, + hasMore: false, + totalCount: 1, + }); + + await Promise.all([first, second]); + expect(store.getState().sessions[0]?.id).toBe('newest'); + }); + }); + + describe('fetchSessionDetail', () => { + it('should ignore stale responses and keep the latest session detail', async () => { + store.setState({ + selectedSessionId: 'session-2', + }); + + let resolveFirst: ((value: unknown) => void) | undefined; + let resolveSecond: ((value: unknown) => void) | undefined; + + mockAPI.getSessionDetail + .mockImplementationOnce( + () => + new Promise((resolve) => { + resolveFirst = resolve; + }) + ) + .mockImplementationOnce( + () => + new Promise((resolve) => { + resolveSecond = resolve; + }) + ); + + const first = store.getState().fetchSessionDetail('project-1', 'session-1'); + const second = store.getState().fetchSessionDetail('project-1', 'session-2'); + + resolveSecond?.({ + session: { id: 'session-2' }, + chunks: [], + processes: [], + }); + resolveFirst?.({ + session: { id: 'session-1' }, + chunks: [], + processes: [], + }); + + await Promise.all([first, second]); + expect(store.getState().sessionDetail?.session.id).toBe('session-2'); + }); + }); +}); diff --git a/test/renderer/store/storeTestUtils.ts b/test/renderer/store/storeTestUtils.ts new file mode 100644 index 00000000..747305be --- /dev/null +++ b/test/renderer/store/storeTestUtils.ts @@ -0,0 +1,43 @@ +/** + * Store test utilities for creating isolated test store instances. + */ + +import { create } from 'zustand'; + +import { createConfigSlice } from '../../../src/renderer/store/slices/configSlice'; +import { createConversationSlice } from '../../../src/renderer/store/slices/conversationSlice'; +import { createNotificationSlice } from '../../../src/renderer/store/slices/notificationSlice'; +import { createPaneSlice } from '../../../src/renderer/store/slices/paneSlice'; +import { createProjectSlice } from '../../../src/renderer/store/slices/projectSlice'; +import { createRepositorySlice } from '../../../src/renderer/store/slices/repositorySlice'; +import { createSessionDetailSlice } from '../../../src/renderer/store/slices/sessionDetailSlice'; +import { createSessionSlice } from '../../../src/renderer/store/slices/sessionSlice'; +import { createSubagentSlice } from '../../../src/renderer/store/slices/subagentSlice'; +import { createTabSlice } from '../../../src/renderer/store/slices/tabSlice'; +import { createTabUISlice } from '../../../src/renderer/store/slices/tabUISlice'; +import { createUISlice } from '../../../src/renderer/store/slices/uiSlice'; + +import type { AppState } from '../../../src/renderer/store/types'; + +/** + * Create an isolated store instance for testing. + * Each test gets a fresh store with no shared state. + */ +export function createTestStore() { + return create()((...args) => ({ + ...createProjectSlice(...args), + ...createRepositorySlice(...args), + ...createSessionSlice(...args), + ...createSessionDetailSlice(...args), + ...createSubagentSlice(...args), + ...createConversationSlice(...args), + ...createTabSlice(...args), + ...createTabUISlice(...args), + ...createPaneSlice(...args), + ...createUISlice(...args), + ...createNotificationSlice(...args), + ...createConfigSlice(...args), + })); +} + +export type TestStore = ReturnType; diff --git a/test/renderer/store/tabSlice.test.ts b/test/renderer/store/tabSlice.test.ts new file mode 100644 index 00000000..f3733b7f --- /dev/null +++ b/test/renderer/store/tabSlice.test.ts @@ -0,0 +1,592 @@ +/** + * Tab slice unit tests. + * Tests tab state management including deduplication, forceNewTab, scroll position, + * and the unified navigation request model. + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { installMockElectronAPI, type MockElectronAPI } from '../../mocks/electronAPI'; + +import { createTestStore, type TestStore } from './storeTestUtils'; + +import type { TabNavigationRequest } from '../../../src/renderer/types/tabs'; + +describe('tabSlice', () => { + let store: TestStore; + let mockAPI: MockElectronAPI; + + beforeEach(() => { + vi.useFakeTimers(); + mockAPI = installMockElectronAPI(); + store = createTestStore(); + + // Mock crypto.randomUUID for predictable tab IDs + let uuidCounter = 0; + vi.stubGlobal('crypto', { + randomUUID: () => `test-uuid-${++uuidCounter}`, + }); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + describe('openTab', () => { + describe('deduplication', () => { + it('should focus existing tab when opening same session', () => { + // Open initial session tab + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'First Session', + }); + + const initialTabId = store.getState().activeTabId; + expect(store.getState().openTabs).toHaveLength(1); + + // Open another tab + store.getState().openTab({ + type: 'session', + sessionId: 'session-2', + projectId: 'project-1', + label: 'Second Session', + }); + + expect(store.getState().openTabs).toHaveLength(2); + expect(store.getState().activeTabId).not.toBe(initialTabId); + + // Try to open session-1 again - should deduplicate + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'First Session Again', + }); + + expect(store.getState().openTabs).toHaveLength(2); + expect(store.getState().activeTabId).toBe(initialTabId); + }); + + it('should bypass deduplication when forceNewTab is true', () => { + // Open initial session tab + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'First Session', + }); + + const initialTabId = store.getState().activeTabId; + expect(store.getState().openTabs).toHaveLength(1); + + // Open same session with forceNewTab + store.getState().openTab( + { + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'First Session (New Tab)', + }, + { forceNewTab: true } + ); + + // Should have 2 tabs now, both for the same session + expect(store.getState().openTabs).toHaveLength(2); + expect(store.getState().activeTabId).not.toBe(initialTabId); + + // Both tabs should have the same sessionId + const sessionTabs = store.getState().openTabs.filter((t) => t.sessionId === 'session-1'); + expect(sessionTabs).toHaveLength(2); + }); + + it('should not deduplicate dashboard tabs', () => { + store.getState().openDashboard(); + store.getState().openDashboard(); + + expect(store.getState().openTabs).toHaveLength(2); + expect(store.getState().openTabs.filter((t) => t.type === 'dashboard')).toHaveLength(2); + }); + }); + + describe('dashboard replacement', () => { + it('should replace active dashboard tab when opening session', () => { + store.getState().openDashboard(); + const dashboardTabId = store.getState().activeTabId; + + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + + expect(store.getState().openTabs).toHaveLength(1); + // Tab should keep same ID (position preserved) + expect(store.getState().activeTabId).toBe(dashboardTabId); + // But now it's a session tab + expect(store.getState().openTabs[0].type).toBe('session'); + expect(store.getState().openTabs[0].sessionId).toBe('session-1'); + }); + }); + + describe('label truncation', () => { + it('should truncate labels longer than 50 characters', () => { + const longLabel = 'A'.repeat(60); + + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: longLabel, + }); + + const tab = store.getState().openTabs[0]; + expect(tab.label).toHaveLength(50); + expect(tab.label.endsWith('…')).toBe(true); + }); + }); + }); + + describe('closeTab', () => { + it('should focus adjacent tab when closing active tab', () => { + // Open 3 tabs + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Tab 1', + }); + + store.getState().openTab({ + type: 'session', + sessionId: 'session-2', + projectId: 'project-1', + label: 'Tab 2', + }); + const tab2Id = store.getState().activeTabId; + + store.getState().openTab({ + type: 'session', + sessionId: 'session-3', + projectId: 'project-1', + label: 'Tab 3', + }); + const tab3Id = store.getState().activeTabId; + + // Close tab 3 (active tab) + store.getState().closeTab(tab3Id!); + + // Should focus tab 2 (previous tab) + expect(store.getState().openTabs).toHaveLength(2); + expect(store.getState().activeTabId).toBe(tab2Id); + }); + + it('should reset state when all tabs closed', () => { + // Setup some state + store.setState({ + selectedProjectId: 'project-1', + selectedSessionId: 'session-1', + }); + + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Tab 1', + }); + const tabId = store.getState().activeTabId; + + store.getState().closeTab(tabId!); + + expect(store.getState().openTabs).toHaveLength(0); + expect(store.getState().activeTabId).toBeNull(); + expect(store.getState().selectedProjectId).toBeNull(); + expect(store.getState().selectedSessionId).toBeNull(); + }); + }); + + describe('setActiveTab', () => { + it('should update activeTabId', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tab1Id = store.getState().activeTabId; + + store.getState().openTab({ + type: 'session', + sessionId: 'session-2', + projectId: 'project-1', + label: 'Session 2', + }); + + // Switch back to first tab + store.getState().setActiveTab(tab1Id!); + + expect(store.getState().activeTabId).toBe(tab1Id); + }); + + it('should preserve sidebar state for non-session tabs', () => { + // Setup initial state with projects data so setActiveTab can find the project + store.setState({ + selectedProjectId: 'project-1', + selectedSessionId: 'session-1', + projects: [ + { id: 'project-1', name: 'Project 1', path: '/path/1', sessions: ['session-1'] }, + { id: 'project-2', name: 'Project 2', path: '/path/2', sessions: ['session-2'] }, + ] as never[], + }); + + // Open session-2 tab first (this doesn't call setActiveTab, just sets activeTabId) + store.getState().openTab({ + type: 'session', + sessionId: 'session-2', + projectId: 'project-2', + label: 'Session 2', + }); + const sessionTabId = store.getState().activeTabId; + + // Manually call setActiveTab to sync sidebar state (simulating user click) + store.getState().setActiveTab(sessionTabId!); + expect(store.getState().selectedProjectId).toBe('project-2'); + + // Open dashboard tab + store.getState().openDashboard(); + const dashboardTabId = store.getState().activeTabId; + + // Switch to dashboard (should preserve sidebar state) + store.getState().setActiveTab(dashboardTabId!); + + expect(store.getState().activeTabId).toBe(dashboardTabId); + // Sidebar state should be preserved (not cleared) when switching to dashboard + expect(store.getState().selectedProjectId).toBe('project-2'); + }); + }); + + describe('saveTabScrollPosition', () => { + it('should save scroll position for a tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tabId = store.getState().activeTabId!; + + // Initially undefined + expect(store.getState().openTabs[0].savedScrollTop).toBeUndefined(); + + // Save scroll position + store.getState().saveTabScrollPosition(tabId, 500); + + expect(store.getState().openTabs[0].savedScrollTop).toBe(500); + }); + + it('should only update the specified tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tab1Id = store.getState().activeTabId!; + + store.getState().openTab({ + type: 'session', + sessionId: 'session-2', + projectId: 'project-1', + label: 'Session 2', + }); + + // Save scroll position for tab 1 + store.getState().saveTabScrollPosition(tab1Id, 300); + + // Tab 1 should have scroll position, tab 2 should not + const tab1 = store.getState().openTabs.find((t) => t.id === tab1Id); + const tab2 = store.getState().openTabs.find((t) => t.id !== tab1Id); + + expect(tab1?.savedScrollTop).toBe(300); + expect(tab2?.savedScrollTop).toBeUndefined(); + }); + }); + + describe('setTabContextPanelVisible', () => { + it('should set context panel visibility for a tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tabId = store.getState().activeTabId!; + + // Initially undefined + expect(store.getState().openTabs[0].showContextPanel).toBeUndefined(); + + // Set to true + store.getState().setTabContextPanelVisible(tabId, true); + expect(store.getState().openTabs[0].showContextPanel).toBe(true); + + // Set to false + store.getState().setTabContextPanelVisible(tabId, false); + expect(store.getState().openTabs[0].showContextPanel).toBe(false); + }); + + it('should only update the specified tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tab1Id = store.getState().activeTabId!; + + store.getState().openTab({ + type: 'session', + sessionId: 'session-2', + projectId: 'project-1', + label: 'Session 2', + }); + + // Set context panel visible for tab 1 + store.getState().setTabContextPanelVisible(tab1Id, true); + + // Tab 1 should have context panel visible, tab 2 should not + const tab1 = store.getState().openTabs.find((t) => t.id === tab1Id); + const tab2 = store.getState().openTabs.find((t) => t.id !== tab1Id); + + expect(tab1?.showContextPanel).toBe(true); + expect(tab2?.showContextPanel).toBeUndefined(); + }); + }); + + describe('enqueueTabNavigation', () => { + it('should set pendingNavigation on the tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + + const tabId = store.getState().activeTabId!; + const request: TabNavigationRequest = { + id: 'nav-1', + kind: 'error', + source: 'notification', + highlight: 'red', + payload: { + errorId: 'error-1', + errorTimestamp: 12345, + toolUseId: 'tool-1', + lineNumber: 42, + }, + }; + + store.getState().enqueueTabNavigation(tabId, request); + + const tab = store.getState().openTabs[0]; + expect(tab.pendingNavigation).toEqual(request); + }); + + it('should replace existing pendingNavigation with new request', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + + const tabId = store.getState().activeTabId!; + const request1: TabNavigationRequest = { + id: 'nav-1', + kind: 'error', + source: 'notification', + highlight: 'red', + payload: { errorId: 'e1', errorTimestamp: 100 }, + }; + const request2: TabNavigationRequest = { + id: 'nav-2', + kind: 'error', + source: 'notification', + highlight: 'red', + payload: { errorId: 'e2', errorTimestamp: 200 }, + }; + + store.getState().enqueueTabNavigation(tabId, request1); + store.getState().enqueueTabNavigation(tabId, request2); + + const tab = store.getState().openTabs[0]; + expect(tab.pendingNavigation?.id).toBe('nav-2'); + }); + + it('should only update the specified tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tab1Id = store.getState().activeTabId!; + + store.getState().openTab({ + type: 'session', + sessionId: 'session-2', + projectId: 'project-1', + label: 'Session 2', + }); + + const request: TabNavigationRequest = { + id: 'nav-1', + kind: 'search', + source: 'commandPalette', + highlight: 'yellow', + payload: { query: 'test', messageTimestamp: 1234, matchedText: 'match' }, + }; + + store.getState().enqueueTabNavigation(tab1Id, request); + + const tab1 = store.getState().openTabs.find((t) => t.id === tab1Id); + const tab2 = store.getState().openTabs.find((t) => t.id !== tab1Id); + expect(tab1?.pendingNavigation).toEqual(request); + expect(tab2?.pendingNavigation).toBeUndefined(); + }); + }); + + describe('consumeTabNavigation', () => { + it('should clear pendingNavigation and set lastConsumedNavigationId', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + + const tabId = store.getState().activeTabId!; + const request: TabNavigationRequest = { + id: 'nav-1', + kind: 'error', + source: 'notification', + highlight: 'red', + payload: { errorId: 'error-1', errorTimestamp: 12345 }, + }; + + store.getState().enqueueTabNavigation(tabId, request); + expect(store.getState().openTabs[0].pendingNavigation).toBeDefined(); + + store.getState().consumeTabNavigation(tabId, 'nav-1'); + + const tab = store.getState().openTabs[0]; + expect(tab.pendingNavigation).toBeUndefined(); + expect(tab.lastConsumedNavigationId).toBe('nav-1'); + }); + + it('should not clear if requestId does not match', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + + const tabId = store.getState().activeTabId!; + const request: TabNavigationRequest = { + id: 'nav-1', + kind: 'error', + source: 'notification', + highlight: 'red', + payload: { errorId: 'error-1', errorTimestamp: 12345 }, + }; + + store.getState().enqueueTabNavigation(tabId, request); + store.getState().consumeTabNavigation(tabId, 'wrong-id'); + + // Should still have pendingNavigation since IDs don't match + const tab = store.getState().openTabs[0]; + expect(tab.pendingNavigation).toEqual(request); + }); + }); + + describe('isSessionOpen', () => { + it('should return true if session is open in any tab', () => { + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + + expect(store.getState().isSessionOpen('session-1')).toBe(true); + expect(store.getState().isSessionOpen('session-2')).toBe(false); + }); + }); + + describe('navigateToSession', () => { + it('should open new tab if session not already open', () => { + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-1' }, + chunks: [], + } as never); + + store.getState().navigateToSession('project-1', 'session-1', false); + + expect(store.getState().openTabs).toHaveLength(1); + expect(store.getState().openTabs[0].sessionId).toBe('session-1'); + }); + + it('should focus existing tab with search navigation request', () => { + // First open the session + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const existingTabId = store.getState().activeTabId; + + // Open another tab to switch away + store.getState().openDashboard(); + + // Navigate to same session with search context + store.getState().navigateToSession('project-1', 'session-1', true, { + query: 'test query', + messageTimestamp: 1234567890, + matchedText: 'matched text', + }); + + // Should focus existing tab + expect(store.getState().activeTabId).toBe(existingTabId); + // Should have a pending search navigation request + const tab = store.getState().openTabs.find((t) => t.id === existingTabId); + expect(tab?.pendingNavigation?.kind).toBe('search'); + expect(tab?.pendingNavigation?.payload).toEqual({ + query: 'test query', + messageTimestamp: 1234567890, + matchedText: 'matched text', + }); + }); + + it('should enqueue search navigation on new tab', () => { + mockAPI.getSessionDetail.mockResolvedValue({ + session: { id: 'session-1' }, + chunks: [], + } as never); + + store.getState().navigateToSession('project-1', 'session-1', false, { + query: 'find me', + messageTimestamp: 9999, + matchedText: 'found', + }); + + const tab = store.getState().openTabs[0]; + expect(tab.pendingNavigation?.kind).toBe('search'); + expect(tab.pendingNavigation?.source).toBe('commandPalette'); + expect(tab.pendingNavigation?.highlight).toBe('yellow'); + }); + }); +}); diff --git a/test/renderer/store/tabUISlice.test.ts b/test/renderer/store/tabUISlice.test.ts new file mode 100644 index 00000000..32c90a42 --- /dev/null +++ b/test/renderer/store/tabUISlice.test.ts @@ -0,0 +1,375 @@ +/** + * TabUI slice unit tests. + * Tests per-tab UI state isolation (expansion states, context panel, scroll position). + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { installMockElectronAPI, type MockElectronAPI } from '../../mocks/electronAPI'; + +import { createTestStore, type TestStore } from './storeTestUtils'; + +describe('tabUISlice', () => { + let store: TestStore; + let _mockAPI: MockElectronAPI; + + beforeEach(() => { + vi.useFakeTimers(); + _mockAPI = installMockElectronAPI(); + store = createTestStore(); + + // Mock crypto.randomUUID for predictable tab IDs + let uuidCounter = 0; + vi.stubGlobal('crypto', { + randomUUID: () => `test-uuid-${++uuidCounter}`, + }); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + describe('initTabUIState', () => { + it('should initialize UI state for a new tab', () => { + expect(store.getState().tabUIStates.size).toBe(0); + + store.getState().initTabUIState('tab-1'); + + expect(store.getState().tabUIStates.size).toBe(1); + expect(store.getState().tabUIStates.has('tab-1')).toBe(true); + + const tabState = store.getState().tabUIStates.get('tab-1'); + expect(tabState?.expandedAIGroupIds.size).toBe(0); + expect(tabState?.expandedDisplayItemIds.size).toBe(0); + expect(tabState?.expandedSubagentTraceIds.size).toBe(0); + expect(tabState?.showContextPanel).toBe(false); + expect(tabState?.savedScrollTop).toBeUndefined(); + }); + + it('should not reinitialize existing tab state', () => { + store.getState().initTabUIState('tab-1'); + store.getState().toggleAIGroupExpansionForTab('tab-1', 'group-1'); + + // Try to reinitialize + store.getState().initTabUIState('tab-1'); + + // Should still have the expanded group + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(true); + }); + }); + + describe('cleanupTabUIState', () => { + it('should remove UI state for a tab', () => { + store.getState().initTabUIState('tab-1'); + store.getState().initTabUIState('tab-2'); + expect(store.getState().tabUIStates.size).toBe(2); + + store.getState().cleanupTabUIState('tab-1'); + + expect(store.getState().tabUIStates.size).toBe(1); + expect(store.getState().tabUIStates.has('tab-1')).toBe(false); + expect(store.getState().tabUIStates.has('tab-2')).toBe(true); + }); + + it('should do nothing if tab does not exist', () => { + store.getState().initTabUIState('tab-1'); + + store.getState().cleanupTabUIState('nonexistent'); + + expect(store.getState().tabUIStates.size).toBe(1); + }); + }); + + describe('AI Group expansion - per-tab isolation', () => { + it('should toggle AI group expansion for specific tab', () => { + store.getState().initTabUIState('tab-1'); + + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(false); + + store.getState().toggleAIGroupExpansionForTab('tab-1', 'group-1'); + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(true); + + store.getState().toggleAIGroupExpansionForTab('tab-1', 'group-1'); + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(false); + }); + + it('should isolate AI group expansion between tabs', () => { + store.getState().initTabUIState('tab-1'); + store.getState().initTabUIState('tab-2'); + + // Expand group-1 in tab-1 only + store.getState().toggleAIGroupExpansionForTab('tab-1', 'group-1'); + + // tab-1 should have it expanded, tab-2 should not + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(true); + expect(store.getState().isAIGroupExpandedForTab('tab-2', 'group-1')).toBe(false); + + // Expand different group in tab-2 + store.getState().toggleAIGroupExpansionForTab('tab-2', 'group-2'); + + // Each tab has its own expansion state + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(true); + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-2')).toBe(false); + expect(store.getState().isAIGroupExpandedForTab('tab-2', 'group-1')).toBe(false); + expect(store.getState().isAIGroupExpandedForTab('tab-2', 'group-2')).toBe(true); + }); + + it('should expand AI group programmatically', () => { + store.getState().initTabUIState('tab-1'); + + store.getState().expandAIGroupForTab('tab-1', 'group-1'); + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(true); + + // Calling expand again should not change state (idempotent) + store.getState().expandAIGroupForTab('tab-1', 'group-1'); + expect(store.getState().isAIGroupExpandedForTab('tab-1', 'group-1')).toBe(true); + }); + }); + + describe('Display item expansion - per-tab isolation', () => { + it('should toggle display item expansion within AI group', () => { + store.getState().initTabUIState('tab-1'); + + const items = store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1'); + expect(items.size).toBe(0); + + store.getState().toggleDisplayItemExpansionForTab('tab-1', 'group-1', 'item-1'); + + const updatedItems = store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1'); + expect(updatedItems.has('item-1')).toBe(true); + + store.getState().toggleDisplayItemExpansionForTab('tab-1', 'group-1', 'item-1'); + + const finalItems = store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1'); + expect(finalItems.has('item-1')).toBe(false); + }); + + it('should isolate display item expansion between tabs', () => { + store.getState().initTabUIState('tab-1'); + store.getState().initTabUIState('tab-2'); + + // Expand item in tab-1 + store.getState().toggleDisplayItemExpansionForTab('tab-1', 'group-1', 'item-1'); + + // tab-1 should have it, tab-2 should not + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1').has('item-1') + ).toBe(true); + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-2', 'group-1').has('item-1') + ).toBe(false); + }); + + it('should isolate display items by AI group within same tab', () => { + store.getState().initTabUIState('tab-1'); + + store.getState().toggleDisplayItemExpansionForTab('tab-1', 'group-1', 'item-1'); + store.getState().toggleDisplayItemExpansionForTab('tab-1', 'group-2', 'item-2'); + + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1').has('item-1') + ).toBe(true); + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1').has('item-2') + ).toBe(false); + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-2').has('item-1') + ).toBe(false); + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-2').has('item-2') + ).toBe(true); + }); + + it('should expand display item programmatically', () => { + store.getState().initTabUIState('tab-1'); + + store.getState().expandDisplayItemForTab('tab-1', 'group-1', 'item-1'); + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1').has('item-1') + ).toBe(true); + + // Calling expand again should not change state (idempotent) + store.getState().expandDisplayItemForTab('tab-1', 'group-1', 'item-1'); + expect( + store.getState().getExpandedDisplayItemIdsForTab('tab-1', 'group-1').has('item-1') + ).toBe(true); + }); + }); + + describe('Subagent trace expansion - per-tab isolation', () => { + it('should toggle subagent trace expansion', () => { + store.getState().initTabUIState('tab-1'); + + expect(store.getState().isSubagentTraceExpandedForTab('tab-1', 'subagent-1')).toBe(false); + + store.getState().toggleSubagentTraceExpansionForTab('tab-1', 'subagent-1'); + expect(store.getState().isSubagentTraceExpandedForTab('tab-1', 'subagent-1')).toBe(true); + + store.getState().toggleSubagentTraceExpansionForTab('tab-1', 'subagent-1'); + expect(store.getState().isSubagentTraceExpandedForTab('tab-1', 'subagent-1')).toBe(false); + }); + + it('should isolate subagent trace expansion between tabs', () => { + store.getState().initTabUIState('tab-1'); + store.getState().initTabUIState('tab-2'); + + store.getState().toggleSubagentTraceExpansionForTab('tab-1', 'subagent-1'); + + expect(store.getState().isSubagentTraceExpandedForTab('tab-1', 'subagent-1')).toBe(true); + expect(store.getState().isSubagentTraceExpandedForTab('tab-2', 'subagent-1')).toBe(false); + }); + }); + + describe('Context panel visibility - per-tab isolation', () => { + it('should set context panel visibility', () => { + store.getState().initTabUIState('tab-1'); + + expect(store.getState().isContextPanelVisibleForTab('tab-1')).toBe(false); + + store.getState().setContextPanelVisibleForTab('tab-1', true); + expect(store.getState().isContextPanelVisibleForTab('tab-1')).toBe(true); + + store.getState().setContextPanelVisibleForTab('tab-1', false); + expect(store.getState().isContextPanelVisibleForTab('tab-1')).toBe(false); + }); + + it('should isolate context panel visibility between tabs', () => { + store.getState().initTabUIState('tab-1'); + store.getState().initTabUIState('tab-2'); + + store.getState().setContextPanelVisibleForTab('tab-1', true); + + expect(store.getState().isContextPanelVisibleForTab('tab-1')).toBe(true); + expect(store.getState().isContextPanelVisibleForTab('tab-2')).toBe(false); + }); + }); + + describe('Scroll position - per-tab isolation', () => { + it('should save and retrieve scroll position', () => { + store.getState().initTabUIState('tab-1'); + + expect(store.getState().getScrollPositionForTab('tab-1')).toBeUndefined(); + + store.getState().saveScrollPositionForTab('tab-1', 500); + expect(store.getState().getScrollPositionForTab('tab-1')).toBe(500); + + store.getState().saveScrollPositionForTab('tab-1', 1000); + expect(store.getState().getScrollPositionForTab('tab-1')).toBe(1000); + }); + + it('should isolate scroll positions between tabs', () => { + store.getState().initTabUIState('tab-1'); + store.getState().initTabUIState('tab-2'); + + store.getState().saveScrollPositionForTab('tab-1', 100); + store.getState().saveScrollPositionForTab('tab-2', 200); + + expect(store.getState().getScrollPositionForTab('tab-1')).toBe(100); + expect(store.getState().getScrollPositionForTab('tab-2')).toBe(200); + }); + }); + + describe('Integration with tab lifecycle', () => { + it('should handle full tab lifecycle', () => { + // Simulate opening a tab + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tabId = store.getState().activeTabId!; + + // Initialize UI state + store.getState().initTabUIState(tabId); + + // Set some UI state + store.getState().toggleAIGroupExpansionForTab(tabId, 'group-1'); + store.getState().setContextPanelVisibleForTab(tabId, true); + store.getState().saveScrollPositionForTab(tabId, 300); + + // Verify state + expect(store.getState().isAIGroupExpandedForTab(tabId, 'group-1')).toBe(true); + expect(store.getState().isContextPanelVisibleForTab(tabId)).toBe(true); + expect(store.getState().getScrollPositionForTab(tabId)).toBe(300); + + // Close tab (should cleanup UI state) + store.getState().closeTab(tabId); + + // UI state should be cleaned up + expect(store.getState().tabUIStates.has(tabId)).toBe(false); + }); + + it('should maintain separate state for two tabs with same session (forceNewTab)', () => { + // Open first tab + store.getState().openTab({ + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1', + }); + const tab1Id = store.getState().activeTabId!; + store.getState().initTabUIState(tab1Id); + + // Open second tab with same session (forceNewTab) + store.getState().openTab( + { + type: 'session', + sessionId: 'session-1', + projectId: 'project-1', + label: 'Session 1 (Copy)', + }, + { forceNewTab: true } + ); + const tab2Id = store.getState().activeTabId!; + store.getState().initTabUIState(tab2Id); + + // Both tabs should have same session + expect(store.getState().openTabs.filter((t) => t.sessionId === 'session-1')).toHaveLength(2); + + // Set different states for each tab + store.getState().toggleAIGroupExpansionForTab(tab1Id, 'group-1'); + store.getState().toggleAIGroupExpansionForTab(tab2Id, 'group-2'); + store.getState().setContextPanelVisibleForTab(tab1Id, true); + store.getState().saveScrollPositionForTab(tab1Id, 100); + store.getState().saveScrollPositionForTab(tab2Id, 500); + + // Verify states are isolated + expect(store.getState().isAIGroupExpandedForTab(tab1Id, 'group-1')).toBe(true); + expect(store.getState().isAIGroupExpandedForTab(tab1Id, 'group-2')).toBe(false); + expect(store.getState().isAIGroupExpandedForTab(tab2Id, 'group-1')).toBe(false); + expect(store.getState().isAIGroupExpandedForTab(tab2Id, 'group-2')).toBe(true); + + expect(store.getState().isContextPanelVisibleForTab(tab1Id)).toBe(true); + expect(store.getState().isContextPanelVisibleForTab(tab2Id)).toBe(false); + + expect(store.getState().getScrollPositionForTab(tab1Id)).toBe(100); + expect(store.getState().getScrollPositionForTab(tab2Id)).toBe(500); + }); + }); + + describe('Edge cases', () => { + it('should return false/empty for uninitialized tab', () => { + // No initialization + expect(store.getState().isAIGroupExpandedForTab('nonexistent', 'group-1')).toBe(false); + expect(store.getState().getExpandedDisplayItemIdsForTab('nonexistent', 'group-1').size).toBe( + 0 + ); + expect(store.getState().isSubagentTraceExpandedForTab('nonexistent', 'subagent-1')).toBe( + false + ); + expect(store.getState().isContextPanelVisibleForTab('nonexistent')).toBe(false); + expect(store.getState().getScrollPositionForTab('nonexistent')).toBeUndefined(); + }); + + it('should auto-create tab state when toggling (lazy initialization)', () => { + // Toggle without explicit init + store.getState().toggleAIGroupExpansionForTab('lazy-tab', 'group-1'); + + // Should have created the state + expect(store.getState().tabUIStates.has('lazy-tab')).toBe(true); + expect(store.getState().isAIGroupExpandedForTab('lazy-tab', 'group-1')).toBe(true); + }); + }); +}); diff --git a/test/renderer/utils/claudeMdTracker.test.ts b/test/renderer/utils/claudeMdTracker.test.ts new file mode 100644 index 00000000..6e23567e --- /dev/null +++ b/test/renderer/utils/claudeMdTracker.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it } from 'vitest'; + +import { + detectClaudeMdFromFilePath, + getDirectory, + getParentDirectory, +} from '@renderer/utils/claudeMdTracker'; + +describe('claudeMdTracker path helpers', () => { + describe('getDirectory', () => { + it('returns directory from Unix path', () => { + expect(getDirectory('/a/b/file.ts')).toBe('/a/b'); + }); + + it('returns directory from Windows path', () => { + expect(getDirectory('C:\\a\\b\\file.ts')).toBe('C:\\a\\b'); + }); + + it('returns directory from mixed-separator path', () => { + expect(getDirectory('C:\\a/b\\file.ts')).toBe('C:\\a/b'); + }); + + it('returns empty for bare filename', () => { + expect(getDirectory('file.ts')).toBe(''); + }); + + it('returns root for root-level file', () => { + expect(getDirectory('/file.ts')).toBe(''); + }); + }); + + describe('getParentDirectory', () => { + it('returns parent from Unix path', () => { + expect(getParentDirectory('/a/b/c')).toBe('/a/b'); + }); + + it('returns parent from Windows path', () => { + expect(getParentDirectory('C:\\a\\b\\c')).toBe('C:\\a\\b'); + }); + + it('returns null at root', () => { + expect(getParentDirectory('/a')).toBeNull(); + }); + + it('returns null for single segment', () => { + expect(getParentDirectory('a')).toBeNull(); + }); + + it('returns parent from deeply nested path', () => { + expect(getParentDirectory('/a/b/c/d/e')).toBe('/a/b/c/d'); + }); + }); + + describe('detectClaudeMdFromFilePath', () => { + it('detects CLAUDE.md files walking up Unix paths', () => { + const result = detectClaudeMdFromFilePath('/repo/src/lib/file.ts', '/repo'); + expect(result).toContain('/repo/src/lib/CLAUDE.md'); + expect(result).toContain('/repo/src/CLAUDE.md'); + expect(result).toContain('/repo/CLAUDE.md'); + expect(result).toHaveLength(3); + }); + + it('detects CLAUDE.md files walking up Windows paths', () => { + const result = detectClaudeMdFromFilePath('C:\\repo\\src\\file.ts', 'C:\\repo'); + expect(result).toContain('C:\\repo\\src\\CLAUDE.md'); + expect(result).toContain('C:\\repo\\CLAUDE.md'); + expect(result).toHaveLength(2); + }); + + it('uses correct separator for generated paths', () => { + const unixResult = detectClaudeMdFromFilePath('/repo/src/file.ts', '/repo'); + for (const p of unixResult) { + expect(p).not.toContain('\\'); + } + + const winResult = detectClaudeMdFromFilePath('C:\\repo\\src\\file.ts', 'C:\\repo'); + for (const p of winResult) { + expect(p).toContain('\\'); + expect(p).not.toContain('/'); + } + }); + + it('returns empty array when file is at project root', () => { + const result = detectClaudeMdFromFilePath('/repo/file.ts', '/repo'); + expect(result).toEqual(['/repo/CLAUDE.md']); + }); + + it('stops at project root boundary', () => { + const result = detectClaudeMdFromFilePath('/repo/src/file.ts', '/repo'); + // Should not go above /repo + const aboveRoot = result.some((p) => !p.startsWith('/repo')); + expect(aboveRoot).toBe(false); + }); + }); +}); diff --git a/test/renderer/utils/dateGrouping.test.ts b/test/renderer/utils/dateGrouping.test.ts new file mode 100644 index 00000000..381f0656 --- /dev/null +++ b/test/renderer/utils/dateGrouping.test.ts @@ -0,0 +1,164 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + getNonEmptyCategories, + groupSessionsByDate, +} from '../../../src/renderer/utils/dateGrouping'; +import type { Session } from '../../../src/renderer/types/data'; + +// Helper to create a session with a specific date +function createSession(id: string, createdAt: Date): Session { + return { + id, + createdAt: createdAt.toISOString(), + updatedAt: createdAt.toISOString(), + displayName: `Session ${id}`, + triggerCount: 1, + ongoing: false, + lastTriggerPreview: 'Test', + cwd: '/test', + todos: [], + totalTokens: 0, + }; +} + +describe('dateGrouping', () => { + beforeEach(() => { + // Mock current date to 2024-01-15 12:00:00 + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-01-15T12:00:00Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + describe('groupSessionsByDate', () => { + it('should group session from today', () => { + const today = new Date('2024-01-15T10:00:00Z'); + const sessions = [createSession('1', today)]; + + const result = groupSessionsByDate(sessions); + + expect(result.Today).toHaveLength(1); + expect(result.Yesterday).toHaveLength(0); + expect(result['Previous 7 Days']).toHaveLength(0); + expect(result.Older).toHaveLength(0); + }); + + it('should group session from yesterday', () => { + const yesterday = new Date('2024-01-14T10:00:00Z'); + const sessions = [createSession('1', yesterday)]; + + const result = groupSessionsByDate(sessions); + + expect(result.Today).toHaveLength(0); + expect(result.Yesterday).toHaveLength(1); + expect(result['Previous 7 Days']).toHaveLength(0); + expect(result.Older).toHaveLength(0); + }); + + it('should group session from 3 days ago to Previous 7 Days', () => { + const threeDaysAgo = new Date('2024-01-12T10:00:00Z'); + const sessions = [createSession('1', threeDaysAgo)]; + + const result = groupSessionsByDate(sessions); + + expect(result.Today).toHaveLength(0); + expect(result.Yesterday).toHaveLength(0); + expect(result['Previous 7 Days']).toHaveLength(1); + expect(result.Older).toHaveLength(0); + }); + + it('should group session from 10 days ago to Older', () => { + const tenDaysAgo = new Date('2024-01-05T10:00:00Z'); + const sessions = [createSession('1', tenDaysAgo)]; + + const result = groupSessionsByDate(sessions); + + expect(result.Today).toHaveLength(0); + expect(result.Yesterday).toHaveLength(0); + expect(result['Previous 7 Days']).toHaveLength(0); + expect(result.Older).toHaveLength(1); + }); + + it('should distribute multiple sessions to correct groups', () => { + const sessions = [ + createSession('1', new Date('2024-01-15T10:00:00Z')), // Today + createSession('2', new Date('2024-01-15T08:00:00Z')), // Today + createSession('3', new Date('2024-01-14T10:00:00Z')), // Yesterday + createSession('4', new Date('2024-01-12T10:00:00Z')), // Previous 7 Days + createSession('5', new Date('2024-01-01T10:00:00Z')), // Older + ]; + + const result = groupSessionsByDate(sessions); + + expect(result.Today).toHaveLength(2); + expect(result.Yesterday).toHaveLength(1); + expect(result['Previous 7 Days']).toHaveLength(1); + expect(result.Older).toHaveLength(1); + }); + + it('should handle empty sessions array', () => { + const result = groupSessionsByDate([]); + + expect(result.Today).toHaveLength(0); + expect(result.Yesterday).toHaveLength(0); + expect(result['Previous 7 Days']).toHaveLength(0); + expect(result.Older).toHaveLength(0); + }); + + it('should maintain order within groups', () => { + const sessions = [ + createSession('first', new Date('2024-01-15T08:00:00Z')), + createSession('second', new Date('2024-01-15T10:00:00Z')), + createSession('third', new Date('2024-01-15T12:00:00Z')), + ]; + + const result = groupSessionsByDate(sessions); + + expect(result.Today.map((s) => s.id)).toEqual(['first', 'second', 'third']); + }); + }); + + describe('getNonEmptyCategories', () => { + it('should return only non-empty categories', () => { + const grouped = { + Today: [createSession('1', new Date())], + Yesterday: [], + 'Previous 7 Days': [createSession('2', new Date())], + Older: [], + }; + + const result = getNonEmptyCategories(grouped); + + expect(result).toEqual(['Today', 'Previous 7 Days']); + }); + + it('should return categories in display order', () => { + const grouped = { + Today: [createSession('1', new Date())], + Yesterday: [createSession('2', new Date())], + 'Previous 7 Days': [createSession('3', new Date())], + Older: [createSession('4', new Date())], + }; + + const result = getNonEmptyCategories(grouped); + + expect(result).toEqual(['Today', 'Yesterday', 'Previous 7 Days', 'Older']); + }); + + it('should return empty array when all categories are empty', () => { + const grouped = { + Today: [], + Yesterday: [], + 'Previous 7 Days': [], + Older: [], + }; + + const result = getNonEmptyCategories(grouped); + + expect(result).toEqual([]); + }); + }); +}); diff --git a/test/renderer/utils/formatters.test.ts b/test/renderer/utils/formatters.test.ts new file mode 100644 index 00000000..7a3b6ee3 --- /dev/null +++ b/test/renderer/utils/formatters.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest'; + +import { formatDuration, formatTokensCompact } from '../../../src/renderer/utils/formatters'; + +describe('formatters', () => { + describe('formatDuration', () => { + it('should format milliseconds', () => { + expect(formatDuration(500)).toBe('500ms'); + }); + + it('should format seconds with one decimal', () => { + expect(formatDuration(1500)).toBe('1.5s'); + }); + + it('should format whole seconds', () => { + expect(formatDuration(3000)).toBe('3.0s'); + }); + + it('should format minutes and seconds', () => { + expect(formatDuration(90000)).toBe('1m 30s'); + }); + + it('should format multiple minutes', () => { + expect(formatDuration(180000)).toBe('3m 0s'); + }); + + it('should round milliseconds', () => { + expect(formatDuration(499.7)).toBe('500ms'); + }); + + it('should handle zero', () => { + expect(formatDuration(0)).toBe('0ms'); + }); + + it('should handle exactly 1000ms', () => { + expect(formatDuration(1000)).toBe('1.0s'); + }); + + it('should handle exactly 60000ms', () => { + expect(formatDuration(60000)).toBe('1m 0s'); + }); + + it('should handle large values', () => { + expect(formatDuration(3661000)).toBe('61m 1s'); + }); + + it('should round remaining seconds', () => { + expect(formatDuration(61500)).toBe('1m 2s'); + }); + }); + + describe('formatTokensCompact', () => { + it('should format small numbers as-is', () => { + expect(formatTokensCompact(500)).toBe('500'); + }); + + it('should format thousands with k suffix', () => { + expect(formatTokensCompact(1500)).toBe('1.5k'); + }); + + it('should format exact thousands', () => { + expect(formatTokensCompact(1000)).toBe('1.0k'); + }); + + it('should format large thousands', () => { + expect(formatTokensCompact(50000)).toBe('50.0k'); + }); + + it('should format millions with M suffix', () => { + expect(formatTokensCompact(1500000)).toBe('1.5M'); + }); + + it('should format exact millions', () => { + expect(formatTokensCompact(1000000)).toBe('1.0M'); + }); + + it('should handle zero', () => { + expect(formatTokensCompact(0)).toBe('0'); + }); + + it('should handle just under thousand', () => { + expect(formatTokensCompact(999)).toBe('999'); + }); + }); +}); diff --git a/test/renderer/utils/pathUtils.test.ts b/test/renderer/utils/pathUtils.test.ts new file mode 100644 index 00000000..f2ba8628 --- /dev/null +++ b/test/renderer/utils/pathUtils.test.ts @@ -0,0 +1,137 @@ +import { describe, expect, it } from 'vitest'; + +import { + getBaseName, + getFirstSegment, + hasPathSeparator, + isRelativePath, + splitPathSegments, +} from '@renderer/utils/pathUtils'; + +describe('pathUtils', () => { + describe('getBaseName', () => { + it('extracts filename from Unix path', () => { + expect(getBaseName('/Users/name/project/file.ts')).toBe('file.ts'); + }); + + it('extracts filename from Windows path', () => { + expect(getBaseName('C:\\Users\\name\\project\\file.ts')).toBe('file.ts'); + }); + + it('extracts filename from mixed-separator path', () => { + expect(getBaseName('C:\\Users/name\\project/file.ts')).toBe('file.ts'); + }); + + it('returns bare filename as-is', () => { + expect(getBaseName('file.ts')).toBe('file.ts'); + }); + + it('returns empty for trailing separator', () => { + expect(getBaseName('/path/to/dir/')).toBe(''); + }); + + it('returns empty for empty string', () => { + expect(getBaseName('')).toBe(''); + }); + }); + + describe('getFirstSegment', () => { + it('returns first segment from Unix path', () => { + expect(getFirstSegment('src/components/App.tsx')).toBe('src'); + }); + + it('returns first segment from Windows path', () => { + expect(getFirstSegment('src\\components\\App.tsx')).toBe('src'); + }); + + it('returns drive letter from Windows absolute path', () => { + expect(getFirstSegment('C:\\Users\\name')).toBe('C:'); + }); + + it('skips leading separator in absolute path', () => { + expect(getFirstSegment('/Users/name')).toBe('Users'); + }); + + it('returns bare filename', () => { + expect(getFirstSegment('file.ts')).toBe('file.ts'); + }); + + it('returns empty for empty string', () => { + expect(getFirstSegment('')).toBe(''); + }); + }); + + describe('splitPathSegments', () => { + it('splits Unix path', () => { + expect(splitPathSegments('/a/b/c')).toEqual(['a', 'b', 'c']); + }); + + it('splits Windows path', () => { + expect(splitPathSegments('C:\\a\\b\\c')).toEqual(['C:', 'a', 'b', 'c']); + }); + + it('splits mixed-separator path', () => { + expect(splitPathSegments('a/b\\c')).toEqual(['a', 'b', 'c']); + }); + + it('filters empty segments', () => { + expect(splitPathSegments('//a///b//')).toEqual(['a', 'b']); + }); + + it('returns single segment for bare name', () => { + expect(splitPathSegments('file.ts')).toEqual(['file.ts']); + }); + }); + + describe('hasPathSeparator', () => { + it('detects forward slash', () => { + expect(hasPathSeparator('a/b')).toBe(true); + }); + + it('detects backslash', () => { + expect(hasPathSeparator('a\\b')).toBe(true); + }); + + it('returns false for bare name', () => { + expect(hasPathSeparator('file.ts')).toBe(false); + }); + + it('returns false for empty string', () => { + expect(hasPathSeparator('')).toBe(false); + }); + }); + + describe('isRelativePath', () => { + it('detects ./ prefix', () => { + expect(isRelativePath('./src')).toBe(true); + }); + + it('detects .\\ prefix', () => { + expect(isRelativePath('.\\src')).toBe(true); + }); + + it('detects ../ prefix', () => { + expect(isRelativePath('../lib')).toBe(true); + }); + + it('detects ..\\ prefix', () => { + expect(isRelativePath('..\\lib')).toBe(true); + }); + + it('rejects absolute Unix path', () => { + expect(isRelativePath('/abs')).toBe(false); + }); + + it('rejects absolute Windows path', () => { + expect(isRelativePath('C:\\abs')).toBe(false); + }); + + it('rejects bare name', () => { + expect(isRelativePath('name')).toBe(false); + }); + + it('rejects single dot without separator', () => { + expect(isRelativePath('.hidden')).toBe(false); + }); + }); +}); diff --git a/test/setup.ts b/test/setup.ts new file mode 100644 index 00000000..aca6a909 --- /dev/null +++ b/test/setup.ts @@ -0,0 +1,50 @@ +/** + * Vitest setup file. + * Runs before each test file. + */ + +import { afterEach, beforeEach, expect, vi } from 'vitest'; + +// Mock process.env for tests that need home directory +vi.stubGlobal('process', { + ...process, + env: { + ...process.env, + HOME: '/home/testuser', + }, +}); + +let errorSpy: ReturnType; +let warnSpy: ReturnType; + +function formatConsoleCall(args: unknown[]): string { + return args + .map((arg) => { + if (arg instanceof Error) { + return arg.message; + } + return String(arg); + }) + .join(' '); +} + +beforeEach(() => { + errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); +}); + +afterEach(() => { + const unexpectedErrors = errorSpy.mock.calls.map(formatConsoleCall); + const unexpectedWarnings = warnSpy.mock.calls.map(formatConsoleCall); + + errorSpy.mockRestore(); + warnSpy.mockRestore(); + + expect(unexpectedErrors, `Unexpected console.error calls:\n${unexpectedErrors.join('\n')}`).toEqual( + [] + ); + expect( + unexpectedWarnings, + `Unexpected console.warn calls:\n${unexpectedWarnings.join('\n')}` + ).toEqual([]); +}); diff --git a/test/shared/utils/markdownSearchRendererAlignment.test.ts b/test/shared/utils/markdownSearchRendererAlignment.test.ts new file mode 100644 index 00000000..8565565e --- /dev/null +++ b/test/shared/utils/markdownSearchRendererAlignment.test.ts @@ -0,0 +1,56 @@ +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { describe, expect, it } from 'vitest'; + +import { createMarkdownComponents } from '../../../src/renderer/components/chat/markdownComponents'; +import { createSearchContext } from '../../../src/renderer/components/chat/searchHighlightUtils'; +import { findMarkdownSearchMatches } from '../../../src/shared/utils/markdownTextSearch'; + +function extractRenderedMatchIndexes(markdown: string, query: string): number[] { + const parsedMatches = findMarkdownSearchMatches(markdown, query); + const searchMatches = parsedMatches.map((m, i) => ({ + itemId: 'item-1', + itemType: 'user' as const, + matchIndexInItem: m.matchIndexInItem, + globalIndex: i, + })); + const searchCtx = createSearchContext(query, 'item-1', searchMatches, 0); + const components = createMarkdownComponents(searchCtx); + + const html = renderToStaticMarkup( + React.createElement( + ReactMarkdown, + { remarkPlugins: [remarkGfm], components }, + markdown + ) + ); + + return Array.from(html.matchAll(/data-search-match-index="(\d+)"/g), (m) => Number(m[1])); +} + +describe('markdown search renderer alignment', () => { + const query = 'the'; + const cases = [ + 'the plain the', + 'Use `the` and then **the**.', + '- the one\n- `the` two\n- **the** three', + '| col | val |\n| - | - |\n| the | then |\n| other | the |', + '```ts\nconst theValue = "the";\n```\nthen the', + 'line one \nline two with the', + 'the and the', + '/cmd the and the', + '[the docs](https://example.com/the) and https://example.com/the', + 'This is ~~the~~ test with the', + ]; + + it.each(cases)('matches parser indexes for: %s', (markdown) => { + const parsedIndexes = findMarkdownSearchMatches(markdown, query).map( + (m) => m.matchIndexInItem + ); + const renderedIndexes = extractRenderedMatchIndexes(markdown, query); + expect(renderedIndexes).toEqual(parsedIndexes); + }); +}); + diff --git a/test/shared/utils/markdownTextSearch.test.ts b/test/shared/utils/markdownTextSearch.test.ts new file mode 100644 index 00000000..47a33d6b --- /dev/null +++ b/test/shared/utils/markdownTextSearch.test.ts @@ -0,0 +1,304 @@ +import { describe, expect, it } from 'vitest'; + +import { + collectTextSegments, + countMarkdownSearchMatches, + extractMarkdownPlainText, + findMarkdownSearchMatches, +} from '../../../src/shared/utils/markdownTextSearch'; + +describe('markdownTextSearch', () => { + // --------------------------------------------------------------------------- + // collectTextSegments (now takes markdown string, uses HAST internally) + // --------------------------------------------------------------------------- + + describe('collectTextSegments', () => { + it('extracts plain text from a paragraph', () => { + const segments = collectTextSegments('Hello world'); + expect(segments).toEqual(['Hello world']); + }); + + it('extracts text from bold/italic nodes', () => { + const segments = collectTextSegments('Hello **bold** and *italic*'); + expect(segments).toEqual(['Hello ', 'bold', ' and ', 'italic']); + }); + + it('keeps code block content as a single segment with trailing newline', () => { + // HAST adds trailing \n to code block text — matches what ReactMarkdown + // passes to its component as children + const segments = collectTextSegments('```js\nconst x = 1;\nconst y = 2;\n```'); + expect(segments).toEqual(['const x = 1;\nconst y = 2;\n']); + }); + + it('extracts inline code text', () => { + const segments = collectTextSegments('Use `findMatches` here'); + expect(segments).toEqual(['Use ', 'findMatches', ' here']); + }); + + it('extracts link text but not URL', () => { + const segments = collectTextSegments('[docs](https://example.com)'); + expect(segments).toEqual(['docs']); + }); + + it('does NOT include image alt text', () => { + const segments = collectTextSegments('![screenshot](./img.png)'); + expect(segments).toEqual([]); + }); + + it('extracts list item text', () => { + const segments = collectTextSegments('- item one\n- item two'); + expect(segments).toContain('item one'); + expect(segments).toContain('item two'); + }); + + it('extracts heading text', () => { + const segments = collectTextSegments('## Important Section'); + expect(segments).toContain('Important Section'); + }); + + it('extracts table cell text', () => { + const segments = collectTextSegments( + '| Header | Value |\n|--------|-------|\n| Cell | Data |' + ); + expect(segments).toContain('Header'); + expect(segments).toContain('Cell'); + expect(segments).toContain('Data'); + }); + + it('extracts blockquote text', () => { + const segments = collectTextSegments('> quoted text'); + expect(segments).toContain('quoted text'); + }); + + it('extracts h5 heading text', () => { + const segments = collectTextSegments('##### Sub-heading'); + expect(segments).toContain('Sub-heading'); + }); + + it('extracts h6 heading text', () => { + const segments = collectTextSegments('###### Tiny heading'); + expect(segments).toContain('Tiny heading'); + }); + + it('extracts strikethrough (del) text', () => { + const segments = collectTextSegments('This is ~~removed~~ text'); + expect(segments).toContain('removed'); + }); + + it('collects nested inline text in document order', () => { + const segments = collectTextSegments('first **bold** last'); + // Segments must be in document order: "first " before "bold" before " last" + expect(segments).toEqual(['first ', 'bold', ' last']); + }); + + it('does NOT include inter-block whitespace', () => { + // Whitespace text nodes at root level (between blocks) should NOT be collected + const segments = collectTextSegments('Paragraph one\n\nParagraph two'); + const newlineOnlySegments = segments.filter((s) => s.trim() === ''); + // Any whitespace segments should only be inside hl elements (like li), not at root level + expect(segments).toContain('Paragraph one'); + expect(segments).toContain('Paragraph two'); + // Root-level "\n" nodes should be excluded + expect(newlineOnlySegments.length).toBeLessThanOrEqual(0); + }); + }); + + // --------------------------------------------------------------------------- + // findMarkdownSearchMatches + // --------------------------------------------------------------------------- + + describe('findMarkdownSearchMatches', () => { + it('finds matches in plain text', () => { + const matches = findMarkdownSearchMatches('hello world hello', 'hello'); + expect(matches).toHaveLength(2); + expect(matches[0].matchIndexInItem).toBe(0); + expect(matches[1].matchIndexInItem).toBe(1); + }); + + it('is case-insensitive', () => { + const matches = findMarkdownSearchMatches('Hello HELLO', 'hello'); + expect(matches).toHaveLength(2); + }); + + it('finds matches in bold text (strips ** markers)', () => { + const matches = findMarkdownSearchMatches('This is **important** text', 'important'); + expect(matches).toHaveLength(1); + }); + + it('does NOT match markdown syntax characters like **', () => { + const matches = findMarkdownSearchMatches('This is **bold** text', '**'); + expect(matches).toHaveLength(0); + }); + + it('does NOT match code fence language identifiers', () => { + const md = '```tsx\nconst x = 1;\n```'; + const matches = findMarkdownSearchMatches(md, 'tsx'); + expect(matches).toHaveLength(0); + }); + + it('finds matches inside fenced code block content', () => { + const md = '```ts\nconst tsx = "value";\n```'; + const matches = findMarkdownSearchMatches(md, 'tsx'); + expect(matches).toHaveLength(1); + }); + + it('finds matches in inline code', () => { + const matches = findMarkdownSearchMatches('Use `findMatches` here', 'findmatches'); + expect(matches).toHaveLength(1); + }); + + it('does NOT match link URLs', () => { + const md = 'Check [docs](https://example.com/docs) here'; + const matches = findMarkdownSearchMatches(md, 'example.com'); + expect(matches).toHaveLength(0); + }); + + it('matches link text but not URL', () => { + const md = 'Check [the docs](https://example.com) here'; + const matches = findMarkdownSearchMatches(md, 'the docs'); + expect(matches).toHaveLength(1); + }); + + it('does NOT match image alt text', () => { + const md = 'An image: ![screenshot](./img.png)'; + const matches = findMarkdownSearchMatches(md, 'screenshot'); + expect(matches).toHaveLength(0); + }); + + it('does NOT match heading markers (#)', () => { + const md = '# Title\n\nSome text'; + const matches = findMarkdownSearchMatches(md, '#'); + expect(matches).toHaveLength(0); + }); + + it('finds matches in heading text', () => { + const md = '## Important Section\n\nBody text'; + const matches = findMarkdownSearchMatches(md, 'important'); + expect(matches).toHaveLength(1); + }); + + it('does NOT match list markers', () => { + const md = '- item one\n- item two'; + const matches = findMarkdownSearchMatches(md, '-'); + expect(matches).toHaveLength(0); + }); + + it('does NOT match across text segments (no cross-node matches)', () => { + // "**th**eory" renders as two text nodes: "th" and "eory" + // A search for "theory" should NOT match because it spans nodes + const md = '**th**eory'; + const matches = findMarkdownSearchMatches(md, 'theory'); + expect(matches).toHaveLength(0); + }); + + it('handles strikethrough text', () => { + const md = 'This is ~~deleted~~ text'; + const matches = findMarkdownSearchMatches(md, 'deleted'); + expect(matches).toHaveLength(1); + const tildeMatches = findMarkdownSearchMatches(md, '~~'); + expect(tildeMatches).toHaveLength(0); + }); + + it('handles tables', () => { + const md = '| Header | Value |\n|--------|-------|\n| Cell | Data |'; + const matches = findMarkdownSearchMatches(md, 'cell'); + expect(matches).toHaveLength(1); + }); + + it('returns empty for empty input', () => { + expect(findMarkdownSearchMatches('', 'test')).toEqual([]); + expect(findMarkdownSearchMatches('test', '')).toEqual([]); + }); + + it('handles blockquotes', () => { + const md = '> quoted text here'; + const matches = findMarkdownSearchMatches(md, 'quoted'); + expect(matches).toHaveLength(1); + }); + + it('finds matches in h5 headings', () => { + const md = '##### Sub-heading\n\nBody text'; + const matches = findMarkdownSearchMatches(md, 'sub-heading'); + expect(matches).toHaveLength(1); + }); + + it('finds matches in h6 headings', () => { + const md = '###### Tiny heading\n\nBody text'; + const matches = findMarkdownSearchMatches(md, 'tiny'); + expect(matches).toHaveLength(1); + }); + + it('finds matches in strikethrough (del) text', () => { + const md = 'This is ~~deleted content~~ here'; + const matches = findMarkdownSearchMatches(md, 'deleted'); + expect(matches).toHaveLength(1); + }); + + it('does not match reference-style link definitions', () => { + const md = '[link text][ref]\n\n[ref]: https://example.com'; + const matches = findMarkdownSearchMatches(md, 'example.com'); + expect(matches).toHaveLength(0); + }); + + it('treats code block content as single segment (allows cross-line match)', () => { + // Code block is a single text node in HAST, matching what ReactMarkdown's + // component receives as children. Cross-line matches ARE valid + // because highlightSearchText operates on the full string. + const md = '```js\nconst x = 1;\nconst y = 2;\n```'; + const matches = findMarkdownSearchMatches(md, '1;\nconst'); + expect(matches).toHaveLength(1); + }); + + it('finds per-line matches inside code blocks', () => { + const md = '```js\nconst x = 1;\nconst y = 2;\n```'; + const matches = findMarkdownSearchMatches(md, 'const'); + expect(matches).toHaveLength(2); + expect(matches[0].matchIndexInItem).toBe(0); + expect(matches[1].matchIndexInItem).toBe(1); + }); + }); + + // --------------------------------------------------------------------------- + // countMarkdownSearchMatches + // --------------------------------------------------------------------------- + + describe('countMarkdownSearchMatches', () => { + it('returns correct count', () => { + const count = countMarkdownSearchMatches('hello **world** hello', 'hello'); + expect(count).toBe(2); + }); + + it('returns 0 for no matches', () => { + expect(countMarkdownSearchMatches('hello world', 'xyz')).toBe(0); + }); + + it('returns 0 for empty inputs', () => { + expect(countMarkdownSearchMatches('', 'test')).toBe(0); + expect(countMarkdownSearchMatches('test', '')).toBe(0); + }); + }); + + // --------------------------------------------------------------------------- + // extractMarkdownPlainText + // --------------------------------------------------------------------------- + + describe('extractMarkdownPlainText', () => { + it('extracts plain text from markdown', () => { + const text = extractMarkdownPlainText('**bold** and `code`'); + expect(text).toContain('bold'); + expect(text).toContain('code'); + expect(text).not.toContain('**'); + expect(text).not.toContain('`'); + }); + + it('strips code fence language', () => { + const text = extractMarkdownPlainText('```tsx\nconst x = 1;\n```'); + expect(text).toContain('const x = 1;'); + expect(text).not.toMatch(/(?:^|\s)tsx(?:\s|$)/); + }); + + it('returns empty string for empty input', () => { + expect(extractMarkdownPlainText('')).toBe(''); + }); + }); +}); diff --git a/test/shared/utils/modelParser.test.ts b/test/shared/utils/modelParser.test.ts new file mode 100644 index 00000000..fae2532c --- /dev/null +++ b/test/shared/utils/modelParser.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, it } from 'vitest'; + +import { getModelColorClass, parseModelString } from '../../../src/shared/utils/modelParser'; + +describe('modelParser', () => { + describe('parseModelString', () => { + it('should return null for empty string', () => { + expect(parseModelString('')).toBeNull(); + }); + + it('should return null for undefined', () => { + expect(parseModelString(undefined)).toBeNull(); + }); + + it('should return null for synthetic model', () => { + expect(parseModelString('')).toBeNull(); + }); + + it('should return null for non-claude model', () => { + expect(parseModelString('gpt-4')).toBeNull(); + }); + + // New format tests: claude-{family}-{major}-{minor}-{date} + it('should parse new format: claude-sonnet-4-5-20250929', () => { + const result = parseModelString('claude-sonnet-4-5-20250929'); + expect(result).toEqual({ + name: 'sonnet4.5', + family: 'sonnet', + majorVersion: 4, + minorVersion: 5, + }); + }); + + it('should parse new format without minor version', () => { + const result = parseModelString('claude-opus-5-20260101'); + expect(result).toEqual({ + name: 'opus5', + family: 'opus', + majorVersion: 5, + minorVersion: null, + }); + }); + + it('should parse new format without date: claude-opus-4-6', () => { + const result = parseModelString('claude-opus-4-6'); + expect(result).toEqual({ + name: 'opus4.6', + family: 'opus', + majorVersion: 4, + minorVersion: 6, + }); + }); + + it('should parse new format: claude-haiku-3-20240307', () => { + const result = parseModelString('claude-haiku-3-20240307'); + expect(result).toEqual({ + name: 'haiku3', + family: 'haiku', + majorVersion: 3, + minorVersion: null, + }); + }); + + // Old format tests: claude-{major}[-{minor}]-{family}-{date} + it('should parse old format: claude-3-opus-20240229', () => { + const result = parseModelString('claude-3-opus-20240229'); + expect(result).toEqual({ + name: 'opus3', + family: 'opus', + majorVersion: 3, + minorVersion: null, + }); + }); + + it('should parse old format with minor: claude-3-5-sonnet-20241022', () => { + const result = parseModelString('claude-3-5-sonnet-20241022'); + expect(result).toEqual({ + name: 'sonnet3.5', + family: 'sonnet', + majorVersion: 3, + minorVersion: 5, + }); + }); + + it('should handle case insensitivity', () => { + const result = parseModelString('CLAUDE-SONNET-4-5-20250929'); + expect(result).toEqual({ + name: 'sonnet4.5', + family: 'sonnet', + majorVersion: 4, + minorVersion: 5, + }); + }); + + it('should handle whitespace', () => { + const result = parseModelString(' claude-sonnet-4-5-20250929 '); + expect(result).toEqual({ + name: 'sonnet4.5', + family: 'sonnet', + majorVersion: 4, + minorVersion: 5, + }); + }); + + it('should return null for invalid format with only two parts', () => { + expect(parseModelString('claude-sonnet')).toBeNull(); + }); + + it('should handle unknown model families', () => { + const result = parseModelString('claude-newmodel-4-5-20250929'); + expect(result).toEqual({ + name: 'newmodel4.5', + family: 'newmodel', + majorVersion: 4, + minorVersion: 5, + }); + }); + }); + + describe('getModelColorClass', () => { + it('should return color for opus', () => { + expect(getModelColorClass('opus')).toBe('text-zinc-400'); + }); + + it('should return color for sonnet', () => { + expect(getModelColorClass('sonnet')).toBe('text-zinc-400'); + }); + + it('should return color for haiku', () => { + expect(getModelColorClass('haiku')).toBe('text-zinc-400'); + }); + + it('should return default color for unknown family', () => { + expect(getModelColorClass('unknown')).toBe('text-zinc-500'); + }); + + it('should return default color for arbitrary string', () => { + expect(getModelColorClass('newmodel')).toBe('text-zinc-500'); + }); + }); +}); diff --git a/test/shared/utils/tokenFormatting.test.ts b/test/shared/utils/tokenFormatting.test.ts new file mode 100644 index 00000000..73cd88ef --- /dev/null +++ b/test/shared/utils/tokenFormatting.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from 'vitest'; + +import { + estimateContentTokens, + estimateTokens, + formatTokens, + formatTokensCompact, + formatTokensDetailed, +} from '../../../src/shared/utils/tokenFormatting'; + +describe('tokenFormatting', () => { + describe('formatTokensCompact', () => { + it('should format small numbers as-is', () => { + expect(formatTokensCompact(500)).toBe('500'); + }); + + it('should format thousands with k suffix', () => { + expect(formatTokensCompact(1500)).toBe('1.5k'); + }); + + it('should format exact thousands', () => { + expect(formatTokensCompact(1000)).toBe('1.0k'); + }); + + it('should format millions with M suffix', () => { + expect(formatTokensCompact(1500000)).toBe('1.5M'); + }); + + it('should format exact millions', () => { + expect(formatTokensCompact(1000000)).toBe('1.0M'); + }); + + it('should handle zero', () => { + expect(formatTokensCompact(0)).toBe('0'); + }); + }); + + describe('formatTokens', () => { + it('should format small numbers as-is', () => { + expect(formatTokens(500)).toBe('500'); + }); + + it('should format 1k-10k with one decimal', () => { + expect(formatTokens(1500)).toBe('1.5k'); + expect(formatTokens(9999)).toBe('10.0k'); + }); + + it('should format 10k+ as whole numbers', () => { + expect(formatTokens(15000)).toBe('15k'); + expect(formatTokens(50000)).toBe('50k'); + }); + + it('should handle exact thousands', () => { + expect(formatTokens(1000)).toBe('1.0k'); + expect(formatTokens(10000)).toBe('10k'); + }); + }); + + describe('formatTokensDetailed', () => { + it('should format with locale separators', () => { + // Note: This test may vary by locale + const result = formatTokensDetailed(1000); + expect(result).toContain('1'); + expect(result.length).toBeGreaterThan(3); + }); + + it('should format large numbers', () => { + const result = formatTokensDetailed(1000000); + expect(result).toContain('1'); + expect(result.length).toBeGreaterThan(6); + }); + }); + + describe('estimateTokens', () => { + it('should return 0 for empty string', () => { + expect(estimateTokens('')).toBe(0); + }); + + it('should return 0 for null', () => { + expect(estimateTokens(null)).toBe(0); + }); + + it('should return 0 for undefined', () => { + expect(estimateTokens(undefined)).toBe(0); + }); + + it('should estimate tokens by dividing length by 4', () => { + // 12 chars / 4 = 3 tokens + expect(estimateTokens('Hello World!')).toBe(3); + }); + + it('should ceil the result', () => { + // 5 chars / 4 = 1.25, ceil to 2 + expect(estimateTokens('Hello')).toBe(2); + }); + }); + + describe('estimateContentTokens', () => { + it('should handle string content', () => { + expect(estimateContentTokens('Hello World!')).toBe(3); + }); + + it('should handle array content by stringifying', () => { + const content = [{ type: 'text', text: 'Hello' }]; + const stringified = JSON.stringify(content); + expect(estimateContentTokens(content)).toBe(Math.ceil(stringified.length / 4)); + }); + + it('should handle object content by stringifying', () => { + const content = { type: 'text', text: 'Hello' }; + const stringified = JSON.stringify(content); + expect(estimateContentTokens(content)).toBe(Math.ceil(stringified.length / 4)); + }); + + it('should return 0 for null', () => { + expect(estimateContentTokens(null)).toBe(0); + }); + + it('should return 0 for undefined', () => { + expect(estimateContentTokens(undefined)).toBe(0); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..60884143 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@main/*": ["./src/main/*"], + "@renderer/*": ["./src/renderer/*"], + "@preload/*": ["./src/preload/*"], + "@shared/*": ["./src/shared/*"] + }, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "dist-electron"] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..65b03beb --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@main/*": ["./src/main/*"], + "@preload/*": ["./src/preload/*"], + "@shared/*": ["./src/shared/*"] + }, + "types": ["node"] + }, + "include": ["electron.vite.config.ts", "src/main/**/*", "src/preload/**/*"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 00000000..5576680c --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["node", "vitest/globals"] + }, + "include": ["test/**/*", "src/**/*"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..50338e85 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'vitest/config'; +import { resolve } from 'path'; + +export default defineConfig({ + test: { + globals: true, + environment: 'happy-dom', + setupFiles: ['./test/setup.ts'], + include: ['test/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + include: ['src/**/*.ts', 'src/**/*.tsx'], + exclude: ['src/**/*.d.ts', 'src/main/index.ts', 'src/preload/index.ts'], + }, + }, + resolve: { + alias: { + '@shared': resolve(__dirname, 'src/shared'), + '@main': resolve(__dirname, 'src/main'), + '@renderer': resolve(__dirname, 'src/renderer'), + }, + }, +}); diff --git a/vitest.critical.config.ts b/vitest.critical.config.ts new file mode 100644 index 00000000..c32070bf --- /dev/null +++ b/vitest.critical.config.ts @@ -0,0 +1,34 @@ +import { resolve } from 'path'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'happy-dom', + setupFiles: ['./test/setup.ts'], + include: ['test/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + include: [ + 'src/main/ipc/guards.ts', + 'src/main/ipc/configValidation.ts', + 'src/main/utils/pathDecoder.ts', + 'src/main/services/discovery/ProjectPathResolver.ts', + ], + thresholds: { + lines: 65, + functions: 75, + branches: 60, + statements: 65, + }, + }, + }, + resolve: { + alias: { + '@shared': resolve(__dirname, 'src/shared'), + '@main': resolve(__dirname, 'src/main'), + '@renderer': resolve(__dirname, 'src/renderer'), + }, + }, +});

)pd(bVoPuA|kdpZ{s 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 RHk0thG23c-%Vg)V{|_ufbPxal literal 0 HcmV?d00001 diff --git a/resources/icons/png/128x128.png b/resources/icons/png/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc2bf508eb274e840f431e757e9de22239f17dc GIT binary patch literal 15770 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 I#_`1e17yQZf&c&j literal 0 HcmV?d00001 diff --git a/resources/icons/png/16x16.png b/resources/icons/png/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..b73af219c218b9ff7ced1a17104ffb667877c990 GIT binary patch literal 710 zcmV;%0y+JOP)}+G?yuUAXllHVZQ)1;J!8bLY;z=e+Ohnb`gX`aJwsj$4+Omlv`|b}G-C zhnjirGT%VWh%qo4RTM=*F)XaAs&(hwxvj0O-$)$E($dnRbMD7H&pVT?3G!Bs_YG8) z7y{*}WH>A-h6QC=5@YPO+wJ4sZub{YQkLb*A%u>^NE9K|fe>oyP*c}6bseZfjfh}o zsOnU|KR89=NkRyRVvK|ssp|^EIQhg0UVZ5W>QFO3e~`(E2}A@{#mopHE|4@xTCLV| z&bhltnt9H<@2qg*@khC1-vrye0W0UuaQ~6RT>SKNLt;SVp3%#26V{T$p3y`gQ7RM6b8QwY4AU@Amj( zV}t4GDQ@2U3spr`8EcR8xqRs}9y)fE#~yi@1N*1= z>dLpM3ds!-c`sv+Lp37en4P_oXPPfj^X>Df3TBEKn6amkHP)%X zlg!M_yq;x^Z8Jke5sAboh(tsJW{R0{oA2%JLIBuDnn_nCZ1}~Q7=pHl0%rH~T s3~>C*Y;tnndb{0zYjg9@-}yiM0|U>DV(T=jBme*a07*qoM6N<$f>U!)$p8QV literal 0 HcmV?d00001 diff --git a/resources/icons/png/24x24.png b/resources/icons/png/24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..13bec5a3b7964e60f2851af641244973a094cfaf GIT binary patch literal 1316 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 AC;$Ke literal 0 HcmV?d00001 diff --git a/resources/icons/png/32x32.png b/resources/icons/png/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..97afb8fc622d23c8321a72bffa4d6a81470e8d14 GIT binary patch literal 1838 zcmV+}2hsS6P)x-s!9mnza_jg@0E11r1yQ7ds>ugViHFL>zqbLZ1u*?b~=q#eqiB1&KgL)K24~#O( zD2gOYI%t`ZK_?Hj79OfCQ!KYyvvkd}b>_aW>-YP9KFz&*PfiE_f_lC9pBFriJ$v?? zR7yENh?TzU`qFolQrtzMl!By?gl$ry)kc}t*)Uh(wkyk`i6k-;N!Z3Vwy}+EW+Zn5gi=SyGy8ZDF78m%>KBZ&L!Pkfp$T=i9KW1AV9bwLE!{8M30k|kjq+ejiaX_;(Y+R9}gyOgaxUMiMqh*~T_tHuTI%!Zw-7jBUEEBO-Xm z+uzFeZA-K|kgZZ{rPfMjQnO(tZJ08hOsKU|3UpnE$B`M^fB^%>HX90$@j5aS+t?;E z*|B{)=f2?iv^vsirM5;YX>CMGR##WK`s%BhOeT!u$S_R!{tY)XnNG2dZEPV42y7!I z8+taejcqa$+muq+w{I_~(dtMWM=~3&j(;fFWSXu6+eqST*Idgr-?)w~TaM$r z7oE$=CoOZ+%|GJTcil}Xg*$(KD|MWbwNmTIFiaVSfyrc*%Rh4kpTF{}*v1B1fQcxD zxt_UFx&p=)nP8hz3YkeMh0lEYa-RLnbExB#i!XRJ?|$$5_`wY~GmZmoI;GZ1ts}!Q zWf%s=Vc^cY?!h*;v4t&cC?yJWg}Df@K}t5*#x`f1v72Y@JDXv;$}m}Dy0*$IUiM;? zNgJn(Qdm3k2*YHR zVHlWBC*UMg)M2F7k#QIp$B|(iXtnaWFMNfuR%{^&o4H`K!klf6F_M|Fjcsnb{dNu< zIKYu3M|kwnBdo4I%C+D64#teNGL9p)HfpV8tK4|=ey+Rzd)PusY_Y*MoAt~^6l@d0 zQMQ?p#5h(ieB;}B)hk}gj_upn|C3v}{<}A@u&_X_Behk=amqMWzJJ3_eCX0oA|;Zr z4FooG0R$U*{z=#g1Q7)YW+DRH{Pk~t*FC@GiZ6bJAKthh zFt)J8e=u~V3+8&}Y^rS_kXR>5DM%tBXst055zJ&JlDPOS@8J*k-N%CuKEyFbN{$I^ z9%I&-#Vrf0u5nb)T-SBKi3m2>Aejh=FdHHQ+stH|+kf^8Y!eaKi6m^+83>Ls5D=^j z78e(P$6R4lqePCfO>ufOfKTc>Oi zJdVA4_n!2?0}q`0$RmH>m8k;8HV|P8+Z3mRtVJ c|NR&L00|G580B_3Gynhq07*qoM6N<$g1HZNLI3~& literal 0 HcmV?d00001 diff --git a/resources/icons/png/48x48.png b/resources/icons/png/48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..50c3eab8b31ec3c668b35b9afa48d89156658f5f GIT binary patch literal 3364 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

sLZiye0w)D zSB2kyBL@59EscEbs}y}@_(k*V8xpv==$v{N%kdhMZ?4$TqX_xq;Bo?_^J82eUIa zS?CdO9k|faVTky_uzM6ah~^`@e+Sm<5`oBIy6@&R)5&f=bx-rirJCRa%j7zaida#p zi;E)5M+Y4XL5$;^^j8Dq$I=)@rYENvj>>UZtsmM=$Xz{eCO%AWcF8a)m=i7WUAV4j zckOggnY+!@TWtFx;9}l4PY@mRGJ6z+o1R$Yt7;A%G#BLEFE zyOb^##c&w1Wj^eDXq~oU50lS?bD=%ZU{Nu(qK?lS^WI0+zs)zgbWBpk+P~9NH1xg@ zTC$~8zP0E@{5fy{`=D7q)+n0!wi$Zcu=(|Xs4A4?gQzCcbHO+m%AeUrU8gVa?8`no z!A;faO?ostPWCq-ciu2WdY0Y|6mP{Rncu(l&t~b3{fU?iCp59ZLSmrKYc#@J-fMB{ zO~rB{DM9D#uWX|x$Q3GW^P2^qWuq}tuhH=t-hDCoH`&)xHxYmR`kwI{xpqfKVajJ@u*(i*LTix%OJ z#O?s;{?7R1Ma~ro9>o6dvQLV$I8YCV@geA#gBcTg-rdlBxo0nHRBeQ^NCB3`G2@iC zET3TaTKSX)aS@wC{tWS}L658ksk^J*16c1~7j>4B3~7OpG#?L?vVNfxG#68?Z774b z8I_p`Ra^ZABuhwgae-^;#RxF3oW$H+Hky${D}?-TN9r@jL&sX2^jbz;KplVMFDrFm z7}P0jKi0A=DUDI>+wl=G?N=Jjin*_zx=3!UqJzrrc3WsV^j$Ob_VZ(BvwBB5XxNx! z$YRIANBEoWO#4#xqm1Wx&GzQjfjp%^kHoQt6f>i-escNr@0o ztKcl~GK;$_*8!YGVH!pa%~SQBrAgBY$#~TH%Kep5>bx zg+CmTbYAb>r~4fkF@XCp&t_+!JZkVJb;~mzPVJ;~hh$1V# zag7}EOu@tl6>_}*ZEVh8p`}iCE+Hp-Gy%(x>3Hs%mW^~j4QMpwq|BPO zrp1sq^>`r*FaJ5tKe6|oJkv!^9Gt^BmhWL*8S*)BlIpM$ekaG2>m|>^Yq;Z*2l7cS z5?8dXv#g=5Kw1+odCU2cUNYXY9_hAh)D_j&T3i**Uci6)ugo;Ru)UpbRf7`67KI#c z0AX3XwEm}ZZp?RN(nt2qqp~-jx7Bq2Kb&9Rp!8PfV^fvk{vg2j(sJz8&~G>9uz8Rz z2F>)c?M(!xCG4}mvRu#eDPb@7dhKM%(JTya{V00>d|VUd@l|(s{AGc|Kb?hmhu>XDdfMRfPCfM)j`}6Cbx#XVRfyDRy#V7~Yi?})Ro{a7RW!Tqdd=spd=lZJXM9U@ zT$kDzOOM-LCs%-<3UsACJ76<012asRFq42XKnp-9=>!vSLF#+4t6bv^JZg@P9w|Z_ z1vFXMJ3X~WwWc1#4;z@HPKzG59QZ}WAA@}0a*TN3#5)QxEhW_--$;G=C-}p~s0Z3{ zhX1a>m6K{nuei5!c4!w}cst6nUa?Cqr3A_Dry{FA>bL4{%5o+- z(Fl(+1%F$#5?cN?7Vu%^;~I5hn7c{$Za9ByCAeojX=zpR<0BzFJ<6JmMcwN5jg1Y? zj)>M)#s7FsfJDV1>lNR3De#F^d3u)$HR&oy7La?Lba-17PjkZ`ZkaVnQ=&j(r^-=k zuH;UPel^ft{TjUnS^M?k*(=NhPE;dQvwXGi&JbSdbjrn-5RJo@`PbWzBK*}yl9G5# zZuS2tnh=sJP!(D{q|bo{7d9wT8nU>}$u~H=1Y+mYzC6ZKz_OB}+a1ThG#w z-6c7b+t|H*Wu-$rZ2Mak3FQy$tkautwU-fW{@XLspm*psOA1u#GC5}Bs*4KOeKN4a zbBRB?pDjJVF`0VX{BwA$y0L$7Hd*1D$27O$Euk#G5^BSebLQj_QC3RQFFt?f?8uLrvJ%D;_PYJO^zcNPszIuxC6Il#My%U#V@VpN{Lre6=RdiH!H`lRIsR(lbrb(yTc8E@4o5TkXq(GZu4^QA4uThk@tE z+Y4VcWN#3R!NA`^Y|%lWtBguShZJ5P_*w%50v zOkxiaAw4{WFCSt2W0{rWh{7(Im1f#uQ8em8mWc29kNwlr)3)0d;mQG4T?9SgwBk^3 zR|ntV@A#!akYWGl1gK^Yz(@oF(!P7U$|tRNuS2d6rm9Z>n(~ew9&P*jTMOkb_vW*E z1+p&R`rR;tdN}x?V{f@qK$Ts?v!Z}WxRJ89agLyX&Z%q#e-_Xn|61mH{AxdtxET9a z;$56C#No>8(p0u$v0hm~4LWz%FbSbNrEEuuHVcY4yGvNgAtz{zY^9OG%&bXO%XzZt zmQZ7@%9jgszZTzR?iQ^o&zWZU0H{K23>qhT6!ZmQagA)_bzi@}{XqTGEPO`s_}@wy zt>lvwnz*?09xms)8T&tbjwN@%CK3q5LP^Gcai>00!qDaRgNOSb71Dx1=eeF$Xi1;h zkO!bjm(9(GWHA+5e-00x?~E$MxbMdM+98rh{`7$Uc6KmTw{z7r$X3k#Wxj|-bu{Fx zvzXrk()ZFVzW_`c0BD)(8a&p&W(gnq zj_n8v*2*bG)rHcI0D=ju@SFn}BQIq4=MNt2ep90;Ej*V6cRCn)CFbh&$ zkMt|Km|(GB$|$2(N-)`;w^?*_sZirDyCk6EqTt#AK8MAGZ*-;qIQ(iWQg|l& zY{~5ZPI{1k&!ZuAs=%(TrVmbJgLc+ArFfML6apX$zS4Mwry!2b(Lq!K-^(IP6CEiM zgMV|C#VvC#J-Tjv;@bny2X)sH8eM-sk#V%+5Q(X9{*um%X6qoUAqLrCJ_h7Xl!NHE z=%tL#WG>N%*k8)Q3L7U!lnfN_Q7Z+b-5~Mb>dBr|Um!&H@#JY2RvmN1hrt~i%v_=L z5S%fHkH|&r-Mw`*h2IVPT6($`0^yM(6Td%P@o}+IWE7tP zDa|Up@;g9k0W^osyeoX+N04a3&&;W3yko?jh19r&MMP?Usmps zJG(t5d^0j$YTfOC+8iQG_Z?T|Mm*N+*LWn(Bgf+rG5=(X`D@xU!ZMaACzDQ%LD;|~HcjQ%_ zs%o&4&_8pcmK)w)bQ`rDOvsITP+zfQ1r}Tn=kg>sj44>Kx|ZA?bRH7H zY3q_Kht+*uy3|%!mgSHT4%VXUEqtXki+;jbKFy$}W3M;Y4ok8?cW@yVE`-9PSoWOL zutTI^kf=l)6f~ABHr_Unl)}M)jaf)O>U{n}hv){=pYA!`!X$S|AOO4?05Bi$+#~$P zYSt|@rFUF#?a+FJymI~A?HxTp(Hp&%As${-iro2q5G#KDj9;`<-p|~hE^mcC3`2nI`JKb;#HRC5utx1wP<&t#9ZPs$pD8 zj_Bk{*(Z9b6ik9f!R!Mtjd8jKyY%^w=VnPgHca`GUhH+5FxKQ-lT2Qfre@FQZLUg$R#prF74-@` zSS5a!nXmu3v5$E8?<372#ux|s2^*a5iUZfLX=#GNC(+UeHKQ6Y)ej+m*tAg$)$Sc- zV%0Kk+E}V;vGCupKFzp34$WrW7E$y;4YMlh1S9ktaA<#Dzx%zhy`k`~b$Y}?C@;-@ zj7?-6DPB}*^X}Wa?-%VFvh6$>_i+Jsb}EOWEI6(Tu2ljdFOoD!!-unHlfvsoeY-~I ze(3oEcaeMt*uEpH@PdxybYJ>ZMpylS^yvDx%i0qDmY;5c|`ji;~(3kZ)FTeORX(A=BbJ13+i8z(J4>V%& zK7x!`F<)vl1JfQw=XMnNp5Ky9^mw2yALMqM{5@sS@!Rou-dm5~n3Cah|8DpXjZBdOEBrI);Bt(83lP&c~U@e~RxYqKIbOUVQKuu36{kVTter zDqXS(aoMrocj)C2p+tc-s*qhjRM<}yU6wfW<_vUHR5tZ?AZvd7Sz!Wqam*(ll#v1r zDPX*DhN`UCw6Ad4T+}m#J-~XsDn5+0?AKQ}*DzT*6W@nA`68Y#fmd)u2Btxs zU4=zg;F^{U7dc9Nk*vfA71EIJZ{1P?;*OV>`+so7O{gHu(SuqQ(_48M8hFbXA@_2c zj_k7*%=}9-W@g0{h!t*|sPqft?$ftuI~bCNDnQn@m5j z1TUY{qjdOZz7Zqm_fa{UYF|uY|5Vq9UH{`1-nhw;hsKu$j>s0rV40Tz(98Li)w)se zw<26Nh+96>I+kG#6ux>pi32Sy-~IK2!<3?IYr|u=gAr|-S>~`TL|`1h=0gk~-nX_O zp$s)+{3Qc5ESWyEr-K7Wx#Os4v-~E=tii)+sxFj9_5=?CfhaXH@#wB3qs3gNe|^>W zeh9T8CVbdu@M6vN%3wvaqreL#erPTW$;kEzy9KY@-*VOPdn%K*^v(}*>oFPUnDe{H zwO1{>%u9?!Kko`4OBwFAc_Fie2 z7jAQ+rd=ABmy$w$hNN0?UC?+Lsi9OY!+dv(oKfS#y9OuF^=HI1&hs$jX{l_9xq||_ zV&6C$4_5XaH&`CiATe~PN1ov$GApaw{N1tDXEfEv>1j%=LG{&i{cUq2jB*<@?!|bX z8!&G5D*xi{!M3-YRcJ+gf9C*KcOYc%`bFNm$y1c0siy;B;kfMCAPdVl?t9eb0}G#U zZ4*s*GlbJths5M2>-6jCdLEWfr|sf|mfdf=ZzXL}Krq9c^lYTl($?ASKSC?DB*JfD zxw`ZeybGF!1Duiqw}&dn7qLk2Gbpbt(e5xa^o!gtl{EjJlraF=czJ&OpICeIXajzB zc6QdJd)39VNLzMWHWK>7nl*b7oHX{G9dgMbpSGdaCs}L&3$u*o&C(I|fBKO0&D1Tt zWd9Ows-g34)7=qiY&ouB%{^o`qr?EB3UwRWUYDo{q!~Lsv`CVkSV??6vyUSqYyGif z{B=_gFPl<6_gPs(w>kt$?PrBkC$!(F(Wh==U_VOl^N8z(eUsEsSO0H~1K|>&i}u}Z zBX2R^Xx@~(y!<`G{Ybl|=>EqmwX|n;t+H`S`||jcd9bFvH&heb`zo8(_GfAN*1w}R zT>?F(l}#ugyhluI1$9-<916R*ozyb>`iXz6SZF;O0GUv}`6ICgeyqznlqL|44KbltmW z=yaPu!(q5ji^tO+ATay0hGof@LUEthhq0(#Yjol2kMr!Axm_Le-Ly1`z~4_0!uFK{ zmm?Ji^4wSUAdG8eCWjowUad80TnA&fU624{gvk=$%5DVDztiMkjQ)sMQK^j`)2&V^ zpUyC|obqHd9GlcTI9FTW^^Ft^z*3fk(%(Mqqta7Wa&N4|2j`s^{AK$4OX7%#X-GJH ze!@F;udbc22v!iFsh+T9JZ3n!Nv~14SwXGi@+KW8gEPK%V42OG5v0s`C6#Z|TGAwC zIbbN&i4ykTl!nPGmur{b(OYL{Qx>^SfOT?ca}7 zk{D9)9^6v|IfeuRHB5jIrw?4gvCaz}F}`Ue!>_ee?#G^rzgT7=IVlpIUA@)&&y+cp zjUffc*ZgvZxnJL>%#lqGF7+hu)?!aiLgxrjkx3^ub%ZMQmvFg!Qw2{ z7lhjA&OUMaa|#g=eBbQ5jQl=jdV@*lU(Za#|5D6qi=`_`MqC^z2+^46GQHZhZaYM3 zEvI?Wtn4oL5&HNz99Rt3<&q}b{|BA8NRP@Ta^GV*opAkqAazmiaR7UZ4}J8rIBMd+ zV^biqY*kiRJS&~of5eBW;3XU6%J?IhPW`>zC2>A3Z`K-D5?29& zq3yPmTm_wHcRz<`V)5k}(O!nu#8XFvNyV4=THx5H!6~?a-5m{8$3~&!LJ>ZbTU$K zWDG>bN(aW_QgP3>`|~`XvC_b`KB-z8b`Ib@aCw|@^CswMX;E+~9%Ko-Jh-@PI(OH*$vkr+>ilgtU2n9vuOk!sHd zJT&KW7q{OGx{W(XC`R=~tGl~7Z@28;kk{&hG|7j8I3{fe__P@dh%hQ8W&62HUjDdVR=qX-p zvUg%LDl*}=WhnbQj2`g*9c#XYztm6TT3!2nE>~Gpwa@F+(b>QEr#FTMaMClA*Bs-& zT4?NwedPG{>R*-(6pL01C9%d2)N?S>2x~l}jscT026DC6#r7|>{M^;u9)LG%ybfXf z&UadI9u#;3^=Nbla-n<#9X$^ zn#8Yj5xjCDxZ~FxUSeq?uGMMz#S#o6#@H_4F;NLJNhi!T%s`@6k$yd$Y`^h3MFYH< ze3_r)7kB>)eQ*og?i-U@!Bklyl|aFf;YCtOEM2}ax<`Q*CT^Bk0-hY3?&$qKx_i*& zQ{%fz)YGm>@3!n7AI8=EaRD*v&HqPVPPwr%8`Nu4)%Nunc0BfqC0=nM?Z8m9 z{_WV_g#@l6cCcJ4cm3~MlOk}`1%&@qY>+m+7f`-)SbV;Ihks*PQ-<-ILEK?=l1Q5e z^F?&+#)bExuK8g1KwV-peMD;U8FYH8++&kZ&pOwP#T{O@96|Feur>~!dGPJ&aclh^ z_IZSRTTr{OL3Je?7e-IoXx91;l&MoQ*+>R%y13i06#Wew&SlvbYnvn?_wlO_{+C4v zGYKD)ih*2xEREa+ZHC_&@3hZ0xY(fa6Hrr59vV}o_45Z2v3BL^vnvyh zxpa+^yPuN?=s1R;v3eWjj(D)t; zuzEaj71PW^*L9w(?Q?aQyNAUwPyJG5NP>}q8_~0QP_FTUES#Tyo>f;rLw5^(cIbl_ zf{{4Q+{7T?x307Lw>5OOjw;YNhZ)&a)$JKv71M+Uia@SKbK3Ow^8l8ZzJJyS*MZ*u z`im2t<(D#$OXn0CJlVO*q;U=2U4=u|((CQz2bcqh1Yf#`TF`qlNLmSeAFV#ET{Dyg@TiEl;c{_Il77#BH#AD$uSZ2XC!L& zdP}C~8jr+X?TE^tL7@~H>{4xoNggLB(qT{+I~agVN4ovysB93wA}{OL?e5jfzNy=@ z+TAjwiQH?R+I6s98jxK*t*1{D@nhHR<1K8Z0q7{*fF(B?NP!4I%(x&5ZR?uUF~zF! z1_Y1v$&PBY(baFLd*0Omr2xBBK#V^;=z?( zW*A0)_!_S|mkn0g?9QdIiQYKgf>G@cn_oaaJxdJMVJjB8!ViEheWq}q_#`6f1OWFvl{(8U$gTp>-hSZUv~pUi^y-Z+n+=^XI7JJ_Y)KHj(829W=-Xu z5=~9fCY`oU7R%R?$xuZy4;`)+tNeRR!XC*)AMxDpz6>NaPQiI@eghhCd6`?kEh>N0 zxc&vQWccTW?Z*v|&Da-^ND|hI)fHx>N64r530^65h?wHsh^MudgMnGH0{U+H`0ciU z{e@*;zrO+8lj=A0t%&tR|IeTiBmnlk2I}udmQF&q{))0`%;S;YjR|&~?Q`uBv(R1( zps3&xmfQc6_4`1oD)_1-xa%6R1Y76O<^5hyxTVW`_1)WMP*#mP*|PIf(@XkqO>lqD zTF{gIKti%oTyJ{iHxt*^_Id8$#_XwhV%1-&Xr+SU%9U1ckczU}S+-t~&T*V=xX*R8 zH`fV>Mj5ZrCjh0tII3th_a<;MPZg{z{`%;gjroN74}lU%Cu1o$gxIj<{%I*^SY6xp z3D`}81ZK+F0MH^>{*Bv!c<)mc{2?S@IO#R|Mr$yRNzGrJ+e+T+TN+;Pv&CNpJgyfr z*gq!EYhh&W6%?OLX)R4;PXqutMOwT8iVy7K*!1r66pACZlpLPo z^3qY5QGex4F~vfCoG@N`wFE%>Ok{*UhLP}(Pl~;;ueUvXcrS6D{eEKyNDYg03yRp|+htaR>Xgn!Hg0&&7w!ffK7`N29m~dp)OJepuy@s1=BlJ;F z+iva*;~WS?$@z~!J~+Qe!Y>e9>=RYr(EtR~P0VfQAGt4nI}afs1&zdWMg6VRDeaG! znzA=Gg+1_46WQjgEpU;jOq@pegjX)qWjpGd}LbAQc@$sWcp=bm!Ou?8r2 zos;f<219w%{;bVO$M3~yf3+6tscpfZq3jzTr9p%6m7sFVFu$S_f3QYmCC^gwDWDl$ zJnVfWkou*D&dB@M%-?ejenPg9{=c54BBf<}j~mT*dHpR%XKrSyk!pNVP{Yk1a#a%f zHtZ3LYM)B9Py8t|%dTyWi@tAl;4*Q{`(b;~$gJnzQ%tYc4+ie#ucz|$gSSU#R+QOe zZP35#PI(hKNaDaeoB+E!JQv4alYuNkzhP!XCYOVtK`sZ^}%`(a;l*@?;jGPekK4FOfv-iZ@Qjf*A=>E$(Z zbo?~w@W!b8okB4F^uj{l)|TLWvln4IKwmnXa{b+Vx&40JR-%_vkyB%Ig;E{w*aJcW zMrH3Zr)nYL?d#ZRF)s0XTVcI@zX02EVppv{iH|dXbBdP>MYLbiGc@*QmD6L_Ix~nP zKJB#!HK4v5f>}H*B-TTzB#cpqvinG`N(OEZf%S7P|Io{64yaVG$7(-HRsUZSFec4A zrjL!mVTw z8s4RNU)KkF&^H@cN5qU~ix6aldUQtSPBIGBaE26=OmmIEFsVFShJu+34WD~@;v+1b zqkQiC70xGKmomYV)dji0ncFC>N8kAx`JZB&0dF=iW%L5cq)2tCTLKqACI(C`v-kK+ z@mqAI0~O_Q=m{p?XRkov0d0B^$>cya(@_KTypjouT^p)P8}ifefJFto#uoXBEp~*{ z0L^xN89hq!&={}HHt2ZG9-*+hKOKEyw-Z`Lf!J!7=pK0mM`e#ch`bC;^0N!gj24OX zwC@o?wo>Gev9kQk_IYp;U{9R~RCHUxL#ZgR0DRbo>oe!p+q1cUsdEgxq?$nL^T69@5M;nfNDbMTs0Z7DwXvmk2xWMx5A~a>O&Dx=O_We%gel~1F)ND! zf8jv3EJ}`(zUOu-eo-4w4c6<}vN@v>`uDA*t3dp-zR73|b_Xjj?ys1HAYk{`aR}5f znMieS)t6p$rDgh;YbUTyt~%Pyx}o@BG}? zvGZpZx(*pFFJ;{NEc+;@Xpma_X!b-};VWbHpQIlF4JQ#BJ`H|I3=n5a!WQiIFhmed z9lxA8YjoSR?!!6jo2!3}XlR)vRd+o~7mmMM`uD^z>O3*W%67E;T1+2t=@oM?=En5>?5duUFjpJ>T-NkuDbrl#r-YbD1M)Piy!Vqq&Py7 zDH!A!^yy`nH9m91^Q9&aJRl#cegH&K0RF`su;$>35B8`CR`}G@rx`KsAXZG$q{N>` z>{q`LT;kGp-Epvx8{a68wOBrea4)iyl>?AK8||}t3#QkuY0_w+bZ0ZqEE&Ho%ANM9Yy<5%&Zw&xMuwWJRa=K(!aCITIa@U zr-Y4lUE4sA-hspC_d|TNTLJI@xL=iT_-kt9LrjqG-#L@Hnn*4Ky=M0sKmqQ*j{sEhZc~kO(@uyCNq^)FrvmPfXr|7eN zd+d-&pSvNyiR@auvuKLGUOt9QOgn|F_#d%coh2$+Ydas>&m`G4; z+lt0Et2yGmma=7M;xG+aXRYMR>lV8rzMoUu z_o=ZYq-De}!N{vxMhA`v^1Ys!#Fn92kH;g6@ynRB?p%74wzCn?8xLPlp+SB+e}Wp zwbiBB=-|sCTWCJGNi1>0<3WdFq1vqpXwRIACM%yctaX*lK-dmegy$LS#yKS&$-AH$ z3+j8{EGx)KajBuKF!faq_(p}U7Cg%~X^Bi)wY)h5Ab^m1C8l&bsV{6)of+9HqYH_m zsReM41&^O=C0_iG2ECM#9^#pkEdq!LHh-_PsWcnWktT^{DEE5yK^kyPAO~L;Cx^Q^j@v88H<_ zj)zv3-|kZ5BIDCpFUoqmm_mf`Pd$z@qQ2COmQq$z{5lFiq#uPVk`|qOQMz*>c{}y- z{na2FC+cyPG_fjAX*-1y$G+Pp%eWe4ak*C!ReL4zJ5sX|Tnh26Axh{6TS@W)-DBt; z-Z43rmY8QSyvYn_FISyJfe)@QbpR-gX!gEnz9@gCLN|PjmKJdUt@c9l?!xkBRqF1^ zzC;kEJWO7X(;ec^M7vup3YkA@seu+?XNZv;Pz~-m{Rmc3I}vCqT&zx)8#n-}fe_Gd zxw2!dp4x#5f$(F^+{~fIdL7nXrIXin80_kOhvyzG`?q<{ z^|2nG?$plGzt>byHDo?qq6%++DN+=g>9#h96VQylOs#h|ZnqbGYJm7>m;wy$|CUog zXCE+a9$f_d0|Y|cemCq2fOt!l2)~wN*etbLY>5+ssyvzJ%!gQy zp!J95TLQ}Mz5&x%tf;<$z`78Lpv77~2^zyQBG$MRMC;|U-L!YS!vy`@C*-WzwRR)x z?U8Rj$`=vJsxrs$-EK4lHjWhl=?G!Tts`EbO>2aL&8+CjUbeNkT?IS+TZ-vea3>u@ zIH$`NbUV}rP`iuh_If5u7BlLUX49jb6z2(94T<%#7?kJHIao-Uz)rp%RR~f1aI*Ah z)XaLT@d^P~Nnn7Gv=z{&Vk{E?oKIUgG9Vvaok#{h%q^lS<2xJiaaEgYlN`RThZ$>{#wLul<;2-TO>6IKuY_^m}0Uwl6wf_VhU9t@;#_@3=h180xNKkqK^H!)iZ zi{q_W{Yh8m-SRyDgRR2^50Jnjym-(whPhx-UcVUNi!PJIsux$e{cKI9T^T^ViO#I- zTuEWCdYp;j@2D%edR}hGv&PEUPZNoOLZOINIio(KfqLdu>(Tjb@j<*xW0cJn^%Mlv zzLhmZ?Rl$j`_@geWPGz+-f<_D-|~3uB>QS{PqHF6=kLxAQA zRF*M3<^cwt5IyfG<=o@xdVa^@Fk2$SW9!3Qks%NB36()HZpOTt6O${G?F8a|Qdv7m z+dMC)M>oT78~$h}Zin-OtWOOSCdGrxUoQa7#NLfF)sA+G%8HHppvFnPzEn?p&V6o5 zLSPNQ+IPx7-|9wxe{6gI`5z-!_K&+#{GY=7mSg8Qt$UXlGw8x9MjFME;;7I^YoE;^ z-{p_6g7Wy2W8!N5dD_~>A7+Q~cnsC5ECB^l<7bI@9~(~^?VUq6b(Tary?Gwgr7iV& zY_y%$#ux0wg|(yhH#+R@;M?lzcYY`4Vu;WZFYXOt4xyrdwJfwx)F@aZa9UXdPL@C~ z#Lo=+4LU&lU_uEV&Rk-H+t$4>JYK!7_Z+W^v7#=wj4gTo=Anja9y&x^4tS(im%-NA zX4d?Ov@B z|9-YwerU?P-}aJ9x|)tpHp%G%uW&Pr-R_xOt8*Pb0jKqh2b=%rv|v|8nKQBCW6_RR zBjLg^2^pvnya!xOi87+a;_~a$uW`-w;-5e!hMmW&8?8=6;)l6Nuatx~ZW%gCTwnja zY|K$GBvGB}7!g75^3PD~i1&5#GDQY7ijEP3s_Q~A^-raxlpA=73ng%6MEphNW$6JL zSzlZhFeZz6dwW;Wz-q`ja83T8(1Z@fApT-m4LdoF))G|F*qD@q<#2>c%z8juzB$dt zuw(6nV>WRj|Mj9cxpeKxdmP;7`9J>joCM)&emt3)oqQ6~RG#~+z0B^ht8S)!KakTB zm3;ckpgaT=dl{L+_inm9k-nSPtlDV862#1e9jMJ==I{841F$f|%MC@^AmqVUtBh`U zx1}mtiuN>oa>?Ok6bX9pQgZelA=)`YWMsm68GpwXi@#0gucLW32C{jNr$u^MLi0W` zlt5EXnuMWlWBxPtY2?;uG1q%UjdA~RCCqAO2YIvAbnqDuq<@bbWxbJ@vKRfUQpuoD zn7b8nlZY*E zzq>MvP_iQi@x#<_wb`^fPHoSyD2@h0l3|D;`FCm1Yawv+LbX0;WLd1dO#2*}pOBGd zEX#G!4Bs$Q$F$Wwd^ZZi5<7KJEFLHZpKlLlWDGue2$k}W9N)Pilm?NzbBdp8*5F4? z!0O|;v~vw&$Gu5<+!{K%LmcMkl-_SREZkL%{y9}_IZDBGp63%7cpW!8`0ER+noZ72oc9Nnb}Hcj_-rSpyi* z)j(9Qa^q_Xl*JQ&(yJFk0&Riv>3l>q`g}TM4ro}MEY)?p?ehZ6nI*;&Ed1*M0bl-n zUHQQAVTgCnkXpy5hvm{-%%tGbK*~`Ryz6JaAcx5**Gn73CKASsp)eI0f2WpLYAbXt7!pE8}8bKgNYJK%u$2zRfd zdObln*!)Nx&iCr-#}cYG+sVlh3#pXM>ZN~)U5u+ppXC(~-G2O&_(_=oak={4A=iqapn2^HK~R+KbsCg z3YVqdaS4)F_>4g(5C7fpcXxL^IhlyRgYeDq6`F7-NKDS?IitKR$~Gz2etXN?8wOrvOMm+ps=pEv6?i{Xrk5-2hxWka6Kn1EYPUk4GVCr0bKZ;ZF09nRyogj%-3%w zz$vsVni&Q+=uNvnJFoctR@BXXv+$1Ln64hoB45?t1KH*)1*UR^Fmi$Z^ zcBcq3Xh&tMbb@FDeoFUGV2p{4HBUy()ohQ5h0|v%Jrf;cJ}MX>!=RdmQpUp-itEz9ugM{&dw$qHa${X(yZZ=Xy^0H`GLeA|rSBdUC@$oNC5yNNq32gsF zZbDiJ^w^3z_FGTkNq7b~np?r7bym}=mrL!Lcywyw2<~T!C$Wj@@csOSPGsPtW?p)M zwldo?H?)Q5OCo&Ev_YF43jA)nU&Ynl?p*n>UHyC)3cH?Ti(xNyc0XqOqoxpSr&#^G z_}*g%gTW<37*l$X^YakN7K3?L`y#QwVw|lzH#|yN5vd^0cdP_}gi9HN(*Shk0X<-( zi@}WlY}79*L>(msnWH;a|i&?foX&Zdv{i zA##9BZTH|6i1*W$)SA-`M!^!bWHx0S=(m`n6I3EISH)g`|4N-0@PX=GvHv=oLrGev z2I9avqt~qbt$IT0Znico=%&hU9%I4r$n z7W9QRD-Ie+BF(p~5%%?TNn`TUONMXPE?>q|A#_D&SuwDf3+eC7qXB}Sh{!Z>`_%oX zBLv){T&rkIWn2n~6>22De`gt`MqZ%~PdlVsE2#8rTf)2?5E#b~<_LGft0kBHCT=W7 zO%nV_@Xbn~my<2861LVuouD~RC)hYA1iQsqB~t!yf%Czd2%t50lm*j)>ta)5qC&WJH*%#-opPi)tLV!&r!l8b#1>~G8E9|{g z$0T24nTe=29qQcZ9$Kk5XaD+)N%g_q{bPxG?s5`9IofSp0_bc6sWC;|=AD zavNqfx4=^A2(6>dN#G{Na3gn%C%+Ji0D{iZ=Ha+_YsNguxhMqI2Ws%hkpn<@4|XL5 ztQ{gE^Drb*$8nxmHhzWIO8Sm3T;sbg-S&HfrS&#mOj#u&8uuNT2(o~wD-e^4{iQDU z%RbNKr)u5H);FuaGDG;mw#AZs?#gu@204i*=L)wwv=cWfIR!tk()+X$@T%xLnG$^7 z$W8U1eh7wKx_wlF+b+lm=<71GT*sIzc+$fjxur}<51hJx_z?Y%3~)6=9dta$bJ$H< z*`ZwzqE6Uw!h_JhzVbukCzqm$q+OcN0$avw_Z&(f9#Eo~EGd3xORw)P-S?#`~P zF!1K(H1UpY?uteJvvAuLx*6AfjR=A3z4SWMa8U*weX9!3kJ@pc%N9@%cCGK-)$70- zS83!k{u4CKcHW7Ox^ZF0zlE|<_$_`8*rc|Av_fq2grgP%96}=JzQ(SVfjHlmqu2M# z9GeRna;(PQ%|cfuEDsW&8=$|H{NZ|yh<|?@WtEH$?v5sV_mq2*>6;TArsqN=y?5T( ziSdsps4nRj%BsHDodPtJ&nrFUd%Ee{ zfq)-d6J^R?+MSR^gO=GJoTJ3s({BOr&L0x({Az5~sO)};W9h|EW!DSlZY0Uybb1DY zxr91Pt?Z8xiMsC3rE9*yn!`AyNh+01j-5JAkbXemVm7W^$(kt%*6D98(EFw=Vb3fIP{G`&*}|q`iiPr zI^A1tLP?4;z=C^f{JXJ>`P7#a)Zrf=M_Qb+xZXIT5ToW~#n0O%u>JuCT|SbW!py$z z6sRUMbv%!T{Y()+jU~hRD^v@&&Bo{vd?QpArAq|k!ha*2$$lsbsw7z&~9EWj0{3`m}16d3V$Uy6^ zeCb-c95i2>j$%epc2Fucsl$Wpc@J~EAIx-;LjJp_XT%z2O%Id!=Re0}FLWPl|HfBU zx_%Tz?}*t&^%OUygVW2I^SD&YjPT1B>W_H&A@9M;V?nO^*a|lgsw47py)M z#?!uhZLYB_=OPvUsCwm4YkcYRiwQ3*)e_A>`U%KaOXj?k~d zlZ$DRYipiAo8!?SC}r4E!sX28=fd3>(%8L_<}pI7t$^fcW>kKbv`_AYnO;{!T_aqOX!x! zKaR06n)Y3_q^GF#&kLcH+YPl|z)q^}^_5*E(p?pt%;?JC_c|h3*4tS($DdwY=-r)b zJUoea8|9H7Dk=EDy|v>f$v{ ztR1HC9=P>`GeF)JStGQPE$7+*-JBd^X-PKZvCo>OG0?|Z`s*v9Dxu};Aw6U?7?}XX9z0Y z5Kc4o(6a1IUV9W9)aM3)wPd8RYLWrG_;~!E$5B*#6*|$(Pl+DRi5rTDN%nMhGBl(D ze5KD+EX_YBHU{w#`qevyCb=0pSM-AowTsnu+x0(kYZvQx;!LER%9}s@1c5?8if>+P zk4$?jp=(3ve%t#!`@S!mfi--u(?axaQcw#`Hp(b-J5zuX>qua!opQS>?I?M)n$! zQF~QF-hzttClR=>>mrx17KqX_(e5|cKhbbWV9@anbcAmt7f!aDNPy)6y%M9u{4ICc zgK>R-ktkpIw>*TJ53V*6eOasst?w4F4}O^W+=wE$ze3wfn&>S9CU6t@mk9@)E&?pK z{wMf=KPV$tH+n?ebweSa_^Wg-_Vj}~Omd4>yq}#b_ChBGuS_=fDN^}h^LC15$owYq zJH6S|zul>Xj~PZ=*{KmQJ-HoG66ZM#^DocHK8+QKwey4wq*sD0O>~XfA5VT+`n&or zShzh#>=EBrb0J?bL2mRkgyf^w!plY$lExt&w@iEa_p?dd@+#JX=P<*@A#oh1R~6$) zViYkrXXoc`z-ZasjpgL(%o1gYVZZV@&Fx?|{lHNAefX7ddn*C_`dT#|tp|EHr#U7i zLHqf#|0*!YZ)XOi!cqL=RCvw-DdMZ<-w@ln>h< zPP(LsUH%R8tlnsm_fJh~YkkEKC_z(sT!u>WT`m-KL5tqzP-YCCR5_WHPB(Zt8rSDG z64?2>J8JC!3&fx|huJUk#QHk~_!i%$PZ)q_F>LV=k3Ui9w!_Vc8UL8NBMEh~g;IgB%T2>e z+6Qd{D`1A3kw1OPXbCfeM3&sbX{Nlsc9Cp7p9|Vh%=w^-9=0ieQy=-wt0WHjs{i!OYQ?@bBE0Z&nu6&f!>FGoD;z<1Rh!3mUQ``{0KKj&x~ zUB*|eWAI8&b(TkE&96y}1(s?zURN|v)J)f$`o=DTdFlP9e2!6Nx?BtfQ&ITc9 zp-L=0zmr_rN_qK&3J=hO_SHBPY9i?8JmPXf9sYuUipI zr3p#pzDloSu@u>?b81m%^=_{D-x)w=aZ_UaoAn5}43_5-5iaaDgwBl{sh2&FC3Gnc zrX57DFZ!*xE12Q4)2Nwor}+D^v(qntb1t4VI`&C`LPRPVt!^X<3vawM!3~^v<$51W^ga3_Yp9RKdTZ4GsnAkBI!?*3O z(Rppbw4V0)sY6DhTWIrfWu;A>9XB}P2eT!02b9@BijX+?hcN4_FYBFReIYjvqX`3a4 z{LcTxM&Rcniw}%6|IRK?&*+8SoPhZ^Yib}X7y2lFdfOQYY}WG^{sI_UGh4ecHU5{u zw92W@iIh|zGhRKRWx-4dfk@B~8Gr^Z3|KKvADn0>$hC$0wILv88v7oqvV^=wu*G^% zn3vPLO1&D3ieM<6xwMT~Mf1lDhV3SyH5V=9zi9VL5pPx+27ML1631Zu4Oct5P<`>^ zsMjXM^DqCgiBuB+UtYHkw~-%z^lDZwiO*m_J;9lT&l(j>AMrB>Um_S|L&o4t5ta;=q>X-7>M(;1P1F6pLq&vdUThnvVOO?|^;PxU2VI z_z^qa_vyh8-P=*|LT~?+)G|Czsq_*r z6R)ZPaaY+4#FHk?tzYxAU3Z-rXQXFUM%>B`K6y4EM(weIA5b%BlJ`}I%O6c#Ori9S zRu#u=IHecpQT__OmY$EQaDxn+y;>G_yhmQeq#7xxk&e){HIuR|xS51UtBL%usMECk z`h}U9-F4Z$;C|0g+qN8WRZxNq0t4XJi-?MLF5XE}bU%u&d@zM0o2Dmlv@t%OtHPgf zhP<9H*5%Jn{-k$v?Mgg^~uNyj*2t%4gmB22F@& zQuhX^rW|8_gAbyKw=JR=d1;R}aU(3N3L&};BKMeI7oIF zOLyCv!j)>eG`^>ZL~?=4>b<4o;rhLzc}jxU+o*>56#Jv#{2hb2_USKt8R9 zyMUH7;kTS5-2=abM*jGj12)Qi-`vyBA}JPm@s^CSUl3)E_a~6^6T*a&GhMq9`|tAgp^9` z1(JWgV*KNS0T8*2eclf;-n_jxXB|AZ4Cqhx_YN`d8d<^^-A&-8^}_J7TL+?@-oX(#K$Uf4>9{;nBPlQzE+?}^3${1q+0)#w$_ zFfevnS!sd-vp*0ikTkR$UGfKIoStWo_w~B>=|w?a>D(RiNjs`-{0<)9e*nU}{9*ra zls2faK7R{^A$YnEq09T-z8D$FkZ>AnZE0xcOd4&L7YkdS!IY?qxZhaf`G$gkV(@4x zEVK0L(m&c9QNK9l%Qbm2Kl87&u)WSs-j@#cf=Hc*4PD`}?_sR>RZr<1A=_X^!`1lX zNQtOHnew6qhDY1f8)y-R#ZzAzBlD4tW=-ke$8=76l;`@UlXlc+SCKfkQ28YjIJVH! z)}7nt-233O#j?)CxxPn|u~`}QX+O2IydBSxyDsNt_xD~KSqyW#Z&R%lk=!W`ru?p7 zmaqsOy`kfEN%ddk-hF4YNZW~n{dyUMEkA&vwUMD=t@xN)3IZa0JUy%BK>jqa#CNGe z3<14}XKg$OSNJpRwq{V$-TTWQp3GpijT&XMrlYEIywBK z_l*HuuOp#f0~de$yiIg#i%n^uPA{q%I@BE(T>q3ql~27i2d4SLo3F0daaNjr%l^)l zUtx}N*0McMG*vpcIIWJyu|Bj15<3i%0E=}+5K?})ySYqU#gZl%rHJ5JLaUuIULs_I zu8$|D(i~3k{TmLA;tjr=S>h*JF`a8+h}@=Un73oA3i1dZlv67;Ejh#WwU@cCYl}O< z2tw)3foQ>u{7Zc->_wWTUOSstg$?8v>6Fb_1H>H6{z;Zi#Kj+l$K(bc^6;_H(a|lB z0Rq*BLmANrsT$wzb+@)L<{3zp%)gQ~s9i(PMU$*!E1cp_{fYDrL*$E>;-ur(=Zn?U z1Q+V4{d7p-Hxx|f-q0J%lP4jjv%?SZ2H9RG;IXUkiA<@m+PpoH5Jx?0OW&GaY_>k$ zKTOu8s_N+D`vx<(b&TjQ{vzB|GvtG}cg~U~0Xo1e;$ujNKgEuA>gssKmnXO)FQdp| zGOJhj2?K+(;w;IcZRbkjI)_}qTkD_?T@On(dOH=7Xo15%7jkm#M8Zk$e913_?xBhPf4pGrrZ28z`d}G^fV-0w_jv2(zxUl9@COnx5s zt&g~KH8{^{4e)bKoHI{sGXC(-q{)52!%NMs_cpPx(ELZpl;2L@I99*+l0N?%&uWA61Wr6xB|m*=x!-NY*&hFD-@rA=6SapJqPB0|h2+L%m-)Zv<3$Nr0;9xk-n3*d zc8_z;AU_Z{5d#nHQ-m;u6W8_FUNg>(mvNh;l(VHl+9xwL#OhG~s1VicP3q1vNmVIz zNr+ixQ8)M7(xIB)SF4&gdf&F2=G1jW%(Q&ms=(Mzu2dKtWva-MQRwRF#>; zL>3ROMfvcO!ro5Bd8~E)1`eS@t&c%lsp@_VyFX|-x!1)+hpLLKB`N7KXg<1DuAV$- z=zB_B&jV%0KXa0^SFH`@ltb5nTtB|tOni=b^Ln$PMK!l>cstD^C745?UW0l;aDTXf z+(9T)8jC`QePq{gpD^ZkRbH4+MDDH0cJ1+2D8reWZA<6$Ce~nWYy!x$?{h`kAq2j> z7)zRNWa&+`!?4Z1R-3IwaBBtX2eh!mxurXfJJ|ij{>99k>))Lj{+T&O%y*d>Z-#2A zC=%I}#5DWj@#%|)Vmn8hxlAVxCT!SF<-&tXVWT5KL-B##S<>{4M2&g2$t}rv(KZzQDcpmsW@hzaazTu%`!8= zPVPZ|h3b_Oz;gDeYy0`xjRb#B2HgP&wDmyRBflH>8xEa?hWYpR ziC-a$feTj3bo%EBuxExO7-&dwyh96G$aLmP9I#_@oCUY|7vrM2><+s z&`vgcJh4tfFFwu^x@gb9!&B0$VnC zu2e$fk{=#$B(?wj$7=+v@YSMU__$wZ(U_KH{Rm7@t$zL)H!H^4luPyYi;NAv!Rma4 zM{t-C^r1BvzL9upZ7z@@SxJ5C5VSvIw%`s64cAifDMwa1D)*BZAj` z#BIJVnLLrk$1s18UQ7(kGaojFsDaPSiM)DQR3#MEz~oKq&r<^li5OT6;pUuYX!!!+ z^tC+~R%q}&Ve;8+{JqEdz5e}Uh6hr#q+VN$A3Y}y9QmBycJW$d^Mar-#u4^Zo=DR`9TM;T+9Zj{iKovbp zwzv;T*05}k2&L%$pjnGeM!@msxbK$Nb;T?kDwLLyImS^Ci8)Ame24p2y`UP%Fpa_& zsU*o0=DSD4CB$AF`|gEbCDqsit4H5&UbQ9Xa6NjxmRkae^K>Bm=#W``+d1A2ZtENy z$>Q&$WeBMo@eg4pj&9_cej2+B+G-E2CuJcOwK?ZF)6LKS-oZe3z$hy zMg(M*RTQwtr84(ou-GL=kj(QI=}|Cuv?p#ovvGQ?5;__9)mwsPuAv~szwN6%2KW_C z##R(CRirV2R{n{-|cR64tGw#w)pVzjb%@ z5VMOlR!602cN?E=YA|K!94o@XN@b?u^jA~O>M<6Uo4{~c!06Numqx=L5!o(V0aM$( zoS)1y=)U0=dM)e~>^wTt%*@7S>ZMen@3uvcL-Z_(1Hy`iH9P*APDrU}0ovu; zuk~w+NY!QyWAHYcW##lK!ZSG(*AUxjL=6vQ!{}mS*~U1t8vkSDt(GX3i6IDsmiPju zSMg`a3)=qSzw#RZL|QF+LBKl4^+hDEEPGu3Zkmo)q6=wD{HIzsOtD@XuYOh2<4 zV=fHLFq5`0$N&fql6#CG;O!K`Q?N)(g@HTr;tQ>?WmO)6oT3$W!qocJowi2(|TdBb=H)@(@!VjHb zA@3c>V+_zkYyPRsU$0ist}LGKr9+PDV-y;NFl9d11v^Pv?D`Ovw;qj=-;7J$)7&5N zw0D+P{{F~s3nvTua@EUGw+177&M8oSW_bu@*N>v|_wvS=rc(D0s9#3BQpXS-dzv9x zrRwF!A)j@DTq>rju77v=M8}2QmNheOU`;ti(VktZwSFQh z1UEI;E4RXoqssTDTQA|i(V?CMqIFUcchlj56_(oxHGDIc>jW|3wfj|A*X#;y>|IJ`_eTf(0L zc<k^NWhQ@@d5^H@ZDYi&_`Vcx74Y)!>3DJP{~WU86EoXfeM8JDvl|D<`_p374oUJwVVIjxb9$+ev9lMj-l z2zkB9M-W=tNoD!cd`rwg4pi|J>+|a+Y8?k>eH#9|IKHlx2bnqUkH%CnVpzuV+~r+Q z`s)4=Kh=WiOwU5o5Y(>CrCb^F>i=WuEV$y@nr_{=2ZFnV;O_438r1b2c2_uw>c z!L14IE{z4Z1lI-{*UNdoJI4MAYt^bfYu0=UYaY<~Y36``Lo;_Rnh=6l1-z^s972ji zXe7D32CIq%0v_zdEOk>o0dQ@zy`T}%rC`H?IGUd=5PPH;lWz>pWkmoT!_uJVM=^(+!7J<*UX zYQA+d>^xG6@v{H(yz5Wp`uaBXrx$XBrtaDgVdK8yDx^UU%lu0TLni8(tu3t^E@jsl zOEbQ~`gSV{m+gAYZMz^`5mpFP5=|6L{piancQ*i6#U1Z&CcjVA%z|rs+MU~d3KK%W zM3wpHm|^~usH%Fxn>RXv4$JQ|x7W=f=3-T}|m%lG)xHz@qcntd2exC(6QtP1ebP3Q| zpGLXU^-OqcW&R2S1P&%Ct22xh^dsV4IZ(9&z3z_vH=z=k)BmRfiflQURa$2pzu;EL^tg(LhhEBg#K+hzi}9 zb(4&4AN2*eFX$XZp5XlrnTF=9{?qClN%N*Kn?RAmk-Uu4FK!pfNtB4v#gma zk@sPr`XR>uNzF`lJxIB%qMNVooAw~S)dE`k9X4*=U`Vaa5Y6LrPuaeqWfB<-CY4!k z1{-iE{fYJn7N%Z>(_+S0vn86W_gg2fa71$)xt*UrB91yZnyqez;53dxF@mR<;>u+^ zPmi(#>eB3TcjuHnmRoW{Zmsp-TPTKDIyaHeKH-BW;O2tVZ(sesCEa;!h_9KQO~JNT z7Z!q4g;Ws<>l;>~*M3Hit}Oh*<~H_4YYi&~p7)ehKW_4;sJXPy4${j`ziHT9cNCw% zi2yHgWex9$dsZJa&1?~)Y7Lp(^(EBM{8zL-WLlUVcuBBX6(cZsMrL(dCa#V0v%Tw0 zrf-kB_h;{G8+F}RsP8?m;-G{m)Yvb?>Za=Rj{ZJuQqP$%Cqn<1^NsjCl2r0QVXW!& zx?aCD_?}{RCRM3=^W!0H0DC)=;14mhq#q84w(dWJexiS~*o_^r zl?k8e8i4y;93JhB*dC+g`m&u-E(st`iVR!N-_>7+h_ul?$)%8K zG--<>^0=im=fhw-Bh%Acuk~tJEs9A_{o9>wC|AAYILCKv5efY*7dp3()PSA10Lr_j z#G=DiB}8_FLBeCfjF+;y;+i!c@fnb%1VbXZSAu)bqotHGMLl%f39weS3|lRxlK~&F z(Yu4QMxf7@UK@8{Rn7ZQRhls(<>)aDE=-916Z-GPmebpG@qe#cW26a-`cr7%2s)c_ zb`fZjtso*j2rQ)QK#lISr7y;Fd`2WfZC(}I^(T^`I4kA_ngP&^ZPQ9xFp_|=t=)-!D>x3@YaUgy0$@mg));hSX(+qd{9uX_t+O2uu+Msn zij!z7(muu%?!f>Ia=O|lARGVFw0C6`*Ope)O0dhKEomdIiHiJzmM&dWLD?iKJOmG`TL0}U3`&7BTe3gl9^Cesg0&$~OiTc>4a zgJ(5uZ6I-g#^eO6C5K=ut&oLMElVpa|8MIqLX7|xNa99ajWTiQT5S{jOE>tic1!7z zd8|A4*MDywJ+;&Z&gj)-g(~Q)k8>hA9K@J*J&WUzDy_JIgYr8OKQ(orw49ocgIoB` z%54PEeYMGn$ZSqt+!ArOyKIK7K*73&rvPHavEr zH{d^PNp{3Yg~{EEzR~+;%b$+09imhugcjTKlGR~haO=On!oA(PzC{MVWNd8@-<k zD#PLU(nysO;zPc6%5pHo2w5z^0!~=+g3Fd2FzC(X9N89>!T$)XpAY@HN|)x=;sJvV zMCG2!4#wB+I{W3LDa&DN?i+XRUGUeG+l{X6$^Z*>0mL+2hDCZ@&q-_ZPQEItwWLBA zgdxyTh!`9!J6tXd;LEfZu71LUSA}iw5xlPH(l9dR&4r!Hu+Fn!eIeI$Kg; zsO{V0W3CGJZ$OLg_~^22V8PRkVz6gV#uMC~n_-xdRnCUou7C8*N6hYcvm>09o8dBP zfkIO1@d>I*hCi~!g8lRHh{>MhXym(hH#hO95WdZM-g~M(dI+R z7gQ3YDz)8$yG}&a>Rjl_z6NqJL=IBb69}H}uKqtAiu2$w$isJ&72lsgL`tia6(D`+ zPHmwwqtx-38G2SMo7i%-V#?P)Yfu%})Rd^)W<_6Zq*QTo@^NQVicb*E>Zf1Xf*+9Q zWgg^wkTsicjnN7%lByaqY~*epyeA-5J4~&4XceDi_#qc*-@m^N5SL`uPhr_^)Xw#T z6CTor*n$GeP8wS2^|HBSEX}R(teL}Linw{>EhT&RF95o=MtyZbH%!lW@8#@bKgTt_ z`mKU;XpTukPHXi`fT0lghrdnF8W@$!j7R&QXsJmgITm?U?A(UNwbJdP_eRWO zlwBvIC0021N0!g4@M*Zk25tyw9ZR|p#7PGoD^n`|RAhuCeLk|eO({!}Fbvv9$>NB3 z^@#`CJ{ZfnjH%C)DwUO~_aPD4bh~Kq&&iJB9?O3kRdnTm4eUjS;R!9-^X!6zC(Vl} z=Tz>5z6R8$2aei8m=e?BU?dVKcHfW-+VfghnwVjv=ES{tTe~BJ*suiGm|QGlzOa^m z_wrJB81z}O5}%4ZY~t!ajS=n8IW_o`e-0_LKCX9)|z|ERsF zx}3(uXPIt)qxe_*?hI?N5PO1{jj4k8ca12BoLR)sy_465BaH;P(c?p0uEnO`^&|Fo z$SBTGF%m zq!%*$b$l43V?$&y8b<@RJpTiU$e$1b0&s@P6&$)3}tm(y2S zuTLB)b=ILqDjD$$rMB@cv$!(a0Lm;-b>aLN5%WVEl;k61FN(E>T*K~$-~9??!*BO6 zva1EAP7#w}y4B(JG?TJBur&B76;T-l1Xz9#Wp^W9Y9d$%%Mm4p+v*?WWrh-O?V@gG z^obn|Ru5r@pNOC5g`KTI8*g2Ky|6Y6!!9bI@a#q3PkIuXl9W0@U`HW9A3$-gZgokW zx}%K!%LOxqvsO+CPV^9|uuU47$hCiaeKBEi*F2oPQ$C^m*lrcw9gi4K_hI7LSiq>q zHFQhV)i3%YTpHJB#8L*ND1srJsaPU!ND#A5CXe)I$;6g%OL77O^YP#dc&&;xXFE^6R6S%@ve0yu%+K2=& zDZq_HBR62`F-+S+bpTfl#_$Y!mc%__qby>ZE9O_%6!e-f)(qxn3 z+jYucRP(Jo1_QVL{`i3l1Z4#)TvLlAjz^8~q%ENv`&E~3+8BOhE6GsL!>Ve4N4>0? z6xyxll^&-F6M2&I3Wl5A2z(h9R+)-g@k}cNw;82bAY18&@Yz-$Wg6l2*PIi6mNmX& z!SG6HHn-xQ>72N>x%Mfky*Bm!m#OZeYy`6hro|aI7=R{j?-ZPA^au4|eD&h*brM8{ zFU#~b2!|C$DdD~yg80cqhs9RcK^9BtBCVnZC#x_8OR=6F?620291tbg{p*mz=B>Oj zU=j@YbM*EjNAuUud(ESZR)~F}SUgytpXbFXR@ja&d2dV;>AEaD2~@mwd9&|WHjE0^ zM^X2`+_)4I9{tQ_Z66zkI*t%s=dyXSA#StS&xS0*TlLBZk|}e}FWPRIhZ`7*mF=0t zp0DsEP0Ty4m!+l`@w;nIxC57f&$HkB=36aW5Zf_GT4X7qj|t(0`d)1+jQysu^O71J4Rb|}dojyp^^)LtCn z>Z<~0aIZJ3>W$EH>4pqH?c8#$*%+pTO0>InY3^QzkEnI6AB#*Dw^)xrLO-MfL84xu zVCVOR-ph9gj1^+Mi({LY;b`f)M*+rzjw^lP*f>gDI(=0rK;@&2C3c&|p40S$I{Np| zOPCH|*PSYm3oI|=q?px7+PTs|7ehx%YRH8!K6`}o|jH$|8yUzIs2j2S}A6)foI%8f4+tn5Ae z(lSi4WBC&BM@-U}ZW?W7ja(p&^y9~GU8pWFqf8eSWIvFXl@|+S^uu2|j~bS287Wxe zFM$W?*P!8;7n4>#gKz14r?%B(WlZrN7#-91Jb>DV1 zKJ9Q4lLVWjZ~MVs1quYNzyaRGFX7v9_aBgcUv*Yp_QZ0WFu-qGT%O0=@cPoqbL(xp zetPY1{XP^l-AMwfp4FcZBRcpeV}5Os&z45iTM8W|w7CPUU5;*S>J+pdtkTxlSNy~i zr%_!~zA-5IXj;P%6Q|<{yTaoTUFe#h$no#pkucBwK1FW&?F*$9y}y9#e3=Hj{rwk_ z!u`wFw)Dgx6b-FZy192D?!M?=r37=LN2w?I*WC>5nMJRe>FR^qCDxkqp%ZzdmJ*q{ zI@r;~{Q4Rr`iEwcX~Q7fS`br&~|@ zs=qKaNKyb*RS=qWWnEn;H#TwAn$?4fATe%Gp2Nn!2Lgb6-19BC_9PC5xj36pOuO4Y z*kcEU$zKipYnPWurT2QKC}IW9OxzCEoF&I`H)>3)cNgNHlZuE+a_!71$SQTc+ms&HiaG zKL5xl_lNtI2zyaizAC?IEDf_VXJu4)x^g#}(Xw-`m*@k2bA(i*KXH z*Su`QXdu`Bd@Dz#85idrkbfCH&om~!H+b(ujbB^~l8Kdb^21n@`}Eriya0^5_pgUz17_y{d`y5RzTtYUz>^eL(1xwjT-s-a_JZ2oV z1bw9rqYT@TKA`>`tDB#mcrVo8u;cWm?b{1@leFmM#2!f-l1}}$+eB!YzGW`S*mhQZ zr~RHS+cEQ}hB$&b@sT;p8x!m1zIXa#aeQ+JfHc<^{R zdCF2444vN*pLK>l0KD`emr*!H zE7lAjWfR?y&!l)nO{a-J>t6VW1q;*jeP09V^7zUp15E9p^nAutnp_$Koek08t{Rg#cYWajKG*dgBt*j0Nkfe1PC#z53M>nRW_WW|OmMkaJxy_N!$ekT=Imt+R*^?dW*3Zd{jXGfUc0UVqh&Ep%FV`d$T` zDC~WEs^57v&UAlqK=E(mJ>6d^gN+3IO%$37PpVMKb=P8{k;q4|HQHDm`ic4#Cqj#f*Ae1#5;1EaX4^3o7x>gwF6{A=X!LhM*CN4gBu&{7xhaT) zvacA)Uo5y)J8HSb5&iVk{?jnJt_PuMGx#ydy|C(9O+3P3QkrJ%yUE81c++lo6g^hR zZUb@Ehl6y`fp0C*cB(-}b%fOkE6S#Ui>k}af}u;n-WYaNc`}0qsjLivEyZG^iaXBo zrCxXAEZ&6wNCr6@#xsrFyRGwqr;S~6kjQlHAcN?yK%Q_hL%6zxeHTIeow*GuLbKKH z33`4EU|P1KYVK{_Yz?Gpm!#f(IDwAN4a0@vCiB#Xr;BAu#Q1w62TM`?Vhln`CMyrv zzAxwD0de?Vc#ov9BV?xC^`Q%Pc*(BK#8i(w!|8b$!0h3eF)Njr4+A(okngz(NuNK2 zNvB&@k(kQ7BV}*F7nE~+Y`Kd&f3r)x@fa>;)$k6b@(*V(ttJ64|8Jsuuf}MysElHo z+7v`R;8o%lpY(6G?OXb@97#Oyr#?pdMfO|xdDcMkh+D4kTo|TFJ5XRf$!DCaTM=-< z?Qm7Rj(_55lw9Yn?6-Bao8JJtBF>^m@Q!4K4M6-{Xxap`PGpMjU*H6)zuUnMHxMmR z%_+E=^L;ZpB*VlH1=0!qAnNl5x8CWABe!=N{Oi_N2VD{ck6FT{ROB)Sc4``oFEjd3 z(9u-V;#_bpC0y+P*EF?awm9|{*SC2akV*L2I$?bpWm-BHD$3xnKyi367I}w=K(=jofn?y@8 zyLD6-*w9dJ!LQ1LV%LiK(V(HL0xodsTXupVHXqGTN-UIbamy~P@869qGKQH4lx$W1 zQIzHfeYmU(^KeOT;zAMA@8?-yolYxZIkM2{7z6mApP7sw6F3tN6P zolbo;kpc^mCrNlYVTxBk9G6u&8s$9k_7zl%+h*>!(l=tcEw zX|BolIMBHU1(DiX*9aVpQ(nh;Yjug{U+pW?zFD(M?*Le=yUnhkBg+RPKfuy1pJtdy_U8@v%JR;rzDUrWgr*OhYMTg!`hohmT>MHg9{hw z(XrWXCL4>3U*sChx^(KEXUINHd*K)u?-}&@h%|L5H=QQb>_MdLnP4sW*Vj*x^_t;T zoVhXHWGq^(bbJWt(&9JX{<6ccPSGFV)$VtHzJ0I#--E~8Ni}6-T=5p6Ilwmhg&)wg$W6a7-QvfQ>gl+`5x0SO6^cirOfmru6Eqp=Y0YO@qCOT= zadc(-AW>8ta50szMv=iIYF!f_@2l{M2-e$-BRk&iG>kFmJlptS7B20*ktKe89}(JO zcq^Z(qCr-8F2)L(ou*3yS8D#JQloN!`HM6kof=H^s;m-)?H{?oNhyA^l9Hks_42ga zS(=mmtyvuBEIwc}8<|5&MXI$0CyMIi$^OwZ=v+FY$|;ZRRhArGO_~pK9k2UMWak8r zD3Fo>(3_w^(y6Rb->Z_sfjMKcMnDAR-h+C>rp2KL)B(F8Idf&5XzX<%lVe$2wrh`e(0^#~p-_=SKx0Ixn*8Z%jF-oL2hacbMW+TzPflT{Yii2baHeM=}+x zRTllx_!C-|XxNlY&O6&0Zmt(3qET zMSUpZJ$^Dg3NBrrZFD|DCK{6?^R+%uwktT8p8l~{#Z$a8f5Kd6V63*RM)C@(yzy=2 zzKpj|j!v1dEvYkTV`$gtj;BeZP9v>vJoC4l>HW!9=j3`L&R=Jo?|lty@NlnL0kqYs zpIly(4LcT#c*P%FZ#lM0kZ`@D98R+vzjd*d|EKd&Fq5ycC<}w3{m9qwt#nwKRMuU0 zIuh2f@+4!RKq2ynJYRrQu)f{P>OLoNmB^CKh)uGluPVCciA?=jLtDK!0bGR%l5-S$ zcEbCZiH?&C_nIp`QiUTMt3T4|_Y0xE5Lr#AyH1O5O0{NzD{sXnFzw4VZZ?6&(;sPx znwSn)Gz-Q3Hju;wM;4*2%*19CViL}qu`ddw`{_f_%ZceuP3va=1M~o6izv+Kk;p^# zt@}Ww&FX>kaf=AzPf}oPT6DVml}&_>8etvw#}MgQev7cV@z&WBNR7C+L3n~sua>_N zl$qS3&YC7ETC`@5i-V|aP?|hzxQmdZR>r={vlPsyGhBmh^^gl=XP|1&cFOI{Sx@p* zQ9ETb4`zxX;Fhg&RjMM}$aG(`Qol?X8D^R@p_iBXo|S`j{KfCR8u>h4GXTYcf$vNl zWfmC2(T^DO+=~FnYSM65I~mIUvJ=!>`2;>_o9exi=bPFZ zJL4C*Z|**LZ#@>n&N>zeob$X`{Cr_ET_zJ8e4b`ffYjwC^YMDdi{$#`FL)xu-vyOs zR&K*!JRXU7hm(!DV3}6EojDB1MzO3-O$y#Cmll38GZN;P7FemqbAL3|r8+SZD#Pjz zj`zpiM?t>yxz2Sqxxg}3?oC!FM=X1(%};4wQqVXv!wjBZrEDOG5<-r@7~!wlCk$qY zJ?Pqo1ThDvqp!h_#mDLdS54y<-+(&!{UQZ7ODzpS38;Q}vP^lt3Dr4bt689|Nsm>C zsqj>qy)zDWt-=^+q?;cWLo}69v}T}X59Q9<3@|xiD7Mq&e_NJJ`q+Xl(f*?{`MeW| zzxw#-$yLy!$ZPhT@7N6T0yR3-uzeVwN*OQHGoF0WJy_(Wzc0|R#{s=3gih?a&*xCijVh~>b(crx^&$= zd+Gq9D~P{hb&=)Bgo|hN*I`nGUDS=s(mheEm}CsIIR(?bz=zhf@^;A0I*;Gx$1qhm z%#99?PmuvE|NbKkKwbldgPsZ3d}9X&!XiSGpJ-(p!o6=bxPFD&JPUb$&lnLLYSW$b z3<;I?O5?>D_xxU@O@hD!n^~4E+f4NMXbSZ?31kay6>ePg`4ip}Q&@Vdc5>PJloj`d zM8fM0ZiXzhsd06l;k4b{7hc%1cbb|@_6&%RhQ;+oU4|@}8N5q^@Cmdg@C5m`tkVt4 zN3~Ur6@qn@Ehs8Fd^y(e?+5!uEJ^s#IlyKWrm#gaaV7p@DdGWgqkfps%ysWS4 z7MjzM^+;!IvgOGVQBe!hmJ1;|k4>aV)xhSD>JiIe-k~TG;Ub^422Xlc*rHq&Hig}6 z4G0sJP9d6p2IStUKdo*LMLcR}hcH$kN*vwb;}prdaO}sQBVO+v{5t`4`@eeU59#{h zxK#VaQp$hYT|^w(sPadAM)%?RHVi;IC8<~RG@m`7YS5-Gwxjoe1n}iFHsL--Rtb*@0zSf2UGeijDQ=r-h%U^beTsk6d znycU?tjP0h?ZTrz4|Rw=#9Bi#j_bb+pbqcz?Y{#^{GtMVS;fVxk@SykH6?Kg($?CD z57`kPC856$+CAiXktC7HvA%<1x6K+vrKi_+{GFWIosEAO_0U1fp8m?J4o4BfmMQeSGoz`^ zR+UGocAVo~4p@q$Eq~>3S;X-RtXTLr*7(8^7bLN3P})~Au~--H4}rY02(sY5PqZB1 zxlV@%-OA#L$nmD6k+^>3h?L!77qzkTEDG96A*Fu3?A^0%Fyxz@vEfb)oJa`N_2X=h z2Uz^_CfdBYkC8&$AWuDkvrNn-UdIt=QLXb_7wsocOMW14x89#!(swi7t@$i=md#e_ zJ)+b3U-q!Yq}2|sxnhteDG`!TZ66SahT-ljY(2~0>?}D04D9scZJA9|;C{1LzAPja-;(<&47t8ymbtGC*E#yV(cod4ekxuKA(Eb=C zqZO5}s7j@hheh!mhL}5eXqtPC%J)O)QL{hI1nuq6CY*Q75VLp|7JE}?ZGlKqzzvsH zG_#qxsyaFUHV=BI{V+YQz=|GVjvt=jCn0K>TrsuSXr7# zzO~5Ix65AyN<*nc>FMNlIhy@rV+v(E!f-H3ZpbUW!LQE(|D8WNHQ=wAlTW5lR0+j~ zxOpT+5~JlQ_g2K5hLfQZ^D;^BLW2}M+Kn+n-*Sqk3Lp<^v1;XFUG!Si!c@V zI53hZ{Pu2-CrL@r$4rVX@$oK;df*BUiA{JzXOU{{&Oa}CY3Y->m1&x%?a1n4N5b+h zz+o#<`Kf++A|rt!Z$JUOr`dxV+v0REF#jWqI^rmg8mkvX;~VJlCLCA5OKx+5c#`dQ7pCoQJOA_+qR>54h&~2UG_l?^<<-eSR}-`B zBGl1SA|tL@iqzYQEju1xKjetvXjpOUg&pMsSQ*QPD#}LND)!D@C?dd%f0@a0AN8r( zLAVC;OjWgs#RpFVg?@N%`Vv5T-b77;9<1Lb&^=rHfP;L3Mc|KG*HVA2Z4wauIkR~= za9))AcPJN?9psq5*+rKUKocY7+4I!>GNG_3dtzFL^S5TPr=n5pTfJm{-d%0w+%==A zj@E=$+>M?z>!EG6yM?{OQZ7MFZ%WGwPv3rFAnm3zhxl%rQ(7y0Fg+qBZocZc8NmRI z$1|_Q4!1LI$WpJAnO1+npNB>F@<6VBn@Ju%x!3^nXXhgX0MrQG8%I?T%#}jQXcmw_ z>i+V+I7TWKyeWgl9d!IKbtQ9`Iy%SGl{Ch$fAC#lY%7TxFe;b+Yu0f3uj3{O(YB7p z^v+Je$Z_f+7RIvQ-;L}#9yTy^J>rgwdgHbab{$yw>&fcwt7GPJF><5#Ufj!0R4LYH zaNJBuq8E0OLIP?yd*QyqhJ52BsX1?XcxoyOP)zKn%*7-x7(blqB}MEu$O3y1s*U_>cc4}75lbl6H#Ej6Q`pak`=Emf_{?eNHB$Lx zbHgU`fka|)B7U#^^45mpv;OoY=cBXoTk`r8_&(4K3cs^!t}13YVwaa_^<9^3hsmb= zx^qtMj=w$Dsrl&&_CIaIM`6tE2D|bEj0MwO9&9ye5Rt*q$Q+8tl_^r#7w2nS$5V9E z!A!cuSbLR4PAw&!gS{61Q;vW^CzDHJ%FdSWYy4;(Rl0Jt6(H&S-Tx_^eFw{F-ej-4 zzw)*DmY?m*fN~nGeR*W-qrxKdnML;dwKM0c#E4AX>h4iLbaYLp3`wx#_dX~6ug*Om zXY^D@zi~^7M4-qw_5i_AkD9{Zk%?T#3HAu`lW?TolP=%|)2I+R+jR-e}z0{hHu zX0sOyMB9igr2v1~soS`1lg5I<-ei-)8sJD1vxvC~XfqyYacL=lZSDl`5Z91ZvJLC0 za%9;$PUC%`JT9iRICZR#fccKzJTroDsv%XCy8N&R84qZtuUlBugYDN2)2LuIe_FNb zb$`yWmt1#g*l=Mg=vf)vTv?}1XJvwDyKIh>2x)`S8Hdk{~&UlH>XTql zatq?sFXOa`j=R*F3s0@yTvq~vu&ozAea4kJych2BBM7lvaILH5F_uUi8P|lFl7nP; zW(Ysom_EO|2UPWiX-tZbl`B?03tzR>O1)jf^FzaZ_+WjUkNmp4;P)(ZScN9`Q5XVm z!3mzueGcXW!uX)>*{$0cvJg7%HJ8=_kY2y!FvaBB->H*+{!fabyxhdcs$|}=7(G%2 zu@+?PmDTs6YFyTIG(VjU@%-aSwPIAL^9WRcsWYaOe=jcnN5c?%d0hIRE`R)|ieFf` z1l0*`hXRT;PW?qj_u9zc)tIBq;|QH6awC_f!!vY=~QFea>c;RpMVw9r*g{nd$EZM{VNJE9*e%xKWL(DHRw5vj1d#pBieR6+tDI*m2Ew27L}5@L_Qjd0UbvKzPQ%51vE zqWU0JrZSzB#pi%L1<_`Z395bpGK>ySdMlnwvgB-nDYvsFgFM*sB0>ZaQoEV3pNGmAV_!h|~HHwPMhC&MPAVbVy{ms`ndA@!5;ZF-js z|4IaIn5j{pnI(G}xWFE@s=!wH4({X3oK$C|21>{8QE!x;=l?18FjQV@J(=&eTw5N> zGJA5!Um7}&iQ|=YeQ7^ZO8Fir;Q9WnmNSoxj4*`HT;}2%X;zOcY2DU*V$DB-lrB%{ zn!Xk1U#9qs-rWYiw0$8y-8Uns_HGJ&-XM_>)?T`e&x5(0u0OIw_2O#qXoe2*E*bAd z;jM#Q$)||oJVs)N5L@b=o+*Dt?DU}Zayq#D7plNJMGE3Mkoy@oRNNLmqKSMSEz z6)FkY8yHP)>ugzgn+bwk5+@@E_!Bg55=> zwsiaJCfZDa47s!)jt^higU*h!yAq0Kh{c+JyPY&yB>7S%=N$ksxe&@LeF^YaMhPIl z;H5?x-C`7@)0O8qZO8d7I{W+By1hY~7oHGDr&uw?FJ@WrMoidc3QK`0{mCSQ$gLL1TdrrWanvQ)4w_u?CoQ!{$?28Hm zizG79Mc?pf=f1M~alA?Qd`z6EV;%U|kTQ%&i#e>av_-Z`&aSL1?{-%YYXna4x4J29 zWG0o$%rDBJ#ieb)*dK3+*z)y80+|REU z@B9@PqmlnOt#_OL1iG~*eSM+roXmJbJ&a)}3?=&;x+Y&mw<;(B=+0})HwI8h6lC_8 zly^1mf=0dwMsPR-RM+;>wipc?5_P$i#(@GokJp?q1VNl%t{o-(c_J0Vud4{HRK6N!qH&Tw?ueEGQ)*fHt+F3JzCt zK~YOfJUDvV%PT%>wpP6Mc1Mb^(U5-V!NG0LtoLx zIz?Tz5U&_9ZJ=L5x_uFWA|k^BKN2qw$FxXYTi;`haWg+zki3&jQhX~4Z1+ww9r->b z*4*q@;tp8;;*L&+6Ira78&*+WRxC=EZxDoC%ZMPLaaaB^MyDW4nBncthEcW`PxL4s zzVV~<1Fve{p~pi26dCRMfZ^Zm{%3oLSyLY_FT#Cv#QqC3iyWaq@uR&IAXmVc2TtOP5->Y7pv1 z>1Ghb?#X=vq6OD1gCDzIO5L*2=eSm`pn z-yEJf1^?^?D zT~3(3!q#2B`v%yFdjel?|*qXoay8P*+ul}PM&V*tX!dof9z4jOeM6! zc?9giM45I7m_uU(vYd z$+z`M{Z%=xTl=U)WS@h?-jCxaC7wolzKDNh$KCnA&90~Smpl%7FumaSixc>W^;O6< z@TCygWGc&i?cRKs5pW`4B2i|z)$JXq~B zq(ABtav>3gZ*sZ@Y}jFG95iGeIL)Xr&gv?nXq?}C5OAPLJ7KiUs@5Jj$4Y%jb*PSS=&J=v!!r*xtH&d87$A zWr`jW*-*dC2?`PR4%|yokj9UtNGkM6JfJ{u@(%Usq}38jY59 zxp2vr?ijR?0i;v3B2`#)2qw^XhH*HFx`>l^jQUFLh9Vs;(FmfGr=TG+LxzEZa(6Hb ziJ*Ie@#@$SWrS6>VhCfN$>BB86?7JSx}UJ@@flb=MA%Wx99QS->%-r!>?4MA)8HQ@s-9{5|ycicaQ!Lbl$p6 z7-0-OK#q2__NmA6pJ@aV;+|{8L3Trd1b=s&9nxvj5y_8hM+R~EJE+z`OT5-PyW^~A zoIa88RcoSEKc)6r>Sfz>V2N0qjVGBL_zcgel5cs`xl~{>Sd9mTTzle8U#@iji$Hph z@X)AiUQ~qvggm?j1fA?BbUON``cjD0CFG&0h1P>LcX&%3em`V-Lxc1l>t@3)n==@e z;aI*uP6pXxY-$t<42!K}-7fuI<-ghDEEkk&>#~;t$6n_fnnlHQ$f40LsPU%vOFPV$sLeAVW)ujF=#p_eFsT`w0Z1uE}+KL6~j*^$qnz!zrD z|I>+gg4z)@+!6ib?CYujgnyNsYi@(y&M;ihnO|dMj4=tI-O!IW$0occlvzh^eArJs z`x|f2l&)Xb?(KE@@&h+<%K>Mbu&KDWG|a#l9cI!Px1$&D6kS*XGBRIx3fu zI0Hji!m^S&+_U&mS}}mO@A6fVo&gi@iH}B+DLg#s>}Oq#GLok@R+6U=KHJRT|KpGSVCBb=d}9 z7UQwU7A0ai$x6LPGppMUmyQsa>QB3t%fO0p`YnoU{_$b$uFaXrhe+%x8w%P@xJ5aVd(u@G9o$iLci(Q#9NJy5?ZwX*r;+RW1 z6_`nG;b`iZuSKLg?pz->*obqog&?8(242=`;e=R7^F8Gv$*wf`K>Ng_Z^;U}fb+O+ z=j#IRrl6Di?U%c+(0!Hn4BdUd*|=DQ5IAjT4-D*EA9^aPlJpGme#EXp)s9KHwF@4V z|3}hUg*DmmVSIF_bR!BP-QCh1(%lWxqmh)9?$IUP9nw9zOX<$h@y+);<75{ndv^Vw zJAU^9pmDO48BsSMd{jT8FAEV3{Kz{O4A4Zmv966usNaO<-klJ$AB}WfIsnaDhqc78v^qoc^<<3tEpx+;-&VuKe1 z6cZP8PHz#9Ww12ZFBrOK;;96W3ZhWAb)kLQj+tMWhz=jjBx`AUxkdMFKSx!@du}>o z$(*3gx!k;Y8TC#SeqFF$ADn{2PFLb+V2k@%pn3#x9A+J;hpvx$J#lxvy&?&E-w<7A z&0&0$msllmiX+|qN;Dz_eb58i13^tf;Ne8R#*V`ga>xEhw~_0`;EeG!iw2$9?hwz4 z*oq*S2Ol@V1_d4xFHD*Di@UzSZkB!524!|3os(b#QT`@MHsk5?u~jX1y)iqc7xtBF zA*!BjmREbM3v7}zp|>~Df3J5eCikFl)E8{aMPU-VNUlED;&OHE+o^m{K=RACFRxb$tgBBvVS2HK?!lx zcBT1A7IkL=*W;;_Sr;Jbk29=-C_f{A5nf*HHP8%b8ubddrS>>Jrm<2I4H17*R#rdb zCj(+5+Xh5P#skBDuhSGx3dV3WeLUQy#Jj4X(jA5rst$y$Hal(qo726zp`XkaNW2~% z%X+t=b3yaL*4Xey@rwT7t|8Cip^#*+Y&t|TxAB`J0T_5Z2r5f$oy|J}%~aXgk68!Q z4^=GeG;&`sR{xOxx`|c2Sonp zqg+pP2%i)b=1wNE-W8m4wl*-kKNL}?FwE!N^j6!c=5Gb`Jjq&lu*}UD#sbH}F7;MP zoZXX*y~s54us>H&q=@KAk=O3KleP!$QC##cS{`5E;doA` zn7kFF*9^FOItE9TNLO^0KMBJkN?ljjyyb^BpTmhAEej!Ee23mAS#@}{1|e_t7;Eod z?E>t?PUw#w8Joik>)QSi7L_81FRog(N+1d&v};Zz$AfK^TA;=BAE(Q%KoGRplQ=zC zQ0_PBt$1Qtz+=COR(Ezde`C^McGH|=x79N?0|pdKr=|Cr+OQuj&af)(J~D~oLF%o* zf1&-bf%fyxC{muP?TZUQf|#J&Gd+YknW>5R?ECkUxyQWCcV;p0tVC^GSfywor7F9= zliGNTU9q9x)$3H~E+E-C+wnv+!CoU#YC|rV#G6SaNq5lWo_Z^KFIM2`4OE zPH36!mSjvZ8-_58FOuaVH=82R#Z!?NH&T)33%Gx-A_I%Tpk=`hl#YD;eJ|QGBYy*g z(<3%nMi(x^#G+{8Yp&cJzj^xbofyU*W68`JMEzTog(0=F)(PlnUUCA)E$~#f|GzxPhS0w} z2i>X;Z~IN_WvEcsh_xwjWLtHWa*)p6P{?3>lKgnAD(sJp6ItWL#^K@-JF4jHY{8F( z7e&TrNj z0ge84>STJ)#KwB_7~?DX{M4gAWb|vt-K3A#{;B81jJ%evwSLoU(XBU^;rDLfvinwL z92G%}OYc!kx5eXLy1$;m$7pOeIvOk{ldtvX_3`)!Ey&AJ%O|OYXU$6PCx4SM)B*2# zRG40tmB&~f>tA&?Rdg?EqvJoZ(h&dsH+tY6%d^Zi^tekb>%FG=n{322W!AV~i@=&_ zdNUGEInU}kd66v_s@-;kbJNjNhXM*wAZ;>CTJWLlHmnfgS##YN0#Psr5F`97eZhj^ z8nTA))mcK=k>}l%;prI0#I^7>#)GB- zoMo40DSvi-y;@;%QRWi}i*6jC_tEPMQj6;y1xU~@l4#$>HSm0%ocJ`wAEaL|Xitv8 z$R1iLR_%CO^%86F*0JCzAN~wH*dbU%doHi-Y-Ads#c1-ass`U!o9qv9&~jC?^i#`-7#M)keW`%ME-&Cpf!kIysO_^ zb52TToVHqpQ(b-BnzVRGFGYxzcmxUk9hfJP49deged!L23VAaDK605=(u@0{V{z!@ zVYl8l%?)~RZabcx(@xE+>cNbNh)lrxUfDjc;kGbI|v<)_9Zt2UujW!S|?C z&i|SZkiE?NT^9eKMMED9t4VV+<3D$p!Us*orLA&FD+fM?<9V|SKQH1Bvx*p>GTcI9 zZ!5~XX|TD&K)B4L9AvEcf$!(XchJe~bL35A3y3O-a(tdg0J3?R4t?sc3rE-Jc-$r9 zrTMa`29iaMmDdf)-=P=S3n9PtkhHF&gpECNb4Uop^NRW3K&mU z=y0oXubx6mBZW*?bj_cn(wMJ31`DL)E?(|LwuUGJps#9g6%`L2Zol>R*Pq4m3DscM zy|D^~Z?{HTEE+MeZ@kKgF9fFx7h{`qto_Xp^FeT9H2g59%yJw&uPMhNr|ry5#_UJf*EPN?`0qqQ_dH)8czL>m zo=6F+703zVLGtY<$XHw9f^p~~#bBVBWk9u`BXDxH`sC~Hb~1;V7jPUT{#PN-ZY!IN z#j2^6emC+Idp`d5^@L@?O);Wrpni3Gl-Sv{dbh|7IkAKEwyEfRl}{Hlx;@Fnbp~d; z6pK{cLi5~6jNb#8=^ya5h_UXAZ~>UTNR$vvw6JNnJ-%SKS`UF0^bugtyd(4JxRP!Cj6Y1WWQUDk4cX>!V_q6z%H>RgdJyg+9~CqyG9BB18AE5N3~ z=|CiMU>?9t|1pyW?Qcni-Q>ubiUQk4=mZWw z=Y^AmnR&K1e2y>S;eslD_wV@>&PBq3SMwK>bZNJ>DKmc$6i^BVs&&zSayKbE_0+VQDdgrQUM!~xQ zn$uVK?Q<~_gNtAT^}_3YenCMI%J_?&L-ptT?YMhrPwevI4~>3@R~|)UJi}Xv61&pK zg27XmOT+JwxiS5Smf2@kxnFdnBAKb6?)Pr@ZwETz?U#V7E9>z+-xn zh`4AhyBr_2U6)j4R;M)M5RKc1;j1Q)+$?V6$5x^YRSU3d!I#3D<>mC|wvv)Sidxu~ zJd#TX$9Er&P1TJ{GTuB);fK#plC%R-Xa?($ooolFT|1*EyHQZiS5*}l@R&F z5dlr7nT8-}L9lRf6n6_DZGSD%vxyZcytrKmWHL0`Q{Q-b=WT|lnfb}!OfiM_(MNCJ z+HnE>noDBi3Sk>xk0_u*=y)D{PzrNxNSG)jeTyXoUXgw8z*w+zyDL)iPatL}OQOEA zCQPu>)+?Boc##HtC-`D7aY4}TAe~d4%E~}Xw!n?8yd?L+A63qLyTZ)!y+>lv{fXd< z@n{l9>zO+v$hoJfLB zN9EO9C(AS;fgZaC7Z7W{uV?=U zg#)h8KwmX3W9DfrF>ayS=fq3PaF+@|rl-rZ* z0{6G8PT%g%$o0El*`cV;NY%}R`$m6BuE9MEDO=Z)W92g7#f{xFlTm!T_;U^&$B~V!aa}ZAmH@CWU6uKcRT56^+Z)1rfM8Oy0CCCforazvYbSF%( zO)e?=7p_Qkslo*A)J9CewXBKAm8iq{l>EJ{ezFFd|sTQED*2lCfCF3l`Ek*N)MaWBUFV!_VtQ@6lu{$@01@O zpo^{}eol%y%aaSqo>`WJZ9M6W&oyi1OTs+t(bD|Eo*mng9UB|MjzOFY=LYUis_ii> zM?8?6s-_>&h}w1l!&_?sx~2)j!{e@o|A+)h6Ca&K@unKerP|rnY~!L5$7$daYiHv;c=`FSBQ7h zPMzHMm*N(MtS3-`4?n>>xtdcYAXIu3k#BQI89jLZ`HS#;a@u@ZpVJuwo$(n}$Hyfw zs4kH987=y{NDFM)w3g0f<EC*f!Z-sY2&}U~U$7++ZY7hK-$|9!|T-3i!X}fwAQ-lI%$k@%@n)e>A zU*&|L%D`7NtJ&*ji@NLOmelud-SeMo!lp=S&prD+d{?(6ZL~J+`$cF7tV`PHpYvpe zu6giRt#n3c@_cghs<*f3* zWd2?&+0R2Y0ORrEQ^juifG#b6-1@%G^z{zVe0gPdU^bDnx?Y&wc>}gz-p|e^jS15J zO6-JH+cEK2Bh}62mp)=Mu}R!=SfD~=;LzRJwQ>q%IQB{HI#=UmU0c{;_FlE=3FBHF zI9aJcv^I!3+}Ff{taz*Dnj5)5U&mQgJj5I!t|`AypX`uJD_1{r*+q6*vDSOquWo2B zd$v!-5Wk&_FO~*?^30M6&dDEE(Y9y*JQ~ujaAEg&Mh3p69CNa@4k=tZKv3vY1fi8u?Z0%E zzw=R9Lv^ft*Wl^;VOVf1E%I5zwR2fv$-Ds{$2F{798rFs7tW;jas&l>z(=ukKcb4f zdXWwdt&Zoo&(D2?S!Ii>q7ZFBOuOsRAsWRg9%Ie$&J8L_W$ke__hNa99azzxp`GHE z&Z*_8{zh=7@o{dfg1-{Y>guOF7{B{*A-pWmtCIc-@Ne zoSQ5as%8~+F;dd{@19m!PN}HX=_;|_mZ3V%LARMdKYTpF_qQATP~PX`{{daF=5Lal z+wW*q%1SYR5|iSbX3{KV)0jP7mRtk`smN4FzP8hJ20%ZIiA6}qqe?3C=gp7MRPXD8 zKC{rpjt^1xv-8xY-0^Ckq+}y;Qke}0pU%6(p@dc}t*VCcH+miZ(?K)lt>hJISSNIO z>_CQNkXrvi8)B*UfMiDl6S|H{gfA`6hpJBzm*+5%_OX?J^j6_CEmA7&PzA?T@m}y2 zU}m{?ZBXBtM%)M~t2VtPmmdfM#uiMf^%`9ryUpa%U#cMFf158U5#|=*gNe5A;HxiDD5iiM|n*O&jO~Z*~LI5%L_;)W3 zSKe5PgQ0<+?f&ZD1iI+MB>2GcsTvROqyGH)aB#Uz2IG9b;b%Yg5CD$>%Ly)8f09C6 zVOGtxXOhJ+Y@kX2DJvt!Usdi(8h8t7-Bg3# zR>`6dpk3nK2Ghe7$o;y?axAX{2PyoWVZb)m^H}#YqEq(^H&kR}m>c$^-gvXj z#5=bb%^R#GN_XSXSMNn7`fV+wOM@ni48z2hc2L*3Pe?y+24ioaU4Sh%=~z$%R*kd* zJvA-sB7|S&mh{x@3&ll)Hc|3O_A)4suV;W(m^UO%c5RcgX|_$4LIf~rQjuYkI4Tl5 zeKW{#xqwebgH`lOSITfkZORbW^x0UeR6Yi`x1AM{_At^)IX@Yh>u9_{*=!RjR!Dg% zpw&sDUke%PnZ&-+LD1dHk1dG?Pf8xFfGJjn;wb)%v2^zn8ud-Ja1K)hmfZe&pc z+I~LM_I1-JhgiXM+K``pttB>aAtD;kXrx2(%daN{+b@bHM6NqyvA<`e zjIbC3>RZjPOOG$eNIj|#c9+>@>p)27&#fu7DTEF<6tmBaKA8x$x&dFRoP`vs>A>Ie z9y%_!LQ?9BkYqU(#j#mSc0&)u^bQLyUS~!u_Qmo)RaVgl_(*?Q5KaH+;MWK2mv0g} z2AE!tnHN~)EPZ43pSdB!FZS_akLxV3Nxzl(jg}1EUNHTb4BNwlNc`9tc2b#VcG5mA zKtnTyijXn^%G*#AJpV2k)tKxJf5xmt5*7Y8?90z3XrkkD(G`!4j zH%LX#=}FLxkm>dH#m?p*d$=|WvR(<-@)2KxirS;Y9Tf6(5TNN^*bU&mgYW&H@Ro1e zh?tm|y4jG&VvEvG4EY_kinf0itz??{n|~dC4hy&Xas%N4+a`=3xukkvvwRg*>G-(3 zB+$pyxFVi5n!X=`XLjvPZ{eB&YdXFKr>Fktgk}rDVLBacaGZ>;+YD763UROvGis*& z{&#)^Y%Q$jRj4ooL;`rAKA?IFXx2vMQoI+ z*iOvq%*W5gAw+>C;G940=UshtIsTCWuM5I+f9!8peSguf2Onb7Rx-JqWT^$koDop)|Z)ut3q zh;B}FUe<%Oc$U@hx1xy#F^{g)7fuDHd`1fik--Q~E1Eax5ZJ8+$ZGt$NuW;i!6Bn& z-H~mm^{jyH3mN5pYZ@jkbUWfbHQfuE#YS9?$k4yh+-0krZD_cm=XxK^HWZNt%bl4P z1ncUMO_UlfZA#beGZd2Iq!6n^QFFaVY<|_LB*VMX?63jL;3Jf+EK*=WHnu%=J&sF; zXxhx2=6iQPHM(@$dgED!RO`Mt+ddL5oLAbg32MgSMNhKiPby*XajibS!1d5I@PMb) zf!nB8wl{e`zD%9}w!Eg}KlMU7JrEo0Hw38sKNg5Z1#kAF-Tjv$B38Ir4yvrjc2k~! zhjG;Rw^@)aT{z?V<+*_zo-H7?zPm`8YUPs?Vd+M+pu`4R&WsdqySEbe7Z1znrh*vb z@<1CH`@UV`_l&TK&LdmR+?2JCOsnhR;dazN`{z$sg zt9(Fa3Ez8=MvUjq#K7c1;ziH&6*B&_nr2A_gOrI;e3*Notw*X^WxY%vrdwoTPc_gW z`>^IeJjA>b58xZhe)4vy8&LK<`&$tA8rl5{G1KpK!H0g3JM=C;keHT7e508=!bCi4 z-y7dp=oZ^+sXWEzR(F0h7{Lu0`UE=Iy2V{+=1}32XS2@^*q$7@7yq{SV^U1T@pJMj z%$F{V0}j&AVg2Gj{nRA<1a3P`*nj{mh8{rlz zW5TyN9rG#%A3r3?Zb=+Ldk+Y$R>(Hr-Z)_PnB+0AlG~#?DW;kAzJx1Z99kJ|RYM<3 zsKwLgL5fq1!3}vF6DGEgrLw+RwdejSJ(JsJP+dnGQgZVo8Y^Ctn~V5Wh{h0+dy`_~i1ECk+`^5|_%tEukWJ7s zWnLj8=PVp*1^eM8Yk?#tisOE?S@hCq76sYXU`(dhezxbg-O+iuxC#~V8`RJhdAJ7= zdpWvqg1C=6=MtBbaP{n;R{~W!f0)*Ucq4WLdk7Sf{47PNtiGx{!QZ@Kq|-hy+JT)K zNe>*AoeY%<8n~3xy&@hiN%hidZT1ZZnNGu|NUvnd3~6|-cnjw=0`@GXj{CloHVPAq z3`Q#WvoJtRWh$n7M+ry?#tsI};|xg0IZXQ^)b*uG-8xi`Rf%8S%_RYWirbr1$L*40 z@IM@7KfclyA=2(%v&bw5l@FeTCmeUTKU0bI-ju8m|M;amP{5*!UKc5@6Q>Lk9Pv6rKJY#JT_ur?vA zz|&j!+Hs@3%by(AsjZunu83Z!EmXz|+0{wESJPmX_K1-_-iR!c|I-bjPBqP@;gV!zMz#Cu4^WKT4`K=ndi{?p0GCqVYv{3aVs8r;wP z#8J<@*~^nOpAq+*6;noRfoKgLiytaz|H?{aK=dwxw2Mx4CPl2ifL* z7%ZA0^lh~Da2yt(x6rl?qQVl;VtXbKw;L*K|k{AW25sz-X=}x#Je5}XgsNIU~Hvcd`y6H1Re)Ycxll1=`RWtytMuMdau4K zIy#$6RD4#SA(I>DXR6H4>7L1WX{;uc;dH|>y8>XdTaBwVOawh+9Ce4=13Q$G8HWYyDbDo*>;tuV*`|g|Aa%3nVc%^Aa~?nGc1vIo2hNq;}!$ z2qceL*gG)*h(jr@*-w)%JJ*`~c%6&~_-Jn1O(48tGFwAD{+rBlFVA#UR=_)5 zo#e-+RfqJn-IXl|mY7pVCGnn+BEi|`E)7nvLfk|c?-x5`M|jvJu$#vB=Dmf4gJn-$ zrSIc{oeH)cl-Q?vR96Tj%&!{Q@ck&S*+ITp`M!PReMlgF&q~}c@aM;e; z^^I!D=cJ(7&<7BmrepLUoCRe(xjgx{)TBZrN)1`Q-oB+9v+C&(vtP5n#_gMRbTb42!g}i_=G5C9HaS0;#Q5r+>~sH# zYo=a(JcWohF`Wuw!^!zTgy!b2$R%uvoes>cHokt2?smwGcKkXu#^*2Do`efMOQFmZ z#pl0--943=Jl|9%1H&faltbEyetRgabLc2q${53g8t)_{{Tcq+OW%Kpe{o_aG26vr|vEWZN;TFf|^ zO;XI^nN6fl2_>pLQ4xiI(Ua+X>~Is%t=EV1TjEBd3?PJ3()Mo&^F1d zD^*r%x`#5OFDc(HrF}0p+Iji;k5Y|ij*GnV%>>kPV}6jFO6;^Fjjujby)d9P#l}_q zE^C*nt&CB5AJi~@VV-D6*dZcF5_6eaC4a54s|w$&$iTzyG-=QfM`nZz+PMlLje}gn zu7tp@%~_4P;(Sc`kW|2ATg8waY)Hr#E)n}a?VB$yRKZ55@Oc0L0s*pL#54?iUm!ft z*8$3ln?rSP+KtsQ`y4LbzGye6iRkE}c$@)$Z`TPocI6y&fhwRtIJa8Qk^1=bHVlzR zcMa#oLWB_iGF;vme2QI9dP!pCvaxNxUunEd)wJ2{M9=-RNb_Lv>5#;(2G7t@Ncq5*C}*~ApY1nh{_=N|gE z#7X*}w<`v{bE$)y+zghjq;83k6-l+30)kD*gsDYI=ClI)1HIY?7C# zd`flUzc@)HLsfB%TBBqM->P8Dbp30&IxIWbNH_+xW=Fn*yt5DM`+F%Tu0N}!Ve*{g zo0M@B`Qk};Y&$=8pdpuveGuO8RT5^|QCa*{tv51QM?oRz74dezO?fHu_7uBQqtfkq{4H&$-Q;UDoY#bcrMJabL*34=%=SGgR}#Yt@dPkz2fNqN}d_ zZR$iobO__&ztrewo3zd=g+t$18RGj=is~O7zv=e(@*Nz&yGu5kY&1X`kV!T{Nt$bW zH$k*374{cHspr395B#S89l7w^Uo@x>=RhVRP<6dmsY@HM*bz&yl()Hf%`T@o<|L-ak7ha9Yvp{^%~?tg8uzK)^D}m(B4vXP*4V~mB;8NIBOxJqJsgdB zy>;GQjxtnhs#IQ|6s80TuEq`YjqEK4!mr#nJ1oB9_i->JAMJ$#6bR}{1I3gV%^v}( zKNBCCtVbqzgS6S_TP%E6-%j>>V`USn9Juq~)};hT1qD`0bBcX;HB}6St$RLkVoP0v zKmotiSF)7S=9cw7w33UFJN+UQi3CO+J>bKRa*)WJ?Eb|?zpT?9JAc3U(I%O0H9g-6 z-&Z-$VxZktv6}|;I|V%?+tXzj!% zTSlBAPlXuOWC{C==$pPVv1j@>9PqQHkfZ{i5i!;6bYN4rQlWZty>FVi|iM~kH zIGl#0^Wf3pLWP$*E5Fy{FQnJXiKi;5dMzozzN5`h%mV*YerAJ?TY=Jw3if#_)q7f-*%9bDd^jH2>Edd7wdv`FM7aB*7~!Q z%uPg<9dfPTkVNFEeoa$h&BO+2dsQMa?+LJMwrSQPg>IT09pkn)?KI2**f0H~{tq3P z9J*Wl7<25kzwUW)HKrA{m}kDI8J6i~lTf=`clpECna;UDlaRC$CU@^(OzSPFzO%Ew zdb&2eu6^_CPv28Igfumy93r~(Jt3neK5gz1uViA%4e$ADGyDc3pE#>eBj|}lsAa%7 zIB{h{j&tCv%Xj8H73~{hG>QsH5(rsz3&@xZgZB{QGcIR}>>TZ8G#+$nHq(OZ;(dLWQ!=f3xoi^7#N ze3re{TGz|F*TuI-Bd_)yBtAjj)SM{O`GTvm=nr!6U(#EWMP68JnVj^T>=E89f$IAAY|{{+hmi_=Vd>?1Ok=P_6gvV6DZd$j~xS= z4`4c_8~h<|Bjq#b_rdil@!l8l~bPc>#L^KA!+y zChR(ve_-Vj##<5+hONSy&1DWdbU|@)cXB(a8V4t67U3Kef5fEEWUlSk#036i^#ehN zvd9YsU79qT`$(udPV$%Z^{=6c!IHyshkKCNj1v1?z|;o*5@@!8d;izFch&5EKP2B%V#ctt?;m#vmauJK|?T7%GG3x6IF6?MrH z$19*XQojuZe(L7v3#l|I?*LLIQ|OgT1S>zwL>uJj{xDli))iHs9Z~P7P&=q3J(o#x zFshzKn4&R`V)x||`oP##kbd#Y-vfcS?nZCCo!u?S15M%-2|)$dbbMn2D6l<}SaN^B zLiqooxh@>0l!R+f<6N@qdxA$XR&9sU4=kZn9Ts_a6 zFPo_W761MIxb9E>xxeR)A;6sSahA^PWCE#Jo-bKRXO6DW)`SJ><&B{0U`ftLrb>xc znobCBkLD{wODr>pl@2*TX5U?p<*x7pJ*Obay~U7rX=!@KFUy9+=1)WD3=5ttWf&Y? zf5L7Db8h6x57Xt|kIDA-z~L#XDcaN5B3vo( zD2I`mD%f=$n!6tzU$FPa1=Pg$sfg zN}V3w*^|0hn35`+jqXmsJe0FGa=ZDbc=&-n$W4r>Ol6B8YN z5T#)kQ~myn)~5TqVAnI5SWy?MfFN9Z9yyU>;ja_Xhx zmDsI^wkA5G!-=WAJ8lhDG2vJ|a9&9i&nKw}z}uXPEzWfe9Y&Il<=5@Qhvmih|*aw+&BA)8}-jzjSG z&&mKxPkj*EDU9Mo4J`K9=B`xaDB&kW@xSrx^krlKcD*vJZ-$xS@%AK}EKGX0I2(t`&)$^*9o9SvmLQTW39 z@1UU{u<#R^QNT49g|L4XO-=6{fol9j0_R+nPv+AQ>?Q@P0PJ1I%jzl?kx?{2!j89= zukppiMOlC#ZMJvsp~kPNZCJ+1gmcyhy#SvSt)zC!$z}6$Z;YdKnOEwX(QFw3dCKf2 z+W7pO>m?ybu)ur4LXa|Qh%h>q;%DRW(us;E(Awz#O2H&ZC<iw)3x@~ zq_A1QqJ^RuzT4D>&xGJGx`y%awM>onfKk9X{iD5thiko7Hc*a0{gqh7IKV2hoB2cv z+0wY8Q)K00xwxj>`DSnOI8M-P$bLU#qPo~_(F3>-1%_xrLyAyOC+eTa*y45ho_kh9 z@eL35JIWDD^?uGSR_AZJL@MK$xCOk6Pb}h_$RHN+gCu*CMw!r>z+ynVciyku$G&dG zYx`pav>*(3q2>iGqZNA44xZIorz>IiTOZ}czhvC=gKG=$n3j8X?qh1Sis2JBe;-Gj zRhYg(x`|A-}IEyVlaYR77VoxB%I3Ww+!BX6B zw^Y9nZ%AY`d?HRRe9CtrHCjPI`->=60ptV9z8Ru z$%ZL1%eAWiPVj8@LmljBC=Jpjwqrb=ola8qB>GJqZ#6(GC}ql;F~-s$)vx;bJ(fm+ z=v=U5fb^ifN<7v>>X`z!ym5;LEXPFQZamBu4DU+0)_w~Sj7@C1nm1F1dHjMgCbhT=rjZ08{k6WR_0q%{(8cHw-7E!lUw#W{&}*_BRyYGEwYd^x9+7g zOG2aO<7Qorpp&54pcH-GXsT{H*NwI=55*V@x9P9c<;t0bi6Si)(lGvzj*8`_e18x* z<#s(-cdyLkGcNiAkhvmUcn*&5R#esa1g1}pJqktEt*T+k``=ev0%lnac zjgVz{Mlt%M>A2tIv7L0nt`aX2k!TMd8!+=9dSho7Mh49x+zSD#%AgQ6IDbW@9e%E! z^k4eM4mQq{1+B#~Uve02mg(~GmZ!Sx1c2pQd${WY8~TS2#xM2P2Y!=Bs{=UJPn@uQ zLgXXI&CO0n4^5PDH`Yx`P&7)^(t7GF)M|Gg?S-xjAes+0GTXae1Oi?<9F-Z;3tn|4 zWEKxU=8#aQZ%A1@4gPe)YHb|7KhUKxSQG?D(Gb$~WjdIyB>o;WvDqSyKwrB!GCQ|@ zy@HxNzrS^v2^MrpfM5%9lKD@b88MhQ7R$Wup?kbgQR|k&mL1Wwg6#!rnc_GjgAs3!%)tONjvzQPVFCV!Xk-DPo&@?CqJw`BeOy z(?gN|X#~$5{8SfpRma(X#B@6a&731c!752G$!pdEyRpz^hV$1Ud^71M6H_1WK^ zQzxw=ans^-AkF#h)$e(?nsOUTc~3u$`7++0;wRF?M zLeQ;39DoD3aO(~-%ND6Tj}W5GoxX;)1MmABo*~Os|h}G#$!2HutIAljeJG?1U;7gL)Z#*Yx^$ ze)Fy00xynS`blqAP87*u_--hkQzzxPkmvQOkrZv%tqtDYt}QoP01;ICKpqrNVr_OF zd$eMKKIRX{{V2X+))v{k>2=(D{ML1%`7dua236;=zv`ON3JRG`ulxS^%`KiK^gC~p zlpc0tZ)~FxHL5Zk2*C*H5p-#pC^(|SgpnlmkQYeG|TiuzLGC8Bs zykY2qfxdK02aADq#j*W$ro^YB%;h#ps^rd-(&lU52kAzn}(>=%1GUZ9rOdV z;P&WmsQ9%gTkLjq8>W5gXwH&eG|9-gtiX@TDFt+qlDI!(9I1@Z?q?nTeYgI^@E8si zjioxDu6!jUv;R3d58z`lwr%Ckix#q8>6@ZLU-$*ur`RbsmFl#+?pf-M_7_Ms6^s$} zQ*>=2xZyXJ(%bFUsZJI>KE1E-^{=rJl~e0Q*rjF@2HW;-{-0|b$Ixp)l>pTRr6EDA<7o|R?co%aCb43&eb;k>jlKDR_>l?*m zDZLjfb~;7Hi#A6r-@L+w+fP$Ezf3=k@*9N1*LkZ@AddNwj3aV77WLonQQcUpBfVzr zMYJ`zVBqK)02{dJenO98XhNELwokIes`hvBz&v@3i`qPyiRD|2;*J@<7sL3CNalqU zsF}1N{MiQ*Yn?yT=#f)@78jiiBh(gaP<4MDP3ET%^bxPYmC~#~B#^^ua%-Vd?|vEV zAC_w5PYe|+vD;}rvffCKG%sfyIpE6|Xgd_o$oCTA_59pF94(c3 zORM`40ZPHNMSOt8RuldFN&L(BG0MYpOR_~Y649sG$7c8~j0(x1E{#+vCvzW8_(v5_ z7?`CjFP|Hd(z!bxX|9m6yFER_ib2N*Ley&Ot>mD4&EJ&7AkEmxYkiJ}eKXE?+?gmS z9DAofSlPtc{^ke;#5#39VFtY1n?ZB9cYMd)`K?(;m{GSHuju($+AH1w3R1Ytca{;r zLFV$=#CQ)!Y1OY3r^_2XUQr|Zm3lgULlozxkHpt9dZKL&+K^DjV`m^P$0)r@j>2CeNh+qB){szK5VaI^Q8-M zaLe5u>V;5IMO<`~$F?~l_s>oNyTP<*Njxf#S4ZY2yH5+J9e|YLl|vlD>@RodrS(bb&8dj z!N8N*8r3K|eQ&ddOBBVn2M>*e|8RhG_SBs5B{uDp8@q;ZH z{er{s%?;`8a!BOwNh=30w9AT##SIk;#+K|gJno_a8r~m1gnT(m<{3==sxbh-$@nm9 zm8W`_{adUHXRVFU|AT?p%VH4+(^9(@WxraN8dJ4` zK^@FvQ)QyU7l|M$hx3jbNHNa@%intpWwPC0hOgf5Br0DH>>F)OCzgmpWq}ZS1S;Bb zwqSAkz~Q#0sv~b_03Wx}!jvBloFtfsLHAa>Q>@t}M!Wi);-ZzIr>93_U0JXBWWVlX z^v2yy-);7s#p0QEHN^;XdGoS=Ee(D}tU=5)PFkD%dI=2aNyovyw2xLMPd8I>=BoFS zHxUfRqYjvRh4l4}4|;h&)8UtY8nL?lMWT0O#oe%`=WMS!>PnLboET9!3_QYq7%)qf7uS>^_*1F}30fZmF+*$V`CGwMVp!&L1 zdzeuA6r8kn^m^61cEb~0gBgMEBg_WfJ9Iie8PEQMGRuryBw38bClAq-zLKLQ+7byBld38U&P~ySt>j zySux)89Ih~$LIfkn~!JBI_uomeeJz}yB_eI0E+qpHwpGNaSA$@id@D*`o=sc7jg$E?A9*69xHy#{J!JWbW9=tVA4}7l;z*9WEtl0CZy!R|49!>>=6LPGAdd`& zz9R;}ecbxILp6!()@p=CcppLJ7T>hYV9WL?k?Yx7Z-0j8p9oPXi|zUg`tm6eT8u2# zVj>Npxa|H=g`-Ws?QVchy(aPbN8)wyYjOF<^gRP6d8&!^FxfL##1-C-GeOQdEPf@s z?@BYR zrr}+fXq%4tr1?6_@@ABK#+&fX^y2^B^xP~SLut=z^ih@`L~m0G@0(e1_uTat+fVoU zVlk`=J@!{@3=rnYlItiTCo~>QDA$+F)_`Td;H>h3{g8-q^{;FRRpiQ* z_1MM_ut&+3AX`x)E-TSAL%LZz`~|BBNrq0s5@2PnX--Qok6G?| zCid5mOhtSQ$hv;u__QU$@h1Tc%oIu3Vdgn>Ek8MTxA_8Ds?}nN!U&20iO!>ZAE5iQ zThh2U%A}7Wd!gq1javrQgAq|@&opd4VZC1TkMe9^(9&I0zj(RuW2>(gQ*HG^9eeBE z2~^*=katgH@rX1>flm#k>@H{jaeSBzjcnP{iUDa5Dw?$xT-HasHvfBo6-VaMTzOsj z6%%dtAlng)!jorsZRTDDuc9Hxv^f~Fe}4(l+di3u*gssFXbbqD!-N9n@bEW7Z~x@= zs{6xZkVLC&Ykfm?+x-cjMV+aw6H~|~z7`w#a1rP2{*CRi#ZG+*hSeb@od>9IP*R#7 z_xMr_Mo^^WqyG%Y6yjStEF?Ls9$DNpD#W4;~-k4elpCs47@s(U!XcXcPJN z{K+`9`HpI%y4~lu*vl@*{nl{#`Od@DN-#lj>c@Ui5gbMnhx)*~G#{y}R+PSl3vpYz zp-R@(;P_|JD}Nb%pV%p^Y!!i!c0n`H5u6b)O3VkFv{-){d0Rr$W>$!*y%vsRlo$colyt15gJ>KG7 zbU^|aRcfM2qs#MHXvjc z$&<#U9!kkVC4!z_cxJ6Svvupa+IhdkjD%4*e;Lppd`qx1^eI2$2XoR%rB!aRABlH)XPSVyT#07qhQ?;pQI{WWH$Svwq#p(e zkWm%n*0Y^2yn6e@$&IUa=Vc(S=&my?vR+2ai4@qSMq@n95qHD?3q5NEF$2CZq(35k z^J=`kFE7-mBW`#ew$7)AF;$RNBWv;TrnSv+kgOv=b?qJv`KrUQc@Y967;Qafc-ig6 z_a3WWV_9Su*B185_&dlHyp$@+WKai9bWqaMGLvMI>I@P@DLhzVFF<=^G;XWE3FqVl!d(FB>-wZ>XE$rwOmBSK za>F^6CabUjLvhV>!mwZzAVDF7*~aQaZyEYSGplnj@xoP+V#3LO5rS~%%<=I=4$Du_ z1jBxEh4X=jjm7#EBYIswS9g*mgL_9S*aGP!0xmfUz|6%wEc~7SvJDSy-Ab@9y-cWw ztZ?fTQ(nwy9rQ)*&g6e;_GH)P*N#({=7k2ytQfPzC?;nvr5LH~c`!&PA>lB^WTH+Y zi8QfEkI#B$IJfxKbkV7Ff8=p&s&8sAh2LTxz+I7byL5gVSbTHB=@iH0;K457u|)n> zeu3M={H5xeWb$i-^pXCla@#n^w|bQ~(n{j;uviEjY=v98`D!+vUYbsu_DeA7XV8(x z(Q0$@m|=JhnU=O7bG5JTp;iRRq+PIuLxznLCgBL0|7WZ@#qC9P?sWd$eD~lJ;gf^k zFD-X}t;SVZ=DG#0@81z3I(EhSG8$|jHIACbcNjyw@sKDcF~pPC3;mVQs@CB}m;53C5w?oK|`bN?>mf zgpI{8y!9vT1~<g%Gj04TdVO;=bU!xr=-RmmaJ_#oVB8-wnP#QYFYTh9+Mb z7Z=o2wRU?KSlG?C_NTORGGlm`JP=;%8bx&E&PaEfd*ch1-%)yvS`Qmc-)XF^Tl1|Z z<9Dmy!%WTk)q>(M2DEC|?GDM^nOMg@EQO;a_KXEX$JxnmO*AAki~*uLYpWze3Z^eh zJ7)i^ZcB2X#EUTsCWn3E4Zx-By#H##Ebm zG-2sss*~*AqJ=Stu|Bu*fm$^j|K#HxsB`J~>1wHaw7E_;l0X*PPbomxMm2L+Jn0D* zr10P26ZwAN5HMvs)w=P`EHy|Ot^EHIE<3U60OJ_I=svdgeVh-=biis?*~c#K%W~Nd z@zQ9)w|pL-s{e4Q^3>=XY@WEZ%-x$9&3aWiYuE)z(v%_`-B{2G)cP4eayJz(;!i;Z zT>#NG2tR(;p)`5V?E&;jp-BZ20f4#lX~&^8n6h561sj9F1o(W@fw0<`t(*Dvj&bkX zH)T4_;oaTc5{V030R>oaGtk*RRcxIpe&=rdYZU7^F1}#vu*<6;ImECFSTp+H=4>*} z_`PT0R=>FxT;Nvk<%mor9)yCstd(54P=a2$MY4K&ed!>ENxmb0#BQda!=qc^HI#72 zz{*`PUsQfxc>CzrC#&74L1E$iyv}KI4lLh_K4@p>M;)U&pLFH^w>Xw<2`{cFaB!b_ zeLl0($Uh7yM7r#8ofR?OyJwf4TtPoXYH zzS4T%!7S7(57h#oGc+pib5V9(fe2hf(OqA4lTX7rG*@JQvY7fRwtjtk`+MjJt12J# zH7zWLGHseVX=0c=_au4ls^E^3WuE6bDLgBJ20$e?u6xHs-M9PTEX-b{M=ot6PpUuD zLj45zhXR%aFZX)hg`<6|EV_9!s--FsC6a`KLnYc*kl`I9_Ik*1zxhbTX(yNH6@7kF z%UA%29e*aQm}y6ey=aRQ>_P?p)V8y}yWR`hC=<_#4}D&WqeVH@1CyScLw=XhRsE&h zoj9H*t80oVU9Y4V)f8JlBdw7yCxZvb_Z!rkU)wD5X$JOaz90#sD5=$ezN0RXz0|-^ z@fG{(X3H031r1NJw^>pZp z$EQSt3EnM|+quwe2-YoFz_g!n`@$A-zL+8 zZKs4}r`q>wp98jxQL{hZV}l*?ZQ7&XdD;ZjkhOsh(Tf^bittDh=)P<#iec#HZnHE5tD|ro+dzTcvwH=rTMT4bq(gEp=zEc>jQntda(Eti?zxXn1^s z^mLH@q=~OJZv&)JIelv`lBt-@9}O*fjjQw1-W1n4)!(@Fl>j0J_T1Bnqx4l0EA>C} z5gjUHmlpYU>rlb+bMuO)KG7z#U6ZP^XhBMe{(FP!3OJAHZZ0-xW$_BIy{h*B%|_E= z?Av!$V3`m%?If%?KI7NgLD^RUK1G>$w69!?n~J{lrFEwmL9-DT(rp=Dk0neQ&2$q{%!gQGLhG`WDz`}_Mla? zZ~%$D@uTn`e{AD4F0YAcJs$h5vx|?yOjt^RP222m;j7$doHY0BHu(JVhb{h)NvkH_ z?*3~sO|8mBpsYGM+I{TZni6Xr^&>UN*MTd{I)XadeOxq4boc-ZKKs_VHhLJCEuYqb z`Z2O}(Q~eyAN$0yx>orO4pO;V?Lt5Q>QQnO~-y@LmMN~+rYqnSohAU*#ZuKCP4h~+Vj{2;J zYg8FpUm|5TuPZBZ@?JLiuQUsqe3q%vwOi(PcQy|39FF!`MOE#2}8cz-@!0fFpZTPwtOa87NoKEW#WCHuA&e0Y%yYfBX62J zsQVrzYZadtuH)841II2_NDrx|v zE9oWmCa0~nwN?)u$3`IO2!`*hJ_+usK9jdnj{6tEalh9DZg;Q$lU|vyk}yaOeJ zGI@46@{NoPD5PZ7$$2JFa*D0%n6zHLlOQKB?dm3Se~$(?kQg_jB(41)?QS=rd=px0 z1#^c;coUC)C`$Rf91sbs#! zi$gJ8bKOCzMU1&R2a^pA4s$bV8&TBXDe`&acp5!rMtm{OM0r2_i_(gFP9>#D^e~U> zvZb{gL!`~n({Pt4CN14M`an!6qeAF!`CXj|b-P%j*SYz)@x;LS8)k&S$uHwk z-qLMXaD?L_W-S|pnBv?UNwtG;Z5NLeVfU36k= zt>KSwuUl*YLaQ>Y@r}L^mP#w9*RLf_kC1=zTI_pobeGLqV`$J{kP+pP80_>JzjQ*c z#SO3CcD1Wi&33cv7bdhTE47sh{h^|CW_D~Rn|V8n@!%9jbB|A{$S1$ky%?QON7wso zi<=C3GCbTOopW!A9&E!Az76O2u-ogwhkeglU(Yr%9AmB)4h(mLwsrzIo^;4qd5~$IV93fNylCZaDHUv|OI;&hGyE z>zHx-y#skgC^5dS`!3N~y5NbMOh*g>e)J3>7dG9tk3p-U4F3d@u?~Agk$sz~yB=_D znn(A%F5S|oq;5Z3W$8*2J8N(?H`?h|-(a0+yA-eeh zbHw4kbGth1tgRB9q3(`W^vJk!%vorkzn1-x(GT-fAB+}w2g5sghJyt0EBai;BWfO6 zruXi6#-hC*e7?gbw%6`10+;7TosgUfH>Mwv+yOmx?eBT4pPMmAg{2^rp}1Dab90S# zjSKsw(SN(!tkX(-$mADHUas>;p?Maz_$M$y<0cG?*sfc@X5K8R^?}qz(Z$t3VqN<$HI1KPBA)Ii%P*Q8O7sdgR=MV?xsT%|XpL_;s?(V8b6vhv6b+gB!203E{sE%$R%w3_JSp6z*V0EI4Ya8o z?Kg~6F#2~53g8@J$OpvrXOMCf zRSvma%qs2Ag=RZdsl_L=h#%<;qn8B}k{Po%b6x6qJ3;D&D7q-Q?|e@O!LT@OC(P}L zr-HC2xXIuCz^epYJtCQoC_O+bg5Vsea%uwhMLq)p( zkjBAJ5p&wr0^icA%roGE)LHDP#TljSuun?|k)6Z0oRkUj~m9PUbCo{P?|@{e@>ycnXiwtQDK3y_N5zI=#B%xL&uqLum1CIPSga`}XbbvcF`F!H*BY1R31OdC`4ov!k&%t+6JpdIjG}>p>Ds2sn=U{6U z%Hg;M#HcLWKS@hV|C=$&T0OC0pcu;s0yv6*VbH47oNJr+9PMn@QLFBjM1-0v z!tzjaHRl0vdwP4gPZ}QVEM+zRa#ku9&tv&_s3vuM9xilj4(L2SbJzV%1TKz2*c&Sk zAb4CHR+tVLk=fvxFbicV7AJ5VU8hC#Qpo>MPBalcuMvTccv2E=l*4tMtf8wQc)L`| zrQ$}sZP(j#jy!23qR`h2MRRxIEZjNNljKZ29YR+i%Rw5l~9FOD{EsU6qE9 zYnr*@8x5Cgtz^Hy=H+?2xPO25`UrYD9H)Yj8@xMW3dV6&;UZ~~c+}7~X!Y!=>&X#wzKV}&rR!7E{!OQr19@QW7(eUq zOJOJq>rB(L@CHb{rrdF{4j?_wA|h}GL!Y$)DHrBC_ne4vmx^QHfd!n=i`s7bWwxLM zzPVe1n=f|{$&}Uq-~Y`hZ($JH%|0~AtwO>DK}U8u)0yW!ikNTn6jLcAW!oW5>5ys{ zdrP;5vQIk8$2n$x!n&o;Q-7N6B z3F}qLQStjJIIz&;;S_z#R@`)_ z#Gf_%=lBc2i&){3eN{;b{&KH-E{9NzCX`W*S5S|)0yLgVcRYn6Wl@{{%M=iP+mc0v zhi`S68|I^AXF zG9<1ctE%`E55>AcFQQwX4(U{%tZqN%?#9Z5`EjgrQy3l+lm*l|w3&T_)WJ;jK#(_> z%DW+qCq(?r%E6b8fB6D8(U}Ks%TVmzKhP`ae++o zcc_c%^XPlFvCBnYy$E$=W{id_j8!*@j57|eWBL~F24TdVl}Ub|d@uOj`t~+KFx?12 z!uIvvI23dT*{mLK?}7NdQr%Byy!u1h$NOG^1sJ%_!U6e`pGc0JOGFQfX$dDv$;U4Y zOiI({MFsOulNpwiCxsAVZ`Q3>D|+giB53U=A5$*)mOuT<15zs=|7u##X>)@blFMxi zD@Z@ug4*@Ow8JH4Luo`{#S-^sj*Y3y-ViGfCvtL#G@^89A*9c?Uy$pp_2OZ?&UyS} z?uzT!rN>!IBV@m`GNy|ideg@Gzx;OG(A1# zFQU_v*dyxmPFIm%_IQoDW<9n_9Y*#-(6@BHDqAQqY#5}lAX=4txSnvB>sQvX+6bF3 z%P+0}33|#AL2Z>#Eph2)p61!iOVI5~v1|LSRd4%G+?HgL;RU(gO6SMpen|S6pG)9? zEAjG@xlLr{x8g!JE`&*ihMASN%GbWa}zO;FKv^gHDZ9d^>c*>@kV>`m<0rTalAmaS=Zhd=jx*%~s&{NBI=bZ)_UM+;p<+p#k zTsrIX$o!iI6ww@(X(loo(0F@$K2pf(ekV8M=}X5gJSZS|uU5w~M27swnY$1c=Cc|B zi_KJ?yz|~J6k%zM2oA#&UGHFD-y3oV-qj=tz$e81b zBv+F4UU!Kjwx$?<4m-w%vn0quq0=0k{7jQsR=W%2_4<-d<$U!mU@1ZKKXaOlx(8+$ zX-0aK*|H&Kh1zvIMg=y%wyK24SN*Uz>5=%KG{npfcf#g#7(nYD_ax``$?jD?$MVYn ziKAaGT!>-lyE2d z-~^qLuk*8FZTP$=ach$6KP=4j)tl}thAoGul_q5pF5{xm1RFtrb69@dr zwEPnBnoDEXJGGu=c50IywPuXwxZ1-ccI9X>=Vn(=?mWiF+QV=zlI+_TBSt^r%LZ=> z@xMkS^dMm5evJ;wcX-ZS;*Y$<16x!xnR>u=1@6=PZ|6ja^-;avGk887uKyd2HU_Kr zPi4Q#NA_^WiAC;8*CkYD)Gy-*dcec&bU6Ry~M69Xt6y_(nZ8v0BW@Q!2@7< z3Ol9s34MPxH9hGUv%3~RgDl~K(PJfL4syP)=?Rp6wrv13s(#j)!|7o%ylSd|NBMdV5oRl09#^JGe?nd8wZB_fNg`8s4~l=)++@fu)Xzd=pjk zN%#K;L{x~rF}}KJ>Q_y&$LV@2{CP=X`-}-#pi{opDE4*l3%F}LODI*t5H7Y|zw<@a zA#M%;fs@ZrhTI^PAAA;1tQkKv>-I16ETS}FvN=6Yy}x32gSFf>lwaSY@#CLhMhtv= z0x}ttNyEQ~6&P<_$|d}~8Io(Tf1!BuhL3)j4_9bgmlFO1jcqoa%d!4PHFPCA2&9X6 z#BuX7GRF-JSKU_j`6cY*-&mF4-~yyTbuixSUFxakjTou7iUEqa>8dl1jDa1*6TZAhMHQRUOk3 zZJGRQ%@fCw-HSOlfhbw?ful8kZYDcvwaXIM0y}~rCqry;jhHLGDJdr%corVrAMTC7 z<}|aFW)PLn!+{a(4D|0;%Gg|gxv6)1EUkytgJ=X+z#F=k)AeDdpl1y=e#1VCbaP7Q zT_erVe7QCj%{kn1$r2KCk1vIv2HGpEpF8|G`P)8Fy$Cn{Ta@L4#lb`X13c5`9@4aJ z1h~CZqA&Cv!oSh`$J4i$VWU#U%QeH(3(hM{_j$yshphC%ytKJ=R^nx%v2NI;uJOV2 zs|K4spI&R6r1{oP)nfK6>gqkL=B5_3IJCn9v#cvKB5uuHYRmUVqh1U$;>?^>l=_y3 zMK#upx!e|Y#W=Pr9*LO@Q-@maY|M?E8rxbPY8;Cn_vxE;iB8e?`Qd>HEy_^)RrKFbAKueKcg}FMqD(h8-U~F^6u)DEa))o8997%w0Pa zT9$ZlOmWo%yI2;1nVl_SzFlorRP;O}{6n<{R@@I-$yUD_y@@3n!!1d_e^42TM`7P5 z_bFA1%kQn?rT2^9hCRzBJ3)*{>Yx`uBM&e>bDu+Aov=#x;r z{Z!Ic;Hzaa%&cuCUceg(qz$`S_3k@JP#FvEgf`~OTLP|Z1uM)>L2!MZ`Pl;bOyq{n zl))zk#3l*^GUaw)*M+O4oKlmWWcVrb88^e^{M4(`!0i1G+vwuoyv72QdWR|Cdrpb& z&P00psa~;_(t+Kp!guHEug5-}AXq{MmTA}+nN%(=prf-fV!Gr-8ZkXn_hHksFw)+a zCV}6uUa!tf-?6W_1k=Zx%;T$dAgpxEUKQ^akk~hkYP|5(lg8>rMpGF4=H(WJ8$li! zL?`!ym6>f9+qHGVUN*C*mph-10vNaF5$9~%SUACmiL35yt>00OjU)YI@YZV?#~Z5s z@nnWM?x3VIgA60kPuWv>A+6gmRK8H?jR33OdBx3($a9;*KoR`==azbg!<*q$8kZIW zn#Zf11#+Ul3$p41z-0~v#P4HO20C6NeK>q`!YM+~ zPUH0r84lw*vN2>?3jyPWq#uty$7P8m`K4yK-R=}c`I@I-jL*kSb z>2lD9`w%loO|&{I$b)t6cpDyW1kLbIC_}}9yL3Lq9RmB*MHv~#p75eRiQysXen|u$ zm0y_^()j^4@NMtlHoa0|wQK7Z{O9IaA%2=*7fsC`iTQsD;~Y!9{l8ZZD1_vnDjZIz zKR&NKO0UhMu2tpXlDle7Ai$~p_X4M^!L42yj3xa1_^mHxVlI3Sr%o{?JNNjCfIeF3 z-75;`<(1wah)vDsy4WU`JP9$%G-W*YU?<6+*dXu~I zGp@?&jkv~!HgBJh8f6vh^*;7H80WSIjD-wIStzpK-Cr1azVs5qsxJY6mG)rJ-Pz`R zJCsn@YnL!p1cVg(^`<4AZi#vpi1FcSW6&ppqmFphr zXcvX!*)QutlWsjXkiW};S|Se>vnp&UAK<1K(BJ$5y|#TaxHfW(UdB9gEDc|EYQUp9 z2%)jsaNRP1uTi`KMIlQNM~%vh?LH0OePc1x(#sc$a5$Ng!nv8JcUW}Nr2YE#)Z^s>_zsuX&y86PyMnh<^7+)IWCRuIJ zmM(|2hG-LjZg~8i^w)_NqEYTaDN|gXPC3}zw*N5e@KR$Qhucnut!Z;APp-lH8HHW1 zDKH=a@w@A{KAWMe+PvfUC8l_qnvVwH_>)F?nQF#Rxjes%ow7dgwSw4qbn;=7Vd`-T zxV;2^;y#=DgehgA^D|#X*z%a#X(*$CdYjoUdXCbwNHYaq%$rxNS(&QmVyhb9`;GpA zW&L@$7@H<})dPYw-UxNxJqr3i?iav3iye{bmV$j_q$5ql@KQ;sa%eLhtK46zz11fj z#Pgo%?`_OMnxB97k}{`Q`66pM5)EA3_NMdm&mNpWX}IJ@i{kf1sEnykjLGqSf+0Q) zVKFi5o0~cRippqS9x`6g%iC{dMgP_$S$-&^OSGwn^BNg`2wAu4oN&Ck)| z6DDz{{bY~LNM74LhZI-qCv1JW&gQqolLyuCmM-6~iXiI9uVuiK5(%+^_tP;>ZLu@4 z;%ngsaTcIWA4BgLWQUzs=MjuW%02q>7%~>M-J{-G0k7YUktQiEr}wMTu1=rvD(K!* zKUS5Zta(664%>EPFeH*VgQph+AFEhln<^^S_zQIeQI?X$Ae!-Tq6$J>TG~~1)?&{O zy;HWVy3Lvm?*2AvJh7zQV8!@1KPKz?@zfhqyv9Y14n>#LYZ3b2G38?W80p@A2|%bz z$6Hm;xB(s;e~Xs}8b!yIof*p0!7;%O9d3GxRI!dY*e!7wmVfJdz!VTzh`lp>)*y_= z{R1E2lB#duE4yH&lAN>@X^6gvh`ZyCx_f30TXvOU99Z2LRsc} z^b$`^pBSba+J%;%AW$4-dA=XR^fhC0R}qAW!C5|3h# zy(0M>(ZoPa6jBI$bmCViUOrJ2VGPO}fv>9jbbrh|NaJWwQb?wBT%fXxk>%e@j^BZ( zRb_9;mcC&*5@>+9#sx$LJEsejE}mi6NSJZ0BSY*3DZ}%^6_&PqlP>E1=ad2uUOojK zK5L|v;?bN_PsrWqrG)}>kQCi2P=Yc>R>&&M%;x?}uHXJw{Y!uNOF2P?aYPTCn5zSi zc9KDKoRiAq3K*T$M5}#xToY6%)>>KB<r6E{+y?{$=*0(NaYGA_NCjWdHzCU&BCW;GwzLXv?a-HV zGZjMcqr(#HQ}@!jg>D$yu)O6S&!SXka1rp(RAO-XUv|u z6_TKBWljDedxtyB`#T<|MEWCC|8?$(Aitzg>m_uT_2zxxkq967Yg|1HQoGHsa&%N} zmHp<^s7_nC$lr?Vz*y5GCgKm{m!%|noV^s4l!Oy+Rj?O{B$34%+jXkHcI09iH_x$& zI1@ytIleD%(k9(0^e+I0`+gf16Z&W&QieCAK>nr7L>0y0R{+;)SMI!uY85bVX~=@j zj-=w`losnlEpTQyZ(&qKcsv{(4Nr#6KuTATX|E?2T^vSKFi4hl=s?A}7%cGV?uHfR zY%7Uv693mq`O3cLxy`HH=2l4;du&L@{QCV(INq{5=C>Tl61SNrj2lF_A`$_|RJ{G@ z*W9<+78MSCVg#X#^6(+p8#0nkqrD%}Rc<}^MbR^1r2ql;Vd+UPjog(}ji|jm;z+si zC3Z27RWN|9U45Z`)VZC;Hm6iiV5+)f_zTY=Yh<$G`o_%WlH` zW`S_S-8pA?gc2@(r2l|GDH&ejA1O#tfMC~o zd`8PU3!NLxZe6e$z0J&AN;Q`__$1TprEH$wUw3pH{A3R1XJdj|^VJA|k}yo+QfuT3TBax_>i&g#J@pOn2 zC@T{Pd((L`Kxzd7FT)u{OspMFO^@X&bV_`*nmDSmh_~DQC3c^S7z6RPB=s(X#*{mw z|7vWP4EaW8YS_kOYs|^Z>o`)L0ie}mMPP$mq$AG$*f9Fs5T+2~rMr=Z+RT2ywzj)c z_?M7Py-pfVjMz^j&2y%5Aa*KIkffqQo*_1#=cIE{DvJ z6{Qi|^N-wL=z?@$ z94Xs+xBCM;#X1JJr+YkDQir_W2Z|Q~cqW6iKk}8eIU&OgMhM+Sx3iO}^4+8u3YHYO;H_|=l74WmR{yx4Cj2`vnaqmZE`tR$tU zdG$KCxPq^1Yn8_zg+obRxkbw6vE!eMTWC7$8p9PJHmY&cU1G!Dme2)G%n<5p<8jQ5 zsr30O^*xM`$_UG9JuSG9r12U)74a3#tP!bMQ8YKl)pHC=IOEM7zZa{vS+cH-UZo`~!$S=5Ne& z{hc~UR*~1z|4?b54O7wQc^dOlSH#st*$`6QftV7+oz zmkeWK6si&*k0=O{l1vCJfuNsr^@l{NT!5`;^^g(sP0X$MrD?uR7#ATa_I7;c&1|t= z%+aR0srJB=E6j`#0Hvs@j6KykmV}vsD*6z9$Hzl4xFMQib>Wh_OYSCBYjY;;^Os#y zGO>FU6vlVoAe*WU+dEbc&8{o-c`HIR#d3ujRH>~1LVT0ta_)EKIW{86M zH3xR7`_`kmU$?$;oeImwh2;BN>ZjMQ{>&TdLwELMdY{YmT|;{elu7C>B2w_-`$X=j zJ7jglU7ljM9|8dc*UHAnspt^{DwCH!A)=f8B1-qW+3=k=uY{k2^N zjO^&$(=Cm|InF5;7|@MuciI$O>80LocPHDYb-BXGSsvb0dD$h}+&68r%F|))^a&{j zU4CrYi|uY+L+wWFA;BZJ>stBPFPX^7ku%(PwyI$CmvnXBS@juQHC|*36>uG$`*sni z-Z-|CGDoVS=?`ap#;SlkFbFq$PeQmXe3F<1{9enLltnA_B%)6@F1=ps(bAM~)$5Jg zk>sft^q=XdvX9xw9@h1XInD0UjIObnGim$W@*+=M+`_f_m!DJFwzVm7qT*(;cM!|K z=80=5EHEw@i7WX-W?*d&@}b39>#dtXtXcGT*$Jl#-PRvHIiC zC(sUp@v*dF!!R>Gf$+_j`-*>9zO0n!-1rp zU3ojPh$>UfDQ3QU!4v1B$IMBL2LXX9jf?0ufC^}~)^kd>vDGEwA}!8+NnanGxYfP> zau5-ROgvDs=t-dHwhY;tMdC+hLPG-Y!w!H z$X8H|O?a!4IIC&3|FY}yEB0&m3cZm>YgpbugfF%s>pXlsW6rmCvj3hVAQ+~bxUO%< zD2et;Q~7|~U!Ubx8bLVy{rxs&!|K*GJ80td>)bj6%fnJ0Sct8QHhMINs{m zrCL!(#92L8f>(UX+2&qNHu;ca#;<_QHy_f1>P)emlrkvjvjOv~(ugP71#iTCy#!7E zL$NuOU$LizzKpeXPZ+V))WS{PRA9@2beuqG2Ha>xu~o&LLYIY>97$&C$3Oo>lHCl4 ztJRCVMWHSCC7kfUI$;)nUsyVEL*RT{GIJ=23C8_#d|0=wg9v0*uoh19|GQ^oa6NKL z);?+)0HgVD!dz}!LvacAPEN5F<~0+t`%kF8JK+X9CI`oseXQGZa>2@VZX!QS&1=UK zjL6*ITDGf5U=_)+9_V~I9HmiJ9MBGP+5{-%4&Io6vLnIB%};~3jM z?mEd3^y8aB$Q+?8%8jDNae4AvT3S%q>B`~50Po&awqyO;^%ZT-R1DKY_@#SR4d-~| zfM&CIVsyzTjm76hC>koV4a%RC66MX5R1KZ`>2o?2KCE7g(rlV&uD7ZoXH65mL!V(B zDAA*Rw)5uvC-%20*?I-d1Ke!WC$8`hYXLE*;Dco_g#dReCF#_zj8geqi@j-_}P@!xZ^hHKs^(Z4hG7@{u6`y zWkFcTt>FIR)&{n+TECcwNiPMk#3RJ}32!`IOga^xj?lWSovteNZCoQv>?9Sn0%aC_ z(rBA-mIY8~5pVZK8T>Wr7$`J;gD=8Io|YSWp!fX!Z%?G8$#1UU2eWoFwg^G6FSgwX zw9%U~zV-7U7rD+Xl=e||!1Dm^U3z7EXzHr-daX1>3T{`$O#Q4EuwDUm^6$Ze>$CP% zZhdw=l}S3l0kqD@46O`63L)XfEil|wb!I308L;ZK9j&nLpHpRK_vzs(u?Z~Js19F@ zoVEt6aUIc01wj8UYmj{W>{c1}WvMQx$vd8&R)gEVph z^`|@VSgq(nHnx;jO9+BR|0i=c9a67r?W3^Y^l3)qiInBL3@&K~(rSduVm9w7tp7!x zS=_e6`0Zi!SQG#Xf^m)r6~_A2N$f=glVL5QcszhyKqLdDk<$&Io~gOaBkvpH)(Z=G z0j|cgL}c-pHqc_OG)jE^7u3ge5}#-p9McjmHQen=x=>~eBWA>i-cO9KAhm#2k2DY5 z3bg||q#wp#b8KF8)&{9RA_{{bt*A2#H^XzCCBRy1N^mT&d9PM}{)UTou8c{fzgZiH z;X3^v`JagC*Sqh1g zmRdfHbXaYpD5}6Z2O=&uCTTq{V3(5Vj2E!$nm2j-^Zfdj-s_FtIrJE&119C93gAE7 zk8GL_q4TN=T1LXkHv&}b_-woBf=b*T9k0b}GrGTx$sN&3+BZNmp{CryvD;&6|E3cJmH_T4#aS#hd5i8*G2Kk`;ZL& zipuJ7db78nFE)>*@V=fEJFzS;RD^vAa)HlBZWfkTl#fP8+dQ~JKXDqzOC8~tJ!*d4 znPN(fu+)KNKpMx%wULx=>ik71y#lh}8FsKWJA7b#*s@aqfUBvgZdyVU*aa?%V<;o< zuqegzr^(E3GYp^Zi?8WijwKtk4^y0k>ituhKCAD zo-wUV8SQbLy@;1e_3}LuMS&-Q8~q|6lw|wyfdui;^N}se9iKG!opp0{Z3q8 z@v!wwrSH$$Hb#xss81EDIBzu|Z&gWnDX*8RjytF1<~!&9=7mmJRWD9FVczQG&m0Rm zuJcpcnN%>%<_OZj{S@uH8~5pXe#EhVyruMfeZGVBXyW{%g>B81k~dsVnFQk6`=T@b z+MC3|=R+NxNZQseM~rojQu*zlyki=TG&sm`#k_y+$mar2UHsA$OvCe-xg$EdUC*$Rb!SnK>09JPi zt5BW%M3F(UU#k`Q{x?~1eNAj&y8TWHDikDMhP)^R+`FIwtxr<{b70Ijcl(6#n+6!W zoya+Kr83<2M?yOTO59Vm9=DwMGOP!90;nT7%(5yS;r+K@RUp@6+^=s>+&E;mv{d(J z@`ZY%*pih8Wh7OR{wZfV4Zaz;GHjuODX88UKLXt>Jp7+iD^*Y^27f4v3BML%z8vaCx=<@^?af?@-vnM@V_H#9 zs_<-(irE#a!I+wosk9nn#-!O1ZZg3atbV>?{mSo9*kdc=HXWm32?ec$Yi! z$$055{B^U1C33k;dYYMT1T|~42ZK8Enf1GJY+BJdVt15xDVnt9elW1e^Zxy`bPe}; zbXjPqZ>->^ZCFXf`#F`LN~JwM!9#?b^!P&cEW@om&De&qSLkh58p2_Ov5lujM**P)o*eBm78ZAII@Wf2WBf1bZVb2E zGfqGO0dwiSIxXvIKBc5=EBT^vysw9-*7ezDNHFbxZkcD{MMF6&dWU1ovC+07TQUSM zaX;jg;^M?D^br=Te6(xtF35Od%eZ=Fo<4f0-gdyu*%6NPL@z>sPgXkBwe%BEs;`p9 zK3$q~K2sst$Xhpq#=3mFMp>_VwJzlj7#R|3&M;5P%=zq;R&~av0OPsbR1Lgq=2jP; zUH-_WlMs$s=gQS8S8gY4+B}ya4vdvQ&an^?ABgFUFRP<8Tr+mKm{=)e#*NN)B?T#}yBBVIShP2q-2)Br*Vv3aI ziftf5+$s1)%0OX#4(`q^F-K*~_L6q}>w^G?dkd~+BLYpAV~gXAv|fHdPTk$6YkQjPD5{SwAy%?@5_Q-dqj8HX-urd}FnS zELT?WQ`hcKdlqr_F9}&bnSbTJD^Q>O_1P~&0Tw^t9NkhwgWZ+Dysrsf{=PM!qWr}q zDEH396`-P+$f>aeLOt5?SACsbD$djY-BGgc!|8FE{n}h2v%F%~P~C9oxM`ZBudSNy zi>4t$EH0x+-rNeB=mh95F4tY^rnjGYIGQI&M15J}MI5S?qBBT5&+0hpeI^X^zOm>A zj(ER3+X)e47QoaD0@{G{rdQ}8Y67cNjpvnqd0=@tVa8eO|a6U`v)8x zgrENc{1-NVb2FQEem<>xh);R`dx>=kK*h};ExA;!ulL=@Ll#QY&x*gz&rAtq?zPXB z5GC3hDCAMw{qbvagEWDp8T8_s9rT){fOdb5hCaM{Qc?x1xE)4d9jNfQfXE~y$Cis$ z(=shSE|nCALG*TzP##_<_#a|r5bGoO7z2;a8)7^)d|=uUHe8cpsPN;fYq%-b7P)b! z{pZ1#Pck(w=_P!astXV`SH<`Vtgm=pe1LI)|5#W(LhKs$(efqK`A{|#F|zW)J@1># z&9|ds)*kvdIbg5X%RH1+6Lw8_PA1$54)9BGfI|gf0bVPej>s|ftXi{FCS7sUQ;a0l z_=epgOJ5Z3ybSyF)-^E^XyL;SvM`Lea)mBogw_+rlFI|My_aQltw;nh4ZP3n{$WOx z_5SA>!{3al8h=jgp3hnCb6^bb5Z3DA>XMLLG`LBxwhOlIze~G*!)d`EJQq+8l#za7 z!AQiRF4-VyQ*Sd^dOFWvn##biQ|*RYck|@3vwcNSZAqd{^!5w(Wx#bZZXeXXuQ2jl zjt=s1AAql!hf{c)&+a-mJ~eeqp|I@r;!;eE9M4I@hshofF||7{w_ds{3nvuCMqWx3 zjcy{Rehd5#%l&IUt7m1^Q@=*}{J?EBL)YSSnG<9`P{+^B{OzA5xsZ-~F-cDxR1MsZ zFGj%(-#8w9dNmM}(I|b=RO}>0De-MeF~!7>wKa}3o~i4qm6vA;I3(% z@5vK)^R`Fk*QM1Rw!C6BVNQSW`lVj-XIHf!8+CHnr*p zt2OarrSG0&`N3~5?iwlAtCDb)?Ma8LFL&v;Bod{hU$`xcsHUbn^>}S^swnQi3fr_Z zu64g`e-=A_hNI5i4Mv2{jfl6&#QnZCVR!Q($Yg1|4Gh`|S7}_mX?bRK65JJfUlZEE zqDu_1bF&iiwV((Z_GVCZI3p(EJ6*_3>`Xxj{Vefh&14TXUsnaFBCD?jzmv=kc4pQ7 zFZ&wPb=w&UlXyHvt2HW{ibQi-_vaWjAwQcelW+O!mQ?!}#>To_tZp+HqjD-UCr1q) zW3L{z%(8He)L9SMQqaK`9G@YG?l2L80&@uHfcOcl?yHcouo*S3wKMNb-;PHU|09Bo z7+LifYNrinPnO?oGZU+tyjraRa3HKLW-0EHNp^ut;ic`*{~So8rMA8ug@t&uEJ{T* zkGY%#LCBs6JWBDL52t8T$9g;T5(1x^nGg^gUr7F!kKC;YL{!bwCJz@B7`z|xG{Edf&cy8}gV zUsK%tsyFY)r{dsVTpVjm74cBq(Ty z#iGbrL3r!8MncXSyaRH+d#2kQjKsrdFJ1mKket`NI}HtK?;9G5Mf(Y7eteviT>0vI z*PVa+SHi^XT}!Y7lUNVI+qm<%qc2h^igL9vS#lKBNUnM`b-p&1x#}(^)B>e=&dkRlm&$X>g+T!yCito9}y38BJEiwQ6+6Z%D z5i84_?fpc(%&YD#N=WTv#k@31CLy1Wdo?|~4A^HtBoB`jnod|^=%C* zzP7zSX$&BHPWm%;8YVd!`&EY$F}+T?4zg{>TV}z_XO*O4MjgCbZfPp&2e{fOdwQ9MRAEMRb6}gg98!1g; z+-gQy#nTL~si?lZ=YIg|&gaoR9P>x~#n-+6nwbBdcBT0Pk&OFFcfDDnlm}M&Gq(4n_m#LP?CNy7MCnx$=eD#+>3b)D`W013ao~ zeH$==120^ZOQmKonS~CFj~SS`=lRc8b$l(p(mvnrMAH)(P;9?kWv#9;E53@-L4&WM zUGo7bd&c7N&aBS=svpDl>(q7Tvu^!IM~V;g5um3VKWkb8hBq}2#ZsmXPUp!j9SQ3y+vo4T*lJrU1gK-elUOR{IW=i=QJg$E@OZ-zWtk7a2$H2Yl|nHB>Rwu z-cE`}t=i~Y!OqoKruZ~0!oqI!etpr*P($G5iqc7AT*`|wQq)dFgF?54N_a(+vi@ZJ zwxP9936`7P2P`Lv@wzBa&{`CWzi~KiIF1?mL(S1Y*X@-3IX9k`JDXfP+`)JA=}_5( z;?}J)B=qK;S$yV~JoXm9?R4GQU%$TaWA!Xn-#3^hRitujlTkkEYQAm1uB@sOT70{l zYOh`K)<4d1e6%A9M|6+y4`H((G>OyllZ=3l{Wjd?1K=suIpE`H@;52{ry3y=5&FMB zSiG(zv+U~Z$D93*wv4R)riI=LD4wHkKQ{rD`^`K+{Tl-bhv!Gn+fT+7&eyJSM`y`A z|JhLv%q`c~j|mwWrJk*Me&`PODCQunxC=IU{1b>%Jj!q*_S15*A>w#kE1I%{ybk*G35V7+(ea(FcCjPs78dgnlXDx%$As!P4Svp5v&JKh~) z=6J^(cAAxb^VpgK5{g}fywGCVLL6SQa~ET`-!IR%3QL967Z(S#)~7OC@`mEU#s}#^ z)BmW(A_fewbrlCF%t^7_HwSN;6NJ;Icikl<$LcH_ee|%_F0%?#GH!s7OPd~M1~l8Z zG&g%nP!iShfi1aUCGcgRGrhfzS5J<2j*yq-rPsoHF^*1$$hR$ZfIcYTECgY;pZIJy z&R*B#^qZ}MPTSF87Q~1~UHxNWX0VP&5#$8KG7#H1Cm<@J(;iooqgR-#h%hi}EDKN`)9?gZ+@>Z}6iZDhQUKJFZlV&=NA8BLPM?fHqeCRN?X zasE|tPWy?3J-1%tV@}L=TnovAJzdY)>6B?--{+aj1qc`hmZ~PQ?&cxGlke7~wV&b| ztw%}M53@dO-irW0N&1Pf{T~LbzR>#WsMr;8XJ(8wSZ)ty;1Zl>^!!7bQnEWWyumb~KT*KvhG& zyoHTlqaHy&DhRj*c0*Zw`Ebf5n7>$|14-h+sxc`^laEr1<}C zBiDL@6$4L6DS=Ae*$V!9ufu!Q5@K7M+q<{=?xyyB{PeIRv6k>)X<^QNlQv#PMz&G_ z|JmI6%kjLbs;VZHzRJqt@9uPQz|Fp#WoO7|{Po$!iAZNRK1ntj3A4h>h(>ty6*qoC z{2qHHb*YUbEe>hy%I5qs_*d)ugTSAE8=`-yVdqk3I2j+x@+vAGr>Bw2O;wcVbFRNg z@X+bdIZdHq`%vq)*dR7?+({xIAfL9apF=Kxptkdd?D3Sy1POL!6rggm+-CWvKR2IJ zw4H^UrQvvCZYVfo_nXAT!kIEZv@-Oo>*)zoz;WYvP6f)4OV*Fh`UfIPcuJ&0WI+D3 z9f*#KzGB~PckI1o@5~|yGfTxGLUWX^PYpPU{LNryfBq%<4>M4XHEQm`*Q>vlfJjE} zm2q-SmU$41?ms$&U{N?%{mc%3P!F1!1maXFJcr zz;62Dhg&L3mH0&5$TI;ioq)qN0z4@~Xr;xw7)apsx5oGO7(@ks!i??Jm>%#H?~tqM zF}ZyTDwYvnjUN~}ZrNtLzrKjuH$RpD2?Q;vQ-QVOWIU*a1WSxNzGMh5`+vcz_;%)k zzdi(&eV^Wa9|(7rI?f)@wmdZuhh5r~nTLW5+9|}?=Bp?_4I!DfpI6C1uJf{4{rUhVA3@`Jf%d@b zhaA6cOz{1P)jH0*knt%C+|%~k z6PhvIdUo+_T$WAic%ol1(VfXo(ebowcau!C8PwbxU8u)^f4>qimDuQI z$w%in{zLgWIJrIkw*SU#?tJd|D(9Yr{an7A5Aane#*KqPFMaIq%jU(4N#bY;C=Oi{$XkBXSs(a8{jtTzQHp;yWDEb zGg=FgrW5U?wj=i#vd|-*@uja>#LmB zy&X#Jnyf55shX6Ol3GIfc0>6kJyr6^&Vw3=!%GwQPg)r}?6o(8W1(^9-4Yp~yE974 z668T&Ys5x_R+6Y2^8vFDiUM2OOgsYJz))F;rJVRpeuph3xon|v1_g#&mv0W%z;Tws z9I1anrRrB>%(M*z6z>v+1O0>U4$UEul>kH10W4WnmHXl8V%gphiD$Fo+RZhj zS=O9`pt;@Kgo{8}*z?1O?o|bUPcTwvnb8y)#vMT`H;2_jAM2A$zFX6T0qDs|{_8~9(Og|#@NtZ~-X0>o@9cRH z?a6n1DhP~6Zlr(}(yENc0o_6|XFH=`n^el5^>JICGeK)$2R}V5Ei5Hf9(M~($uIkG z!SZ-{j%r9PNNx|nz3qp_E~4XrFwGwLiKvrlKuJy03)792k?A$84JGd(`4>&*LkjsU z)dUNItOf5>Pvkh;e`^!3^j0AI9+CO|F(@^N5L;#*njyCOgQx4A#nLCa)a&*cvxIZzrk7a;4_Wbyj4oS@lIgEBJ8hEK> z(z+yvYj=eP9ja*W{(S5787`MV&0cOe8ye)3yVOdJ*ag9`D4)S zcv3Lo$&LB^kyh|y9j?a3)+2=CQU_Fw(3(ND?KiJIo3lcyx9^R3rkbURzsMxu=hX>@TW#gc4WZg9c^^|4axFFnIEcg{^0Z`I z#KiUaOfoV7*O#s18qOyF((aBV)d-T9n+s~;CQjdFNO4tS-Gl_(-QD%(^eJxaoQG~7 zcJSv-UKB1z4|3k>e|*Q_LFt1Du59Wqy;*4B0C3isB|rbQRq#ogU_DE^u4CzVsc}F zd+qR5Kq5??BWmn){D{)@^VV65nWn#q>7L6Qd@vP7 zwV#K8>?q5#jvG1U97fjTk0kiQ^Wpw2$tA4DiP$R(pDO1Eo1Wo1=zR%gLwX0HDhuaqCPJ<_;oP=>r09?qC{+H*-q4kvEb{I|hl%y1VW`M{gNJ%xm4NR7jkVf|K4H|{r9#b%Oleqg zN?Uk*N}v;{fs4f@(M|(ps^Wr%B_#|RnPB}k)21p9?5H!%+CV0o!$T5p;DEb(Z@zsa z(PES3ER`JkMaZU^WS%qt&6S9O{3}JK(-Ifi0Dp*aDP=O>Ee>=SVuwIH{!EK2sfp3M z(e2r5zVB%GP()t-PGwfzTj{2NSK~e6n~#dXMpxMht`n2V4rP)$0q1PZ~kDt#~F};FYy_fh? z-Lw56(d+3Ag`&jxl9FMhw5fVn>)z~EVEjP7z=$gN7t}*Pk{KdrQiDs0)qLyJEt(`% z*HAj2{M}o^Qw1{R16n^apUAX--UT+D9QWWU?f+>fk3ZpLytPMWtJlUZN z7rd4e^*DP$SI5QOI63LYBg)QmxVjCx6H4*B0e{`JbNEziP+Ohv-iwj#9)q{Nn-bG9 zJbHh@6VzJo1?6u6y85LrPR||h{+*Z;nkMDtlUe*BPtlsLIg1ief1;kna6R zl-Jn#)4?^Gi)DvY^fuQ*d10H#(BSbf=f}6}OSq^HuH=-FvQx^`t&5W?amIGbNzPJ40uV}hc((Z@)G=F8BHwF{3n@$r(_Lc^v-{$`vS%M7*N4$*xVWHNKt@}Vonwb{s(sr~I}kZHX5 zryR!GVPmY@6S(>NzLFaY;=n_1bIOr$6ClMZlr7)lgql*_|Fg^5KlL_zm2RmC^(>qRvrjkV&9JnS~7)Ao|6{)_Y)|sv#tzCZBw5G2eSqv@EA4>_1lr zTvg3%(Igw4ibV8J{2okL(yQC34F=+h&Rh2abXjpEZpPxfpCX&E)jUoge&;5zJ(tvl zvzjVRVqnL^s<~Bof?b;6YCFuA?P|R4x@N4CMH#kwj(b-_q64u@*jXn)6rVPoJ)hHh zN4s;TSba?wm^HGyMGgoW6Mud$yrMcTRs=@7#Wx2%ocnrR9)JaRho9*zVtlOJ`dxL8 z;u+lUSnp$l)$O#4HGATR`N+Bo)92ZRx?R~7TTL;ak#UDA4>83}i^komEd0g1T4#-6 z+(uQ;oWGt%kl+r_(F548FZnINzq(L0c@QKgz%{pZF3r$N@!c{}GNfgjq@FWwCzann$LbPiMJ1uMEJ3lyGe(&)k$8wD%g6GO!M zeL$Cs)Kt9JMaiVD(*PB%dtZ8OO6yGuqX%lzSiSCu{~IhIWt!rk!c89oCrQua(A#_8 zedfNzYAB|360{Y5@qd7b?-fY=*<|9K0c6GN^}*xU3(sO=s=E9Hts$x!xaqd|SKA(U zJlcbCDTslFMRMQw)cUT5Qp`rMotPCWdu9+}-ovGWzlOiMlvXJOu3y@=)^CX62X(Nk zX9&1(?*NOw2Uua7S!DD~%&mE$pmo!Sf1&jVup(UpXqvGdCR9~{YK?YM=<@{GS0M;Tr2Z%sf>|DZTM2zq z)T`uXD>^{9F}dJqecTdsjX(5ss;BcQ76&|4+|uj9blBa+k*}WYeNB;T`+Hmmi6jVh zpx3#92QKcb|N7k6ls@LPKB=M1$emnUZ}$|<2bSO-GT=ZvILJH>j){PCfVf}^%C5Ve z10t;f^WW4Wi(kEy{)cjODNE{QE;CRT?irdZap+C8Yzac<4I6fF^od1*M0^W40$kn| zeKW;`)X(Ml+7Ns?7`t~qc%r|56jA&KPZJC1Gt=g>=tp78an=d$(4Nq*gwz$Z3Tekd z%mCfCMpqVA`CTk*gR$z>hyq;xbTjj8W*KD$T5K`Gv{pV(NvbSU_XN!5&&67B>Y14N z7!Zv&N(`FNWc*7w`5gdB!NEFNn62URwTd!#=`_cCHQEbeNC+4FKn2u;Dzc_ zLp!Yq5$MZFU&z|TA#UPYJm@zu2a5++RX?!q!Gj$+d(lUQJ;qg$PuTBvw===sA+2td zM!bm4)D z8$+#33*n{SP82HpI}7=+n+zSMKDxV#os^Yhu6!(oe!|6R5&+MUDW*hyt@#k>gwOuDxO22I?#$K}RrnviuCwtN#{gi^Eog4ffT>Ez2rXoUR);r-P2 z4xA+7!dNR@ow$;p2jl2$sxZ_vF1K^u))JK(S|J7P{#0CVs@82xJi*}Tk(m3Ng?%4u zEq_crM?hBN39DPAeo7a?m#5_NI+{$z=-Sc=={~P&u#`Kp(!74-li1LCWXB zAMtx%$(!GMZW6Dg?6xt~FB*ijpE+#&7%FMC+ze)`WDf^Xd+rTA>V~a**jxLsIsP$X z5IwC;f;j%WSll>bQn#2HjPer(oq%u139+cvJ%6cM0S`vQ-jY4$H^c<{|5U%8j-EHc zAd*vdM|(})VE*pSA=&ZOp4X3nIU4E(#ZJrMt&2$w-a7?HCmjL*LMCc{8H}O&MPB>1|?k%1&r~s6DVQZ#dPBa7>64!mnWtZ|R%uHKj*#j=oZ!Rh@Zj)XhpX%+l12G);fCUYfFRlBf%+BVMh*4$Mn#Jt^_am0xB#`2&49`D zD{|3-`)}3Pc6ROWh`DtX4-#CN)EBGzO|VP%%hB)KcRa z!va)v+80H|Bfxw1`?)TGDCZsJy<{Ve(+=suc1Pdy3=L(}(}|PL;waGxQH9RGeAyZO zOXo-1x*oBOx~2rRkshB)-T0Iv%RGX zr@Z$%dA9Uu(8P#B+5vrnG_=PVu`5XqkW+YaUkj|B;1+Q)Hl7e1iAoqCVSTVMNp`;* z4A79Iw7mM)L?RlX9?=!~PpZbEH)z{)Gu_yY;?2s(E(wzjEaKW+mlj(SeOGnB`+`uIWAf0;B3Ed?KkT@h1 zdhN6dbso+n%yO!_)s*q*%%>rcc)NX2At2-r0!*;PxFe>^&gDcz^c1V6;z=HiZN{u( zUZSthGAnM1?28fX7J{Vi$n_bq_bHS4WzbMv+{(1oc>nFZ0A504|K+6KI2?JV9`GB| z*v-k&v%HUM=f5;*BMnC{dW6X6e{441=$jLJe$ZmEYJ|SK1}mr8xTn7=qbs&bWY9kL z;jA2;nVmBjw!D#_K1Dlgj|}WLkl-4S;4RBDse;P0;#Krpps=v8x@o|pB0hXGp+~R- z6;U>a1!n8~%$8Ddv<|1Z+vL4F>8Cf^4Hpyp$O-5*AaB0|NloDDJf%royd1c+weAy; z_^bx`$rn-2kSZJF3Fvq!!}q%_zH0UMl(jD&Fxq*rr=NyiQ&qg+)nScCpMvtjUCisnk!_u4lI`Q`$nO1)UW zD5tjYMY{xB2FR^1rUzF${=<9+nc#f)l+u>}w_0P=%FFW{C`<&-Iqe4nU7Iq0hbRF& zYQXR&Qn?)F2TKY&ZmN?%PI1M#7f_pre9?Y&I4bjCVHq0ul2f`HXQKZ5@UKo0)9BqU z-_TP>V6dQ&Y>MuEw=)J)dmjBy9VfS`jtd(;U~u>q?g0D8dBFE>)VJ>y%8iLdUs*Yp znFNgRAnO6(GYoygKz{j_pQYl)M+7C(iTkKQemJ3+bbK!K-HgUR4Avr4`bkAu&WR97 z>TP-Kv|D!4re+n-GGZ~Zs^^Yw<a0S4CEZh3bQ~ynx~8cF zqZ=SySsb=FqnndbR|SQtU#Ieu>h06N%WY_bZS;V$pPxCVII(fM~b9x>>5wZ?ZQQpynhxIfp}4u9-;Ek`T3vCFqfUt1 z8zrY``q$o3=?4|G(!LxLi{7%752E9Z@q)MyxE_)x>$eGKKeQ#l3y#wx!` zu9jUk=D)~%RIb}H6z-1O>LACa7D091?=~3?KyAvmqE>$7bE%5}&LMX7N;-e1qrPy0 zA5IQT*-?rv<}d$WrHq{ZwUwLlNQ%&m*%QoGc;@yVvlHk-u0a6S2P6e4(%#n23fiK!SMD-$W#^`*0mvhvY}dh(GC|vw zlKpH{V-?)Q?h5DHyB)$REPOa;mT)v@<}u3x&>}ehDy8v(--Q6u+W~&)#3R(|(}?2B zaP(l?ck4_PcfNA%YVtMQGJf{xbm=h3VW|_?kikjc1>c*n6{hHV#pLEm%AuExuEsSm!c+AA>s%T#%AW&VgSxqMZv3 zV^ta)P>i!EgtH;+jDgb^#A8^m^X5FijIDyaCM>0}uJ~nOD5T4Us-K9&;WaJr)~ja7 zvZFIVJ)?=?l$!sbDz(RpN0A5+NAhEZjMepK379rnb*fE9^@f-^RQ7CHMph89omGZo z2TK%DBq+EzE=14-N&-TWLjz8?P2Mu77#SF~m74mew1lB!oTd@sWOLewqtk+pe1q17 z8uzVH(lgInE~16w=XV2DQ#}i&5zUj{?ClG>0YSLb|JUy5-{ z`1v)zBJYC+sbuH6c6L8kd1Q)z%JkWzsSPQWnrxZQ?LZc}VrN%W&LUeY+$qjf9uw{f zH^+?i?Vorrir|YnMa&l`h*pbKt0LFrvZ{l6T*!lkCo(Y%6-EiyK}FRNVi&^%BSEj$ zc}nTWr&shtDde5lAQx8Xn3_P@!_8`9i+7&RrVtF+#1X_b^N=y{hO@Ia$Zl5*)rKDU z>*)a%S8MG;BB0!$ajRWntRh+&(sw=!;QM--H9jn9-0QWzsA#` z*z0^5!+n7k`6okpywN-iUqjm|Qggnys@uaWv(t}XQ&4w~lSOSNhJ3ji#kf!w2~A#p zJ)fA8O?rKXzg=P=;uW8hLJ)Kk9#o8!atN{gz{c`WIz^wy-YNkTSd=oDB@^}ge7(l$ zbsPq|N$WzdS;i)e=0l_-OJs-wj6To#%YN;S)H8!lo@RvG!r?pNGC(0$(B;Dy7)TYR zAFKueX{ru5=F)#DM7185Kbm-b#$qH9MgH`Fm5%}p_D zhrFov!Jhds0p#}Dc&5wDS-{Ig?pOt}V0zcBFwij&Ck2O`o-10sCte#37%NlG%RIQ1 zRbLFuH^wWKP3}2O4a9fr5vxsV=uYT%pl?Pz<#EP2Uz7U%|J zI-JWZZ2M>Vo#Ub-^56-Lotxy@H7`Ylt6`W*3f0ewtdWZPclqkEa!R+1M>znq&KlQU zNr@%N>~=14>MFAcD_G5UWWY zxZ__@bHM#C=ABQ$jz>S`sdZpQrMt60khUr)IdtnM*}9?Umn!N}_1B0g4-@?ux}*3I z>$*;^z+EIaQMVuKWk8|~BXy3kN>J<>9EI6`oHPY`^P^GQ|tPHtdU;NjO$wKfB$O z@`${_OBS&2mCn_DzBmNxn(JZLb|&waI1OdpgjsZ}_`r@VwNQVcz3?^C`JAWqLDzt< z_4I&~qQo`&)#ZC8hr!1YQz~3Sr;+dIYg1(DQgf3tz#9qMzcUre z_8~xD4>xcNucTGnsqNk9@4v|l-Um4Mc055&4ZlBGk_Wy>*Qm$+u`7Fyb8%9f??Lnd z>s}vevfVihcvHj#0G@M*i{*&O`{+iSnKY8W)$;XdXKRs^no(x=vS=C(?h zZ+rTOp{=w2X?3Mbf_rOoGt)cbCeTNtq=Baqx-^sDQ6_GHn4<%Y;A5xlv4raohf}b6 zy^$gZ8P(d{@ZYgFd9;T^;}D;+FW>Jol*FE8k*;Y<4!desMQIR&;|;mWogn0}{P5El zF13u6KGMNJK#WX?!rS=wQQP{cJCi@oXx^)fgTylgf7mV0LI`eWU?WP8sKtP{v61w| z7rW?PxVgp)l`vSgma50gH5G{m>E4n$}2n~Qa@_h%(`QZv8+1Js#s0cG^WhWD57LQYlcP|rKJ9lIfkJwSK%uJp1 zhnpK#Sh=1uxh`c!Xu_1!LjU1K9C*8rFjn!&Ke@Fp-2q=Cjo8EDCV%HFKlb&@k5=S_ z^cT4s5Zz1sT@u>#d%m$}Sh2|lRrB%nv@JtkiSkKjrkd7Hr^d(cyXdj#_$P-u63aUd z>SMqPPzHn$^z5oZoQ1pnB&Fd}?WrY$xjAdKVA6ut?;Z6SXvUr?Y7@*P^P!`LmYVFR zhb?p}7lEZ|*##dF ziiwy7x<9`;n`_rl@IVLp=R?BsQ_bd@e|gKvr1szRsei6uFF`Fxh@utdcB;i4`ll_e z6gwMV^EJA;0IJ1x3>W4pgLM>hvY6&LpA`6S3T9Vq9ydq)(z9bVR0Hj3 zE6Omt;E}^!oIUt0W@iU`lmgE9PP)0Wfw1Y~?9{J#FgG6K_spB=Pj5p4 zAeO3i64r;?_?@BsX*88V#ezEFY}ttLN0X1N*G<=Oh@+qMQb;8THz{YR;NTK9o}#21 zj&-7*9BpI|#$cyiyeV^|Y3P^6EO3sLJ+t1n9c$E{oA)pvV3f2trSb9h%#`!~^9tng zaafx_W=;U`f^ND})qQX4_6^2ooRDeZLc6`rR#Hn>j8oC>>&0>>Etq}39qQHP6C5vn zEkwr>3U3z7**{7Nam=bJ6bJ*tP+g$b05#kzfeQGgO}B>wU4VLO+H^tXcP0SKyxoXe zVq>Ci;0kqc1<%CP0HN(1X#>JUDVEMyfN1~vD1)SlZVW*&DV`n&XfdDyLN)0XqR|7~T}cEZjR{a3-o$*~Q}3?%J}J>{ zbN}nEcb&I}N@)LLvefzeE026BX_<4$808v!O2doHq~k~Pw`qQ#bLDMl53Y-u)cllJ z;++*7;ks8Qch1!VBPz7#yn#D=SlxK1xT{{vcO|R08X@Dx;$e?Xu%LX|qO$D-w8?(T zX1Wu+Uyh0^!MG!vESs&k=Enaz*YRp(M5m&k73m zTK5sq^}O4&Pkp}#hf!UQ`|8)zIpB@sSe^S17L;JIxQ-9TfeWu>$ol3VmHEYU=5N#e znK}{A<{Wg$Oar1~bxdt=ijJvWTz8Zm*N%^A@W;S`tt~Yy4K)d~2mQw7&j*xczz1xB z^H)Mk+PEKT?R8CloDUbo#$)jaQ(ItefcC_5w?)rc)F9j*GpoE|cU3u%^Sx7r&kP?w zQXWhEiAUD~>N+Bm%E(NTSt zuTWmCWQ@zpndV6Kcd)n&JD4mkh}Uk|44(;;x6Ku9$R|<-@pR))*@YwcL8zdg19?dM zl8Dn>;x~=pyo*MvSs-MC#B5iIq|KvIV5nOE zCz)G_`l-{*3!PNb@-j@>QT6AFd3_fl$P1eRgT@skQxt_I`NlJL*^K_#A6e`gy z=cF{7tI{A=mtx_n<*|=4XL_fRB6vOG@fGt4*3#u7xyoI!;oMrGJCR+9GVb(8+|Y7D zo<9MKcLoraG)+$2YqHU!RMc?+2@I(>Qmt=uTXeZK@3fR70yGKQoo!X90D_3-g*Qom z&S*6oRS8e_zgIr_r2h_}Hk_q2vW0#0-b|p1dYBpfigN=I|LXqqV!D~x8Q@+*q-DUd ztJ0}|;$P};mbWeOu`@etua=`a6LXso&YPKW=P@z&S2`dhUwR4TxXZh9V>~Wfc9jHb z?8X6arD*<*Nv3MMot7SM?+cV^09C74kV=7}kwK8|f!0c_R@FX{ ztQ>bYD}C)oGOW>VTC!;v6I&%HD`6hPps0}VfhDXl^3Em%x#}%~N5Ia+`bzWZG@`wYB{6=({7Mng)lXwU86p&vmgc@lgGFwyt=6 zhxu{x&6VYZu>PhM+v#aOF|(~>p3DIj&62ki=9u- z81UVulO+z#EWJ|DO3V;e5fm?*6qTbxbaunNnugz7UX~t+<*Blizes*_5|hY$*?HmA zb!=~Mu4rO|I@*4ccZ-jPFW)#qb^HPuI&x0@Wzyrm={ym?B)A{^uZXBRZdI;f-Slx% zB_{5L3cCWGhijfo0Bp|AYQ+(=!;qW&(EZ|P;&TFHy2==*xE)8`zqD<5BFEy9`*My# zf@{}xhY7$@_esug)MQ}I^-&hvMj-3OV=+DB^pfz>%_nF{&=MN=tN(m7Qcevi_u0K^ z7uFJs-xyYEB>}naRq=^`nJq6BI6GdSmU1|qI758_?j_shs>Dri1s#WQil@sEX>qNN z&INh6Az+~uH!-L=REI%f+(>N-{+z4?5V!M?ICDelJgM65l@gWp%%ClbrC|Pj6nsDq)4Y zC1aTZ|H0wiEKLX+P8-RzhP~Xg?3XmV#ICS>Yu}?~=DG}fAJ2dB0>TFGpAY=2#+J?@ zot#|A5hB;wQ%D1FRtA7^CR@lCFMlaPb%BGyz4q?Nh{ZCFds3 zn?W#uA?QlvO6Lt3iKFCZ@(kGRhsY!o$+ze38Q;YG>OkCeA>oye>z+<`Gj+n_-8N=U zVslTP5Xp;j9e~wbe@GXEzy=dGUH=rK7wj*o{7lzYB;C*AISJ0- z-r?6~oZTys@hcFw$^FNTasnZ})J^ef*p3$RAldS%$L6`$^;}Ma24>$vZ&DHSF9arC!9@n{6e6r^vN68&%vfEJXiAg(*rO*9Ea#rw`Q z=P2dMLi}NiwIq+v1Gy!-Cs_1u2PIN&Kg4buY|q`WG2IysKzfR^PnUht=|TsLgl9r0gEgv-x%}K>|iTWJB8|j1w(H^WGi9P z2dlZ&*1htv@*-;4$JKOgL+^ol$bV8*kHxOo-tZbq2P-jdS=BQbwlR z)U$I@@h|jy7o|LfN+f~zt~I|22;7T%Qh1^#GhY%6r`f^YNL@PXxiT4aMgKewSf995 z(s=I{_9`rK;I5!Mus20-si4~NO9@ADVMW!Qf+fU*BS}i3!tDmvp`NRy-5ouO7e(an zw61S1dIniTc=TS7C5BDJQC!@RJ^FdXG`k7CH+HhhC&W0+Jxdm*S`&Oi26y`{>HdoYe;12`@Yhc`=1 zeZzj^7vO%BQCKtj?(UsUHWPMV^H>Ec?h*dAUK`arroS`1gSIJvsxd%PQ|UPvYc zkDt^o$!&=>X`7{pSw8KUY<@fPtb!6)rLNq+S@;`U`}SqVjO6eJ-3BP$H@H9_97iWsC*wMiifA>JVSeT)jt+aVSZb*#G~V zWqZr$orj^8Ra-PE#^gaA_gp++e%{B=)B0&FUNg{p{2l!!Lz#%(`^r#yt#m1P9BKM! zozDZB8~X|KOY^5=_X5W8M42TbWMIS^lRcfV{eHWzmh2mDT3dAxyP&G3eP4nbFyhs1 zBX>HwuK8xnF*CR9RQ1i`tTcCx4G%mB&CNCAays{hPG>g*I)8cKD&LUT8)`%qe(@xz zF2X-3jcp*oVsfGr>=14&#s=W(ie}AS;>BPB4`x~9VS2I)OYCZF4O%xGkG)! z?shPIq7U^j1bm4LK!dSPD_j(j`IHqk<1tIg^|TC&akxue@~tH+f)9^}BMAV1Ni-=_ zr23$u(r^-Ofj3mv-*^CTbFKbrMn5F&w#e(5ATS*k?cwk5w7%0f(PsPGm?9x6i_fic zrk#p_zbpXVWlk}4i~ZU#O4qJlE&FZFA#jX)w+zTc|ID;jG|-AGFbEB*u;I7kPsirP zl%Rvd_Do1*4S;V`#UQotHhs}!!(C+gXoboT@qPTzCe229a$&gXijgnSoc#Mtck+{0 z_iKhk8t%9xU8spq3kd2(#NOf7C~*Jmd?vMDWp)=k!O;KF^gf&Qj#p?VvOAtH@2WkP z+QPp1I`sV(Ifv1$+Q|n<6m93;L;5t+y`Atqhp>?9&WJe9d)%6fW&Sk!YHK$Ba=&e18nf_DoVLBm-gYeR0^H|9G3 zNw_X49cm)*x3?MM6oi9Clxuei`|fkqx`qf$iFGf#JKJh34vV=KiES2Z&Y3D-wkR=oL-8=W@d zwc;xZ0DH%j!qq)y>YSbW|KJg8-;dr2-S@1FR-I@}I$7Ab#Ml=d>A-ZTb%^5Ly%?HD z`dkAbRX+m(fLN|sM#kCnMJ`q2vU_R!=h7#MJG2?Jlk`o|mOle%G?0xVON9-VF;N}Q zVM?5|Q_V*aI%<|71{4OK=Y)6wWdagkxh>!RYmr*xTGkIM6R6qj2;9DR7DxOzU_D4LUDL8zM^>VA1Oj z9kd}}oL7+8Gz{)^hHdRCu#ArdbPz-JaWEvA>}*dfzLuHxWvCx{BkR#i*0{HqDF#z( zzm3E0(HKonyDtMu`34jisoSfG_7EHv%C z5=C&#oLJsGpJFU~Jg{A>8Jsg_+iw4@Pmt`hK6v3r3~lMU=RAsZE|2Cq;%KIKZs#GM zmvQX#63xX-C-vTq=x~ZYuJvQxm0E3EwI|kfE>g|_z}F~|fu)`RYd7_L>Rj>QcUT8b z`)hNk&s&g@(z6F%c*g&1{E-0j>Wk`(1cbdGcZMe0V((F&3FA||KGEm)GwnsP?c2X? zVleOdD5t=zAJ)UK>2K@u_=7J zkR|J}5s#;6ir&1K+Z+IZ>NQ)1%=J%1HAK7BnFpF}H(oL5l@Xd5b=PygJ(D-`Tdv0( z(s*r~J|@JYOUTur>zn_o3|j#mcPve+KG8Xe)^)-@8^Fki_Bi93J1Bk;|RbGSrW1bU0#!4i>7aGXX<6%+_KdyslARPL!g1Q__n?t6F zk#`BIZaJ;ZQ6E4H%dQMMM6LAP9Lx)K5dAn%N=b5Qci?KAZ$5THZ1^ans{2qCy{H37 zMXf6+LrM4&kor2Zq1sNWKe|N5jkj+N>sa$nP&X2g%BZw^S9uL>No5rrRR9WlsD=O= zLx*}1qn80u1|0eG+MhP(D!Y7#G}Rie$5^uBE0a`?jalK;eoTpFb~9;>J|nz-f(;S^ z{CX}mI5C1GD%Xi5p&0-IsiW}~drk*XMEzj!lH~eK9&1hC!V8Y~L+X)PU072ma_85S zL^kzt55SUmdeN^RR2-nzAHVNucp6N$be=gGB2Yl;Ozur3+j$C&y1-aJ?HO&H3eYot z-+^$cZTOu*d2ejnF^_$U=r{u__6%1qTH+Kv*6NpiPaOa-zdcXxMRvyAp7H53iwLmK z?$jL*k834FtX^Tp0;Zrov3JfpHvv0Wi2DkTnB%pcW>vs3T8)u$$>&dnTq)x=< zT_?4N1V>}DSEhL$65U7pu7@mp5gC!!-v0CG=Z)11t=Dw4XYuv-c8q-cMD$KXPu=H; zPxQR&&uwxY9#d(_%`PquxVSvD{<8N6eOGMu3NS>#lxm!?(J|0nYck>q88t*U?zCRS z?#$BPWwMKbCui3AzHv4jffn}9M+RD0c}}@{zME@`q{7~BHW+lo!N`MR z+~+KYINL7WkTV8_06q+)lz#lAzBhs%Vzi9wPh=2!Klty|GOmxjq&S9^b+7`fBMPXG zzcc`#pJP^hDPizDk2QjZK@0|)N++CXFvRO0Vlegd;wkq8f~BlLN;n)200ReQtz*bZ zYSZ+|GN+jh{nmjVok3a-`U>=ThdiM3+l;)dXppuZscr))u)9VQY1-%B7hPW;6UH`~ zH&eDbLB{JCBl&G*ulyVnLz3wWuLrxQAVJYN^>-71Q5N$&K!G5suRVE2uR-5SDm14) z_LNFrKb=N>%z8gnph?neTi)-6uado8k3sYHGl*xexr0g^Iz*VHz8{DT5!%X}MV?OK z)0Ff$#{G2Wd6u88Upz)-!1EY^CfmAHZgV0m(AGN;EznJhz5~!BXSsc@9R3eTO6YGrRUM>ht+#$(E(Q17P;*iv=-P7 z0KZLt__H~Et@FwD)zV>>v%>=cqW;Z_b!vFF^PX|;%|rD3DfP_SkDQvGEphh#-@cpy zfLoTQPoIW~08cT*v=U;GQ1x^uD0O}Ot~Fk{XynhLvbOJBrxo_L%-XYc>bkt^&ushL zXA3H58qJJ&Jq9io@X+;pNM{6w3?A^7V97=^czmEyaoeU2^SAf8M;?3rEXsNxfkYOd z(OJ|rpXm3zge;9ck(Y$a%L^P1OAALXpi-)eOYDgeWtJ6_(iEV=iN>(>BsEo!OWpe| z%B3i%)~IMRQaxt9lKNS7lCYLlfRbA1B>K5!uD^bs#eddyzFd9E?R+#RAC&JCs>hwf zPXVofj-Dt(OVWUWORSMZLBFqTB(f`fOJ95Nu*D z4FrhyRLcWRtIQwtera!8qZ}w@6k1!JgbZrY9z}J%ChyD5AgDZ)+q@UW@IBzad31AL z+PYg&*1Gm`^GJ%p0xV5dtqr}(c|qzO-w=&}jAV+}in0b*NIf^! zUFW(T87OjHoi+Y6_@nEWX94zF^YjT2Cdu)<{WSzt2_VfsonOY|EOxDTUe&bQL@*hi zPvUtNSv*GhRGM$2O2(TpWKHmV3a^d;-q?5aHN^cYMdv;;8T$K+3Iyf7x{P&_8hgrm zZR@B6O&c286n-56*4p1XA9fz>jUhIoaI?>otm4!skDr$a3Q|Ba?}2iiBE-wz39a`C zQu3WTKPAe2FC)}_Qm;J@nV`~guXy?jHf>XEK%!L`VXoKn3F z#p=VF{+`a`eZL)xw4wP%f2N-5(?T|{isl8=uDqk2bWt0=lkXJA*X(oK&R&FoM{DWM zX~2Qkr#-M{R!*^REN@4E!=62IE^#>n0Jkh(eDMX=bv=pODo9&~O&jnROcd4=2{`?o zX9!y(euj`)N;(JUZ&nEKp(%XJ3(wfsdI7$*V6C za(2AY{6?s|ea7p1v=Ph@=slhxsv+|ihYQ@ja|ag}7j5G#D6}H65lDKI5s#<#1X1IS ztV6&>_w620G59on)aH(+OXU`7Oa&RadIFb(jEt25isl{Ugk0>Y;$sb}tN8hm#U3-Ce;Jg_KsuGeY%kHcQ5< zy(GF|Ffd4N9T0xd#+lGe&ruz>D8BL>2IGbQURN7MvGwrPI=5%DrGFTH%9<}j9ekvM zWsPo!3WjH;FRb`wEKBa*J~=WxSPC7G>J%oqa&SzD*u3({_1Z7BRuVHw+e~JOW^BFk3}g04W~7 z24Tj?E|#U+eV>6Y1}MZxQbst-n$|g?BU&bX1w0O589JyXYt}bvv|;Tz04xAm`#OR_ z8qDzKUrlP~OF$iw(LBI3R*?aKfI_^U)^(Ig?WKXnE&!qF-}9~pIQFaPLrq(c2w+nl%uEwyB3PcyJCyxS4%@#|D*mP@8r$% zUcXN8}aLO2G-{01gPi^ClboXFlG{!_A+~yzaBY3)@42^`f^zVP*hJzvqHew-wz)V8`jTX}#(8 ze(%UjANAK0rk-$*#uXvbw(oWhytbqML>s2=gH{|PfX1W6?D`x{Q`^587&2_?01k%* zckf){^5UW?nFOGotIx3f$A*cV;RZ5jU90k#dfxJlGEo1v`4q@mL+Fao+OBlVrFvCM zKYd&ik)?Xj-OM@QDhdsd$XOKC+S3)Kh{7ky@~Y9xA-Mf{RwTQ^+mzIt*j1zGpW2!5 zaUJm4>7l1YIJWWR)Q$~wPX?d!bm*tsw`v=gbU$-O% zM^(yK*Qo5gS1AL6gcr!1Yefg0AFKkXS)J#h8|!5>B)ZX^^pS%aMh_AyYonQl`O5$` zP-$3`ZG2oup$hWkgi_X)<3b{DK;zmV1#W53hKSJI;+2*y5`=-Y#C)Q>stl;CD$N9ykor0V=Fv(64NP4T0Lkylk&n@Uh;Atv`#2s8k)PT_JccDl z_37{WeyxuNF{tD0+upu8w4w5ZXoFINE@(VC?&~6>?A)|2wa>Cz*V3Pw5KtS;q-2`* zrY9p0t5;n>1d*3)=yo7XMFaKzC6z;>z0kejN)G68iA){$vze2EjeZ_qKXQ|E8>c?* zzKsJ9i{^5TQ!`SJ0$Lo&g9gg#=1|0>c`k#(y*C zEz>)*ym9mmnR5ZH1*G(I73sC%IVbGAM7mA1=Dc;|IU&4!8XgUpXKjRIH#Q#4Ngj3XoXTJOj)=jo@BW6I9O)=1vcb_TwTPK|BUNYvLE&Gy=g=xFzo zbGdyv0|2)yfBn~gow7Qh?=hi9IR%+c#;XtuhCRWmi>H9;_q;RzvpV*QC-^<%-_j*Q zMR*=vg& z{&F*NMhkXiXG$eFz;1sv2#q|SwMsCOen z)!)T%sbgW<@jURuI(SyEZ`LwvxH)mCEJj*z{WTn&L&3)^{nQLh*85X#VXle$Q7i+Bb{IS8WQiS2AIaAn-T$1 zBUy?%&n(I`2q?6IQq3FDk71~Ovm)R7Po&O)qelSfV;hA;$+zzV0F{E$*Y+qYH%U$l zCj(5wHJa3UJ+(G#y==^}Vw-br^JC?}Hkyj-lX)q94BUO@;XZF*ue`pxQ9ug;jYiM4 z%%!x+&u`^->Nk^E**a5TBhMQ(HtkvX`xqo7y|iSNJXf`Qse=TO9Bz+Q8t@-I%f3CY zPYS#nb2VsL!E?|2#)&YiJj=s7Op`iSVz_emsu8W}l3G0)w2=saTAvQXPYU==6@X-! z(Cb78PX+jr+zGt=^nCNP0!(#5()(X!l}#o)f*qs8Nh0Hk)TiT~O2hk-#&QHd7&M_# zo)M}nrzU4n^K&o^O08XeOe6`B8V{=Vtn^E1h6Y`8W@z!}auZE#R7Jp+H2@hW|u{rRZFs`Yux;>k>J zuA+DK{6-Es%Lm@ydjQb6?3XhD@CJnxY;J{(d1kYLbFl`b{jAjjyK#AjqCQt^7m_{m zZtJjp*4`G+_>RC1uRf1%5m4Z52wF+s(scv~ytX|Q(MA{O&m&aWLpZhZtvwaqW?=hf zg}!dzo0AC1wfPY&z_bUtSo(9aK}Mm&&5~-{7l#8bE-q@jsArO^0o6`NvQn99IA|`Z z8AGDbH&aS{^y*3@_KI}_u^F+cDNV>gTG(#YgdRRDO`cdWv-M`IjR8e zXE$0>RruTaSfClriBQTB)aqV&ZkLRu(~x?-eLi~kYuIM)9hWeTSv^Hw!HW!$CDe@hf>Qg&$ptly`&{w(fU+I|BrJ!eE2?~a zfdK`iG~hxxacF&M00IHXHM4k1?WcI7sDThoudbgrk9wmVsDU1oDu4BLLIub5D07gQ z6BGc@Xr7nMQ_(n;!3`bNz+Sw&6fZjvd@*8r$HUfArim@Y3&BMHcq-3+ty9>TB41;%HR$% zbg)IDTST51Z}DG&l%W;k6JNc){Ohq0AgakDGJ=uM<)e*(g3vX zKOo=-_d|mL4DRw-ZvkzMjs~g`II3L%FmjcFMIYx<^B^&1{hdz3)&@ZUH=TjMu1!uZ zDYbM&oDnE!L#y2q!nk=M!Avmf4PMmg%;5Ht*Lih#I-50Sxp0I*6zv-6g;uxKZFyze zr%~^-Myu|(1uhuSv2*P?!Mx(>l6G$FcxU?X2-s~SiL5io(jzjMy}rzzkG`{W@AbiJ z!|Jgf|9IerZSxES+`gOvfHx>=fu_CZG%{`8b)O#>qrWiww-aR53eDM~-V>}^(bH%0 zbzNR!C9i)rv79$H{k;v@kOYiLioI_t`5Rnj8tj2-Vajie&KGvu%37fERTT zZA=j|y@!PRg1c_2IW?mwwUGO?0!0k}>Md>kAXbSvjaC{A$kHli1$M*d?f-0aQS(l7 z?tpqNTkRJ+m8lz?tp=4$sp?a5OL$_9R|2q-$}y1mJZ(+JSjc30ACzJ0b@cjLUWe*_mHMdBzn+ipo#_z= zloTH)HjLYRtV#OKMmzzj*syM8m>2_Y^d464Ew%g&dffw!XDJKaJ9jIuZxW-38h$R)fVPGr`VbTJI0g?kjUZXya!~-7qP5R2Rv&wea2N-PadmvZMi5D50 z;r1H+a~i|Ob@UU9nl1@dRb2mQ5D`jA{faT#uvuEhf3{sYN-f_mr(GvrbzRsn=4V?&CjDPv*Ic&VX z*w6Sq1}`I8?a^~)+1B5=weg=IbNk>NS(UxcjMzS}x%KLP*ff@jow zeTnE1A+ee+HliS!SDOq=e>MXIG_pNQrl>a=U8fbswvK3=vrRav40IFB_fWyUQ zHy+kPpdnMV^2Rp>tF-WAHslL$upmc~Mi=Qx88F-jF~5|rgxGhImEotd(Ni~T!CBl3 z8|>V6SH8vYD}}I9RZbUZMT1&4_oVJm7X`g~oa7E|_E7bA3$;X160!@}JK>v`hx@_` zaH?ldIe&r}->q*dh?I~}Qq4lo)(M-O1(55ysLhqKuo&d1!_4di??4S31T|8ic9e}w zq>Q{AkQTlYD|IY}ftsw2E_7!Xua(v(@konDuJ@fP(16x{H*}TdD-jlIt&@zi|J{7H zOJ4f=V}2~F@_uRKu=Prkw^g1>#8#u0O-xt5fkMLIk(SOw61y_!ucT|r$q2_-OUR{RU z58B?_*7X=IA$(kc7z|f8a=r);G6J@HbO;?@@(>P_Juv$%2>CB z=~9}^t@O*|^Qp;aG8z4qG$IyY?P~)1b*e0DHRvilCPuv;FauirVFkBSLDuS)|0WDfgfg=5yP^tj+l7)5>fwK+27XXf>_?Yp( zt&j$Mw!I+fBTecEY6cEi`a0PDF_k1)c|>=W<`VVxu)t#Pf0?RnZ7bd;?-w` zFd_9!=M@NAn`gReprsV7>=EO;-`m)8(#K4Sq7n0^O3((80g`+Sjn<_awJo!JUE8Tg zGc3!3ytI%=h{4ZFQKgpwsG3qO3A7R}FDlTr79E03{ao_l&~iw$u-r=TRgFP|Lb)r= zIji%>R1~d70Bz(wwT`JKCOqC8do&RMWX_909ERQ!19o1kxgNSKgv@u5b5J7{w_ zES=t+HqM^4spfrXfKyos zFJHbw${CLyKgP|v0x6@cEVIVr5X@KVPmvwNb0a9Z1y9AZ#(x*ZlWe^6_>j=Q@3O>z zpKM%-QB{vH>1#E|xnw|x0Yd>Iy5R$0X*_9V>mc`=j4UK_ijP3CVVP6LVOe^gfOZ_H zhz!a(_ah&b4{KCR=XsBxi4DP#{%Xd=<4x3!;KSk2f%bJReO@&LFzjJa{Xzw-?40x3 z(!4Q%V@26dmH3|;$3QTjO9$_ z#Rk+Sl~bb)is@A4-YQXD+0hp|4w!IQmI^KuG5q`9Rf&g$DhmrL&>}iW1>LW&ukq^D zE4;qCR=FNn9#Wsk!@);k3@*j zV^4TQ271)r=vgoOt(V9#LUN-yx98gSgrA50MjK$Xd3xZ(shcgAYNP;Pef11~^;iEB z&z`+#MsPD=O2JxIQN)ztJ%ouC0%hu+x!x~`0@Kw(R^prMh!rGi#%A~CK)kBeuo4we zdsn!xMD^;ks=OUL(3KR(MI97@96Nv*6)9D(F?;U#{3;uU0@`rGr_>BH8i0kq4%=Da zgc`E0&;9fL?|m#Jq3Hm6#^;2Df;zT!t@p&27B1dH%o_->EEyLUhYlL#Q~{ov52C%;(|Lba}z?OuCc*+WXD;CMU^igV5jIEUG~9$SP4drA~I){?g6T#Y;i01`>^!vex# zIkZsX()&vs*4@0or2`nsi^U#sG~kHpwPI`}9U1_^bX`F;3WunLS*H#rl(OP@lz&g3 zJ;Pd7{OC7-gk?EkSuT{XHr^kY2Rko!qbc?A=eBL_) zil0fH&$M`A!;58xtlhT9H1Bhshld~It4RJsS8jb4uW=z9hyckB{(tKhc{q|zoYea+UO7@406Fy&s67}yNt zn7K#^%W|k-X$z_6v4|(W$25SDY6J}D%1HILkUAf2tx=6cNsuf3Qi5uq$ItYx>#LN~ z*8&@%FjACx)n=z>`N}{5=&0UO5j!hkJ+Aoj=~MiVzxpfu_y6%%`0~rI`u;)0lCI^q zK;-lo^*JxnQ2+Fb^)l=E*8e=6$ulZF-O;SQoq7wlZoU0}0>JG&c#gtIN1pM92a;GiMErINfN(DVFy#yY+_Dgr zM1({wR?!-~T!iT*kq&=8V{jdwbE?POwjT*D{VW&nY`O;`*!Q*Z69E)nz4j_5uTOhu zvlZM?U-yK|Ob`!Ob(ubTAchs5ys^)s8lsKQ6T-77f!P@K_nsiOb0OZw?S0?6PtTq{ z#b5lzf5!6{&vF0${jTg*ulOq0&5;=Jz!<7XB$Eqd##!c-9ezz44A6S>WuAtD?n+_Y}-UvG_ndIF2u(qQm;o0 zuce1CbM%4+q}IZHt@J2rt(F!q&Nn0QS@mVfSeApz&YqgVvM~4b`daV%8Gey#nl60x zRIQtBu9U?q!~NAtFe}lO*P3#UO24Xrv)3}!qbD5Yp+JfR_U_i`j8cRb1psPC4@ho= z&`Kc#A*C6G>_w9GDS;#$4ZsF>JzCNLm{K}zi3rEF0ur5IXi+e&Se^rPRzoxAtIN-t=PR3c@t zlG>BFf1D*=C+5p3cMzr9K5gL)#w_s>8hz!Vh=Re$6HXkALfyxd1&Hg$AAn2(*ioHK z1qzgAc#FK!pf9Pl)a}OxNMuKz+P$vwQ>9IpkLsR!y}xi&kP_Ne9;oPvmirVvGQ)!Y zZ2|OKHo(9bg9Qy7Q8H83UYC+1P_x$D0W5p^TqUM~M2QT2Q1oaPX*afegdpEC(8pMM zecesZVC8VDlB~hHucR)6nb&z9mPL{uj(hvk z__nXyt|UX9M<>katM@`rcaP@U&PCLooySvvq*Fcop5A>fZ&uC#z^%(&q;yk3_y8HB zCgo)z`3ul46fbF?em?Th?uAp?MPiN2*+Py`K#MSX^-8$mI*h^dWa|>0eV>6D9(_DP z6qU%h=1nX@o;|wT=k`47TG?Xya0sf0&d=t2MrZx*OlW$y2GdZ{&=Sz&~%=iHy?$N&|7-YlMecE2YQ6A$m;y(Cd)(4r@JRD9n+Y926sQkT7A z-D9V~5T3gpWuf}d23KllvAYt*N_+z$IPn#MBRX=Y1t6Z^Az5SU|Lb(@zju$EG7wrn z%f`#nfC^=VG1$Qo3~V^@Ieo3j=su944Ts++6$OBl%IQ7SyqTRis z{VX$kg;P($bvV>*_IO;IAu%?s_45G)Q~*E+qS`ZTm~3Bk%WuO zOFVx37QXYH@8F;OlYfGH_wM(#m9*R#@;vk&4)dNM`46DRlsZq4UYd0-d5!cr8f6;8 z4Q!w?;MYMu2rcyacRmZypXwmSL$6Ta*bd8c|5Ltj#t-{En{BQjWL}z z`uLC5xmrINsO0e_=6eP=TksY#-;z>uf4sVSjXRf@<~Ew7_)}yn)3fty+8R^5CcNv1 z^)vc!y(T;-i*|6$a$L`M-=CzWSxswFh^&w8Kbc+mD)UH1lTGvl}ITiq>k+UUCN zZ3Xr@`?r>1-gj{y0V}CIUWgn}_PxDw%G}y{nYD3Bo9D7$&H%uzOJ5MOZMZ^DjDI=H zDDU^$0--=O3eRn3)2&f#g;OLLW}fpsLsNqlQD*y@;XCV3)Lvx#TYA_rMZ%1WzM94} z@;&t03&-;M%FlV(`n`Ezn%WH@0y84oStT+8Xf$2)Gv51_pGqmXxw*m9r(faOvuDlg zCWaG`nkS^5WGB8S3R|xSd%0O@Z8}~1y|koNnkP)yDS)1B+jw}o$KKSI9Zz{{8#7 zfB!+{j+C&lH(tv^CZ!B?-qm z+YyXSM9{D=Fz@K^#CtvfA7Fqlcd&;efp|>%Xh0u-&T?dB6SI;~@;D}zoeN~4*W3}* z=Ro&e`dH^V>FXpD`Yjqn5+1GJ=3n@>gL>0Q+Xq>WwCEN>1B-aZNFSAwgrG9LZM-D` zsk9?F?TAQ8D4ai|MPbN9pvekqYpmY>vaX8X8ep4qLZKs$H|k184WhI?eK{;h;3Gt( z18t=gBqAJ|(Z4pMzveJmhlsWgA`3%E#7#}8G0aJ6KwKfh(vF55*Lt)9Nq}?a6W)Ko zdu6G|5DNHu(aQ7J*VnkdzQVd5$FnxWdVQMBiw9iT z&%E3Mv$^tZrTu8>bMea=0Jvo#x zNZUVuY@_iT8Lplo$)55#? z;I=|90vNnLi?QJ&_cO$vOI2E`;v~vJg4~O(0D#o=9X1S_VYqTgtR8o81V%!DElsHF z(rmO;Pkw!?dt;Pm7|I|9fSj}krM;&Tg)Pk^4OSBD+<-mq%}ZN)lic&(FE&KGw|fY^ zMLYMc+G#W?%HSxLzZH$xfGAYPHJ1zs(5RCVBorQmdUcw*_OI%xwNK4}puzw^>dHBg zTDU471E>HfU;otsfJAaO6#{l}W@~C*(uKq;-<9~3K&dFNJ`cXFQFV5hnXcdRz~1p; z4K)y?fj4Zh@j(p5I0GHgpKa`?6l@M{7x}RD*5t%d5hIYw3L5`k`)SeFZf&p9!~k9- z$p&t;0J(9ZpTk!MRFwzZ zN0B8PKZ9M57Z3!H?1?hFOINLhl#o;D`5Zc?Mk+dlZRA8~_q3V(1i7!(E-RLd zi~~|?Q6jFAZRA#g9OWS8A!b?A4B!NH6iYK$=WsUA9dL%%0g)KfTwlx6a6BHd9*Uc~ih`Im*S~Tf@v6vHaO7I^~dnQ_Z2(i#lH!y+ijWs^8ZE zSyyBE8hMrp^~N5_*AVHU!}>74&-6PxK3!+z;F>wrym|!mM4!)Ezkr$IjA*-yquPnw z_W^>)5jO)MW)3Vbiev1HKjw(0=W=>E0|2)yckWy?#c4D=J(N$-O4*~6QZV^%Kn$t+ z-WXaqGr%H)(Hfkd(Ba?p&%Ju|cac!`1e!Il_rQzUSR+I?3YpT+dBS)PMb+bskWdZO zMD@%7nnffXm2OAJQ(|&s_HRHv) ztF(bZq{5N;v+n<7>x_W_QKh?=sLy5U3$tD?)o78Fa>Ac+!x;IiUtKs(Kz*kf9|@_2 zk*EHwR?Zf_a;jP%ih2agEhBG;XL<08CJ8I=B8Y*R)@cDxglw$9_xVlo52%W&^*j+# zyOxhYiBP(++b6VrpJaMeUnr}#8o0wZ$x+VrU8P{_aTYyp;&9nEM}uKS10@M9F;shp zX~J{Yji62%@qXGpKoqQ4nTfzd>%p>XY`BGGkU1O`zzTiVaRK#uz*3N@W)SDB^Fv}h z<*{eF_kML?Koaw%toH)yAP*ZCJ%2`@PZdSM>pQ7T=pCnP=j?OB@mRg8)f3I$D|AC& zB4rd<(quqtb<(sv^LPDMk6hT=elqJkQf*w0nC_2rH{P13uFwHII|E z;A|Jp%y72C#sdgW2?dIfH4lBAjcewO_XL_(zXeY`FE@iJUVXaW?$uC6`<}<%Mj&`m z47y*opL@WAH%`y+j|AUd_?2f=%r>c+;i2ir*99`&ncw7Wp*cgp6P@6ViO5`g$%X=Z z^BB5T-)+VW^;`n@ET#tCR10Q2afFhx2UFEjXEWTd>Vrh9w~&7ZV-!%M*IQ!B zh=jB}#>Q#~YMdVQwO|ZcZkIH3bi>WmWI?u|y+{T}fafq@fozEYyAD`&ubNs*oJ>;B zYooie>$=fCtkq77ZDfm{u1MZ_0B^5{+OaVJlzq9!KL>-0*pQPP9BfD>sGmH3evhM$ zdQ}XCv}hwWj*)^QuQ6$*FU33hhrRGr+(z#%J>@UPn>Bd#2_I zQab};G#C*@4|wCa4K4cIqC;s@y6S!mksW}e*lvv-4;<=V#58(=lUPi*LaZac?roYj zBCp6Svcqq)Sw<6t-g=@v*`R4NKC_({Ry2wOn&|em(!9qr(VXis+4G%13sDaMRZ9zN zAX=XGjG%!pur)F?UA=yv0<5VQ6u}#fO-u^&`$itAUsAJVBLl+nh0j<1Gu}Qi(2;5$ z5rCQ}7Xs9@^V;=suq&`zbB zx0mHKQB{m*)AG$wb`{1ox7&Q;KBSG-_(*Ve7SC)GX| zor9<7G4_t2%K<*706@S|1p*9!CK}+S!kjlCwaUd&$H_+?a|bE;78ExA0lBFmT8s6X zKAxNHcbcD=Ui`D)NWnLSbjmAyJGG-!Ey?cj<_7CgZYW5Jsi6U}G$e2w0pI!2Nl*W= zYuJ1C6gjo$`Hl1zPoLNW3--pb*H3Rx*aI?V^6nIR+=dpu5pMh9$!*)WZNkhw5Mb$5@SIz*y|DP0VC;?l65ea8+f!?X_vp2tr!!r!6 za$@^n1)e?w7KP%^g!Bx(^@I|1A7*2)^GQVgoTHw1{rzmy z^}rslAG7hyz#RR%<)PWTn!h|FaJJqweP;dg=H8GaE93hm79OQ#?}9_##?(U_m5fy!Il+%FB%s0gt+Pz)7S;dVH+co;{W`08#ut zMk83$d()P5(g0gyz2kh|0*o*;u#o~G-=G?5(BlP1Ge}fN2gS^|sT81Ri)RHmZNQ{x zo@xeoJTW*4$U7(QIdOQ&lwfz5N}ep@16qz-n5(nfHx?S(aq(ypYPRmDndIG*ryul*3j3V zTg7)arrEf($H41rG#38d%do1Eb?<#oIf?o;QzoKu@;~dzw8GCbR6P3Xx%CXLXs#_! z==U~W5CfV37`z+Si1Lj7S=k%!jJDjbD0z_OF@D~9m?HY?xjIFW@syqjWSbd;(N&*z zo-7Z{N(6%Jtxb=f-Wsy&N7GHOVO`JY#d5rRlkdFo>SHK-9*G%n5GnK)6gt)8W9MD3 zBk#HBUTjacdH@U4e_59CUTETs?5M_=V>oU2WMCp?SVN5e8I7a0OyrN>_60!C$-tf9 zck6gm#7i3!8~Ij0C7>t#1kkTDjMjw)-nO*Yd}2NI+x4%FzdFwGS>Y>RR>wTNRl<2r z`Sd}p17g$zEw6@L)tPec27jIB)Mi+&(i;E)lEws-Xx_knkfM3lG3FoONDMaqQ)PNa z5oG?_d3Mbk-u1s8f9dsW_p|ypI-t?{loN=wxy_xv<83{K*4EuhKkc>m007(9K1q!i z>#YxMRywFuy{x6vSVc!dK$n`6GB+?rCyNt6W?fet*JHgxQLizcOQuWljycv2l(Gdo z_x9=8o@4bETcRkgfrNn}Y{5g_w+OWL)|S1^X>aUa9TdnIn=@+W+=*zazq4(5vLn_L zv<$KEP%?RD!r5@gZ|BQ?u4QR7#}RG!=qHK%aTu(7@_5}SNx@$uMJq1y>sekUZX9ceZW<>QyhFLTwtJFv1j*Nx9aq;^R zO0C--t~(3HG~3iY`g!fnHX@I1UON$x7Hxv|8P7nC5;jG3So+TT;CJ=XAq~2D^?JaH&)DtBFMyPI#8f+-zT@?P+w}&>k%Q9fO?~{5a8Vol zzFZY@^UW7L8usFIXuJRcY_g}W9T#O7_GEwDcQe1lm2{rV7_Y1w>*@X0lrnOG>*DCL z)IbA!MWfU`UYXg!hXha|9M>Z5S^O?|B{~?>n!( znRjxg$lGyxf-Evr^z%`FX2!BNPVc$MBeO?$&wF#5am+k~ULPX`XErZ71hIQ4%=tq* zPs7znZvcmS?eY0XZnrCM##o*4nVpxdH)2HjBSTHoXK&Mu!s#R0*?09b);py^9}Q&9 zcs8PkXJl{A#G5hNqXSXfGlLQs!7Kio>(_>+HFCWhK{zu6v-1Qu3kSmXNfg_PEj%=Ic@WD^gd?v ze5OHw0PIRB)x@LN${?mo&Yb9T4&Lj}Jfo|W(yjtDzKfDHP)O>Y4x2fgY0U3>SYL*D>YBM`OetukrEu@m-Dl zaNYpES%#k-WKu9Eb{;%F7}tuQV`k>L=J`EE7n$j}5kKj%+A+->EFSP4)#d4xwvSajUWZ&8LCnM>rOX^dmJ~WvcML^8;+*r>X+l}V`HsFWn%y<(W_gti$ z0f4VjPE}61L=3B$Su2F`!X7tD4?WXfxIMvYJ$-sCd*H!L$m(}yo4}sIzeh9MH)~MZ z@yrB%RG&8&{JRcS_q?BWo+29Yvsx*$WAYTO$RM%(jSSw%;L_vaVvQ!xC_2?xj|?-9 zul9_+nKEK&Vd-ke;DJe6xwf0Sx3(hhge}&%_UPfguiLim+Hh4nM!ViT{;>5&;EHY6 zTZ8rQE!_ZZShA-z9=BDhqPg|>z?)Zl1jM6z#OsmF(h0phezJA4{Mhq==(=n)Mt_H@ zSCkSq;}kk=$E%u$%#aH!D-7^9+bR#)%Wn)bH;wtsfKOegD|wrzWKnxU)fx`ufd zEKhmhi??Z7#lSxAy=%v=z3vuu-zyQndUV>Od-RMQt2d5Q0Tf%m6En`(cS~EbT)A+ z)-Z{pxYTi_YF~FFlMR<{UQG6;BpC-ZC%81TpUr?c`M#hKuq?S7c!Sfw_mMwK3MgQ2 zG9YKaN5Dn+Oe%Eeubq|Wv*P-@3KVP&X)Xik-8MI-8LjOQKSxo}cyywjVd!D~OoKi< z4o_LHq>kp%g^mAc9Y?%r0Yd)TJ96Y1z51#dD3^dvLJlQSZ$$OFB=h4*To$3CwrSTaAc!P2V0N$uHh#3CD z_BLf(_7=89<2>|`pVOP*TmVX)b_3;2fCXP(?7CD?HwY;3@f* zo_cNU8EM}9SS8o?B{J;1wGUE&IQcbk=If=mu+uzP#noBLgAN9NZ8qxU~dboU$)3Px1;qVkVxH1o&lY8SN`B!(S z=T$tkjnT!-QM7PaLV&CfY=E%)10sp(o_L)z{To48h<+56J%@VdxLf6G_6)oqyjMn_ zyB4-ybLqR%xw1cnYLt3e=RUmoV&(e>R&5gz|Lcp@l)ogeA44%F70I4sqOq|@vTkLO z&noyPDmiIGaTeNV0|v3nPK3UnZ7b*k&qb=@eq;Y!Jm2d;MQl9>d;97h2{JFwc-{Ww z)$^Y|Fqmfu22=cT@lp7nY@K&^$^{p|2`u-;-5!crd0OsZzju4U=!aeK^s~wH9rJvz zrna9oa;Dv$8cH=+g3RwV)~fAPM|~ruKLcJ4kQtiP9lK7BdV*lBrMH$w-p(8u-zJ`C38ezHYhV$HHp4qP0k)hyLqpoK%teyuJV7_>rhg#&J#S1bm_|MfIS2W4*9Y z=nmMx_tBVWSnXKmDTaVFm#U0Oe}GS~34IMbEh z`Yg@~2%!&jlCFVSeQ2s27)LpN#V@m5=F+H0s?d7E(K4L1gS;ZL@zKa5N zQC72GGE?nd_UxDx-e>Z{Dn9!9vsN478j)Z_FIjFR9Zw7$n-`b})|aialslw;iH=Xq}mu6T09t$`#;&~I> zA-?<iDO$AH&@yUOzz5NG)dIO(*K zMuCRGJ(TCJ!9;AZ?fYD73VI{}nOXA8CD>_EtDGTb?pO1WLP7hOtC1+Ra`4JNzcg4v zIec~v(aSd3C_?>p>*DR<<-ZBUb+gqAtuAnb`wk?h>4B*E7f+JHt^0G+Gxu-p7z8!N z7`Kt;727;-DB}4O(DhV|Q$l8QNB3Axk@AiPCS@R$((%Y=8oam4y`{h@%a<#*YGN=q z$5F|JdCTL|MCyo9B$ah>pP4EY66|*lZ`*%$35EWs^M;!US*q-Se|18YK6dPm!CSUS zKcOuOl_EWqvDm@$;)`}n6-=$)#vi}XQy3Eyb>Pis`I54+mMN#j*aCH|dQ^AFyp=;= z@AE(Js|c|LC|yzQWX#cyjPk*^dl5yp+U(VqpIC7L8T`-h$XNvy3sumNpUWVwX%W%d1L^9sInPJm^ zm(DTfwm~-5;I^ZvH{)Qt=jf$J(K&eh;h1t@!|{Wq|C zZfg~qPPr`9nqr*ySR01h^#G#zfI+3RqbjlUkv$cZ-wZP|Lc`VzjzZPNOHSlzSCBIa z*DDGNfQQlR)WBb_TQEnP&IX96YdmW|Wkh^LJx#v`1th9HcxaN6R{8F21jWg8rpk?l z1Iw2hu{azQ+7Y&c7Ygiv5RoqK!=?53% zo=-{MskbkE8o^cMC{GHLOgW}JZvHwkaw8_b@W!5w#@S>J?VVc$VR`aGQm=G{vf9DL zlOFlwNAHcNj8axOUlzi2)EkGZc=8~6gI`X?Pjc5BU0?Z*9OkY27$iTnUz7p=&LHQ^H6&2&( zScYB%`Gkgf&u_me7(I9L+eoLnyH`yJr7&=w9$U(JHB?xJsBK;l<|LLV6u!@lrWx0b zGAz~rojEkuxdIE~&Q!lgA$vxCAofjKYLPRN9_$xFIri!T8|f*w#-q-P$j*N$K*?*w z?(P0xW(t&htDTX|l3IT$D@W0LrO!S96sp8T#5AuZWNfT@4?U3=sc4k3=h7(tb70>H zn@x`Ld!zGWP(B9<-3KAg>ez3Da8*^BJ8y1-%k0wv3x-X#;7n!)Sx81wdEY^XHm{ZM zE;OF0y0XCYuHmt)_Yj=7modO zd=nneCAtFxp6Z9DSJ~ITE9xEc(C>0`IyDoFMa!N=KTS!{D38u5WDEwNhc8CpI+}Oa z1706*7(6?k924lO*AKL7&T6fre^e_n8)F!2F&4Mctq8;iPMdtPeMhkTc^5$VmX`g6 zD<|`-|Bo&DUjjb|>qM1K55M!N48@BL7a)DbV?Tn~z<%FCQEBX{d^Mr;^nHzerDS?0 zMgVrjapZJC2%=;lNXT9H7QnDe3|e1fUn^MiV{;+OYUq!}WMK37iexH&p=%1}=j=yK zmqZ!!6l=^?a2d79G)@S(CUokbc*o!!AW+%)5$6v*RxFHu5A@#djqs~6HJnSxj@MiP?ana(+#iwTdKfZGRuob<~Vx6JKc zk>L>eBx6QH&(#0R1LJO{r^xBK$IBR2m< zorTx)5Lbv^+!#>9np$1H`YqA0?@brl!N3do=6)F;E1l8r_@B74`2sf^O2Bjb91`Ur z3y|aOiI7Xp;BE(gwXV(qme}AB$!od8aw7UY`(8@Y7D8@_&>yPml77 zX8kX`biivSn;W?+WM(<5p5G1PsM?$s9+PWxdvZbxt25-w7oZWZCG8ycCa0f%X4j4v z1)`hx2kwyJP6I4c^qYm0*9AVK&(*~$UB0z&#d_;K$Ezq6C(!_D?c3fbz8g8vz9upy zC~l8=w9Gxbu-TBUnZrdP*7I4Xi}jB@x|Qz$<*bTWF427Jw{Q?9WuRfK@9iVI&AAl5 zS;Z4D$@T$_tp}>T|2bkFEhxb!DG8L%)Yj1JHnJS#Uz@t+2M4l;-$telSFq^^2U32l zc0ax768=faop! z-ixS^%d9Y#r7cnl_#Kxd#kYLKQZn+)?vK%Cr8E`qT@qNp>|q5CCM$m!zD-)yxvxjh zDi*ZXEshjXe$AkZ)e^D<`&eVDuqGx0QGNVQ)oOG=00jB+Tv7a?%kON~ueI6|jnl;T zz)8mrqW7x=@c@Slc~z-d^hKy+4_ck?0W)Du$(18vw`?4jy0Fn;R}So<&{wdiI$!IJ znC&ZJU6}D?T3g;b+h$!hQ%Dt$seIUc*cE?rH4heY8w%{U_A@~gwWOs5V2G)OCZkns z&r3J7VvW_vUqa)!-1GB?1+}1(GHKFqn-xOVyYv@Ldz_XJ zsP^lFqUD~B>-^lnVJ0BmIAbq=eGjaNwGS*^cp+dQ6jW*}%&f=Wnl>_uGT2aFxI`qY zh|$pqG27Z4eh2&@A8m}e8@Pb z(pN9LKz?vtG)IN{sx?!m(OrRSV`+Tjm%zU_H&1LnUc8&}C(=b$r6K)BjP5n8P!Qr$ zf$YIwV}0L8WP&F!&Skdf*~gnRi${q|XQy)~qbqgB5aG7~J|p(qBSyJ{zteIZ(n-EV z1H>)?*dQuZkSs~byBY<1Y2$KG>UXWDVZ7v1grQv9>hM95;_I7xQUcW9jO5a9{u=Ds zMOYJ-Gx$;z$?hb-nU6%>k~+cE3w9Jnz~AmNjSSgI)p~e7V^Dt)QM2ic$cyHL3OL*t zRe*#qZE|{!WIC-(q=`D2L7b*mjHy4oyqU9Oc%NF6R9JIKrOB@3sd;NGIPum?iBClk zPgB2}_mu7-0O_hPrk%Q>ilceWPodSLZhPZmOjh!d@#4bX~1wmfwqqXMcy>GvmO8E4kU(e|*@a!JuN)Ngoz3j+_Zg!hSURMMBBJ zlt=(g<#7gRUr$DM(v@aL8`4ArOc-G(vwB<`rR!lIYTz6 zhOKkwuIE0Hk;|3vzsNnMF(+;Nzu~ID#^~URPH`^G$<3d;uMrCaS7}d{DQRi4Shc0w z39b1&D0Z=HPGhX2d7_W~RN@QC*HvJ}oihr%GaxD+l$aEskN_8hmgHC%G8=RJ>z4gc z-*7j{KxN~^QekiJ_@49C&@SHx@D?bh;mUNE$c_5b92|5Z)rfT(vf9YoWDs_=o%tdj>&6$rz@4&;O<$!Bm~Z1|V~5^<1AlJwwtkn2&$!ng{i+3zqJjb>%lb$kq^0 zFg2adJFBP4bJRMEZ*3YDO#qAT9X2j+oB3J zvlQPnowL)N`Q>y$djICN?YBid+n712rO6G*nv9oKqOs|x4}av5C=K!RoyN<-+{$Y1bs=wLwe5meyshTa4R?_g1RP}ghh8Y`wom%x&4&)g8$_$A% zbBX4-G-LltZ{pnjcjWz~2(uGErV>mx=;}uQ&HC950^)LB+>DQJ+VvyUp;4bvA?tn) zy`b7;TGmFd_<=Lx?(J6so_WIGQ;aW?z0mc-=FQ-Wi^S8C!X_USO;{qq&iu-GWji9v zP3iGFe+1F2mkSL%$?$dgPU371rbX##d`x~&$uHQGE0(>%Q!}uW7yY7~J2_K9$)_y2 z6*+t_?7913$A`^voEvoVu}?7x^_*61T2xdeW>#ae=yZ-;25%FT|2eU)lPvxW47FWq z3wn86A}V-hcQBx1 zNmza2;H$wy|Mj)909u@l^Cmks=>;UdMJMK!thM=ZDaCY#<@z(;?TyKz{v?{5K-!{` zb4^oGnpZ)Td@-yIW%sz*tJih|P&oRft*Oa;GvhYnVs{=gJ?DGesIkzJ? z_FwT^2|O?CQgqAfuH=yC|7!9;yaFD5I%It_oz!KmnHbNMkSKoFf?RL=IDjepS)Yd1 zB}k#{r{>4-+YE&z!Z)k}HNOa)g}tIFbGJWi9`nRm`b1t&4%_-Q2-MXJfh z5BOIp94ODpvPy!}Fnp8N+LIZh4<0*iGyMt&TprLo={t95og}0x*4`#k?i^bW&!Z+^ z+YK>lSsvit6QI|GHnHG0T(9VpQlsi5WY*?W?-iq53cVk~7ldOY=t~{NFU=S1o){nY zCYN#D7C9p+x79L?wk#EZ?;wv7{ly)%KS?mXg z`*t%l-_=`$NcC9=7GycZS>ew<*H;;VT zngFC3%BP5*4MkDYmF%6-j|?d~m1|V7$5F8u`5q53K#-YYz^dp^cIj%UTdJ9AU5f&6 zQo0f=x_j)yk_l4&q)p7_O4{O=y1NCbPUezLr^@bn1ak((Ktu)6o{| za*#CZS}H5gs(^wx#UQa@5{R8}j9edc`5;wA=kQ&o&0f8zQ_j2Gzv|Uyy`A0RDLYnE(qC=HCNHILQnHo&QyM^OW90Xg{e8n9uisbYs z`yKIoobN^$#tNBif`<&L#7}!QS%q&nlBMnf&0o5{7YzUjhA%Ch#HsnXL7m>j|8Z5PJ!&y=-b}Y(KO|?k{&nLUdKNTzens76rBD zr1f3ubpD=ft3{m*9(C!iB@4sL7)dFio-Z%?Y7MXPO(6 zM|n?RE=}b?*Q>lWg_lDC*U-7dne^?r|4?zNa(~J*vw79Oc0uk&l8bx(OCxJvRrc7{ zlfzBgk3dBsb0N^3k$ES)UYi;)qGZ-lLt$Clmt zKp!cxFnslnpEQe7f)8J>+np>m+F@+^0M8np7@Gpw6$YA*pvm9=rjn2zJlWSyb%I-xF_+yZ3$a#!iV`boi12 z@bRIGdS{-onI81@q}F@~$61v_wnU589ZdU+(rOAFdAM3?BK^=tFVl|u?$NN5vRdVo zpn3@`;z9$cBeU6oXw&_owO*Vf4D>Io91hGCRwL^bTBivU58M6fUV}Y7 zqmeBqWJF6cldG%iHD@v<#UF)kHi_%0JK_Ti+E;+oyS=|Qv8i$FBg5|T=^yoG&-fPM zenV%A(eu#mzMp`MNy0f}Xir+Auqb)K%t+i@`Uw{_e5k{Su43aIAVvBgp*Bcu49wr# zQQ+8;;@@_V03xaj0!ga8UaX=UyfBRK#l`nv36$hmOW?y*A> ze!0WBsXt%EKh2g|rUiSv;n>Q2hMvW!p4Da-l(bQUyU6!h7Fo9K*&e_9GnABpf#|%8 zk^v~qVlHe3a8vQZBWPR4W}yadGT&9A`b1_PMU39NJoDC#O^0>b4&*ZfZW1u$6Z4 z6{R?Hdi7AYyd1c9eoEa8I{melRe1HZOPkMbeJDN5j~5Wrc(gxHECg*rY|+mQ+GE(4-lRo2h&Yazl+7OPb?0?Fmn)rFWz|s8fnTpZ=0G=ej?u>x;*xC3%zvLO#?Zj@BI%)+!mteyT1l>J;aHqwj zvLda_)rpdEN)Z$|k1-AYphohWtB>8qh<D%V3OKAPoqT=pcLy%f5*YdxgRTHtJk@$IOH>0V(zP`|+mWG3tRX{kX``yqJ zLB5a|g|B(ZED(_J4W#AbX(Tgf|6CRhfX0ZnoRj^F2Zbv^zdR3SCjQZ|^^6G)fJLl) z?mQlpv}lEdq_H=onXl*i<|qBu`A)C&RB9yA3P9-_%ybD~r8~SM@H9EJxv6xgimUL6 zZVN!glUTcXm8O*dvm88}8Hy{Yd<(nG+}dJop365Q)SIKcO5#Xo-LrX;L+L@k2IGd^ zEdTy(_smExQYb2R8Aw)Kb$7Tz-m-mtAMGXrc{^KznOw{uW}cN-Q6BwT^))xAph$kJ z7qQFWHv}77_}nUGO>5HxSIe0FGP_v3M;jW{&nHDcU%`Ca- zJ6L=NF!yE?>#XGIxXb|+_s-_hUTC>OhN#R73{xnauhhUKB>p6?Up~`wcD9uG#uSji zt}IOQO*1cb2bGqX`dL#?N+N8%Yb;ia)lkpJyygm5(-!;xb}z>_ji2eG2G~O>RU#)D zO5#5EtDs;vBQOG5TqN`YO7N`MYc;4?ms!QPc3+D1jgH)jYX2BpyWv4bazfO_tZo+q z980t75H!P3tIdtTgyKNMY2AQDf$y!x3la#sItfr({)Hi{Jo)?DS0G7? z@UB4(!?aNxq-};yf=(u}Md}OR;hcqq+2T-!1~J2xnb`Byk?=@sZyxK*jBUMAB*wh$ zEw+>Ute7seQE@lNhSG5_6YIc;zsYt)llNyV8bBOolY~5I7c|^T{nf?5vD`rHdVnh4 z>tWNE3&W5jP>GUdi%$SCLV0M0M~}7G zcdIKW0rn7e79ScaGxrtkqpy51{`-t-tx#xH0kvaAgc7?lT?|iL9m!8?MK0j>EfQ&I z!1M#qxiT>$ZpxjFTTLMZg?;m6UypT?T*1NYF(b=b*#7SZg5C`Bj-tMJp<3T z7HU<2!%nuONZO*fa$9ba+pqgQsH?}Bb610YY1x_F z>PqH@P*BwsLy-;SM^rhl-)efJH1?CsV*3RZh_Lergq{CicQ68v?to$up;#EU>7+}# zHN3xH!;FI#w6~|orQqY=-{1f9k5{e`9crm?h4hG-Q^KfRPkkq9s@N}#EA zA4@zP8EJ8e{zyF2_Rm%-1V58m;y!fI^gMQ2pP)HeO{ZM^a(`O~Pg zqr`-rZ~l-V3d>#!qtx4EA9h&DOe;KTh$ksxX@m$D020<6%e}I{(tWas!0!(NVGIkG zlXd$$KtFbD3qgcS$v5g>J#kPd-I?sA?dzV%A$p$ctx-WisoVNGFh@6Z0M6Kzl8=e3 zbcJ*6b##Tq+WXh6p^?-aZu)43J6wt+@aAKw7)->i*06t0KH*R|o3)M=tfC%QkdJ zm>M9@{UN3q@_{QnY3tSdYSl`EU3-Nh#Vwz2G7|IJ)$T(MTN7S{_&ZX+Y-!Ji0H4oe zqyn3-lR+7KW4w`t`{*&lO)wG@M(K-A*nd4@98_Y#YfL={^$1rU==ru^9v+1l`wr*g zo_PY1=dM*mf)`n>**0sTlg=W!h^S~A#JcA^?u2xs^BXCM&F7clxQRs#EDGnO!Ty$7 zSWAA&qIzKQhwm|h2uV`PExbvx>b^dST4TI;G&AdA$@fG&o*oC)5)GTIr1IFESSh{5 zZo6^(S<24>s@+s22Y4s=otiKNA3_+~9i#J=xH6i(Pq8vLaCbpf1guLyJnI$wei)YhS>^r9C~nhzri%#TG?6P3D8$Uc|;FU&`PU=U-(F`ZL6?;}C^_ za-dn}TlCsU=i^~69GqQ`=* zeyoK~pR2i{SEe`337XeFO-suBl@-%Y`#@-TA9zL~bHhq9i!HS>i8PMroVCU)-<>to zqI9rUlq2k5bDy&bQae+EkhfW*mvEqi@n9>UCX>x$G%mG(UIR+*D;H7mX(%3|Gv)NTt2gIua?`A3u2XwYQ zc7yh956GlO1AS;&m^88;yENE+GN62HsGBr>b{_ApwT|BDOCPDr?YPh5QPO$(jg ztptD%CT-!*N_Xu3VmnL$^ocu#Ib}z#H5tyX_@rtLb_e*NQY}i)?-;}eJ424oVDxLw zwpngI==B}Cw;W>PRyOMRRp(n;x0lSgek34e)%tS0C?V|cCs3pxj3ff7u`ze z6x}ntIxOE?xaz)YTew_Kk+g3+W-7C=TmqSa$>cd%&%eS=ZB=du=`tnt+^#;V>FS8W zYLed05tK*Z&2xXSQjvQnROc=Tk1H(MB+97tj+cgm%!+CZds0H<)R3bo-|fc|ilNF? zNf{>SsNP8%4=xrA>);5wZ)sDTIF!zRcQsMaR>yJe7Xw7v4<60LmU4aA+T3~3$b~Qj zi}`lt#=U3VugXJ^=wDj=%&ZJQ_P-h7L7!CHY#rAoxLUdqJPm~Y6w{lFDWb%?9G%7CZHV!vQ3ch0rY7)yBckz5 zIP!xG1Lx*MIR4&1)w;9rG0-L6ub zJQ1}moD2aMLzT`+6)#VqCwm=q`_aU4HA|dKE1|KUK7*BqOh3I)WBp_}O~bXW8bqRC z5OuTil~gaU)=t162aPxV)Ty)8v$8HLvad5qexug~Ol($(xgJKJh%7_%#j3E+$x_|k z1=f#_`)bkJ=O9RHZ{f1ratY|SPwj)7wm<1yxFpgtr;LUul%(X(-7R`9^Utz@qv)q8R7 zoTWP&FLmYX&fzRGD=j;{UOGW$t75}_rKdK05Pl^ zv*fFQih+Z9BDY>3Jw^O}8t{Z7TS_Uk9S80-wQOaqX~@)^x`_3lBVk+ivKJcM+)Ago zDJkOl5`uJ|gLBm<&5OkpmYsoV7&H1&|0%dso_a?xE5DtyT{w(V(x2|y3yV?{ksHve zsRn1pJ-hT}w0f5rdm%d^Ugr0)qrb`I6+R->ma2idWANI&23t!;UyTR|~g==~>Fq7EZR zwma3c()H1|eH^p9`Xf+hyGlb-Mku8o3JwpY+g+I_B1U#@-!Sfrb>Q-tmQuHI?DRvs^muXGX*(QM$O4b#Dtew*?JkvILwt*L7Pbdjk|}Joar6Qt5Jo zkG^EA`OPlqDn~%%UU1J?kt2>Z1}DZ(erOw{JQrr=bBRgN7I-DA5ve}QH=jT`HC`n8 z!n9$3Vy3L33iV_9bj01>ai#Yu<)D%Fd{D20P=&HkDm^ehX8a-8I3;Ba2)_ zx74{0r7A>0EONY}IX@P>hfS`l*AhQ1>RwezacncP4C>CDpPO?&;s$i*G196%qk59W z*MUPxQZm#O3SnPeJHB6XOVd@zth~<*H5%J}40xud$@IIDBeam_Ej0#E=dl8D?=!l0 zgRF?vT^d6#YT4RVj4JZB!49;WeDA5m%^yZIfdXs2)s$oLaC2zfnr`^JOy9ZF73iDgHr53zaS4-ojN->Q7lO2lwy&FvQzt91Eq+!2W54!F`g3tSq?=r3bINe%x>f-XTGH-=UhOvF8!VOeQ- zw{VOqI6Opekay)M3n zPK$lD#Jwg`-dm}eUqhi{wx_dZUbld3K7(l1ys7*Jq8q3nB(8<_xl5Au_IcrvZjc2ckp0xa-b5dmsG4&&HJDAM-mfX(vWPQ|jBFsbQsroh>gjrImk}yiqy;Un>Y#EY@2Leysr+}5 z77|za7X66_=ixS@{`&s6Q&?tuv2^j{n#(Ha@4CxkV>|o4A9cg_5e`56d+iTDf4tWZ z{|X4Oc4T2K;u7AG?cbDb(%!nSgp(}?j;!AX^8m;vVkXGxYqO~5PKid+2SNul?yw7kqx%^1!RM-a#UFfFb_VFjOqz1P2ArDOzi~Fi5M@UBWro+_ewzw6_Od2Ge0y@6Q zR^FwD$mc~#9G2149D{ujYtI05tTgd@$dY)9aAJ-O-ovP3(Vq_O#~sTJ_#H|F7TmrE zJ}D$X9u(K{Z6e2;#lKeKfikvN`yPQiaE>3cV;q7{RT>0Ytp0oq6 za@cK?AzJHGX#Kd z%2x>!*)0zB|EfOLw8p&p7a6QdmV0&G)$8E%ABO9rAa!wuWjco{}n_t}>j@`Vp{u8-ADn;N}&rs0HFD49UuGZY7Oezgs=_zDFUS z-)5Gm5YOh%iXLAfHL)-?X*h~{ImG>*@T4R>c)fFihXn6=LsToq=I5Fp!N+dg=~uV0 zY+lDCQ77ExA7isx;$Tr-{c}OwGUH(=Rgh{3F!wdu3@!#lfnK?eGA`dD&RR|mDTxJI)Tk=j9qk`d7mv4d&-lQlp z_l@YNZ_%@2K69P{@%0_=Ns0Ip;d%$B-{w3pmV>IKyyK!@v7<{JAYG(UyniCrTX z>0YT68Lv%dk+F!SumoJ(Y`r`#-}^g#=o^*I9y*v%Vqm)A4$Ms735V=tuu*JIaQ1fd zv-OQpNxbViFPi;z_MDNakZ>0U>^IoZWCuHCW{yan#o)bzb-=TaFg&TpPQYd^U^=gp z)>dv=)un}Ph4)IYOs4*$u+6UW=Fg9==>FRUAJmNcE~s4b2C0fhOi$~M)#kp5rVMb^ zZMA+OQg`9n=b!uSsEGN=i=|3>Gl4|bajf{ z?@Y0rOs6p(N~_bv1qV!GI-HU=<{l=d_tFwFXsha+B*+L;Pxcr;BNSzuXZjvf-(_SN zeAmNO#X@K${q$WMNs_RBL*A7d=L-H1RbO%5msyIL>Bu#Ps}Zbil%qU$AG%)62TIHW z*ye|AwLlak2DHu8_|Ug^ba>zKWGaySVDmTKlUSAp0Qqipty~Z7coyZz*IhifL|7+P zB*5jbKYHDxL$0L$*iAloc|m(RWdSU@u_2LhgoQtqBYBZZklKwC@;+8@XepC>N%mf$ z8%ZgbN1qr*W7(wHfc-m-Kk#wzZEk4Tx|kpc<3nOc{eMRFyJ*QK^AS&I(d8|W2voUc zO`3c>N5j3#;sZ!tzk%lXRkuZdLQT+wc0#PU@INmyB-LoNbBY6_jkOQ_S1C(+pep5uAq(ya_?Nv^_+cd9F zuG@zfVG02s`uWpuP9`muvd(JWw|UU)z~yNbXI$!kfQM(Vu|^sALrKCVzesZO-TOgU zx^WMScy$8JG%fcxSQpynU?nudptUF{5O+#5jcS%qvv%wdv|uTA;`lr8_9c10;fQLZ zy#tuNfiopT$za?A&{!^4tsA7O4=zEsnv@sJRdfS9%cuX{zL!F||GmLCd~sEoOT?sL zn9qj%Li>D^w_c{nw|uz!9Ux=l2;gzejVaJ26-1iu$*?lXNAAGKWt!I*pKHd1vdfKm z?<%VZw2(<@CZ*SubqpW=zM-7mHG77N_ER$mP{veF&NGOM41N^=oo~|np3SyK1AQ@R zC+&2KU9A^&SF~44O6O|lgG8uH+`=hMEDJ*zhgP*2eWPg`^;Eh{yU{aKE2bg^T*bq` z41#oL+~ea#TLCjTd`}!~eY)}PBv)@Xfkh>3N^(2;M%&^Au8r|RH|Pd@j^4^q;7Gi!%({$(4Z6ym-y-2D6437q%n{sTDX>|W@os0QLg z)t4P+CIbs*cD0aCjLH`>lH@Sbk!L%fP0;qMb2u*&fl?Co?3>@>b9wDikm<%Xc9J$S@;8~D=FsP;-IA=v z&=OnDvd|-c1=Xx<|Psm-e2;uYAF350Aa?rn8g&&Y8?Ar<#Ga zk*j)K;8=kf;Lfp5JO5XI+Ywag8%8S6Q6sK{kVHW5x4}80G&qC znvYb4Byk%Qb}y(=^a+mGNxo3q0V(7~5nM7e1v=^P&^QAcAuISqF_&g&;1I(b_=hfR zMQF-wt>+*K9nxqGB2j;b^|V$P5f0f9D^-ieOx#SXd#aWdobUcpqqqX94y&$&9C8Zq zjM!~Wu&U5G(9xip-oguSMLqqMKiNR`;JvJk0_5nr(%DGtUbo-*qmvG$bNiD^d~4_< zU~4oHNPqg`Wsy)6a*pR$HgKMm9gnjd>~cWqNSn|R}eZ`Ct#QMb3;#X zbuA%SzbnFt_tnA`yWi>I)fY>@GXaa%mJik0lhYs7ZwLcls9K0T&iEde4M>-*@eyQ6 zith=FwK2Rl1$OFYD?!`yWf~&ONu(izj*B6Dq--bSjn%8cV(A!FI?s)#`muRHt31_5 zNdMuLu?M7pB@XSu`3fPb*IcP}_7MO&Za?go||v*9nub$y?sDd3V{py`43}iyILtCKngTIljo2I z3vIHj!gnzC+996Mzqj&wV{5{Bw_g*^*mO>LOD^fR?uHl)Ltcuo_FPkEH7qaxPX^+1 zdb7KxdXD6K>vr=)#qxIT#}WDP5vSHFxe%CF`2YtkE_G|>DMT-k&H>3^1H-p1q*`DC zsKkR%V`@9%I<0x#_4uuPVNqx!^xDgpolrE{GYeCQ``gfuPe$-SjeSZ z5GSmwG4E10SG#VMBjSdt?L!B(_F5)~^E!h?n`e;)3k*Ntw7s{DTCn-jZ?(dKe6Vk4oUhqM?h*KWISMP269w+B}}M zRaK%w`S~B}sx!ojFqfav3ek&H(1Ve4v-o;;tj#JQP;N#=A(MzFTR7>WI@yo)i&8r8 zQ2K+t@;yFNf*e&q!db$^QQ}Woo~$3UZ*;Sb`-GV2lNe{)4hD2Vel|I|t5qVSOu^0?b9&RN|!}^l2rbVE}dC>bCi3aR|pYeFsgj8n)q&#NgxTnDf)ni zo|K~A4=>U}n8z-Ttf+yoLHPY>;$iM;OX))P^33l7sBp3EC@I9y|9gAd23xjwEfm0P z`!3ePyk9=({Y!}?Rxbdgv;Q_%KqOyQ%40vBweV}T&s)#vuCp;-mKgza4TfDoMkpp*TOJ-hQL4n1O2M^pREZfg|H`tyaku3f zXXL{kcoYe^ZQeYJ@V{8l2_M$&z$5}JF2_A&CX4mLS&LU@RB86%kf&RBoVx0(c%;S zZv!wax4^>+UZ>lg_kXt-p0py1a5oKEm+UXl4xiLKI(1$+_MitU)w+QELr|@+LNiLQ z0G~Q0{eHG6jXfI%f6@@Yg`4$#nH8J|yY>~dQkMypn{jU0@K9J|ZOYvC?uA{^TyF*w zaS+geKkuc1!vj)q=pEr0z%?K)O6bkAsV;jp(QGM>+Q6L0v?keOJ4l+pfB+YIjUwx` zKSS)+!80b1>57q8{6;*=b)_Ly-hBSjYPgb2X5%c!r3+WbE!#jG!c>vWY2!%_20jdT ziK3tSL8xxn?#~XTA{j^wpEjGS1XBjW*+u1>{*R{jV28VFzrRQCH4>dfi53yPmqc$N zdMDawgD7E)8a1NVOYcPVXu)V9I)h;J&gk9f<3G87&+`g64qtok^IU6v)>a<>Bx?~$ zJWd`qxaJ=6&!uF#a{Das$|s0A=mak)IPZRw<9b`PGvuH2hAv47ss_7!gBTY7Pn$+P=|!xeo0N& zLQ5moo>ph?$=cum?aC3?$meAceOg&RqC8YrV_lj&cJ@Ep%qzdjt5I1sk+AQQ&XDa- zHM*6BA8zNJTZqoYa9>-T3D1Q#_tiCTH)53DHGO>z@FOMQp$5GZ&?+^RIJx?)A<>=D zL01Ee_K9PhUMT^UnpVa4(ckNgVtnY=+)+;R3j#OXL#lCWGwR+S2RaWR_vB#(R+re@ z^t=$(laZrHnut`9fZBVeL*w61NP0(9t6vH`XT&?YoSoZt6t#CWzfzaAp5D^IQuClo z#@v&7(X*^i=)kKfTQNw|d4HyUe+$q1H;eZql6nJ62b^%lLRn>VUFz<(NAlyM`{AFN z3MH;$PVrZ-vI*;N-%lK}C@W%X_zKNp`2x?^E*)P!vOYi8M2y_IZbwZ9EYY2mU9ld) zo?(ln*ZhMD@_y5K{>7QYzi1XEEr=@8{JU_m*-|=n<@272!b>3--gK13VaEE=Zo;mj z);+0U;rUo@$T98AqJ{0RT#R!};fNo&=`GNI_ z5-iTMR~9JRX=K&oS5-w(wp%RUe;C-)xlnNeU6F))i@*7DPp{qBKrbBKP%eGVg@(s0 zE4>?I;n}+S1Q3Bg^3e>C_L-h= zB{&~;Z+eR+20U3`T=gw2SkV;Ob;kClkodkC&-G#}rjg-v%g}PI9wyr}Y*^z&8aG7h zp;57xlu!7>F+rFc(rVEVahMrBb2T7%P9HDD_z`#g03%Bf_!U7Fb?e#>JU5*aakDv5 z#uch@)8cEsxRv-F0+BV7=1f*bCH5v*M|E59nm_P2xj z1^NC8{H?Kix^9AQSEnhGD*TiEuk0^gehn08)fT^GsTvfiVPA7%ax(&+vN8Z?+1xj&&)Z-9a|_+R7sA@=UF8Wl z5-SQ2N^5^`E7g7`*yM0ryKlQODtbP;Y(>KD8%!3;cqz?k6Bq-ihk#f>Y*!IHd?^zu zTRUXWUzEk9HuX8V7T{#ML2o9VgIPb*yostt7p;n-%(3vBNEm6?r~f?EdROk7bXT}) z1B>+*dE~uK#eoSOpaMnGVyG8DgmwBJbFT9?X5o15(UeWyB&{`f{tA{S-CvJhk#674 zm!Lg0qD=fs-$sKy$%LQA5wCocB&zS=jmc6 z1aIZO4-aiXLEEMLqngpJZCihEP{TnteCT#2Ltf~+I8E*NB&+3JR{fz_B*OAv+5Fp^H!-J zaO3QXKCIq@4=-=xJT+q?;@VH;LNQ&Yl>3r$WVJ{a1~6M>k{u1qB;1f|NM>NC7T3pJ z(1{dh$4(;ErKRggvUU*1CMn=~zg4^N%R`ie;Byu$lhC?ni*XE3m}okTeF%E8PyNOv z5{@J}9yP_MT^>n{RKKS;ZKnK4kgOK|DYrE9u|{y_zW+ScvzkDQsDOO!b{qHqbNSUF zKm7-IWP6PIn@vo?8kiDpC{?D49W%N;c?Q;lUP&XOnvZ>NW47Z}IKEy)eSw;+EyEE( zc7@?boo(}G@-6V~-_(Lm8J0SOnE&n7{-$LPmY=P5n<4MoP(aAIt*r=;C4 zsV?oBzd*h=qnmoOEP8Nv|NXoLa*xNs90KqLBg_vbAx%h^GeJqdhb0{+BeC!H;e7Mz z)lV`DxQ-4Pxu=QJoh*Vm(2l6KJhOGw13Ke^#{j3l-+~3j@pax3Qj%98{B}OvK}Dxl zq5y9|DT)(fxgV`#RpZBUiRt(3zhM(cyL4i5sl-LLu4#aZ9r3~tu{pb*T&Q@}bvU6w9WWdR%pO6? zNpjrI6}a)vhp@6-8ziLY#ADVvdwaRK)QsBQ@>`8R5&98eaSjN@<{l^?RqLCcCP7Co zzE)K1<8Fcw1+uUK670P!4AxeKyDI8WUeRmJ)BA^R%em|AHbGbO(HBW`k_PFEe_yl< z>~C-dv}$%r2Ta$Kz2~j>;T;#>ecFj(y(*p`&lLHv3=irH%q4mJzsHjLlX)%^K+iI` zFku$*xh5vdbuT)^VDV;1z`(*=_Rt7=w{n9Zu8n$Uz`9Y1k}kVk{sn}MgcUJb+TQy; zm(TvUmAwi)zB;MTdm!$-H0=y`3*4YbpCA_PJdy?Gdca-Q z!96kVGDsGH%?XkB*8psTvs`r87sY?v7aYI%Y#p|cjmLT$oA)ho#t%?7gr22YZg*oI2;DN!lUAOId^JXx0WaO{=F&}ireMoi)JMbRfd0*pudynJl8uy%A zC8oZ?PyIaiJQfQOzTZVxu}qgUXsGpUr0acNU}}gCt~Tgy?{JCcW;9spZA(+Yw3Sg% zq>@`{`oluGaf|?qwsEAhwzM2OVvlj!zl21R=ZVyDM`|eYIGd7_D9;$64Quxq9wn~4 zn%R5<=~EdsjzQio&xoB$=Yk%zla<1l_NwIYGZ#*{K<7j_=#0W$FpPFywWnw zJq2J%Qr|ucq16n}eOcs>RY>TR)SI0?mORne8csN5vYX|(Azhr-r4@`9w>{q1QR<0c zE*0OC3D(>9JH1Dfv;!j0a2Ky11}M$o29Yb;7mQF3LUM9x$-Bip`b;gLv)nL^Ti&7-c921QHO#a>H_qWMh+C8OO+-w+#?``td}Sb2qx9cld%y%2yD(w{&7q~5dH;(9 z_J5QX=*Kxh?E{u&738kw&aq1c9bMf=5fXGYr^(G)Ll%rP~V<0-AECp~ovOG&> z4kf-f8O#LJv$6@D&FuI{v4Rac^%W}Lc?{teIBs69K$hb;FiEBCxYrqBSb|T+6i_*% zexwhZ@s1nd2AfHkA7L;&U8VdY#bY=gElF%_z`SHV$9yk#DA2F%WTrFNAPSvHsU)QR zpam#N)jbWKu9(N|VA%`QPQr2G&;Te!^7XeYh$>(c$E3b#e-AtxYFeqOcj~+1y5E5n z-D{MyQ)#5?VE;I6(mZ99_G55-!PGICR9KRYPEs-YU#vx0%6|*EO<_;vLs# z=vWk^q*`W#)wfbB1P*~r?BOV5rfV7ZdTx>nSf5_M=|cOH^>NjQe15-J-FLF}2UY6r zGQU6;%vq;&wBPS&LNKPfpXzI^zlfnwiPjna>L#4yO}`i)Xcj^!V5_<3fXfq#jalIp zvq?N0r80=zOiv?uhCdSkZ~o2pCw{r3l9j&!o_!S{7xVZ*_M?^aMO=-Y@W%YmI4yvt z3QHZYf@khw)FR8pakGlvjTHJ2bRS1sw=HIM%N*Z7n(gadxRQ_%!t^yG!mlzeQRm&; z-I13myoI`i28OB9ZQUF8yz=UC*aGR9dN!LQcLk>f3yPcyYJb^-+q?N zSzuo~dkg6B(!0`4ire@9uT-quH^3hoSJWCmBGBUTW&J3H^};JFH<7trQl9+sDlx>d zoh-MuE`YwBZULp-a1oYw6bV1#awsaPLHZhzeS5GVhJwzr@$04orhziv%=SW84wCfo zqE~|KgP8P4shXTSU6Ba~(uN6|Q*>n|z`gs$%A4gl`P`~|hqs)XHRzdrtzQ*m4 zCTd#;F%oJD$tnj#!MA-$r>kQN;$OaHd`lAcMnQ@E1JMF-$96%>;J2|j2Me1CH@-Up z6KyFM{RF;&xg*YkC4Kl#5kaSUr~6v6J(ttomt7RiB6NAY%T_&iB2>%j+f5uQLhoy!H)&6!V`r`zV`DBmm0A&4C}C3_Y`7|d(rF#PWq}t-o-JE=SL}PY4S64{Z-Vo`KfOA+KDA*WjU+MG$I}bAG^t04QzS&c!Y+JaY!$!p zqC7&b=NIlW4O*}vnJ8`5AE_$i2ib;i)qSeGH$JQtc|GS_>uE=_J=5bSoS$ka)p7Jd zMQJdcih*6)Vv(#P3eVGh$Fh26-Tf~rwQ|?KqA0^qir{eGqxZ3V%Ygi#D z*$f}n@rkAaFl%ArxPIZHluhVOo4r0_++j+4Uyr0Dh5JH8f%ZfUoI|UXwq+_}b!a5b z3Uq@_?Z>@5x7uweu~g#XQ8Sk8G^W#x-# zQcvy&gyM_3=Hj`Q-v7bM^h%-$DTHZ)C!?;yP)T>N1=Ozt&6Ob>` zdRkg90X@-wJ_XX){p$k(CD-F>Lqt;dT4pSPV(es3+j>zJdN%z6&@ygD+! zKZ%s>%|q>Wb}^&vK>hy&adckJ+SqIR@Q|0mT2|>z{W5O= zWQz-a>VTjG&pya;$G4!fw$6noc#}cg*V8pJ=;-E$2^U4D<6Rl$zF%nK9qp+j8?Z95 zFuu&lqWX>q7z31Vey<&yzIt**0pPR~F8ITr2c{7EEK3KU5?cu!iRvm3(-i1BL~-3k z8U9G5VxFjdun9ctlW9YZJNw`onHcX5>c?BsxDUM#Qqs<$Lx&&_G#*-R12GaguO{xf z1VBiGc&6Kfwvt$p&%C1zYfxb!|2I8?2bYlLR`ejo3h=T7v8`%fy~M+LftYPc(7|N? zs-QYXLf}oJSGzx5flhi2qt|MGco^VMRaypLa2i`mTxW#rf%)dWOHD_!olTjFJ3cpbk9=)@xOBN;!F;=AOjJ37jB&`d55@?k zeb!5tS7Eae2)gniB6nGc+8*5~^StTj{U=YdmlXPc(xJhrjKB&K;U*I-plnVr&2vlj zc#CLRs=FS@HjGC}D-rz_a&gz9XnOH6_lxZcJm4(M(>7ZvD{^^Jlra5vQBVYAZh=|2 z)9&E1{7agBxZ+VyD=XbI_{I)b=P-oKU~uNVq5phBNs_1a^|4DQ-jQ$acitssrVg|p zH}E7h{PkcZ>X=lU>r2lqy^k3SWXv|u46SbF*T+jgPyQ1fUD z4cRu=YKDG9@uLe{ZA|jP-6Vd&@&l0_^t`Q(#SGS3&1+mJU?lz`o&TCL_?907Yw?yrYXAdd+ zqe91u%R1G%)Uf=E;J>#ym}X(`mS|Qhw+g%Zmg{Lb83x};j_qcCwsqygaUA?Ucy&2< z^%8D;gB>adk}dFy_fspU*JpGif?5ck_z+bNTtj&Phl_iym6&e3<&4<5F?dcozu!$! zvZuXp`*Kh|eOGe{P``5uRx1t-OiT6OO|RTiGdvZ6ZTdBkLfaRHK51edlEgaYVniPQiIGN`=`6fjrqFTjhxu+x3ZAhadJ zq9^Jy7Zw^D#P-9J#{n^D2jF~r6yJ^%c=s1;3c^AyCv<^W_JRuEDC-XnM= z|0m=*(|KiX=#9D5w!$Y5y}MH;P^ZPmyZoc}o>2)JJB*;xbt>LQQLmh)`|Z9UQVI9h zlK%pmZzl-y@YfGTqGe8iG~f-ulCx2Q!rw4=P8IMFr*kCIst=ZMHrR^DDkwG0VF;P#*f{P zIy~cw715jGHp$g-K)ld-mp!LAcA0^$s9l4s+%ci1J8#pk(;v?)SCh45x_ctY^bL-d z)tgsr9w=Ay=7+0G_iph*PWdOB>_Gyo+4_7CWvJW714tEVLy!2mo zuAjp?-kS;}+GkqX+HQ@Dmu6+I@SQ2zZ6hc{B-2Jw-z!KYNMCKf7#Poe*VtAp*g{8r` zE$8)nQi4+bt@;i0@8B^-B~!7iB0lsE=8)!oJpvzj8!YGM_6xdP zZ-og;_`&Sbc!*nP@cjS21niZ#ZTyPtZFc^&+XILQ>>yveoZim_oSehZ3@Cl^+&!Pe zd77^mKZ1V52;51e<Y)8=5^mt0$95;>iW^ z__${oEXdDl^jF3=(Tshxc0$_m_;9>o`M4hx8}v}~_%!gkhk5;ULs|M&%Z%>RwY|YP z7UahL&ZuCi)`0w=arcx|M;RxC#C_I}{Fj!eBDYRV<%bQf%hqbwsQpjeZkuv=&iC6w zQkH_Q@XCd0L6gY!8#5iX0qK~DxRU1_&zYUx2j`(K&?)fN?#oLO)bOa~`tDz#QTdiY zJ~SgLBc}E%2Ctg};Sq7y#U9Oq@~EXYuMIhu-49wTZ`b=zF5AQY)#qNsfJFrhC#JeQ zLH8=@eVp&A#Plufb9D6L{s&6eS>V$C?-$y8A6|o%d!^*3#!8q%tNOF(t%$xuE)|Y3 z)sS_>yM8`<$_6yqJa!jf79O||ua5JTR*e#p;E@-al&qzTuKWdJUMqqfue3%e&Guld z#>WB7rx~&M^>@HATCk%=Q#2bJJ72pq+x``D7LJ?+Fh8|rQsjExsCCM4E@k+pdh8Gl zSICutL?ietR(~~_arBs^H}U*8_o7ZF>$5cMg8bn|Lq@T4;pXdJ+_pFDV=X*L%Q9}r zg#kSTST4{v-roXmt%|cCR)g^A&-CWAfbIMyw<|`pIJJy5~PnEH8*v zm8(R^Li{&y$Nz2DA5*G&An25;J^SY{N`ioj0ur_fJQS6EP9I@0Qa|1_;C>D@FpGSU zD*2?53s2i+g0$@&L%aR5f|bvW)9hef%^>G3Zo=}bup{Ejrhs++x~p;5J_x6eUZD+r zQ#@0?UB6XcT?;0(1#t?_Wi@qqgsKEb;m%*f{T<(879QRWdvu9*oRZw12ccl6z#t^> zdLlkB3XkllLU0fO`J(&IAvlZaQv2ZD=45|dz$*KvB(-fw{b$L?3c1^+tZ#3pJ9d;5 z1^R;SGmT6 zeHSnl?)?Y79+$aCHoFISho+-Li)bFfb;aY2?CJUzPl~Ia8^WO>1tuJcmpQa#^kY zm58$n1Iw0|UrcLUbC!Q29Y&NL`D8XNXP)MsZuicJS7VhAFPvY}$zyp=6ecxjTp$O% zL1^rOJ{+>qrsDuGkl_67+#+4$SnauW0@-gx|I;9@FX~}v&gO7tWn%xxSqlqH0Qnx1 zE^t}NFe;dj2y;IVX->p6g7mBK%(eFQ+uGlmv3LY6EeNM3P?9)1)a2$P-pk&1fSuQv zvy>7OnPa`}uVELBs&3@Us^cJT32OcS#Y}X&IED93L|hm#sd(ga-woK=ODRP;JeXVC zD#=?_sw0DV^S7>J4(ow2Mg@V<3FklkhqchWU|UG~udW#Fbg?bPBh^ z2(!lL@noPRg4_oD*{7piJJpP3hGiVtMdDX|RMStn#?k!ErVFh@u7-`eZD`ht5yfcUg|~)?!XBxkq>qb{ z_nX=kGBO-8w&HiIBX>FWAgjaz8%Kh!%Ovkx&!FeIL1`8#-`6PVp38|Yw*JmqpeO$C zgEU8$iBTmW`Ue@;r$(nQ3NlVbnFwoD+N_g)pp^TLkEZPt~` z*Yz6Fz2NF@-Ppl!ivuc2@1rbMSUJA_t+ygB?pWDV6C@u+PGqI)Isnl%zI7wp7gLmj z6FYM%d@}#DT`fbs;*$To4%&x>%m#m>j`4Nv17bY8ZMV>FZ^G?(mvPS7)o|L?JdmWr z?qzBJ<=xeiuE>Swek*rx&Qo!vOIy}ZH4D4D{Rtp=M&QQ~(8 z1mou|`cePMe127#55cVl83wt6U#V%^$5R{0G}3qaP&YD08jN^%wii(?D+xS7&Sb4( z=`5~zJHWCwKAnG8!csPu0-s@bo=eL9>Aa?X#6-vJ;_otoW`!rs-ptc9u&xGcpmHR% z`;}sCkKzF|fAno6+4kWZ0ZQ!H9`ttu4_pc?SSC3h>fJNf7q)45#Ep@gXov)f!O-69 zIl!#z{&k|<0cO?5fvFUb>T?yyhq-FVEaM-1 z9CQr%H-yVP-V;edki>ZbC~)VN+<7`j)*E7ZsR=VF+I4oOoWVJxqf1$CiqummH_UaV zExUb?`!@yogq?a4e`%nB{}E_k>D@Ydv1z{ChCNJsGph7tfJwZXn)ker-y;LeeWiwN z1_8wdBzhaD?Ou(xJvb(k>$|4-l|r@CU-bBEyYIhsGTcx7z+{?Pni=^C@M7j?A$cv6 zp%>QF8iA%KPmTAiWM_|sJKxYBQ|5*CFt=uhh7!75Snk6j8*iU7Q0c;P;r+`Swdn? za8I^_L&dYBG#R>8>yxrG7FY@3-EBZQysRfz(7}{O<@uH5(&tYsK2Yn+?Tk}dq%NSF z_6-oRIQHQ}L+|~A3}z~93;Dl=gn~-hYdXhl=^AQ62(mc#(AYYkWta4t|4@6 z$|BhpzTa|-tvweuJTHDhsYF7AfqAtH#0RA6)1Hnp%X>!p5TLHhobG6pnw!&CC1w$MxD!vEd{|Cm8@^+5D;#ah!XH#NiHMN8Xi0s)(kOt`7n1%#Tw6 zHsocM*kf-HgGSX0_K#y3smFfmA)*!}ob&tS{Q}iQ`{7fE$`%~Vwu{`8NSbUsKY=du z?%$YMa`7wh+|@&d??rTsQarEzn<88|3U>)c=R&7E?+-`X07-IaFNa-ufXi2wuOzN| zv~_8XOT8a5!)W7+YCn*<96KWF7~=a$d~J(vrN9DArgNBB!fc&Y^;4VnIt?3*345v0 zU%6Ln83a5&_>Yrxf63Y)nek3-Z`djr5HHHN3TbEh+1@sTsubm!(a}zON*#$s>HENr z{_xXF{{F%mB#`6!WkFiPH~fR2-11bXj%*@9SLPEW~Su z5J_An?tGC*>&rVgsKx}$)>=3f&Q8U_~a+2iP!Oy-@`)&N? z_JHcBF0t4Ohg4w@C&@_pCqsZ@CN&f9={PQEgCd)2PLrDmhHlx8k$tj~cc5`KOxq z7roJD`fS|7bIcPPCzY1s5H0l(*QMx}cWW4fTI366_oQFhsLatldM7itYK_{>RtNp! zF8f6|>~(qOh)fV7 zu?4q{M^xvd;Cn5YyN5?~P~a(!1yi1thYa{{3kPR7qn=&;iD5}i%Aa`?8m^6bDJs;H zwar9dnTTw@mxw={{u9H#``i+)2hM}4@>sTWs~_Q4Mu_i|Wtyv{A(bG3_vUC-lJc}_ zQ=gPjB~;-m?a~KLxQvPw>cpFB&qq}qg73|I8BM#gYs$Q;mlZbDEFrb}+p-zyI7R?w z%GI8&)6KHY42x$YiNrx??pMm2zbUh_WZCdpujn$~%y^Khpt4HKr{o(-I^Y4XyR=@@ z?_)oMV@Ka}%C&d1D++0Q$(hUd!Z6g0-tg_t#uJVBT6)-qoC}{aT_z&O7GdO=NFbz_ z{7L?CupI$DzxA06qh0jwHlfqq^#!rd(Mr$gLi6eUpJ5hJo2QGM4xcO(?DnH5#x(a@ zljT}B`A5lbi#gYIAn_f;2WNV z0id?=lE$2oHVriSdQdF){B6+-pT!<_>U4df6jzdx>7&r}J`uolS5aZks8D;@l3?bW zV|iD2SZ0ZlvnQM2UNYuHM+Jg#HMfpXTLb4!J|qLYV%KSBsr;4fM#>mhd~_wS(=Dtf zeAXnZjW{udP_V4xg4^Lac@<8DR?@Rf(flUGEFhq>FjmArBdBl-r&O+O@8NOS67?nM z0Um@V2X=fbDk@rx45t;gv3Z%a=E(Wx-Y2&DH4urbO}Q8gWk`Bm&e!^pdQP%<{CqRR zZh7cos-(n~#OYzpvxY~a>LZ~@`lRkgO2Q4hrJIskqtL;t)$hih4NUT5tFf)sHrNcM z{=6S~sb4d!wXbwUc0QCn5|JIP^cN|UecI^HnuU21CdX}dk2rTX@3+NJXd~3mO3jj! zW~4##GU`X+KRF$B%#_38r-0UBf|L{ zp}WWw=6A9u%RN2RfhiKCibI2e;Nwqgzj2}@OVnZgwM0qCDxV?_&@0O{*Z86P;-gdrs$P~iKtor{>IB{Ha0vjrPdl>jWtDO zqWa{A;qa5LRJ)ds!{RdlULyZQdJ#Sy@Fdk!S23N&3rNhv&0YrxQ%kCJaHA1!(NmTe z7!8xTBWCtLdGXr3HDw`={nN}d1v>uEtIB#Z^}fP=*)O^V8pa&CK6sP5K96iOoWHzh zeJh@Mo+cW5-pw;Kr?M<`Kh7CORn;r?Tce-^pYR)pZ97qsT`7JxEaEyTmWEA!1dN9$ zwdRC8j~pp`ef0UsasYx6O*yf2zZ(Nd1t5vZn55s4=6`p_3yzn;((r%K!G;@&TWs)` zTj>T+IkF||2otOsC5fGV`N^eni7U;J1#C{g7la8yuf!xI)b7!-UX2NPpM`yKMoflv zsL&p%znCK~X093utC=sqJos2!#<~TnFLZr>ZST4`4s^2iSi(-bLJXJ7kd?kc z@*4OBgUqbpw_n(xu_}-tw&1C0;f=kVp;G2t9;d)hQ6x)}2%U!4Q9DV)bas@6FshN>xIwMe^^L{@XGPjl_XU$#fS* z#-mjOn}aJ)CP~H@YZmkCfz`J~EXvpO8&c)Fjvh+`5)A> zQ|)@Y=ksF1(oVl3zy1cROn$G)8$PK!mE|_DY}%PT^c0CD=$l1H&GeGY0y2@l7z#6J zV|9xXO5vr-xJ~S+arh~6tTV_JQKYT}V6UH~Ja)V^wn{wUFf?j-)jq%NLQDITN%O_b zQGJuhbYh|dsQS!}NM$3S0PXF;LM0EfSJdsA^_HlopPu$JZ=eBR^fGq`g7>%Z&Y_RR zwo>yuW0jee+jAV7!<(A1ze@329 z^%FjX!Emq6bSsj{lGCP%NZt+f>31W|M^PA2bG=F> z58AU|WBKP;_5s`HI4}ZT<+Q%JiOr|=-ovBYZz-RrKjW9Eo}s<+nL$yzYj1BUV+%H+ z=J}&a^(QQr-v3-f$s`pf*mA18yI1bG zByw=%9Hndy-JIX!R9NcRJ4vkJyE9I=BM$~nKtYswCo6{zG?;#lc?|#xBI4RxzB{QV z{k~?G*m*I&M@XtCVD;(Hv+XoYHNo)(#Ti*3eBU6vp6V!Ve3I%tSIX`~qc*n%Y@jPS zI>nXKnloECo*+Pp@Tr@+t~Q`4gUD|hyg`^A`!d<*JE5%_t}VI=OdvDJQOrarQPEyM z&4<*Kvr+FXWM+YFH=iqfzLckv9{jx6P;Qtxzl#5TGM&J1Y~nm@$u$z4*vCUd;#N*9 zQNuz1LzVCtR@=(!h@yn~n7`vtq&1g17*p`42^61(kp%0@t79>kuycarxe7kq8ZOLJ zs)?^##>x({d}`%4h+}nk|7rVLS-ii4O1`^Wi-2mC&|fD>u!h}(YTgqSyQxYA2)-&` zX*O85VuA2dQqR}I@4*N%94W`*{7R=v%+`E7KS+0AR$@DlG8tNGb?J8buj zgq>yDU|i1pkd#A)QiVtY{{uLyc`QeK>h$I8cK1XVA()7$j6|!e9})joq5AE6EN9t` ziv$}ua>*W482cu9H8Ks*bXmJyfBhb2`8Myv>yT%96Y;Oz=g(0mK=IXgt1_MQHfTTE zRIVNGdUkxJI-rwtfzmcEcQDWVClsC(h(Pdz*Pq`=m0ha1C!UqOm^YzpaB-uOPu}@> z9@_zqgPKE+b;Ogi*ngPD$cwnU4 z!sYUYVW;1dp!*Hx)4WGy-f6$=J%tU69sF`2(`HTmv`-<0U7&{Q<4%A~pljEM zNpXZ(D+OS>o=ZAV_1l=C=Zp3Aa9015?z?NPpu5hKok3j&=FTn@(zrE_SJv`iAOBf6 zpjd@UGU)5etR2fBq0nL2yZ5;7B9uf@uZVUX1MquN8=V<>3!4UKzw)Qib6t;8(OG^E z5G7VuuS>hH9+a#a2=F;HcDx^Q5T~6POQzi0IDQg%Dm+LsMc1cAVJoGj)W&opbt;IW z9AVylJ&$62OFF3T5$GF6FU`L}sgOfx3Fa6Ry9mNN?NKjCN5Zb(q?=ZVuAJ2^SEk@CO5FDomvi&+NB zNiuhHS~qBj`Iv0_3)IHv8rwDS#S>5Qwr;%ued=huC^F>tf(IuU@)z0#KG|;~{Pv8M zF?X13xLkmnRa_!7nL_1f_)BN2ArnUsUPY{7HHzAD3?sPbo6X}S9#3CU!N}>({jnU5xN(rvzv0We9US6#S zB>zr`B%kq{Y#F?`pA8+ptG)z*it1wsj&>8G1(<8>r-ok*n*{N8&|)bDbb`@`bYet? zkPgo;Jw)_%42yto+cLzg)r$ZPVa7oH4Z5XA?^`=pb9|Qj!i$PtmOlw09MjKrmHba! zwAI?EZ{F+Lj6S3>Wm0q(}L(E{s$_3v~ zdT61feo|-QJKfZ;k+|STpayO6%h~si8SKh4OO>usC+eT8T)VSMXO&Ihu@MME@ zM(lcd`69qr?kk13+~iov{;fg)$;bC=k1x9J6o9tmRs0* z2HS4+Gf|^nXN%~e&CKV;B*8T1_P@(egG~+S-cFd-eH=UCO&@n{WiL5cbBqFq=fP{9G=M` zSA9tM39x($rW@vU%r;W$*)dmDR_pjfLK|piZ=6OsqooAFB*TRI_-@3)Puo@=NITJq zVKkA@JUcsg-K&$s(h`SXZW%c*Iy5L3ftjbJdPPC5+dqp{#~R)^X{O+&h{p*t!L8Pq zHQ3H-hmUo-q{m`N0DcViCjy7oUl6&h%9ZQCl2hGJUu13syCh>Efd#q&US@KZ$ro`K zzUo*eXUQ=}e{GYq-Dn^Fy@V_Kg>44#F0NqqWC4Ft@UlJ+ekPSnL2;HwPx6Ly zMgN6mZfgHqU)A$|yFf@|(G^qR8NO#PrH;5(MF;6K*gtQQcwQJ#{Zt*80Lh!lHB;V``X&Lc+k9+&RS7e(X_yzd`BeMT*OVcu zF?fmae&@Vs!k$NSb5@M}t2unR? zVr%^mg42Sk$G(9W3^Uy{F@LsEZ? zV)b323JSlZ4&!9L)_uX`xi5`{2sP7@G$IX8uP+BI}P9p=53Ms z=5AX5qjq_PJfY-sUa)KnLKlnsM}ax(E8ouIpDUQzAej^zR&titOpI&IonY!9)Onu! z@sT;y9~{%Km1||;@j6_HbBbB|MCS%R2K-1%bIGXF0GW4=ZWxAWa9(bG3PVJF&(`j52>7r>dZdRP7~Mma`(mn^8p z+<#FJdq!FM?eoU|_RU3rqx=RLpN{c3RrBZU*58$20^Y8d50&qXvP4nwmWI8%`nzIE zDeR*{I0jD&7c1m960)p)a3+TO>?YD=Id*x!l^Utvo&rnoG<1KmE$tnr;Ehw&a>xD&v(?dwa ziBkLi8T|~gb&5y2j@5-M}X0b_&Kdmhci#+sqyi1+tsEB75cuAdMs8G$7!eof8Nr#cS zSfBdG06EYupA5{jmceZP6;gwd_0#f9g;dmC+?-RQdP`s0hme+Z*Gw$@XIV?hg66Lf zV{pk4!L$75{gsSb;|`iO(>v@W5OopbhjC<=y{0dj7=)N%Ji-4tTs3(=EegC=f$V zf;u_c(YArS#J;u6(;8Y_x>^=QU_<$k&&&=Vth?0bN7=+3<7>-~Ffv(x=Ko!I9C`jR z6^7Nd#VH;|WBtT(1d~QfUXs{GpRbbcxl+JBPgS`g4vxIV;psMHNs)tM$7QD#N*wz? z20t^tId*fA{x2=0|C=|`P=EETLM9Y&|1A36`pV}4stHxfS7dPD(7;U9-DE_@?C0qS`D3#Xz0Zr-UkT5@)g9UOY;o1Up{qFUK8~y*kTm`cKl2&x zJnM^3eEYh8(2kDGGF>1HLL+RIu8gUo)mq#sq|^}bc6s1;Y!|n)@6XQJ!wd$8GGKci z;V&(SerslugK4aeMQ3r&OG9>FS%LAAg6_P;=YYpJsRKPuiT_8_RmC;gxNSNFBt*KT z8|m&CA&qp0(kb1Iba%JXFuD=xPHB)B9UC?BoA3Yko$Wc=!Jgf5-B(Dq(A~MUx2o}^ zyJM=*yOchCXnZv~56gWoH&xfwfg^$P-a_FL0H+*lJ@6U>DVy2aweQ{T|MkyQmK~;k zihf=(tdo{i#M9-i?>LZ73N}1wB>!QM&zC5|MX)c%vpUU2Tzr+&oX)$e_h>ZM!4~mE z;1kOoY zkoLhkC_UE>q;{M%4V;d^oGZT(^iBZg(bX5jH@>-NvXXuv!!>f{tnIvgBmBycHlgdv zrCaoz{Y1*te%;yy)iJW0s76?H7|xTUXoh=$DM@_gb#q zkTY%_b>#CBu5LZKq3_w)Rf*MIg(Ecpo)>Abkmt@=a?$gelO|Gj7IS>EJa zaHb;2g%e;ZUD3gT1li+(I=ZZMMqlnaL$H+}OjpdnptnuI$_xZ9d|4==TCd=n9%JRiL?P z??PF6^}8D#yD7mx#cR8CrpxarbZQM@wgLWOFf5&AGw{{lu+4c8lQBlsNSREv`K!f@ zWOW{zTT`n9^LX1VCSeu3&EOx>9}e<*5wSxou03qfoo+x>j4{7_aEANQEHWp;{K9Q# zH<%mqQ7XdP@+)&w2fFv?uih<0b>V)*aEjdp^a&=<56XVv(TmIHyJhEpnPvsK@S7hh z{dZ7QDN)$czNm1VtjeMv+q&UrawfSp-!Du^WLLw`}IFv!y$Oi8Q zFOl=H_Nc@S(`%-rod;1lq|;AC|4&D1UUE9gA?xGVTCyNcHOM;&9h58mUE+gPIwGoH zHIn@-nL!XKafLpTVeFxV?^`~Fj?rgja1~zx4CAt4bAylDi)~iT_-a_?gcC*eNC(O| zDE_X24n6*j`C>-pX0XkVhoBX0TCTNNFL5Q3jUvZ{2BG`YVYb@Zhk)9 zbg$b{M3$==^Be52%vv7zaDsUA_ivo`0a^jTSjjWNEKRAYDlDPfQA;)-uqP|U+=FeX z3mcrDe8YnfmpPzgJ4#NQ*dNfC?LRky?6Wm@1QZ=`y(L=}w+Va_k&XFCMKXvsA+9Oh zn+vF;3a1B ze&!ggS4G-J2R*WvrtMPWg#7Zfr}6yl~IB(9jzhG$=q zls`9*W>PAa^SXMaJiIPiFLwS_ zMaT8F?=X7D?)A6ndD7J6M=Puv zyZ2jup@r*9R@$p%h0AdpU~!ygplpiWgj>yh`()%$lY~3*noi-0o@-DT(NZ@?L+?&8 zgd&GrD%wfh7vGiyUKtCOa65IQjp|C162iO|5u73uzrYq{D2cgOMp@7qZzv4Lohot( z9bu%mkr_i8B#V`)Op@!gK^6ZD<(hV509A?-T1Y)~|JtM9BQ;wo%T>U>tPKgF^O>kv z`*J(X&=&YA=D5`p3QIJw$j{)JMkaG3W~@4)4W4UYUHL;MTH0N&Odx=8&xZIJaqHeQ z8Jou<5QB(a++^OpS_pUt5XzBFXl{`+jb2cui+i|h&|l@-%XI%5%qK1${iy@|p|SULpb;ju_aNSq_3Vrn%u3~X z-UU7GV%QS5O2UMuzcyzeu{Dah*60uI$=#arD#sip_S^li=8)I-uhr#jJ)!)=I{%9~ zgs~zd#47TH7w%}n=tZ{FQ<4c9eA3Bt$4W@fOHm>qq>tVC@;Ilpv|+8*dIrt&!bxZ5ajsR*dDjhjx^Oo{KxZ zJs2Y+p0f)&M0$T;k^uYW-rsl20Wh4x_+YxRy9Fc5fbCMO_$ozrPnu(o}4NCV`zwZ&u`JGl?;gmU(`Dn1NHTCHy zIa?w1UGQ^&$%AqcbnU(=jX|p-Hl%Qx=VS8(i>pi@9*!ZS&KnI;EmV+#DvA$05*dPF zyA=)btJr+9r5X4|b^h(;y8|4U$(`GJk3XvC#pZ8VoE3M_PN||i5<8?|$#hAT8Y)d( zA9-UwO6uURKr!#7vQ)BIb~T}I0`#!&D-=S|r)qNY1V&VYEXB((lL`#Z41fY;_4Q&G zk8RENE(d?UZLCk2e#&|9jfd+p|8It(f#;JNg5Et5cT-^P*m=S$|4&6`@4t9ag6fTuzMH)(}49_b0n%n6>t zj<#OASWoAkl)Ko0_oA?eIKGpX-@Ss>Fi7Wq^)7<1wnd!h<*0K)IeUNxgQ<2(c#tYh zXH>dK6L?}H+pb7Mt`Auqx}(zX5iO45gfN;d=%7ABoJbKk38wM zkf?Tuh6M+*G%dZZc%b8!!d z8qZ*-FRNxeUX-YcuF(bj4%T0036=)l(wL*re)CDqwkWmO`)s`DZg_6Zupb}(3*ZVV zylvvWHb_AACQkaLrUXPZ;3#pof+cT>JFA5(VcyJl?iC?Wk9BKTqTuY0M3oBYRFHt` z!A(RhbTmE~DSUPr$!kewDJ60wG;c?U!J31pR#zTVWk~CS%%)5{9uk;PYYD}NyP=c1 z9az>78tf2YAQAv3f+%oN*|G;Q7o_Zv&*v8ERPs1aX^-ubEqDq$S0bKAO*SPB6lXAB zEF@z-eFSC=H>8{=OG(p0*<00zBgQqA%$yw!&1{tCT|dxUMs++|B;cq(h5d z<=)iTzQ_;ITpRb6XZt%8hu&1V)|P_XM{|dq&HZV$fVfpcQHG!|jMlD}`JnQI>}8W4 zClYpAlU$Y?4S!^j&$pj`Xr^vZhX#!|Qc0-V>Kwm^G^N@-da)N_@~Kd0UT1=Uw;4DM zgHgLF+^{|;`WW7Dm<<=}2xbD`Zp1|w=#3ju`67Pr`B!M2^^bGkRXu?8m@XRu5Q68k32fU&mu ztA->8S7YOvTtqn3^yaGUw!f2sb9ZxSX9ciPKYF0#X)-vxBg&c^#TUCWT6RnI2T;ig z<09p@3)y~#8{E`Pt1!1cFZJprU>0+IoOJ*gDsREH*^IDNI~w5;ulNa%NrU~pJj@!n za43iukd0e2`MKh$+TK=UhYTL>I(Nb#5r4F7_XGOKrO=)nYm;z_%PIF??8s9(Y~>MX z*?7j)>~Qa|g4ZVwSahOi{o0u=S1$?@oPknNuBwMQ8(*s}K215vD~XXh1=ivvGevG! zyc;6tu$;~=Zy>1cE_LZ8Ii+T{*^P}+;!Pz|18^@ZIe|?zCb#b3>)0pR_d2V;WhjLV z5-=Ot`rjk>5M`#3yvKB5Dl!F+L@J^R1yW#0YR+#K^^xz%$GpXbPBcNHKA7tr*mu!e z5O7knE2+e0q}_7?1Vw}(O8SIMdw0!MIW5B4e22q?0SjlnSR%nOO)N zq3f%PTw}=H>q=qqDI%GJN}>myO2w+14ld@leP;AaHVtYd9cvV{qdh9oCZm}7n3@@* z91bTA6Yo3DkpSskrQ5GStT3c&Qu%vhTKKS}IhKAa_B%S~jVWwY1Yo!IsM}(}Gzbpm z0@diQBJT*~IbpZE-Hut^olH1svFf|y4ks}c`ouc)tvsN23#4}c!^R>X#6^&NmkmFC z6J|3^!=axS8aVXG;hvBvDX4$$D&)6+@hqmxu+$=sDJ+^ulh`s4 zN**Fjf7|@!6ZmV3PcPfHlYO-#M9og3#SqkZKATNUTt(Aofn{$EVsb&^f3bRgFmCvr zm|hSElAosuWX6%AD&dO+?Sq*918gx054pNl*-vlvFwpn6?qVHEzT1VQZZ3;_HBeJh zMvG4^;k{1Z+}(7+KwpYgZx43sw=?prV6p($8tV~mk?j-9Rt5Y>1Pc!yZFD5XCb?i8 zrGJQ?Gi}23II>wju*TY1P8PdTQ-I|#E={eTVJBnrmL#{bfhb25_SCu~_8e$_F@Me! z;c)7K(*HA5^t+!OZu2|h3d;Unkn>gH!mj%?^&-xf5Zz0H6WJ`j?v{MwBRyTs<{4SP z@N6Bf%87)WtGzK3N?_i!4;GP5@lvdJwse-Jbk1FR2J;!2tkOqUTNEO5q^AYqP^^Tn zD+G*?Mlo{{QW)@OOmepKU#%)7sjNi)7n3rB*aVsN~93Mre`LX3_s z7BjO_kQaZuA4-Onf~84&D1`4^xL-$2XoPIZI54K#BUndnalFP@REEDlcL>d&RO(4=JLV8(9NiB*(UsCzyvD54R}K3yoGqpja)U7`P7+ReQIY+zM# zO9_%=!4RNTV+ad+4%jjhWOU+~iHBZ#m|I(6Z*^vaz}!DB&~*##o>oa|nG+RFEp-tP zq&)XcM!cmAk#ZBl8^uV^kZ?*xEl2w*f1O*SUU;JQn+5Xi9yNhrsW%i9q5-kegcBESYz^8R6Y6nf30fR4ALol(%3ttZvq+nX{i>>_A+9 z6?^V|J;l;&DD(tiL^~bRBy?A&ye`BeS)={fWYw=oSW&8apa32&^s7IipZ zZB||Yc++dW0CBc(bqB<0!Y!2re;TJXaM?O&6GWBF0v>bjB;~?l`OIuXra<8U5>_E?&0g4o47%IWOJ z!Ohx=LZeS+^J9i~rk|y;3&X;bsX5?T6(BXEo2>0T(FoR}VV`lGhRARD#xAHV{6@VI z^|KNC99zeYAIO$2%B57tC~@?(!pC7u`1gfnw1zQkY9rE#-do@5ljq$fJ0iH4#Q$AU zFP`tN{(0KNhK9kbE0Yh0`w5SVNp)MQ+z{S|Kj-h$9`$Dg-@AkOu>+RzMq@Yy^w0_M@T8>{P@Ut!*-cmpF8&c%z#I zY6|>egHE~cw$z1$=SJH-0}*LzfCG|AN9_$bJ$T%Vz4v<VRkGbt}EX%WWq(Ss(7uBr!&UQe41ta$|CToY|>!(_qt4V)M@d z1(PWNCmZHl&WO}QQg+<2oij;k-*^Y&Y^ULH{S$4OrXE> z+Q3kf8R=zE>-#Bvj+jdc6is>EO(K}wxcIB5VsW;V?H=Be?~?9czvrG)nu2+t_Mn7I z_2VrCbAlr#I$Y(~`!n~|P_n1W0moJrV=wQ)PX`4%qwh1U`eDxnY-XJ9@dSgJAE(`R zu+H%llRlKyImcmAT}n=Q(^5oB$jA@#ZhxGH$8{5m--G=wAHt#p&l_0#nn2SoCrfPW z-JX=g3=I1O5kT3GeQX1J2_BnJmzjnp(;Aop7^O$S`?uA!JZk#M^xxg)lQQ3z1veP` zq5h$4cA6`lm&E$Uk^I+4q6tfhcXJTR2H&6a=8IuNnhlo1rnnmLpbo^Bm%J)6fn?#V~4yZH!SFt;+xBx_~?%<8UYPMqdmZ6)gx zUM8jBU!t#*2W+vrlPng*J9FbSl1ME2{eLmA;OcR{kYBYP4S=MiUja8ZPd|(x3^Zcp+T0my6N^^*O0!g9ld8dwV&`&$*N`5i&8IT)hYR| z?PB-7gC+VJBJFj*Giw{Fo=m(=q7^?KIJ$z*L#|txg-iQz}#&3JCkkn)QPB zNH~-6cvg9m=}6Q5dQ2mme??|pV{zb}NOuYcv%yi2k<_Zd zR~jDe1r$<@ig85#$8C*{Pe0mHD*GC?X`54QB{RbJxW|Lfr}@YL)4j~Vc*@xT<6CK^ z3GQjPjFk++H%Gf;;ySDC#Z$vdg<%Kbt&IzA$Vzkb8h&k!RFC2a60{Q37ez~}%SgQ* zlNkZc_!AA6lNxJqmV4p9n@A*z9RgN$1(d5FS_}6`bW10{qSefC4BxCS{txiI>wOi4 z0lqrl(wXSbXPY?E@y*K7WNxV0XUD%oGYhKg_spphSFzx8$W>wsiZc8F4#1noMyL; zJvP?o11@s7Tmagr=_>izL_bbAqpq|ncdRl{^T5F&i} z4=2qjanu4HBS}OS92$CY-iMf<(q5|aJY6(FlfP8oiD=XY)z>%kAaW$AIzQeztatch zaF}#W49ih(o5MHSyk#*Nk5XP1`Q?$u)!Eb-$oI5+g=vyMlp*olQn{4T_VLQD`Q8<`4_eg)!eDl)y`Ha-$b5^zCD^^I0z+Xgkn|7T!7AELf!qQZ0VSQz~a;HqV*fB zmSN?Ryr-<>jiOwInxF6*y5deb!9GXqXV;%Fi{J>?qd5FA;K#cbNQd&+3q}spP!LQy z!4s{`?6hhFSiX+VN-u1nByX;dfH92;kUve=+RoU%5-c8Y3r3VJL9p+GnpayLjQ2i% zVTWRo5TF^RnSxO&M!s92??-*=-SmD*A<HSs4>CvG9$a0!PgPg zp6r|P%44wFeauDfQFuwR>sKUWw8EGnx!Vh_h0OZu>>H0fiYUK}kB=X_7l8Y)Z))^t zI3VyzQ=>0~Zht`D9dcrSv@vs)*>Y$Q9x6(`N-4+$ zZNe3$&P=2#DhYBd{i;`Q_-D~PPe;}I)Ya;)c5g81}M^t<@?7gD> zkEAwk{h=a3VWgJ3QpC3o-F0##Y3l?*Sc*PuZgcs8M+h{uHJ?g+A`qn2At(wJ)QFdN zqmhKjUka6el_G7my1eXwf|eqx9E8&GEa?Dq50%7Ectj0hxqjM+{J?c2SHngVeWMa6 zFkX9Pa%MG*HCzILPCv6nu5MJd&F6+0hR_=QGl^{Txn~0Wt`bVkl4pLVhR>dWQS}XL!Qv1LG$S-mW}ck8Q1KcOJc&6TYXgQE z#kab^TI;8j;VxF29-|&HHbk22Pdg_{yFav{pd@3aV%ZepZV}qkVgz_zOp2&}nl>Lf zk)d2H{}?k4Yqs}V4jZ=W*hZDj2#IXt8nr2is}}fA-T_O7Q@mT_JlQrwb}UtlGQCET zc$PEPza<%!md5Vr>YRhB$9S@o!l(UJ12^9$TpFY!!}=mz5C$PT{&XkO7H@YDBuLJn zcFX#^H*a4NOzf75fy6lgCD2ORO;h(sCu4K_%P>gv@qz_z4$@P+yzd`8V+2Z?e))>y zh%O+!whJ@gY4d!gfY~DlXHlo2Nmvb!^AsAD#A!=aqHpjB()4>d)Yw8+;`DZEv^Nih z;%+f1r$-8bmWh5U27q)<4sWfK-dpmD@GrUJARwn*$Dc!QF}{_9bfIRK@dr67Ib`_L zo-y};%p&7;>@R}&@&X*q{{eNZtW}n%hE|75p~(TaBaQTbXniU~r%#GNrS;`s&2N`t zW0{S4imK3&+u>WSO`VWz(4k(w`dT4R@nXAo$Vwd8fhvxcPj0qKBocGZ`@4zXGD4C< z3Gm>EXVqIu&~*ZGf_Y6e;G6ElSV~C5M|1JPzRc+WJs&x*Zv7}>5alFd0)Yx5?8*p>vlTh+Ad5j8a zH&VIwfH=_{ySV*cwhe2xROmF2@%d9IFTc^$LV%RWf$v53d45jOJ)AxR3m~v-oqtol@ zTCam<9XV*gmD|6pk!`rzd+ivKhS4rr%#t%W5B4R%l7PgFfR(ipNyvdqZ)@)?^ zD8EKiO&v#dW*viMuJG1%H*sDIap->CIWc?S)`E6sT^h1|RlHA__#FL&KN zS+=ke9+;mFV4_LfHw1~btw?YQbV&dFQTQF#2Wt#GFTuZEI&2AgCdiE-KjUPlim)na zZi*aP%HI~pP5#9_F;a@Ri72#>92Xqd8{BGmn3-xqIk33#$D*AX`U&5pfnv==KC3d1 zxSUUVKa zj-Nc&szMxt0gOv9fHBI=M3EJHpj{F)u&!W+xxte#A} z78wly`>>^YVoC0!Se1}{hk-9xSc5AA9Ihz~e9Cc~U{5xeoKj9ucrB&#$~f+}pYP4z z#eZE8CQkdO7l(az&BF&5U|L3IzFT7Q%j#jvS{?Obi!r&RHkU%ly*qNWUMBS>xghj$ z3$W~_;&9^UUsscH;5e9h;LShqoAo1GRp4IEldPLEARq{$7?P;kH^zvY--HE?<+q4a;k6?^>)a|L-33AaF8sjnUDNnIu= zxb(Jrl>7FkY-*MyzJcUgZOP?t@WhFVPK(!ln^uRFF+5gjs^5gRWS*N5f*+;|DdjJDei&8_Sq{;hx2wvvsq~{x=E_OjCtPp%i)S+Hr&NKMsn#6 zw2uxn0&CN-3iP1|dTX%W<4(%JP@$XNcT2d;n2{dP=!n#sRH&iaC@H(qZ*hc1fKyF; zwItN^EitK@a`Z*NAn%h_%dg%hOw%fMg|#F}r^TWgRR1{AcKd_}WFXQY#W#ix658me0hmQo(u9gc9R$w-?KiMDp-ZR1d1b3E+{-7P< z_I)DzC|buvt1nE0ugU$fhI0%{nVwaj`>-aipX>a{$R;Sk`bRK6UUhvCHad)5ZMe3; zLuWKOm zX#5Z8J>Lin5&~{WZ%|pTmp?vIOd<*?4~dxS8ft1~7p=n2809 zCXl=;T4>Slr-dIf-yvc2?ZSnjY>!>(`rgTJ>X3h^YqpnvH>0?6Z6vqA7%8~}Wxo8G zL>QDfy`jmYMP9n^~9>7-qZZ_vo3w?HJd~4D-+5e65Xm%WQSam1P2>w za3QZpO>K`VEhSMPXLf6nZX@{-ZEO{+&~$^XPBK`1vUPMLiM%D!_0u1z2S#G#_VIQM00g|FBZ;a|mzlDON5C)qe8(usx z=dsx-$yqgYJBMFqinUB3-U>sF8v#e|uss6K4cTr|K9Pdh#?YY4Sw4w}EUq9y*;j?M zgzwJoxlk~+GFmeV{vQ_kc-DHAv(*)|Y}}b$XQeR(nit46y^a-G#!luUSeCY{axsp( zK1{5o!#sII4t7^P60r6qFwQgiX235msGwNSq7!w+kofNDN+@?67XvE7ZIhrMqH-Q= zpC+A(7UW7B@xa)JZzBNVRKx#8QpvCJx2?fmlt#QMd3KbKro@gnL?Ob*tCxxup0ewe zV)OCL$px~+mOH7WP@Iv;w8!XcWDMrCe%|k6)448t0&QyfP`wF{cBFP*NH9l2g|m=o zjP>XNaQCj*s;&j!aQ%6Q_@o62?>Oj|*2PoH0ni^r#c>_AM+InKOpGX1 zhIk{H^(gC0H?X6#8wjdWN=i5~=U%3jH6g?V|UQBB4O! zHy$-kJAgN`2J!N*5FgjJzJE=|^pAqi*!#;8;Pw>Agu8eQXm8y>gdFsWBZUdubS8TnK+0{Q&`nR^gw0=MAsN(r3H7@PmpjO0`G9qQaj!(I8^QF6z0gUge`M#sb7e}OKg6D52F5BkmQ_m@Z@ikn z9kx%q)<0ZlgJl2GT?>tCn2WX_4Q2=>ejd=$3`Sp_sWSfy7O-s#w76~n?F;8l--9&D zojGAk?X`CvTF>boL-!Hc%zC&H4LU2%yCB<3XH}w%nAF@E1Vpdysb+p0oc#yorpvH7;j{CsR{6-2teZ>$4hBKbvF zk)Ss=t>@g>LPJZ2o3?IB&n~=Sn^7;2iTLifm-^RPJU_2ClS+{Y6Dy!b}N@RkfQ0k zAURcm!~``q3+6@citad1Jau|pBIfFuzZf0K7`3)K>=_D`P4_lgvH%ZthNSBSxyRl_ zX&w=}(#A=1o(HEszwZ13+=F8{t~TzLUBKYdJ!BVl@)bq(FIiJ%bv@Oehp_g)DhUg6 zd^e$^)O#q|vtUW8*#p4jXl{m z-EQiIL1D;?7#u8VJs~&Q!XO0%+7Il7kyN{+T=a5)ME+q^`eOs~h0!g__@8??*7kZ6 z!m@eRYdm>nzlBxmPmeU{55MD4E#edVQ9s|Ael@)|JH&LdB+}K`qy7rbwY$)(tRo}s zCCp>tT0$wZT3vpi1sOXY5IBu`8~Uanr|D24c)WcRcR|LpDhy*Y!`Jn`2-O<44{+(m zyjWrAoVZ7Q@Q#-m@lnDUqN&w4I@XU`x?KUlC0@x(1hx0*iF=9rZrXMyEW%8E-)(Pf z=Q~oYeAG}oyi3I@xFLNeq5BnAJE^#h2`}VOL_;wGn(ZzaQ^!8vpsKU~tPdR2ao@bp zo%4O7$KDJWFA;ge;C%%<$&N(YD7MRXAsYKW^BT6h63;t^tWCp}{pBZ;N=Zv6`{6gz zt@{3S$J8t~ncq$nAL||Dvn9@GMw|M3i|+p5>EPxsbOpL^dRshg?QDR{mw|iU%U#AA zL$Pqii-#6NBGU$HC((s8(l*G)>cmTJmafw66av%fyaSud6763KoE}n+_5Oya!NHkK z?L`vR|Iz{#)KnRl9`8W>{3g#(RRZ%vSHKad#KcL~N%#EqXf#;43%4mKx?^%g@Ns@@ zqk@Vj^i=H4q98++K1Eiw{o{cAtIK|Azt?Bpe^Mt%7yitY@;hPh9^{ctSO1Gj5|+E; zN&e!Cqi-s}C&$Awtk>>+&G{@z(*xI>Fe^>ZqR~T-1FuHSg8_M;%HN+y_a!Z?+t5TY zNKd@AMQ_Et^_gf6WuQLs?8l+QKfMKI-6k6U@J9~iPk%^C>_XfpM_P{)F??u&KhSH~ zj))V-qGTY}G@H~>Y(CC(-(;|%D)^+^BRFZVKuA_iQq}_3N&r`?{&b?%lXG3Pwj2PB zCp-WNX9Q#=^;^9=IXyXFB-P#WP!GcKIMUm6Fp=GfYfTO1ZZ+gH*<7-#>yCi=I1s|r z_9z~mr_V8`LcPUApX*xfIriLM8CNEGUhL!6(Kf7$(rH`ozO}f?esfRud{c8JrmvYC zK~hNHkEmBi&(Hh{+>S4x_v)vd%ZzQ1v$Ra>oM5@TLXbOupWzFswFp5#SHcO!OM7{uNZz(C#OjL~Hd*9=C$}b2^SIBU$W9 zMEsp6f;bD|Xd=Rvtd*d0vX48UKYFE7Kh~B(-JZ^wFpCU`$?+yS*9u*!FrUrlJy$ zOQZCc_=JS+QliPYMzjT)%V&5f{dxUCB?XX9LGRw3MCSjwWO9}Au}9Wz-OnirnC44; z(K?q?bmjDZTmp1KA;qxStdnISNUzO({L&Gy2f>-yoiPuFJ6!w;X)O9TTW9PIlhzcm zeX4Uv%yxPGS*ico$Dj^}W7Ye{J@{urIH`2AL;kl?k~Mu3#?C|Sef5%dt>d3MsScz2 zu)p5Aohwg|-e(Kj7=C#SP^x0SR@feqm319i>w4y1Ce+Ky3ZkCN%M)RZ9(MoY9>ABl>l2{WC?Hawe zWSjFp^&{Y3?^fF}_ZOH=PTYrmB2fj5&f^e$YHAw27jmwvhvvJwO0gKMx(l6wYEktY)K6iBgaA4Ph_LrYTDcyczI> z;$`kVf7)@R&@M*+=yPymA^%=oC&8Vm z7^8n6cZ`J!o+HnJ(}xm7OJb%s1~|WXgJ)nST5rRfRw~P7oXP-tw>rkJvf?m~5Y{`e ztIO}!_l1_gM9JD4C6Z|1G3Tkx!JY(!o$pyU^Rom?NdJp^6h_4+BsjV$2#N22{sIAPNjx{L=tEUUWx#ZhDgf z2tAnIxouxqI&lVBU%G$RHX4=Z_@Z#l!Uq!)2vnl@kP6GJ)St9}v%#@f*Esfl3%B8l z1MaZvzQePh!*2|DdraKtk&F9TQn~2wX9A#zgk0zzH$r|kjd*N+8S3vTm9Xe)a*!0t zn~+3AUdbH!OEuer-Q})YjrId@`GlD%DnX-UsC?EC1ZIuDn#OX!NLsf$H;aqI&Cw8h zo&MFt;GIT84He7o#K60C#&YZML7H(qTLFIN`@dsthfW3EEAM4y`vwqbi%8Vy6m9hC z2NN4hVEz|wNO+D#0^Wj4}ii*?ry_&n~Sjt(Z=LjcWXti#l67 zqq`t^*W`R9Z<$<3=Amu>AB4Zz?1h3@drt;1enY?YR$8Mv_c_$CPBvI~sP`12o!EE$ zZqyXuTJC3ZgIwRff2NO%DH^BW-HJ*@v)Y}KP;wg%MkD*{bprbADWN-stNyoY8TBbQAA$ul^N#&%kv)%4sixt`8Ny(IM&R-f`cJTME1 zJQaK1BuZ*$c4<}0xJMXf5ZVDyEu#@ut>!?RCU5I+qSMafY9NJlkkV#@sW+f5tVOj^ z6b)L$CXl+&$wh_GtH>*2QuKsMp--T6Y}7ac0%4_tX^30*a@DP9sL8co@@AR;q`idyU(6YX8nuh>FAO*~1x z#|sY-l_zkM>Ui@F!EE$9*gR}{sK|{|d>KIEvUI&^(Yw{^%FH0+EJo@z^#R3Ft;l0q zwP$^8ll^+(MrM9Ux!lK+CGgl-RfUGhw}$)r!w}MYka7)Cc)RbWce+j~M;UfQ&oW{} zvlCB&2zL1OA^6J1HwI-%(fG#+{Uj>60izu}BZc(^{ls|eAvKwwULB|0sb4Q&lufvL2SkZVp*aWF%wxW>KIz^Lz1I8G zm@gv$@4eLXhs-V(-fZQow;24?SEGSl`?oNvA$-phm~_3(1*77tn+Gdx)?T~)cci() zhw!~{+!+QW9ngs{VjQH!?nA8H=1YNwF)_^%JtARiu!;=nm3iEhkw>M9ybsM z;kGfH+`ejdPVAbV8lsXjtW(Uk#4BecJqRDiI^-KviPxE*jPMzGOnlz>g%p~5x0wfk zBQyT50M`rRhnKMbZ4PJbZHmS(%$877<2=4lgRA!b2Ay`0%J+RX^1R@}z5xX))#cKk zvXQu06$)w^A^}e5?7DOOFc%7ZHkrcER;pVh_1y-#YgR?Bu<{uw=e+=$*VP?upyVyR zuJ9I;Y<8>}u@s(<8uY+kwwFe+j7@V#o(H_z^anSX#bcMp4Xt` ze%7A|`CCcwyeJ%sm_3%s+TBNIBK57A>RTZ^qluF*t6hKggwmJA6n^XZH1QxBFmRL93&J8V~H|+L#4+Cua!%Jv#&ok_|+P>k-!t=8^t8za@z5jEWTAGkHNoU=` z%55n2w||+4Kaq_p{cBZ)W!zwAc<&#gHg#KX#u`C1a%Jn91?R=t&()1sn}8OQ6kMVk z5FMH#3h>j%s+!)}b^TfWT!Nab*-cT7VVabx7J#p%|F`N$S*lMI!mTW46Og&nCOkNs zy%eH%1@ETcK7`>&u$Y10QPUw|zOUYgl$>$MUU{+ue3*PA?lN;je^GBL*SnbFQs#c~ zs~K1Gy*QFcUijLEsc7N<@+4U6ABeRT@W_@g;-3s^zB$l2kJ-01h;#bxSRyKu660!; z;cjiG9)d#9NB{WI$1z5%VzOD=vS7bcH117Rz4zS7nN`J?AnmfCI!V2Z((h%Rb{D$9 zIU751i{LeVVOQ34`?)t;)2CmV(Qs@e^D-v?At+t=Msn5yi>+2Wi3)K05r$X zv_CFx#>$PeQWXg+Vh+A1`R_%`r0^(SV(=?!sW$Z^k~rz@l%{7W>?&=(os5A2eO*}k zTdW*Gau^SzCB$QfsTO04ZM`sdb-+!P$qp>I0Xd8r#SHCtr%rJGjOEV^;K-@ylbCnn z;DdbbTcN6HZv#a4Y^aOq7DITWv6B1*i;eM=hp<7`He^`5Vyn4I6nbaa-`XYDc-}X) zCzR0@o?8K+l6Z)Z$GNfoWXIYJ+$;Q?eL|s?V)n<+!L_gyh)W~&ZKbn~)`>o7^lJhq zH%Zsszw`QkD{z|^?#mvg(umvez}o)0E~Gf`#8L9lqjqG?Y81m~kV~y*+S(p#dJx}X zegwgcXr$|%VY&XYz)&V0u$k-o{NN98?0j(YfJ+aZbPLJ0&A!HlhDt@t<00Lq=XpN} zxbwX0H#A(GmsjEWtv+Vj?SeDIH)@Z~xS~{P?V>*kLDJJF^3dpovm_?rmt6!(O#aL@ zbq~S4fFHqi?lKIq5dy)ExCpH7cAvIoriPajV@x1 z;$r979$FT@%3W{R?j;Pldu`N&_9M_ONf5o%KKWy8w1)8O3tGlckE^o$$Jyup`fchx zTp<^iH?ex`+ckkjEP)4mAR23!x^Jz{U$`jc&(d^O;@-ly0}fcIOtE`IOz#CZKVyv~ zM&tOGZw#{{(*oXU=4l0z22nlznf1| zfofw^6>YOEM12{$R9Tm1D&Pb5=%Apt+dv!GW<1?LAzU`@%6?ylzYEcAGx%e zW1SPd-&F!*P=TRaN)C#yvjSOVvLEq6<0d&`T3Z-PXc+tm%z@*NJ zE7B5#YGrdSo+8+XtT(u1djpJpj9ISh$&OgKkQ=o6)8%p|&AR%ose}7`)##JE>9g(HuLk5MllE>m|t8r3@!)Av2*8-wT`wMjUjcbPs2+-;^*YQEx0+CKJ{{)hH8nZ;U}+j# zT8i^bqvbex1;ex|nbty9v&7{&s4v#hsI$LsWN4DeR`vU?NA+~_ty^iR(!)>(Swxqd zH$duO5l&fk-8mpS@>WE-is3<|wduv|PKpO8bE#a4!Kq2_*}FC<0qZ$cRlGZfVIbYv@Q!AYUCi9zW+xLpt;3pSF{UAu9&z;;(qrLKSFy67wk{%4{pKRD-P}R1;j&Makx(6G3Hq3Uhq6%TQ>D^L)*tWLwWROU?8 z+q>lI{$FOP7wgbhA&HCI`uQ3xQNmDC(yK@%ekf`X$&Y0vuA4KHHFc2YHJT)?<~9|_ zp=`87t!9v9y)9qASKK_hJS%g&m?kc)8#@8bHTYJE8am|PJm8W&`0YVi5?-DR!`Z){ zzT<%Ci)og`8zXUVn@o&;SmjhHepf$=0RWqJfBDA=(?PfD8$2qjN~WGbpbaS8J8|VD zyq`RVw@lI^f-^5o4y<}3lNziBNmA}lH5={_B;@Y|A{U;~lR9+; z>m&?lbot_Z5yH@pNk`)}ZiACkI4xU6SwE3WbsFibG&g^1mU+^p2KG#R!#(f{s9FWU zEL8r!(|SRq+UUpDi>KyKnvXT+qF|qc{3%GKz5i!Q5B(9~%#~VZylcsms(??!v>;FF zgZ@W#3>Hg10IetCRF9r4rO1P|2XZAMx(4T!SJ}ss-^nQ+B%=0cLF2%QXm(sm19=-VH zy?FEW?|ojxzt+>a%-OVqUX2|7^wY))7_~&zlujocq{1_;*Uw}QL7-E^g802(%cNmQ zK&WHU&cd^7fU}y*>dXmosLk=cfWJ!jCJd1xSowcPDy)eYtzW_yP0Z>PG+6VH2u+7; zd0EFtS3b&gM-4BAE>rTi-QXRppJ@^=J;_RO|@ssG14@;CH zgjP>syUQRnOyLo~5BO*!4lH00@$er#b9UB9*iFU?7-yr8;3qY553ccN=oStUfo(8l zv#rmR#r}_X2sv~y7oxOQXOCcF0)NE*YD$LE2Ffvuv>}mtpdeZ#klir?htPmMoNB{I zQ>(sZ^^Z1ql<=(9#F(SNR^)0}9 zr);EJZU^FcrU#oAKR0lr%(eQIfP!{DFXl$pYVPv(Qd{Ug@^$|v7}4>TtAOvUaT<5& zr%-3ydv@Tn^y8{cPfvC*;f0F59t+#NkdnIM-%B*PH_2_C3KUk1rGfzf+~2!{Z|K$S zFEaZg89hb8(Nr}xmnxSN;;Uq5kbRZwKP31upCf19A=YLv?D(UPe)$>hjJ05x9H>x` zTr|A+1zB^aWBZYRTE7K|9q=PDRQ!9Z^uoTeTGue_cYAj0SF87@IjC2cnlHo~$E!Of z#_eGDJogu-Gg&f#d%QEelE~N{)GA>Kn*v;saDk6IGKDwza_!wumH7gsK@@Hvh32qFB|H8?Kf-HHW$=N zc)XPC;aqFi(NH6#(9?SuL^l4%kMw`^-xN}{9VJbPT6sDYnIPM|FoDk^9ZXpv3oVB_ zpRKOb3k((?dIeGMYxr@jQ@F6h7I+mv`LaFta414dG)1hS84y7cRYFL&sc|zU11Ldq z?G`@a0sI!pp0F!}B+cKHg(UZ6;4GDl4}9cGf4@(;ZYr|Wlbfm83nrKA_~_6df%)!i z!N_IfdeLm~BnrzoDS*Mxa6M*`ei5^vXufK*;!g%r@d484NdI9=f5!-f!xSbIW zO}g2oxu}$%QfEqW_*4uMO~Wy(&-7=-?>|=eikL8D#Wnm(6I=DjsvqMse!<}>7hxv- zOvuYB7GxR{O^=ZBEIvoGdT}$P^Y3A%X zB2YQ1l;}T4_Xm&~>0w|qfcLW`2ghQ>J=oJT++HZXdJs`FJd(O50E5u5&*7QM#>C2R z`uRF-jGN8^V_#oc|ElH&nvC7U8ME@8@k6IB)maEV+MPP~+2Kv-*yMA4{W%XC6$-bS zmWO!Who(0GRdg0xX{|>M#&%sw|gDM zVX-kvVa?Yn#q$1YQX_)C`WwVUK2mFoeRm@)FQ;c^N{iTj`~4DkZ2L*0pp!M}?^^dy z=*T4Tpi(A7^B<_8DrbGg|FZjQ&&SF1t)|Bj8Q43VScti`QrFZ~pc;*SfqhN_ zGRZciGNRM~Q9Z$DTz>-)dbK(^i&atoHS`z zDd_UGS23Rn#FrTej7M4ZuI{rfC25ZTuSf@=h4k-W?tH07!MGYEDw(zPAP!A9v#}>WW5c`EF7W9JH>b&;n87=YW+GNy3 z`sM4&9hdv+*?K~psT$f*u}xY~hl$<;G^67?>U;iZp7fL+Bs)1G#xbS&Vxsj{>?b3tjxR1c6`tDa4o~4TL@82A#~i| zgD2E1$J12sf2&3+antd{9X5Z@3a&Z{SF>dX4z(Yq=LZXpmuriDWhd#xqnC6)7!$pp zL0?>*j|=JfK6G0vA^=?FovE~g7pQXWqxMOZ>$cWO^EkG?xnz<3;DTw+gvGSj8@y>{ zG%ov86t8v|S?+5Hh;Jz}QeJH;{R$dEIn=pPK&owzbv$Y#5d1*;I{Q}mDotFa*LCL` zaLT=gWux(LmZGfyxpl=%;rG@Riv~YLBm_gK!?Z9qxawF3tcWP@5DvJQ&%*z7ELv4P zAuRV!_2x(lB**+nsX<5B-?3VKZnP+Mpo+^2Elee&8~(O9#WLVrdvqHBzW{r6ZihrV zyfzwAWo_)kzuz4?;mn63d27W0{;GAR5GFIrxHlCE9fFZb{OD0CY|Gxpd@_DKxu7^d z6VMZt68kC`5?;ovD!ecj)U*2G^1_3ZhDd8-`6}pKOtj!6X0kx%_dte;_B+YQiRH3_ zr;O;(;5aAUJB(G)56%w0>STvx=4`hC61MKtbA6vo>A6Rx(d}1MO{7~&NFTQ2VT06n zPoKmu&=Xr;-N}%(k@6EH0ik>;HH_0~7s%mz>&p2;pLDOG{RuC^BZYE-_zG1dlY!P6 za&P}8CneWsClJcXvHQL}k-$sSed9So=TG;T?6i6J736qieraIjxhKW!urX<>V^U=w z1L^|Ur(}0s1<*yxLZRL=eTRDoXgvx3g2NdGKasK$ystS1IKGodAWDB8fP2yJ6d(=d zT#0+j!M()7w1>iF!^l&S2*A$EsQ|Fz<1rvkesgVue^I5&fg2A_ie>&ISi z`qM&Qm#pK@759K4A6bcv5vB;)u-qqSx|^=JY{mtj=a3CFA$f;Fh}CPr#PpV71O83& z!j&ii*(f#)kS8SqpGJCA!B6HVOnb4Jv=U~%1wr)FRaLV**$ zMYy)&`nn4E#^0q-6f%&}+0gY4r{02A zDeK-rP$<$H4ZVD1v&=xrqM&bI?}1xppb+E+JO9|ZkZoA*O|)~`P}Zb4HP^_UgLv0# z;Op+B&I8`Rfg_obpb1Mu1EfTSKQrh2(D&~aS0|Utsgm7HA^$734baO6zSxpf?id^g zUAka<%_Thwlzs}^yY|qqki(A2p5Pe0RAMIY9~#$V;@wWOa0sx)3MT<-lfWA9nbn-zhVMsNYmPUa^zD9@Fk|y=tRI zJ7uT!>g~v96(flWwrZwFqR_#pG*^ELq+ z#CSp)9(jb1af}OucJ+!GRP27<@z`Ek@slju4yg(w_^c7vMzNy-W*++n8s-n%<#ZqC zW)x8txdKh53-s?VPQD9A(3`9WwSVB3N+PG-p`I>~XX9_c7nkE4hykr{*5L9$&a=V1LY5gF+YnzZqp=9W*sLr<<~Fu&6th(YWD zO^`zLi=b;(eNIW{$t_=4)sMf>Cs;d2e@it^yHEeP2;h;iL1dZycDw&qPoJ2bVaf57 zd);f6Y-3zRFA;BeJN`;+a^p)^v02Z12<{?y>+!UAFm+J<=M+y(mb+qFGm9^E{o3R8 z^*=D>@MVUU(nSFT?ZzW0I6`Ig4O!}n^CA=J3O6R&-nOq3rQX3Wj461pluyQ0z1>)~ zf%5Ph)`TL^HYN>JB20f@-3|1*(p@;je>Y&8jk9#{@p|b^+}@n&>}wOO^{;ep`PRJz zm!vJckYrl)-kQkuvldFx#P$;#RXsBlx zTmPMR$29xz)rG<3$OL=qaZ4UVG> zZRU~lQWHGId)={p*AmRU@eTp-^whjPU+!ES<}utg6n#KpJVHnjH5%^HXucQ$SH-iUS4E15K+!?FCG1%-0A*Thc_uB@kVh+dk>3*pqgN zV{vu#qYQeKYRbG_zmFjhpva#sM$ae%RdI+k#3B!du%GWjG ztD6SSM@AyD+IKORwJ4}?l6lKy7|BQAa8BUlw3SN0S0wiWYrEv+ z*_1T1?%qW{o1R?p-%%20#eVP%;-EPjKG5@k(AG8tWGt} z0XW26|3?|dvOm25P>e)n(+d1h$qAfkZ;-Xr5x+Wl~1w1EAxciQ;# z53u%YvV_mYytFQVC;^qW-tp!OS5};(6{tGBG`m_HSEe|cjpUPf%*jRl8O_|C5{$E; zm;JRf%=EQC;YU3d<8aOyRnK(tqg`^EmqZ$f(u9Yw`h0|eu-mp4{$aFtsGDlb@b`Vl z1Pu2(r^fQnP}X?wz+*fTotMG50!>RRs=Xt%GQLNOmEz#b;Ig%2zRT>IbTLNpQHeD? zI_YW!-THHasj`2%CBv`yS-%jd5b^kF(i1;73AWRMcZbYFE-fj@de7kvdLy(Z@13mK zm5M1>f2MH#I=7ud)vDAlrmW9x93Rev+X6VBUt{(z5A!MetA^&1yWG!WV&Xt9Cp9{Wy=3l)&QmB>89To1Jp zz30Y(+v zJ9X*@WsxZE;Of${qs|@d)GoeaNj+A@E(fO`Lvhfg19=x@5L+?jMXz|<&?UBHO=Wrb zana>y9{po}zWGFm5T}ELyKIL#0keG}DJNObVV@=64R5|iy1B4A5UF@1!^(U*?_vdT z;>@XW_dzt{*I;!5nuec1v(Y3tJRa6CaY()d-+d&b*LV)_Ok) zdgABRzY|G5_ExASiY^^1aUAtm;7hshSd=mWaqTS-2QbQ`yODL)$dk}kA4^Eao_HLZjY@vA`~QwZ22Rt)D~Rbg1FF14aaQVZA!wX20`M5K95 zv2UNuso7uA%l@&w<%E}0oT61>5WlwB&8Tm?s7!?P$m^9A0P(vwGy1Q*pt|aq=fQ#~ z#TCSiL!?&q?+s#{pySI5G~hvDU2xu|D6$r^244vWj4K;(H((}i@h+f(w>SLL(b?xLJ;!_CejQ1hg={MGJ)J#XcAh`YqJqv z+st@@7WNMF3#XU1%g=36g9`o=&4Pz1E2(H1K!@LEe(*$Qw&qAY<1v=ozK>uNlJ8@5 z`A%TVFW`7`Ec)*ElY-VXgU!Q*tk24|Nr9^@hrXb`X`)o+;RONq~smcl9FpLQy%Yk~&VXXw7+|S2n?@8| zuG9IYW+8RLmyV5)McZQ<#;fk@d9<6bhesD=^s18-bFyS@TgpY?seSJg+G!2yqC=VZ_MN?a^Jp--3U;@?l2 zkTia_X+^{$XxE}Q!n^OrrNUpkT1^)WX0eHvQHC+iJ^(Ytj(IbqS`=#AGh-Te!YB}` zA}SEta+~uYb)+a)-Cf_}M)BF6OcGeaHJn|Pgu+=bc#4gwSrez?LQ9*A{T8gR5Kozc zn81#6H}b2A(@)CZ(*Z?*mrnr-0#)Wx_(ZcZs0P3F(@G7bM*lxADC=1l)081zlyy}>Ud z&M1K}XWHbMtwIi|gMywye}>QW;k787d;$g$UHS0JbxUxCcOf==L?%w-8XZ&-lEpNjoPtlA$QQLty=O<6C>4(#P0~ zJs~}gZQU<#THZ0k6CMUD%I{rOHMeTs1~Ai@N->{jE4N++X#K^EnDmSy~) zh^mCngJhR}%8sXr_?EteKPe@Euq-h7qBz=f;{yJ1UMQyMbZ*4SPWsC)_R_@%6Ch6h zV_L=IaNU7Ea^0-`)gjvOo~)^l2>boQf@)@=MpY~l3UDiVbO|Ta!^(Fc1sRw2t2Itk zLTKgHSju9h4@d8XSMGwwhiGcPcaT0S0rB4Ov>~p=@A4+srA(g_;$Hg07``JTyeR7` zl`w`= zJjmtPyx1MT8}U(KLcm8>c3(}oWVnwC(SwJGua(=gcdmJ6tq1^nV{hg`s2Ft}r75z`ayx6j?dtwJh*^7gW0ogaF&Km)Pm7$Cv$8;Fa-@<=P zPUA(ys~qr=9@N0fAaiFv`Axw>F9$h!i26jdd?J2tLLdG9d5V`&fof7jd||%&q#{5D zOLzs3A(`I{&Ap^~ZziA8DSl{12*&!J2CvooLlIAI$|8yQ3;jdkIhHyZBTO=W2%(Pk z4=_AGt~n*$Xe`wx)UD@2SXL4Y;*gA>NR)qU*qs`|Kbda56XpF~T)7edp;i$+w~(ln zPvzgAGpX)tT6bUsR`$^{9RR}^Qt10V?=mlUiV*I@qtiNyeN8%?-O0Ps-sCA%Zh;*p zhm+9l{#3t;)>y1=c^xX=Vc(E!_FJSYztf2uC*!MAmMbst5PWrAVg@2^>!(&M4RclV z@j)PMtz!ErA2Vi*7mdr_U3-28;qA1?>s!?^FH)dAtZ}eQUc6e-X~dZUhtjU zNkk^fAFFO;GW4Bh<=Q{whbb0|9&|)TZK@7-96;+5z7}4{UXP%KC%JpMZe=;UW|dv>26o{ z7Puk%Cg;K6vGYhzh#0yy=Iv-oF@Bn1acldR;LVR#SLr~V&|L1vxpUb#Q7${=; zM*r}JDvXK9ur{MOVUQm3RgIksa^QwvCFzfRIqEG%c;{M*-g?SlUZ{p;H7TEHJTu92 zVNaSzRJI6R_9*yO|f zo8-X}0^?t|OolCn5hfU(V47)WdFf{qo9n&+C!J5H=b%*aRbxIA3X~0Ssgc_7RF0I7 zR;Gr2_M*P5Lyq9Sp=`uQF_3UGe)TSvTx4Z#``kf8f^(%xmlA50Oej++CO*_JSi3xhzQPqG#Gem${iybp8^&6k5?ZMr($vlanOe6eGfTER9zuW;InQ>2NgWw1M@k7Tig?- z(qq-MaiBp1Ddd4G`(}V*HdMx1SsZ%pLT*y;eQ1v$zsZbRZx~!VoAg{pSCTh0I|5@8 zCSdx|i40)BG%&c8PuPaRIfQVu+xb>Jd7ImoqFBg%E+v=hB>qsvzH&IFV1SUn|4kF6 zXk7L!rk5{eQSnAi(8pPU^mF?1qb9@q^#m2%B8nJg6=GN@pSf)G(6+F!_Z@2Lg=qLu*z~#@fYE#PKiN8DhzxUB zdh&?4p}njIqfvk%da-C;wbv-WeY%|6a4h@cQ|Da4Gn(dmCmjt3BjPTT6hfG{R+*L)jLwMcbvw6(21{A%|*L3=56MhGnc28eS;0`Q4&Zv zOU&vWF4=u^1)})oB7VtNu&hz+t?0qvIY!R>0 zHxK;{zY}XQO$&}vOY0pwyU%F|lj3_Syy&IvUZ~{JlxciQ-x9Wml9|ZiBd0SxqT_7p z^ktSHUa=!!9h2pG>;~h)W{AEcCpcWunPaDS9}?(XA>+i%NMe()HhH}D7LBwQLg2Bm zc~m`+O^H>F@_o4=k$mxeYZmk@;QX&Aultn+zlIHz0nY0{owr6J7N&8=Al;`g`Gw%Z z!Me$fWFVAa1c=x(5*OF}(6(7gdPv!E?r;bn9dusDwV1&jqh3an_Wq-G`Fr)rAI&5! zr_H`_0v}~OOq(WEhV0lcYhMg5G(nvqy|}sco+ERg-^sr3@QXn8?+-6}xy$gDP~8K$IJ^AR)8=&|zxk== zVVF9iE!59dnA#oN?upG$yG5~~)IKqOlsU>|Mw(J;K!1ip)GN>w=!5+u%P?6)^Wdx zQI&$XsS0xJmow8Hax-b?q5#Ycc}YRW{evMx_j`w-UsBIK5DyJI_c9Yz{3zOMaT@uA zh$Ih9Ji8pa(2wf3aOe#}&;5yLV8}Dte^`W30zBCth&=~i)4`6DUdLLfS83bB84^r> zmCF^D37W<1#i6V)c9@Pyy?=na^N%ht0MBQv6sEBgUW{w^&4FFZk?bqw+LQ-D*WwjN zt%uV6la_Xa?Ny@6-ZqxH`#zz%IS$(k_d?NNM+QkTI45xnA#LLv2$d_73> z4Da&0K_=f>Bs%P{9x=XP+gRJM$yQx?c(D)pWWLcVJdSKV(1PJpV;T>sbO{R;MN)B# zaMF+;7lg1w-Ge=Kq0Dj1Zck~9$y!}*f^L!^=MTw71))Q)r08$BM)DWI8&e7o6DXO} z9>D=o`Nuj2xwiG$VS>3*U(aJ!3pw`Hp;hwnB2ZPAiUA0L2N|F}s9l4P>8Ktp)+LIh zxBp#Zfi28{qABeYZF7OWx^gS))nUTqbOU=9to^Lk7>Ufx!MqA38?J8j8hx;fANsXp zvF*;h(D>Z+vg;@4K|Un#5oGYiWhTj|ZufgqfLMiACJd=oF(2uzJ8n@6|CBN!&4AYa z?MjNOF!?V6hqP#qaT1KVsRVWYt>HlkGmE*>5U+cZ?#7Ov>Dk@zTjR$Uk)Hcq(Usm; z8iWA+D5Rj+fbQxi0{7G#)J*4`Dc{)JAhlO^eEZ7QR6D7_Q@kpvI<1?=>@-WSkZcuH_XJnn5a^d*;ZuR0HIPS*Bwq9aE z!O@5kEG^Oe7aSsq5{pt8n8`iW%UziVDv8Eo_ryfH6H>5BnA%aNlavlQaG|x3of4_MUI+4=;+v|4LG#`$a zXH9Ad$?l(+R8%oE1Wc2O)P#vcVR-~x!Ghnar7xTP1w`Bp4bjCz6eUsdbG%>_+>?9> ziHUCB-q~26B^k_fA=-6xh}-_PUMwoz4f{S_$%mO`JjU<@Wz=NPi&GY%WJXk&uCJ4V zG1hsEwv@~gt1Ni?!uw{aMNY9 z?-oXsdKA&dE6+rhtLgw6Pq_vh_{+EseUJ&>>-9yze`P$!dhenjnYh?jAytoml7E%N zGCFIW_tQu)2K{{FmQ!bLLKpThE~t;SH~9|~lf6V{clRg2Pqn{=&e={}v-j==c(0tg zv_N@D5YA{?D8Ra3InE^X!oab;a$$^?Q9mEoujALqesq_J%sC(-0ae7*2W;o?&bViL zi{52Hi|ErvyVmbxGuzI$)B^9X7u$ljQ1it@zL)qnG`U{4;k3Fbqs=@FEV3TsYi6iC z_mOF-ii+?~lZO$z<`itQk?2(Xa;RFDPM8^LwM{b?LPk_rN+{RjuLR*{k(|cIeHvdk zo2_6M51LD0ur)MXI=wk=74Zxz72!0b>@kYZEMncH2p$9=(mA6bVQsH*gb{I^dHMQ3~)e>o^VPYG(==a$nrpf>TJ z2?P4#Z`1OGoQ%`rjpEj(8MO_zLv>2U`t*=`1w@Q4qVh_M1(;qP2)jK6+~c(TJsYDGz2 zjmRMKq&JE&Z?M&fw=!Ne`4RMj8$HZ+C`X!3-f+b9Vi5%nDetb7o~hYEj21r#@RC8l z9ZYAAlnA!&H+nns%Q79Vw6D(H;#pYIC~<#?&E1$rAV6MsS0xL|W?w%ww5=n*A51}a zSk$1KwEnKqKU(igs`Xb=-YguCd&d&6e~-MLn|EA4si?&YZq7G&SUL}p6KCSeKX={v zas1}*_I;TTui1QQwBIu~+_#PiKe!XI(BBvH%;X~FdV6J_5T%18AyJ_k&MfPvm%X5r zU;bCBCqC|K?4z2zhnH?cjjt(a^yX#P!-##>1w$b9=XziD&R>aG!KO^NNgx2ana>K_ zYTXVM0FacRaZlx$+!xmYi*4VT9L^zOpVCVeO}%yP$pkZuMr0|n2_>M0-&u85Y-vq= zhWP*}GTtMRqQ93A{*xDQdv*=)1unbv{{9`A$UUhVqvo3Mg&-#8ExEX20h3eWM~f15 zUps`G7N8W`i-3X3Vby_+S5UnW4{O(}0NwA3AZP^6T$1OW{ipt&%Z+cN&;7g_%QK_#kCCrAsKU`0|aag6n7ut61;hgpB`I zbHf<;?Bl+s|Fj`7n4E%vluC6gc617RTNj$YWg9x#+0tL5{QCl!S&!veQ~u{D^-QwU z2F323QP-2PWZGu``WITsah3O~=A=5=Vgn!h&XKacIin?dKOVYJ_ilE=W#s(4#{o4> zI~;dM;G}jM!j6ECX-qee9k(lt+yzIba+!AZ@lN(l*Ih$q#E+hsRp`wDfBHteKU^ne zY_U=xIfkS##XK-hZi>JfI7n62;KI{bWJ7-!yzCZ3sxecZP=;6#Oto6{di3{WrGy!* zHNH4GB0#(p);p0Gw3_)k>zhU{)F5%v-Lg%a_f`GyGb>FD4l+q-fk&L=MS3b>IrUrg zB&?XM-;=lTw;}X;q6`GbP$v0#VMXPZ*lC@|NoT1cNJuprFL&IX(hv}ILNBQT&_q;( zw)vW@aL4gjz?P5hx;Xqf{X!8&{Al{QFbI;*#W%6PC(8RTDDoOD$XjNYS6#ZQn$E6f z4(i^C@n1?-%1*@FoaF@c5&T3Zk=y=DU<8E zh^9dZYfUA1+7+mRrNJG!ia@$CKAAY-23r+jfVZgm($>y7&wtz}I6V)i^>pRW@UXqg z158Zg^u-d(rhqcLcsLka-HT|M>zB_nM zuU5?|nBB-+wSKQ{L9wK57tZ80{@?S!O=em#mEMJnUF?*rdO^%pMW;CEU0G`^65CF~C2s+X+w82Z z`uX05Y;JD;`oa9Da4xT6Ao?nsg2sAK-P&+eC|Qxr8)p<$3tbI-|E+n^%&zL1d{*FH z`#?+}=t(qtQQ>}Er7V#d5LJF>cPI!6wI&S^N=YHR5WZZsq?^2UU!A&31S7oE@Tc(O zIgY}FFWkX_#0}-QNLV|kM<|<*)$5l4JRwJedRqq9bS+C|L#53W6%fz)sp^< z-!t*pCnDMls+dxnvH+ZT3+8w+v`Bp`6tpVlqrW>=w%+{VjEkMkCqW6}cHqNpYYw5< zKfswNtRfVxrd&ZX=F#PuuB8i7|BqWth{SgfjaVEHISG~ekELCPv&HQ#626_hhds@x zI~3j^rkmlb?H;3hmI8rOg5wrY)F|3PH3XN3aq0)4sfG$>9MA7c)K*{2zfHv+em?}%XDyDWC7w*T8k!2w{2=~%HMHP(fF|S z>DUKTqa{u+$e3585>0$z8F^@{AozE=&!U~hwU=B$ty9gCsHN2KYU0eE_{8Yv|AUPw z#LqICHOvEv7G$tO?<~?x>;-XN+!YfzTVe}HPK6j z8+VP6cEEU_@lP+Q-nNPZE(I-WxbsJ4j1%z#AdNM0gHCX9%fWZ;@siPdF5D8*iV587 zwd}YgWx%jGB2?ohS(7CU=peg0F|`oAt?JxY`XW5AmxHyJOH~@Fr74~%uuB3&q%(FE z#gQ_!=7U})+eN#GVAJ3@aXj@%3YEiYvG9wx@LmZ^7Q5Kt!;BNJ4@ zcXTNxRET(rCe*?IHMf;Px^Y#1s>Jfn*dQd;jeRkRLUc{E%SX)$6So%D>d@4{Qt(K8 zweY|wc`DuKNio2CGBO_(tTKp{n}2A@QN@F&h5Gs{hjYyw4g#{TGwTt9#8bO$J{~Tf zulLL5Y8D&3adW}4^Sm)m$Mr`h9>ylbGe25dyZC<7p363Rxbos@UC@xC=5FW?_D0}+ zlV5Mc?@YZ6^%Yi6Tu4zMuQoA&>24XMa#$zY-DqPy%JH)%o*nA^6$SCn=5j5xaXhZW zn2E;)R*x$25MX=y^Rcf#C0+{j|BIcNlWVc+G+^p2U9T#3MK@zZG*btp9ImEo**r`U zRs5S}GE@GdzGj9fjNP04leP9EWT%!yo1Q(6AGDmGt}iQQSAHb2@oD;{03sq*Rm+n1 z{-lp`{4(at65wWr{Pj~RC8!J|(o{CS6g;zSidR6As&PEea8m>TOkU-8?k{1!ck8t9 zQ~m6vJS}+DyDkc0U!-cNfmqRk*qVxAoUdc5M{mMs_BS&3nb+H-hRkK!FLS%hWKyh; zJY3=W4OZ$V{}xcqmL0Ld4+(KGNsIN(u=}!&k53g0hci}r*Q6n)tWVjSlh`jf`qH2y z?-)b+Ke^L1iF^Y9q5G6(iGK*D;-N$C`#YnSVgWZ46%`eKvgi_us*)7ElDXPXhSi;P zJuwjdvn#y2Ba&RAq@=LCOySv`bF1t~E24Q7ubk1SX1N=f4kj93z>gPy`5A#$-$E(7 z*DpV(@iIsoT?+8iSjgDny>Xkr9v;zeHO@`ReK{ZR&-o5{gY-`3Fd4({HQ($w_O>e=hO9L_W`>C|YdS zq|j6bA(=6iTJV-(@;;QepJoWEXAc)MT8ERg_NMF5sP)0jevdq zlJsv!8Q*4Y*g-aOc?t0P&B%kex)_?!#x_0NgMB_a@U;xg!OYs9=J2;7_=kcy zdDu#*k$jONyIv~OmUHAk_;rGg@6m3fp*5$lagYLGQO=nJU{G>NK|Tznez+IB2)PqU zw4+0ZyM_|0KZO!)C2rTE-duFcPUIM~tuR;I?k&MpXns@eOa(IM%?|>W+FyQMid6%` zTeyZFd~>vY3BQWX$UCLUMSQluQ9il8)#~tW7Ck^R$3R&4?h@0mSs?U@Ai0imp0Dzf z?fJhu0E)oFQ19rw`uA}ANfFW2C)Bq6^P81+xvoIJIBJ)bfo9Mm{1jq2_K3o8@Dt&! zz3G$(gkcwcnG_d_tSP_L(Gc;2pJnxI>#T>(Rg#yd?N2F_ z`ub933XDqrz(@by;nZp*Pt&Xvmm`2uX=WxQYwk+5cZeahB8 zuxYZcYsUiX)s6PeYh=cMl-Cs5SENvdR2rM~*uT)yTDpQo;{0-M5M&+QU^WJoP#^Ux zqq!)*$$igEJ>%!p=h{91rkf^&2F3048PuJwiEubLcsK|9K%S`m&_>4)1QTVkl8Oe;t z+pV}$>Xu`hyz_n!SO@aRIdwysbuT6@txk+@$4AV(Dz>fihwC9qi4uN!@T)hk zX9*yo*S>%zBrq>zUQM$@#=PHV;J=znNY!Fs{aQzdQTK25lBsl6AZ&?2n(J+(uzUc) zsX(CbJHO@HwU{iMx;)Hr+qaV6}wE@r4X6f z05fVzp5^JGKaqLV7io_bkN6v37iZzR^dz-k(ltY|pM^V9IZ0VD&#h2R-&MOHZDdPp zy%Ln!_^Ms7bBow?Jr{BQRWwL>XBvJ^$6u1wU!}+l7%EXtLhXQ2-2q*{Wm~G>;+1qR zdln>U%tHm@aDW34TQ@U!|I8C}AY1yoZC(V#R1$bnAuaJK8L4;yik0$G;KQR70r3j| znIAsfdUz@Q?UIptIT#seu?e4{FL6O@J#T1KYOUkBeNXe0J2M)P)xuX;R|7q=b9~fN zcU7AQLCvt`g28DYH!TccG-d}6r|Nlhg-qP)s<@nJTH<^UZa6!pSY|eK(WK!+`fAEW zH!0t}2e9fa7E}sW(Oqr(zcFs68qZZXjV9PRWqSgD_aqEZELu?%*o1P2kC$06Tw+Hb zF)DsPZDKj#3y6H0TwnNC0|RKdtfwl|3kC2BK6u@oT}f=~qv73%`CH4jX*mYpKZ3E+ z1`13Zb0Zk=#>85cLeEM*PJ**<|9H6tps7|$C7+B*M-~rd2Jjwx1oW-bqD{Kv;aaG9 zj?&AmY|m`7-wewjBlwWF+AG~*jHnF;y)^DSmViw&Zf~*DcM>Wmw|__*p&9Sh8tEqa zEk zuynT&${-WXp0Rvz2oce{vkfBu_&eTI;FAjf!oeRmdwOfWzL-j#3#*L|2?iHBv+q#k z)eNbg(OE~Hu!K66F+mX2arEifx1&rm&dpysQ}h?VZNg-*w_)Qu`&&ubu;7SmWps+! zKh6H{5{nFw=hO~3I#yGm-NX5keyGkV$^Q=Kfa0H!OARs;SnysTF1f`{8rsF}?2-h) zURb2IkqcQSU^c9ojyz+UK`RaBIRL%-^boJpIyLfwa97Z3Gm1PtMyd{*my=T15sARZ%i%E0RRM~ zIY{dAPNwqt)|K z>R65Tr7#yoRG8S}CcR1%O;yPqeG}>=1q#`oF$_-J$qAm^-x~rei6S3TyI`HQ7ge3m z)bU=W=ACv9nzw0}m@iFq=~w@lwIdU*GfrA8rXg@sCjS_jNumYK01V#_Z$+*!*zUH7 ztY(e!VXCs6dlE1TseWfFFfdywJIjO~Ls5)TAQccBc7G4Qd3v~hN<0DI6GIAQhRI*% zwJUpC)PDC~F4(#5C$t&A$y)cjH^rHPL4>VJW$eo&1CPMzljUF=IjKkb_2HL_a{^|B)7 zljmjs0JJtsi1@K#EN@hi{Hcf<{i5sW^Q(AU871_$LFH`PjL>H`d^(K{t+JXz@_4=@ zZ`MBrC*3V`Jm9;`U3(*20%uvxxmnZqK5==x+qw0ldBRoQJNDp))d)q4 z@#w675Dg?j8uv~tAf`qb824pv-QX>j_2|vh&Zr=2^#S0s1an*Hr3WQe`Ym^q{?-b0 zmP*Ql+hzxBIvt=zP8kaQBSk0us|ygrv6rKK!+aTM?vW9uCm^sG{cCzJeH`l@h0Pq4 zN%XF=r95dqMe5b7pBEifae2(9h&|8GC7Y53$tl%};6(M-uHO6JAHOYt(5dkF5EVa! zsNRh<^|%iYtB%cA^>Zkjx&#}hSDRMpe)tQB# z{#kKMvMAocCNVzMv&$${EZ|_Nd5-?*v3=JL)A7AYlOm4W{n0JC)y9eE=jtrcjGBZZ z4yk`879@D7y5G*F@nzs-8oT?-MVgE$L;bL?@E>{Q(h6f{49~wuP|O<^I4J$gKfn2$ zX_O_~I6wS@+K-}6wqQHi)%APRewQ+n4d2r?2t1OK40R+t_6eU_pcPF_^d)-3;=|_r z`L62VIo+F&5wxL4tgB|>*#X07oM@5(&{=eq@A?&&`Wfe4#Xl#vFm3z`a(VGtU8#&wdu&p*#<#`F zdx9sV8>goSUMoR2R1k{-tL}mHkLzc)pY8(xNymTX<)v+nzzNP&E3LN_K0yv;cuR&S zasFk^`F(5uIXfYbid4|Z;3bxk`Hm*TF=)l;NB6x3a{EyQV@{7gPucky19fs2VL2UB znog(c8v9^c>X0iMORVlu)VaC8C)%|wh>1JNGkK$8qN!(n*9w0!N~qcH9}%AINZV^q zT4KQ^+;FA$IOtr`=;W=`@R$wxv!`gPIiElv<+RngBz{MP7enalx#*bOiL4WZ5OHol zrpAt_T%r;wnXld}a!8|RQcbhzFXkw`adfFrlsYo_ksh@W4ML?%(m5bLr9-1+KhdnM zjl4Iptf?7w-uXM^ziPT4L%ku_-EE3Zdhn)B%)-Bb?HC$w?;|YBIwSL#F01&2+CX8} z!1!k@lxJV>N%)tBgNJ?u|K1tgZ)>vN?cG28LrEeH?{!A3vH#r@xx0 zin6En$@PoBR_!Qonh$e1^QcuAOtP^xGI^|v%(7Ex{^qn+WhU5VzfAL{ zuS;yH)qtd+J|F=z_Z4?Fs}I!SJY-U5!Lj{{`s|ah0i`s{Q?p}L1O?5pwYQ6>5Np)M z%?(wH>uMkB?mg{TeH*^cdP@Bt&ya(AA?O-nC#HtBddwE3Dga`C zBLokNS)+}ERA2ZV%iI1k8QwnaYN>K$T2(0*q7z}1T&r&OH&`eTd-Tc3h^?pCx>zq7Va}oK+I!iJZ*DaZjq~e+wDOy~O1IPNuqT{P;cbRFXH}xxGpgtk+KQlFHT7F#>e#;prz>>5Gv(V=gMim`BFSg zloONm9&g!uzw$W~^OHT*{@rRrE*jVU@bh9alPb;zsNaM1K}5zku`WL8nkDRkjOsNE z$H*YsODAmfTJ2DF>9#!IrMmDlsjKJ*$A49tPF(|APG9-IIjq@)H4h&z|5w{-24BJ8 zC`zI86soiC;KcTVs&~yL$(+9NsR5+vspAV~kS#+(;m6aODWeeF!mbK5q&!e~wN5x($_k>^H&EP{zq}HqJ2H-`+xL7I& zfSDLVV56d`7oAlcf55c-JcJ{uvut+%cep_Xtx=B_h6Xj>za0}0Mf_EWnVbTy7e=DW z;=n=G<#5q1*^oUHbs<_K_#X}ji`rtZbop`P#RExG}i(>6bw>;ON1TzEd@voTOC z{Wy6b8?vHTE|iI?`CnF3r`H$iohCg~7#GVpF)~g^eIM(mQ4jf3g?WnOOlLJA6I(Q4 zXlY+1xo`j<|+^|3vMYn~Gs zSuUB&*~{xSr(jNaL)SPsA14P_^Oe$8fl>mbLVM}bl;%y*7;U5XE0Z%pn2^jvm0+#5 zAeGWk%HFM;(*vQpX{v0S*>Al#bBp#tX?w^_aSyijBuPC__0xwYWfxUx)cLY#W%oMe;W=2vQ8IdU*PfHa9bk;T3Ov05kGa5y;NeJ)!_yFhR7?sP4 zc?=!u!XY^u%(C1<@PP-q?nzJ#lLKg?Kud6;R+wLqS%K3e?z;7k33lE?qDgVvbU{|3 zlSIt!ZZqL7*_-bn%?mf?7!$CixutsV^pvUU2$%OWODjvLK!y(z{1i-|+j;I1z57!B zJ$YsFY@3&iYuB-d9{jgXYV{v}k^HXjr=vz&^GK6JK#1vD+Hn%FWo>TB0@QM6E;dGc z&%tKLU48ntrKo16SFhm5_w|>yKgipo!%-1PCr)yHjd&l)z?3e~f`(_9^ZuDUe$Qv4@NYkZ%2&&dnWkgkI`>$)q=l4}1qdHb? zzz#wr`cbJv>q_%fxLPO(5c`ZZiHe>;c?t*9T20|ARb8QjSw|>6toA9qTpZns%sWM? z(abXQ!&+g{$t`Qwvb(D4n4*&1dEU~Ro$d#DnRc)Io@`RNQCYsXdBUW#xsom#16*_( zY2I718Q8uqoS45<+KmLbqKYwGorh^W2V%H3d%O*7xaqK%;1)mEUwrmEk0Fjx@8MYb z6gJiWP?;j-BLz`EQxFvoA}>s1m)Sj(P0ZdR)2>Ui2?pP|i0|fkF;x04FgiGcPv+Z{ zuvg_0%g!`ejdI+!?u=~2X2tZ8_&BMO#2+k9FBZLm?^lH%wh??Dhgb9{9zPjp7S4i_ zL~P01cjTmcP_yTD)M=)+O(NYU^h?P4D_2vTq?a@&GN!V~>c{54E#9o7BziK7YNEeR z-~x0c{2Q za+|&l7iKYd-D}#5#;Nn;?PQ_LFattq1)qQQv(;x(8=9oTh9LoTS*QqyIx1b|Qtv^W z$gWw+h9-fg#Mj*>oUTP^fIHF;Q^+wGB|a<=bsDM4NGX>qX%eY<;1M(02|3@+yXpZ^kC8AvnITuoVPPbURu9~R*eiSUf&}U$m{-xQK zl~EPLVs;w+vb2czp3A9Lk;G`&LWsoxS%b!~)-e>P_P7f#;$dJRVK*1FuVznZ;RYlI zEzv~)7n5HB9CTKQD!L3HLg%Oh6M=vmJqN6L^ue!EAGWklgFdZd6yQg;7K_qW59fNuphn7Ke+r= z)7Kd0%5+8F`72X-nLU+K9r8xqPkp}T8XXm#-2?GVA5~QL%-Zd>)p0pWJlZd5Qz%TJ zUY80se+i_ijJL$$!8Rj^rr`jNrfsiN%5VB$t*$R|A)%>bhD}<4U4KI^kg@k$;P&pK zL`I4}Dx|U11Mv#@1b@-$`zT>S$5kn;PaNl9jJJJtw_ zn;iO2ICQV@K)~Dlgy#4Jy5^%YLpUWYt^Kdsluml7{iz~E#x~w2@alYW3}Kt#RoV3$ ztMxVs9ZiVd8x&S`4H>`sU$Z8lvywiREJeL0;*Ee?o2m1gP<(p07EX65O&;`2jF-Z7 z6p=ZIsD!VmYtEa<&kYHVII?$PWy=1RN}{Re&UNZjl}xINcm*2zUiyf;U~zcQj4$|P z5vV6@XZ-3iZ7e(Qg^Lz9$>kEAF|G6&ksYjNdz8T#?(P{F%NW)faRPp%3`1@+o-Hj@ z{yFv>b2^{@4IVGmy$GtL>rlcy1y}JnOq#MRr>&btcb2lF;nUFHO3ZZLoJ(*qaecwm!7YuI(;z5@+~k&k-9~&SE25^7vj}g!;WyRdJ{#l-GpCTryVw;Zt90 z=7S`9H(6o2IW^7 zO;~Tn!j-?4y?)iqRYJ&Y{Z$=>)+tD0Mh8pKS>wj=n$J;hr~c*h+RRSsIKNp8O$xd` z*^a9P4;iipe@3})Un+f(J#Gu^(2b`?=dO^o?lO+_?*o|e0Q5?-nomYCQ}bl(^4sZX z*(&x53=g9AzGB?Z)-qctKfAmJ=~VXLRUJ2&Ljj7%w>%OT%z;;#BF%{(=F5g`K!8@o zVV?VO=D}PW0&fTDMTM7-tBCslG^H4E*Tid*@PS_kFU;=6Gc4**O_-k8>zV~-B8}rf zv1)UtR^#X_uzODUSD%LD%klU=Pn*rgxx3Njy%CNlt&#{45s(M+Ukm^c4p3Ei`|il( zeu+*MlD30&TPrE^&s#h3VPy=-eLqFyOJJ0IGQ#hf^qaEod&1l0H>KCw`t`zYO+%If z8m6x|yI}lfzZXeQQMpZK-M;iB|JH|b7(0_zU0=t7tQV%Krrxm^!cs@nsM-YtMMY7N z`y4QQ^hAla(Vj`|RN=KG4X({(;Sg;&JlWw+)fcuuf6Rfd7VHC^}F!Li%i> zqrK)-&3O2&c7Alm6OB#FbmSUYy+Yf|rZj@x6aMd`w?^nHAc1Tk+>pTY`=?rUV7bY~ zS9@F!Y80*)kXh{GPfJ7Mf5Lyuxk^QU z0ivirBto4bNQxcDoFMrQl{u_p6dw%S70dU^FVY-p@#PxC^-hB&IZ@Z}NR*$*=P0XY zIpyN>_=nk*`LzLW9@ifvnb>l+M=^iA)WRmBep{i*Uh`SiVwlZR7aQ*38yFoHdWQ9H zr7iApk$RFeuXbo5hEHs=%E$Pvl^&JLv6UNzMBfr zTo&BPZVZFwZW=xs*Qd$MVT_0DWFMX6lIamW0xQ^15MTmDu(5#tY%JGr1uro4-4B_v zeKr@q@}^N3C-qIE?}&d^pmHE{Ewh5}!jF@?{s_UjKis3l<_-3EV$)L(e%SoC2T^BK zp65N$N-4E?J1Y4xREHV)-)|?dr3Yy{#elt+jIJ99$kW$DSXfV77W?g(OtB0b{l{y@ z)<7P6Z+`kS=7{ffSWOq`>g;KV{)4si*N3D*yl3p9(90P1HT%Ei!M=I>Dc4Qt3AtdW z7SMrVE^)72h7#&hCkx?`czP#qSQ(_rUe)-$+q6CLf>V>hNI>hb@JF2Mj17s?`0Grq zni=Kkfq)Z_vn@qbg7!nk@)O(tmdf?qy=bnUIr_qiUkEmJbwxLb6cO)Fi>ZTh>zMAD z*BQ}pJR{VS>Z&Z|W8}rtmYTVSOF8RHf17@fF0Fk&B3HAQ(+G4l+K| zh<8%?&T!3#VM_BfjgMPMQ(p_6iWE&M=nVY{nFR5;UJF42;on&zqo01UPp3 zN=9SKaAixx+^7->wbW@W8f}riq2a(P%G0ISvsYcqEIQZ2KJ$4Q@ODh&_*J;@bUdOi z-6>U+Ct!|7fb7DN-R6MW0b?`x-;N9I!}`oj3jXY142_~datGZ&9V~?V;e`#bnVd46 zHptU7FH;Jw(5%JG^~VYNhPlJJZdgSaB6NM!i@|_YPF8jw&}EmiD27- zmiIr`7kIEqa9liju>$tODGWnuqxA5i{-^u=du&W1L*Ui}j=7&zmZ6W8AQsHhe?4$@ zB|T#+@I%*Fkjdgg|Jun6IGE((t8tUurC!L?S*R|#LPz|Kp6Pway~KLL+A6w^io_U1 z`Frou{d?m|w}iw_w`0FA36DB9NQ0iFLITV`&JT$0U9hJG^nVMyul%f&MDCz?pq#NX zWCQ{u6+Kv3er;>_NFFh_lmbDV@N7Eq>@8h`WqzT9m?BM33ip?90Yom%&zi}2gh>^y zqVTWV-G97GRE%aHpPXLOP{tN-;#)3*+&)yJai#9*nRyB2iI$O%&fn@9z?j4-vmXsc}T-l<#MVY)l6Njaic#uW~IoX+dYKb2U1$yp&D||ZX%6n}8?H|R33YY_Nz2KY|wU<>YNQ3#u zwyaYVPbBYb6EK&0^^F*YYpTGm(Qc0f*(fU+e6xsRtUJ-%r)+ZT+2@7{0f2k9ti9N36^`8ahcNNcG1=>A1$NSXW)DdxtQb_;JzgjxR zqrBS?jy+}05Uu##$&CZQg>@7 z?JgTRWVL&i9PTcxHfLg#^=c1pn@9T3aceTQ8bcE;wq z-WE64&=A~c2{2&R24Ea>FmwDtrJT}MC1qA+H7)>rk!>2kvkNKFEP3dI394;7Xy`2l zZ!AmTmGjI2knhGjS~-aN|A)=P%^IIs$XOx0j=YvRpve}uRhNtJf2dI;XTD?$gOMsA z^oFpUjB6aP5vkQ`g!E0T6?@Vac}8yiuu zCRur?QwoV0b0*^Ff&(Y}l9c06S@W4GcAb)_)3ld~2Z?LtjUY)DY6mO+zs72*>F@>y z0ru0@c0&VN7ugSH zGNf4GsX=+-KT&18k0&za@jFELq|>XWgEujK;hD=!LBpO>Vl%O@doma1c`KnY_ye9e z+S9FKZ25B4EBbK+Y=K(J@~5&>rKjAt?On16WTfzDE~T(zLy_ z;|+oADp41{G=0r8dn}{3mMi<}uHDh%pcU_owKi#K<>Z>pdUt>SXF{lKenYNM10(Gy zfZ;&yqSl-Elvun!WLF;VPLb}8;xvliYZBtCw zCnn#M2i&TTmT23g7-DS*+SdH&a1TAWQJwi}f`dM3$T-SIWT=@jbG4pQ_p59C2@2&2 zd!MFjLYvDgG5cI;d+Yu)mX+fjUg3VVQZux$=~~FWAe_x*rF6S$#>(X%YXXcgOx@6! ztE9c+Gxrk8LxBsA#98VMH^D1vSMY-DE zs%|TUu0iJlE6m>GbN3gxd7C`JM9^Ht9Q#}qD#?CZxo}hVRbS4?Su~ZZz8CQZf`wU3 z1vgrY%O~63=|lhpJNACe6Dn4!MD;FNHP(%sECu86BHo{S733eEq3m-gIR=S#q4c0z zmfxnIK2d;=!qbmMvjb>S@knM}2_7DdkjVAfsCcov*P&5VGi50`=Xwf)xtGuBnW{~~ zO;W)^IGa;U5;Dr3H5NU@2d#wGr9avkG= zc7FMlAAN4Bm#2tO6kTlfJ?0g*<~&y7mx zpo|RWS96Nrwe3AT1KO_cChvDd@_xIn=yAf;^u8JV5xG+4kmwrj!q=-?i2N?%hKjKh zYEKe*UoEYf!&dS&tQ?JIsV#6EurfSRX33?d)&LM!Q6}vNee! z%!5ghAZ{<1HEsV)d}liidnwnNr56(j$-6Gq#RN>D7{;FnT+AsgogYf=M%^5>)|G!M zqk?nIGZ+zleUE508WgXd*(jLl4Y|XmuTX;>pL=5P-CwRw|C|%DFA!#H32hZSQw$_) z?P!x>fc!n3(@Ig6rcf2>ueW8pPPqI5NP0o39XiB-JIYUJX}0EdH(sY!uye6>>1Y8& z=4Wo`ZIg-Duri7zT^;8C&4**jh9g2QwByOvekc@YD5oT49Ff}eNU<-Sl+ky7lVyNY z%`OBIb38Y%MEBgN~!ma ztb~gbF`KL1cpZ?EzE5ok^Na55PA;l?il0P10zldkYupmq7*E;yA(oZna0t z>ly3m6SRc}Vtu8gV?*$uq|fe`z3R-Eq1VoZjMixi+`2Wl`di zW21O;@$RS^;>pJ=D9uJs3Smx*tPRi1E=Qk+{<|MVW6M8IZhUl?0yDfVGLwl`&VXu4 ze|9RlccXbme^`f$ZjZxMc%RkF7`SuVZzxR{2?LgG1nB58J;BoxDJjd!$0|(x2_4`IXb>g?~jf1}Dxfc@pW!u7!f3cq>Mkie0-AHyH@Mwh!UERJe1IPGjO+0#A_Q-|1I7_oUm^Z6=TI#b~r{>SIS_++N>!} z0AFSMChuR5_4^(iq|Enc&W(2g_${eAHmh3o-B zv2{`w-sFC-l;V;@6w4z6W*1)HhY^To0Z3Mz;?9Hx@TbT1zSt;Dhep#G7r{N zpj;YoAUATl%?_$;NJ>WYp9*TF@(A?&bWg&taf^5{=_SA zki^quYL?A!>HH^fQgWJ)FP0R;>?gjo6TeJ@;7`IiT0=7ND6-yr zpea68Ulzp~9ZnTDKo_K56P04-2yX-%I_5fQOPRYpsk9&llc$g}A$QMUHJ1JBPso#- zz2y@WzEtoiE01(Wv6bm+b^x98E@~IDksN;Hpb+W1P>%Hq^gW-Az&pR{+}Tt+;eD!? zl;`>BiMX~(mof&2lHz^nWKnU%;^YHW`(Pc?a3_2ZW7N#Hx9HFs}^RM>jZeN2>g=$ zCMr%Gz!LrumTMX4>K!S&ty&>#>LtH^)=}4#`d1C#%jVfB|L93}kgua^h6m|2cJokZ)pr_#Af}PixcqUU#`HO_0mSvwD@b1E=C2iN%2loc(z zDL_c6o@2R==9 zBglA<@O;`LW$7!kwNg7nM&S(1wypWaj98ml+J@FGH-@hZ{V| zGu70m{|yo@7hwJmH~f?m*Njey%Ql}=0QD;Dj}g$BxkUZ}%v47vvBxfKZ^!nyVDRkE zq?)QA?hv;{C9~^;YtZBk?2aFOpJ%)AAgWH;=`OeF6>EM~SM(uc<`nz#^quVj*M0MQ zA&GLxaX`xv=Ar|qWK&bC2+sS}rJLqYuirCT)QJI*?67kJZj=H*o1;B>R4uciDBDHjL zcz;k1p}yD4_S21vg$oSDWbq}PJzj!tTDqTT*TSA!T7wT>@*b60pDK@CnB`hAJxjfA zIHf4jkEt{B8hma*u*1|ZJKo4g)Qz%Diw4!`jPHa#+zY(2n^b2~t>K)s%KWf=-i#kt zek10bFFsH(`MJd3t}%ryx7?B@?|@4Avi>E8q`PC!i*{@sbs#)X-dhD0qP#^w6J~NU z%#Io?+!G@o5q$2CLrDi3t?JQ=rRvw`H+kyY9_ZYeOiYDdnwd=(hcF|oUNu?HZ?#A@ zbyt;N_&lC+8b90=By)rdVs}&%%EeEI(E#VsneG1|0?t?!)ptqc?4K5mEAISacZb&nhab%4O zGpA4EnzmwX>2S_NNjRuwDW};2B6rwCH`v4m-QxC_}sdM#aNdO#xRi5coe7gxqT5_)keC|vX7o_<VCYCIbb8N)@N6gylmPo4>GZlo|- zmQ51=9-dBmP?0%SZ@?~UB*wwCJRMGc>?}0-W}_Q1pfoE>L(S!R)M?>_YbwPof4J+v zYvxJg9FA_t+6XFQJ+89#$Rw+Zx->aSaC3MkS+JcaBr6BUqz7!PM&V) zy6)ZolUuhBf1_(|YNT8_q0SXqEkHrd|N zsr_Htau-~@#~co!CGp5?qAOBT(((LVgn-Rgw9sD&wSs%{d@{lCWXOwqr@ng3i9tkR zF`8E5^$l`~=wW$X1|_<>_GnIWpNkBp4nR{cQ4gQl2^O(1Yp@?pP6z!E_6}hWD?Q41 z*sFISPmB*jr<_@#huFoWtDn0u6!qV02z*P5>gJ;u@?3)lf21CMkUrOdDL6({v$xj* zZRKMGhFUh^YwS#4y47=@^gCE70^7>bguLHDdEf{78XW(~ux$vgj8S9+SvR|;} zi$k9NF0rj?r_j&2{)+b5kI2T~@XS#=@0gXbedBwsuV$TnXug-%O2W^^^Gq~Y1?YV3 zfHupb=+2DYnjQW%oW?jUwQa9SOUNvft|9~Y?G?#QINOAg9AB2SaOh)DjUFN}`fG-L1o7=k*3&4%#$i3s)HA73^F5Tu4dIJsOx;0p)P#%i=@%b{G zfXZm_QZlFfi)>JDK{yWcy~`uX4ZV@40GaO1-c-^#eMT2~i8+Z|Ki*!N$Cg5bNS zsI~V4;SbSz)t>#BBg&{10c^`3AI<4N1dXl&P82hg>myzh8OB6*&>jLJF_au8Qh+>S z&u5fW75HK{83u&yGede2++#GWb|X^jRKo6Z&Al>N@G72@xD{LBO(#xHPyMx6e^6|- zh#`4i3axn3``?6Q_0Q!A#RD5?;)zGc6=TI-x(7dy|CdhF9flMK-d^Z;BJV)i4}*j> z?<6#*$`4mPvCsZy5F7-`7HA|~swzc{aNtdGU39HD{_rcHollLf*K+xs<@JHujs{|^ zmfbAE%H&Y3okOK%fi>b}xEu8a5olDha~~5pIx@7s%dMc zJhZG~P}KDy2F3E;Km}%EvEj=z9W1fpWKERo3)*YC#zT{vU)>1WCG;Aw4BnD65L5n| zsx3SONNqaJ`%g<`T1Wh;KM77^()o19+CNb_@%{SVu~^7lc)ozKt-_@YGwh&DTJj*r zp2{QFr(Q>cW!-OQDJ6&%M|93?_eB2k?#IyuUa658!TxOCxX++hpyRze`r?SnMU43T zI={qe)2ou69iI@rXqBF&drtNuGgA4)^iic>2&WY%uv%z{ju6Sdxbjp?dWn^VConOX z(x%|WfA+<1Ev{SReBA>iqOKbU4RE79@zs^L_}>KhbcUo&K;&Wz{z0XT##Y&{v`sD8 zX{=7;pH~bQr=}xKgr=cQrIRW^Cy;DO-A<7(z0W))qZd-?xP&h5eQh($I0!Xet+%PH zk(t$Q5~n&IUEcTgXRL;kVDZKShq9mi4es}|{=-NZa<`Lfd|C-5HWDosV%60rt{N=C zn-?x>ZgPv1`@4S0QFoPp^qCF7(>opp6EQJv5vBynT-@XSO!0BrtfJ}TfhTd*&dR@Z z64WCWi|KjJo0GD>B+Db;k50m>DFldo5x_1wne{G`TULXno|5qgDQKq>Z0(!g1osMP z-IL<;KR?0)cU(!=Ji5~tJ|HQ~I=$aDbWym=s@YvBXbM3|W4Dabm5Ww9*}{V^N|g)m zN~0#!O&poN<_R9Jv_6=nDX);iVH>ET?Hi6dWrF|L7sqss%|n^NTo}7L8$$~Hrn@ru zkOA9PzT3Y7aA|AYKeu=U7}&8N@yLLdT&ipKj4Z(lqcwW+I5u~YTyD-4x8O~jcj(<(mkow|N1 zpge0>=Bn=6}%5dj8#*~9j)vALKNv)lkO2K@% zR~w}*`a;T#gbDlO$*B0;#{}#rll%C2PNl$~&BPW9-)SQNH2c!CAvSRa<=JWlZf@;-+1j;!tW%b_Gk zQFUbg5W6R#9%njav<=ofbVV(YYl|7%ln8|;F1)A=X`?JX8@ts6Wj$G>U1}!Gpjwk( zutErC+~yCTNdc6@y_{g-kN0gzeNQI{0;XW(=HJ@cc_y>g8C46u!$+xfw7#BM<4gL5 zqNDXE^kyC=GMACMVe5<*k20Eyymu?`Iub)8!Wu0(%HxZ+5a1@V(WxlBR-MgTn!o8+ zQ>j3sO}9#>wSE8Ub|=CzcZ3#wjWxe#H2cNXheeQmucp5fz3*^Kwc1Cr{(+ZCWl+3a}#snW?&U2 zn$o2sg5}cn5Fvk1mJqPhdD}K-Vp)A#2dHeiBSAwG%9kFZlE*!hNqzNH{@<>fJw2tk zv`Ckwx7p_`UWC-;!|1y5P)`wU(&Zv_@|`~2$o^g!=s5mgN6TG*LRfXEs%c`hex2OC zkWDGkDtpO#`KgEUW_;uy{qtX(g^L7(``T~=w56XypU3qQg-u3SU31C#x}Ik;|2oTS zeoH~mGd(Ck*yziae~Zj#7E@fD95VDJf*|5i`1GqxF4ByTQ&%DWyr+8pceU`>|cRGqSYE z4k4tHTEvcGPJg)6cN8}5Ol=-jbBAbRX6+?Uxw5x}@(bzh?kZ=?Pi2!Rh#h57Uo%Sg z8Lo$Zt_9yxlAu24Q4kJ==XzMucEsiQsW2}{$D}q>CVYj6%PxmI@D^s3{l5JJD__;> zJ5cZBy-iq~snJb`C5|CMs(g5W-#yz)FlQ#`&XF*^R`5|;v$p$Uc{LXScV@j8&oBXw zr9qG83_(oGxEKBiH*7?RZQEoFlQYgySjdW2$J5^EJ|W|`fhl}4Ten0luhJr9>10dp zAqJf=Jx^A=9Aoz(X6D0^IUqBf4Kpu)veoMv2z+`Vc(_MZl9MRv2nsyUh6;IqcDrRf zkWGIF6bKdq7;ckkcGp9jo3oscO}+On9bME4sLyUGyL5uWK-AUC)s6K%UbNoL(wnAt z9oSMvQ*4V>r5(}K_W@4 zpTTp%VLwmH;M`Fb2WfY}M>@`{Or1KZj9H9EXi|a@eeq<8rdK29xS#O6=~UXp8yBWT z=k;Cq0+-gY3R@QKQ_bxti>h_04Vy%W!L~AWp(0CyOsgE|Y(ix;8Jqrb)x5fFq6=EE zGk9nBvI^tTn+>R}KR*W}Q#W!C4vzm%Kk{@__!NyAuD(DCCO-N3o%FN?qR2@6t332+ z^+tE{jbFMbg}!5l8(NHb~XC1mSJEti^<$wCt#NV8}f$ z_#R2)y599Ui@jWYkq#}y@ot*~_dD9f-)v!%h^FqcJbToC{2`2Jm5)$?kpOou|)^K*}11@cqspIuxU1w#k|9)dvKxi^0g-A>(5&X*eVdo=Ar@JNuSCZVbSFNK5|`1ADb zi$-XKS-GjeCZM{* z7(};uYPo2r?rNpTY9bSC6j;1)!n5%O>sjaDR_v2@R6^-T2QUJC<3E-9o6W7elfM?y zCJMEmBoZ;NGkuBQV!@;sSZwBh`4M5x#G?~LPTzqNAWLtA+-R*F5*H#%jv%>!G#?L} z*5fY*%2?z#e}r?g$*{#`^ScM-d zT+H#1vuCB*R_e#P3Sy)<{u|uz`_2L%T4=15Y{ng}=)G&Am`jUq!yEEF(vna?C569w z=I5%lE|PQDkNnMWErUGXEeRs8K2Djf55T|3?!FsahoVehGXndrQDWCGsE76zk;N&& zG!z_}Q_X&UrEF^w*m!6rDz%5ZB`Nk~3YI_2Kc_!!&+|Ia3J8s4 zjT0w6ZcSb2N{qYH7TwjMX@DI?j1$pt3m!-*@^cWFd=Ul7cUy zp+M8QsptR4(piQz`Tt*h14NKcX;4}kNr90P(xB1}Qqnn^FA~z-5>nD#(jg5agwYM7 zYcym0=kNMI+2ifH?|tID&v~6O0N6oZo5K7i$;8x%AO zqm|dCKkvV88*pkM-^q5|a6#=FK27ZgH~RO!Vm5g|WZj9Ng>Pjn5p6?&p8MQ^=wFS$ z-oDCY_JH_e0=v`7U~+@S#Z6p_W1NC}B;yFG5Hlabe$^(sD#26wZ^l{7`pWoTyc3QYXsfTP?ysGI)3`k zS~OpyM0w?m8H`__y9DOJA`QD4y0Rff08T8Nxacf#SZrc258{1z@7| zVM4F#{)7wv2lMAxoE|fGMw1;UchP}-zh%JO1L>-nFLvXlm5ma`yP<(w8C>1cf+qtm z7?pzpb6+`oUj<2pzJX;atkwp{4ds#_*{ef-~XLOW+-`<2c< zjZlKGn4YR){=TNOOPa7{U=!XgI!X~v974hm1%2~GpQ5W3jJ>}Ft9T9#(w1cZO+G&p zP#sSw_~=!{1CdOcTl(m&UZoCq!sibUT3=);Cf=JmNV;;(`20F$pf(}@s_4r?@au2h z+X~S{`9aa&pU}m*xjkEMR%8*o^*m?C<4fLG2NX-k+(5HfRx;)-$>2ohIcu}()8m;% zsvY;e^Qa2hfG^`ZZ|UCC*{>rv5!7zpWRQ#Lbl-QmMNSoh#g#5sQbCQE(43uy&KEW= zWNmpmP%2*Ohp%}4(D~P=(}ViLHKy7gTSV1SMpgez&IMxDF;-b_wshjHr13X|G5{98ghlrC@b4QeNCzq z_*+`0#5DJQZ>Ge%@xTOSQxO7BxGeff7!=vdFZ0PuDOUmK__qf7Ns{>vHbeqS+ z*aPf<*b~k)zoX!xzP0k696ehhI(wr%zaDAK(3iCDKmLU*X7ShjjCnz$2ue|UzvJOf zo@;Y4uazSxTNfbNg)OzE?nq-0I`j{?hh$**G++a*Iw0b~=c3m##qM|QXJQ|x7&SM`i9ViPThZ%1~2ZnU+h z7E{!qY!B-j7Kr;K_45T-?$&USFMT44cW8HD2RMwFmqJ@U&038lItUT=0-Rjuj z4_!Rq0cf7v^j1xwpKk8(>2Bng*TGHsHc|Qktkt-613#}qsk8XECqL%Vu$yar#|WAe zV-}X+YE5`=fak8bRpkNXm9SW(T>EH!^}!Co@L&hT8SbY1iyU#`o=QdT8e2R5((8`J z6i}qACw=eWHW3(6Fvb5JY5DBvf=uy;0_YtD?3=L^HR>+>o?SB$Ov8z@*W1fu5}vlf z=zC7KhT2Pry%+(iZLuBCurXMs^yl3iX6?&Wix)G|yPy!ji~!kIwt`imAKItSL+vHpab>ay&oASVk`UAdLvOS8cBCZaYR*ZkLZO?P@NWkPIZ5=K)1H! zgW0wXWp)lc7O6xUnC1v93;wp-YQ8r|(^~;eNG2Uh&vjUyT}$N(w&w*$Wx_uMI8Jrm zbQ~GPef~UkL}f3oPtO@p*?@U#Ts;1)gGwd>1uc5_V!hiH4?v@P;XzU_#yX^tltZJe zOZz%vNvP^`1D*^ySMZcyZR7WjJ-1ROyR5X26asx_!)!LyoOK-ocRbF#ECXc>Is~kg zn6f&$S5*uKGOZR&zu&vTw=lCVCt02P0SpY^N=vh5q~6TWVU|-qxlT0RO=2{KY1xOp zdd?t1TbLFZXChy#c)Q~;`06UM6z2FaSy${;|U1PMQpR) z&hiA}pN1dNL@w0O5SH-4NEMZS7u%v`_YF9GY3Cxt&H+qr3Xi5w;r0ri@{HTZg55UOfsrNVby$fP>9>1;v%c`G<5m}ExawY+IPs)(1; zXM0}(gd~)PjgZ4+z$V;7q=yn6bh%|0wy|$ zK25bP;RA;5Gkq-N=5-1v`BE80JYo}X zGSO@5sdX;iUCa8an!{}5 z0iE|FKZ`tv1rrjh8h3yOBm*wkk%Le7Cf>Jun%9Vesy}-G*~f&s|I%_MuhCHXm-a zBoJ?rAf1o5D<_r_+*iszw!Su{p@3@)2^|)|lAHE3Y*(QZzpz$tCkxw_sr1rmVV=PY z8>g{P!v*yPe603%w(47xxOTSNH$pP|&sg54-B0^W{CwrU#HVMhc%MCC=?W<<9Hufp z0)G7X5{!8$vhSP6f@Uw<3>#3%wC}9#$5MHOy#d!!qHF=XB%3;5g+jH1IrgPzK4>pJYPbZ$abE-^Qm{&ea{$ zUP~6cj150w9(gj-N%;J;6jPyi$308UtatDD$?;__n3{5l2#5X~bhV5-F-ismvG^`F z9#MmGnKwC%Mx~Szlw}{X=W({h|Ja^Lr?wXlV~jtj9}n-bWg76OimEnvwyQtMNCA}u z7hVqzJp7`KOVBBHr8?hZJ+Vy-Dig=ofOTF}6#92mGAJTQ6GJx|Q&Z>$ps1*YBfM0|)fBs6VgDa9D3KTLwK= z4!}4}3qy4~dAbBU{R{8ibUo2|o%*Nkg;QmOqM5e59vvQvZ=YUj<2?K5>xiis*w~84Gwl6t66QcGdis|@oWDi2|XMa-(G$=y1n^@+D*gL(kq|vmjz62$=}T`*`N?? z8IAvG$QA|9iPXpp9oE}^RaU%6oJ{ne5|_yA%GT#vR1V`Amd{&;PAJ0HXNc(taI=6jj{O14TLV)>#luBz zb4MR0Y%?z?H-pluO|}kEx!5!l{}>pL@vg?6ojWU~-P-rnkwVgY^}4cV&J~3Z9}XWw}E_ zRUhI>6ErtJ#wO1(Dgf@2ofDN1Ve5g_H|IJDWohNM7s>jZU=JD$0bp2O=xHQ38$h6>=3Ma4EH!bTjTOqb0Zys2r_8K z7AZ*L5bg~Rl#|&Ff>PVcQe#h+eAPHoa~y%(;gjYLk_4rv@mDn;<0Nvd01=>qh@!)~ zPDAaZ?nL1;G{ByX%R|PlnSsUG#ZSu9&kqt?i*K}Sp$`c$tTvccS&v)B3ae|YcY&%D z_7LSlopY+}0${(v2_>NwvF zPId0bm24ccEod*8xmwROdAF$1J7y->H$)&O;dHn&eyR_cY)3H`Q+O!=fQ^3Awxo&K ziYS5PY}G@$`c05gfx+}ow+gp=;q<%c>%dpxPdc3n?^wDAEJE*;19o8Fzh7quQ27_R z{EYI*fq*C!lPpF4qv|jcrXMUN3jS8X%pID(>>Wu4VFDJu3XN+R-}f3(hR;I z9)OMrWg^Q8N;yU43Hekl@)x?h!yZMZk%&Xa7nlMn)sGb<}=ms1@g z=?(N>1;RHeBEZ{|>Qx0~M%B8VjE;pf&W{GdPk3C7$flfQ-)1md`Eh=O6t;tQd{(SH z^?1vVnZOex3%C${7BEBtitbhZ&LOnjR5b;^TFBv-#gPD<-U zIBWcC$0dNKm4(YHR@`~HWfQ>;%cgc;ZY!Fhyfno@0Y8Gh7eLj#W97R zYVtqhp*pn|H;n zFz(6565(r`z)wLN{tI*DfS&zdQocEVOvCPh9wse76e-4&@gBiIhO}diLp;ku! zj(D7Utq~QL+`B4e)VrAp4k>gm>?xl1|``W^Rd_LcJQ_6pSxV65;Cxw?84SjI5@kt(lPj#Q)Q}OOQ7&to?Pt$vzI;_u~r& zU`J2}`xVdO(eVMSVKDEr^;eyUO^)SuFbR)I*8qi#P&P@b27fAvQBX1)gk)W+O@?}B zbA;@BQ<^5#eJb>wd@>E=bo>NYiP(y={7nOSSPx7QdnHMCm&2qyA~wH}a_v%7{l)NL zZ{NNs14|DhS1)s;w0+)H*{NsS03oU=)Gnd}Bc)UVk6|)a?Yw}?Hulm#t~zklDUVJy zuNDIRiH4!@-}e~=I5+XZ<*wUSUVt7kiC78pm_W~g__h__#ZbX>rJ@}=vABphP3S%7 zl>tbxI)29*#>{jg!h)0#(h^WrYVa=Jo%mLZ@v=2_?9@v zH4rg+jE}nF_deayKatP}B8Wi9TSmk%{gaE}nK|ca$bl;x)T)(uoKGjR2lvGlZl~+l z_tT1K%7*7@>p{$M0(3Duj^C06I0qu~`F`5moxo|jCt4SB?@PRO-90NoR zaUW+071Qn-y^9UQq?{ZLvtS@-f-}qg4pN#hYg^ga|KFIW`g~S+ZGEBs<^1V@yGI6n zVh<5K1GZk4kS*zzPlY_bE#u6iR^$v#td_u^ccz>A>99NERwV6X{Q;mDeFk1}&iM-{ z#HcDn0GBO{dYdTMW#Qv@YF~z6aBjH#S-b}}=gjSl81}%{Vd&^U*~gM4W2Qit-}(v+ z#5P)Pk&1nw@%J%j@d{!BdC{X-d74q^_vmr@CT#M~n6rE6oj_ z#&`)nporl_dN{{4#hsW8n76?|w^yd-mxZ8V7cGh8zmTH%u;d|^NJ?kRR|v{(Y$6e{ zuI@gCxbr_RO*TiXJ9}qJAShvv2>rm}84;E?V422Sy;VBVFCz9e#!wOnD#%&#&8k7! zc#fF$`nrSvDZ!n=Rp!hKCamk6_ongKEg-aWOW`map|<1;PQWK+@2a>KaF)HEu~R)bSu?Vw21Qo6ti`fT-32q@u(9kLhTsFNNj)#9&qxw9k03tPKcykx7({6s~di&QV(F}sPD?^;e zPgCia02y6X=E4dom$ksqZtOS2s?f#SZnANlKM(zDGreTKh?Okun|iteBF_k( z7;;s~Mi}DBkQ$`QNTY~XzTfH-HfV4Eoa51ZYpo|hhKU(w!&H^gzl_{rizq^)tH=Q-0cvY0mdl)oz^G5$7U|5{O)qiHb;qAqxL@>5~KSdzxd0YFtl!KAW;qEbB@1q;`-aJD$*1b%b z!SY%h4eDgvdU-k(?d~sb7_bBD6#ORKSt$aTq1!+wMV#fHs7`E#Ks%8+u*0UZFe1Z; zHuuIQHL8c;d))pbC&dO>ql^lz=6v9YvFs*ezs8;jp>qP|oRxYY@d!z; z0L$E%M=?_hU>N`t|9La74t)92KpcFJYZ3_O>O>qEi2GdP!qS$Qh|PCG(ArYUpMFN5 zIj;{J+WwkDzAb1qYZ>vV*!fiV6DWwj)C0SVVEPoVo?Q#dr%jtw2j5f(T8dK5CDVDc z+geqkS2}gTaOAaCT}Ixz{rKE8LtTQIdbz7Vu|l!E?csu<;oj#P$7j$tj);@8zD24cAq`XBl-7dLW@ z?@p$(mEZ9=bf-!~9w4^8l1YBMG~dqYVXG>vq`msepYh9^g(%m6Fxbo6>m`HR>Zhx& z4I4M~r*lc3IVc%*{R zO6-rO*}bx|bp;qzdS;;-z7uKu9U)V{(9G&oGln3oew?paoXmk!<~t6K-)!2AcR!qc z%79TMsGY#dr&6<0DlBRUwge)Bw}92eogJmFJchj5XWD66+7$!1-%tU23uaf z{%9PR03^-si4W#);MaO~z5YQ&BoswJk-6r+xmB*_?JAXl6D#xnH(EsJ(2@|pTRP3m z?43=OsiTo%GQFDrKhMp;)vml%X`FceDUObH&LEbV$cidOPnoYJh|9$=;FliXWX{u8iw0g!Q zxxsf!W7X?g{|)u?RyI(5f~cpEh1%w;r!&PR1_cs=*Aui@_|-v_gwgcwg9<}y1Tlu2 zaK0{bFx{-#{>#PFMEYI+yb&k;dJGMy=G46-5Rj;B_;Q+tB(;`vaFu#ZtCq%WF}=}q zDPYE%-L7D*3eS5iSJLt;`LC&&1TNO8MQeO63FE&$x$Inmr$*?2 zQ)ASuZOwI`-Fpt6Svv!NgMNsS@YQ;}cgI0vz{HVo2v2S_z>zXYqFZKeFt=c>ay*+u z!?mI#V9Gl4x!-V$iOiLt{?&btBx_DhZ{gkA1-_k<#s1@L?6b4$9k0}cYKIfoqfBe< zHx%T@sZ~KojSfI-S8j`F=MV^{|26;f_2VUNzl-6LvsDYp*js`pa6Hs8qi%WYE1xhRn)rVB&=#+%U#rJ z&HJ22vW}1HJ*Xsza>eYa<9&M(d=c3IP)2*U{}}+ekjK!~RL0hDeE2d90>NDAFK~pX zIE@B}2K8Yo2_eHDa^E`xOxNWlXMS*83i@`su%rWKI%}UFB33Yd9F`I#CylN!b z$}l8k;@>e+`QKT&>+Z;Hxzqpopwnt0MmY2HXxS@Ltt#@5A*~jdlVo9TB{JE;+^K3` zj)&WI*DEU}Ywk7x!VKNPm*27R4crwte0{J$ybX_A(XS$CYpW0$y^ZF=0Is>xXGp~MgrlD3r2{N2d7^w;E9E4sQMk7Ttj0UM0s;*#dU>Ho_3z=S;qbNXhLQ8W zEs>3#QRr=W;H^URgG8}QT-T%e6HympmM3Ujii{j*wUvf4K4qAFu43ivN(2Btph6eU z9aq(HJl`W+;mEeZCGtxqD-;nHVm5iX>RQE?K0Wj1sp(B>a#& zY5#&Xs2SX+n3t>S79F-0lgXxdW67@ZzLSCw-O8-p*Jx*qUEiO2~of>kZoKy1-4;ZAr6u9Y08O8g$!geOE3mZwOJ6q7qxKs!t~1@)RL>nSEzO|a`;OQo(3d3F@1FXf zHq-8RMb-A0?ooBH&)pCUASI}6imP-wtor!3_`SEp)zln_%r&1>^&ert3WGbjW z+V!rz*!Mg))k1bABQ^R1o=P_Lgxgse)X9G0@LeNp(KjKOi6BI^P@N{ZKOKz<0?z27NZA%yYEpaFpg-H~_Q9^VYpE}dsYe%8 zUB>H`fada9HYQ=wb{@_CC8~e$HMdUTO{wvL<2XDWUR3({d&VxSfqHQ%xY3cRQqk1@ znZd6DS$POGs)1YW-PH}#7iu<-bGRQtP^w>f=b^hsGcaFJiMwzcx)ivHO+d1DO zu`8UrvA2gsA8-Z~QlLg`s0U{D`j>J8u*Qaz^!5S$sjRYc&c`0cd;`m$eL{0tQ;pTI zSGljdDv z%%&_idZpYfVACvX6I8gZie2n-4+o{3nFtZYCa#k~I49-b_hY1r==`gE~RbcUBEo>^Ew|Pm5xyev>rh%OW1Q^AVv{zma*38L__#fBURgHgz^YxWXd~ zW0nKcY*1C{C>90Zv|D!SF$I)dhh|8~q`jQ3Dj0BDukqMRw0 zQwk#YfwUV6lJ_E0f2;y<_vsFPYJ}@1uMo~Gz=KGE8a9mgsqNO{Wo+bq-{LX?VAd7k zJ!$W@=Xb?QmXPID<%9g5xKML5 zHd&-y?~FeMRv{Xm$HFI~)5a1BfRlT5`342vUa{Vyu8U{e`)-EDj!D7$i`k4RolP5R z?g^MbSfG3O9$e7yB6l~@V;V|XkI7-v2;7WUaOsSf^ACNv*&GSnRjmPu-U@RCF-lQ3 z>tPi;v;{CgjREFj+u@y$Vi*f;%&>fl8{YU2w|WdPjfT=Of#`HM4g*K!jGa3QEcDzn zsb|9KjRug>aUJ5=?_4=(Vo-t*HO_@BAYW*do}n{u6$+KcJv@SLUV)!v-PvC-pgUn3 zGG@GdACTm()rEj*%_F}hZvnU!aGseq)1DvPa-s6zA?lKksQZK)2_&bBb0DzLkCYNa zj?zm75LBmbNWB76Oy>|lRbD_?b40J@|4a5`NE1y9<|9K3u|^Zh(? zDiH5hSzCD+xxCOcaOSp2n|^8spY)ZV!5cSjFOYUPPq{KWy%ThxnTE>tBzfhdKle3q z-1d6*5#jbf`8l*s8~zg;ciiArSN$sNO4q8AaIbNRBlTQwDVL)}zsntvAGfGyQ^Vq3 zK3!gLJ=X>oov~GkjGGf4S8UwX8I57EWgwGvQ1hW?UGuZDDbH5W^0tzx%b3phj&DE! zQ;8-XIIBbJ=DvHTFg*SpV74up`f5u8bB`-*udDs7=#Jx!+I6xPMJp;V6WxlIW!|r8hm#Ax9VeW- zHwOoB7N#wp#+2VwT_aJ!Q^8-e0)U<83V8S`yn^7`E)IX zNZ7n4{YuayXnm@0@V2e%;rZ8Qv42&iCXeG?y%3b9a(iZzR7K*BZzI0{irPvA`)6$p zuYPI&Xn?(asTU|+k^|g6Q9c@0MHkb6;nO(qL=)|n(U~j~M+3uO<}+IJ;(M<;xv+ua z>1E627RUF+$({@BzL8c!%A^y{H+~nW;*(Q(m0RJ#zV);uj7mD6f{;jP?k2ptYoJ`G zx@JjzsP&JOg6!+4*Um$tB%9)ILa80zb$F1_2oSh5{-36~LU#rtvUgl*piZ2GElkjhR&Um0W@Ce`= zR!x%zB-U#W9nQ@T0foQv-)`5?vx*0LL|1opd=I=GR6qMiNMC@zoOirY)N#g-m^wH3 zs8=IDdPBuyk0^hv1kURFFV`H8t5@4V!YGI($O4id*`U0bAH;_pJ0pC2Q){apSGEN8 z8u~NqXjQ`-RyR($hLx=f%XW5#^ z_^yh=vUdgAxF7%bie=vfoUOLC#!pHN>(^prT786jHN11LzV_*B7c=eS_yvw`tQ`VX z0I?Ef8bQl#j+_81<2hVPmbZ~IP;XWY)yC6pib_27H~5*tHNqfdhE?(~q$sh3{f~r| z4icr-sfxmaNc_1W5-1w5PN&PFU3+~Yp-m)4i39pX$`gnCclQC2Kzc?N04&Ww-20Yr zOJEa$Kr4Y+-!?y8udPFndph%eEoR+BU`yF&Ya{F;~E)ze*Db%ExV6I5id7htP@}SSf6Ok#s-0JqkxH_+%OJU zd^DWB`Z-mXTe?iQ$g^neevxBmPtUM6uT$o&CsaJ?VAkHy(QBA@70rMs8&AyP0?eQWc2+R+Uo=fn2rrVr$e<|Cb3g6J@F2`^fZFLa z>}Fx&6V9p33FNd(`w0Veuy-~+Ullv;pbzVT;IT+um_b6CFcaohXCikUiUXW9W%%$`XkI5*q|C~?#p-3sgEc$ms*AYy6q(5jeo|s!rREw=Y!Ub1EVr}!!s7}k% zIuJ#@Qbye_b-a-X(Q6C2Xb?S#^EZ}{JmAj2%x>VZkC1B9b$e5)mr^HdaIh!Wr}g9@ z7Sx#|tkdh}BG(4cklmcg^QsHx5!C#b zWU7JnhNYB|RF9B7O%caVqafhdAQ&r_sw9YzRtDD_zRT6w;qkl+xvlMYhhl1BMb(PP_r=>k2%OrNcSBNK z`OlU(5)UlI^U|P$UG}yjFVC))1xtWn3PCDcosFqm0jszfC1j<8z~?Vt)MT+2ZK#tm zea@~Mc`f#9CvSVT;w>en`hO=9&Ni9W}!KF_v4JAXu*{;6exsP+&8iZccHD#rxY!23qIHc! z(Q`vk@wjf6g&})LmROnu+?@9BAq8za{fOvOM0(YoSX+0&AOsZ#$U?z?vRb@OxpO+a z6B4t`*r1ur>Vcy#9Iv06h6J1C`4SE_FS@oj?iTaUb>h;JuEcbDSZlUGOpI>CgT417 zRIC6~jUZ8E^fvu?{8TgvyNMvWK~$dNlzD!_js98-QN=Ls;j-tBej865*??6z28vN!xHO~f7~wAkTrL`9@5#3`qw&xTi-`V?~ea2=nR@NUkHIf-|swI z(OzC+ZT#&G!s3SCUS2fqx#4$D8_T&)QiV5twxdPuH1?@CUCjXkR5pP(v9*l?y%jm- z6s~S}tw;$6zqyu%y15$3zwv>&JN`jv;iL}ZxgacvO!l`@Rwo z2Zy_5|HxUFM7egWQD^-*=0cNwmul!jQddHdifjn&+> z6^CJfs+rq(OB$IBS&d7IF~98s??(b2f`?skJIbEy<`P?(beVJAQBk#luN4^&{&>={ zKoJSC!N+id^PRM{&2ki2-;3a63uWb-k0D{xWE__OFb(SGM2%s6dnVeV7bu9_X)miR zSY1rCe~;chuhQGvuj3B-t;aFuVdgeki%Qqrg4*-bb+lBZp z-odA3bMG^*U+Sw%WgUJ~AdlV>5qp3fvZDWhivT_GrJ_M57BFe(*sMQJ`o*W@S!x5A zdYfSAjpi=f`eVX+&A{7>TR>o{4a1V^qaDfH-+ltzk@sLp5p|4hKk8Qsuv=0_e$^EG zt6SB6Qgn0ov%cc=Aqy<;^9yFjmCg`|C zQz%BiH?3P9zSOX$K(*}OoZO6fcTyeqY>=+{$7}^>B1v1%N53V##u+- z0~VzeK48p+5kbw0#~HQUT*2L) zL<>iW%h(-G!0o$P4R58F+&CgnSER3S&YO2DJ81K}K2uqQL}ybtHz;{c&0K}rcFbg6 zJA;a;X@4kWqYc>h?%z?K1bETsB6r>V?*9B^S`(?Msq@B?Os9O=H@+kX#O$zwDVR;{ z55u7OV3$V0*Ld34;;tE!7oG?7&v|GLME5URx1#&+W$L0R>#4A$+H#sCZSqet#gn(@ zVpGoK+%gS!a?Z`p7%rw)e~v%*S6?_}_U?g|?hLo58tR|&vFepyaQpv=Ue7k77ogu8 z`GoI0`Fn5YEIw$BSkhMf)9zqn0>jQ5olF~f9YL+}MnM z+g{Sw>oZFzAWr>Dp7T4H2}=752tgT^HvdGWwY0a79qZ8kyyTh7qQIZJ^L;=mhY?l( zb-PU9EOtU*+Eg9ew`>4;9g_{$-v{bcL%gu5JH4BnzFs?nrMJEio>{NGx8Xsea{(rI zMY)7({|(GlDl7Y+9*+1A#$HdH{ziaezTj{^$4BG+>Be2<;ccAOgz^eMPaU&)V@<5D zI%0mW@{&B9AH{q0gSrgeH?G~)U%}@~>)#k`({4m&owVw65GtAeh4)HGYfA7D^HpDd zO(Y8rpP8kvUQ0-b5e(#?`~r}od!NQ zov{X945FOE^r`FTK8~V~IGXaBA7%fmYD|Ki3}-FK=P-4~C7>JgLJjpKxxr_xQzbv3 zbKdP^pX8=wh9}ELQaCQ8E=a4Rx@0jm1p`RNg%r?Zg{Z%M=SM22%tY8mo&|J&+1hAV zh+r4@6I1iPyQlCvUG1|*sdoQ->2dTDe-;+N=Ze!bV((tA(QrkK?az-4(_u+%)p~18 z#2EGoG*91_1PYdw&=vf0fYFc}P89{_>~QCaGFpr_M(T+blg^Tv1JI}}j7H?Zsku*o z2OvPeh!E?T?+ZrAYYD+7n6ZOJ2(e&!9xO6jMUM9)^BYilk=s;w3tI zyE!9-Sc;fXrt=HGd<#H@Z}Ton=k}^PFr@3@A+W#FpxFg+Px0WI%?`ua5T8F&tx#xs zffeSMaoVA$Gko~d+|toye8k6Ky?Dg56RPKL+f`ppu>D)fNqx54U=&Qr`2Y?JGHg^7qS z5qrHG5XLW*ndFkaZlq{B;QWn{Q4In?v7X=hxxvXhZ#PlXL4RtVoUZ;qcjOkojMZUm zPw3BalLR$zt@)ZTK&HDtQp8!U{^RyFlqG2W;w=X(AsBb7gr>0B3is8a?Zw_CXPL8M zQlVUl?!@u9g_dJ~&=g%d*AJt%2`Tr9oo9pIWgt{g85avQ7M5oiN|``FuLi&q6Joa>g3o@~;>9XMoZQH{TfuKI&ocw zGGAYF=f;Ujj%EuAES6b(mWVh7Uazja{opH1bMCVUs(d@yC}=;W=Bs0G63|pAYk+z3 zN&M`yfyARH*N=d#_t&a32?6v806!$YZLZj1xAU<*s&n?A8lszRq33wX8o9w1`en^P z;w3}JSl7jZV3_371abGFP|N43lAGxky>wf+(VA}sWzU6?)>+w9nz?IMU9W`c%GLRWozh7gZtG5VxJFUWi%M2{h^#C|!=j}bUxZfp~(`x%4$7Y3L<@CN(+sg3tSocB!rfs_I1gTm-I%)B|?3mB| z3nZ0uK4?WM8@cc<_Iv|TVU;i^OK8*1!j&cJWH2&x0yp&52{;8#Y`?-ELH2Z9Bb;D{ zBq!WESU|JO3d7UHr))Qh!CH!mj2EHML%(=&s*yF@ukb7d8LatJY#NIC^EH+fWrYRy zP4gT`VFgzl8q0adht=2JJGAuW(UBsQARwh7k6&%_MeuH77%`eO=t6wZqF8M`HN zRF*#B6omC=lHPIS>l;SDg`8giweuPiqmTIH!~@UKx{#N`C=2K+w>)Wi|>`EUdF;_!7n+MKsme?dZ9tb=3 z-#A=%O8)J9MJ$ zc}L7QCzjOk=$rRBc-4=#IXPEd(709k)d>=v`Q2e}?`f8*WKE-Gk;Lw>CebG9-fWZI z#~GNSahuR{(cOhxU$k6~Rzz8!{6c6Ir!63OApFAEHlt8EOu{WhN+)xdPCM#zcJ&X6 zx4_Ztu)=_p!`Z~Oa__O}s^nF?SH0lys3_&-R=1a|PHUSB^(Efdm)OXe!27t{ddWu; z#Cjj~yhC$(vHuAUfzlUf*q`LQn5vV_L0X&aOSN|pHREk?gFNo5*+CnH5kEOW??RW4 z9U6Y$B8l+=1(k%!m z9RdO}QczO5L+Khl>1K3nbiU*FfA6^9f_V1C`JVGR0}vwNEq^-M7pHhxm&CbO|6)S0 zSTAW;@K2>An--5e?svG{mJzE}>H!B7cJVS#A`GGTWWMb^ocFsSMfzJeVE48m$@=m) z#E}qa?rh;=!C76igW1#`G%Hf&2WD!#Um3g!z7)gA4wvXm@5YtkU-)PI!J-{b-kS^W z1WRt*TG%~s;aY9fp)!sIv`z^LdFvl&N;{36#$Tp(jerl$T?e##x}9W2b$M~*j&qWW z@J>&=C&qcY6uv-fACVAfB@Ua9QzJ4!B;4eQ~sjgzGGanUL!Gp^~EeC!4YP1(0VnR+WBizsi>*g9KYf)!Jx4R>61lR#+Q=K%Du&tRs?y%8?A=P zRs&o;6Rj92XgBm@H)0lH=zYFQlB(_2$2+r@?lD*_SkI&uPx{?M+wpy$AcfU)iVAEY zM{2h!Lj(1EjxSmcpLE;27xp%@icaCUL)l$3p@NTZjZ^T8zv*Lb-7EiXI#LRHF#RcuEKLA?MANW?1@6!H!O}j#Pt-mRrf8&tg!m0TgT}PH} z$HPy7_Q~+gV8kHKI00Nt#eBi#1w9p?22*%}Ej4&{TePt5@y0x}JuDI|L1B%g24)OV1(4U&j^Y3dmtI$GvC z%j+?A#MHj!a{5JGW}5#i*Yon0UjtGE+7doiN%}iNRdu~q8xFKz zHNMlT!}p>f|M}3VIlE6nnorwyGE%t5ITLbg9HCr#?7l!d0{ON>UkmV_=K}_GB7<&K4S9sfNDE}?~Kc_cseyeW*j^DudU<>RY1+4>sMlP9Z~VN-bzGQ`t2Csaj+lV69jQP zxCV#aJ@r(FUZ}Z5Q@d^;V5NdPE*jz|;lYWb7-cAjq;c%=Dmx^r9~Rm>r~uHkDOzhG z0Iaz%Ly4ZslH%M;SqF_zo?g2bB{gMh*MT{|OE(FUe3BFLIGFovYwyics}F`{h&M4JZ`}V}9TMu9mzG)*svU-% z^|#$;C8(Kzo+7l}{ZY7^3n?eyg2&{S~f~&4c^q#;5 z8CN!oACTM=7e$o8A#*TBJSeJ8#MQJYiP&zwgxz11URyrhop?fzC0+dPUK`pBe!ezh zMn3!ItZiVE1+U&=OKC;Bz$d9p9x%T#Z~QbLSn`6S^=H|>44f(cNX*qo{HM6bT9;4# z;6tamUUuEvH;+MLg>!TxqV(S>C_9QH+HCcKx4&38J`}mnGuSpTArW#Ay-%>P37o}! zn3zI?q<1pae%I@0ZbvV-&;mI#SLU<*%0nsDKzAllr$aqJk{RiV$Mf#+I|DC!CXXqq=zk%SoCeOQ$Ye>)_bBkAN(l~?3rJ0OlKf9M;8xS0`^Iym@0 z+`hDE4~}?3OmQR;!@;gzCFy$xsryqD>g9j$%OsHkUZrtqOSAHFQxG$-@G;#9wYkxF z`94V`8sRMr59S^ojVr##Askn7#`HkFwJTbRjKE|w?qVu>8Q?u4`fkI`Fq8vG7F>}M zZ`q$D)0xO;=NJQj;qlMh1tChj>)VVQriG+Q15SZBN%V))wMD05?4+(OLG5>Im!SU{##XgF_Cm--QbO*+P`VcKW^NJC{{t)gXk zupJe~XZ?lHGai9K?WzTX1^VdC2 zsv#~=1UqH-2Z>b2kP-{ObP|*KbpMFI4LFXacIrZGQc`x!x;5Cm)bp5EX!!aVypb1$GNp=c6XYy6|X6A39`h zN2+685Ho=OZf4F<4!6axan1nrb7iH#U&ooT=HJX@Q3txVvU{ZdKf!u4SWj*hAW0r@ ze(LYq$=M2%g7eB5$7IZ!B*t}ShD`Mr2Uv@^A~Ei)%%-$WXy>#aL@+7IwcXNw?5L8k7mA}R-vPv)C5 z-Q$RXMpEe~SpN3l(^_GXv--LWZrfnVSF3M+4#$Sp&|Qg$OAkEXp<&y=cK=Icd|K)L z=KvZa43eAo-KvSMZmcM%sMzz@nI?Yo*VPLNtKOh{oF@BrXBo1tKN`R!9Q*l!VKQr% zZ&&+p92%=N4SC58`Mk~kSZe^%@)y2D8)71!>dR>_w5{lz3l%uyn{z$MbK%lbvpPwS zJd~)6fgyC&MaQrFbdg}_+T!CFPN)gl%1itp`6xfZ{RFztsqL!WCbfHVd)U%a3Inv1 zp$;~F%1T1iW@6UNGNWQlf-v@I2@p4(J4~QdIczJCk$Mf;IzwgCH_os+=^2#ppA=PI z-PME0(jklj#$eJnSK=5X)w(-*-VeL~QhV4TSz9dF{;8{`x9R<+?=127s53ugHk?$= zM%YSo(dbWEQ0bPKdyS5k55tD@XBnbbQ+q;p+b?$O8bK!`|16L{o(nm;8yU_my!@#% zii|b~cvV|To74om{jPG|@xRpvn)c5A%_?VM+cL~3J#&4+UiQv-u{!TeOF?g?SbJtX zW7_XPxZ@n$V(UN1SPlLO5l)%Z%2Tg08!N)prEz@{pqyUKwwipycAeEc@Hs~5ot>9k zmNW7czghD~u8tiq3E1mDHx%vfCXdeg8FEU|$sGZo45XjT15GlCoG$?tf_cfK3PXn9 zjRiJ{3LxWp;&y3RLHx%}5zNgS^Sd>^qGi%pTOD;)aRqs#+mhq9Jk zgfxP@Lc9Q(t5uB9kB$RxUyfyEcA38au&!}7iW5#cX?-t#LUXNy&28v#FL$ThDcP$q z46kRsJioas$Y3fs!TGh)_O!BKIN{rn`G zO$XIIkBs9EdV&Yn?B)_6a#l3nfe$;lh+i`fp=IE9-rfSALvxv#AFweL1a$=Tg0_D( ztO(Ff0k-mrWK9=qr9us=(8(dII*v?`c62tCJhLx})BV=N z*2$#D#9{H;std7lbi68`T*uarJJ%-I-S>^{Gr$9bj#I~QKe=>|%dMIB^dpe@dk1Z@ z_t#GHB_LCEl@Mg3z!g~)Dp5;scLi*KehHixpaTEmNGobl?59DMgpNMg^3}%?JsC|z zd2<;VPg!TD8(o-Si`p0<18!ql%&^ff!mK?rix-5I2Vl?zYy2RKU@sosLG(rXF>!-= z5PB#H!uZ?W(ROuKZux-Kb=?Pk{(`9Mew*jCi0ccu-N+ zUxW}!4`2uD)W3%7KGprwO2cte0l(novs;*{LSu^IzaFxa*BDss;E$;&*$g}KUzey% z69UE|J{yY%yPOm}J$)@YxiZFIdZ$*33WwMcqdUACs7VNQO#_Y)V5n?I7`Gdedl4K$ z9&J&AEu+L~jjm@lla!vUB)!g1^^x=2ik?*AbiES21-jjTw1u5H!bvuLj7(!fK?ooO zS__fZWEcyP(M-+S&VnOI%ely!V4lXu5QYO=Nc$I5P%-#>P5fxp8EnkhWx1m+Te@tqdp6E0fal6 zC}?1NIn#IF3B0P-g%nI;NM?7a?I~E@H#*iYh2f5hwue3VzNklp+3zj>i?|aF=?bXM zOCWiptbtURbVL2hl6Tn|P8dDrUfi#c2he1DR?IrMs!HL_HrPFO%$ zaczK0YP5rb-VBoQGnp1)TjUM!(IxE+tIAx~%A3h&ELxJ{Zo%Q)y<>NFg&ZYaHY4k* zDfT;Ki_&xAGIXSU+TFBy0-88w`bcb*h(tbX@q~YLT|M(RXnB%BiF6_NO!w#bkAN^3 zYgoMWQ=IhQ>d+fC1hh@MCn@aS=6-iG>8vM#$t6)&mIqe+DVRwH)*B}uh*iys&-;B~ z%;5<;E4hk=T1f7TfMa`MH~Fim5ImJ?HY(}>k`VOoLMT5Z?Yxr6g(&D@c%EKavb~|w zTM?@t0Dy3Siu_xBD{8-?>##f0B*X#icSW~$HqAP;_;f31(G?guT?fz2!V?9Cj2)y% zlvq^Y102(ghi`nwzj`k(NL;lKs;%&kiEIP?e=6G)58REJrA?WUPYUL1Gkd)L5s)-^ zg^}JsM_6ja-*zwwab)l5O$wosP{E~Q|mx5J8+THfIkiP<&d-w07p z0myuYvxk5mJnWVdC!+v!^*qt!NW%0x4c=aey0t61jk%-6=P2y6p**k4KO!!UtDnGY z_bE9obuL6Of+&~UTkJC2RP-r3*rIJEkl)&ZZn@R!gnhw+W%2ii@;uKOlnUm`K>Ct7 zhw}4Wns=)U;Qg~AmKzZe8SS3HNu6M0m0zIMC`|F!m21t_ZOE)x^*el;zPqDyPvFaaylnHrNje-E3HkuV*!3g(TB= zMnE8y5WB&-xqIC1IR!!ixXpWr>y*DNvl=Ottg0S1;(`B?zUuJHF8XL1R&{JWCvE zQ>ft=##Lr58S$qNQtxxQJ%2B6TwmknNrgl!B_ZgM%Wc>5;LvE$(}n{AvHL*yxRvxo zF8#2lbvB6~7EbrLCKr5uhQB^Cn<7e)U+yrB9gCvC$AyXTFhy1?4w0akd3Z$Pxg=V> zZZ`qE$Q-}J7iDVpW;f1)%eC_JGJ@zp&qm5n&qM_9bmMx=v^^x~BN~GI*i_l2#UO7^kW`u zMs1zQ{(8RYGi-{LEk=FNb!wDa@#E%qq5?}k{r4g|^pwa?5q81%04z3z^NDZ`#7MRM zf>PtXhm_KjJR%^MP9Qf9|K=n!u>B(!5H2H&{DO(M4-=s;AUt#8!MA%32>+b>8RDAL zE-KaI+XW7P+u_S^aH5u%!~%v>M$Rrg1#qLQys50@HyoywwCw7ohBGITDwj~~EL4is znpg64{1(2)Hn%W_#Xx3@W*GplELoTMY$ASj+@w6DZJL!soX{ik(bQ z>u_rcP-*o-GkD`AP+|m6Re4+o8C%^gEICU|Z%UTq#a&f2`gcean?#!cFGhZ&n54>w zr*Dya$JXV5Rj|%zrp;GAK>YgstS=XS+#L{;d$cTX%pMpbP{e-M&zm0{^n??3UoQOz z9QODIu~mkCw7kEEB4$B%de1qi)0NFgD&f;ey3p{Vh^ioOz_PK1VNQV}`^*aftbfN8 zBNq&f(_^cV6T33)=$<839iY)Ye=D?+Oys7CaA_k?rxT?mwWUv-oS$=UOCutV__Pwe zo2+|ST_l%J=+BGq*cY=qb~d@}x2ObaX9DVL#R@Nuo>ZmZSMvqI!(Extxmz4u(&2W+ zmn7Hc*7nj$0F??-KiLSc&}f&rj2r7{d)xQq)?zAzHn3N9f&)mxk1eQEJNyOq`lX(@ zqHP^${|1X>+s#F#Ngd0!-}2$gv_^b9BeHxF<?@xfqrr_8_ ziZ_d}rp9MTHB}DTkfnsaaihw`)wl=Lq^|UD=8e~ABqUyxaX#+u>W+9s;2obzH&?%?2fD)&5JS| zjoL2~LuAFcja&LtAaN=?-{)&mbj_l8b8fQ04$$COE4N9s8QL*mU+2_*zK}N(;V>}3 zy_1_)BxM?|UO)wpChU+^{>K5UN zxB_QiR#Dok_{}JSh%x(cKNJF{#zjJ{JgI@BwyV=w$TL2%^FI+HG!%)5&M{SsDBRF0 z-HV0CWb9{R3!3a4iN;gSTy5j{mLII{k|yZLIl8<=Soyu$cy*uUTKd8h+UTw7&;q5sUJ(h* zVG7aAd;M)4@#!h?GYb?bWQ_0HyGpI32?y?texIohP2d z>e1uetBTV4($r|!hD9jMNM5AQzM82S`oOsj^NKs#X0f1qc7aQT!bqNPZ~y7m*J~*E z8)RL*&vTmlYbCn%7=)S~dXCsdpPw#>wTthu*-sd4g>piJV5QkQC4H0XQM#tS71tzY zM3#{FW0dg2d1Cu920?*LdI$x*%@0RF`P!B@U>nF#vY*vFmYshV(}bVvtpsMC8Rpwb%` zikC7X=M|QO^WLC5&gc#`iI)R}9^B9$v7cCnBF%1ht#3M~Dv$+rN#SQH>j&P}g9Jot znEK%fBLtYfg9lH6{X~^Y0bC%(JJFiMnOiQjP8Dm_d_E0KqbSu;Ve0h3_WW{3WrqLKvX>ahmMRCSaZgw@JlwNcr zAcF`^9ss)E!4AH;fIOc;{uj68ZLZ_zt6QnNEVt$1BuP~hb9rcUgW!yKLtw{6JbO!L zxmzjK42(Q?NokjY%lJ4IukqdG#8oh^irDaNkRmUS8v2#Nhp`5Ftq{$rWkJxv#YIAI z-YC@7NvydK;k6_tO`a+mV^}l7SFh`z0D21!zCTwepRPqY>|_CmT2gzP7kV5E3l zR5PoOpG5S$q>(9-e*-{ZYxqS^ zJ9)fwU-z~I{Yx((9v3*zpuRZ!yv=trT7XplQ73(*rG2ZL%Gp|)LE0=(l2yXl@`>&Q=D>_9hM6kNEpJ6oW-Ml_FFsNMGyb3sAfQrS|Fq9r)X>fC1;QI}V%9p$ zbA>5jF~gk5Pna)<{4$POsdbw{-6lH*M@$U*o3Neview|j>hvGt@AVMc8=8B1d4zeq z+!kXQc1t1kaOf=&e2-dM%1S;!jkFakN}KMesI+Lp1geZ`H^jafZ13|jH^TI*6m&rv z0^%4td{yu*oI;98gi3&2C2j0OL(03?{!2F%t62Nh0+%0G=p34bw<(Jil zP(AVYcOPBzd62pnR%+&MCX4ncLyf1vEDssya6k5w+eh&^GC7&0qZJ@oC)xk#UGmcH z_0eSsGbVOJ*I)Y=vNe5~hA46Re)C1Mq}9~N>QPavUV2^g5&M)2MQlfZYBZ3{*MB`SJRT-9Cr z&h=yFYUG1Om;!5`I4T$BZHAI0e#vZx;T}%hR?HT>90;`sxvU!;^Wo~4#mzY?vRU;E zWyP@&2fX3ASwgxb9v2{&f-s=;9iiaGNp@KsvdRcB`Z2@1bF0~C zN>pGV8kM$Q(tT)9tamr#VONx{;*3i)r2b}C^z~f$t87hUpW1+8CE!ft!&bdg(^x}m zxopHSwQ+uBgEZ%)-)~`z-AmxY{$9*nn`Flm{V8jV%wCh>wC15n27Rb8;ZL2Xmydy6 zY`s_c|GCi#oEufJ_tI1&x1DNnq>tWIS{UO$Ejw$$U5s6wOB|R-e7OL<`!VS694}M| zy0hcXIqkt>hG-aFLZ2hiQOSF=QZq_g+!GvsGAnQACKatJNzKjugJKaSaZc{ghVM5( zNFcQM+F|~?U8BL^fl<&&f*zdTczBUg-VB9ABupi6&@*+an&Q4TbnH6y!FOxLqsryI zAaR?P(te6eX)Ny>A)DXfM|{K_wr1f0EtvYsFA?Ol(CpL!-?o99ZPLecPAa;*L#}J9 zZhP{YdFpk?x}Kd~Hja&(#8ZJ9iAGy}?H6YxY-Z9A9droV4WU&wL9#^eIE(vg|*+`)2W^~VpkoNqI$Yf;I_Q5^*pkNqxnS7sL>3QUxV0k09 zd0{%g`BO{xcNKU_#JDU9tfU?*-FN#d#f9P1ddz3yjyZe`e^Q<9ffOW7+M#e*fMoNA zSpqFRFT<7%i`1J!Z)R-mLQ!->?=M;3F32vW_Bp;!voe981jz%~#rNxk`joM3buHph z0u+M2BR&T7?zBhA;sBHr#^Cd7u}0CgE2vV z;g)!H%H*cO8+oG2=M*p8(7#=Z8xja=kx4RrHQ(=gHBtT>CmB}GFjKiUDHLIZ^L4JQgacrj4GgiAGj-Zk zSy0txQe=0zC_@s}GGYGsE(6E;S6onSg+?YvZE!q{CIQ5`9X+j_cLoV38{)ZNR^)TI zFdEJ_vz#IxLW;2OY>|B{q0+K%KK}>cTYbx04}FtHbTo1|*x!u%g+L~y))^=VmYL!Uj<*~ zI@H+w^N5jsnh~5EGN!e8kyv+|eiz5F-hVD1>gPsQ3t-&sBM|9Y!IN*gmR@gW0yxjT z9aFzJ^M63kM|y?37ZBhLkoKZaqH^rgI;?RT!Tx>PM1^{q1^r}W){X#w(EjCJ+ue0U z7lA>`)tw}#@*JsO4-(4CkRu)od^j)k#2O>XM?_5Wv~;8p-|Rsv_dJjv7+btL9byHo zEg}}$DMEshlInNs(pqZ<_&9u#%4PxWtr1X(jK6L@E*O%-&Nbz)<)lq@9q+1!&HhrN zN%@mwvRZ!wx$W%|*Ec+1X)x!P&kWDlzHAxqM=BB`Vt-O}=f3p&dGs#R#4yv&|4Js~ z${;i|`l#UjgbBTh3xyifj{C21Bpq?A{Sb4!|AD9#B~ky03dqJ$d1NCGhV36!ywTi! zGgh|cV=QYFsKbKg)`22p;9s&fBOy%LTA5I!M71Du?SQB<9+NM+3EuqW<6)yk`BaM! zOmKA*P;Lc7ofH!|cFSo1(S7`gs3PCh| z&z3PekLGQ5vAb0>aOq))S6AxAQ1Z>|)hW+!=^k20$+BzX;6E5SW9WAep&3u~4TN>9 zd8|7lypbsFgUNI+1J(KH)d`hy?m5GqD09xdO>{yw5l5cim)Kyv7sN(? zKs|F3P*%AlCTWxN*a*8iziq9Dnuw`ae;1X^z4`WI^L&`H!wBU(bEVSfANn3;_h4a~ zAQ^C=F5?x@zwv76&>L++vfga_yU$BvBvO#T_gY9F_wz>7*8%|Oyhi_e<)~*wmNyOW zojM_1aP81qt$xY9l)Uab>{p`OzBNC!a&S@bNQ)&|4$H^1H%!OEwM`CbIG7}SWUx*Slr%INI=vPxx*iy9u^IkkA z`iA+1V$CjYW%Hc_Y6)cdm@7rMR7H8V1dDyqu_El(G-6ZSCOIsaj&aqxCUUi<%ZZN6 z#H%P`x6Cm|syAQp79yv#2zm77PIEF=iziFT$dDVcvbQ2_?@Ql~w=7|d21rlbywCib znp2kB=%}QjJJls*h@q8dB>OO*U9d^&IL9Zs+SrAPegQBT!Dg3EJKM#RE`*8GZjGuRNAtrG?K__{%$z_YJ+B9B z?El0D-p9^;tI$gOw9sPS^3Lvi@HkOe3TtSNk7@ri04XkArrBZy#YQy zXT|Is5;X023TGKm?l)HKK;_tJoe2(ZPs}TDPcWyt5<7vp4^-7MqSx=f1_lFLtCLdT zxu(HNV!U`7}j(oa){ zEWVB;)@~KRXu;Czk_i>}#4X6qgG>7l_|XFszc;={NsIk`+PaL*R=e)btEdm=l!KeJ z#BMyP+#urujw=7 z309r!5cTs0N!3=BCMriID{=xtC|0L8o?E`pR;ueJN4`!+te{y zxU`q@+Y-&g0tK89YTF%(B(neBnk3AHvm==c)$g@;v?oL*#U+~RSR`giafXd7`jv5a zqI0~6n5Qfe{pTXYf&l9GIr~|J?X|k!sEq~HO%09i76UA%Ds(6;m z$$HF3oG#d7~upU;D9u9}LR;+@^)($^{=Dxr7^l zyA;)a#f{etw@BI69i64{^R>Wp#(*1hCEJ}29`w)%jhD z1CWFM=ycE4(FJ>>DeYFKYd%#sn^^f(VG3@RU~Xcl>cWh~Eh8i*0%9%{X-0ql+7%jx zR==TXQp5I}JT7LHPZR-uh*Bv7UJ~XFU!{8pek(4~OnE|aw8Mz-vh5b8_4B+}LjL+W z5nddc-2@5)L+!}XqX>8tZIC#wX=e{{XSmOX1gvn_(?MP|{wI7jIWKm})w7(LWnz7n zZPo~K9&*B=SmBET zDTeAWsrpTxC=-FghDJyCiB8CE36)3Z;9k8@(fp5el!dEz##3CTyx*kK@}uyYskmw- z=cJJP?|jQLgYkxEI(Odv1ONSSCg<2@_I9z-4~@(=ptQ4KpU_ouX)TSlgm97=+mVxDx07EMa^6S7jJUfsLNeGgYwg`% zDXaX6>3ar#28o|5qf4>=)#!+7H<0>0m{X}v|G~En-V3{aU@)xhq0wymv+)?CB@Y9 zZe{oMe#0XXCxF}KC+{k=2xUTy)Xe?%CJSaPz7xBkO!RgE+3Tx?T$pyqh+Hv+(2)c| zep#dr`$%VhjNoCezCV=uer;s(E5VeQhiz7bGTQ>fd#)%{FT21L&fNarEutT**y8!# zQ(t}S_xY|xdRz~5c8odhP+6vU#yhQHJkJM!o2rY zV<*!q`gQsOYi5^*G$2@jyL2uDHyH_RCxs-n(8=9M^s)@{*0%DAAFF6v;igK;L0{=f z1I_<|B_+WS=K9-V<_sT_gxEziRP0hXn?MxYdC76-`vD0ic8Q`IX zWbAukH4i#x9zBBIMuk~F3uDb}SY?;#wNs`%DuVniIyJVOLnrk@B~UrI*Xe3ze4rbN zqikmrp9$4#L@|SauE(A;1I)JNu+elueYoz>-L3tC5vk(L^H#O{W03mIo2gC9E|5~( zxsbL-&%|y2LCQ*_g(M;fnqWaQ1+QlLxA8FLKh_$kruX6FI&Sgk%~0Ua8zvE4L9{fI zdgR)gikaX9FQ#|naq;KL1$`zd=y^g(MEUFOz_B*8SI#R;30L*G_^G~!9BNXWnFSZueDZFrd-be!jA##ldt*bP2Gc+*wQ~V?HFxhaAl&0NNX@m z_Y1d^eS0?qV00Zc_(k68#l@vu1JBOt0v0xPeQ1+msM`|#Gz8WR1zS?Z!EdNq|La;-{_Bu$FggT5sd6&0bQ(r?CHEw^sGe7_bgVl4yr`S76^ z@Fm&XOI>DWn^kyMP?T}ey_zikg{X&gz}a**c{J9SVpFvKeK$Wq;osgQo8GPA=A|P< zqC^kjM$!nrohN&y+xXueigGu$;Y9B%?F)!jPO2m`-}mCRh5Le?cd0T-95wO!+w(otaE5Sv-g%DO>A64GbJh)hSD` zTG1NPo4tnYI7S3=hB? z=*nfH(M)^W8LW9{arqnid1hrj4GSi{G}aZi20v?^0;%$lpfz`Ltg^4>8*(bdq~q=_&vfI6(gv^;35IR4mx5b9 zazPbPh>BC`p=!XoV9R*Ye0j9k3r5LTxRo}lbB0xa_2Omt#fp(D&b}PB{m@s{P6%VY z12P=I%(HnkM9mBLD0$-J+4chdFu$;7vDgd~hg1=}6{q!3GEOywyXEP1Q`R-^*e%w& zOD1*XYT=@~3^W9%+dARgi{Iuk+Q`ZU(TfeM$h8mz^KX!_i1izVBk>PlP4sVTzqN4W zHaSfS#n2rG-E>BRdXqi{|GNR$VyFa*nByfo`d8^}PQS0CE^i?7@f=x;s9HYA#AE*Cj zrW2B%jS~%pwyl?_bpxSBB6i=(pHdPu<0Jo8rz% zttt=YKEsJ()*)l!;A%P1g^#pu!@C1OPkKs7kS9y7%q%$4!j*g z!c~IVs>#ub0UjmqPz*$9{2#Il3bq|kgpC}u6+t&Z1yS}JAwUl);q#bx_F;|ZrlUIIP>q8KcL242AtT((m23ENtJU15j8oJq5abk+o`_d@ z;*-1mxi8eorpeKIFC13q3vz)s4PFH*cD@R0s6~A-*isxid#g6G>^rwa)D$foDA8!9 zbY(zUVl)N`jtNq&0!&O{N9-oKAG4s$7=Xu0YcB_mVOr{wyo)za!`7tB*dDS4Sz zzse*F31^$t;5b#%LiY)ARSB>llrJcgM&HsOr^MyIKpDGkyq!hyflQcCGS$+mB|-v| z!Qi`ZI$vB)Gs}J=yP-AzTM19T4*nyc*?=c~62~FTPH#@E3`=}u?|;)(s|6zf1##Ta zj?r}eEEsTDFoCY63AyuAegXB(0a)gRhyI@XeIdYNU+8HPMmv2$&aM>%l>bPDhqr<> z*~VC&JEFuYTLv3tuzLbuB-|0+dHxw%Z2GU6swDt>b*cUHbK~B{86P)K)4wFWFP$R> z#i5_0!q6QZ%)dEN9Pw|wr5w5U!_{x>HkfbZq||b*Z<@1mHy^pMNr3z+CCwv~D{bPG z$lakQ03&e+cXR}#0ET2t8R*L!ckIs}ndZV{jTVk8#|ec*s2JD?-fSvBIF z^B|k5kzW#Nad+wJ&5yLlvruP?u*VKkmiOlC%1{2v*Hax+=?!fTS!&wFA@($Mjus(5 z51qZco$^`6rX`9@!iteOoVbbIocbdwEEmmkI=47}GUfmSwzG!XQmzLxIp6|3?e^+} zkIu#e4ZmR=1N8x07JO9_&VX*plkwCTg*URC0k0DL5cl71fu9p24nS$PIA1~MZ=0nR zK`kU1{pu}@obDOao2kfc{cQ|L+~tzEoT&nuid#$NMj*9&Q=&b)-l+H|WYd9ky>Mu` zj5@iDig6b9SR&8R`r(5$mXk&Kh%|-A z-9zkj-J55#_SNx9pP^+~g1Fzw`cNI2h^S;=C}@Yr7N5OqAD@Pz2q_>VJ{8U!F}re` z1}_NVzOr=vKGaY~v5pHs*zV>+neV>M?cXbgv(ywGU!)V4l;oi2jZ8KEcF_lhtn)YS z8wAp%a;vpEY2`U^1RnZk&>)kB;IWg+luWum9=8gG8m{%2bSp}Q8APrgVgMkZ zKUXU|S3ePxualNR4Gl_tnN`P)!LHyH*@b3d?TivXNW8+FpTTK3RngSMcXw3)uOY!! zRvjGsH)erqSF*M9cW}dwO@eThgRK)*)M&1oIp!-#DOWnPIvvL&Q>)(E$CZB8uOCaa z-S(^XRXLLeh&s4F^#&(~=f~!TbByj5u#4GlY4ZzgFyR)j^*1XTj*pmJhV0?irnAgT ztF`R-44qUQlnBUyLcId)hSFF{EX6H(2Gk+#Dq!7J;&e!24t$4bEvw3Rs~!@;GGt={ z6RHdo3rF(dGV-n%s}yP^V0&;vL6Av+ z2kR*Y*n{}5$MsZ2&@Rv_fgM%wo#G`56^b(Zu&0!hQ^XcJnIOUV$(7!^0TzpOcIL2H zf+WVY`#12%X(RvTo2T)XbPvA#Ek=(OtET1k*#m?ca?O6c)Dh#}d4qm+yxdnV?EZUs zatt7d9W`>pr0u1oA1I~>E|HYKGf(>)oLlR7VH)6A@h1)2G(Qhr$Ta`|SUT&6Cf~OW zj|K$+QDlIGbW3+gBN75ihlIrFQKL(GG)!qpl~OuJcaD&e(lFA|9pCwR-~V7gJiDLg zzOM5;4)}ZSJgO+pa^L0Nteo8}{1$Qhc~kuGa`6q(S0}x^C}WVVTIBCDm(tc8|MH(G ziMxs37R5~A8mrvT5(|uVx6L~%;42fT$T2_M&$6@lulJAbChmWMcIh-0mezB4(QiAN zB>zabk$}gS#qlInq1=IdV7gHdMiEWphrSl>ty8Yv`Gv|XKWUn`74-ubpDO9%7L7C8 z_ez5sdzJh7fOVj>d={I}xfC$lY9~PN*#T(Z!BZ8-;7pe~spW7G;sl9F{(@zFXBQEn ze5|A54i$O8p+5!@s}xqDwtLZ{JkeDIygZ5#82bD>E;UC~^C&{-O^s)Htrdr-PEzjV zTtQBkW3>n$hw&(jA#BbL>}r6|obmIX@>?>7TlfH(G3FB!#+o5DO-u@nm>_d(QxJ@w z2pTnpYc{OQL_hk~bl(9D;w^xKscwQfP}E<(-2YO>5z`kuQ#WI~LoK4I)-W?7C}sLr zr0!99ZO>&$ZevO5nJ-2O3Kl@c_Tp@R`F3^5O=h%Pnwq8|q!cGC>DMZb#jhj_k+nEz zmx-UzqNtna!6flR=IhbRXZw;E3DGsC6vV#>m+lD63U)`UwP?H$eT&p6Qd64Yxs{u+ zCOX;h&l?HBXE>d!vW~1N^!gz;tlbm(h@XU~eBC%jMaPY7gFfWOO`KR$FH?_K>A`dzH-m6;WBwMK{h{03{Qm*83s z&1__j%8w#~!G#_w4Xr90c2_0~);39+7~0&;TO(|x8WNp?>`kS*-pPXI+E@LTJOJ&e zjgO#i59RlD3v&bnotRg+zY6df#Sk zp6-)B8lqQ>^`LKB%H=g0(Cz=c;JK#xwJr)am+FrQc|~MqcRMD>&0*}bPEeV9hjbUR z+s!)8Hp!KG6FB$NSU@G+G-~Y?eBcEU#{p!TRQdyrOnnF65}oAHC{;`F}M}AI@EQ% z+vWCnV{e(s+BPvX?2zRn;g4IZGg$gvyABiO_izK@_}wu8F=C}~CH=DMh! zq_{YH7F3C-Kl<2Tb}HZzo;X!bw%J4}tor8bJb%qvi$a($@7>Tg|7qP~RdYsCk-p<)AAR&N%J3oWv2D4u zH<8c&p(dyd5yY0QxMO)@xJ&#suul0WxR9ywNOC)PuFfVT;PK6u1QLMbCBt-w1V#aJ zIDU=D7QIFJ$^F=?w^=qu4+W}G4|`5;wA!B@i1@-=kkKde>=}y|r>8%9eJb}Aat+tU z)`Cz=I}cv=?-_NWnrO8#&q3J3CF>tmYC)6unK42+123H!>p6*))Gcwo(dblULngCR z;A^v|Rt5sU8&@=zKiQnPn(oN9z9J9zJ zieGB&ah6RN4+nE#GXO@f9bQPXg~?8oAL6c70w^|GdCrG&ew7U50uRPb*^<4Dv#ogR z8_TqUaO~EVv-y0@an>);$5v6+V!OT}beDyvwnUEO7cE5^LAmC<6Ths(NHcDb#GAG4 zRcG)*&XTa1!iBwcM&Dj{i?)jX zH%{Cl+V&`bj(sp4$2v&_{yDZfR~7s`uhIS3@@g*MJB49C5_T>X^_y+qR_g>+Viw^M3i|Yjm!75f@>uITgPag?I)&}njZ4s*<_P>SU`P;=j5RGb> z;M?Xvso|(LbTDlHIQ+S8OkMKEmkms3P?OSI+sg(%cu`I;Xoo0(3C)TTKu=0#?aS?U z!+PzMOjMifzU*w5D97g5CfLrSz=+FNbwwlQ+-535PD!tFWR_!^hD5D>aB(euMEHT# z@iULvx1L!__W8G{&S^h?|E=xxi!x}3X*^zj1+vmxkLxNNW;*c($G`RkG{9-tvrVm@ z!(jhy+vS~NeYNXdVcWKMg_UB%5x9AN?PQM@kt5xwZyo^ElD#HAZ*5LUsV~7K?>`sI zITI+ZXOR-lHmQ)Hyqz}%VaHtRaU7&r1EY&~?dK*=BZl|_(k0K`c1B{H}Ox%kcjuvO|7YQ^+*Q8UTd40^`O2xNky1~ZR+xZT`8-O(f^A8ubfzVy>rUPoUq6FWg3 zjf$hQyj4`DH}}47WLWC6XS{IC{+_|cU8=;%ksdOuU|1xVmOJ1V3+okDY4fx(%z2fz zZ**IX@D!ThwH7NZn7d6xEgJaYLvP+>g2h$lq_+04+f9HK`=KF9Nkn3IhUNzpq=}vE zs0c1`kEhQMOR}_L^|5k-#{(lK<0d0h7pMcvCJl}v@bC*Gk`U31pZKfAS06;A)$oVP z=du~(eZl`WhRUJ?NnNC4RrNL(L-c%mBW_FtPguyT4x8Swp(D$hW#*L+Yow}aJ7?-j zF{?bW(md=`FUQ#?Tsgzw(PUzVoMnC8Ie4It|Zpe&QX37^~Lp`1o7< z*m!m_hL?^a2UI`*?EeemVm=)snwS1y#z70+BbZFu)&NCTws~Oz;X&{a1V|_qMZO)? znHO;{$tD%*@uxswkxExHDkm_%a%rQ$=P^U|j-~KA*YVK{3s3u9!@7|KZ@VSX^0$ke*Z!NcNQ|HZk23d(G5fHGGAlM64X_S`ah z?%P`Jj_E{5d8T7oU?pBrg}`0u$^C(h>7NU8rAYU`#JA`)+a0O8Muda$hFuJJw9(9C z!E^jS2L5>JzOa1f(fWX`uhH!7nWpwY`$_y>xNFFJTfp(o)A1rX&9U;8OBWw zE>M*zfuPrK*B1&L4{`K|YsEJPzn4G3d15#<%Jx!Y7p~ilsG*Fe@1HRez-8WFZ`jB( z431;%8yHJDcKJ&bPOB5NOwN%(2(Xm;6kX4DS){TP|Oz9 zUL=EPLcq_n0Cj>Ci^+^z!&WXE{(X&_KJE=61A%mum1nGbkGnPZlxa|9b*uHB;dO)Q zxqEjWi|hui`*Pn7&Hb#O^4SzNU0`m{1W)>dX&jKvF?xEe??;>1+X}H_NcJ&mE>HrM z<;}n7El1&V#g$&(o!|L84>Ek({ z4HR`l5mfg~Ap$*k-CBORnmgTuBabW3%xChh%7(s(Jj%VSu;`Z*wuMbhi7z*zZDU|n ziYFF-?$Gu--4|D=3~Z76N-bdJOOLRXD%a!G6adx~2db@Ar41)I6Zj6dv;RzJOzBKz z6bE%oH&zI=T@xF1wnzkXpk|UQ)8h;>z$Ab-OZo@ZHGR`Dqo_{&cd1lZPO{jnNrh+SxYW>y%z4sU2TBBUv$z>TxK zc8g_)ZA6fOS3KyybvZf^QkN=6TxidVaY{ir*u zXx#A&6H}}0SEsg@6ed*9NEEZ5uc}0WbN%XO_V%1;*y`d zj-_j9EBcB(jBu?#{()#zDrJ#V0p(80ajIvWBXnLC9)5%j2^VBe+lCOrokythMMN^8 z)HR#o>{)X;wg9sbV(L56KMToBt&* zFHH7Irr>!>MkUdpy#R{z_Tv2?@ZhoK()YHFd-2y>JFgedn71TdV%BrST>un8 z_ts0iNQ_Du^CbOaIJNmg|0NJH1juOP`nIkD3|L71A+rE! z{S~ST-pbhZe25;j+%MqA=KT$cx_l#a_(o9=YX-8kj}}r$J637#tF=YfO1J3;b($c7 zkvoCEYnBM?t&=41r=>xEhLS$N31#@>LH4X~^!!rTj~ZQ47kSv~Am?g)>aT?i_J9&_ zubsID7a_k-VG1*Rp!gJt-qpc@TZ8V~GT96n*~%uo6~8rOv-7=+D4dBy3K}~h<)I2L zCE=$TcPwHMs;qg(_pCY%yhZ=T`G?DrQh)xCw99GT!TS~}_LHO&A$KZlW->dP%s6+f zX>Zspj1yA7q(WET9~|YMrTxk!4SN+&9u2E?e-C2B3d^x zj%q5cA-z=m@dZ7`J2ywJdQNoZxnPA^5%_>zDnq!xCUrBVi&~<>n)tp9c-DNeqS!H9 zZeS1)PKZXGR~jn|U};8y6*6cnGRC=DK2bhrUb2hc^4-z+i~af86|Z|E79}vIs4% zIhw#u66VN9)@40=O=e{OHzkBFL~N?Lk7+Ws|M;OZ+=hw&1;ZZ`Q5#927xC+9MV?cG zalciAkLfZC_6C#%#^ei40PJ8UsHd7Y%3=L&2s;&zSc}hvR7sG_A95a!&alxr*y+}U zTa;?BR+Os@3NM;Hx6l6dEb)srIXX906 zOiULg_c{4|u^B6HhM{xq#4)xH!wKjJi~Q#!`unFP%x_7$`xw&?@p3cFe`o2@!+?0w zeM>yjCyi6dgao8_7*0P6vGl#B z4j!~on4cCiPB2PR<}VjPvB$6sh>^&lL;iku_SIZcsgX7r%zRHb2eyJj&r~#Zoy^CB zt!C*>*?zmUdVx z=}by3ZdI!6fH$67uchxb?=*uzG2hCxJ`$k6rh|wbDSm-#RE0m)-458RAdqdSHR4wf zx*@UD-73~w0y)=|pB*;=sO2t-?I$pdr;Av?6vIg7- zKlEbp9PR?dyf zNOPHVC2;aanoDZR*LE8$Lok8UBE)x8Qe@J9hXW@4siak&ATW@X1*6;Q?i(R_I|S~( z9!YC#_c5d1zcF6u!DCk=l+DD@I2WKLbO@K3mm}ZD-+9&q>%TAJ~ z>ZcT`N`XrG1X$5c{$0KDO%^F6Q|Gey1&RG#*2iVg+U6Q+ToMVTN{>tT*f`}0lXypI z)EnuXk3j%(QuUMec?webT8^cj3bVT(f6-p%$@u-`ATD;i{#IDIEz(-O1fh)uTwFi-B zLYm^ZxyV0>6^)j!T{;2y#F@0Uj(g z$K7M(9E9SXOs8$8OR>u`Hm9P{nJZ0~k^Ux_bqFhBq79Ns?$rHM>y8~|TBEZyK1n0g zh#>jub>}oIgAO|=r0#8-s_U5t$ytpo{{c*EQR3*<(!D$E5Lq ztO)Y?Dc`~jjE=tj?DHVncS+qUO}3zA$?u~uUiS;+t_smg={Fzs`jbE!2W3wlXx|QL zNwnhr<+;(Spp(g*U`7?6D9hhp$)1C6rjWgC($yIPMObnR+NqcfHh85H@&O_V59FCO zaanVDDoYcWGzizTD0Hj!Xr0HVwq9aJyWuF7CeQ#gp2gMo6u1icRvcET7Aa8+wv=rk zjf(i~z&3|(NXdS6nu-G$=EpY83u5sBsy|gZ1(3B))VCYHd0u7f3CZ~Ww`0vW&qtcs zP-$&0hu3hvPZK=-F`RlRDM?%gyM&{{RCWw*(0X>jQ%SYO<(j zwx8wYKC|L(+KmzG?Ydx?xAwF1=H=3bbmJigX*nRS`m}beOlU zVnTwsMi>#&Deg3B@%URY%~JZ?iv2a6R^AcBoZE5d`8Ay@_w{0_H`L8`n*jH&RykLi zZudH8vB6rqQn+Dyr&ynVx$;5IH#NY~v$qoKI}exLZJfWBIr`cJEj=Hz^V`Zg?pnr2 zcTJVv{X!$_ufi;@9@xl<8ghMT80kvS_TE&fXXjX&Zt+hNuLP%0HCrhZLM=sy;^M zE;?b)!Hr`wmHjH>4BKC#!%WP#&wuo?tYo|jHZd){d_c~N-66OrFY^G9K-N1e!KvTR zAr}FDr29>j)loz(Em7s;MbZP`^c-NHKPcu{@MY>n_FU?bc3lDHyZ!w!lgjTL_#sXv zsIlJXhe52dnN30AN7w;&* zG%W7#0v6z3Do*?`4gdN5N7wu+F99rlYSfjvz;Bdo151AXjlDmscdCe+%*-DoG}1Dz z00u>0i3hFII=H7Gb%g0oWq5$>wayY|zkN@5wEsACC67~Ym4wVtOY=aS$LsqDK&WAC z9v|6@obuZ8Q`7l^<4L`tGE&L_WMDD0w%)sp-vK69Uz)D4V|PYTG*nKSJUt|JcX`V) zn}53h+TznUG^b~w@Fx5`j3#22Fj(Q=oTgSMfHx*@vUQi^lLJo7M8G$Tq}bQk5jQxJCJwS0L$X%6PNcj@OH*Be$3257$#;b`v3uV*WABAB__l2$w_M!Y znC2XAldh}q;08plepb~Uk7Qt%qP2X;i<;Z+e1wtJWOAE2n(uJ#s}panl80a^?=`Lg zoeVk9V;M@(R$fK8AS4|L9=#m-xaP9ZaOk&xF5B~8V6fk97J6D-B(>WgxhOT^7#KojHZtUAr@n-G0e92k+%Go!*7 zW&M5eMS^`G=np9AF?F^c+WlD4)Lg9|OYDqdg!c8AmhZ4t+1DX^`c#*RrCBb}jG&JwS5j8Eo^@J#7S|G(DG(963&^al1}LIdhRe-%2nZ%7zG_ zlWVVO83)8w>zmfm;{g@{Eokr=uqU6axU1-tU3{6jCq_Sf2;4&$fV zdii~vOQSAFAH;}H<;%ia&zA)3N>S$F>NI}K{u=cIaYfayCKM)`L{+F0ouO_-3}c(V z(6^xdo|ZcUU;MNt*%|W47Rc|Nec-r0e}#=7$ld|V4YX)JsRflm%II(6pfzC%i)*{5 z0dbJKob#@i7atllv3>0ROZlZwkLi?cXVb;ZP{dPSLAHu9++v72GQV7pyC;LIhtj=b zsx3Tl=ohH>l}jr-Sh#XX;BG^3?$V@`4#FplqNre|$Id!=;=mPtpj~LJJ6a=hx%3#Y zGT3mILmQdC6r2#ob}+FSN>jOD%)6}|FwuD?j3zXH_BgHzS+NcwGErMA_bv<18TZi> z%wf?wK>G@@&J7S>sONpAEu|%(rx6|HHd{p;1^qr(TQGM%ltevU6al8DhjydwU`zH= z9eo^F42#~^8U0yA^ru%cmsFLJINH{GiD_EBvVUfuC&9n-D|(q3;?4*)}gm58^_)7s0afqTxKs}ElvWt8^`?me1kNkBTrrpKBU%) z`f6OsTgi0~x{9cS8Qik0aq3To@rEgkRWhyk zh28-~v|~G)mrrZvvAG-V27vZ24+xpQFHB2Bp&hn<;mfF5Fe;JiA*}Yjg)P~+TbOGU ziXH;Hy0lYDG(I@Y9gI$K;|X?^nKoND9D}4G>%p89@hqU)Yiv$1>ep!T;kbRKk`gUW6j35NSVnfE0kd-wI?U5MS+91 zkATk-Uy%Cjs)G}0o+dduw86onpMLgvikJOM1Zo$Cew4N%dViG-%OM+2{ZcDG+%y*> zJ3k*h9fz2U^Ziy3@!LT;?IW$RlhGhR^hM**j3B!oP1T8n>spGedJI9X--zqP-%J?T zj%7Xn?80;!wAs;Qrp8G{naFgX{vbW#Y&qh z<2!GJ!7gy5%3R<|4Ksp91LXFwJLYC;PQT$`NI)FtjN%axbAG@01gHv?T82^kr<BgHZA!W&~bsB%fpVx)$7djTP7$}+v>lFT36s*ngSfZkB;Nd_C^WWQ=Jx0c|lx=QwY{2$ow!~L5KBTaM#A1Gi2=Y-JMu|7$ z9VH$l7&`7oVC=qgBt{-&i>ZU$tv;ob@sA?FB)rE9RD7;Z#Q$;2G5eTnw>P#o1%cT0 zy`Xi>(N_-MR>_gtiOxr34DCFZ=5Bq?;+)chL#owTUX`fQ-M3tU4z zWIZ4i6$x@V9kIaTM(*$7utMFaKWUkn_IcyVEsmQH4;wZjRcZ1ZZkKnsNJ5U}I6As`ALr zeYtW%2%%p*10X;V5NBeMKbovjh$4SPr|U7Jg*92z*O2Rq!Ou=(@Uy3-(_(?(P!ylp z@<2+-rT_iEA;RjBW$Yj4T)T&nTZ9?W!VTCrC9IhfQjJH7c!61yONN{vfYeCC2KdLJ zYk~{xs@m~p6dF|e=pi$*y&Ym7n))GH;^CVTDLVe0HS;-Gx(AcG`LCE-O)(AM{lO;R zreap~<&ypWgzc3~S;)dhb^rU=>OOb`FKB4Ohi5Sm-L6oxyH{JQd@mmU2y-<+^0k^N zl+03v-;ar-B0Kz0i-IF<1pj4$3itY|3}{%>Soo%OwjubnzLizl(*T?!$cRs^$h5E= z9x%M>wl*`%A1T0IBf$bK!k#Y(5d!!$OCu^p?ux{bbLnW%%3-=?A@D3&MtV)J)#_Be zj-!u57TtmfatK&MfX+Yh9Y)+3tLL*xi{E(j?0UKc815nkhIy`r0u5}4aK|>)>jwH* zuWdhVok=OeHMw4^UYnkEVb3y~ADnModXm7$7uWLQ@GT95J||?xwQP7Ug+3a1`Cw%3 zL(Z_AZBZ%tm$D3MMzV+WHM`xC`cga@+)=MvpQw3%Y1d-?oqDX&9>1XP8xezR!BT$V zMkB*VM0_}qbMPpzI)nzldyC8x8_zN->O<#-SM2ezV3xVb@(2dn8F++yj^PGjs>c6> zev7S;*m^cL3^opfSLgk7N${Fezkv4*4YCm**R`UR;%#GR;{!_9x~kv`mX;2ICg0$! z+9mG8(kxlS$TC0Pl=0tgQ5fh;@cfxivPKKv4VbGW^c3!RPsZd<(oiW~dFn#a{*(Cg zOg!w-Q_4Q;z#Mcb`du4xD}Y}4irRGlhqCwN=-@Bj1gLt-Qx=>==o5-%o7*A$;YZY^ zg&eRizr7v`_@N2dLeRTaCYW@eH@C@`C~ik?N*49`ZX{%{b_4`{7QZO1cyL0@uHFu! zYsujszDGU@7B~mKi2cm5f7OKBWG&8# z)!02CqC9;9$=ekFdBjtORptxlM(JYy=q4cLY7^9v^kQ+ZB%BwB*NF%P)BW74fWE`_ zKQc$hyfUoJs;}*p-KUTSye%Ne9Cx7FShtHmwm*x}E|XgizyYRi(|oaF27Vo+9_7_^ zzBxQh>2Nxvm&JK5pGoyPMO`39ZbIiMw?{YCqUK=uCE$RA|3&iEmVDESg=~Do%wJbX zW7j2SdrnWkHdPAy2c@({dC8m%Fmytr4x#iPwj5EL;e^A>`VTxw~_u))Nup` z$->r*alGu-(u9m3;trryz0~Ip56ye>knLj-)3~~mQqXa7|6#?-KWziI5Up(eEQFy;$^sPd$d<_`yf zR22yT+W$levFE~I!B1rGS6mn^YV7IgTBj7sr>QPMuOiIoxqI|f#`;=Mada)aigc1UOTb9F`}# zqvd-zqDSd}^V5}|1f_F@C!5z;a;Q@;*#|5OZGuZ{ISR~u8J-439_O3HzBh0D@Z+}C zg#BbO#GH|JK4}1baa}&E6XX^T)OMt0?9GbOq|(H1VH%=X$qG}!rVPC>QUCm%W6M7t zoxEfxQFj~&-li^@8Sg!8Aq57DZM9(qI=kSOn zM>Z%Pg~(l-+O>kq1VX3#`ou6azCc;aVY53bMq+Oj#qvRc?QVfpCLDgvF*c5tjGcYG z#zNrnNy^aUz&EdP7Hg#kv1@&M%klf&4s-+=O0za^Lz)N%?hJ41k!d$lR>oUArW!#E zEyXhq&aDZ9GG~B~(D?V;1Al~j+saBxNXx&X-^IN1z6!QBaFkiwsi}KrWF>}MW>A{j z#zjI2y~lIqpj-))Ij*YYaxHt;WTN$*@7~31DP$a^llC^IV)aqUHFoc3%%qBz%Tl?6mQ9rIEB# zvcw=_+k%5QWBN^NCD6>so!%^R6SywqMDelqyvf)Tbenw4wmo*7Y!_1}6wjSdjA?TH zwqBKjYNkIMJqmV+$$HOSqx(AZ3N)?!8_GB3D!Si@002u5SBx;d(qB*(`hlrKE8&CO+sCzA zSVUy~h!z)VBSpUq{lP`86Qr|e9y(fc4*aX3(BUrX!DD8Z`^z4@(NR83SU-B&CI0Ya zTle3|kHD~oOZ6)ct}aM4H8ow`Akq8I`p)61nOUg20EU{yD$2a?L^NPj)xTad%JNQS z%~<44C@g!j7L{!RB0inl!NP@;8vO=Tf+}{0NhB95>QsyXrT*qLlQ%6k1udG)m0Gz3 z<@XtaMWO`i!I2`G8mM1$rW53l-{U$-HR~taP`+Nmz|FSrY)Ct{s|teRA;k1iv(gY< zkfGI;6RenInjlbYthUZYdb!(+uzbz-@-JMgV$ZfwG&wi&!wm7Fxyt{a1P{MXtQ-D{ zyEjtJ`wH))$SX4pl4dHZXD=ri^9>|Y-2y6>XHaC4ygcm@Mi@mrnR$+^GB*;rU{S0; z^QOyr{q@`2HKj$~xUNjeX?guCw=k=}c;6Q_)%b>j>xg+YKGfwux;Q_H$0N4KXKLx{ z>uB-2F^m;C@|Or;3nAX_*x_gnpnEkLArd*!%xmue@!$G$66h#mer1ZyeiPuVO1?zN zgnMlKVtt&lFtZe?vo%XLw(Y_@-Oc_mcuM7w^C?n8t5@}t5oLv) zuC&z3NA-fnRn~&XS-Qv%U%y6G#R!L2g_eo)$;q52h!raOYzXJV+t7l#x8Ij4lR@ITIxZW*qP z^D7r47=HK3f2icr>B%jUq2)H8! zl43ToGZ|02{@|qe{UiRw@N}qD4l^WWnk5!5!(q3Y&0F|o?y=uKj@)1?zWdNG<8~D- z(J@-0Q*O5Hocg_NHJOq$oJ{0~N{J5@oa}b=>^PU*3Ec%WJ=S<@|AjS%E(?7KK4mBYujB_VH zIXs^9Qz?y~62Uf4*b%1ecx^QHGF(y&h&|yGkv5FAf63WfT`LgT7^fv!SV0&bkD`ng3p*fstz6R(>XZt_K@@z+daZ%=nyc48 zYFhvT;Qz@SBEzN43)11m=524JHKS7&Bm4RCcX%jc$W+%dzmN(Uc5&9YB%~E*IfO$K z@B$x3?U585L5K=?U4wc~CM~B=Ct>uAoL|$G*OH2mkp0eG(fUA|E1VJkg4RtrHA7yp zJ$3XL6;h?V+M&2$&kY2v(P!Y=!UkF(O?JdpVNa+bBJ7_U7H zw6eTbZx@z3vVrv1IH;wQ>$Br#QXZ5g0ca`vvjf8M3ukT_gUffq^$!8YSIPKK4j_~9 z`8+@uN(QDJMQe80P<&aACiDD?@7KCq!Uu)^&!ozv_^Of#3UEaDLYOcn9rdQ@)5|%C zEOGw_+vDg65}rA6G~&AkpSWzS6{AH7$#1#LFZZstZ}6~YuX!oAcTB?1$4SFzI%{u( zT>qOnd%il*Iz349VyZwK9)EaGBmKbwaxBDbIv=&Mng4Y(!wuO=dU$)(vT-D-0mP@B zP-$A>>XLLqG(>`1U#;tA7H-YqOY493VQ)J*(rJ+bH=Gy^;ks3sIZ>d@%=U?!KNgN$ z>ONZlNj3f^`1?E!zesPej20`|8BQ<2B-TcmIBCksp-4Z=294YhcA_lj-iSVaCYpE& zKR(-BBxt%8gLt=SRWXD|*}wS2nSMHHT%h>y$j8AW>DV54FU{~8N7GtXOfLU<{qpQW zBcoM_iOz@GWDg6 z4Eb4IDsNlhhaSjgr252Nb ztyFoX;7q@fFhQjX-a&3nAnl})r zL3uj229OgLB0;zwVBqKx{45R zi+!ro)o!hw$*BVFZxZ6Tk7|lhD=prd%|=H}C;w&8zlO^nL&t=3)p4>#8XU6b>@v9{ z9|&r^Mdqxu@j-UCj?KJKmp-RWwKBgo8WN%$+9TiR?kNP2&Ju#sxF5b87csQIaK|w{ z?p5uvv(FLUh{j=^D8SLvveNo6aiK#ELz;lCV2eyRpJ;}RzVQ-BZMpa^>Bz53tVT58 z6RYCsVoU|Msa6{S1&Xflprc_?(RP;mLq(gkwz<1;ag=XaXlI^h&RUK9$-ZZpT06gEo_SallMdi-!bc~?bn zuRpJHskZltXs4ab{F>Ff?U;cVP5`6Xdopaef(V>IOcucV2SRVzE~X(@c)A)hP0wht zfCi@mTx-Nbhk79l%c8}0*l7Gzlb7-BSWU%=fL5%)gPvGTH6n#K`bUu5qdiOl`4ds# z8u{`|XevF!&{JkGgK{Ltq?2s?zpnc3bmigB6c?t+{@`ETC3DScua}&()h`%xZTbgo z51g}Vqt^`|75c!<@DBg4!Bxw8@Wgotm6c%=BYCSKU)s{now5Mq2r8>>W9~0nOK~RDRCkq}xZC8d)_=V-be0y1-&H8UkbXQ<9E4{)n7m|xd;)~n2dJ6ML9 zZUG`cj4?efgz4(WHCXaOIh`CI3nekG9s%u54(bK~EA3%I622DrwR}+VS?}-5+R9E3 z#j2!o^k66Hpxy2xR^ysgk7PhdVRbJP2+p{0fD&iw?<#BQLlto@D&RZ-ki8}L{njKD z&eNkk`W5#DbNQvLI3>wJvJ~@E(4y=>*yEZNE0|o|UYJRkduxMAN|>n83it(67W^E; z0ko0s?}oYmQLild=xRS`=A@5OXx-d&&l6tUF{sYM)sdH7!seTh%P!XT7o>>II$Z4N z!TdDP(J-p11p&g$73cyFslFJ7l8Z5<{EJq2HTQ^V zHozOYRip8-y2;*Rb7g4>4XhN8ino&tvG>1 zH!0cqrpxNgiv06zi{SOaVkBb7K=gP7^6(||UE*}+QJ}*u19fuJY%_;a3c*|}!#O)q zIydmT#cPAQ9^iKw55H8LfYsY8aXA!~7*C-$F%r_Z>hFW_YCA6BC01v0xl78w0QCEf zwO}XUNJb#M_haJe`BuQwwflgybaUkO{XS$f2~W5w#)q$M4el+|yK4-1^iN_v^;&{_ zYX<|%s7tdeEhBf-u~4=0{O(Nh3b|7+^3b#$C)n6OKBxq;xQR}IS<_v0&2O1%vDypY zKaY*5`nYFc5nTLcz;_)OKqNATUTB9g;I_@Zp3t7~9sqYrCsm=}zjqQ~Z%pooypB*A zI&^Cq6BCmKC(8LI3*RM0WYO}Kw0JsaohwQD%AF+F6C0?AZ9(j7)U*rmK1|HoJJBBH z7IUlKZTi~}L@&}Bq6ecWWC*hA?OJOj3d>gd}A_*!XShNl%S?8#qpk3IeE^FCX!HZ2e-qDODBc0ejoj5uh zNgVEj|DHTg)F*-C4JJsHCpgLPxA9zmA$aS#XCiR+(W1>&BVpSi<~zJIlHULG;)$`M ztVP?BRofq{1vaz{@cbDJ*3i(<9*ePUZXH@l`Q6dFEVlOa^x$Dy_+InRz1G5SKPNov z{(M9(mca26PdJQt_Ai1?dHsHR4LGOCMRGt#pYBULFsHp9mTFp6F=)QMqbP94^M%2t z@x5%q$0XTW5wjI6_6QV=o%W4 zkZzRj4y9uVMRMrwoM8y*?mNH#z4v^DbN2h5z4l(~k;LoH|4)Yf>2t&yT*f^vvw=@K zRYTI1l<9UPqw(V(zmEoYQ_LEBZLjHcUBvw?cCO*i7Xu|ttwlnjXMKwkZ)ry6_o!|3 z1~(auM~=s(3$ywr%TnB>x|U4iWfRsMq*o^{8k5lr*}!gic?g z?e>XW!H3$)N&E#+oMpgE>eMSqm^79Gd`Lh#OtKbDXVH^w=GA+s;<$5WPqEkJf0P zYXU|C@J9`_cpjqEU&)z~Q!( zo3R7QhpF$^#Ic_EC3@-$XMQ;^_ ze^pp12X@tD<(zXVP?={Z;?8P_tT81)y7^5xqVxJaI8MEC&uZJ|$IGl5R6J#Ct=$UP zB}wJCOHUv-h3RDW)elU(v^T@TOz_);x{z%xO!WpawmD@B{j*!s$LftsA>r}?07gre zKgl4Vbcy?u2cT*YD_djmV0j%-bCUA%=V_qg-$49echuE(%={^fLmVk|jZUV2ps0%X zSOWSJ_y&ExbRgsv^E@LJ_kvT_ehSw9QW$SP-L(7sin_AGK${ug$eCzMgM8{A$ceAq zhF;dEqgz?Q>MU=daL8(jQPsoS8s1i|23ZqpHR0a4z=V6tTm>9s%;55W2X3(r53`p) zS7q}jP@Ik+t6NKl!e?k#+Xbbp0;vl9z!$=|XI8dxlXu3^81JW$-a?(#9nsAv0v~$s z0uL?LEDEEo%1O#HM~^TF6Mdy?zKZ*I{r3y(^G%4IF3#{b>x;tvpz4#gGjbs9o2PQ{ zwXnZYW&iCk&*c2i$hS&oXb!lKlC~uW3I+7sclR7$d@V@cG>P7xwC-GlUHQfSUdd$L zg`@z`Ya_;r|ItFWM6unfe+#D4mRFs$rZFEa`q_AZl;PxzdS<8QP=iaf@6Aj=he*F| zi3hO+l~FO{nBtK31;^nN^}M3&s}zdZtN1=uGS*#s{$=B?a9uO24p~qzK`v(CvZ4kB z$K9XZJje~3oV&FZ=8%t{FlQLun*eQhC@OQQ|FaJ*jfKr2`VSR(5kjDan~AoBy6= zYD=h3h@E!s1_CwQL-k6swLSGy`sU`A+h!Q@_*qxG*8p503pLf#5_6)LqzbE<*e#r~ zJvvETZh@6X8gWt|&vZIJ1)W_~hM>*p+oGrPlen0SAHE$x& zz+>4N8)X}ODL#Yk*G+r{5ej)`hMyX)KDDOJrm*L&CrnNi&JIkhw(xBQ#ZJ_W6wV;o zI|ebbYt-u*_}5e>&KWD8Xk;F>xoB|v?*#j;V-GC)UMRIZ9VKbmTH!yllyI>?sXPiA zxYk)Eu8i`8<|j!zB~8E2j%*Uk8XGs>d)h1K_-hHMYpxI4@z+v8U}|mqvAv6wM2UneCipd#z`i$Rbl_k+#RTM+gPSXC9)&5y>-Vr81+J?ULP?$4PM&L zH;5TQtfY^kOQbKM2B#8`o)9?W$T8Agndw^^wIz*@+g7P}b&-3W)vRRl{%^mzs^ zK7+e>P3dO;`thb-s&hS|emT6_15S>|h;S*f$C2vr-GKXEVidzE^Tc!U+Ohb5V64TGaLh6}DTi(ULd#SuV zV8nvW6X(l0jZp$3irw8Ahw~@z-AY5>zOH}!Qq)hsEJv<$Tz*(OEX>;jCjEQ<9p!pI zmj7R_vGw;pS7=rV2>G%{YR+oseyCJz2a%Mo?KxpbZiqapC3~FyOoDK})9spyX^>bk zwas-lpLDQiee$Zob~uAKOyo2J--YFxY|DxL3~!b>6XyOnqT$bU_dTgsEkAByZTV+{ z`=|VqjuY4T+AQte>~!e(84vJBHv0&!%Xl|B2$Ai6E{Btp;Hp znlIigi?W)GE|?Z;?fYpn9N!d5)W;YPcA?^vaSjuVm)pQ!Xn#96MI|g;`wi6%BLQ18 zKqjOqC3T~92yK#)Oa^Fcb`_mV(HCnFEsCz(j>)X>^{zVe?)vHr1)nSqlmJ4W>wilt z?SIY%Il%*Z2+>Y0)RXf#pU^#H#`+JWc(BwgF4f*G9y@<kl|MJhXSL- z^$*H=d+8Q|V%*gpR_4`Wb(ef+A!AWu5r=OPEW95+Dm>O-W!>@bP#Im-X2L1;BN`kl zf4-wJR4?=775^5w^TG-SihNdP0^}}iX?BgKadFigE za8TN5JTAL4)5)mkd)rI?^Aj~vrrVcjApgX;_WY8#x>{^ZfV$yAq%FUI2G}8cg`OYy zRl(U@4RF)oiX%MhN*gn+rfvU378tBDX6*#Qg3$ZNMZZmdi^iXe@q)S|KR}S^ zn*De}uUn$_RA|U9jlxCKBz0eDh*ynMVy@$H45W2KV$+r6nwjSR`d>#ss@)8Fk{un> za`k9X`*w#)e!x*gy7_d3M&B%UzhVyWKCICmnh$IiWVlNR`6BerMd*u_etMs}E}@X! zCUV>BVb|QDQf0af`!+a`-E|(WoF~Km45Pc%Ka05pcgFFUDnrkMxKVDYdxGDMUY*(m zSvP~e=v`s1C7knT<3OyLvU}#*?8{~vy0ZjXnE<%j)IqmWJ34%mDHgk_@u3=xSM$3P z0l3P6a6~LTgM&A%J8sp|{zALyV`|QCXvvD@#M^k9Vs4pV5j3G1l2hbe45q$lv*eM@ zC0)h`bL|wB+74G~Jxgx3yPcIM+FcGz!HyzN!>|n+bn%A&nPVRU83hCcM*Cf%E)vuq zcnnEPawC{?j388sW;_?C&ed`mdE-m)e#W#H-`I~LXIAsMcH4+L88>_7kaIJjwX+lc z{Gzl8>)3k3>z0$%@bojuECoCT%_y>94hqkln0F#&kO|?uv)qh16U?{pwZ#R8>SKSb zj8aXJydt~z976D|rdh07g0cyw<;)RC3??J_@cKh`d^1lcjgK{ZbA9(~2``PrS!rK4 zSy4k-?<4dSFIrOP z3P4Ri`NdUZU#mt0JW{t1670n?N zI&o`uXrZAMpR?Q)OoR$3=^D-+=l)kF%gLc%Xe*~1FAzQ3;|(h zxJM|YF_wF;syl=JZQaFJk)9uf=N@+%p=Sz1?J9t`x0E3yeWSP5RdlRrSPo54*g^*bK+yJ8heU5 ztzSnrev}D|yp;1FPBLwo9QISMwUZ%~JN^BvZ!;GIgXkNB#tKV)(N>d&ca*BD`5l6( z9JCsJXNSo5vnwe^@bY$rKsCGx;E+0vhr^M2f2UY2x3bkb9NgWRj|XZrxKL#~xgyo< zeTyN(8>GxS?oi9#7~~;#)g63q_3QtasCA4VnzhBT*gfHRM~?$#WFsDM=OXFE9xR|i z8&SMG7n>a*8gx3Wg0$>@90gAqn#qxuB{Zb@b3p*f z{K9uFaAY_S&UIcuEjpj+6T1%c&ERR2OR$(s9YD>yBr51sW`od~&bd z&x-dKdcDh$dJe=t@|OM6&Wu2h93~yDlXW8vzLYss8w+oMRarf6Cbv3v`6zT)_vxj} z`o6a!9Q?~kQwx)3z@j`3s6mFMfNQXIs!&8F z(cm_fQp@bR)zt&8ML!KKQnmC(92edvbOEe1_z4Y-;0TQTyY|Nf3yvX0V%aec< zZ?VTUWBM4$XHB$Hx&7OI8{@cW^7j_xce!f+O~eFUGajG*J*fS(8FXd9zmwqXmzwFY zkU;~!Mk8Mz!^;E6r|1^FD|ynr1$9l>{3(1FFAU--{aU%jE9{&7@N&DVXF`rx!kC^Y z1-7nX>gP3P1rqM?fI~)=`$2S`G>Nc+=ckHIWDsa}v!cHGxc8uU(fYt8Fc?j^p!iof zr337HD%5jL*QCxV)>6%E=@zy3+q1aY8HupWVP6MoTx zZGH))I9fRvmijTaThZ?R?(*h%rLJlq)bMXwg;SifalceMK)WdIHut?Ju#b+L7qIFbqI4%%djw$h z|ClY!U-nU$k-}=(fOnYFC8!V9LQO)S9H=?C5W^rEer|es%n^mQduEh{ZpY8q@|`w5 zFa-hM(a-1%Sp-hx_UOl27Gd^68z2z?`pS`Ocbcz1Z`o4aP$2`&DLK7JUx)jWBtw$D z_$=vwWE=rRiUYbfl{zur=%|AXQrF}_{W2}${(orno~8ezqI zfxWEGs9MNou%VAW+S9+GlvPR%lO&w>6p{m9nFTF~POhk4@kHb`du)*A+ac_ltlx-h z=31%-sP#&h>yKaeA6)PTr7EbhCv!1g>o69jsA;$=B2`fny}4y=CTWoS zU7>TGPw*`}HX(fA%q;od7oL`gSe~X-`o&a`0jwv=b6!M4lX*@nu2g2TN!mOfyie7X zA_amIZbxegZ`|eEDVx0$%lovVBtiwAB^(${aG#sqfKt}2iSj!(d*QpyjrJM%^YwBm zmnb+#Y{vq^f%vZyrEz~_m{R{X&-avLRnjK#OdAq;Mzfp>&T!4v>(*Pv9yC6u)yqrB zw}X5bzoR4EI~}Oo>{-!Sb1O`R<(8bgGk}DnfX&_fAkrd$guokMX0W4GO_q8aTRdA= zOGGY=v#oO+L(Ent(vG+c;Fg=g`EcgeXtx^M+{BKfd@^-GTfq~k^hx3LR&6uq*lYKs zv^WC#e$=`0-g3dde1pmt(&afJ+~w;{)gUGN6V&dGfp4F_dRfew-CXNnGhlW4`4`6- z_8H%e=bUC#Z=I4w_QWi^$c`67`Pw1oK|%~ z-nz&1$yQ>~gmZ_!T3R%sXxmP-iCvWn*7ataf3dCZ`@5zboWR*Ar>s8DL1Ej;ZKc)R zz}Q(<1~x1HoD=vq#z=Vq_0!_A^+mD3^FV1PhKua>>b2YBgM$o#%u(6P&>~jkN(ymt zIwu|VRi0nepK6Q8yVIzh5-?~&ROXV-k(>;S7Q70QW|(q(y>Afq;nU1BdaL*2@{XFo zfJB9nU{_C=hi)($Kjq?YPSuafmN{8%2?cQ+tu5w9tVs=^02NqeIFvk=QupszIJelz zhL;4GaA|$VdzEpy>wAR2SMhrRxjVJj27wme+h9kjb2{ko{L1^km*zK7sQAAJ6tNU{ zU_)A&!QF$gMg(3_5fw~PK7MMKLu}lnziQqZ_qyEYQ;+nsdt6&3<59PmW6s1dW z49kMHsgx)?Ln`tXwwb(*{VwE8N18gLq*36@k}u zM`rs_-O|-sW4j5>F3k5mwU*Y3N9$hbr(F>w&ID#epS9V|R z*b(wu+S17J3X2R`Z?c~En7B|nPwB$~f0u?Z<@l0J6R=hz`?DSGV!EH3!g@6)*_nbD zmc;WtxwWis$g+u3JROwN9mJF${LJGm>IGi+JqvwnW@ukcV5N!hWh;aNk{^#4<7m_ z;=29LC%-h4_T*ls{6qV6SdTCEpR;#|9Zeigjs{*iU2%dw+PZh+yvp^TQ&VxH|_`jo{yk(#-v>OApP)E=ph+>AlD16 zzk+kkNu{PK5w_h$+vFa~k+W2tf)b3&%es0dO$KQ*_KuRQ&3^pLn^U5PS~ug@SNPDg z2PsP#R zpToB?u#ickNES@{xSGxWdmrH5$hQH>j0PvS&WNJz?&&e!CpGkE2UFlB$(<`w}d1o7kaD5kjZYZCcaVs@$ctCE52g zl@BoDhH=6DrqrUmu0gn}wYN^NN^)FsfxYvyYl6lej(^j3-&2%Zk{#_$^{2y!W5W2K znjcN*t_5I8IH<2A4FTl>%4QS4Aw!dy!a>{lQBhIQR)bU&6>Qt*G$IId8t7Y&Fo03$-#^cL%yF$BVvRK~f|nT&}txBHo)w%OPX0c)luI zeB>c5(-vL4hVzo(N5hylb6#B8Q*rEeJY?(by+F3a%bz!823sq|(tfLpoqU--w$x-{ z@uKV^u|$Ujp+w_d79>U@oNl)NQ`<6}ke#Gbor z)^^1W3bJI_4O0v#pw`LY9Lna*Lsv|CGoT=NXLd>)tGlYaK&dDfl%C6+KameHk+PQ0 z;qC6HO?<=iu;I2$x_4W}QQ!u0glx?g*D>*=p@rbwV{P$AebFPv2QTR;rQk3fwU?NL z5K)e=$P^l8k238NOH#P6@ZjbV?KwtyVPjz7j6{7FJ@O{nsEZt}0em~_qiLS?t=+{= z>({niOEZmd?{>Y3bIdKGLMUBG<=9&j&ak!&$9Q!xF4G(k5stpG(yHiq^wV%6pnBuD z?!zPu`Fh)fnVY4&3q|e0BS-es4Liqti`hjCN zZIh`~en^CO&6C&Bc_ODN2JS~I{@5TILL39sI|l7RriTA_2%w|+mh|-WeE#)*_*v-h zDn3|~*-!O7r-B{3hQbt6c(r$fWP{y~j0IU*PWt&*?iUg{aEt9SCpZLva=x-73lSy_ zL*{9HkDke7Dx22qGhqw9i@zq*_XOd1C(-Xwao^a(2ZBx+U-mL+TcFj4zI;a}?#Y5z zefHjCh0l)?{{c(y3+}+wffG@Wm?oW( z28|+;k&Uy8twd^O&nD0-v@C6ZaKDnjl*)za2Na-RkpF|9exzrbAEbzj+o&t4-j9no zZMA(V!D>O_7%(9QHnY~LekYaxm_ zMP?`PF+H9BP8q*jZL01v63ZJLEQ~M0dyB^SBUQmpE7t|BsiGn+a=U)PakXVUQ*%g* z7A?qHtx2QQ%xa5-~ zwFqz`C0fo(u{A06xltU2#uMQTk^=39R&Wlc6ju-)16q*FVK}m7!zYq}sa|eO2}Lb6 zHJl$BSK8SQF1?5_vJc>YzE@aL@r8F^G(IeB)$CpoC)=PvtWb-lE6t6yp9DjLPe&Uz@Ry;@A*mLTr3IS|UH0taB)CX{g zrWp6>4?G6}y;g%Hl1?Mm&}iOl+X5{WYnIgn%du^M&nfjju+<4(MQZdsKX7mI;kcX3me`1u z*E_pAg|t|gHLx6!r_;MR3;(wwnr! z-b$x}TXcfshKZycq-yN|(Aw2ir%h1C^P7E3Wr|Dr_}BfgHEnB}w&F33_zfqit4sH) z_Q73QQ3cOnHt%Ez$;L9*9NV-UG@djF{{1ZzO;8-PZKq8MJenw-`fQyUGkNMGdS(*m z;xc!vv8bHG>F4U6jKq;#heTXT9WGm=q3EV;EoBLl^>3f^&bTE1`jyKeUbbc2yN;Vr zK|XcvklBxtT(UG(z^qJvMKjUbA|q3DgfrzMQJg)~^5Exc?_VhW0TR=nLYuREi+)TFu8 zg!Ht?b!Z#r*acytoxkhv?;r7zmd$?05fS=3k2+QvP8`ErC*7u)Hu2+BeBfRnzENi= zsMsy+)SM64@*)aCz6F#Ej;=RNuB_} zuPRoJz_;L&qxmjRahRJkhEzLN4BS@E_CiVt3pdIpGQ24AU|HmAHRGd9h_B;oU@~K1 zd(3LqJTsE0efdUQ+{)+9#!4#iy)s`A2V~?S9H{0ZO+7P52kZ(moO@eF2^( zm>v~X+4r&4_M65dyhR1Xb4gc{KI82Gk3eV5S%Q4)+*&6_3|g2`UpBX-c!-9{a=cc= zzs>2`sW!tD#AtD~1_Qmo@^024#iooJ4~m!tCU`&AzwoBBh@Sg}cu+)O))i2T4bN9r%vcFxoG-Cb_W{zd}>)?k~hX(7)mW2R0z1dq1VI`6d^j zWCh=n3E;Gm;l=jj<74%Lqmed8vSH-QL9`Tng1FobECmINbP0IV znJ5;`MwZGf8cQxh99f^7s9oJo($Jz)@K%YDH(uu~n8ug1deVu%(lJ;X*7yqLPvs{| z(Q!`4<>!LPzm5YZZ!Pxirb&R#RR)vAzxeJnGUwn7POFxI?pQQ-bx%5G@bBkKZPId8 z)hyg&*qhNM=icD~JH3eRx89a21Nu($tPE}pcR|s`{Z@?LO?{g9bn6#GzY0Tm1x%j(EB3$})Spf-b2jf% z{Xq4=v9ul7Jemb?V>lb7ec=8en4-k^r563qb%4dQq=$j0nQhLTaWDMbwH0&HhdTS9 z?_Dn-kwDL2AoJ-5m9R9oi)Ftbd^CcX-2-aC!-zJ|`07SCCyV~FCJ@ISaU)v|Tt!k_ zm!c9kgftqkG~kaTUpnba$+d2sNj#3wg?3u?h4=hRtiah+%xC^?si{`$X<(l`31l6dZM0yt-)owU;L3tdfi40wlN9*5(1j+pS;Rt zEGX=6VX-`}H1Q%!Fyqbm#Np#I2wJyBYGt!98|W?(&PFtOyd^vk`-+>>{IPggr1(?s z8@4lU)^J$E>vWbLPZP<1-kjRys+5C*0%n%4p=2R82AK|t|D+N$p5I>gEmvj>7?8 z>Q`=_U*}0T7!3-Oy(Fvc6*}U(xm@d*NKBU2Jglz~ERxl@nY4NBpL4ky4zUQRk-iB` zT=9zTN*m=kTjL!^OZ8jmb9d^{W!SSOU#m^=O6!FZuzr{vn=2Zwof-M?N}M-p^L`RP zh^m+fZCqpodDaA8nkoSuPJ0IErP-Pp+J2;cv`E6Sve%oA`D8N;vOR8V930JGS?Ov^ zN1xe>vG;EFX#P;}jrH=}wN2jikMHffaS&4&D_qE`c*BWXk#tauf^5*(1gCLiTY#mfR_e*?RKaT`p?*g%cuiNFoxh>=j|81ZG`y#4T;F-5e=&=1>BvXl&~Q3P z!awosD^|<6c|75l)3Rf!&}EsYk-5807Z!Hem_UI_72f8wS6T2Vy@ z;!WH6*DDK!6B(72`9l73N$hJ`l$xVrZH=wZXR__PkO#it;f>-FOm_9pZP3wvV5iDk zc4`kMDb{IPR5HlDb7h4jFYMG`HrE*~IOl&?AIfZTu)Djv35UaX{{8#cvMcaD+@CPa zR1V!lc4{c;IjLlDzv*!G;GYmQ@?YCHGQvAH7KiTUR?Pzxl`aeq32f(t{5CYsI`a5Y zaFihxBQ<1if>ItN&b$gzE}I>Ff3lH?v+6Q^`3dHmNIsJ42@lu+Z=W+QxZ4&t88baH zqc*9u#*fnSzh3^lJW*vrn4Kof!AOwFM1nZ!S6uVPmoOf8 z!z`$t`xAEYu2Qh-unKWtgc%x6=zsawyRtY;F9n{Va>#tNNI zW@`1PQ!1tlGNR`%h3*snq-6vrVw7J<>4~2$Z3U@Bl!J~;ET2WA2BD4<`#bW|8{t*k zw#i700Uc|bYt}aRMNS!!COzu2**0{5NN0j8FKGMCGhkmA9|<=O5EkK7w)er5(Zuye z7q{N)n6g&Eq4IM|Uf|pAZ=xI$;4(&Wo$$S>?eoLt@Ox@@SC6naI#W5eYe9+)k_m(` z_C4Caoay75gsRGXXr^BPWda)8q#bpRSUe^lsF2M#mhYR%-Un==X%i_X`_FX`YYC9m z!WWlt#@d8wOO{zpmu8#=f(D-77G{2vuhbyEmsrNa^o`hT6}ubKN~dmv2R189O8!bvYuJu<3Ssa%DZP_^;}K zP3SmgK}WsM+`|%9>(zj7e*0fulhvaD_ zNi7=5jrpE{U>2x}PDa_yaW^hZs?R;ApA-?k75Rdg4`{sUV*RIM3^8TF8$l64SC|6} zo~809^KKYwDye}dAEYl~kj9Gzs(-*a*i8?pw)j0@`whm2LCW@FCQQEBfZ|2U$@n