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.
Files changed (436) hide show
  1. painapple_code/__init__.py +30 -0
  2. painapple_code/__main__.py +18 -0
  3. painapple_code/_version.py +24 -0
  4. painapple_code/auth_middleware.py +930 -0
  5. painapple_code/cli/__init__.py +204 -0
  6. painapple_code/cli/deploy/__init__.py +15 -0
  7. painapple_code/cli/deploy/claude_seed.py +41 -0
  8. painapple_code/cli/deploy/config.py +260 -0
  9. painapple_code/cli/deploy/container.py +715 -0
  10. painapple_code/cli/deploy/launch.py +163 -0
  11. painapple_code/cli/deploy/runtime.py +223 -0
  12. painapple_code/cli/lifecycle_cmd.py +443 -0
  13. painapple_code/cli/list_cmd.py +572 -0
  14. painapple_code/cli/manage_cmd.py +670 -0
  15. painapple_code/cli/netinfo.py +125 -0
  16. painapple_code/cli/profiles.py +390 -0
  17. painapple_code/cli/serve_args.py +163 -0
  18. painapple_code/cli/serve_config.py +256 -0
  19. painapple_code/cli/setup_cmd.py +664 -0
  20. painapple_code/cli/ui.py +438 -0
  21. painapple_code/cost_analytics.py +879 -0
  22. painapple_code/data/models.defaults.yaml +18 -0
  23. painapple_code/data/models.yaml +18 -0
  24. painapple_code/data/presets.defaults.json +32 -0
  25. painapple_code/data/strings.yaml +2269 -0
  26. painapple_code/helpers.py +406 -0
  27. painapple_code/paths.py +1514 -0
  28. painapple_code/prompt_explorer.py +984 -0
  29. painapple_code/providers/__init__.py +224 -0
  30. painapple_code/providers/base.py +710 -0
  31. painapple_code/providers/claude/__init__.py +74 -0
  32. painapple_code/providers/claude/capabilities.py +218 -0
  33. painapple_code/providers/claude/errors.py +79 -0
  34. painapple_code/providers/claude/launch.py +50 -0
  35. painapple_code/providers/claude/summary.py +89 -0
  36. painapple_code/providers/claude/translate.py +74 -0
  37. painapple_code/providers/claude_sdk/__init__.py +139 -0
  38. painapple_code/providers/claude_sdk/driver.py +412 -0
  39. painapple_code/providers/codex/__init__.py +87 -0
  40. painapple_code/providers/codex/capabilities.py +321 -0
  41. painapple_code/providers/codex/errors.py +70 -0
  42. painapple_code/providers/codex/launch.py +142 -0
  43. painapple_code/providers/codex/summary.py +196 -0
  44. painapple_code/providers/codex/translate.py +204 -0
  45. painapple_code/providers/codex_app_server/__init__.py +100 -0
  46. painapple_code/providers/codex_app_server/capabilities.py +41 -0
  47. painapple_code/providers/codex_app_server/launch.py +202 -0
  48. painapple_code/providers/codex_app_server/summary.py +291 -0
  49. painapple_code/providers/codex_app_server/translate.py +263 -0
  50. painapple_code/providers/codex_app_server/transport.py +238 -0
  51. painapple_code/routes/__init__.py +0 -0
  52. painapple_code/routes/api_agents.py +441 -0
  53. painapple_code/routes/api_app.py +368 -0
  54. painapple_code/routes/api_app_commit_sections.py +127 -0
  55. painapple_code/routes/api_app_config.py +997 -0
  56. painapple_code/routes/api_app_session_prefs.py +217 -0
  57. painapple_code/routes/api_browser.py +690 -0
  58. painapple_code/routes/api_commands.py +374 -0
  59. painapple_code/routes/api_costs.py +128 -0
  60. painapple_code/routes/api_drafts.py +139 -0
  61. painapple_code/routes/api_exec.py +149 -0
  62. painapple_code/routes/api_files.py +542 -0
  63. painapple_code/routes/api_git.py +657 -0
  64. painapple_code/routes/api_logs.py +649 -0
  65. painapple_code/routes/api_plugins.py +275 -0
  66. painapple_code/routes/api_project_config.py +418 -0
  67. painapple_code/routes/api_prompts.py +231 -0
  68. painapple_code/routes/api_providers.py +53 -0
  69. painapple_code/routes/api_search.py +411 -0
  70. painapple_code/routes/api_session_stash.py +234 -0
  71. painapple_code/routes/api_session_welcome.py +151 -0
  72. painapple_code/routes/api_sessions.py +742 -0
  73. painapple_code/routes/api_shadow.py +323 -0
  74. painapple_code/routes/api_shadow_db.py +512 -0
  75. painapple_code/routes/api_shadow_files.py +332 -0
  76. painapple_code/routes/api_shadow_search.py +476 -0
  77. painapple_code/routes/api_skills.py +610 -0
  78. painapple_code/routes/api_tasks.py +147 -0
  79. painapple_code/routes/api_terminal.py +540 -0
  80. painapple_code/routes/api_upload.py +293 -0
  81. painapple_code/routes/api_viewer.py +229 -0
  82. painapple_code/routes/dependencies.py +109 -0
  83. painapple_code/routes/ws_chat.py +837 -0
  84. painapple_code/server.py +2208 -0
  85. painapple_code/server_logging.py +263 -0
  86. painapple_code/services/__init__.py +0 -0
  87. painapple_code/services/agent_session.py +2738 -0
  88. painapple_code/session_store.py +253 -0
  89. painapple_code/session_store_core.py +879 -0
  90. painapple_code/shadow_db.py +830 -0
  91. painapple_code/shadow_db_plans.py +369 -0
  92. painapple_code/shadow_db_queries.py +595 -0
  93. painapple_code/shadow_db_schema.py +676 -0
  94. painapple_code/shadow_git.py +1296 -0
  95. painapple_code/shadow_git_frontmatter.py +314 -0
  96. painapple_code/shadow_git_sections.py +520 -0
  97. painapple_code/shadow_git_summary.py +462 -0
  98. painapple_code/shadow_parser.py +560 -0
  99. painapple_code/static/css/00-variables.css +276 -0
  100. painapple_code/static/css/01-base.css +245 -0
  101. painapple_code/static/css/02-tooltips.css +36 -0
  102. painapple_code/static/css/09-left-rail.css +448 -0
  103. painapple_code/static/css/10-header.css +777 -0
  104. painapple_code/static/css/11-connection-bar.css +120 -0
  105. painapple_code/static/css/20-messages.css +750 -0
  106. painapple_code/static/css/21-tool-blocks.css +2340 -0
  107. painapple_code/static/css/22-edit-diff.css +619 -0
  108. painapple_code/static/css/23-thinking.css +1584 -0
  109. painapple_code/static/css/24-background-tasks.css +359 -0
  110. painapple_code/static/css/25-context-block.css +252 -0
  111. painapple_code/static/css/26-session-setup.css +144 -0
  112. painapple_code/static/css/30-markdown.css +854 -0
  113. painapple_code/static/css/40-input.css +2418 -0
  114. painapple_code/static/css/41-autocomplete.css +127 -0
  115. painapple_code/static/css/42-activity-strip.css +97 -0
  116. painapple_code/static/css/43-file-autocomplete.css +121 -0
  117. painapple_code/static/css/44-snippets-autocomplete.css +224 -0
  118. painapple_code/static/css/45-selection-bar.css +433 -0
  119. painapple_code/static/css/46-keyboard-bar.css +119 -0
  120. painapple_code/static/css/47-keyboard-longpress.css +116 -0
  121. painapple_code/static/css/47-token-profile.css +106 -0
  122. painapple_code/static/css/50-welcome.css +3095 -0
  123. painapple_code/static/css/51-modals.css +301 -0
  124. painapple_code/static/css/52-utilities.css +77 -0
  125. painapple_code/static/css/53-image-upload.css +456 -0
  126. painapple_code/static/css/54-todo-list.css +102 -0
  127. painapple_code/static/css/55-file-upload.css +113 -0
  128. painapple_code/static/css/56-chat-navigator.css +218 -0
  129. painapple_code/static/css/56-permission-card.css +332 -0
  130. painapple_code/static/css/56-plan-approval.css +204 -0
  131. painapple_code/static/css/56-question-form.css +602 -0
  132. painapple_code/static/css/57-swipe-indicator.css +95 -0
  133. painapple_code/static/css/58-session-families.css +627 -0
  134. painapple_code/static/css/59-about.css +196 -0
  135. painapple_code/static/css/61-log-explorer.css +723 -0
  136. painapple_code/static/css/62-json-tree.css +489 -0
  137. painapple_code/static/css/62-skills-widget.css +650 -0
  138. painapple_code/static/css/63-commands-widget.css +30 -0
  139. painapple_code/static/css/63-plugins-widget.css +174 -0
  140. painapple_code/static/css/63-terminal.css +516 -0
  141. painapple_code/static/css/64-search-files-widget.css +448 -0
  142. painapple_code/static/css/65-snippets-widget.css +426 -0
  143. painapple_code/static/css/66-active-sessions.css +979 -0
  144. painapple_code/static/css/66-agents-widget.css +265 -0
  145. painapple_code/static/css/66-file-explorer-widget.css +855 -0
  146. painapple_code/static/css/66-git-panel.css +791 -0
  147. painapple_code/static/css/66-history-explorer.css +1496 -0
  148. painapple_code/static/css/67-diff-viewer.css +602 -0
  149. painapple_code/static/css/67-editor-view.css +748 -0
  150. painapple_code/static/css/67-file-preview-widget.css +1873 -0
  151. painapple_code/static/css/68-browser-widget.css +337 -0
  152. painapple_code/static/css/68-compare-wizard.css +355 -0
  153. painapple_code/static/css/68-csv-preview.css +106 -0
  154. painapple_code/static/css/68-diff-preview.css +103 -0
  155. painapple_code/static/css/68-helpers-install-widget.css +491 -0
  156. painapple_code/static/css/68-image-annotate.css +346 -0
  157. painapple_code/static/css/68-terminal-view.css +98 -0
  158. painapple_code/static/css/68-uploads-widget.css +229 -0
  159. painapple_code/static/css/69-cost-analytics.css +537 -0
  160. painapple_code/static/css/70-config-panel.css +3736 -0
  161. painapple_code/static/css/70-excalidraw.css +136 -0
  162. painapple_code/static/css/70-prompt-explorer.css +890 -0
  163. painapple_code/static/css/71-chart.css +111 -0
  164. painapple_code/static/css/72-discussion-sidebar.css +678 -0
  165. painapple_code/static/css/74-debug-logs.css +656 -0
  166. painapple_code/static/css/80-open-dialog.css +146 -0
  167. painapple_code/static/css/80-quick-switcher.css +383 -0
  168. painapple_code/static/css/80-widget-system.css +1763 -0
  169. painapple_code/static/css/81-quick-actions.css +1020 -0
  170. painapple_code/static/css/82-context-popover.css +1233 -0
  171. painapple_code/static/css/83-context-menu.css +339 -0
  172. painapple_code/static/css/85-lazy-loading.css +102 -0
  173. painapple_code/static/css/86-zen-mode.css +708 -0
  174. painapple_code/static/css/87-grid-switcher.css +346 -0
  175. painapple_code/static/css/login.css +198 -0
  176. painapple_code/static/dist/app.bundle.js +7177 -0
  177. painapple_code/static/feature-triage.html +446 -0
  178. painapple_code/static/icons/apple-touch-icon.png +0 -0
  179. painapple_code/static/icons/favicon-32.png +0 -0
  180. painapple_code/static/icons/favicon.ico +0 -0
  181. painapple_code/static/icons/icon-128.png +0 -0
  182. painapple_code/static/icons/icon-144.png +0 -0
  183. painapple_code/static/icons/icon-152.png +0 -0
  184. painapple_code/static/icons/icon-180.png +0 -0
  185. painapple_code/static/icons/icon-192.png +0 -0
  186. painapple_code/static/icons/icon-384.png +0 -0
  187. painapple_code/static/icons/icon-512.png +0 -0
  188. painapple_code/static/icons/icon-72.png +0 -0
  189. painapple_code/static/icons/icon-96.png +0 -0
  190. painapple_code/static/js/action-delegate.js +187 -0
  191. painapple_code/static/js/activity-strip.js +193 -0
  192. painapple_code/static/js/app/context-menus.js +619 -0
  193. painapple_code/static/js/app/file-preview.js +163 -0
  194. painapple_code/static/js/app/init-ui.js +574 -0
  195. painapple_code/static/js/app/input-events.js +561 -0
  196. painapple_code/static/js/app/input-resize.js +313 -0
  197. painapple_code/static/js/app/message-actions.js +419 -0
  198. painapple_code/static/js/app/panels.js +210 -0
  199. painapple_code/static/js/app/rendering-delegators.js +246 -0
  200. painapple_code/static/js/app/server-loading.js +365 -0
  201. painapple_code/static/js/app/session-ops.js +287 -0
  202. painapple_code/static/js/app/session-switch.js +443 -0
  203. painapple_code/static/js/app/settings-helpers.js +280 -0
  204. painapple_code/static/js/app/startup-welcome.js +290 -0
  205. painapple_code/static/js/app-context.js +157 -0
  206. painapple_code/static/js/app.js +2134 -0
  207. painapple_code/static/js/auth-fetch.js +46 -0
  208. painapple_code/static/js/background-tasks.js +274 -0
  209. painapple_code/static/js/boot.js +73 -0
  210. painapple_code/static/js/caret-position.js +171 -0
  211. painapple_code/static/js/chat-navigator.js +710 -0
  212. painapple_code/static/js/chat-search.js +323 -0
  213. painapple_code/static/js/command-executor.js +176 -0
  214. painapple_code/static/js/command-store.js +497 -0
  215. painapple_code/static/js/components.js +1414 -0
  216. painapple_code/static/js/config.js +218 -0
  217. painapple_code/static/js/context-menu.js +740 -0
  218. painapple_code/static/js/controllers/chat-controller.js +4252 -0
  219. painapple_code/static/js/controllers/dialog-controller.js +217 -0
  220. painapple_code/static/js/controllers/tab-controller.js +1828 -0
  221. painapple_code/static/js/controllers/thinking-controller.js +915 -0
  222. painapple_code/static/js/diff-utils.js +486 -0
  223. painapple_code/static/js/drafts-pill.js +95 -0
  224. painapple_code/static/js/editor-view.js +815 -0
  225. painapple_code/static/js/effort-settings.js +430 -0
  226. painapple_code/static/js/feature-triage.js +811 -0
  227. painapple_code/static/js/file-autocomplete.js +516 -0
  228. painapple_code/static/js/file-tabs.js +123 -0
  229. painapple_code/static/js/gestures.js +448 -0
  230. painapple_code/static/js/grid-switcher.js +334 -0
  231. painapple_code/static/js/input-handler.js +1472 -0
  232. painapple_code/static/js/keyboard-bar.js +495 -0
  233. painapple_code/static/js/layout-switcher.js +109 -0
  234. painapple_code/static/js/linkify-utils.js +200 -0
  235. painapple_code/static/js/login.js +141 -0
  236. painapple_code/static/js/open-dialog.js +879 -0
  237. painapple_code/static/js/orphan-terminals.js +373 -0
  238. painapple_code/static/js/path-utils.js +179 -0
  239. painapple_code/static/js/perf-marks.js +102 -0
  240. painapple_code/static/js/permission-settings.js +475 -0
  241. painapple_code/static/js/pointer-input.js +59 -0
  242. painapple_code/static/js/preview/json-tree-menu.js +226 -0
  243. painapple_code/static/js/preview/json-tree-toolbar.js +291 -0
  244. painapple_code/static/js/preview/json-tree.js +457 -0
  245. painapple_code/static/js/preview/preview-breadcrumb.js +387 -0
  246. painapple_code/static/js/preview/preview-edit.js +367 -0
  247. painapple_code/static/js/preview/preview-events.js +361 -0
  248. painapple_code/static/js/preview/preview-history.js +567 -0
  249. painapple_code/static/js/preview/preview-inline-edit.js +869 -0
  250. painapple_code/static/js/preview/preview-poll.js +108 -0
  251. painapple_code/static/js/preview/preview-render.js +572 -0
  252. painapple_code/static/js/preview/preview-search.js +421 -0
  253. painapple_code/static/js/preview/preview-state.js +256 -0
  254. painapple_code/static/js/preview/preview-utils.js +251 -0
  255. painapple_code/static/js/preview-plugins/chart-plugin.js +22 -0
  256. painapple_code/static/js/preview-plugins/csv-plugin.js +188 -0
  257. painapple_code/static/js/preview-plugins/diff-plugin.js +258 -0
  258. painapple_code/static/js/preview-plugins/excalidraw-plugin.js +25 -0
  259. painapple_code/static/js/preview-plugins/html-plugin.js +104 -0
  260. painapple_code/static/js/preview-plugins/image-plugin.js +20 -0
  261. painapple_code/static/js/preview-plugins/index.js +34 -0
  262. painapple_code/static/js/preview-plugins/json-plugin.js +99 -0
  263. painapple_code/static/js/preview-plugins/jsonl-plugin.js +152 -0
  264. painapple_code/static/js/preview-plugins/markdown-plugin.js +266 -0
  265. painapple_code/static/js/preview-plugins/panzoom-plugin.js +63 -0
  266. painapple_code/static/js/preview-plugins/plugin-helpers.js +282 -0
  267. painapple_code/static/js/project-color-picker.js +110 -0
  268. painapple_code/static/js/project-colors.js +163 -0
  269. painapple_code/static/js/prompt-favorites.js +187 -0
  270. painapple_code/static/js/quick-actions-menu.js +1736 -0
  271. painapple_code/static/js/quick-actions-registry.js +1239 -0
  272. painapple_code/static/js/quick-switcher/controller.js +294 -0
  273. painapple_code/static/js/quick-switcher/fuzzy-scorer.js +123 -0
  274. painapple_code/static/js/quick-switcher/index.js +30 -0
  275. painapple_code/static/js/quick-switcher/providers/base-provider.js +51 -0
  276. painapple_code/static/js/quick-switcher/providers/command-provider.js +56 -0
  277. painapple_code/static/js/quick-switcher/providers/file-provider.js +291 -0
  278. painapple_code/static/js/quick-switcher/providers/panel-provider.js +132 -0
  279. painapple_code/static/js/quick-switcher/providers/project-provider.js +361 -0
  280. painapple_code/static/js/quick-switcher/providers/read-files-provider.js +104 -0
  281. painapple_code/static/js/quick-switcher/providers/skills-provider.js +129 -0
  282. painapple_code/static/js/quick-switcher/registry.js +69 -0
  283. painapple_code/static/js/quick-switcher/ui/item.js +54 -0
  284. painapple_code/static/js/quick-switcher/ui/picker.js +464 -0
  285. painapple_code/static/js/recent-opens.js +40 -0
  286. painapple_code/static/js/scroll-manager.js +530 -0
  287. painapple_code/static/js/scroll-state-machine.js +359 -0
  288. painapple_code/static/js/selection/action-bar.js +793 -0
  289. painapple_code/static/js/selection/index.js +15 -0
  290. painapple_code/static/js/selection/selection-handler.js +877 -0
  291. painapple_code/static/js/selection/state.js +181 -0
  292. painapple_code/static/js/session/agent-progress.js +202 -0
  293. painapple_code/static/js/session/handle-agent-message.js +481 -0
  294. painapple_code/static/js/session/handle-message.js +550 -0
  295. painapple_code/static/js/session/interactive.js +587 -0
  296. painapple_code/static/js/session/message-store.js +271 -0
  297. painapple_code/static/js/session/messages.js +253 -0
  298. painapple_code/static/js/session/persistence.js +179 -0
  299. painapple_code/static/js/session/restore.js +136 -0
  300. painapple_code/static/js/session/sync.js +333 -0
  301. painapple_code/static/js/session-container-pool.js +371 -0
  302. painapple_code/static/js/session-setup-panel.js +274 -0
  303. painapple_code/static/js/session.js +1047 -0
  304. painapple_code/static/js/shortcut-hints.js +272 -0
  305. painapple_code/static/js/shortcuts.js +1146 -0
  306. painapple_code/static/js/skills-autocomplete.js +262 -0
  307. painapple_code/static/js/snippets-autocomplete.js +738 -0
  308. painapple_code/static/js/stash-refs-view.js +83 -0
  309. painapple_code/static/js/stash-ui.js +768 -0
  310. painapple_code/static/js/stash.js +785 -0
  311. painapple_code/static/js/status-bar.js +1184 -0
  312. painapple_code/static/js/token-profile.js +274 -0
  313. painapple_code/static/js/tool-renderer-blocks.js +1626 -0
  314. painapple_code/static/js/tool-renderer-thinking.js +813 -0
  315. painapple_code/static/js/tool-renderer.js +1500 -0
  316. painapple_code/static/js/tooltips.js +197 -0
  317. painapple_code/static/js/upload-manager.js +1019 -0
  318. painapple_code/static/js/utils.js +521 -0
  319. painapple_code/static/js/welcome/api.js +363 -0
  320. painapple_code/static/js/welcome/cards.js +626 -0
  321. painapple_code/static/js/welcome/context-menu.js +585 -0
  322. painapple_code/static/js/welcome/families.js +801 -0
  323. painapple_code/static/js/welcome/preview.js +234 -0
  324. painapple_code/static/js/welcome/state.js +129 -0
  325. painapple_code/static/js/welcome.js +1874 -0
  326. painapple_code/static/js/widget-system/base-widget.js +717 -0
  327. painapple_code/static/js/widget-system/device-manager.js +178 -0
  328. painapple_code/static/js/widget-system/event-bus.js +152 -0
  329. painapple_code/static/js/widget-system/icons.js +120 -0
  330. painapple_code/static/js/widget-system/index.js +49 -0
  331. painapple_code/static/js/widget-system/init.js +54 -0
  332. painapple_code/static/js/widget-system/types/bottom-sheet.js +307 -0
  333. painapple_code/static/js/widget-system/types/floating.js +915 -0
  334. painapple_code/static/js/widget-system/types/index.js +40 -0
  335. painapple_code/static/js/widget-system/types/modal.js +147 -0
  336. painapple_code/static/js/widget-system/types/sidebar.js +158 -0
  337. painapple_code/static/js/widget-system/types/tab.js +146 -0
  338. painapple_code/static/js/widget-system/types/top-sheet.js +203 -0
  339. painapple_code/static/js/widget-system/widget-manager.js +1187 -0
  340. painapple_code/static/js/widgets/about-widget.js +260 -0
  341. painapple_code/static/js/widgets/active-sessions-widget.js +760 -0
  342. painapple_code/static/js/widgets/agents-widget.js +897 -0
  343. painapple_code/static/js/widgets/browser-widget.js +579 -0
  344. painapple_code/static/js/widgets/commands-widget.js +694 -0
  345. painapple_code/static/js/widgets/config/commit-sections.js +449 -0
  346. painapple_code/static/js/widgets/config/dir-autocomplete.js +224 -0
  347. painapple_code/static/js/widgets/config/gestures.js +161 -0
  348. painapple_code/static/js/widgets/config/models-tab.js +988 -0
  349. painapple_code/static/js/widgets/config/quick-actions-tab.js +882 -0
  350. painapple_code/static/js/widgets/config/shortcut-editor.js +587 -0
  351. painapple_code/static/js/widgets/config/state.js +517 -0
  352. painapple_code/static/js/widgets/config/system-controls.js +90 -0
  353. painapple_code/static/js/widgets/config-widget.js +1535 -0
  354. painapple_code/static/js/widgets/cost-analytics-widget.js +621 -0
  355. painapple_code/static/js/widgets/debug-widget.js +693 -0
  356. painapple_code/static/js/widgets/diff-viewer-widget.js +1420 -0
  357. painapple_code/static/js/widgets/discussion-widget.js +1107 -0
  358. painapple_code/static/js/widgets/file-explorer-widget.js +2166 -0
  359. painapple_code/static/js/widgets/file-preview-widget.js +861 -0
  360. painapple_code/static/js/widgets/git-widget.js +794 -0
  361. painapple_code/static/js/widgets/helpers-install-widget.js +720 -0
  362. painapple_code/static/js/widgets/history-explorer-widget.js +1804 -0
  363. painapple_code/static/js/widgets/image-annotate-widget.js +951 -0
  364. painapple_code/static/js/widgets/image-preview-widget.js +692 -0
  365. painapple_code/static/js/widgets/index.js +176 -0
  366. painapple_code/static/js/widgets/log-explorer-widget.js +1057 -0
  367. painapple_code/static/js/widgets/plugins-widget.js +305 -0
  368. painapple_code/static/js/widgets/prompt-explorer-widget.js +1119 -0
  369. painapple_code/static/js/widgets/search-files-widget.js +529 -0
  370. painapple_code/static/js/widgets/skills-widget.js +843 -0
  371. painapple_code/static/js/widgets/snippets-widget.js +479 -0
  372. painapple_code/static/js/widgets/sub-agents-widget.js +333 -0
  373. painapple_code/static/js/widgets/tasks-widget.js +379 -0
  374. painapple_code/static/js/widgets/terminal/connection.js +283 -0
  375. painapple_code/static/js/widgets/terminal/gestures.js +357 -0
  376. painapple_code/static/js/widgets/terminal/init.js +642 -0
  377. painapple_code/static/js/widgets/terminal/link-providers.js +532 -0
  378. painapple_code/static/js/widgets/terminal/osc52.js +213 -0
  379. painapple_code/static/js/widgets/terminal/render.js +159 -0
  380. painapple_code/static/js/widgets/terminal/size.js +54 -0
  381. painapple_code/static/js/widgets/terminal/state.js +211 -0
  382. painapple_code/static/js/widgets/terminal/wrap-utils.js +101 -0
  383. painapple_code/static/js/widgets/terminal-widget.js +793 -0
  384. painapple_code/static/js/widgets/uploads-widget.js +461 -0
  385. painapple_code/static/js/widgets/zen-widget.js +1001 -0
  386. painapple_code/static/login.html +51 -0
  387. painapple_code/static/manifest.json +73 -0
  388. painapple_code/static/sw.js +293 -0
  389. painapple_code/static/vendor/LICENSES.txt +769 -0
  390. painapple_code/static/vendor/README.md +103 -0
  391. painapple_code/static/vendor/codemirror.js +35 -0
  392. painapple_code/static/vendor/codemirror.js.LEGAL.txt +304 -0
  393. painapple_code/static/vendor/github-markdown-dark.min.css +1124 -0
  394. painapple_code/static/vendor/highlight-github-dark.min.css +10 -0
  395. painapple_code/static/vendor/highlight-lang-dockerfile.min.js +8 -0
  396. painapple_code/static/vendor/highlight-lang-nginx.min.js +21 -0
  397. painapple_code/static/vendor/highlight-lang-properties.min.js +10 -0
  398. painapple_code/static/vendor/highlight-lang-scala.min.js +28 -0
  399. painapple_code/static/vendor/highlight.min.js +1213 -0
  400. painapple_code/static/vendor/marked.min.js +6 -0
  401. painapple_code/static/vendor/purify.min.js +3 -0
  402. painapple_code/static/vendor/xterm-addon-fit.js +2 -0
  403. painapple_code/static/vendor/xterm.css +209 -0
  404. painapple_code/static/vendor/xterm.js +2 -0
  405. painapple_code/static/web-client.html +635 -0
  406. painapple_code/subprocess_registry.py +535 -0
  407. painapple_code/tls_cert.py +79 -0
  408. painapple_code/tools/agents/shadow-git-helper.md +400 -0
  409. painapple_code/tools/excalidraw-to-svg.js +143 -0
  410. painapple_code/tools/install-helpers.sh +148 -0
  411. painapple_code/tools/shadow-git +320 -0
  412. painapple_code/tools/shadow-query +120 -0
  413. painapple_code/tools/vegalite-to-svg.js +79 -0
  414. painapple_code/turn_query.py +766 -0
  415. painapple_code/turn_tracker.py +221 -0
  416. painapple_code/utils/__init__.py +0 -0
  417. painapple_code/utils/agent_cli.py +306 -0
  418. painapple_code/utils/chart.py +103 -0
  419. painapple_code/utils/excalidraw.py +107 -0
  420. painapple_code/utils/file_lock.py +162 -0
  421. painapple_code/utils/file_paths.py +648 -0
  422. painapple_code/utils/generate_icons.py +251 -0
  423. painapple_code/utils/proc.py +205 -0
  424. painapple_code/utils/pty_backend.py +59 -0
  425. painapple_code/utils/pty_posix.py +160 -0
  426. painapple_code/utils/pty_windows.py +245 -0
  427. painapple_code/utils/token_profiles.py +91 -0
  428. painapple_code/viewer_templates.py +657 -0
  429. painapple_code/welcome_search.py +879 -0
  430. painapple_code-1.0.0.dist-info/METADATA +292 -0
  431. painapple_code-1.0.0.dist-info/RECORD +436 -0
  432. painapple_code-1.0.0.dist-info/WHEEL +5 -0
  433. painapple_code-1.0.0.dist-info/entry_points.txt +2 -0
  434. painapple_code-1.0.0.dist-info/licenses/LICENSE +661 -0
  435. painapple_code-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +261 -0
  436. 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
+ }