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,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Diff Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides human-friendly diff visualization:
|
|
5
|
+
* - Line matching (don't duplicate unchanged lines)
|
|
6
|
+
* - Word-level highlighting for modified lines
|
|
7
|
+
* - Collapsible unchanged sections
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Compute the longest common subsequence of two arrays
|
|
12
|
+
* Returns indices mapping: [oldIndex, newIndex] pairs for matching lines
|
|
13
|
+
*/
|
|
14
|
+
function computeLCS(oldLines, newLines) {
|
|
15
|
+
const m = oldLines.length;
|
|
16
|
+
const n = newLines.length;
|
|
17
|
+
|
|
18
|
+
// DP table
|
|
19
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
20
|
+
|
|
21
|
+
for (let i = 1; i <= m; i++) {
|
|
22
|
+
for (let j = 1; j <= n; j++) {
|
|
23
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
24
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
25
|
+
} else {
|
|
26
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Backtrack to find the actual LCS
|
|
32
|
+
const matches = [];
|
|
33
|
+
let i = m, j = n;
|
|
34
|
+
while (i > 0 && j > 0) {
|
|
35
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
36
|
+
matches.unshift([i - 1, j - 1]);
|
|
37
|
+
i--; j--;
|
|
38
|
+
} else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
39
|
+
i--;
|
|
40
|
+
} else {
|
|
41
|
+
j--;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return matches;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find word-level differences between two similar lines
|
|
50
|
+
* Returns HTML with highlighted changes
|
|
51
|
+
*/
|
|
52
|
+
function computeWordDiff(oldLine, newLine, escapeHtml) {
|
|
53
|
+
// Tokenize by word boundaries (keep whitespace as tokens)
|
|
54
|
+
const tokenize = (str) => str.match(/\S+|\s+/g) || [''];
|
|
55
|
+
|
|
56
|
+
const oldTokens = tokenize(oldLine);
|
|
57
|
+
const newTokens = tokenize(newLine);
|
|
58
|
+
|
|
59
|
+
// Find common prefix
|
|
60
|
+
let prefixLen = 0;
|
|
61
|
+
while (prefixLen < oldTokens.length &&
|
|
62
|
+
prefixLen < newTokens.length &&
|
|
63
|
+
oldTokens[prefixLen] === newTokens[prefixLen]) {
|
|
64
|
+
prefixLen++;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Find common suffix (from the remaining tokens)
|
|
68
|
+
let oldSuffixStart = oldTokens.length;
|
|
69
|
+
let newSuffixStart = newTokens.length;
|
|
70
|
+
while (oldSuffixStart > prefixLen &&
|
|
71
|
+
newSuffixStart > prefixLen &&
|
|
72
|
+
oldTokens[oldSuffixStart - 1] === newTokens[newSuffixStart - 1]) {
|
|
73
|
+
oldSuffixStart--;
|
|
74
|
+
newSuffixStart--;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Build HTML for old line (removed parts highlighted)
|
|
78
|
+
// Skip wrapping whitespace-only tokens to avoid cluttered highlight boxes
|
|
79
|
+
const buildHtml = (tokens, start, end, highlightClass) => {
|
|
80
|
+
let html = '';
|
|
81
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
82
|
+
const token = escapeHtml(tokens[i]);
|
|
83
|
+
const isWhitespace = /^\s+$/.test(tokens[i]);
|
|
84
|
+
if (i >= start && i < end && !isWhitespace) {
|
|
85
|
+
html += `<span class="${highlightClass}">${token}</span>`;
|
|
86
|
+
} else {
|
|
87
|
+
html += token;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return html;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
oldHtml: buildHtml(oldTokens, prefixLen, oldSuffixStart, 'diff-word-removed'),
|
|
95
|
+
newHtml: buildHtml(newTokens, prefixLen, newSuffixStart, 'diff-word-added')
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if two lines are "similar enough" to show as modified
|
|
101
|
+
* rather than as separate remove + add
|
|
102
|
+
*/
|
|
103
|
+
function areSimilarLines(oldLine, newLine) {
|
|
104
|
+
if (!oldLine || !newLine) return false;
|
|
105
|
+
|
|
106
|
+
// If one is much longer than the other, not similar
|
|
107
|
+
const lenRatio = Math.min(oldLine.length, newLine.length) /
|
|
108
|
+
Math.max(oldLine.length, newLine.length);
|
|
109
|
+
if (lenRatio < 0.3) return false;
|
|
110
|
+
|
|
111
|
+
// Check common prefix/suffix ratio
|
|
112
|
+
let commonChars = 0;
|
|
113
|
+
const minLen = Math.min(oldLine.length, newLine.length);
|
|
114
|
+
|
|
115
|
+
// Count matching prefix
|
|
116
|
+
for (let i = 0; i < minLen && oldLine[i] === newLine[i]; i++) {
|
|
117
|
+
commonChars++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Count matching suffix
|
|
121
|
+
for (let i = 0; i < minLen &&
|
|
122
|
+
oldLine[oldLine.length - 1 - i] === newLine[newLine.length - 1 - i] &&
|
|
123
|
+
commonChars < minLen; i++) {
|
|
124
|
+
commonChars++;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Consider similar if >40% characters match in prefix+suffix
|
|
128
|
+
return commonChars / Math.max(oldLine.length, newLine.length) > 0.4;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate a smart unified diff
|
|
133
|
+
*
|
|
134
|
+
* @param {string[]} oldLines - Original lines
|
|
135
|
+
* @param {string[]} newLines - New lines
|
|
136
|
+
* @param {number} startLine - Starting line number in file
|
|
137
|
+
* @param {function} escapeHtml - HTML escaping function
|
|
138
|
+
* @param {object} options - Options for diff generation
|
|
139
|
+
* @returns {object[]} Array of diff entries
|
|
140
|
+
*/
|
|
141
|
+
function generateSmartDiff(oldLines, newLines, startLine, escapeHtml, options = {}) {
|
|
142
|
+
const { contextLines = 2, collapseThreshold = 4 } = options;
|
|
143
|
+
|
|
144
|
+
const matches = computeLCS(oldLines, newLines);
|
|
145
|
+
const result = [];
|
|
146
|
+
|
|
147
|
+
let oldIdx = 0;
|
|
148
|
+
let newIdx = 0;
|
|
149
|
+
let matchIdx = 0;
|
|
150
|
+
|
|
151
|
+
// Track line numbers (separate for old and new)
|
|
152
|
+
let oldLineNum = startLine;
|
|
153
|
+
let newLineNum = startLine;
|
|
154
|
+
|
|
155
|
+
while (oldIdx < oldLines.length || newIdx < newLines.length) {
|
|
156
|
+
// Check if current position is a match
|
|
157
|
+
if (matchIdx < matches.length &&
|
|
158
|
+
matches[matchIdx][0] === oldIdx &&
|
|
159
|
+
matches[matchIdx][1] === newIdx) {
|
|
160
|
+
// This is an unchanged line
|
|
161
|
+
result.push({
|
|
162
|
+
type: 'context',
|
|
163
|
+
oldLineNum: oldLineNum,
|
|
164
|
+
newLineNum: newLineNum,
|
|
165
|
+
content: escapeHtml(oldLines[oldIdx]) || ' '
|
|
166
|
+
});
|
|
167
|
+
oldIdx++; newIdx++; matchIdx++;
|
|
168
|
+
oldLineNum++; newLineNum++;
|
|
169
|
+
} else {
|
|
170
|
+
// Collect consecutive changes until next match
|
|
171
|
+
const nextMatch = matchIdx < matches.length ? matches[matchIdx] : [oldLines.length, newLines.length];
|
|
172
|
+
|
|
173
|
+
const removedLines = [];
|
|
174
|
+
const addedLines = [];
|
|
175
|
+
|
|
176
|
+
while (oldIdx < nextMatch[0]) {
|
|
177
|
+
removedLines.push({ idx: oldIdx, line: oldLines[oldIdx], lineNum: oldLineNum });
|
|
178
|
+
oldIdx++; oldLineNum++;
|
|
179
|
+
}
|
|
180
|
+
while (newIdx < nextMatch[1]) {
|
|
181
|
+
addedLines.push({ idx: newIdx, line: newLines[newIdx], lineNum: newLineNum });
|
|
182
|
+
newIdx++; newLineNum++;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Try to pair similar lines for inline diff
|
|
186
|
+
const paired = [];
|
|
187
|
+
const unpaired = { removed: [...removedLines], added: [...addedLines] };
|
|
188
|
+
|
|
189
|
+
// Simple greedy pairing of similar lines
|
|
190
|
+
for (let r = 0; r < unpaired.removed.length; r++) {
|
|
191
|
+
for (let a = 0; a < unpaired.added.length; a++) {
|
|
192
|
+
if (areSimilarLines(unpaired.removed[r].line, unpaired.added[a].line)) {
|
|
193
|
+
paired.push({
|
|
194
|
+
removed: unpaired.removed[r],
|
|
195
|
+
added: unpaired.added[a]
|
|
196
|
+
});
|
|
197
|
+
unpaired.removed.splice(r, 1);
|
|
198
|
+
unpaired.added.splice(a, 1);
|
|
199
|
+
r--; // Adjust index after splice
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Collect all change entries with sort keys
|
|
206
|
+
const changeEntries = [];
|
|
207
|
+
|
|
208
|
+
// Unpaired removed lines (use old line num as sort key)
|
|
209
|
+
for (const r of unpaired.removed) {
|
|
210
|
+
changeEntries.push({
|
|
211
|
+
type: 'removed',
|
|
212
|
+
oldLineNum: r.lineNum,
|
|
213
|
+
sortKey: r.lineNum, // Sort by old position
|
|
214
|
+
content: escapeHtml(r.line) || ' '
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Paired (modified) lines with word diff
|
|
219
|
+
for (const p of paired) {
|
|
220
|
+
const wordDiff = computeWordDiff(p.removed.line, p.added.line, escapeHtml);
|
|
221
|
+
changeEntries.push({
|
|
222
|
+
type: 'modified',
|
|
223
|
+
oldLineNum: p.removed.lineNum,
|
|
224
|
+
newLineNum: p.added.lineNum,
|
|
225
|
+
sortKey: p.added.lineNum, // Sort by new position
|
|
226
|
+
oldContent: wordDiff.oldHtml,
|
|
227
|
+
newContent: wordDiff.newHtml
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Unpaired added lines
|
|
232
|
+
for (const a of unpaired.added) {
|
|
233
|
+
changeEntries.push({
|
|
234
|
+
type: 'added',
|
|
235
|
+
newLineNum: a.lineNum,
|
|
236
|
+
sortKey: a.lineNum, // Sort by new position
|
|
237
|
+
content: escapeHtml(a.line) || ' '
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Sort all changes by their position and add to result
|
|
242
|
+
changeEntries.sort((a, b) => a.sortKey - b.sortKey);
|
|
243
|
+
for (const entry of changeEntries) {
|
|
244
|
+
delete entry.sortKey; // Remove temporary sort key
|
|
245
|
+
result.push(entry);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Collapse long sequences of context lines
|
|
251
|
+
return collapseContext(result, contextLines, collapseThreshold);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Collapse long sequences of unchanged context lines
|
|
256
|
+
*/
|
|
257
|
+
function collapseContext(entries, contextLines, threshold) {
|
|
258
|
+
const result = [];
|
|
259
|
+
let contextRun = [];
|
|
260
|
+
|
|
261
|
+
for (const entry of entries) {
|
|
262
|
+
if (entry.type === 'context') {
|
|
263
|
+
contextRun.push(entry);
|
|
264
|
+
} else {
|
|
265
|
+
// Flush context run
|
|
266
|
+
if (contextRun.length > 0) {
|
|
267
|
+
result.push(...collapseRun(contextRun, contextLines, threshold));
|
|
268
|
+
contextRun = [];
|
|
269
|
+
}
|
|
270
|
+
result.push(entry);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Flush final context run
|
|
275
|
+
if (contextRun.length > 0) {
|
|
276
|
+
result.push(...collapseRun(contextRun, contextLines, threshold));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Collapse a run of context lines, keeping contextLines at start/end
|
|
284
|
+
*/
|
|
285
|
+
function collapseRun(run, contextLines, threshold) {
|
|
286
|
+
// Check if this run is at the start or end (no changes around it)
|
|
287
|
+
// For simplicity, if it's a long run, collapse the middle
|
|
288
|
+
|
|
289
|
+
if (run.length <= threshold) {
|
|
290
|
+
return run;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const result = [];
|
|
294
|
+
|
|
295
|
+
// Keep first contextLines
|
|
296
|
+
for (let i = 0; i < Math.min(contextLines, run.length); i++) {
|
|
297
|
+
result.push(run[i]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Add collapse marker
|
|
301
|
+
const collapsedCount = run.length - contextLines * 2;
|
|
302
|
+
if (collapsedCount > 0) {
|
|
303
|
+
result.push({
|
|
304
|
+
type: 'collapse',
|
|
305
|
+
count: collapsedCount,
|
|
306
|
+
startLine: run[contextLines].oldLineNum || run[contextLines].newLineNum,
|
|
307
|
+
endLine: run[run.length - contextLines - 1].oldLineNum || run[run.length - contextLines - 1].newLineNum
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Keep last contextLines
|
|
312
|
+
for (let i = Math.max(contextLines, run.length - contextLines); i < run.length; i++) {
|
|
313
|
+
result.push(run[i]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Render a single diff entry as a row (no wrapper).
|
|
321
|
+
* `hidden` rows get .diff-row-hidden — shown only when the block is expanded.
|
|
322
|
+
*/
|
|
323
|
+
function renderDiffRow(entry, hidden = false) {
|
|
324
|
+
const h = hidden ? ' diff-row-hidden' : '';
|
|
325
|
+
switch (entry.type) {
|
|
326
|
+
case 'context':
|
|
327
|
+
return `<div class="diff-line diff-context${h}">
|
|
328
|
+
<span class="diff-line-num">${entry.oldLineNum}</span>
|
|
329
|
+
<span class="diff-prefix"> </span>
|
|
330
|
+
<span class="diff-text">${entry.content}</span>
|
|
331
|
+
</div>`;
|
|
332
|
+
case 'removed':
|
|
333
|
+
return `<div class="diff-line diff-removed${h}">
|
|
334
|
+
<span class="diff-line-num">${entry.oldLineNum}</span>
|
|
335
|
+
<span class="diff-prefix">−</span>
|
|
336
|
+
<span class="diff-text">${entry.content}</span>
|
|
337
|
+
</div>`;
|
|
338
|
+
case 'added':
|
|
339
|
+
return `<div class="diff-line diff-added${h}">
|
|
340
|
+
<span class="diff-line-num">${entry.newLineNum}</span>
|
|
341
|
+
<span class="diff-prefix">+</span>
|
|
342
|
+
<span class="diff-text">${entry.content}</span>
|
|
343
|
+
</div>`;
|
|
344
|
+
case 'modified':
|
|
345
|
+
return `<div class="diff-line diff-removed${h}">
|
|
346
|
+
<span class="diff-line-num">${entry.oldLineNum}</span>
|
|
347
|
+
<span class="diff-prefix">−</span>
|
|
348
|
+
<span class="diff-text">${entry.oldContent}</span>
|
|
349
|
+
</div><div class="diff-line diff-added${h}">
|
|
350
|
+
<span class="diff-line-num">${entry.newLineNum}</span>
|
|
351
|
+
<span class="diff-prefix">+</span>
|
|
352
|
+
<span class="diff-text">${entry.newContent}</span>
|
|
353
|
+
</div>`;
|
|
354
|
+
case 'collapse':
|
|
355
|
+
return `<div class="diff-line diff-collapse${h}" data-start="${entry.startLine}" data-end="${entry.endLine}">
|
|
356
|
+
<span class="diff-line-num">⋯</span>
|
|
357
|
+
<span class="diff-prefix"></span>
|
|
358
|
+
<span class="diff-collapse-text">${entry.count} unchanged lines</span>
|
|
359
|
+
</div>`;
|
|
360
|
+
default:
|
|
361
|
+
return '';
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Render smart diff to HTML.
|
|
367
|
+
*
|
|
368
|
+
* Rows are grouped into hunks so each hunk shares one horizontal scrollbar:
|
|
369
|
+
* - All rows between collapse separators (context and changes alike) → one hunk.
|
|
370
|
+
* - Collapse rows render bare (they have their own click affordance and must
|
|
371
|
+
* stay full-width, so they can't live inside a scrolling hunk).
|
|
372
|
+
*
|
|
373
|
+
* options.hideAfter: rows past this count get .diff-row-hidden (shown when the
|
|
374
|
+
* enclosing block gains .expanded) — used to keep tall diffs short by default.
|
|
375
|
+
*/
|
|
376
|
+
function renderSmartDiff(entries, options = {}) {
|
|
377
|
+
const hideAfter = options.hideAfter ?? Infinity;
|
|
378
|
+
let html = '';
|
|
379
|
+
let buf = '';
|
|
380
|
+
let rowCount = 0;
|
|
381
|
+
|
|
382
|
+
const flush = () => {
|
|
383
|
+
if (buf) {
|
|
384
|
+
html += `<div class="diff-hunk"><div class="diff-hunk-inner">${buf}</div></div>`;
|
|
385
|
+
buf = '';
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
for (const entry of entries) {
|
|
390
|
+
const hidden = rowCount >= hideAfter;
|
|
391
|
+
rowCount += entry.type === 'modified' ? 2 : 1;
|
|
392
|
+
if (entry.type === 'collapse') {
|
|
393
|
+
flush();
|
|
394
|
+
html += renderDiffRow(entry, hidden);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
buf += renderDiffRow(entry, hidden);
|
|
398
|
+
}
|
|
399
|
+
flush();
|
|
400
|
+
|
|
401
|
+
return html;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Render a single diff entry as a row of side-by-side grid cells.
|
|
406
|
+
*/
|
|
407
|
+
function renderSbsRow(entry, hunkIdx) {
|
|
408
|
+
const hunkAttr = entry.type !== 'context' ? ` data-hunk="${hunkIdx}"` : '';
|
|
409
|
+
switch (entry.type) {
|
|
410
|
+
case 'context':
|
|
411
|
+
return `<div class="sbs-linenum sbs-left sbs-context">${entry.oldLineNum}</div>`
|
|
412
|
+
+ `<div class="sbs-text sbs-left sbs-context">${entry.content}</div>`
|
|
413
|
+
+ `<div class="sbs-gutter"></div>`
|
|
414
|
+
+ `<div class="sbs-linenum sbs-right sbs-context">${entry.newLineNum}</div>`
|
|
415
|
+
+ `<div class="sbs-text sbs-right sbs-context">${entry.content}</div>`;
|
|
416
|
+
case 'removed':
|
|
417
|
+
return `<div class="sbs-linenum sbs-left sbs-removed"${hunkAttr}>${entry.oldLineNum}</div>`
|
|
418
|
+
+ `<div class="sbs-text sbs-left sbs-removed">${entry.content}</div>`
|
|
419
|
+
+ `<div class="sbs-gutter"></div>`
|
|
420
|
+
+ `<div class="sbs-linenum sbs-right sbs-empty"></div>`
|
|
421
|
+
+ `<div class="sbs-text sbs-right sbs-empty"></div>`;
|
|
422
|
+
case 'added':
|
|
423
|
+
return `<div class="sbs-linenum sbs-left sbs-empty"></div>`
|
|
424
|
+
+ `<div class="sbs-text sbs-left sbs-empty"></div>`
|
|
425
|
+
+ `<div class="sbs-gutter"></div>`
|
|
426
|
+
+ `<div class="sbs-linenum sbs-right sbs-added"${hunkAttr}>${entry.newLineNum}</div>`
|
|
427
|
+
+ `<div class="sbs-text sbs-right sbs-added">${entry.content}</div>`;
|
|
428
|
+
case 'modified':
|
|
429
|
+
return `<div class="sbs-linenum sbs-left sbs-modified-old"${hunkAttr}>${entry.oldLineNum}</div>`
|
|
430
|
+
+ `<div class="sbs-text sbs-left sbs-modified-old">${entry.oldContent}</div>`
|
|
431
|
+
+ `<div class="sbs-gutter"></div>`
|
|
432
|
+
+ `<div class="sbs-linenum sbs-right sbs-modified-new">${entry.newLineNum}</div>`
|
|
433
|
+
+ `<div class="sbs-text sbs-right sbs-modified-new">${entry.newContent}</div>`;
|
|
434
|
+
default:
|
|
435
|
+
return '';
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Render smart diff as a stack of side-by-side hunks.
|
|
441
|
+
*
|
|
442
|
+
* All rows between collapse markers (context and changes alike) share one
|
|
443
|
+
* scroll-container grid, so the whole section scrolls with one horizontal
|
|
444
|
+
* scrollbar. data-hunk indices still advance per change group (not per
|
|
445
|
+
* scroll container) so keyboard hunk navigation is unaffected.
|
|
446
|
+
*
|
|
447
|
+
* @param {object[]} entries - diff entries from generateSmartDiff
|
|
448
|
+
* @param {object} options
|
|
449
|
+
* @param {function} options.collapseLabel - (count) => string for the "N unchanged lines" marker
|
|
450
|
+
*/
|
|
451
|
+
function renderSideBySideDiff(entries, options = {}) {
|
|
452
|
+
const collapseLabel = options.collapseLabel || ((count) => `${count} unchanged lines`);
|
|
453
|
+
let html = '<div class="sbs-diff-stack">';
|
|
454
|
+
let buf = '';
|
|
455
|
+
let prevType = null;
|
|
456
|
+
let hunkIdx = 0;
|
|
457
|
+
|
|
458
|
+
const flush = () => {
|
|
459
|
+
if (buf) {
|
|
460
|
+
html += `<div class="sbs-hunk"><div class="sbs-diff-grid">${buf}</div></div>`;
|
|
461
|
+
buf = '';
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
for (const entry of entries) {
|
|
466
|
+
if (entry.type === 'collapse') {
|
|
467
|
+
flush();
|
|
468
|
+
html += `<div class="sbs-collapse" data-start="${entry.startLine}" data-end="${entry.endLine}">`
|
|
469
|
+
+ `<span>⋯</span> ${collapseLabel(entry.count)}</div>`;
|
|
470
|
+
hunkIdx++;
|
|
471
|
+
prevType = null;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const thisType = entry.type === 'context' ? 'context' : 'change';
|
|
476
|
+
if (prevType === 'change' && thisType !== 'change') hunkIdx++;
|
|
477
|
+
prevType = thisType;
|
|
478
|
+
buf += renderSbsRow(entry, hunkIdx);
|
|
479
|
+
}
|
|
480
|
+
flush();
|
|
481
|
+
|
|
482
|
+
html += '</div>';
|
|
483
|
+
return html;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export { generateSmartDiff, renderSmartDiff, renderSideBySideDiff, computeWordDiff, areSimilarLines };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drafts Pill
|
|
3
|
+
*
|
|
4
|
+
* Small clickable "N drafts" pill in the top-right corner of the empty chat
|
|
5
|
+
* input — the visible entry point to Prompt Explorer → Drafts. Drafts are
|
|
6
|
+
* auto-banked silently while typing (see InputHandler's draft auto-sync),
|
|
7
|
+
* so without this pill they'd be invisible until the user opened the
|
|
8
|
+
* Prompt Explorer by hand.
|
|
9
|
+
*
|
|
10
|
+
* Visibility mirrors the shortcut-hints overlay lifecycle: shown only when
|
|
11
|
+
* the input is empty, not in shell/plan mode, and at least one draft exists.
|
|
12
|
+
* Count refreshes on `drafts-changed` window events (dispatched by every
|
|
13
|
+
* draft mutation path) and lazily when the input empties.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import S from './strings.js';
|
|
17
|
+
import { CONFIG } from './config.js';
|
|
18
|
+
import { openPromptExplorerDrafts } from './widgets/prompt-explorer-widget.js';
|
|
19
|
+
|
|
20
|
+
// Don't refetch the count more often than this on input-emptied transitions
|
|
21
|
+
// (drafts-changed events always refetch — they signal a real mutation)
|
|
22
|
+
const REFRESH_THROTTLE_MS = 60000;
|
|
23
|
+
|
|
24
|
+
class DraftsPillController {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.el = null;
|
|
27
|
+
this.count = 0;
|
|
28
|
+
this.inputEmpty = true;
|
|
29
|
+
this.suppressed = false; // forced off by mode (shell/plan)
|
|
30
|
+
this._lastFetch = 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
init() {
|
|
34
|
+
this.el = document.getElementById('drafts-pill');
|
|
35
|
+
if (!this.el) return;
|
|
36
|
+
this.el.setAttribute('data-tooltip', S.ui.input.drafts_pill_tooltip);
|
|
37
|
+
this.el.addEventListener('click', () => openPromptExplorerDrafts());
|
|
38
|
+
window.addEventListener('drafts-changed', () => this.refresh());
|
|
39
|
+
this.refresh();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Refetch the draft count from the server. */
|
|
43
|
+
async refresh() {
|
|
44
|
+
this._lastFetch = Date.now();
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch(`${CONFIG.API_BASE}/api/drafts`);
|
|
47
|
+
if (!res.ok) return;
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
this.count = data.count ?? (data.drafts || []).length;
|
|
50
|
+
} catch {
|
|
51
|
+
/* offline/auth hiccup — keep last known count */
|
|
52
|
+
}
|
|
53
|
+
this._sync();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Called by input-handler whenever input content changes. */
|
|
57
|
+
updateVisibility(textareaValue) {
|
|
58
|
+
const isEmpty = !textareaValue || textareaValue.length === 0;
|
|
59
|
+
const becameEmpty = isEmpty && !this.inputEmpty;
|
|
60
|
+
this.inputEmpty = isEmpty;
|
|
61
|
+
this._sync();
|
|
62
|
+
// Input just emptied → opportunistic freshness check (throttled)
|
|
63
|
+
if (becameEmpty && Date.now() - this._lastFetch > REFRESH_THROTTLE_MS) {
|
|
64
|
+
this.refresh();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Called by input-handler whenever input mode changes. */
|
|
69
|
+
setMode(mode) {
|
|
70
|
+
this.suppressed = mode === 'shell' || mode === 'plan';
|
|
71
|
+
this._sync();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_sync() {
|
|
75
|
+
if (!this.el) return;
|
|
76
|
+
const show = this.count > 0 && this.inputEmpty && !this.suppressed;
|
|
77
|
+
if (show) {
|
|
78
|
+
this.el.textContent = this.count === 1
|
|
79
|
+
? S.ui.input.drafts_pill_one
|
|
80
|
+
: S.ui.input.drafts_pill_many.replace('{count}', this.count);
|
|
81
|
+
}
|
|
82
|
+
this.el.classList.toggle('visible', show);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const DraftsPill = new DraftsPillController();
|
|
87
|
+
|
|
88
|
+
// Initialize on DOM ready (same pattern as ShortcutHints)
|
|
89
|
+
if (typeof document !== 'undefined') {
|
|
90
|
+
if (document.readyState === 'loading') {
|
|
91
|
+
document.addEventListener('DOMContentLoaded', () => DraftsPill.init());
|
|
92
|
+
} else {
|
|
93
|
+
DraftsPill.init();
|
|
94
|
+
}
|
|
95
|
+
}
|