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,585 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long-press detection + session and project context menus.
|
|
3
|
+
*
|
|
4
|
+
* The session menu (renderContextMenu/showContextMenu) appears on right-click
|
|
5
|
+
* or long-press of any session row/card. The project menu lives lower down
|
|
6
|
+
* and appears on right-click/long-press of the project name beneath a card.
|
|
7
|
+
*
|
|
8
|
+
* Long-press detection lives here because it's the entry point for both
|
|
9
|
+
* menus on touch devices. The triggered/timestamp/suppressNextClick flags
|
|
10
|
+
* are read elsewhere (welcome.js attachEventListeners) so they're hosted on
|
|
11
|
+
* the shared `longPress` ref in state.js.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import S from '../strings.js';
|
|
15
|
+
import { debug } from '../config.js';
|
|
16
|
+
import { copyToClipboard, showToast } from '../context-menu.js';
|
|
17
|
+
import {
|
|
18
|
+
state,
|
|
19
|
+
longPress,
|
|
20
|
+
contextMenuOpenTime,
|
|
21
|
+
isFavorite,
|
|
22
|
+
saveWelcomeState,
|
|
23
|
+
} from './state.js';
|
|
24
|
+
import { showPreview } from './preview.js';
|
|
25
|
+
import {
|
|
26
|
+
toggleFavorite,
|
|
27
|
+
renameSession,
|
|
28
|
+
renameProject,
|
|
29
|
+
setProjectFilter,
|
|
30
|
+
} from './api.js';
|
|
31
|
+
import { renderWelcomeScreen } from '../welcome.js';
|
|
32
|
+
import { showProjectColorPicker } from '../project-color-picker.js';
|
|
33
|
+
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
|
+
// SESSION CONTEXT MENU
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render context menu.
|
|
40
|
+
*/
|
|
41
|
+
function renderContextMenu(session, x, y) {
|
|
42
|
+
if (!session) return '';
|
|
43
|
+
|
|
44
|
+
const sessionId = session.session_id;
|
|
45
|
+
const isFav = isFavorite(sessionId);
|
|
46
|
+
|
|
47
|
+
return `
|
|
48
|
+
<div class="session-context-overlay" data-action="close-context"></div>
|
|
49
|
+
<div class="session-context-menu" style="left: ${x}px; top: ${y}px;">
|
|
50
|
+
<button class="context-menu-item" data-action="context-toggle-favorite">
|
|
51
|
+
<svg viewBox="0 0 24 24" fill="${isFav ? 'currentColor' : 'none'}" stroke="currentColor" stroke-width="2">
|
|
52
|
+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
|
53
|
+
</svg>
|
|
54
|
+
${isFav ? 'Remove from Favorites' : 'Add to Favorites'}
|
|
55
|
+
</button>
|
|
56
|
+
<button class="context-menu-item" data-action="context-rename">
|
|
57
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
58
|
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
59
|
+
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
60
|
+
</svg>
|
|
61
|
+
Rename
|
|
62
|
+
</button>
|
|
63
|
+
<div class="context-menu-divider"></div>
|
|
64
|
+
<button class="context-menu-item" data-action="context-open">
|
|
65
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
66
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
|
67
|
+
<polyline points="15 3 21 3 21 9"/>
|
|
68
|
+
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
69
|
+
</svg>
|
|
70
|
+
Open
|
|
71
|
+
</button>
|
|
72
|
+
<button class="context-menu-item" data-action="context-open-new-tab">
|
|
73
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
74
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
75
|
+
<line x1="9" y1="3" x2="9" y2="21"/>
|
|
76
|
+
</svg>
|
|
77
|
+
Open in New Tab
|
|
78
|
+
</button>
|
|
79
|
+
<button class="context-menu-item" data-action="context-preview">
|
|
80
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
81
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
|
82
|
+
<circle cx="12" cy="12" r="3"/>
|
|
83
|
+
</svg>
|
|
84
|
+
Quick Preview
|
|
85
|
+
</button>
|
|
86
|
+
<button class="context-menu-item" data-action="context-copy-id">
|
|
87
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
88
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
89
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
|
90
|
+
</svg>
|
|
91
|
+
Copy Session ID
|
|
92
|
+
</button>
|
|
93
|
+
<button class="context-menu-item" data-action="context-copy-url">
|
|
94
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
95
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
|
96
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
|
97
|
+
</svg>
|
|
98
|
+
Copy Session URL
|
|
99
|
+
</button>
|
|
100
|
+
<div class="context-menu-divider"></div>
|
|
101
|
+
<button class="context-menu-item" data-action="context-fork">
|
|
102
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
103
|
+
<circle cx="12" cy="18" r="3"/>
|
|
104
|
+
<circle cx="6" cy="6" r="3"/>
|
|
105
|
+
<circle cx="18" cy="6" r="3"/>
|
|
106
|
+
<path d="M18 9v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V9"/>
|
|
107
|
+
<line x1="12" y1="12" x2="12" y2="15"/>
|
|
108
|
+
</svg>
|
|
109
|
+
Fork Session
|
|
110
|
+
</button>
|
|
111
|
+
<div class="context-menu-divider"></div>
|
|
112
|
+
<button class="context-menu-item context-menu-item--accent" data-action="context-new-session">
|
|
113
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
114
|
+
<path d="M12 5v14M5 12h14"/>
|
|
115
|
+
</svg>
|
|
116
|
+
New Session on Project
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Show context menu.
|
|
124
|
+
* NOTE: Menu is appended to document.body (like ContextMenu class) to avoid
|
|
125
|
+
* event bubbling issues with the welcome screen's click handlers.
|
|
126
|
+
*/
|
|
127
|
+
export function showContextMenu(session, x, y, container) {
|
|
128
|
+
// Close any existing context menu
|
|
129
|
+
closeContextMenu(container);
|
|
130
|
+
|
|
131
|
+
state.contextMenuSession = session;
|
|
132
|
+
state.contextMenuPos = { x, y };
|
|
133
|
+
state.contextMenuContainer = container; // Store for later use
|
|
134
|
+
|
|
135
|
+
// Track when menu was opened for click suppression
|
|
136
|
+
contextMenuOpenTime.value = Date.now();
|
|
137
|
+
|
|
138
|
+
// Render context menu - append to body, not container
|
|
139
|
+
const menuHtml = renderContextMenu(session, x, y);
|
|
140
|
+
document.body.insertAdjacentHTML('beforeend', menuHtml);
|
|
141
|
+
|
|
142
|
+
// Adjust position if menu would go off screen
|
|
143
|
+
const menu = document.body.querySelector('.session-context-menu');
|
|
144
|
+
if (menu) {
|
|
145
|
+
const rect = menu.getBoundingClientRect();
|
|
146
|
+
const viewportWidth = window.innerWidth;
|
|
147
|
+
const viewportHeight = window.innerHeight;
|
|
148
|
+
|
|
149
|
+
if (rect.right > viewportWidth) {
|
|
150
|
+
menu.style.left = `${x - rect.width}px`;
|
|
151
|
+
}
|
|
152
|
+
if (rect.bottom > viewportHeight) {
|
|
153
|
+
menu.style.top = `${y - rect.height}px`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
attachContextMenuListeners(container);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Close context menu.
|
|
162
|
+
*/
|
|
163
|
+
export function closeContextMenu(container) {
|
|
164
|
+
state.contextMenuSession = null;
|
|
165
|
+
state.contextMenuPos = null;
|
|
166
|
+
state.contextMenuContainer = null;
|
|
167
|
+
|
|
168
|
+
// Menu is in document.body, not container
|
|
169
|
+
const overlay = document.body.querySelector('.session-context-overlay');
|
|
170
|
+
const menu = document.body.querySelector('.session-context-menu');
|
|
171
|
+
if (overlay) overlay.remove();
|
|
172
|
+
if (menu) menu.remove();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Attach context menu event listeners.
|
|
177
|
+
*
|
|
178
|
+
* KEY: Uses setTimeout to delay adding dismiss listeners.
|
|
179
|
+
* This is the standard pattern (used by ContextMenu class) to prevent
|
|
180
|
+
* the same touch gesture that opened the menu from immediately closing it.
|
|
181
|
+
*
|
|
182
|
+
* Additional protection: We track the open timestamp and ignore clicks
|
|
183
|
+
* that happen too close to when the menu was opened (iOS can generate
|
|
184
|
+
* synthetic click events after touchend).
|
|
185
|
+
*/
|
|
186
|
+
function attachContextMenuListeners(container) {
|
|
187
|
+
const session = state.contextMenuSession;
|
|
188
|
+
if (!session) return;
|
|
189
|
+
|
|
190
|
+
// Menu is now in document.body
|
|
191
|
+
const overlay = document.body.querySelector('.session-context-overlay');
|
|
192
|
+
const menu = document.body.querySelector('.session-context-menu');
|
|
193
|
+
|
|
194
|
+
// Use the shared open timestamp for protection against immediate synthetic clicks
|
|
195
|
+
const openedAt = contextMenuOpenTime.value;
|
|
196
|
+
const minClickDelay = 350; // Ignore clicks within 350ms of opening (increased for iOS)
|
|
197
|
+
|
|
198
|
+
// Delay adding dismiss listeners to avoid immediate dismissal from same gesture
|
|
199
|
+
// This is the key pattern from ContextMenu class
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
// Document-level click listener to dismiss
|
|
202
|
+
const dismissOnClick = (e) => {
|
|
203
|
+
// Don't dismiss if clicking inside the menu
|
|
204
|
+
if (menu && menu.contains(e.target)) return;
|
|
205
|
+
// Don't dismiss if clicking the overlay (it has its own handler)
|
|
206
|
+
if (overlay && overlay.contains(e.target)) return;
|
|
207
|
+
// Ignore clicks that happen too soon after opening (synthetic iOS clicks)
|
|
208
|
+
if (Date.now() - openedAt < minClickDelay) return;
|
|
209
|
+
closeContextMenu(container);
|
|
210
|
+
document.removeEventListener('click', dismissOnClick, true);
|
|
211
|
+
};
|
|
212
|
+
document.addEventListener('click', dismissOnClick, true);
|
|
213
|
+
}, 10); // Match ContextMenu class timing
|
|
214
|
+
|
|
215
|
+
// Overlay click - but also needs delay protection for iOS
|
|
216
|
+
if (overlay) {
|
|
217
|
+
overlay.addEventListener('click', (e) => {
|
|
218
|
+
e.stopPropagation();
|
|
219
|
+
// Same protection: ignore clicks too close to menu open time
|
|
220
|
+
if (Date.now() - openedAt < minClickDelay) return;
|
|
221
|
+
closeContextMenu(container);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Menu item handlers - these can be immediate since user explicitly clicks them
|
|
226
|
+
// Toggle favorite
|
|
227
|
+
menu?.querySelector('[data-action="context-toggle-favorite"]')?.addEventListener('click', async (e) => {
|
|
228
|
+
e.stopPropagation();
|
|
229
|
+
closeContextMenu(container);
|
|
230
|
+
await toggleFavorite(session.session_id, container);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Rename
|
|
234
|
+
menu?.querySelector('[data-action="context-rename"]')?.addEventListener('click', async (e) => {
|
|
235
|
+
e.stopPropagation();
|
|
236
|
+
closeContextMenu(container);
|
|
237
|
+
await renameSession(session.session_id, session.name || 'Session', container);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Open (current tab)
|
|
241
|
+
menu?.querySelector('[data-action="context-open"]')?.addEventListener('click', (e) => {
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
// Save state before opening (for "back to sessions" feature)
|
|
244
|
+
saveWelcomeState(container);
|
|
245
|
+
closeContextMenu(container);
|
|
246
|
+
window.dispatchEvent(new CustomEvent('welcome:open-session', {
|
|
247
|
+
detail: { sessionId: session.session_id, projectPath: session.project_path, fromWelcome: true }
|
|
248
|
+
}));
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Open in new tab (no back button needed since we stay on welcome)
|
|
252
|
+
menu?.querySelector('[data-action="context-open-new-tab"]')?.addEventListener('click', (e) => {
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
closeContextMenu(container);
|
|
255
|
+
window.dispatchEvent(new CustomEvent('welcome:open-session-new-tab', {
|
|
256
|
+
detail: { sessionId: session.session_id, projectPath: session.project_path }
|
|
257
|
+
}));
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Preview
|
|
261
|
+
menu?.querySelector('[data-action="context-preview"]')?.addEventListener('click', (e) => {
|
|
262
|
+
e.stopPropagation();
|
|
263
|
+
closeContextMenu(container);
|
|
264
|
+
showPreview(session, container);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Copy session ID
|
|
268
|
+
menu?.querySelector('[data-action="context-copy-id"]')?.addEventListener('click', async (e) => {
|
|
269
|
+
e.stopPropagation();
|
|
270
|
+
closeContextMenu(container);
|
|
271
|
+
if (await copyToClipboard(session.session_id)) showToast(S.toast.session_id_copied);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Copy session URL
|
|
275
|
+
menu?.querySelector('[data-action="context-copy-url"]')?.addEventListener('click', async (e) => {
|
|
276
|
+
e.stopPropagation();
|
|
277
|
+
closeContextMenu(container);
|
|
278
|
+
const url = `${location.origin}/app?session=${session.session_id}`;
|
|
279
|
+
if (await copyToClipboard(url)) showToast(S.toast.url_copied);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Fork
|
|
283
|
+
menu?.querySelector('[data-action="context-fork"]')?.addEventListener('click', (e) => {
|
|
284
|
+
e.stopPropagation();
|
|
285
|
+
// Save state before forking
|
|
286
|
+
saveWelcomeState(container);
|
|
287
|
+
closeContextMenu(container);
|
|
288
|
+
window.dispatchEvent(new CustomEvent('welcome:fork-session', {
|
|
289
|
+
detail: { sessionId: session.session_id, projectPath: session.project_path, fromWelcome: true }
|
|
290
|
+
}));
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// New session on this project
|
|
294
|
+
menu?.querySelector('[data-action="context-new-session"]')?.addEventListener('click', (e) => {
|
|
295
|
+
e.stopPropagation();
|
|
296
|
+
closeContextMenu(container);
|
|
297
|
+
window.dispatchEvent(new CustomEvent('welcome:new-session-on-project', {
|
|
298
|
+
detail: { projectPath: session.project_path }
|
|
299
|
+
}));
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
304
|
+
// LONG-PRESS DETECTION
|
|
305
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
306
|
+
|
|
307
|
+
export function setupLongPress(element, session, container) {
|
|
308
|
+
let startX, startY;
|
|
309
|
+
|
|
310
|
+
const startLongPress = (x, y) => {
|
|
311
|
+
startX = x;
|
|
312
|
+
startY = y;
|
|
313
|
+
longPress.triggered = false;
|
|
314
|
+
longPress.timer = setTimeout(() => {
|
|
315
|
+
longPress.triggered = true;
|
|
316
|
+
longPress.timestamp = Date.now();
|
|
317
|
+
longPress.suppressNextClick = true; // Suppress the click that iOS generates after touchend
|
|
318
|
+
showContextMenu(session, x, y, container);
|
|
319
|
+
}, 500); // 500ms long press
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const cancelLongPress = () => {
|
|
323
|
+
if (longPress.timer) {
|
|
324
|
+
clearTimeout(longPress.timer);
|
|
325
|
+
longPress.timer = null;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const checkMove = (x, y) => {
|
|
330
|
+
// Cancel if moved more than 10px
|
|
331
|
+
if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) {
|
|
332
|
+
cancelLongPress();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Touch events - use passive: false for touchstart/touchend to allow preventDefault
|
|
337
|
+
element.addEventListener('touchstart', (e) => {
|
|
338
|
+
// Don't start long press if touching project element (has its own handler)
|
|
339
|
+
if (e.target.closest('[data-action="filter-project"]')) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const touch = e.touches[0];
|
|
343
|
+
startLongPress(touch.clientX, touch.clientY);
|
|
344
|
+
}, { passive: true });
|
|
345
|
+
|
|
346
|
+
element.addEventListener('touchmove', (e) => {
|
|
347
|
+
const touch = e.touches[0];
|
|
348
|
+
checkMove(touch.clientX, touch.clientY);
|
|
349
|
+
}, { passive: true });
|
|
350
|
+
|
|
351
|
+
element.addEventListener('touchend', (e) => {
|
|
352
|
+
cancelLongPress();
|
|
353
|
+
// If long press was triggered, prevent any follow-up actions
|
|
354
|
+
if (longPress.triggered) {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
e.stopPropagation();
|
|
357
|
+
e.stopImmediatePropagation();
|
|
358
|
+
// Keep suppressNextClick true - it will be cleared by the click handler or timeout
|
|
359
|
+
setTimeout(() => { longPress.suppressNextClick = false; }, 400);
|
|
360
|
+
}
|
|
361
|
+
}, { passive: false }); // Need passive: false to allow preventDefault
|
|
362
|
+
|
|
363
|
+
element.addEventListener('touchcancel', cancelLongPress);
|
|
364
|
+
|
|
365
|
+
// Capture click events and suppress them if triggered by long press
|
|
366
|
+
element.addEventListener('click', (e) => {
|
|
367
|
+
if (longPress.suppressNextClick) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
e.stopPropagation();
|
|
370
|
+
e.stopImmediatePropagation();
|
|
371
|
+
longPress.suppressNextClick = false;
|
|
372
|
+
}
|
|
373
|
+
}, { capture: true });
|
|
374
|
+
|
|
375
|
+
// Right-click
|
|
376
|
+
element.addEventListener('contextmenu', (e) => {
|
|
377
|
+
e.preventDefault();
|
|
378
|
+
e.stopPropagation();
|
|
379
|
+
longPress.triggered = true;
|
|
380
|
+
longPress.timestamp = Date.now();
|
|
381
|
+
longPress.suppressNextClick = true;
|
|
382
|
+
showContextMenu(session, e.clientX, e.clientY, container);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Check if a long press was recently triggered (within 500ms).
|
|
388
|
+
* This helps prevent stray click events from being processed.
|
|
389
|
+
*/
|
|
390
|
+
export function wasLongPressRecent() {
|
|
391
|
+
return longPress.triggered && (Date.now() - longPress.timestamp < 500);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
395
|
+
// PROJECT CONTEXT MENU
|
|
396
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Show context menu for a project.
|
|
400
|
+
* @param {string} projectPath - Full project path
|
|
401
|
+
* @param {string} projectName - Display name
|
|
402
|
+
* @param {number} x - Mouse X
|
|
403
|
+
* @param {number} y - Mouse Y
|
|
404
|
+
* @param {HTMLElement} container
|
|
405
|
+
*/
|
|
406
|
+
export function showProjectContextMenu(projectPath, projectName, x, y, container) {
|
|
407
|
+
// Close any existing context menu
|
|
408
|
+
closeProjectContextMenu(container);
|
|
409
|
+
|
|
410
|
+
state.projectContextMenu = { path: projectPath, name: projectName, x, y };
|
|
411
|
+
|
|
412
|
+
// Render context menu
|
|
413
|
+
const menu = document.createElement('div');
|
|
414
|
+
menu.className = 'project-context-menu';
|
|
415
|
+
menu.innerHTML = `
|
|
416
|
+
<div class="context-menu-item" data-action="ctx-filter-project">
|
|
417
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
|
|
418
|
+
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
|
|
419
|
+
</svg>
|
|
420
|
+
Filter by project
|
|
421
|
+
</div>
|
|
422
|
+
<div class="context-menu-item" data-action="ctx-rename-project">
|
|
423
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
|
|
424
|
+
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
|
|
425
|
+
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
426
|
+
</svg>
|
|
427
|
+
${S.ui.welcome.context.rename_project}
|
|
428
|
+
</div>
|
|
429
|
+
<div class="context-menu-item" data-action="ctx-set-color">
|
|
430
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
|
|
431
|
+
<circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/>
|
|
432
|
+
<circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/>
|
|
433
|
+
<circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/>
|
|
434
|
+
<circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/>
|
|
435
|
+
<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/>
|
|
436
|
+
</svg>
|
|
437
|
+
${S.ui.welcome.context.set_color}
|
|
438
|
+
</div>
|
|
439
|
+
<div class="context-menu-item" data-action="ctx-new-session">
|
|
440
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
|
|
441
|
+
<line x1="12" y1="5" x2="12" y2="19"/>
|
|
442
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
443
|
+
</svg>
|
|
444
|
+
New session
|
|
445
|
+
</div>
|
|
446
|
+
<div class="context-menu-separator"></div>
|
|
447
|
+
<div class="context-menu-item" data-action="ctx-copy-path">
|
|
448
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
|
|
449
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
|
|
450
|
+
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
|
|
451
|
+
</svg>
|
|
452
|
+
Copy path
|
|
453
|
+
</div>
|
|
454
|
+
<div class="context-menu-item" data-action="ctx-open-terminal">
|
|
455
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
|
|
456
|
+
<polyline points="4 17 10 11 4 5"/>
|
|
457
|
+
<line x1="12" y1="19" x2="20" y2="19"/>
|
|
458
|
+
</svg>
|
|
459
|
+
Open in terminal
|
|
460
|
+
</div>
|
|
461
|
+
`;
|
|
462
|
+
|
|
463
|
+
// Position menu
|
|
464
|
+
menu.style.position = 'fixed';
|
|
465
|
+
menu.style.left = `${x}px`;
|
|
466
|
+
menu.style.top = `${y}px`;
|
|
467
|
+
menu.style.zIndex = '9999';
|
|
468
|
+
|
|
469
|
+
// Add to DOM
|
|
470
|
+
document.body.appendChild(menu);
|
|
471
|
+
|
|
472
|
+
// Adjust position if menu goes off screen
|
|
473
|
+
requestAnimationFrame(() => {
|
|
474
|
+
const rect = menu.getBoundingClientRect();
|
|
475
|
+
if (rect.right > window.innerWidth) {
|
|
476
|
+
menu.style.left = `${window.innerWidth - rect.width - 8}px`;
|
|
477
|
+
}
|
|
478
|
+
if (rect.bottom > window.innerHeight) {
|
|
479
|
+
menu.style.top = `${window.innerHeight - rect.height - 8}px`;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Add click handlers
|
|
484
|
+
menu.querySelectorAll('.context-menu-item').forEach(item => {
|
|
485
|
+
item.addEventListener('click', () => {
|
|
486
|
+
const action = item.dataset.action;
|
|
487
|
+
handleProjectContextAction(action, projectPath, projectName, container);
|
|
488
|
+
closeProjectContextMenu(container);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Close on click outside
|
|
493
|
+
const closeHandler = (e) => {
|
|
494
|
+
if (!menu.contains(e.target)) {
|
|
495
|
+
closeProjectContextMenu(container);
|
|
496
|
+
document.removeEventListener('click', closeHandler);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
setTimeout(() => document.addEventListener('click', closeHandler), 0);
|
|
500
|
+
|
|
501
|
+
// Close on Escape
|
|
502
|
+
const escHandler = (e) => {
|
|
503
|
+
if (e.key === 'Escape') {
|
|
504
|
+
closeProjectContextMenu(container);
|
|
505
|
+
document.removeEventListener('keydown', escHandler);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
document.addEventListener('keydown', escHandler);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Close project context menu.
|
|
513
|
+
*/
|
|
514
|
+
export function closeProjectContextMenu(container) {
|
|
515
|
+
state.projectContextMenu = null;
|
|
516
|
+
document.querySelectorAll('.project-context-menu').forEach(m => m.remove());
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Handle project context menu action.
|
|
521
|
+
*/
|
|
522
|
+
function handleProjectContextAction(action, projectPath, projectName, container) {
|
|
523
|
+
switch (action) {
|
|
524
|
+
case 'ctx-filter-project':
|
|
525
|
+
setProjectFilter(projectPath, projectName, container);
|
|
526
|
+
break;
|
|
527
|
+
|
|
528
|
+
case 'ctx-rename-project':
|
|
529
|
+
renameProject(projectPath, projectName, container);
|
|
530
|
+
break;
|
|
531
|
+
|
|
532
|
+
case 'ctx-set-color': {
|
|
533
|
+
// Reuse the context menu's position for the swatch popup.
|
|
534
|
+
const pos = state.projectContextMenu || {};
|
|
535
|
+
showProjectColorPicker(
|
|
536
|
+
projectPath, pos.x || 100, pos.y || 100,
|
|
537
|
+
() => renderWelcomeScreen(container),
|
|
538
|
+
);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
case 'ctx-new-session':
|
|
543
|
+
window.dispatchEvent(new CustomEvent('welcome:new-session-on-project', {
|
|
544
|
+
detail: { projectPath }
|
|
545
|
+
}));
|
|
546
|
+
break;
|
|
547
|
+
|
|
548
|
+
case 'ctx-copy-path':
|
|
549
|
+
navigator.clipboard.writeText(projectPath).then(() => {
|
|
550
|
+
// Brief visual feedback could be added here
|
|
551
|
+
debug.log('[Welcome] Copied path:', projectPath);
|
|
552
|
+
});
|
|
553
|
+
break;
|
|
554
|
+
|
|
555
|
+
case 'ctx-open-terminal':
|
|
556
|
+
window.dispatchEvent(new CustomEvent('welcome:open-terminal', {
|
|
557
|
+
detail: { cwd: projectPath }
|
|
558
|
+
}));
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
564
|
+
// PUBLIC: GLOBAL ESCAPE HANDLING
|
|
565
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Close welcome screen context menu if open (exported for global Escape handling).
|
|
569
|
+
* Re-exported through welcome.js as the public API.
|
|
570
|
+
* @returns {boolean} True if context menu was closed
|
|
571
|
+
*/
|
|
572
|
+
export function closeWelcomeContextMenu() {
|
|
573
|
+
// Check session context menu
|
|
574
|
+
if (state.contextMenuSession) {
|
|
575
|
+
closeContextMenu(state.contextMenuContainer);
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
// Check project context menu
|
|
579
|
+
if (state.projectContextMenu) {
|
|
580
|
+
const container = document.getElementById('welcome-container');
|
|
581
|
+
closeProjectContextMenu(container);
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
}
|