painapple-code 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- painapple_code/__init__.py +30 -0
- painapple_code/__main__.py +18 -0
- painapple_code/_version.py +24 -0
- painapple_code/auth_middleware.py +930 -0
- painapple_code/cli/__init__.py +204 -0
- painapple_code/cli/deploy/__init__.py +15 -0
- painapple_code/cli/deploy/claude_seed.py +41 -0
- painapple_code/cli/deploy/config.py +260 -0
- painapple_code/cli/deploy/container.py +715 -0
- painapple_code/cli/deploy/launch.py +163 -0
- painapple_code/cli/deploy/runtime.py +223 -0
- painapple_code/cli/lifecycle_cmd.py +443 -0
- painapple_code/cli/list_cmd.py +572 -0
- painapple_code/cli/manage_cmd.py +670 -0
- painapple_code/cli/netinfo.py +125 -0
- painapple_code/cli/profiles.py +390 -0
- painapple_code/cli/serve_args.py +163 -0
- painapple_code/cli/serve_config.py +256 -0
- painapple_code/cli/setup_cmd.py +664 -0
- painapple_code/cli/ui.py +438 -0
- painapple_code/cost_analytics.py +879 -0
- painapple_code/data/models.defaults.yaml +18 -0
- painapple_code/data/models.yaml +18 -0
- painapple_code/data/presets.defaults.json +32 -0
- painapple_code/data/strings.yaml +2269 -0
- painapple_code/helpers.py +406 -0
- painapple_code/paths.py +1514 -0
- painapple_code/prompt_explorer.py +984 -0
- painapple_code/providers/__init__.py +224 -0
- painapple_code/providers/base.py +710 -0
- painapple_code/providers/claude/__init__.py +74 -0
- painapple_code/providers/claude/capabilities.py +218 -0
- painapple_code/providers/claude/errors.py +79 -0
- painapple_code/providers/claude/launch.py +50 -0
- painapple_code/providers/claude/summary.py +89 -0
- painapple_code/providers/claude/translate.py +74 -0
- painapple_code/providers/claude_sdk/__init__.py +139 -0
- painapple_code/providers/claude_sdk/driver.py +412 -0
- painapple_code/providers/codex/__init__.py +87 -0
- painapple_code/providers/codex/capabilities.py +321 -0
- painapple_code/providers/codex/errors.py +70 -0
- painapple_code/providers/codex/launch.py +142 -0
- painapple_code/providers/codex/summary.py +196 -0
- painapple_code/providers/codex/translate.py +204 -0
- painapple_code/providers/codex_app_server/__init__.py +100 -0
- painapple_code/providers/codex_app_server/capabilities.py +41 -0
- painapple_code/providers/codex_app_server/launch.py +202 -0
- painapple_code/providers/codex_app_server/summary.py +291 -0
- painapple_code/providers/codex_app_server/translate.py +263 -0
- painapple_code/providers/codex_app_server/transport.py +238 -0
- painapple_code/routes/__init__.py +0 -0
- painapple_code/routes/api_agents.py +441 -0
- painapple_code/routes/api_app.py +368 -0
- painapple_code/routes/api_app_commit_sections.py +127 -0
- painapple_code/routes/api_app_config.py +997 -0
- painapple_code/routes/api_app_session_prefs.py +217 -0
- painapple_code/routes/api_browser.py +690 -0
- painapple_code/routes/api_commands.py +374 -0
- painapple_code/routes/api_costs.py +128 -0
- painapple_code/routes/api_drafts.py +139 -0
- painapple_code/routes/api_exec.py +149 -0
- painapple_code/routes/api_files.py +542 -0
- painapple_code/routes/api_git.py +657 -0
- painapple_code/routes/api_logs.py +649 -0
- painapple_code/routes/api_plugins.py +275 -0
- painapple_code/routes/api_project_config.py +418 -0
- painapple_code/routes/api_prompts.py +231 -0
- painapple_code/routes/api_providers.py +53 -0
- painapple_code/routes/api_search.py +411 -0
- painapple_code/routes/api_session_stash.py +234 -0
- painapple_code/routes/api_session_welcome.py +151 -0
- painapple_code/routes/api_sessions.py +742 -0
- painapple_code/routes/api_shadow.py +323 -0
- painapple_code/routes/api_shadow_db.py +512 -0
- painapple_code/routes/api_shadow_files.py +332 -0
- painapple_code/routes/api_shadow_search.py +476 -0
- painapple_code/routes/api_skills.py +610 -0
- painapple_code/routes/api_tasks.py +147 -0
- painapple_code/routes/api_terminal.py +540 -0
- painapple_code/routes/api_upload.py +293 -0
- painapple_code/routes/api_viewer.py +229 -0
- painapple_code/routes/dependencies.py +109 -0
- painapple_code/routes/ws_chat.py +837 -0
- painapple_code/server.py +2208 -0
- painapple_code/server_logging.py +263 -0
- painapple_code/services/__init__.py +0 -0
- painapple_code/services/agent_session.py +2738 -0
- painapple_code/session_store.py +253 -0
- painapple_code/session_store_core.py +879 -0
- painapple_code/shadow_db.py +830 -0
- painapple_code/shadow_db_plans.py +369 -0
- painapple_code/shadow_db_queries.py +595 -0
- painapple_code/shadow_db_schema.py +676 -0
- painapple_code/shadow_git.py +1296 -0
- painapple_code/shadow_git_frontmatter.py +314 -0
- painapple_code/shadow_git_sections.py +520 -0
- painapple_code/shadow_git_summary.py +462 -0
- painapple_code/shadow_parser.py +560 -0
- painapple_code/static/css/00-variables.css +276 -0
- painapple_code/static/css/01-base.css +245 -0
- painapple_code/static/css/02-tooltips.css +36 -0
- painapple_code/static/css/09-left-rail.css +448 -0
- painapple_code/static/css/10-header.css +777 -0
- painapple_code/static/css/11-connection-bar.css +120 -0
- painapple_code/static/css/20-messages.css +750 -0
- painapple_code/static/css/21-tool-blocks.css +2340 -0
- painapple_code/static/css/22-edit-diff.css +619 -0
- painapple_code/static/css/23-thinking.css +1584 -0
- painapple_code/static/css/24-background-tasks.css +359 -0
- painapple_code/static/css/25-context-block.css +252 -0
- painapple_code/static/css/26-session-setup.css +144 -0
- painapple_code/static/css/30-markdown.css +854 -0
- painapple_code/static/css/40-input.css +2418 -0
- painapple_code/static/css/41-autocomplete.css +127 -0
- painapple_code/static/css/42-activity-strip.css +97 -0
- painapple_code/static/css/43-file-autocomplete.css +121 -0
- painapple_code/static/css/44-snippets-autocomplete.css +224 -0
- painapple_code/static/css/45-selection-bar.css +433 -0
- painapple_code/static/css/46-keyboard-bar.css +119 -0
- painapple_code/static/css/47-keyboard-longpress.css +116 -0
- painapple_code/static/css/47-token-profile.css +106 -0
- painapple_code/static/css/50-welcome.css +3095 -0
- painapple_code/static/css/51-modals.css +301 -0
- painapple_code/static/css/52-utilities.css +77 -0
- painapple_code/static/css/53-image-upload.css +456 -0
- painapple_code/static/css/54-todo-list.css +102 -0
- painapple_code/static/css/55-file-upload.css +113 -0
- painapple_code/static/css/56-chat-navigator.css +218 -0
- painapple_code/static/css/56-permission-card.css +332 -0
- painapple_code/static/css/56-plan-approval.css +204 -0
- painapple_code/static/css/56-question-form.css +602 -0
- painapple_code/static/css/57-swipe-indicator.css +95 -0
- painapple_code/static/css/58-session-families.css +627 -0
- painapple_code/static/css/59-about.css +196 -0
- painapple_code/static/css/61-log-explorer.css +723 -0
- painapple_code/static/css/62-json-tree.css +489 -0
- painapple_code/static/css/62-skills-widget.css +650 -0
- painapple_code/static/css/63-commands-widget.css +30 -0
- painapple_code/static/css/63-plugins-widget.css +174 -0
- painapple_code/static/css/63-terminal.css +516 -0
- painapple_code/static/css/64-search-files-widget.css +448 -0
- painapple_code/static/css/65-snippets-widget.css +426 -0
- painapple_code/static/css/66-active-sessions.css +979 -0
- painapple_code/static/css/66-agents-widget.css +265 -0
- painapple_code/static/css/66-file-explorer-widget.css +855 -0
- painapple_code/static/css/66-git-panel.css +791 -0
- painapple_code/static/css/66-history-explorer.css +1496 -0
- painapple_code/static/css/67-diff-viewer.css +602 -0
- painapple_code/static/css/67-editor-view.css +748 -0
- painapple_code/static/css/67-file-preview-widget.css +1873 -0
- painapple_code/static/css/68-browser-widget.css +337 -0
- painapple_code/static/css/68-compare-wizard.css +355 -0
- painapple_code/static/css/68-csv-preview.css +106 -0
- painapple_code/static/css/68-diff-preview.css +103 -0
- painapple_code/static/css/68-helpers-install-widget.css +491 -0
- painapple_code/static/css/68-image-annotate.css +346 -0
- painapple_code/static/css/68-terminal-view.css +98 -0
- painapple_code/static/css/68-uploads-widget.css +229 -0
- painapple_code/static/css/69-cost-analytics.css +537 -0
- painapple_code/static/css/70-config-panel.css +3736 -0
- painapple_code/static/css/70-excalidraw.css +136 -0
- painapple_code/static/css/70-prompt-explorer.css +890 -0
- painapple_code/static/css/71-chart.css +111 -0
- painapple_code/static/css/72-discussion-sidebar.css +678 -0
- painapple_code/static/css/74-debug-logs.css +656 -0
- painapple_code/static/css/80-open-dialog.css +146 -0
- painapple_code/static/css/80-quick-switcher.css +383 -0
- painapple_code/static/css/80-widget-system.css +1763 -0
- painapple_code/static/css/81-quick-actions.css +1020 -0
- painapple_code/static/css/82-context-popover.css +1233 -0
- painapple_code/static/css/83-context-menu.css +339 -0
- painapple_code/static/css/85-lazy-loading.css +102 -0
- painapple_code/static/css/86-zen-mode.css +708 -0
- painapple_code/static/css/87-grid-switcher.css +346 -0
- painapple_code/static/css/login.css +198 -0
- painapple_code/static/dist/app.bundle.js +7177 -0
- painapple_code/static/feature-triage.html +446 -0
- painapple_code/static/icons/apple-touch-icon.png +0 -0
- painapple_code/static/icons/favicon-32.png +0 -0
- painapple_code/static/icons/favicon.ico +0 -0
- painapple_code/static/icons/icon-128.png +0 -0
- painapple_code/static/icons/icon-144.png +0 -0
- painapple_code/static/icons/icon-152.png +0 -0
- painapple_code/static/icons/icon-180.png +0 -0
- painapple_code/static/icons/icon-192.png +0 -0
- painapple_code/static/icons/icon-384.png +0 -0
- painapple_code/static/icons/icon-512.png +0 -0
- painapple_code/static/icons/icon-72.png +0 -0
- painapple_code/static/icons/icon-96.png +0 -0
- painapple_code/static/js/action-delegate.js +187 -0
- painapple_code/static/js/activity-strip.js +193 -0
- painapple_code/static/js/app/context-menus.js +619 -0
- painapple_code/static/js/app/file-preview.js +163 -0
- painapple_code/static/js/app/init-ui.js +574 -0
- painapple_code/static/js/app/input-events.js +561 -0
- painapple_code/static/js/app/input-resize.js +313 -0
- painapple_code/static/js/app/message-actions.js +419 -0
- painapple_code/static/js/app/panels.js +210 -0
- painapple_code/static/js/app/rendering-delegators.js +246 -0
- painapple_code/static/js/app/server-loading.js +365 -0
- painapple_code/static/js/app/session-ops.js +287 -0
- painapple_code/static/js/app/session-switch.js +443 -0
- painapple_code/static/js/app/settings-helpers.js +280 -0
- painapple_code/static/js/app/startup-welcome.js +290 -0
- painapple_code/static/js/app-context.js +157 -0
- painapple_code/static/js/app.js +2134 -0
- painapple_code/static/js/auth-fetch.js +46 -0
- painapple_code/static/js/background-tasks.js +274 -0
- painapple_code/static/js/boot.js +73 -0
- painapple_code/static/js/caret-position.js +171 -0
- painapple_code/static/js/chat-navigator.js +710 -0
- painapple_code/static/js/chat-search.js +323 -0
- painapple_code/static/js/command-executor.js +176 -0
- painapple_code/static/js/command-store.js +497 -0
- painapple_code/static/js/components.js +1414 -0
- painapple_code/static/js/config.js +218 -0
- painapple_code/static/js/context-menu.js +740 -0
- painapple_code/static/js/controllers/chat-controller.js +4252 -0
- painapple_code/static/js/controllers/dialog-controller.js +217 -0
- painapple_code/static/js/controllers/tab-controller.js +1828 -0
- painapple_code/static/js/controllers/thinking-controller.js +915 -0
- painapple_code/static/js/diff-utils.js +486 -0
- painapple_code/static/js/drafts-pill.js +95 -0
- painapple_code/static/js/editor-view.js +815 -0
- painapple_code/static/js/effort-settings.js +430 -0
- painapple_code/static/js/feature-triage.js +811 -0
- painapple_code/static/js/file-autocomplete.js +516 -0
- painapple_code/static/js/file-tabs.js +123 -0
- painapple_code/static/js/gestures.js +448 -0
- painapple_code/static/js/grid-switcher.js +334 -0
- painapple_code/static/js/input-handler.js +1472 -0
- painapple_code/static/js/keyboard-bar.js +495 -0
- painapple_code/static/js/layout-switcher.js +109 -0
- painapple_code/static/js/linkify-utils.js +200 -0
- painapple_code/static/js/login.js +141 -0
- painapple_code/static/js/open-dialog.js +879 -0
- painapple_code/static/js/orphan-terminals.js +373 -0
- painapple_code/static/js/path-utils.js +179 -0
- painapple_code/static/js/perf-marks.js +102 -0
- painapple_code/static/js/permission-settings.js +475 -0
- painapple_code/static/js/pointer-input.js +59 -0
- painapple_code/static/js/preview/json-tree-menu.js +226 -0
- painapple_code/static/js/preview/json-tree-toolbar.js +291 -0
- painapple_code/static/js/preview/json-tree.js +457 -0
- painapple_code/static/js/preview/preview-breadcrumb.js +387 -0
- painapple_code/static/js/preview/preview-edit.js +367 -0
- painapple_code/static/js/preview/preview-events.js +361 -0
- painapple_code/static/js/preview/preview-history.js +567 -0
- painapple_code/static/js/preview/preview-inline-edit.js +869 -0
- painapple_code/static/js/preview/preview-poll.js +108 -0
- painapple_code/static/js/preview/preview-render.js +572 -0
- painapple_code/static/js/preview/preview-search.js +421 -0
- painapple_code/static/js/preview/preview-state.js +256 -0
- painapple_code/static/js/preview/preview-utils.js +251 -0
- painapple_code/static/js/preview-plugins/chart-plugin.js +22 -0
- painapple_code/static/js/preview-plugins/csv-plugin.js +188 -0
- painapple_code/static/js/preview-plugins/diff-plugin.js +258 -0
- painapple_code/static/js/preview-plugins/excalidraw-plugin.js +25 -0
- painapple_code/static/js/preview-plugins/html-plugin.js +104 -0
- painapple_code/static/js/preview-plugins/image-plugin.js +20 -0
- painapple_code/static/js/preview-plugins/index.js +34 -0
- painapple_code/static/js/preview-plugins/json-plugin.js +99 -0
- painapple_code/static/js/preview-plugins/jsonl-plugin.js +152 -0
- painapple_code/static/js/preview-plugins/markdown-plugin.js +266 -0
- painapple_code/static/js/preview-plugins/panzoom-plugin.js +63 -0
- painapple_code/static/js/preview-plugins/plugin-helpers.js +282 -0
- painapple_code/static/js/project-color-picker.js +110 -0
- painapple_code/static/js/project-colors.js +163 -0
- painapple_code/static/js/prompt-favorites.js +187 -0
- painapple_code/static/js/quick-actions-menu.js +1736 -0
- painapple_code/static/js/quick-actions-registry.js +1239 -0
- painapple_code/static/js/quick-switcher/controller.js +294 -0
- painapple_code/static/js/quick-switcher/fuzzy-scorer.js +123 -0
- painapple_code/static/js/quick-switcher/index.js +30 -0
- painapple_code/static/js/quick-switcher/providers/base-provider.js +51 -0
- painapple_code/static/js/quick-switcher/providers/command-provider.js +56 -0
- painapple_code/static/js/quick-switcher/providers/file-provider.js +291 -0
- painapple_code/static/js/quick-switcher/providers/panel-provider.js +132 -0
- painapple_code/static/js/quick-switcher/providers/project-provider.js +361 -0
- painapple_code/static/js/quick-switcher/providers/read-files-provider.js +104 -0
- painapple_code/static/js/quick-switcher/providers/skills-provider.js +129 -0
- painapple_code/static/js/quick-switcher/registry.js +69 -0
- painapple_code/static/js/quick-switcher/ui/item.js +54 -0
- painapple_code/static/js/quick-switcher/ui/picker.js +464 -0
- painapple_code/static/js/recent-opens.js +40 -0
- painapple_code/static/js/scroll-manager.js +530 -0
- painapple_code/static/js/scroll-state-machine.js +359 -0
- painapple_code/static/js/selection/action-bar.js +793 -0
- painapple_code/static/js/selection/index.js +15 -0
- painapple_code/static/js/selection/selection-handler.js +877 -0
- painapple_code/static/js/selection/state.js +181 -0
- painapple_code/static/js/session/agent-progress.js +202 -0
- painapple_code/static/js/session/handle-agent-message.js +481 -0
- painapple_code/static/js/session/handle-message.js +550 -0
- painapple_code/static/js/session/interactive.js +587 -0
- painapple_code/static/js/session/message-store.js +271 -0
- painapple_code/static/js/session/messages.js +253 -0
- painapple_code/static/js/session/persistence.js +179 -0
- painapple_code/static/js/session/restore.js +136 -0
- painapple_code/static/js/session/sync.js +333 -0
- painapple_code/static/js/session-container-pool.js +371 -0
- painapple_code/static/js/session-setup-panel.js +274 -0
- painapple_code/static/js/session.js +1047 -0
- painapple_code/static/js/shortcut-hints.js +272 -0
- painapple_code/static/js/shortcuts.js +1146 -0
- painapple_code/static/js/skills-autocomplete.js +262 -0
- painapple_code/static/js/snippets-autocomplete.js +738 -0
- painapple_code/static/js/stash-refs-view.js +83 -0
- painapple_code/static/js/stash-ui.js +768 -0
- painapple_code/static/js/stash.js +785 -0
- painapple_code/static/js/status-bar.js +1184 -0
- painapple_code/static/js/token-profile.js +274 -0
- painapple_code/static/js/tool-renderer-blocks.js +1626 -0
- painapple_code/static/js/tool-renderer-thinking.js +813 -0
- painapple_code/static/js/tool-renderer.js +1500 -0
- painapple_code/static/js/tooltips.js +197 -0
- painapple_code/static/js/upload-manager.js +1019 -0
- painapple_code/static/js/utils.js +521 -0
- painapple_code/static/js/welcome/api.js +363 -0
- painapple_code/static/js/welcome/cards.js +626 -0
- painapple_code/static/js/welcome/context-menu.js +585 -0
- painapple_code/static/js/welcome/families.js +801 -0
- painapple_code/static/js/welcome/preview.js +234 -0
- painapple_code/static/js/welcome/state.js +129 -0
- painapple_code/static/js/welcome.js +1874 -0
- painapple_code/static/js/widget-system/base-widget.js +717 -0
- painapple_code/static/js/widget-system/device-manager.js +178 -0
- painapple_code/static/js/widget-system/event-bus.js +152 -0
- painapple_code/static/js/widget-system/icons.js +120 -0
- painapple_code/static/js/widget-system/index.js +49 -0
- painapple_code/static/js/widget-system/init.js +54 -0
- painapple_code/static/js/widget-system/types/bottom-sheet.js +307 -0
- painapple_code/static/js/widget-system/types/floating.js +915 -0
- painapple_code/static/js/widget-system/types/index.js +40 -0
- painapple_code/static/js/widget-system/types/modal.js +147 -0
- painapple_code/static/js/widget-system/types/sidebar.js +158 -0
- painapple_code/static/js/widget-system/types/tab.js +146 -0
- painapple_code/static/js/widget-system/types/top-sheet.js +203 -0
- painapple_code/static/js/widget-system/widget-manager.js +1187 -0
- painapple_code/static/js/widgets/about-widget.js +260 -0
- painapple_code/static/js/widgets/active-sessions-widget.js +760 -0
- painapple_code/static/js/widgets/agents-widget.js +897 -0
- painapple_code/static/js/widgets/browser-widget.js +579 -0
- painapple_code/static/js/widgets/commands-widget.js +694 -0
- painapple_code/static/js/widgets/config/commit-sections.js +449 -0
- painapple_code/static/js/widgets/config/dir-autocomplete.js +224 -0
- painapple_code/static/js/widgets/config/gestures.js +161 -0
- painapple_code/static/js/widgets/config/models-tab.js +988 -0
- painapple_code/static/js/widgets/config/quick-actions-tab.js +882 -0
- painapple_code/static/js/widgets/config/shortcut-editor.js +587 -0
- painapple_code/static/js/widgets/config/state.js +517 -0
- painapple_code/static/js/widgets/config/system-controls.js +90 -0
- painapple_code/static/js/widgets/config-widget.js +1535 -0
- painapple_code/static/js/widgets/cost-analytics-widget.js +621 -0
- painapple_code/static/js/widgets/debug-widget.js +693 -0
- painapple_code/static/js/widgets/diff-viewer-widget.js +1420 -0
- painapple_code/static/js/widgets/discussion-widget.js +1107 -0
- painapple_code/static/js/widgets/file-explorer-widget.js +2166 -0
- painapple_code/static/js/widgets/file-preview-widget.js +861 -0
- painapple_code/static/js/widgets/git-widget.js +794 -0
- painapple_code/static/js/widgets/helpers-install-widget.js +720 -0
- painapple_code/static/js/widgets/history-explorer-widget.js +1804 -0
- painapple_code/static/js/widgets/image-annotate-widget.js +951 -0
- painapple_code/static/js/widgets/image-preview-widget.js +692 -0
- painapple_code/static/js/widgets/index.js +176 -0
- painapple_code/static/js/widgets/log-explorer-widget.js +1057 -0
- painapple_code/static/js/widgets/plugins-widget.js +305 -0
- painapple_code/static/js/widgets/prompt-explorer-widget.js +1119 -0
- painapple_code/static/js/widgets/search-files-widget.js +529 -0
- painapple_code/static/js/widgets/skills-widget.js +843 -0
- painapple_code/static/js/widgets/snippets-widget.js +479 -0
- painapple_code/static/js/widgets/sub-agents-widget.js +333 -0
- painapple_code/static/js/widgets/tasks-widget.js +379 -0
- painapple_code/static/js/widgets/terminal/connection.js +283 -0
- painapple_code/static/js/widgets/terminal/gestures.js +357 -0
- painapple_code/static/js/widgets/terminal/init.js +642 -0
- painapple_code/static/js/widgets/terminal/link-providers.js +532 -0
- painapple_code/static/js/widgets/terminal/osc52.js +213 -0
- painapple_code/static/js/widgets/terminal/render.js +159 -0
- painapple_code/static/js/widgets/terminal/size.js +54 -0
- painapple_code/static/js/widgets/terminal/state.js +211 -0
- painapple_code/static/js/widgets/terminal/wrap-utils.js +101 -0
- painapple_code/static/js/widgets/terminal-widget.js +793 -0
- painapple_code/static/js/widgets/uploads-widget.js +461 -0
- painapple_code/static/js/widgets/zen-widget.js +1001 -0
- painapple_code/static/login.html +51 -0
- painapple_code/static/manifest.json +73 -0
- painapple_code/static/sw.js +293 -0
- painapple_code/static/vendor/LICENSES.txt +769 -0
- painapple_code/static/vendor/README.md +103 -0
- painapple_code/static/vendor/codemirror.js +35 -0
- painapple_code/static/vendor/codemirror.js.LEGAL.txt +304 -0
- painapple_code/static/vendor/github-markdown-dark.min.css +1124 -0
- painapple_code/static/vendor/highlight-github-dark.min.css +10 -0
- painapple_code/static/vendor/highlight-lang-dockerfile.min.js +8 -0
- painapple_code/static/vendor/highlight-lang-nginx.min.js +21 -0
- painapple_code/static/vendor/highlight-lang-properties.min.js +10 -0
- painapple_code/static/vendor/highlight-lang-scala.min.js +28 -0
- painapple_code/static/vendor/highlight.min.js +1213 -0
- painapple_code/static/vendor/marked.min.js +6 -0
- painapple_code/static/vendor/purify.min.js +3 -0
- painapple_code/static/vendor/xterm-addon-fit.js +2 -0
- painapple_code/static/vendor/xterm.css +209 -0
- painapple_code/static/vendor/xterm.js +2 -0
- painapple_code/static/web-client.html +635 -0
- painapple_code/subprocess_registry.py +535 -0
- painapple_code/tls_cert.py +79 -0
- painapple_code/tools/agents/shadow-git-helper.md +400 -0
- painapple_code/tools/excalidraw-to-svg.js +143 -0
- painapple_code/tools/install-helpers.sh +148 -0
- painapple_code/tools/shadow-git +320 -0
- painapple_code/tools/shadow-query +120 -0
- painapple_code/tools/vegalite-to-svg.js +79 -0
- painapple_code/turn_query.py +766 -0
- painapple_code/turn_tracker.py +221 -0
- painapple_code/utils/__init__.py +0 -0
- painapple_code/utils/agent_cli.py +306 -0
- painapple_code/utils/chart.py +103 -0
- painapple_code/utils/excalidraw.py +107 -0
- painapple_code/utils/file_lock.py +162 -0
- painapple_code/utils/file_paths.py +648 -0
- painapple_code/utils/generate_icons.py +251 -0
- painapple_code/utils/proc.py +205 -0
- painapple_code/utils/pty_backend.py +59 -0
- painapple_code/utils/pty_posix.py +160 -0
- painapple_code/utils/pty_windows.py +245 -0
- painapple_code/utils/token_profiles.py +91 -0
- painapple_code/viewer_templates.py +657 -0
- painapple_code/welcome_search.py +879 -0
- painapple_code-1.0.0.dist-info/METADATA +292 -0
- painapple_code-1.0.0.dist-info/RECORD +436 -0
- painapple_code-1.0.0.dist-info/WHEEL +5 -0
- painapple_code-1.0.0.dist-info/entry_points.txt +2 -0
- painapple_code-1.0.0.dist-info/licenses/LICENSE +661 -0
- painapple_code-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +261 -0
- painapple_code-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init-UI mixin — one-time construction of the DOM element cache and all the
|
|
3
|
+
* sub-component instances (autocomplete, chat search/navigator, scroll manager,
|
|
4
|
+
* status bar, activity strip, input handler, the file/snippet/skill
|
|
5
|
+
* autocompletes), server-info fetch, and the left-rail buttons + drawer.
|
|
6
|
+
* Extracted from app.js; applied to App.prototype via Object.assign. Uses `this`
|
|
7
|
+
* (App instance) plus the imports below.
|
|
8
|
+
*/
|
|
9
|
+
import S from '../strings.js';
|
|
10
|
+
import { $ } from '../utils.js';
|
|
11
|
+
import { CONFIG, HAS_PHYSICAL_KEYBOARD, debug, setServerHome, setServerWorkspace, setServerVersionInfo } from '../config.js';
|
|
12
|
+
import { AutocompleteUI } from '../components.js';
|
|
13
|
+
import { ChatSearch } from '../chat-search.js';
|
|
14
|
+
import { ChatNavigator } from '../chat-navigator.js';
|
|
15
|
+
import { ScrollManager } from '../scroll-manager.js';
|
|
16
|
+
import { StatusBar, ensureModelsLoaded } from '../status-bar.js';
|
|
17
|
+
import { ActivityStrip } from '../activity-strip.js';
|
|
18
|
+
import { InputHandler } from '../input-handler.js';
|
|
19
|
+
import { FileAutocomplete } from '../file-autocomplete.js';
|
|
20
|
+
import { SnippetsAutocomplete } from '../snippets-autocomplete.js';
|
|
21
|
+
import { SkillsAutocomplete } from '../skills-autocomplete.js';
|
|
22
|
+
import { WidgetManager, WidgetBus } from '../widget-system/init.js';
|
|
23
|
+
import { OpenDialog } from '../open-dialog.js';
|
|
24
|
+
import { layoutSwitcher } from '../layout-switcher.js';
|
|
25
|
+
import { withChords } from '../shortcuts.js';
|
|
26
|
+
import { state as configState } from '../widgets/config/state.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Rail button → the widget it shows, for the `.active` highlight.
|
|
30
|
+
*
|
|
31
|
+
* Lives here rather than on the widget configs so the rail stays the sole
|
|
32
|
+
* owner of its own visual state — widgets don't need to know a rail exists.
|
|
33
|
+
* Only entries whose target is a registered widget belong here; rail-search-btn
|
|
34
|
+
* (chat search) and the stateless buttons (layout, help) are handled separately.
|
|
35
|
+
*/
|
|
36
|
+
const RAIL_WIDGET_BUTTONS = {
|
|
37
|
+
'rail-sessions-btn': 'active-sessions',
|
|
38
|
+
'rail-agents-btn': 'sub-agents',
|
|
39
|
+
'rail-bg-tasks-btn': 'background-tasks',
|
|
40
|
+
'rail-files-btn': 'file-explorer',
|
|
41
|
+
'rail-preview-btn': 'file-preview',
|
|
42
|
+
'rail-logs-btn': 'log-explorer',
|
|
43
|
+
'rail-terminal-btn': 'terminal',
|
|
44
|
+
'rail-git-btn': 'git',
|
|
45
|
+
'rail-browser-btn': 'browser',
|
|
46
|
+
'rail-history-btn': 'history-explorer',
|
|
47
|
+
'rail-skills-btn': 'skills',
|
|
48
|
+
'rail-agent-defs-btn': 'agents',
|
|
49
|
+
'rail-snippets-btn': 'snippets',
|
|
50
|
+
'rail-discussion-btn': 'discussion',
|
|
51
|
+
'rail-cost-btn': 'cost-analytics',
|
|
52
|
+
'rail-settings-btn': 'config',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const initUiMethods = {
|
|
56
|
+
initElements() {
|
|
57
|
+
this.els = {
|
|
58
|
+
tabs: $('#tabs'),
|
|
59
|
+
tabsViewport: $('.tabs-viewport'),
|
|
60
|
+
newTabBtn: $('#new-tab-btn'),
|
|
61
|
+
messages: $('#messages'),
|
|
62
|
+
messagesContainer: $('#messages-container'),
|
|
63
|
+
activityStrip: $('#activity-strip'),
|
|
64
|
+
inputContainer: $('#input-container'),
|
|
65
|
+
inputResizeHandle: $('#input-resize-handle'),
|
|
66
|
+
messageInput: $('#message-input'),
|
|
67
|
+
historyRefsHint: $('#history-refs-hint'),
|
|
68
|
+
sendBtn: $('#send-btn'),
|
|
69
|
+
followupBtn: $('#followup-btn'),
|
|
70
|
+
stopBtn: $('#stop-btn'),
|
|
71
|
+
connectionBar: $('#connection-bar'),
|
|
72
|
+
inputArea: $('#input-area'),
|
|
73
|
+
openFolderBtn: $('#open-folder-btn'),
|
|
74
|
+
modalOverlay: $('#modal-overlay'),
|
|
75
|
+
modalBody: $('.modal-body'),
|
|
76
|
+
modalClose: $('#modal-close'),
|
|
77
|
+
confirmDialog: $('#confirm-dialog'),
|
|
78
|
+
confirmPath: $('#confirm-path'),
|
|
79
|
+
confirmCancel: $('#confirm-cancel'),
|
|
80
|
+
confirmCreate: $('#confirm-create'),
|
|
81
|
+
statusConnection: $('#status-connection'),
|
|
82
|
+
statusEngine: $('#status-engine'),
|
|
83
|
+
statusModel: $('#status-model'),
|
|
84
|
+
statusBranch: $('#status-branch'),
|
|
85
|
+
statusProject: $('#status-project'),
|
|
86
|
+
statusTokens: $('#status-tokens'),
|
|
87
|
+
statusCost: $('#status-cost'),
|
|
88
|
+
uploadBtn: $('#upload-btn'),
|
|
89
|
+
promptHistoryBtn: $('#prompt-history-btn'),
|
|
90
|
+
uploadsBtn: $('#uploads-btn'),
|
|
91
|
+
discussionBtn: $('#discussion-btn'),
|
|
92
|
+
fileMentionBtn: $('#file-mention-btn'),
|
|
93
|
+
snippetsBtn: $('#snippets-btn'),
|
|
94
|
+
slashCmdBtn: $('#slash-cmd-btn'),
|
|
95
|
+
skillsBtn: $('#skills-btn'),
|
|
96
|
+
chatSearchBar: $('#chat-search-bar'),
|
|
97
|
+
chatSearchInput: $('#chat-search-input'),
|
|
98
|
+
chatSearchCount: $('#chat-search-count'),
|
|
99
|
+
chatSearchPrev: $('#chat-search-prev'),
|
|
100
|
+
chatSearchNext: $('#chat-search-next'),
|
|
101
|
+
chatSearchClose: $('#chat-search-close'),
|
|
102
|
+
// View containers
|
|
103
|
+
sessionView: $('#session-view'),
|
|
104
|
+
welcomeView: $('#welcome-view'),
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
initAutocomplete() {
|
|
109
|
+
this.autocomplete = new AutocompleteUI(
|
|
110
|
+
$('#autocomplete'),
|
|
111
|
+
this.els.messageInput
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Initialize file autocomplete (@-mentions)
|
|
115
|
+
this.fileAutocomplete = new FileAutocomplete(
|
|
116
|
+
this.els.messageInput,
|
|
117
|
+
{ apiBase: CONFIG.API_BASE },
|
|
118
|
+
{
|
|
119
|
+
getCwd: () => this.activeSession?.cwd,
|
|
120
|
+
getChangedFiles: () => []
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Initialize snippets autocomplete (#-trigger for agents and snippets)
|
|
125
|
+
this.snippetsAutocomplete = new SnippetsAutocomplete(
|
|
126
|
+
this.els.messageInput
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Initialize skills autocomplete (~ trigger for inserting `/skill-name`)
|
|
130
|
+
this.skillsAutocomplete = new SkillsAutocomplete(
|
|
131
|
+
this.els.messageInput
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// While a type-ahead picker (#, /, @, $) is open, tint its toolbar
|
|
135
|
+
// button with the accent color — same look as the Tab-cycle
|
|
136
|
+
// highlight — so typing the trigger char visibly "lights up" its
|
|
137
|
+
// icon. Every popup toggles `.visible` on its container, so a class
|
|
138
|
+
// observer catches all open/close paths (typed trigger, debounced
|
|
139
|
+
// show, Escape, selection commit) without touching the popups.
|
|
140
|
+
this._initTriggerButtonHighlights();
|
|
141
|
+
|
|
142
|
+
// The welcome / connection bar is a single button that opens the
|
|
143
|
+
// OpenDialog picker (which handles picking or creating a folder and
|
|
144
|
+
// connecting). Label comes from strings.js.
|
|
145
|
+
if (this.els.openFolderBtn) {
|
|
146
|
+
this.els.openFolderBtn.textContent = S.connection.open_folder;
|
|
147
|
+
this.els.openFolderBtn.addEventListener('click', () => OpenDialog.show());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Initialize Chat Search (Ctrl+F in chat)
|
|
151
|
+
this.chatSearch = new ChatSearch(
|
|
152
|
+
this.els.messages,
|
|
153
|
+
{
|
|
154
|
+
searchBar: this.els.chatSearchBar,
|
|
155
|
+
searchInput: this.els.chatSearchInput,
|
|
156
|
+
countDisplay: this.els.chatSearchCount,
|
|
157
|
+
prevBtn: this.els.chatSearchPrev,
|
|
158
|
+
nextBtn: this.els.chatSearchNext,
|
|
159
|
+
closeBtn: this.els.chatSearchClose
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
onClose: () => this.els.messageInput?.focus()
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Initialize Scroll Manager (Slack-style new messages indicator)
|
|
167
|
+
this.scrollManager = new ScrollManager(
|
|
168
|
+
this.els.messagesContainer,
|
|
169
|
+
{ scrollThreshold: 100, topThreshold: 100 },
|
|
170
|
+
{
|
|
171
|
+
onScrollTop: () => this.handleScrollTop(),
|
|
172
|
+
getSession: () => this.activeSession
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Initialize Chat Navigator (Cmd+Up/Down to jump between user messages)
|
|
177
|
+
this.chatNavigator = new ChatNavigator(
|
|
178
|
+
{
|
|
179
|
+
messagesContainer: this.els.messagesContainer,
|
|
180
|
+
messages: this.els.messages
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
getSession: () => this.activeSession,
|
|
184
|
+
onLoadMore: () => this.handleScrollTop()
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Initialize Status Bar (connection status, typing indicator)
|
|
189
|
+
this.statusBar = new StatusBar(
|
|
190
|
+
{
|
|
191
|
+
statusConnection: this.els.statusConnection,
|
|
192
|
+
statusEngine: this.els.statusEngine,
|
|
193
|
+
statusModel: this.els.statusModel,
|
|
194
|
+
statusBranch: this.els.statusBranch,
|
|
195
|
+
statusProject: this.els.statusProject,
|
|
196
|
+
statusCost: this.els.statusCost,
|
|
197
|
+
statusTokens: this.els.statusTokens,
|
|
198
|
+
sendBtn: this.els.sendBtn,
|
|
199
|
+
followupBtn: this.els.followupBtn,
|
|
200
|
+
stopBtn: this.els.stopBtn
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
getSession: () => this.activeSession
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
this.statusBar.init(); // Set up click handlers for token popover
|
|
207
|
+
|
|
208
|
+
// Warm the models cache so {model} placeholders render with the
|
|
209
|
+
// correct label on first paint (helpers modal, settings toggles).
|
|
210
|
+
ensureModelsLoaded();
|
|
211
|
+
|
|
212
|
+
// Activity Strip (between messages and input — peripheral, not competing with cursor)
|
|
213
|
+
this.activityStrip = new ActivityStrip(this.els.activityStrip, this.els.inputArea);
|
|
214
|
+
|
|
215
|
+
// Initialize Input Handler (history, drafts, input events)
|
|
216
|
+
this.inputHandler = new InputHandler(
|
|
217
|
+
{
|
|
218
|
+
messageInput: this.els.messageInput,
|
|
219
|
+
sendBtn: this.els.sendBtn,
|
|
220
|
+
followupBtn: this.els.followupBtn,
|
|
221
|
+
historyRefsHint: this.els.historyRefsHint
|
|
222
|
+
},
|
|
223
|
+
{ hasPhysicalKeyboard: HAS_PHYSICAL_KEYBOARD },
|
|
224
|
+
{
|
|
225
|
+
getSession: () => this.activeSession,
|
|
226
|
+
getAutocomplete: () => this.autocomplete,
|
|
227
|
+
getFileAutocomplete: () => this.fileAutocomplete,
|
|
228
|
+
getSnippetsAutocomplete: () => this.snippetsAutocomplete,
|
|
229
|
+
getSkillsAutocomplete: () => this.skillsAutocomplete,
|
|
230
|
+
getCwd: () => this.activeSession?.cwd || null,
|
|
231
|
+
getPendingImages: () => this.pendingImages,
|
|
232
|
+
isUploading: () => this.uploadManager?.isUploading || false,
|
|
233
|
+
hasPendingUploads: () => this.uploadManager?.hasPending || false,
|
|
234
|
+
onSendMessage: (text, options) => this.sendMessage(text, options),
|
|
235
|
+
onSlashCommand: (cmd) => this.handleSlashCommand(cmd),
|
|
236
|
+
onBangCommand: (cmd) => this.handleBangCommand(cmd),
|
|
237
|
+
onPlanComposeChange: (active) => this.onPlanComposeChange(active),
|
|
238
|
+
onConnect: () => this.connectActiveSession(),
|
|
239
|
+
onAutoResize: () => { this.autoResizeInput(); this.syncInputHighlightBackdrop(); },
|
|
240
|
+
onUpdateSendButton: () => this.updateSendButtonState(),
|
|
241
|
+
onPreviewFile: (path) => this.previewFile(path),
|
|
242
|
+
onSendInNewSession: (text) => this.sendInNewSession(text),
|
|
243
|
+
isWelcomeMode: () => this.chatCtrl?.isWelcomeShowing() || false
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Type-anywhere search: typing on welcome screen focuses input
|
|
248
|
+
this._initTypeAnywhereSearch();
|
|
249
|
+
|
|
250
|
+
// Fetch server info first to get home path, then load directories
|
|
251
|
+
this.initServerInfo();
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Accent-highlight a trigger's toolbar button while its picker is open.
|
|
256
|
+
* One class observer per popup container; the slash popup only lights
|
|
257
|
+
* the "/" button in slash mode (bang mode shares the same container
|
|
258
|
+
* but has no toolbar button).
|
|
259
|
+
*/
|
|
260
|
+
_initTriggerButtonHighlights() {
|
|
261
|
+
const pairs = [
|
|
262
|
+
[this.snippetsAutocomplete, this.els.snippetsBtn],
|
|
263
|
+
[this.fileAutocomplete, this.els.fileMentionBtn],
|
|
264
|
+
[this.skillsAutocomplete, this.els.skillsBtn],
|
|
265
|
+
[this.autocomplete, this.els.slashCmdBtn,
|
|
266
|
+
() => this.autocomplete.mode === 'slash'],
|
|
267
|
+
];
|
|
268
|
+
for (const [popup, btn, extra] of pairs) {
|
|
269
|
+
if (!popup?.container || !btn) continue;
|
|
270
|
+
const sync = () => btn.classList.toggle(
|
|
271
|
+
'trigger-armed',
|
|
272
|
+
popup.container.classList.contains('visible') && (!extra || extra())
|
|
273
|
+
);
|
|
274
|
+
new MutationObserver(sync).observe(
|
|
275
|
+
popup.container, { attributes: true, attributeFilter: ['class'] }
|
|
276
|
+
);
|
|
277
|
+
sync();
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Fetch server info (home path) before loading directories
|
|
283
|
+
*/
|
|
284
|
+
async initServerInfo() {
|
|
285
|
+
try {
|
|
286
|
+
const response = await fetch(`${CONFIG.API_BASE}/api/info`);
|
|
287
|
+
const data = await response.json();
|
|
288
|
+
if (data.home) setServerHome(data.home);
|
|
289
|
+
if (data.workspace) setServerWorkspace(data.workspace);
|
|
290
|
+
setServerVersionInfo({
|
|
291
|
+
version: data.version,
|
|
292
|
+
staticBuild: data.static_build,
|
|
293
|
+
bundled: data.frontend_bundled,
|
|
294
|
+
bundleReason: data.frontend_reason,
|
|
295
|
+
diskVersion: data.disk_version,
|
|
296
|
+
restartNeeded: data.restart_needed,
|
|
297
|
+
license: data.license,
|
|
298
|
+
urls: data.urls,
|
|
299
|
+
author: data.author,
|
|
300
|
+
});
|
|
301
|
+
// Anchor the file explorer to the explicit workspace when set
|
|
302
|
+
// (the project base), falling back to the OS home otherwise.
|
|
303
|
+
const anchor = data.workspace || data.home;
|
|
304
|
+
if (anchor && this.fileExplorer) {
|
|
305
|
+
this.fileExplorer.setHomePath(anchor);
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
debug.warn('Failed to fetch server info:', err);
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Initialize left rail buttons - mirror functionality from header/toolbar
|
|
314
|
+
* Rail provides quick access to common widgets on desktop
|
|
315
|
+
*/
|
|
316
|
+
initLeftRailButtons() {
|
|
317
|
+
// Inject text labels for the expanded rail. The tooltip text is the
|
|
318
|
+
// single source for the label — read before refreshRailShortcutTooltips
|
|
319
|
+
// appends "(key)" (shortcutBase covers a re-run after that).
|
|
320
|
+
document.querySelectorAll('#left-rail .rail-btn').forEach(btn => {
|
|
321
|
+
if (btn.querySelector('.rail-label')) return;
|
|
322
|
+
const text = btn.dataset.shortcutBase || btn.getAttribute('data-tooltip');
|
|
323
|
+
if (!text) return;
|
|
324
|
+
const label = document.createElement('span');
|
|
325
|
+
label.className = 'rail-label';
|
|
326
|
+
label.textContent = text;
|
|
327
|
+
btn.insertBefore(label, btn.querySelector('.rail-badge'));
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Helper to bind rail button to action (click + touchend for iOS)
|
|
331
|
+
const bind = (id, action) => {
|
|
332
|
+
const btn = document.getElementById(id);
|
|
333
|
+
if (btn) {
|
|
334
|
+
btn.addEventListener('click', action);
|
|
335
|
+
btn.addEventListener('touchend', (e) => {
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
action();
|
|
338
|
+
}, { passive: false });
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Activity / running things
|
|
343
|
+
bind('rail-sessions-btn', () => WidgetManager.toggle('active-sessions'));
|
|
344
|
+
bind('rail-agents-btn', () => WidgetManager.toggle('sub-agents'));
|
|
345
|
+
bind('rail-bg-tasks-btn', () => WidgetManager.toggle('background-tasks'));
|
|
346
|
+
// rail-orphan-terminals-btn is wired by orphan-terminals.js (uses #orphan-terminals-btn wrapper)
|
|
347
|
+
|
|
348
|
+
// Files / Code
|
|
349
|
+
// toggle(), not open(): the button now carries an .active highlight, so
|
|
350
|
+
// it has to behave like the toggle it looks like (and like its own
|
|
351
|
+
// Alt+F shortcut, which already toggled).
|
|
352
|
+
bind('rail-files-btn', () => this.fileExplorer?.toggle());
|
|
353
|
+
bind('rail-preview-btn', () => this.togglePreview());
|
|
354
|
+
bind('rail-logs-btn', () => WidgetManager.toggle('log-explorer'));
|
|
355
|
+
bind('rail-terminal-btn', () => this.toggleTerminalPanel());
|
|
356
|
+
bind('rail-git-btn', () => WidgetManager.toggle('git'));
|
|
357
|
+
bind('rail-browser-btn', () => WidgetManager.toggle('browser'));
|
|
358
|
+
|
|
359
|
+
// Find
|
|
360
|
+
bind('rail-search-btn', () => this.chatSearch?.toggle());
|
|
361
|
+
bind('rail-history-btn', () => WidgetManager.toggle('history-explorer'));
|
|
362
|
+
|
|
363
|
+
// Library (skills / agent definitions / snippets)
|
|
364
|
+
bind('rail-skills-btn', () => WidgetManager.toggle('skills'));
|
|
365
|
+
bind('rail-agent-defs-btn', () => WidgetManager.toggle('agents'));
|
|
366
|
+
bind('rail-snippets-btn', () => WidgetManager.toggle('snippets'));
|
|
367
|
+
|
|
368
|
+
// Discussion
|
|
369
|
+
bind('rail-discussion-btn', () => WidgetManager.toggle('discussion'));
|
|
370
|
+
|
|
371
|
+
// Bottom buttons
|
|
372
|
+
bind('rail-cost-btn', () => WidgetManager.toggle('cost-analytics'));
|
|
373
|
+
bind('rail-layout-btn', () => layoutSwitcher.toggle(document.getElementById('rail-layout-btn')));
|
|
374
|
+
bind('rail-settings-btn', () => this.showSettings());
|
|
375
|
+
bind('rail-help-btn', () => this.showHelp());
|
|
376
|
+
|
|
377
|
+
this.refreshRailShortcutTooltips();
|
|
378
|
+
this.initRailActiveState();
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Keep the rail's `.active` highlight in sync with what's actually on
|
|
383
|
+
* screen, so the rail reads as a set of on/off toggles rather than a set
|
|
384
|
+
* of launch buttons.
|
|
385
|
+
*
|
|
386
|
+
* Driven by widget events rather than by the click handlers above, so the
|
|
387
|
+
* highlight stays correct no matter how a widget opened or closed — rail
|
|
388
|
+
* click, keyboard shortcut, quick-switcher, FAB, or Escape.
|
|
389
|
+
*/
|
|
390
|
+
initRailActiveState() {
|
|
391
|
+
const sync = () => this.syncRailActiveState();
|
|
392
|
+
|
|
393
|
+
// Widget lifecycle. 'destroyed' matters because a destroyed widget
|
|
394
|
+
// never emits 'closed'.
|
|
395
|
+
WidgetBus.on('widget:opened', sync);
|
|
396
|
+
WidgetBus.on('widget:closed', sync);
|
|
397
|
+
WidgetBus.on('widget:destroyed', sync);
|
|
398
|
+
// Promotion to a full tab closes the floating window — re-read state.
|
|
399
|
+
WidgetBus.on('widget:open-as-tab', sync);
|
|
400
|
+
// Session switch shows/hides whole sets of widgets via inline display
|
|
401
|
+
// without touching isVisible, which is why syncRailActiveState() tests
|
|
402
|
+
// isShowing() rather than isOpen().
|
|
403
|
+
WidgetBus.on('session:widgets-shown', sync);
|
|
404
|
+
|
|
405
|
+
// Chat search is the one rail entry that isn't a widget, so it has no
|
|
406
|
+
// bus event. Watching the bar's class is cheaper than threading a
|
|
407
|
+
// callback through ChatSearch for a single consumer.
|
|
408
|
+
const searchBar = this.els.chatSearchBar;
|
|
409
|
+
if (searchBar) {
|
|
410
|
+
new MutationObserver(sync).observe(searchBar, {
|
|
411
|
+
attributes: true,
|
|
412
|
+
attributeFilter: ['class'],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
sync();
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Paint `.active` on every rail button whose target is currently visible.
|
|
421
|
+
* Safe to call at any time — cheap DOM writes, no widget instantiation
|
|
422
|
+
* (isShowing resolves without creating).
|
|
423
|
+
*/
|
|
424
|
+
syncRailActiveState() {
|
|
425
|
+
for (const [btnId, widgetId] of Object.entries(RAIL_WIDGET_BUTTONS)) {
|
|
426
|
+
document.getElementById(btnId)
|
|
427
|
+
?.classList.toggle('active', WidgetManager.isShowing(widgetId));
|
|
428
|
+
}
|
|
429
|
+
document.getElementById('rail-search-btn')
|
|
430
|
+
?.classList.toggle('active', !!this.chatSearch?.state?.active);
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Resolve the static chrome tooltips declared in web-client.html as
|
|
435
|
+
* data-tooltip-key="<dotted path into strings.yaml>" — the markup carries
|
|
436
|
+
* no user-facing text of its own, so translations stay in one file.
|
|
437
|
+
*
|
|
438
|
+
* Runs before refreshRailShortcutTooltips() so that the shortcut suffix is
|
|
439
|
+
* appended to the resolved string; clearing shortcutBase keeps that safe if
|
|
440
|
+
* the two ever run in the other order.
|
|
441
|
+
*/
|
|
442
|
+
applyStaticTooltips() {
|
|
443
|
+
document.querySelectorAll('[data-tooltip-key]').forEach(el => {
|
|
444
|
+
const text = el.dataset.tooltipKey
|
|
445
|
+
.split('.')
|
|
446
|
+
.reduce((o, k) => (o == null ? o : o[k]), S);
|
|
447
|
+
if (typeof text !== 'string') {
|
|
448
|
+
console.warn('Unknown tooltip key:', el.dataset.tooltipKey);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
el.setAttribute('data-tooltip', withChords(text));
|
|
452
|
+
delete el.dataset.shortcutBase;
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Append the user-configured key to any rail tooltip that opted in via
|
|
458
|
+
* data-shortcut-action="<shortcutId>". Re-run after the shortcut manager
|
|
459
|
+
* reloads user overrides so the tooltip never drifts from the binding.
|
|
460
|
+
*/
|
|
461
|
+
refreshRailShortcutTooltips() {
|
|
462
|
+
// Send tooltip first — it doesn't need the manager on keyboard devices.
|
|
463
|
+
this._refreshSendTooltip();
|
|
464
|
+
const sm = this.shortcutManager;
|
|
465
|
+
if (!sm) return;
|
|
466
|
+
document.querySelectorAll('[data-shortcut-action]').forEach(el => {
|
|
467
|
+
const id = el.dataset.shortcutAction;
|
|
468
|
+
const base = el.dataset.shortcutBase ?? (el.dataset.shortcutBase = el.getAttribute('data-tooltip') || '');
|
|
469
|
+
const keys = sm.getShortcutKeys(id);
|
|
470
|
+
const primary = keys?.[0];
|
|
471
|
+
el.setAttribute('data-tooltip', primary ? `${base} (${primary})` : base);
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The send button is NOT a plain shortcut mirror. On keyboard devices plain
|
|
477
|
+
* Enter sends (input-handler.js) and Cmd/Ctrl+Enter is merely the
|
|
478
|
+
* connect-when-disconnected binding — advertising the latter was wrong.
|
|
479
|
+
* On iPhone (no physical keyboard) Enter inserts a newline, so there
|
|
480
|
+
* Cmd+Enter genuinely is the send key.
|
|
481
|
+
*/
|
|
482
|
+
_refreshSendTooltip() {
|
|
483
|
+
const btn = this.els?.sendBtn || document.getElementById('send-btn');
|
|
484
|
+
if (!btn) return;
|
|
485
|
+
if (HAS_PHYSICAL_KEYBOARD) {
|
|
486
|
+
btn.setAttribute('data-tooltip', S.ui.input.send_tooltip);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const key = this.shortcutManager?.getShortcutKeys('connect')?.[0] || 'Cmd+Enter';
|
|
490
|
+
btn.setAttribute('data-tooltip', S.ui.input.send_tooltip_mod.replace('{key}', key));
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Rail hamburger. Mobile: toggles the slide-in drawer (body.rail-open),
|
|
495
|
+
* scrim/rail-btn taps close it; the composite badge keeps passive awareness
|
|
496
|
+
* of running activity while the drawer is closed. iPad/desktop: toggles the
|
|
497
|
+
* rail between icons-only and icons+labels (body.rail-labels, persisted in
|
|
498
|
+
* user config). The mobile drawer always shows labels.
|
|
499
|
+
*/
|
|
500
|
+
initRailDrawer() {
|
|
501
|
+
const toggleBtn = document.getElementById('rail-toggle-btn');
|
|
502
|
+
const scrim = document.getElementById('rail-scrim');
|
|
503
|
+
const compositeBadge = document.getElementById('rail-toggle-badge');
|
|
504
|
+
if (!toggleBtn) return;
|
|
505
|
+
|
|
506
|
+
const isMobile = () => window.matchMedia('(max-width: 600px)').matches;
|
|
507
|
+
const applyRailLabels = () => {
|
|
508
|
+
document.body.classList.toggle('rail-labels', isMobile() || !!configState.config.railExpanded);
|
|
509
|
+
};
|
|
510
|
+
applyRailLabels();
|
|
511
|
+
|
|
512
|
+
const setOpen = (open) => {
|
|
513
|
+
document.body.classList.toggle('rail-open', open);
|
|
514
|
+
toggleBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
515
|
+
};
|
|
516
|
+
const close = () => setOpen(false);
|
|
517
|
+
const toggle = () => {
|
|
518
|
+
if (isMobile()) {
|
|
519
|
+
setOpen(!document.body.classList.contains('rail-open'));
|
|
520
|
+
} else {
|
|
521
|
+
const expanded = !configState.config.railExpanded;
|
|
522
|
+
configState.setRailExpanded(expanded);
|
|
523
|
+
applyRailLabels();
|
|
524
|
+
toggleBtn.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// Exposed as an app method for the toggleRailMenu shortcut (Ctrl/Cmd+B)
|
|
529
|
+
this.toggleRailMenu = toggle;
|
|
530
|
+
|
|
531
|
+
toggleBtn.addEventListener('click', toggle);
|
|
532
|
+
scrim?.addEventListener('click', close);
|
|
533
|
+
|
|
534
|
+
// Auto-close when a rail button is tapped on mobile (after its action runs)
|
|
535
|
+
document.querySelectorAll('#left-rail .rail-btn').forEach(btn => {
|
|
536
|
+
btn.addEventListener('click', () => { if (isMobile()) close(); });
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// Tabs-overview button in the rail drawer (mobile copy of the one in #tabs).
|
|
540
|
+
// Capture rect first (so dropdown anchors where the user tapped), then
|
|
541
|
+
// close the drawer so the dropdown isn't hidden behind it.
|
|
542
|
+
const railOverviewBtn = document.querySelector('.rail-drawer-grid');
|
|
543
|
+
if (railOverviewBtn) {
|
|
544
|
+
railOverviewBtn.addEventListener('click', (e) => {
|
|
545
|
+
e.stopPropagation();
|
|
546
|
+
const anchorClone = { getBoundingClientRect: () => railOverviewBtn.getBoundingClientRect() };
|
|
547
|
+
if (isMobile()) close();
|
|
548
|
+
this.tabCtrl?.toggleTabsOverview(anchorClone);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Close on viewport resize past the breakpoint so state can't get stuck;
|
|
553
|
+
// re-evaluate labels (drawer forces them on, desktop follows the pref)
|
|
554
|
+
window.addEventListener('resize', () => {
|
|
555
|
+
if (!isMobile()) close();
|
|
556
|
+
applyRailLabels();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Hamburger badge mirrors the active-sessions count. Other rail badges
|
|
560
|
+
// (orphan terminals, agents, tasks, summary forks) intentionally don't contribute
|
|
561
|
+
// — orphans are stale state, not active work.
|
|
562
|
+
if (compositeBadge) {
|
|
563
|
+
const sessionsBadge = document.getElementById('sessions-badge');
|
|
564
|
+
if (sessionsBadge) {
|
|
565
|
+
const update = () => {
|
|
566
|
+
const n = parseInt(sessionsBadge.textContent, 10) || 0;
|
|
567
|
+
compositeBadge.textContent = n > 0 ? String(n) : '';
|
|
568
|
+
};
|
|
569
|
+
new MutationObserver(update).observe(sessionsBadge, { childList: true, characterData: true, subtree: true });
|
|
570
|
+
update();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
};
|