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,530 @@
1
+ /**
2
+ * Scroll Manager Module
3
+ * Manages scroll position, "new messages" indicator, and lazy loading trigger
4
+ * Implements Slack-style scroll behavior
5
+ *
6
+ * Uses ScrollStateMachine for race-free state management:
7
+ * - No more boolean flag races during tab switches
8
+ * - Coalesced scroll operations via requestAnimationFrame
9
+ * - Explicit state transitions prevent invalid operations
10
+ */
11
+
12
+ import { ScrollStateMachine, ScrollState } from './scroll-state-machine.js';
13
+
14
+ /**
15
+ * ScrollManager - Handle scroll position and new messages indicator
16
+ */
17
+ export class ScrollManager {
18
+ /**
19
+ * @param {HTMLElement} container - The scrollable messages container
20
+ * @param {Object} options - Configuration options
21
+ * @param {number} options.scrollThreshold - Pixels from bottom to consider "at bottom" (default: 100)
22
+ * @param {number} options.topThreshold - Pixels from top to trigger load more (default: 100)
23
+ * @param {Object} callbacks - Callback functions
24
+ * @param {Function} callbacks.onScrollTop - Called when user scrolls near top (for lazy loading)
25
+ * @param {Function} callbacks.getSession - Returns current session (for scroll position persistence)
26
+ */
27
+ constructor(container, options = {}, callbacks = {}) {
28
+ this.container = container;
29
+
30
+ // Configuration
31
+ this.scrollThreshold = options.scrollThreshold || 100;
32
+ this.topThreshold = options.topThreshold || 100;
33
+
34
+ // Callbacks
35
+ this.onScrollTop = callbacks.onScrollTop || (() => {});
36
+ this.getSession = callbacks.getSession || (() => null);
37
+
38
+ // State machine for scroll operations (replaces boolean flags)
39
+ this._stateMachine = new ScrollStateMachine();
40
+
41
+ // Legacy compatibility: expose isUserScrolledUp as getter
42
+ // Actual state tracked via state machine
43
+ this._isUserScrolledUp = false;
44
+
45
+ this.newMessageCount = 0;
46
+ this._lastScrollTopCall = 0; // Throttle for onScrollTop
47
+ this._rafId = null; // RAF throttling for scroll
48
+
49
+ // PERF: Cache container rect to avoid repeated layout queries
50
+ this._containerRect = null;
51
+ this._containerRectTime = 0;
52
+
53
+ // Bind scroll event with RAF throttling for performance
54
+ this._scrollHandler = () => {
55
+ // Cancel any pending RAF - only process latest scroll position
56
+ if (this._rafId) return;
57
+ this._rafId = requestAnimationFrame(() => {
58
+ this._rafId = null;
59
+ this._handleScroll();
60
+ });
61
+ };
62
+ this.container.addEventListener('scroll', this._scrollHandler);
63
+
64
+ // Hold auto-scroll while a real mouse/trackpad pointer rests on a
65
+ // link in the transcript — streaming updates otherwise yank the link
66
+ // out from under the cursor mid-click. Tracked via pointer events
67
+ // gated on pointerType 'mouse' (NOT a CSS :hover probe: iOS taps
68
+ // leave sticky :hover on links, which would wedge auto-scroll off
69
+ // after every tapped link on touch devices).
70
+ this._hoveredLink = null;
71
+ document.addEventListener('pointerover', (e) => {
72
+ if (e.pointerType !== 'mouse') return;
73
+ this._hoveredLink = e.target.closest?.('a') || null;
74
+ });
75
+ document.addEventListener('pointerout', (e) => {
76
+ if (e.pointerType !== 'mouse') return;
77
+ // Pointer left the window — no pointerover follows to clear it
78
+ if (!e.relatedTarget) this._hoveredLink = null;
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Is the mouse pointer currently on a link inside the transcript?
84
+ * isConnected guards against streaming re-renders replacing the node
85
+ * without a pointer boundary event firing.
86
+ */
87
+ _isPointerOnLink() {
88
+ const link = this._hoveredLink;
89
+ return !!(link && link.isConnected && this.container.contains(link));
90
+ }
91
+
92
+ // Legacy getter for backwards compatibility
93
+ get isUserScrolledUp() {
94
+ return this._isUserScrolledUp;
95
+ }
96
+
97
+ set isUserScrolledUp(value) {
98
+ this._isUserScrolledUp = value;
99
+ // Sync with state machine
100
+ if (value && this._stateMachine.getState() === ScrollState.IDLE) {
101
+ this._stateMachine.transition(ScrollState.USER_SCROLLING);
102
+ } else if (!value && this._stateMachine.getState() === ScrollState.USER_SCROLLING) {
103
+ this._stateMachine.transition(ScrollState.IDLE);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Handle scroll events
109
+ */
110
+ _handleScroll() {
111
+ // Skip during tab/session switch (use state machine)
112
+ if (this._stateMachine.isSwitching()) {
113
+ return;
114
+ }
115
+
116
+ const wasScrolledUp = this._isUserScrolledUp;
117
+ this._isUserScrolledUp = !this.isNearBottom();
118
+
119
+ // User scrolled back to bottom - clear indicator and update state machine
120
+ if (wasScrolledUp && !this._isUserScrolledUp) {
121
+ this.clearIndicator();
122
+ this._stateMachine.userScrolledToBottom();
123
+ } else if (!wasScrolledUp && this._isUserScrolledUp) {
124
+ // User just scrolled up - update state machine
125
+ this._stateMachine.userScrollStart();
126
+ }
127
+
128
+ // Update button visibility
129
+ this._updateButton();
130
+
131
+ // Update question indicator on scroll (check if question is now visible)
132
+ if (this._pendingQuestion) {
133
+ this.updateQuestionIndicator(this._pendingQuestion);
134
+ }
135
+
136
+ // Lazy loading: check if scrolled near top (throttled to prevent spam)
137
+ if (this._isNearTop()) {
138
+ const now = Date.now();
139
+ // Throttle: only call once per 500ms
140
+ if (now - this._lastScrollTopCall > 500) {
141
+ this._lastScrollTopCall = now;
142
+ this.onScrollTop();
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Check if user is scrolled near the bottom
149
+ */
150
+ isNearBottom() {
151
+ const distanceFromBottom = this.container.scrollHeight - this.container.scrollTop - this.container.clientHeight;
152
+ return distanceFromBottom <= this.scrollThreshold;
153
+ }
154
+
155
+ /**
156
+ * Check if user has scrolled near the top
157
+ * Note: On iOS Safari, scrollTop can be negative due to rubber-band effect
158
+ */
159
+ _isNearTop() {
160
+ // Ignore negative scrollTop (iOS rubber-band effect)
161
+ const scrollTop = Math.max(0, this.container.scrollTop);
162
+ return scrollTop < this.topThreshold;
163
+ }
164
+
165
+ /**
166
+ * Update the floating "new messages" / "scroll to bottom" button
167
+ */
168
+ _updateButton() {
169
+ // PERF: Use cached button reference instead of querying every scroll
170
+ let btn = this._newMessagesBtn;
171
+
172
+ // Show button whenever user is scrolled up
173
+ if (this.isUserScrolledUp) {
174
+ if (!btn) {
175
+ btn = document.createElement('button');
176
+ btn.className = 'new-messages-btn';
177
+ btn.addEventListener('click', () => this.scrollToBottomForce());
178
+ this.container.appendChild(btn);
179
+ this._newMessagesBtn = btn; // Cache reference
180
+ }
181
+
182
+ if (this.newMessageCount > 0) {
183
+ // New messages - highlight style
184
+ const plural = this.newMessageCount === 1 ? '' : 's';
185
+ btn.innerHTML = `
186
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
187
+ <path d="M12 19V5M5 12l7 7 7-7"/>
188
+ </svg>
189
+ ${this.newMessageCount} new message${plural}
190
+ `;
191
+ btn.classList.remove('subtle');
192
+ } else {
193
+ // No new messages - subtle style
194
+ btn.innerHTML = `
195
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
196
+ <path d="M12 19V5M5 12l7 7 7-7"/>
197
+ </svg>
198
+ Jump to latest
199
+ `;
200
+ btn.classList.add('subtle');
201
+ }
202
+ btn.classList.add('visible');
203
+ } else if (btn) {
204
+ btn.classList.remove('visible');
205
+ }
206
+ }
207
+
208
+ // ─────────────────────────────────────────────────────────────────
209
+ // Public API
210
+ // ─────────────────────────────────────────────────────────────────
211
+
212
+ /**
213
+ * Set session switching flag (to pause scroll handling)
214
+ * Uses state machine for proper state transitions
215
+ */
216
+ setSwitching(switching) {
217
+ if (switching) {
218
+ this._stateMachine.beginSwitch();
219
+ } else {
220
+ this._stateMachine.endSwitch();
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Retarget to a new scroll container (per-tab scroll architecture).
226
+ * Moves the scroll listener and floating elements to the new container.
227
+ * @param {HTMLElement} newContainer - The new scrollable element
228
+ * @param {Object} [options]
229
+ * @param {boolean} [options.skipInitState] - Skip initState() when restoring
230
+ * scroll state externally (e.g., O(1) tab switch where session.isUserScrolledUp is used)
231
+ */
232
+ setContainer(newContainer, options = {}) {
233
+ if (newContainer === this.container) return;
234
+
235
+ // Move event listener
236
+ this.container.removeEventListener('scroll', this._scrollHandler);
237
+ newContainer.addEventListener('scroll', this._scrollHandler);
238
+
239
+ // Move floating elements (new-messages button, question indicator)
240
+ if (this._newMessagesBtn?.parentNode) {
241
+ this._newMessagesBtn.remove();
242
+ newContainer.appendChild(this._newMessagesBtn);
243
+ }
244
+ if (this._questionIndicator?.parentNode) {
245
+ this._questionIndicator.remove();
246
+ newContainer.appendChild(this._questionIndicator);
247
+ }
248
+
249
+ this.container = newContainer;
250
+ this._containerRect = null; // Invalidate cached rect
251
+
252
+ if (!options.skipInitState) {
253
+ this.initState();
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Soft scroll to bottom - only scrolls if user is already near bottom
259
+ * Does NOT track new messages (use trackNewMessage for that)
260
+ * Uses state machine to prevent races
261
+ */
262
+ scrollToBottom() {
263
+ // Pointer is aiming at a link — don't move it (checked before the
264
+ // state machine so the streaming throttle stamp isn't consumed)
265
+ if (this._isPointerOnLink()) {
266
+ return;
267
+ }
268
+ // Use state machine to check if we can auto-scroll
269
+ if (!this._stateMachine.canAutoScroll()) {
270
+ return;
271
+ }
272
+ // If user is scrolled up, don't auto-scroll
273
+ if (this._isUserScrolledUp) {
274
+ return;
275
+ }
276
+
277
+ // Use state machine's coalesced scroll
278
+ this._stateMachine.scrollToBottom(this.container);
279
+ }
280
+
281
+ /**
282
+ * Soft scroll during streaming - throttled to prevent excessive scrolls
283
+ */
284
+ scrollToBottomStreaming() {
285
+ if (this._isPointerOnLink()) {
286
+ return;
287
+ }
288
+ if (!this._stateMachine.canAutoScrollStreaming(100)) {
289
+ return;
290
+ }
291
+ if (this._isUserScrolledUp) {
292
+ return;
293
+ }
294
+
295
+ this._stateMachine.scrollToBottom(this.container, { streaming: true });
296
+ }
297
+
298
+ /**
299
+ * Track that a new meaningful message arrived (for the indicator)
300
+ * Only call this for actual assistant responses, not tool updates or typing
301
+ */
302
+ trackNewMessage() {
303
+ if (this.isUserScrolledUp) {
304
+ this.newMessageCount++;
305
+ this._updateButton();
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Force scroll to bottom - always scrolls (user action)
311
+ * Used when user clicks "new messages" button or sends a message
312
+ * @param {Object} options
313
+ * @param {boolean} options.respectLinkHover - UI-initiated force scrolls
314
+ * (activity strip growth) pass this so they can't yank a link out from
315
+ * under the pointer; genuine user actions omit it and always scroll.
316
+ */
317
+ scrollToBottomForce(options = {}) {
318
+ if (options.respectLinkHover && this._isPointerOnLink()) {
319
+ return;
320
+ }
321
+ this.clearIndicator();
322
+ this._isUserScrolledUp = false;
323
+
324
+ // Reset state machine to IDLE
325
+ if (this._stateMachine.getState() === ScrollState.USER_SCROLLING) {
326
+ this._stateMachine.transition(ScrollState.IDLE);
327
+ }
328
+
329
+ // Clear saved scroll position on session
330
+ const session = this.getSession();
331
+ if (session) {
332
+ session.scrollPosition = null;
333
+ session.isUserScrolledUp = false;
334
+ }
335
+
336
+ // Use state machine's forced scroll
337
+ this._stateMachine.scrollToBottom(this.container, { force: true });
338
+ }
339
+
340
+ /**
341
+ * Clear the new messages counter and remove the button
342
+ */
343
+ clearIndicator() {
344
+ this.newMessageCount = 0;
345
+ // PERF: Use cached reference instead of querying
346
+ const btn = this._newMessagesBtn;
347
+ if (btn) {
348
+ btn.remove();
349
+ this._newMessagesBtn = null; // Clear cache
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Initialize scroll state based on current position
355
+ * Call after rendering messages
356
+ */
357
+ initState() {
358
+ this._isUserScrolledUp = !this.isNearBottom();
359
+ // Sync with state machine
360
+ if (this._isUserScrolledUp) {
361
+ this._stateMachine.forceTransition(ScrollState.USER_SCROLLING, 'initState');
362
+ } else {
363
+ this._stateMachine.forceTransition(ScrollState.IDLE, 'initState');
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Get current scroll position for persistence
369
+ */
370
+ getScrollPosition() {
371
+ return this.container.scrollTop;
372
+ }
373
+
374
+ /**
375
+ * Restore scroll position using state machine
376
+ */
377
+ setScrollPosition(position) {
378
+ this._stateMachine.savePosition(this.container);
379
+ this._stateMachine.scheduleScroll(this.container, position);
380
+ }
381
+
382
+ /**
383
+ * Scroll to absolute bottom (for restoring at-bottom state)
384
+ */
385
+ scrollToMax() {
386
+ this._stateMachine.scrollToBottom(this.container, { force: true });
387
+ }
388
+
389
+ /**
390
+ * Get state machine state (for debugging)
391
+ */
392
+ getState() {
393
+ return this._stateMachine.toJSON();
394
+ }
395
+
396
+ // ─────────────────────────────────────────────────────────────────
397
+ // Question Indicator
398
+ // ─────────────────────────────────────────────────────────────────
399
+
400
+ /**
401
+ * Update the floating question indicator
402
+ * Shows when there's a pending question that's not visible in viewport
403
+ * @param {Object|null} questionMsg - The pending question message, or null if none
404
+ */
405
+ updateQuestionIndicator(questionMsg) {
406
+ // Store for scroll handler
407
+ this._pendingQuestion = questionMsg;
408
+
409
+ // PERF: Use cached indicator reference instead of querying
410
+ let indicator = this._questionIndicator;
411
+
412
+ // No question or question is answered - hide indicator
413
+ if (!questionMsg || questionMsg.answered) {
414
+ this._pendingQuestion = null;
415
+ if (indicator) indicator.classList.remove('visible');
416
+ return;
417
+ }
418
+
419
+ // Check if question element is in viewport
420
+ const questionEl = document.getElementById(`msg-${questionMsg.id}`);
421
+ if (questionEl && this._isElementInViewport(questionEl)) {
422
+ if (indicator) indicator.classList.remove('visible');
423
+ return;
424
+ }
425
+
426
+ // Question exists but not visible - show indicator
427
+ if (!indicator) {
428
+ indicator = document.createElement('div');
429
+ indicator.className = 'question-indicator';
430
+ indicator.addEventListener('click', () => this._scrollToQuestion(questionMsg));
431
+ this.container.appendChild(indicator);
432
+ this._questionIndicator = indicator; // Cache reference
433
+ }
434
+
435
+ // Build badge previews from question entries
436
+ const entries = questionMsg.entries || [{
437
+ questions: questionMsg.questions || []
438
+ }];
439
+
440
+ // Collect all question headers/badges
441
+ const badges = [];
442
+ entries.forEach(entry => {
443
+ (Array.isArray(entry.questions) ? entry.questions : []).forEach(q => {
444
+ if (q.header) badges.push(q.header);
445
+ });
446
+ });
447
+
448
+ // Limit to first 4 badges
449
+ const displayBadges = badges.slice(0, 4);
450
+ const moreBadges = badges.length > 4 ? badges.length - 4 : 0;
451
+
452
+ const badgesHtml = displayBadges.map(b =>
453
+ `<span class="qi-badge">${this._escapeHtml(b)}</span>`
454
+ ).join('') + (moreBadges > 0 ? `<span class="qi-more">+${moreBadges}</span>` : '');
455
+
456
+ indicator.innerHTML = `
457
+ <div class="qi-icon">
458
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
459
+ <circle cx="12" cy="12" r="10"/>
460
+ <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
461
+ <line x1="12" y1="17" x2="12.01" y2="17"/>
462
+ </svg>
463
+ </div>
464
+ <div class="qi-content">
465
+ <div class="qi-title">Claude is asking</div>
466
+ <div class="qi-badges">${badgesHtml}</div>
467
+ </div>
468
+ <div class="qi-arrow">
469
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
470
+ <path d="M12 19V5M5 12l7-7 7 7"/>
471
+ </svg>
472
+ </div>
473
+ `;
474
+
475
+ indicator.classList.add('visible');
476
+ }
477
+
478
+ /**
479
+ * Get cached container bounding rect (expires after 100ms)
480
+ * PERF: Avoids repeated getBoundingClientRect calls during scroll
481
+ */
482
+ _getContainerRect() {
483
+ const now = Date.now();
484
+ if (!this._containerRect || now - this._containerRectTime > 100) {
485
+ this._containerRect = this.container.getBoundingClientRect();
486
+ this._containerRectTime = now;
487
+ }
488
+ return this._containerRect;
489
+ }
490
+
491
+ /**
492
+ * Check if an element is visible in the viewport
493
+ */
494
+ _isElementInViewport(el) {
495
+ const rect = el.getBoundingClientRect();
496
+ const containerRect = this._getContainerRect();
497
+
498
+ // Check if element is within container's visible area
499
+ return rect.top < containerRect.bottom && rect.bottom > containerRect.top;
500
+ }
501
+
502
+ /**
503
+ * Scroll to the question element
504
+ */
505
+ _scrollToQuestion(questionMsg) {
506
+ const questionEl = document.getElementById(`msg-${questionMsg.id}`);
507
+ if (questionEl) {
508
+ questionEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
509
+ // Brief highlight effect
510
+ questionEl.classList.add('highlight-pulse');
511
+ setTimeout(() => questionEl.classList.remove('highlight-pulse'), 1500);
512
+ }
513
+ // Hide indicator (use cached reference)
514
+ if (this._questionIndicator) {
515
+ this._questionIndicator.classList.remove('visible');
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Simple HTML escape (PERF: use string replacement instead of DOM)
521
+ */
522
+ _escapeHtml(str) {
523
+ return str
524
+ .replace(/&/g, '&amp;')
525
+ .replace(/</g, '&lt;')
526
+ .replace(/>/g, '&gt;')
527
+ .replace(/"/g, '&quot;')
528
+ .replace(/'/g, '&#039;');
529
+ }
530
+ }