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,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gesture Handler for Tab Navigation
|
|
3
|
+
*
|
|
4
|
+
* Intercepts two-finger trackpad swipes and touch gestures
|
|
5
|
+
* to enable tab switching instead of browser back/forward navigation.
|
|
6
|
+
*
|
|
7
|
+
* Works best in PWA standalone mode where browser navigation is disabled.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* GestureManager - Handles swipe gestures for tab navigation
|
|
12
|
+
*/
|
|
13
|
+
export class GestureManager {
|
|
14
|
+
constructor(app) {
|
|
15
|
+
this.app = app;
|
|
16
|
+
|
|
17
|
+
// Wheel gesture state (trackpad two-finger swipe)
|
|
18
|
+
this.wheelAccumulator = 0;
|
|
19
|
+
this.wheelTimeout = null;
|
|
20
|
+
this.wheelCooldown = false;
|
|
21
|
+
|
|
22
|
+
// Touch gesture state
|
|
23
|
+
this.touchStartX = 0;
|
|
24
|
+
this.touchStartY = 0;
|
|
25
|
+
this.touchStartTime = 0;
|
|
26
|
+
this.touchCooldown = false;
|
|
27
|
+
this.isSwiping = false;
|
|
28
|
+
this.swipeIndicator = null;
|
|
29
|
+
|
|
30
|
+
// Configuration
|
|
31
|
+
this.config = {
|
|
32
|
+
// Wheel settings
|
|
33
|
+
wheelThreshold: 120, // Accumulated deltaX to trigger (px)
|
|
34
|
+
wheelResetDelay: 150, // Reset accumulator after this delay (ms)
|
|
35
|
+
wheelCooldownTime: 400, // Cooldown between triggers (ms)
|
|
36
|
+
|
|
37
|
+
// Touch settings
|
|
38
|
+
touchThreshold: 130, // Minimum swipe distance (px) - ~17% of iPad portrait
|
|
39
|
+
touchMaxTime: 500, // Maximum swipe duration (ms)
|
|
40
|
+
touchAngleThreshold: 2.0, // Horizontal must be 2x vertical
|
|
41
|
+
touchCooldownTime: 400, // Cooldown between triggers (ms)
|
|
42
|
+
|
|
43
|
+
// Edge swipe detection (for non-PWA mode)
|
|
44
|
+
edgeZone: 30, // Edge zone width (px)
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.init();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
init() {
|
|
51
|
+
// Wheel events for trackpad
|
|
52
|
+
document.addEventListener('wheel', (e) => this.handleWheel(e), { passive: false });
|
|
53
|
+
|
|
54
|
+
// Touch events for mobile/tablet
|
|
55
|
+
// Using passive: false to allow preventDefault() for blocking browser back/forward gestures
|
|
56
|
+
// This is necessary because iOS Safari interprets edge swipes as navigation
|
|
57
|
+
document.addEventListener('touchstart', (e) => this.handleTouchStart(e), { passive: false });
|
|
58
|
+
document.addEventListener('touchmove', (e) => this.handleTouchMove(e), { passive: false });
|
|
59
|
+
document.addEventListener('touchend', (e) => this.handleTouchEnd(e), { passive: false });
|
|
60
|
+
document.addEventListener('touchcancel', (e) => this.handleTouchCancel(e), { passive: true });
|
|
61
|
+
|
|
62
|
+
// Create swipe indicator element
|
|
63
|
+
this.createSwipeIndicator();
|
|
64
|
+
|
|
65
|
+
// History API trap (fallback for any navigation that slips through)
|
|
66
|
+
this.setupHistoryTrap();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Handle wheel events (trackpad two-finger swipe)
|
|
71
|
+
*/
|
|
72
|
+
handleWheel(e) {
|
|
73
|
+
// Skip if in terminal (has its own scrolling)
|
|
74
|
+
const inTerminal = e.target.closest('.terminal-container, .xterm');
|
|
75
|
+
|
|
76
|
+
// Skip if inside a floating widget (widgets contain their own scroll)
|
|
77
|
+
const inWidget = e.target.closest('.widget-floating');
|
|
78
|
+
|
|
79
|
+
// Skip if target has horizontal scroll
|
|
80
|
+
const hasScroll = this.hasHorizontalScroll(e.target);
|
|
81
|
+
|
|
82
|
+
// Check for any horizontal component (even small)
|
|
83
|
+
// iPadOS trackpad back/forward gesture starts with small deltaX values
|
|
84
|
+
const hasHorizontalComponent = Math.abs(e.deltaX) > 0;
|
|
85
|
+
// Only treat horizontal as the *primary* axis when it dominates.
|
|
86
|
+
// Many mice/trackpads emit a tiny incidental deltaX on a pure vertical
|
|
87
|
+
// scroll — blocking the whole event on that would kill chat scroll on
|
|
88
|
+
// any message that has no horizontally-scrolling descendant (the
|
|
89
|
+
// ancestor check above only saves us when a <pre> or table is nearby).
|
|
90
|
+
// iPadOS back/forward edge swipes are intrinsically horizontal, so
|
|
91
|
+
// they still satisfy |deltaX| > |deltaY| and remain blocked.
|
|
92
|
+
const isHorizontalDominant = Math.abs(e.deltaX) > Math.abs(e.deltaY);
|
|
93
|
+
|
|
94
|
+
// Prevent browser back/forward only for primarily-horizontal wheel
|
|
95
|
+
// movement (except in terminal, horizontally scrollable areas, or
|
|
96
|
+
// floating widgets). Must happen early before the browser can
|
|
97
|
+
// interpret it as navigation.
|
|
98
|
+
if (hasHorizontalComponent && isHorizontalDominant && !inTerminal && !hasScroll && !inWidget) {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Only care about significant horizontal movement for tab switching
|
|
103
|
+
const isHorizontal = Math.abs(e.deltaX) > Math.abs(e.deltaY) && Math.abs(e.deltaX) > 5;
|
|
104
|
+
|
|
105
|
+
// Now apply filtering for tab switch logic
|
|
106
|
+
if (this.wheelCooldown) return;
|
|
107
|
+
if (inTerminal) return;
|
|
108
|
+
if (inWidget) return;
|
|
109
|
+
if (hasScroll) return;
|
|
110
|
+
if (!isHorizontal) return;
|
|
111
|
+
|
|
112
|
+
// Accumulate horizontal movement
|
|
113
|
+
this.wheelAccumulator += e.deltaX;
|
|
114
|
+
|
|
115
|
+
// Clear existing timeout
|
|
116
|
+
if (this.wheelTimeout) {
|
|
117
|
+
clearTimeout(this.wheelTimeout);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if threshold exceeded
|
|
121
|
+
if (Math.abs(this.wheelAccumulator) >= this.config.wheelThreshold) {
|
|
122
|
+
const direction = this.wheelAccumulator > 0 ? 1 : -1; // 1 = next, -1 = prev
|
|
123
|
+
this.triggerTabSwitch(direction, 'wheel');
|
|
124
|
+
|
|
125
|
+
// Reset and set cooldown
|
|
126
|
+
this.wheelAccumulator = 0;
|
|
127
|
+
this.wheelCooldown = true;
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
this.wheelCooldown = false;
|
|
130
|
+
}, this.config.wheelCooldownTime);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Reset accumulator after delay (gesture ended)
|
|
134
|
+
this.wheelTimeout = setTimeout(() => {
|
|
135
|
+
this.wheelAccumulator = 0;
|
|
136
|
+
}, this.config.wheelResetDelay);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Handle touch start
|
|
141
|
+
*/
|
|
142
|
+
handleTouchStart(e) {
|
|
143
|
+
if (e.touches.length !== 1) return; // Single finger only
|
|
144
|
+
|
|
145
|
+
// Skip if starting inside a floating overlay (widget, modal, dialog)
|
|
146
|
+
if (this.isInsideOverlay(e.target)) return;
|
|
147
|
+
|
|
148
|
+
this.touchStartX = e.touches[0].clientX;
|
|
149
|
+
this.touchStartY = e.touches[0].clientY;
|
|
150
|
+
this.touchStartTime = Date.now();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Handle touch end
|
|
155
|
+
*/
|
|
156
|
+
handleTouchEnd(e) {
|
|
157
|
+
// Skip if cooldown active
|
|
158
|
+
if (this.touchCooldown) return;
|
|
159
|
+
|
|
160
|
+
// Skip if no start recorded
|
|
161
|
+
if (!this.touchStartTime) return;
|
|
162
|
+
|
|
163
|
+
const touch = e.changedTouches[0];
|
|
164
|
+
const deltaX = touch.clientX - this.touchStartX;
|
|
165
|
+
const deltaY = touch.clientY - this.touchStartY;
|
|
166
|
+
const deltaTime = Date.now() - this.touchStartTime;
|
|
167
|
+
|
|
168
|
+
// Reset start
|
|
169
|
+
this.touchStartTime = 0;
|
|
170
|
+
|
|
171
|
+
// Skip if too slow
|
|
172
|
+
if (deltaTime > this.config.touchMaxTime) return;
|
|
173
|
+
|
|
174
|
+
// Skip if not enough horizontal movement
|
|
175
|
+
if (Math.abs(deltaX) < this.config.touchThreshold) return;
|
|
176
|
+
|
|
177
|
+
// Skip if not primarily horizontal
|
|
178
|
+
if (Math.abs(deltaX) < Math.abs(deltaY) * this.config.touchAngleThreshold) return;
|
|
179
|
+
|
|
180
|
+
// Skip if in terminal
|
|
181
|
+
if (e.target.closest('.terminal-container, .xterm')) return;
|
|
182
|
+
|
|
183
|
+
// Skip if target has horizontal scroll
|
|
184
|
+
if (this.hasHorizontalScroll(e.target)) return;
|
|
185
|
+
|
|
186
|
+
// Skip if text is being selected (user is dragging to select, not swiping)
|
|
187
|
+
const selection = window.getSelection();
|
|
188
|
+
if (selection && selection.toString().trim().length > 0) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Determine direction: swipe left (deltaX < 0) = next, swipe right (deltaX > 0) = prev
|
|
193
|
+
const direction = deltaX < 0 ? 1 : -1;
|
|
194
|
+
|
|
195
|
+
// Prevent synthetic click from this touch sequence (stops iOS keyboard popup)
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
|
|
198
|
+
// Flash indicator to show threshold was reached
|
|
199
|
+
this.flashIndicator(direction);
|
|
200
|
+
|
|
201
|
+
this.triggerTabSwitch(direction, 'touch');
|
|
202
|
+
|
|
203
|
+
// Set cooldown
|
|
204
|
+
this.touchCooldown = true;
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
this.touchCooldown = false;
|
|
207
|
+
}, this.config.touchCooldownTime);
|
|
208
|
+
|
|
209
|
+
// Reset swipe state
|
|
210
|
+
this.isSwiping = false;
|
|
211
|
+
this.hideIndicator();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handle touch cancel (e.g., interrupted by system gesture)
|
|
216
|
+
*/
|
|
217
|
+
handleTouchCancel(e) {
|
|
218
|
+
this.touchStartTime = 0;
|
|
219
|
+
this.isSwiping = false;
|
|
220
|
+
this.hideIndicator();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Handle touch move - track swipe progress for visual feedback
|
|
225
|
+
* Also prevents browser back/forward gestures when horizontal swipe detected
|
|
226
|
+
*/
|
|
227
|
+
handleTouchMove(e) {
|
|
228
|
+
// Skip if no start recorded or cooldown active
|
|
229
|
+
if (!this.touchStartTime || this.touchCooldown) return;
|
|
230
|
+
if (e.touches.length !== 1) return;
|
|
231
|
+
|
|
232
|
+
const touch = e.touches[0];
|
|
233
|
+
const deltaX = touch.clientX - this.touchStartX;
|
|
234
|
+
const deltaY = touch.clientY - this.touchStartY;
|
|
235
|
+
const deltaTime = Date.now() - this.touchStartTime;
|
|
236
|
+
|
|
237
|
+
// Skip if too slow already
|
|
238
|
+
if (deltaTime > this.config.touchMaxTime) {
|
|
239
|
+
this.hideIndicator();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check if this looks like a horizontal swipe
|
|
244
|
+
const absX = Math.abs(deltaX);
|
|
245
|
+
const absY = Math.abs(deltaY);
|
|
246
|
+
|
|
247
|
+
// Require some minimum movement before showing indicator
|
|
248
|
+
if (absX < 20) {
|
|
249
|
+
if (this.isSwiping) {
|
|
250
|
+
this.hideIndicator();
|
|
251
|
+
this.isSwiping = false;
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Must be primarily horizontal
|
|
257
|
+
if (absX < absY * this.config.touchAngleThreshold) {
|
|
258
|
+
if (this.isSwiping) {
|
|
259
|
+
this.hideIndicator();
|
|
260
|
+
this.isSwiping = false;
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Skip if in terminal or scrollable area
|
|
266
|
+
if (e.target.closest('.terminal-container, .xterm')) return;
|
|
267
|
+
if (this.hasHorizontalScroll(e.target)) return;
|
|
268
|
+
|
|
269
|
+
// PREVENT browser back/forward navigation for horizontal swipes
|
|
270
|
+
// This is critical for iOS Safari which interprets edge swipes as navigation
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
|
|
273
|
+
// We're swiping! Show indicator
|
|
274
|
+
this.isSwiping = true;
|
|
275
|
+
const progress = Math.min(absX / this.config.touchThreshold, 1);
|
|
276
|
+
const direction = deltaX < 0 ? 1 : -1; // 1 = next (swipe left), -1 = prev (swipe right)
|
|
277
|
+
this.updateIndicator(progress, direction);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Create the swipe indicator element
|
|
282
|
+
*/
|
|
283
|
+
createSwipeIndicator() {
|
|
284
|
+
this.swipeIndicator = document.createElement('div');
|
|
285
|
+
this.swipeIndicator.className = 'swipe-indicator';
|
|
286
|
+
this.swipeIndicator.innerHTML = `
|
|
287
|
+
<div class="swipe-indicator-bar"></div>
|
|
288
|
+
<div class="swipe-indicator-label"></div>
|
|
289
|
+
`;
|
|
290
|
+
document.body.appendChild(this.swipeIndicator);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Update indicator progress and direction
|
|
295
|
+
* @param {number} progress - 0 to 1
|
|
296
|
+
* @param {number} direction - 1 for next, -1 for prev
|
|
297
|
+
*/
|
|
298
|
+
updateIndicator(progress, direction) {
|
|
299
|
+
if (!this.swipeIndicator) return;
|
|
300
|
+
|
|
301
|
+
const bar = this.swipeIndicator.querySelector('.swipe-indicator-bar');
|
|
302
|
+
const label = this.swipeIndicator.querySelector('.swipe-indicator-label');
|
|
303
|
+
|
|
304
|
+
// Show indicator
|
|
305
|
+
this.swipeIndicator.classList.add('visible');
|
|
306
|
+
|
|
307
|
+
// Set direction class
|
|
308
|
+
this.swipeIndicator.classList.toggle('direction-next', direction === 1);
|
|
309
|
+
this.swipeIndicator.classList.toggle('direction-prev', direction === -1);
|
|
310
|
+
|
|
311
|
+
// Update progress (bar width)
|
|
312
|
+
const percent = Math.round(progress * 100);
|
|
313
|
+
bar.style.width = `${percent}%`;
|
|
314
|
+
|
|
315
|
+
// Threshold reached state
|
|
316
|
+
const thresholdReached = progress >= 1;
|
|
317
|
+
this.swipeIndicator.classList.toggle('threshold-reached', thresholdReached);
|
|
318
|
+
|
|
319
|
+
// Update label
|
|
320
|
+
if (thresholdReached) {
|
|
321
|
+
label.textContent = direction === 1 ? 'Next tab →' : '← Prev tab';
|
|
322
|
+
} else {
|
|
323
|
+
label.textContent = `${percent}%`;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Flash indicator to confirm tab switch
|
|
329
|
+
*/
|
|
330
|
+
flashIndicator(direction) {
|
|
331
|
+
if (!this.swipeIndicator) return;
|
|
332
|
+
|
|
333
|
+
this.swipeIndicator.classList.add('visible', 'flash');
|
|
334
|
+
this.swipeIndicator.classList.toggle('direction-next', direction === 1);
|
|
335
|
+
this.swipeIndicator.classList.toggle('direction-prev', direction === -1);
|
|
336
|
+
|
|
337
|
+
const bar = this.swipeIndicator.querySelector('.swipe-indicator-bar');
|
|
338
|
+
bar.style.width = '100%';
|
|
339
|
+
|
|
340
|
+
const label = this.swipeIndicator.querySelector('.swipe-indicator-label');
|
|
341
|
+
label.textContent = direction === 1 ? 'Next tab →' : '← Prev tab';
|
|
342
|
+
|
|
343
|
+
// Remove flash after animation
|
|
344
|
+
setTimeout(() => {
|
|
345
|
+
this.swipeIndicator.classList.remove('flash');
|
|
346
|
+
this.hideIndicator();
|
|
347
|
+
}, 300);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Hide the indicator
|
|
352
|
+
*/
|
|
353
|
+
hideIndicator() {
|
|
354
|
+
if (!this.swipeIndicator) return;
|
|
355
|
+
this.swipeIndicator.classList.remove('visible', 'threshold-reached', 'direction-next', 'direction-prev');
|
|
356
|
+
|
|
357
|
+
const bar = this.swipeIndicator.querySelector('.swipe-indicator-bar');
|
|
358
|
+
if (bar) bar.style.width = '0%';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Check if element is inside a floating overlay (widget, modal, dialog)
|
|
363
|
+
* These should not trigger swipe-to-switch-tab gestures
|
|
364
|
+
*/
|
|
365
|
+
isInsideOverlay(element) {
|
|
366
|
+
// Selectors for floating UI elements that should block swipe gestures
|
|
367
|
+
const overlaySelectors = [
|
|
368
|
+
'.widget', // All widgets (floating, bottom-sheet, modal, sidebar)
|
|
369
|
+
'.modal', // Legacy modals
|
|
370
|
+
'#modal-overlay', // Modal overlay
|
|
371
|
+
'.prompt-dialog', // Prompt dialogs
|
|
372
|
+
'.prompt-dialog-overlay',
|
|
373
|
+
'.context-menu', // Context menus
|
|
374
|
+
'.tabs-overview-dropdown', // Tab overview dropdown
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
return element.closest(overlaySelectors.join(', ')) !== null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Check if element or ancestors have horizontal scroll
|
|
382
|
+
*/
|
|
383
|
+
hasHorizontalScroll(element) {
|
|
384
|
+
let el = element;
|
|
385
|
+
while (el && el !== document.body) {
|
|
386
|
+
if (el.scrollWidth > el.clientWidth) {
|
|
387
|
+
const style = window.getComputedStyle(el);
|
|
388
|
+
const overflow = style.overflowX;
|
|
389
|
+
if (overflow === 'auto' || overflow === 'scroll') {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
el = el.parentElement;
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Trigger tab switch
|
|
400
|
+
*/
|
|
401
|
+
triggerTabSwitch(direction, source) {
|
|
402
|
+
// direction: 1 = next, -1 = prev
|
|
403
|
+
if (typeof this.app?.cycleTab === 'function') {
|
|
404
|
+
// Temporarily suppress focus on the message input to prevent
|
|
405
|
+
// iOS keyboard popup during gesture-based tab switch.
|
|
406
|
+
// Covers ALL focus paths (click handlers, reconnect, rAF callbacks).
|
|
407
|
+
const input = this.app.els?.messageInput;
|
|
408
|
+
if (input && !input._originalFocus) {
|
|
409
|
+
input._originalFocus = input.focus;
|
|
410
|
+
input.focus = () => {};
|
|
411
|
+
setTimeout(() => {
|
|
412
|
+
if (input._originalFocus) {
|
|
413
|
+
input.focus = input._originalFocus;
|
|
414
|
+
delete input._originalFocus;
|
|
415
|
+
}
|
|
416
|
+
}, 300);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
this.app.cycleTab(direction);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Setup History API trap to catch any navigation that slips through
|
|
425
|
+
*/
|
|
426
|
+
setupHistoryTrap() {
|
|
427
|
+
// Push initial state so there's something to "go back to"
|
|
428
|
+
history.pushState({ gesture: true }, '', location.href);
|
|
429
|
+
|
|
430
|
+
// Catch popstate (back/forward navigation)
|
|
431
|
+
window.addEventListener('popstate', (e) => {
|
|
432
|
+
// Re-push to prevent actual navigation
|
|
433
|
+
history.pushState({ gesture: true }, '', location.href);
|
|
434
|
+
|
|
435
|
+
// Note: We don't trigger tab switch here because we don't know
|
|
436
|
+
// the direction of the navigation attempt. The wheel/touch handlers
|
|
437
|
+
// should catch the gesture before it becomes a popstate.
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Check if running in PWA standalone mode
|
|
443
|
+
*/
|
|
444
|
+
isPWAStandalone() {
|
|
445
|
+
return window.matchMedia('(display-mode: standalone)').matches ||
|
|
446
|
+
window.navigator.standalone === true;
|
|
447
|
+
}
|
|
448
|
+
}
|