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,1873 @@
1
+ /**
2
+ * FilePreviewWidget Styles
3
+ * Lightweight file preview with syntax highlighting
4
+ */
5
+
6
+ /* ═══════════════════════════════════════════════════════════════════════════
7
+ CONTAINER
8
+ ═══════════════════════════════════════════════════════════════════════════ */
9
+
10
+ .file-preview-widget {
11
+ display: flex;
12
+ flex-direction: column;
13
+ flex: 1;
14
+ min-height: 0;
15
+ height: 100%;
16
+ background: var(--surface-bg, #1a1a1a);
17
+ color: var(--text-primary, #e0e0e0);
18
+ font-family: var(--font-mono, 'SF Mono', Menlo, monospace);
19
+ }
20
+
21
+ /* ═══════════════════════════════════════════════════════════════════════════
22
+ WIDGET HEADER OVERRIDES
23
+ ═══════════════════════════════════════════════════════════════════════════ */
24
+
25
+ /* File preview uses path as title - needs monospace and truncation */
26
+ #widget-file-preview .widget-header {
27
+ height: 36px;
28
+ padding: 0 var(--sp-8);
29
+ gap: var(--sp-6);
30
+ }
31
+
32
+ #widget-file-preview .widget-header-spacer {
33
+ flex: 0; /* Title already has flex:1, spacer would steal half the space */
34
+ }
35
+
36
+ #widget-file-preview .widget-title {
37
+ font-family: var(--font-mono, 'SF Mono', Menlo, monospace);
38
+ font-size: var(--font-size-sm);
39
+ font-weight: 500;
40
+ color: var(--text-secondary, #aaa);
41
+ white-space: nowrap;
42
+ overflow: hidden;
43
+ text-overflow: ellipsis;
44
+ min-width: 0;
45
+ flex: 1;
46
+ /* Show end of path (filename) when truncating */
47
+ direction: rtl;
48
+ text-align: left;
49
+ }
50
+
51
+ #widget-file-preview .widget-title::before {
52
+ content: '\200E'; /* LTR mark to fix display */
53
+ }
54
+
55
+ /* Separator between copy-path and other actions */
56
+ #widget-file-preview .header-separator {
57
+ display: inline-block;
58
+ width: 1px;
59
+ height: 16px;
60
+ background: var(--border-color, #333);
61
+ margin: 0 var(--sp-2);
62
+ }
63
+
64
+ /* Make header button container for separator not clickable */
65
+ #widget-file-preview .widget-header-btn:has(.header-separator) {
66
+ pointer-events: none;
67
+ width: auto;
68
+ padding: 0 var(--sp-2);
69
+ }
70
+
71
+ /* ═══════════════════════════════════════════════════════════════════════════
72
+ HEADER BREADCRUMB (clickable path → quick file switch)
73
+ ═══════════════════════════════════════════════════════════════════════════ */
74
+
75
+ /* When the title holds a breadcrumb it becomes an LTR flex container.
76
+ Override the rtl/ellipsis path-truncation rules above (needs the ID scope
77
+ to beat their specificity). */
78
+ #widget-file-preview .widget-title.has-breadcrumb {
79
+ direction: ltr;
80
+ display: flex;
81
+ align-items: center;
82
+ text-overflow: clip;
83
+ }
84
+ #widget-file-preview .widget-title.has-breadcrumb::before {
85
+ content: none;
86
+ }
87
+
88
+ .preview-breadcrumb {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: var(--sp-2);
92
+ flex: 1;
93
+ min-width: 0;
94
+ overflow-x: auto;
95
+ scrollbar-width: none;
96
+ }
97
+ .preview-breadcrumb::-webkit-scrollbar {
98
+ display: none;
99
+ }
100
+
101
+ .pb-segment {
102
+ flex: 0 0 auto;
103
+ padding: var(--sp-2) var(--sp-4);
104
+ background: transparent;
105
+ border: none;
106
+ color: var(--text-secondary, #aaa);
107
+ font-family: inherit;
108
+ font-size: var(--font-size-sm);
109
+ line-height: 1.4;
110
+ border-radius: var(--radius-sm);
111
+ cursor: pointer;
112
+ white-space: nowrap;
113
+ transition: color 0.15s ease, background 0.15s ease;
114
+ }
115
+ .pb-segment:hover {
116
+ color: var(--text-primary, #e0e0e0);
117
+ background: var(--bg-hover, #30363d);
118
+ }
119
+ .pb-segment.is-file {
120
+ color: var(--text-primary, #e0e0e0);
121
+ font-weight: 500;
122
+ }
123
+ .pb-root {
124
+ display: inline-flex;
125
+ align-items: center;
126
+ color: var(--accent);
127
+ }
128
+ .pb-root svg {
129
+ width: 13px;
130
+ height: 13px;
131
+ display: block;
132
+ }
133
+ .pb-sep {
134
+ flex: 0 0 auto;
135
+ color: var(--text-tertiary, #666);
136
+ font-size: var(--font-size-xs);
137
+ user-select: none;
138
+ }
139
+
140
+ /* ── Breadcrumb dropdown (appended to <body>, positioned fixed) ───────────── */
141
+
142
+ .preview-breadcrumb-menu {
143
+ position: fixed;
144
+ z-index: var(--z-overlay, 2000);
145
+ display: flex;
146
+ flex-direction: column;
147
+ min-width: 220px;
148
+ max-width: min(380px, calc(100vw - 16px));
149
+ max-height: min(60vh, 440px);
150
+ background: var(--bg-secondary, #161b22);
151
+ border: 1px solid var(--border-color, #30363d);
152
+ border-radius: var(--radius-md);
153
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
154
+ overflow: hidden;
155
+ font-family: var(--font-mono, 'SF Mono', Menlo, monospace);
156
+ }
157
+
158
+ .pbm-header {
159
+ padding: var(--sp-4);
160
+ border-bottom: 1px solid var(--border-color, #30363d);
161
+ }
162
+ .pbm-filter {
163
+ width: 100%;
164
+ box-sizing: border-box;
165
+ background: var(--bg-primary, #0d1117);
166
+ border: 1px solid var(--border-color, #30363d);
167
+ border-radius: var(--radius-sm);
168
+ color: var(--text-primary, #e0e0e0);
169
+ font-family: inherit;
170
+ font-size: var(--font-size-sm);
171
+ padding: var(--sp-3) var(--sp-6);
172
+ outline: none;
173
+ }
174
+ .pbm-filter:focus {
175
+ border-color: var(--accent);
176
+ }
177
+
178
+ .pbm-list {
179
+ flex: 1;
180
+ overflow-y: auto;
181
+ padding: var(--sp-2);
182
+ }
183
+
184
+ .pbm-item {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: var(--sp-6);
188
+ width: 100%;
189
+ text-align: left;
190
+ padding: var(--sp-3) var(--sp-6);
191
+ background: transparent;
192
+ border: none;
193
+ border-radius: var(--radius-sm);
194
+ color: var(--text-secondary, #aaa);
195
+ font-family: inherit;
196
+ font-size: var(--font-size-sm);
197
+ cursor: pointer;
198
+ white-space: nowrap;
199
+ }
200
+ .pbm-item .pbm-icon {
201
+ flex: 0 0 auto;
202
+ display: inline-flex;
203
+ color: var(--text-tertiary, #666);
204
+ }
205
+ .pbm-item .pbm-icon svg {
206
+ width: 14px;
207
+ height: 14px;
208
+ }
209
+ .pbm-item .pbm-name {
210
+ overflow: hidden;
211
+ text-overflow: ellipsis;
212
+ }
213
+ .pbm-item.is-dir {
214
+ color: var(--text-primary, #e0e0e0);
215
+ }
216
+ .pbm-item.is-dir .pbm-icon {
217
+ color: var(--accent);
218
+ }
219
+ .pbm-item:hover,
220
+ .pbm-item.active {
221
+ background: var(--bg-hover, #30363d);
222
+ color: var(--text-primary, #e0e0e0);
223
+ }
224
+ .pbm-item.current {
225
+ color: var(--accent);
226
+ }
227
+ .pbm-item.current::after {
228
+ content: '';
229
+ width: 6px;
230
+ height: 6px;
231
+ margin-left: auto;
232
+ border-radius: 50%;
233
+ background: var(--accent);
234
+ }
235
+ .pbm-up .pbm-name {
236
+ color: var(--text-tertiary, #666);
237
+ }
238
+
239
+ .pbm-loading,
240
+ .pbm-empty,
241
+ .pbm-error,
242
+ .pbm-more {
243
+ padding: var(--sp-8);
244
+ color: var(--text-tertiary, #666);
245
+ font-size: var(--font-size-sm);
246
+ text-align: center;
247
+ }
248
+ .pbm-more {
249
+ padding: var(--sp-4) var(--sp-8);
250
+ border-top: 1px solid var(--border-color, #30363d);
251
+ }
252
+
253
+ /* Icon sizes in header */
254
+ #widget-file-preview .widget-header-btn svg {
255
+ width: 15px;
256
+ height: 15px;
257
+ }
258
+
259
+ /* ═══════════════════════════════════════════════════════════════════════════
260
+ BODY
261
+ ═══════════════════════════════════════════════════════════════════════════ */
262
+
263
+ .preview-body {
264
+ flex: 1;
265
+ min-height: 0;
266
+ overflow: auto;
267
+ -webkit-overflow-scrolling: touch;
268
+ touch-action: pan-x pan-y; /* iOS: allow both scroll directions */
269
+ overscroll-behavior: contain; /* Prevent scroll chain to parent */
270
+ }
271
+
272
+ /* Loading state */
273
+ .preview-loading {
274
+ display: flex;
275
+ flex-direction: column;
276
+ align-items: center;
277
+ justify-content: center;
278
+ height: 100%;
279
+ gap: var(--sp-12);
280
+ color: var(--text-secondary, #888);
281
+ }
282
+
283
+ .loading-spinner {
284
+ width: 24px;
285
+ height: 24px;
286
+ border: 2px solid var(--border, #333);
287
+ border-top-color: var(--accent);
288
+ border-radius: 50%;
289
+ animation: spin 1s linear infinite;
290
+ }
291
+
292
+ @keyframes spin {
293
+ to { transform: rotate(360deg); }
294
+ }
295
+
296
+ /* Empty state */
297
+ .preview-empty {
298
+ display: flex;
299
+ flex-direction: column;
300
+ align-items: center;
301
+ justify-content: center;
302
+ height: 100%;
303
+ gap: var(--sp-12);
304
+ color: var(--text-muted, #666);
305
+ }
306
+
307
+ .preview-empty svg {
308
+ width: 48px;
309
+ height: 48px;
310
+ opacity: 0.5;
311
+ }
312
+
313
+ .preview-empty-hint {
314
+ font-size: var(--font-size-sm);
315
+ color: var(--text-muted, #666);
316
+ margin-top: calc(var(--sp-12) * -1);
317
+ }
318
+
319
+ /* "Open a file…" — the empty state's way in, same picker as Cmd+O */
320
+ .preview-empty-open {
321
+ display: inline-flex;
322
+ align-items: center;
323
+ gap: var(--sp-8);
324
+ padding: var(--sp-8) var(--sp-12);
325
+ border: 1px solid var(--border-primary);
326
+ border-radius: var(--radius-sm);
327
+ background: var(--bg-tertiary);
328
+ color: var(--text-primary);
329
+ font-size: var(--font-size-sm);
330
+ font-family: inherit;
331
+ cursor: pointer;
332
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
333
+ }
334
+
335
+ .preview-empty-open:hover {
336
+ background: var(--bg-hover);
337
+ border-color: var(--accent);
338
+ color: var(--accent);
339
+ }
340
+
341
+ /* Override the 48px icon sizing the empty-state illustration sets */
342
+ .preview-empty-open svg {
343
+ width: 15px;
344
+ height: 15px;
345
+ opacity: 1;
346
+ }
347
+
348
+ .preview-empty-open kbd {
349
+ padding: 1px var(--sp-4);
350
+ border: 1px solid var(--border-primary);
351
+ border-radius: var(--radius-xs);
352
+ background: var(--bg-secondary);
353
+ color: var(--text-tertiary);
354
+ font-family: var(--font-mono);
355
+ font-size: var(--font-size-xs);
356
+ line-height: 1.5;
357
+ }
358
+
359
+ /* Error state */
360
+ .preview-error {
361
+ display: flex;
362
+ flex-direction: column;
363
+ align-items: center;
364
+ justify-content: center;
365
+ height: 100%;
366
+ gap: var(--sp-12);
367
+ color: var(--error, #ff6b6b);
368
+ padding: var(--sp-20);
369
+ text-align: center;
370
+ }
371
+
372
+ .preview-error svg {
373
+ width: 32px;
374
+ height: 32px;
375
+ }
376
+
377
+ /* ═══════════════════════════════════════════════════════════════════════════
378
+ TOOLBAR (toggle + line badge)
379
+ ═══════════════════════════════════════════════════════════════════════════ */
380
+
381
+ .preview-toolbar {
382
+ display: flex;
383
+ align-items: center;
384
+ gap: var(--sp-12);
385
+ padding: var(--sp-8) var(--sp-12);
386
+ background: var(--surface-bg, #1a1a1a);
387
+ border-bottom: 1px solid var(--border-color, #333);
388
+ flex-shrink: 0; /* Don't shrink, stay fixed height */
389
+ }
390
+
391
+ /* ═══════════════════════════════════════════════════════════════════════════
392
+ VIEW MODE TOGGLE (for markdown files)
393
+ ═══════════════════════════════════════════════════════════════════════════ */
394
+
395
+ .preview-view-toggle {
396
+ display: flex;
397
+ gap: var(--sp-4);
398
+ }
399
+
400
+ .preview-view-toggle .toggle-btn {
401
+ display: flex;
402
+ align-items: center;
403
+ gap: var(--sp-6);
404
+ padding: var(--sp-6) var(--sp-12);
405
+ background: transparent;
406
+ border: 1px solid var(--border-color, #444);
407
+ border-radius: var(--radius-md);
408
+ color: var(--text-secondary, #888);
409
+ font-size: var(--font-size-sm);
410
+ font-family: var(--font-sans, system-ui, sans-serif);
411
+ cursor: pointer;
412
+ transition: all 0.15s ease;
413
+ }
414
+
415
+ .preview-view-toggle .toggle-btn:hover {
416
+ background: var(--surface-hover, #2a2a2a);
417
+ color: var(--text-primary, #e0e0e0);
418
+ }
419
+
420
+ .preview-view-toggle .toggle-btn.active {
421
+ background: rgba(var(--accent-rgb), 0.15);
422
+ border-color: var(--accent);
423
+ color: var(--accent);
424
+ }
425
+
426
+ .preview-view-toggle .toggle-btn svg {
427
+ width: 14px;
428
+ height: 14px;
429
+ flex-shrink: 0;
430
+ }
431
+
432
+ /* ═══════════════════════════════════════════════════════════════════════════
433
+ LINE BADGE (shows line number or range)
434
+ ═══════════════════════════════════════════════════════════════════════════ */
435
+
436
+ .preview-line-badge {
437
+ display: flex;
438
+ align-items: center;
439
+ gap: var(--sp-6);
440
+ padding: var(--sp-5) var(--sp-10);
441
+ background: rgba(255, 213, 79, 0.12);
442
+ border: 1px solid rgba(255, 213, 79, 0.3);
443
+ border-radius: var(--radius-md);
444
+ color: #ffd54f;
445
+ font-size: var(--font-size-sm);
446
+ font-family: var(--font-mono, 'SF Mono', Menlo, monospace);
447
+ margin-left: auto; /* Push to right side of toolbar */
448
+ cursor: pointer;
449
+ transition: all 0.15s ease;
450
+ }
451
+
452
+ .preview-line-badge:hover {
453
+ background: rgba(255, 213, 79, 0.2);
454
+ border-color: rgba(255, 213, 79, 0.5);
455
+ }
456
+
457
+ .preview-line-badge svg {
458
+ width: 14px;
459
+ height: 14px;
460
+ flex-shrink: 0;
461
+ opacity: 0.8;
462
+ }
463
+
464
+ .preview-line-badge .line-count {
465
+ color: var(--text-secondary, #888);
466
+ font-size: var(--font-size-xs);
467
+ }
468
+
469
+ /* ═══════════════════════════════════════════════════════════════════════════
470
+ TOOLBAR SPACER (pushes elements to right)
471
+ ═══════════════════════════════════════════════════════════════════════════ */
472
+
473
+ .toolbar-spacer {
474
+ flex: 1;
475
+ min-width: 0;
476
+ }
477
+
478
+ /* ═══════════════════════════════════════════════════════════════════════════
479
+ LANGUAGE SELECTOR
480
+ ═══════════════════════════════════════════════════════════════════════════ */
481
+
482
+ .preview-language-selector {
483
+ display: flex;
484
+ align-items: center;
485
+ gap: var(--sp-4);
486
+ }
487
+
488
+ .preview-language-selector .language-select {
489
+ appearance: none;
490
+ padding: var(--sp-5) var(--sp-24) var(--sp-5) var(--sp-10);
491
+ background: var(--surface-bg, #1a1a1a);
492
+ border: 1px solid var(--border-color, #444);
493
+ border-radius: var(--radius-md);
494
+ color: var(--text-secondary, #888);
495
+ font-size: var(--font-size-sm);
496
+ font-family: var(--font-sans, system-ui, sans-serif);
497
+ cursor: pointer;
498
+ transition: all 0.15s ease;
499
+ /* Custom dropdown arrow */
500
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
501
+ background-repeat: no-repeat;
502
+ background-position: right 6px center;
503
+ }
504
+
505
+ .preview-language-selector .language-select:hover {
506
+ border-color: var(--border-hover, #555);
507
+ color: var(--text-primary, #e0e0e0);
508
+ }
509
+
510
+ .preview-language-selector .language-select:focus {
511
+ outline: none;
512
+ border-color: var(--accent);
513
+ box-shadow: 0 0 0 2px rgba(111, 158, 255, 0.2);
514
+ }
515
+
516
+ /* Override state - show accent border when language is manually set */
517
+ .preview-language-selector.overridden .language-select {
518
+ border-color: var(--accent);
519
+ color: var(--accent);
520
+ }
521
+
522
+ /* Reset button - only visible when overridden */
523
+ .preview-language-selector .reset-language-btn {
524
+ display: flex;
525
+ align-items: center;
526
+ justify-content: center;
527
+ width: 22px;
528
+ height: 22px;
529
+ padding: 0;
530
+ background: transparent;
531
+ border: 1px solid var(--border-color, #444);
532
+ border-radius: var(--radius-sm);
533
+ color: var(--text-secondary, #888);
534
+ font-size: var(--font-size-md);
535
+ font-weight: 500;
536
+ cursor: pointer;
537
+ transition: all 0.15s ease;
538
+ }
539
+
540
+ .preview-language-selector .reset-language-btn:hover {
541
+ background: var(--surface-hover, #2a2a2a);
542
+ border-color: var(--border-hover, #555);
543
+ color: var(--text-primary, #e0e0e0);
544
+ }
545
+
546
+ /* ═══════════════════════════════════════════════════════════════════════════
547
+ SEARCH BUTTON
548
+ ═══════════════════════════════════════════════════════════════════════════ */
549
+
550
+ .preview-search-btn {
551
+ display: flex;
552
+ align-items: center;
553
+ justify-content: center;
554
+ width: var(--layout-btn-md);
555
+ height: var(--layout-btn-md);
556
+ padding: 0;
557
+ background: transparent;
558
+ border: 1px solid var(--border-color, #444);
559
+ border-radius: var(--radius-md);
560
+ color: var(--text-secondary, #888);
561
+ cursor: pointer;
562
+ transition: all 0.15s ease;
563
+ flex-shrink: 0;
564
+ }
565
+
566
+ .preview-search-btn:hover {
567
+ background: var(--surface-hover, #2a2a2a);
568
+ color: var(--text-primary, #e0e0e0);
569
+ }
570
+
571
+ /* Lit while the search bar is open — the button stays in the toolbar and
572
+ re-focuses the live query rather than reopening a blank search. */
573
+ .preview-search-btn.active {
574
+ background: rgba(var(--accent-rgb), 0.25);
575
+ border-color: rgba(var(--accent-rgb), 0.6);
576
+ color: var(--accent);
577
+ }
578
+
579
+ .preview-search-btn.active:hover {
580
+ background: rgba(var(--accent-rgb), 0.35);
581
+ }
582
+
583
+ .preview-search-btn svg {
584
+ width: 16px;
585
+ height: 16px;
586
+ }
587
+
588
+ /* ═══════════════════════════════════════════════════════════════════════════
589
+ DOWNLOAD LINK BUTTON
590
+ ═══════════════════════════════════════════════════════════════════════════ */
591
+
592
+ .preview-download-btn {
593
+ display: flex;
594
+ align-items: center;
595
+ justify-content: center;
596
+ width: var(--layout-btn-md);
597
+ height: var(--layout-btn-md);
598
+ padding: 0;
599
+ background: transparent;
600
+ border: 1px solid var(--border-color, #444);
601
+ border-radius: var(--radius-md);
602
+ color: var(--text-secondary, #888);
603
+ cursor: pointer;
604
+ transition: all 0.15s ease;
605
+ flex-shrink: 0;
606
+ }
607
+
608
+ .preview-download-btn:hover {
609
+ background: var(--surface-hover, #2a2a2a);
610
+ color: var(--text-primary, #e0e0e0);
611
+ }
612
+
613
+ .preview-download-btn svg {
614
+ width: 16px;
615
+ height: 16px;
616
+ }
617
+
618
+ /* ═══════════════════════════════════════════════════════════════════════════
619
+ SEARCH BAR
620
+ ═══════════════════════════════════════════════════════════════════════════ */
621
+
622
+ .preview-search-bar {
623
+ display: flex;
624
+ align-items: center;
625
+ gap: var(--sp-8);
626
+ flex: 1;
627
+ min-width: 0;
628
+ }
629
+
630
+ .preview-search-input {
631
+ flex: 1;
632
+ min-width: 0;
633
+ padding: var(--sp-6) var(--sp-12);
634
+ background: var(--surface-bg-elevated, #252525);
635
+ border: 1px solid var(--border-color, #444);
636
+ border-radius: var(--radius-md);
637
+ color: var(--text-primary, #e0e0e0);
638
+ font-size: var(--font-size-base);
639
+ font-family: var(--font-sans, system-ui, sans-serif);
640
+ outline: none;
641
+ transition: border-color 0.15s ease;
642
+ }
643
+
644
+ .preview-search-input:focus {
645
+ border-color: var(--accent);
646
+ box-shadow: 0 0 0 2px rgba(111, 158, 255, 0.2);
647
+ }
648
+
649
+ .preview-search-input::placeholder {
650
+ color: var(--text-muted, #666);
651
+ }
652
+
653
+ .preview-search-count {
654
+ font-size: var(--font-size-sm);
655
+ color: var(--text-secondary, #888);
656
+ white-space: nowrap;
657
+ min-width: 70px;
658
+ text-align: center;
659
+ }
660
+
661
+ .preview-search-count.no-results {
662
+ color: var(--error, #ff6b6b);
663
+ }
664
+
665
+ .preview-search-nav,
666
+ .preview-search-close {
667
+ display: flex;
668
+ align-items: center;
669
+ justify-content: center;
670
+ width: var(--layout-btn-sm);
671
+ height: var(--layout-btn-sm);
672
+ padding: 0;
673
+ background: transparent;
674
+ border: 1px solid var(--border-color, #444);
675
+ border-radius: var(--radius-sm);
676
+ color: var(--text-secondary, #888);
677
+ cursor: pointer;
678
+ transition: all 0.15s ease;
679
+ flex-shrink: 0;
680
+ }
681
+
682
+ .preview-search-nav:hover:not(:disabled),
683
+ .preview-search-close:hover {
684
+ background: var(--surface-hover, #2a2a2a);
685
+ color: var(--text-primary, #e0e0e0);
686
+ }
687
+
688
+ .preview-search-nav:disabled {
689
+ opacity: 0.5;
690
+ cursor: not-allowed;
691
+ }
692
+
693
+ .preview-search-nav svg,
694
+ .preview-search-close svg {
695
+ width: 14px;
696
+ height: 14px;
697
+ }
698
+
699
+ /* ═══════════════════════════════════════════════════════════════════════════
700
+ SEARCH MATCH HIGHLIGHTS
701
+ ═══════════════════════════════════════════════════════════════════════════ */
702
+
703
+ .search-match {
704
+ background: rgba(255, 213, 79, 0.3);
705
+ border-radius: var(--radius-xs);
706
+ padding: 0 1px;
707
+ }
708
+
709
+ .search-match.current {
710
+ background: rgba(255, 213, 79, 0.6);
711
+ outline: 2px solid rgba(255, 213, 79, 0.8);
712
+ outline-offset: 0;
713
+ }
714
+
715
+ /* ═══════════════════════════════════════════════════════════════════════════
716
+ WRAP TOGGLE BUTTON
717
+ ═══════════════════════════════════════════════════════════════════════════ */
718
+
719
+ .preview-wrap-toggle {
720
+ display: flex;
721
+ align-items: center;
722
+ justify-content: center;
723
+ width: 32px;
724
+ height: 32px;
725
+ padding: 0;
726
+ background: transparent;
727
+ border: 1px solid var(--border-color, #444);
728
+ border-radius: var(--radius-md);
729
+ color: var(--text-secondary, #888);
730
+ cursor: pointer;
731
+ transition: all 0.15s ease;
732
+ flex-shrink: 0; /* Don't shrink */
733
+ }
734
+
735
+ .preview-wrap-toggle:hover {
736
+ background: var(--surface-hover, #2a2a2a);
737
+ color: var(--text-primary, #e0e0e0);
738
+ }
739
+
740
+ .preview-wrap-toggle.active {
741
+ background: rgba(var(--accent-rgb), 0.15);
742
+ border-color: var(--accent);
743
+ color: var(--accent);
744
+ }
745
+
746
+ .preview-wrap-toggle svg {
747
+ width: 16px;
748
+ height: 16px;
749
+ }
750
+
751
+ /* Adjust line badge margin when wrap toggle is present */
752
+ .preview-toolbar .preview-line-badge {
753
+ margin-left: 0;
754
+ margin-right: auto;
755
+ }
756
+
757
+ /* ═══════════════════════════════════════════════════════════════════════════
758
+ RENDERED MARKDOWN VIEW
759
+ ═══════════════════════════════════════════════════════════════════════════ */
760
+
761
+ .preview-rendered-wrapper {
762
+ padding: var(--sp-16) var(--sp-20);
763
+ min-height: 100%;
764
+ }
765
+
766
+ .preview-rendered {
767
+ font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
768
+ font-size: var(--font-size-md);
769
+ line-height: 1.6;
770
+ color: var(--text-primary, #e0e0e0);
771
+ }
772
+
773
+ /* Use existing markdown-content styles from 30-markdown.css */
774
+
775
+ /* ─── YAML front matter panel ─── */
776
+
777
+ .preview-frontmatter {
778
+ margin-bottom: var(--sp-16, 16px);
779
+ border: 1px solid var(--border-primary, rgba(255, 255, 255, 0.1));
780
+ border-radius: var(--radius-md, 6px);
781
+ background: var(--bg-secondary, rgba(255, 255, 255, 0.03));
782
+ overflow: hidden;
783
+ }
784
+
785
+ .preview-fm-header {
786
+ display: flex;
787
+ align-items: center;
788
+ gap: var(--sp-6, 6px);
789
+ width: 100%;
790
+ padding: var(--sp-6, 6px) var(--sp-10, 10px);
791
+ background: transparent;
792
+ border: none;
793
+ color: var(--text-secondary, #9ca3af);
794
+ font-size: var(--font-size-xs);
795
+ font-weight: 600;
796
+ text-transform: uppercase;
797
+ letter-spacing: 0.05em;
798
+ cursor: pointer;
799
+ text-align: left;
800
+ }
801
+
802
+ .preview-fm-header:hover {
803
+ background: var(--bg-hover, rgba(255, 255, 255, 0.05));
804
+ color: var(--text-primary, #e0e0e0);
805
+ }
806
+
807
+ .preview-fm-chevron {
808
+ width: 12px;
809
+ height: 12px;
810
+ flex-shrink: 0;
811
+ transition: transform 0.15s ease;
812
+ }
813
+
814
+ .preview-frontmatter.collapsed .preview-fm-chevron {
815
+ transform: rotate(-90deg);
816
+ }
817
+
818
+ .preview-fm-count {
819
+ margin-left: auto;
820
+ padding: 0 var(--sp-6, 6px);
821
+ border-radius: var(--radius-xs, 2px);
822
+ background: var(--bg-tertiary, rgba(255, 255, 255, 0.06));
823
+ font-weight: 500;
824
+ }
825
+
826
+ .preview-fm-fields {
827
+ display: grid;
828
+ grid-template-columns: minmax(80px, max-content) 1fr;
829
+ padding: var(--sp-4, 4px) var(--sp-10, 10px) var(--sp-10, 10px);
830
+ column-gap: var(--sp-16, 16px);
831
+ row-gap: var(--sp-4, 4px);
832
+ }
833
+
834
+ .preview-frontmatter.collapsed .preview-fm-fields {
835
+ display: none;
836
+ }
837
+
838
+ .preview-fm-row {
839
+ display: contents;
840
+ }
841
+
842
+ .preview-fm-key {
843
+ color: var(--text-secondary, #9ca3af);
844
+ font-family: var(--font-mono, monospace);
845
+ font-size: var(--font-size-sm);
846
+ line-height: 1.6;
847
+ word-break: break-word;
848
+ }
849
+
850
+ .preview-fm-val {
851
+ min-width: 0;
852
+ font-size: var(--font-size-base);
853
+ line-height: 1.6;
854
+ color: var(--text-primary, #e0e0e0);
855
+ }
856
+
857
+ .preview-fm-value {
858
+ overflow-wrap: anywhere;
859
+ white-space: pre-wrap;
860
+ }
861
+
862
+ .preview-fm-chip {
863
+ display: inline-block;
864
+ margin: 0 var(--sp-4, 4px) var(--sp-2, 2px) 0;
865
+ padding: 0 var(--sp-6, 6px);
866
+ border-radius: var(--radius-xs, 2px);
867
+ background: var(--bg-tertiary, rgba(255, 255, 255, 0.06));
868
+ border: 1px solid var(--border-primary, rgba(255, 255, 255, 0.08));
869
+ font-size: var(--font-size-sm);
870
+ line-height: 1.7;
871
+ }
872
+
873
+ .preview-fm-block {
874
+ margin: 0;
875
+ padding: var(--sp-6, 6px) var(--sp-8, 8px);
876
+ border-radius: var(--radius-sm, 3px);
877
+ background: var(--bg-primary, rgba(0, 0, 0, 0.25));
878
+ font-family: var(--font-mono, monospace);
879
+ font-size: var(--font-size-sm);
880
+ line-height: 1.5;
881
+ white-space: pre-wrap;
882
+ overflow-wrap: anywhere;
883
+ }
884
+
885
+ /* ═══════════════════════════════════════════════════════════════════════════
886
+ CODE PREVIEW
887
+ ═══════════════════════════════════════════════════════════════════════════ */
888
+
889
+ .preview-code-wrapper {
890
+ min-height: 100%;
891
+ }
892
+
893
+ .preview-code {
894
+ margin: 0;
895
+ padding: var(--sp-8) 0;
896
+ background: transparent;
897
+ font-size: var(--font-size-sm);
898
+ line-height: 1.5;
899
+ tab-size: 4;
900
+ }
901
+
902
+ .preview-code code {
903
+ display: block;
904
+ }
905
+
906
+ .preview-line {
907
+ display: flex;
908
+ padding: 0 var(--sp-12) 0 0;
909
+ /* Same row height as a .cm-line (sm × 1.5) at every font scale — a fixed
910
+ 18px clamp made rows drift vertically vs the editor when scaled. */
911
+ min-height: calc(var(--font-size-sm) * 1.5);
912
+ }
913
+
914
+ .preview-line:hover {
915
+ background: rgba(255, 255, 255, 0.03);
916
+ }
917
+
918
+ .preview-line.highlighted {
919
+ background: rgba(255, 213, 79, 0.15);
920
+ }
921
+
922
+ /* Range boundary markers - visual bracket effect */
923
+ .preview-line.highlighted.range-start {
924
+ border-top: 2px solid rgba(255, 213, 79, 0.5);
925
+ padding-top: 0; /* Adjust for border */
926
+ }
927
+
928
+ .preview-line.highlighted.range-end {
929
+ border-bottom: 2px solid rgba(255, 213, 79, 0.5);
930
+ }
931
+
932
+ .preview-line.flash,
933
+ .preview-rendered .flash {
934
+ animation: line-flash 1.5s ease-out;
935
+ }
936
+
937
+ @keyframes line-flash {
938
+ 0% { background: rgba(255, 213, 79, 0.4); }
939
+ 100% { background: rgba(255, 213, 79, 0.15); }
940
+ }
941
+
942
+ .line-number {
943
+ flex-shrink: 0;
944
+ width: 48px;
945
+ padding-right: var(--sp-12);
946
+ text-align: right;
947
+ color: var(--text-muted, #555);
948
+ user-select: none;
949
+ -webkit-user-select: none;
950
+ font-size: var(--font-size-xs);
951
+ }
952
+
953
+ /* Fold cell — mirrors the editor's foldGutter (same ⌄/› glyphs, same 14px
954
+ column) so Code ⇄ Edit don't shift horizontally. Present (empty) on every
955
+ line; lines starting a foldable range get the arrow + click handling. */
956
+ .preview-fold {
957
+ flex-shrink: 0;
958
+ width: 14px;
959
+ text-align: center;
960
+ color: var(--text-muted);
961
+ user-select: none;
962
+ -webkit-user-select: none;
963
+ }
964
+
965
+ .preview-fold[data-fold-end] {
966
+ cursor: pointer;
967
+ }
968
+
969
+ .preview-fold[data-fold-end]::after {
970
+ content: '\2304'; /* ⌄ — matches CM foldGutter openText */
971
+ }
972
+
973
+ .preview-line.fold-collapsed .preview-fold[data-fold-end]::after {
974
+ content: '\203A'; /* › — matches CM foldGutter closedText */
975
+ }
976
+
977
+ .preview-fold[data-fold-end]:hover {
978
+ color: var(--text-primary);
979
+ }
980
+
981
+ .preview-line.fold-hidden {
982
+ display: none;
983
+ }
984
+
985
+ .line-content {
986
+ flex: 1;
987
+ min-width: 0;
988
+ white-space: pre;
989
+ /* CM's base theme gives .cm-line `padding: 0 2px 0 6px` — mirror the 6px
990
+ so text sits at the same x in Code and Edit (no few-px shift). */
991
+ padding-left: 6px;
992
+ }
993
+
994
+ /* Wrap mode - long lines wrap instead of horizontal scroll */
995
+ .preview-code-wrapper.wrap-lines .line-content {
996
+ white-space: pre-wrap;
997
+ word-break: break-all;
998
+ }
999
+
1000
+ .preview-code-wrapper.wrap-lines .preview-line {
1001
+ align-items: flex-start; /* Align line number to top when wrapping */
1002
+ }
1003
+
1004
+ .preview-code-wrapper.wrap-lines .line-number {
1005
+ padding-top: 0;
1006
+ }
1007
+
1008
+ /* ═══════════════════════════════════════════════════════════════════════════
1009
+ SHARED PAN/ZOOM (used by image preview + excalidraw)
1010
+ ═══════════════════════════════════════════════════════════════════════════ */
1011
+
1012
+ .panzoom-toolbar {
1013
+ display: flex;
1014
+ align-items: center;
1015
+ gap: var(--sp-6);
1016
+ padding: var(--sp-6) var(--sp-12);
1017
+ background: var(--surface-bg, #1a1a1a);
1018
+ border-bottom: 1px solid var(--border-color, #333);
1019
+ flex-shrink: 0;
1020
+ }
1021
+
1022
+ .panzoom-ctrl-btn {
1023
+ height: var(--layout-btn-sm);
1024
+ background: var(--surface-bg-elevated, #21262d);
1025
+ border: 1px solid var(--border-color, #30363d);
1026
+ color: var(--text-secondary, #8b949e);
1027
+ padding: var(--sp-4) var(--sp-10);
1028
+ border-radius: var(--radius-md);
1029
+ cursor: pointer;
1030
+ font-size: var(--font-size-sm);
1031
+ transition: all 0.15s;
1032
+ }
1033
+
1034
+ .panzoom-ctrl-btn:hover, .panzoom-ctrl-btn:active {
1035
+ background: var(--surface-hover, #30363d);
1036
+ color: var(--text-primary, #e6edf3);
1037
+ }
1038
+
1039
+ .panzoom-ctrl-btn.active {
1040
+ background: rgba(var(--accent-rgb), 0.15);
1041
+ border-color: var(--accent);
1042
+ color: var(--accent);
1043
+ }
1044
+
1045
+ .panzoom-zoom-label {
1046
+ color: var(--text-secondary, #8b949e);
1047
+ font-size: var(--font-size-sm);
1048
+ font-family: var(--font-mono, 'SF Mono', Menlo, monospace);
1049
+ min-width: 40px;
1050
+ text-align: center;
1051
+ user-select: none;
1052
+ }
1053
+
1054
+ .panzoom-canvas {
1055
+ position: relative;
1056
+ cursor: grab;
1057
+ touch-action: none;
1058
+ overflow: hidden;
1059
+ }
1060
+
1061
+ .panzoom-canvas:active {
1062
+ cursor: grabbing;
1063
+ }
1064
+
1065
+ .panzoom-img {
1066
+ position: absolute;
1067
+ top: 0;
1068
+ left: 0;
1069
+ transform-origin: 0 0;
1070
+ max-width: none;
1071
+ max-height: none;
1072
+ border-radius: 0;
1073
+ }
1074
+
1075
+ /* ═══════════════════════════════════════════════════════════════════════════
1076
+ IMAGE PREVIEW
1077
+ ═══════════════════════════════════════════════════════════════════════════ */
1078
+
1079
+ .preview-image-body {
1080
+ background: repeating-conic-gradient(#222 0% 25%, #1a1a1a 0% 50%)
1081
+ 50% / 16px 16px;
1082
+ }
1083
+
1084
+ .preview-image {
1085
+ border-radius: var(--radius-sm);
1086
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
1087
+ }
1088
+
1089
+ /* ═══════════════════════════════════════════════════════════════════════════
1090
+ HTML PREVIEW (rendered mode — iframe via /api/browser/render)
1091
+ ═══════════════════════════════════════════════════════════════════════════ */
1092
+
1093
+ /* Wrap is the scroll surface, iframe is sized to its content height by JS
1094
+ (see html-plugin.js — server posts scrollHeight, client sets iframe
1095
+ height to match). With iframe height === content height, the iframe has
1096
+ no internal overflow, so wheel/touch events fall through to this wrap.
1097
+ No `display: flex` here — that would collapse the iframe back to wrap
1098
+ height regardless of any height we set on it. No `position: absolute`
1099
+ either — that triggers a separate iOS bug where the iframe loses its
1100
+ internal scroll port (still relevant during the brief window before
1101
+ postMessage arrives). */
1102
+ .preview-html-body {
1103
+ overflow: auto;
1104
+ -webkit-overflow-scrolling: touch;
1105
+ touch-action: pan-x pan-y;
1106
+ min-height: 0;
1107
+ padding: 0;
1108
+ background: var(--bg-primary, #0b1220);
1109
+ }
1110
+
1111
+ .preview-html-frame {
1112
+ display: block;
1113
+ width: 100%;
1114
+ height: 100%; /* initial fill before the autoresize postMessage lands */
1115
+ border: none;
1116
+ background: var(--bg-primary, #0b1220);
1117
+ touch-action: auto;
1118
+ }
1119
+
1120
+ /* ═══════════════════════════════════════════════════════════════════════════
1121
+ EXCALIDRAW PREVIEW
1122
+ ═══════════════════════════════════════════════════════════════════════════ */
1123
+
1124
+ .preview-excalidraw-body {
1125
+ padding: 0;
1126
+ }
1127
+
1128
+ .excalidraw-canvas[data-bg="light"] {
1129
+ background: #f8f9fa;
1130
+ }
1131
+
1132
+ .excalidraw-canvas[data-bg="dark"] {
1133
+ background: var(--bg-primary, #0d1117);
1134
+ }
1135
+
1136
+ .excalidraw-preview-img {
1137
+ border-radius: var(--radius-sm);
1138
+ }
1139
+
1140
+ /* ═══════════════════════════════════════════════════════════════════════════
1141
+ TOAST
1142
+ ═══════════════════════════════════════════════════════════════════════════ */
1143
+
1144
+ .preview-toast {
1145
+ position: fixed;
1146
+ bottom: 80px;
1147
+ left: 50%;
1148
+ transform: translateX(-50%);
1149
+ padding: var(--sp-8) var(--sp-16);
1150
+ background: var(--surface-elevated, #333);
1151
+ color: var(--text-primary, #e0e0e0);
1152
+ border-radius: var(--radius-lg);
1153
+ font-size: var(--font-size-base);
1154
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
1155
+ z-index: var(--z-toast);
1156
+ animation: toast-in 0.2s ease-out;
1157
+ /* Failure toasts carry the server's reason (e.g. the writable-root list),
1158
+ so they can be a sentence — wrap instead of stretching off-screen. */
1159
+ max-width: min(560px, calc(100vw - var(--sp-32)));
1160
+ overflow-wrap: anywhere;
1161
+ text-align: center;
1162
+ }
1163
+
1164
+ @keyframes toast-in {
1165
+ from {
1166
+ opacity: 0;
1167
+ transform: translateX(-50%) translateY(10px);
1168
+ }
1169
+ to {
1170
+ opacity: 1;
1171
+ transform: translateX(-50%) translateY(0);
1172
+ }
1173
+ }
1174
+
1175
+ /* ═══════════════════════════════════════════════════════════════════════════
1176
+ WIDGET TYPE ADJUSTMENTS
1177
+ ═══════════════════════════════════════════════════════════════════════════ */
1178
+
1179
+ /* Bottom sheet mode */
1180
+ .widget-bottom-sheet .file-preview-widget {
1181
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
1182
+ }
1183
+
1184
+ .widget-bottom-sheet .preview-header {
1185
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
1186
+ }
1187
+
1188
+ /* Floating mode */
1189
+ .widget-floating .file-preview-widget {
1190
+ border-radius: var(--radius-md);
1191
+ }
1192
+
1193
+ .widget-floating .preview-header {
1194
+ border-radius: var(--radius-md) var(--radius-md) 0 0;
1195
+ }
1196
+
1197
+ /* Sidebar mode */
1198
+ .widget-sidebar-right .file-preview-widget {
1199
+ border-radius: 0;
1200
+ }
1201
+
1202
+ /* ═══════════════════════════════════════════════════════════════════════════
1203
+ SYNTAX HIGHLIGHTING (hljs theme - One Dark inspired)
1204
+ ═══════════════════════════════════════════════════════════════════════════ */
1205
+
1206
+ /* Keywords, built-ins */
1207
+ .preview-code .hljs-keyword,
1208
+ .preview-code .hljs-selector-tag,
1209
+ .preview-code .hljs-built_in,
1210
+ .preview-code .hljs-meta {
1211
+ color: #c678dd;
1212
+ }
1213
+
1214
+ /* Strings, attributes */
1215
+ .preview-code .hljs-string,
1216
+ .preview-code .hljs-attr,
1217
+ .preview-code .hljs-template-variable,
1218
+ .preview-code .hljs-addition {
1219
+ color: #98c379;
1220
+ }
1221
+
1222
+ /* Numbers, literals, booleans */
1223
+ .preview-code .hljs-number,
1224
+ .preview-code .hljs-literal,
1225
+ .preview-code .hljs-symbol,
1226
+ .preview-code .hljs-bullet {
1227
+ color: #d19a66;
1228
+ }
1229
+
1230
+ /* Comments */
1231
+ .preview-code .hljs-comment,
1232
+ .preview-code .hljs-quote,
1233
+ .preview-code .hljs-deletion {
1234
+ color: #5c6370;
1235
+ font-style: italic;
1236
+ }
1237
+
1238
+ /* Functions, titles */
1239
+ .preview-code .hljs-function,
1240
+ .preview-code .hljs-title,
1241
+ .preview-code .hljs-section {
1242
+ color: #61afef;
1243
+ }
1244
+
1245
+ /* Variables, parameters, tags */
1246
+ .preview-code .hljs-variable,
1247
+ .preview-code .hljs-params,
1248
+ .preview-code .hljs-tag,
1249
+ .preview-code .hljs-name {
1250
+ color: #e06c75;
1251
+ }
1252
+
1253
+ /* Types, classes */
1254
+ .preview-code .hljs-type,
1255
+ .preview-code .hljs-class,
1256
+ .preview-code .hljs-title\.class_ {
1257
+ color: #e5c07b;
1258
+ }
1259
+
1260
+ /* Template strings/subst */
1261
+ .preview-code .hljs-subst {
1262
+ color: #abb2bf;
1263
+ }
1264
+
1265
+ /* Punctuation, operators */
1266
+ .preview-code .hljs-punctuation {
1267
+ color: #abb2bf;
1268
+ }
1269
+
1270
+ /* ═══════════════════════════════════════════════════════════════════════════
1271
+ RESPONSIVE
1272
+ ═══════════════════════════════════════════════════════════════════════════ */
1273
+
1274
+ @media (max-width: 600px) {
1275
+ .preview-header {
1276
+ padding: var(--sp-6) var(--sp-10);
1277
+ }
1278
+
1279
+ .preview-filename {
1280
+ font-size: var(--font-size-sm);
1281
+ }
1282
+
1283
+ .preview-code {
1284
+ font-size: var(--font-size-xs);
1285
+ }
1286
+
1287
+ .line-number {
1288
+ width: 36px;
1289
+ font-size: 10px;
1290
+ }
1291
+ }
1292
+
1293
+ /* ═══════════════════════════════════════════════════════════════════════════ */
1294
+ /* EDIT MODE */
1295
+ /* ═══════════════════════════════════════════════════════════════════════════ */
1296
+
1297
+ .preview-edit-btn {
1298
+ display: inline-flex;
1299
+ align-items: center;
1300
+ gap: var(--sp-4);
1301
+ padding: var(--sp-4) var(--sp-10);
1302
+ border: 1px solid var(--border);
1303
+ border-radius: var(--radius-sm);
1304
+ background: var(--bg-secondary);
1305
+ color: var(--text);
1306
+ font-size: var(--font-size-sm);
1307
+ cursor: pointer;
1308
+ white-space: nowrap;
1309
+ }
1310
+
1311
+ .preview-edit-btn svg {
1312
+ width: 14px;
1313
+ height: 14px;
1314
+ }
1315
+
1316
+ .preview-edit-btn.save-btn {
1317
+ background: var(--accent);
1318
+ color: #fff;
1319
+ border-color: var(--accent);
1320
+ }
1321
+
1322
+ .preview-edit-btn.save-btn:hover {
1323
+ opacity: 0.9;
1324
+ }
1325
+
1326
+ .preview-edit-btn.save-btn:disabled {
1327
+ opacity: 0.5;
1328
+ cursor: not-allowed;
1329
+ }
1330
+
1331
+ .preview-edit-btn.save-as-btn {
1332
+ background: var(--accent);
1333
+ color: #fff;
1334
+ border-color: var(--accent);
1335
+ }
1336
+
1337
+ .preview-edit-btn.save-as-btn:hover {
1338
+ opacity: 0.9;
1339
+ }
1340
+
1341
+ /* Modified indicator dot (inline in toolbar) */
1342
+ .preview-modified-dot {
1343
+ width: 8px;
1344
+ height: 8px;
1345
+ border-radius: 50%;
1346
+ background: var(--warning, #f5a623);
1347
+ opacity: 0;
1348
+ transition: opacity 0.2s;
1349
+ }
1350
+
1351
+ .preview-modified-dot.visible {
1352
+ opacity: 1;
1353
+ }
1354
+
1355
+ /* Edit tab modified indicator (orange dot after label) */
1356
+ .toggle-btn[data-mode="edit"].modified {
1357
+ position: relative;
1358
+ }
1359
+
1360
+ .toggle-btn[data-mode="edit"].modified::after {
1361
+ content: '';
1362
+ width: 6px;
1363
+ height: 6px;
1364
+ border-radius: 50%;
1365
+ background: var(--warning, #f5a623);
1366
+ margin-left: var(--sp-4);
1367
+ flex-shrink: 0;
1368
+ }
1369
+
1370
+ /* CodeMirror container */
1371
+ .preview-edit-body {
1372
+ flex: 1;
1373
+ min-height: 0;
1374
+ overflow: hidden;
1375
+ }
1376
+
1377
+ .preview-cm-container {
1378
+ height: 100%;
1379
+ overflow: hidden;
1380
+ }
1381
+
1382
+ .preview-cm-container .cm-editor {
1383
+ height: 100%;
1384
+ }
1385
+
1386
+ /* Match code-preview font metrics (.preview-code) so Code ⇄ Edit doesn't
1387
+ shift size — CM otherwise inherits the widget's base font size. Family is
1388
+ left alone: CM's default scroller stack is the same generic monospace the
1389
+ preview <pre> uses. */
1390
+ .preview-cm-container .cm-scroller {
1391
+ overflow: auto;
1392
+ font-size: var(--font-size-sm);
1393
+ line-height: 1.5;
1394
+ }
1395
+
1396
+ /* Override One Dark theme background to match app */
1397
+ .preview-cm-container .cm-editor,
1398
+ .preview-cm-container .cm-gutters {
1399
+ background-color: var(--bg-primary);
1400
+ }
1401
+
1402
+ /* One Dark's base foreground is #abb2bf — reads as white→grey next to the
1403
+ preview's --text-primary. Override the base; token colors still win via
1404
+ their own span rules (inheritance, not specificity). */
1405
+ .preview-cm-container .cm-content {
1406
+ color: var(--text-primary);
1407
+ /* Mirror .preview-code's `padding: var(--sp-8) 0` — CM's base theme uses
1408
+ a fixed 4px, which made the text sit a few px higher in Edit mode. */
1409
+ padding: var(--sp-8) 0;
1410
+ }
1411
+
1412
+ /* Line-number gutter mirrors the preview's .line-number column:
1413
+ 48px right-aligned, --sp-12 gap to the code, xs font in --text-muted.
1414
+ Line-height is pinned to the code row height (sm × 1.5) so the smaller
1415
+ numbers stay vertically centered against their lines. */
1416
+ .preview-cm-container .cm-lineNumbers .cm-gutterElement {
1417
+ min-width: 48px;
1418
+ padding: 0 var(--sp-12) 0 0;
1419
+ font-size: var(--font-size-xs);
1420
+ line-height: calc(var(--font-size-sm) * 1.5);
1421
+ }
1422
+
1423
+ /* Fold gutter pinned to the same 14px column as the preview's .preview-fold
1424
+ cell, so toggling Code ⇄ Edit doesn't shift the code horizontally. */
1425
+ .preview-cm-container .cm-foldGutter .cm-gutterElement {
1426
+ width: 14px;
1427
+ text-align: center;
1428
+ }
1429
+
1430
+ .preview-cm-container .cm-activeLine,
1431
+ .preview-cm-container .cm-activeLineGutter {
1432
+ background-color: rgba(255, 255, 255, 0.03);
1433
+ }
1434
+
1435
+ .preview-cm-container .cm-loading {
1436
+ display: flex;
1437
+ align-items: center;
1438
+ justify-content: center;
1439
+ height: 100%;
1440
+ color: var(--text-muted);
1441
+ font-size: var(--font-size-base);
1442
+ }
1443
+
1444
+ /* History view (shadow-git diff browser) */
1445
+ .preview-history-body {
1446
+ padding: 0;
1447
+ }
1448
+
1449
+ .preview-history-content {
1450
+ padding: 0 0 var(--sp-12);
1451
+ }
1452
+
1453
+ .preview-history-empty {
1454
+ display: flex;
1455
+ flex-direction: column;
1456
+ align-items: center;
1457
+ justify-content: center;
1458
+ gap: var(--sp-12);
1459
+ padding: var(--sp-48) var(--sp-16);
1460
+ color: var(--text-secondary);
1461
+ font-size: var(--font-size-base);
1462
+ }
1463
+
1464
+ /* Mode-toggle button in the history bar — slimmer than other dv-pivots */
1465
+ .dv-pivot.ph-mode-toggle {
1466
+ font-family: var(--font-mono);
1467
+ min-width: 60px;
1468
+ text-align: center;
1469
+ }
1470
+
1471
+ /* Wrap-toggle button in the history bar — icon-only */
1472
+ .dv-pivot.ph-wrap-toggle {
1473
+ display: inline-flex;
1474
+ align-items: center;
1475
+ justify-content: center;
1476
+ padding: var(--sp-4) var(--sp-6);
1477
+ }
1478
+
1479
+ .dv-pivot.ph-wrap-toggle svg {
1480
+ width: 14px;
1481
+ height: 14px;
1482
+ }
1483
+
1484
+ /* ── History wrap mode ──────────────────────────────────────────────────────
1485
+ The History tab renders through the shared diff helpers, so it inherits the
1486
+ nowrap defaults from two other features: `.sbs-text` (67-diff-viewer.css) in
1487
+ split mode and `.diff-text` (22-edit-diff.css) in unified. Both files already
1488
+ ship wrap overrides, but gated on ancestors this DOM never has
1489
+ (`.diff-viewer-content.wrap-lines`, `.edit-diff.wrapped`) — so the rules are
1490
+ restated here against our own container rather than retrofitting those
1491
+ selectors onto a widget that doesn't own them. */
1492
+
1493
+ /* Split mode */
1494
+ .preview-history-content.wrap-lines .sbs-hunk { overflow-x: hidden; }
1495
+ .preview-history-content.wrap-lines .sbs-diff-grid {
1496
+ grid-template-columns: 40px minmax(0, 1fr) 4px 40px minmax(0, 1fr);
1497
+ }
1498
+ .preview-history-content.wrap-lines .sbs-text {
1499
+ white-space: pre-wrap;
1500
+ word-break: break-word;
1501
+ overflow-wrap: anywhere;
1502
+ }
1503
+
1504
+ /* Unified mode — `.diff-line` is a flexbox, so the text cell also needs to be
1505
+ allowed to shrink below its content width for wrapping to take effect. */
1506
+ .preview-history-content.wrap-lines .diff-hunk { overflow-x: hidden; }
1507
+ .preview-history-content.wrap-lines .diff-hunk-inner { min-width: 0; }
1508
+ .preview-history-content.wrap-lines .diff-text {
1509
+ white-space: pre-wrap;
1510
+ word-break: break-word;
1511
+ overflow-wrap: anywhere;
1512
+ flex: 1;
1513
+ min-width: 0;
1514
+ }
1515
+
1516
+ /* Textarea fallback when CodeMirror fails to load */
1517
+ .preview-edit-fallback {
1518
+ width: 100%;
1519
+ height: 100%;
1520
+ background: var(--bg-primary, #1e1e1e);
1521
+ color: var(--text-primary, #d4d4d4);
1522
+ border: none;
1523
+ outline: none;
1524
+ resize: none;
1525
+ padding: var(--sp-8) var(--sp-12);
1526
+ font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
1527
+ font-size: var(--font-size-base);
1528
+ line-height: 1.5;
1529
+ tab-size: 4;
1530
+ white-space: pre;
1531
+ overflow: auto;
1532
+ }
1533
+
1534
+ /* Save button in toolbar (edit mode) */
1535
+ .preview-toolbar .save-btn {
1536
+ margin-left: var(--sp-4);
1537
+ }
1538
+
1539
+ /* ═══════════════════════════════════════════════════════════════════════════
1540
+ FILE CHANGED NOTIFICATION BAR
1541
+ ═══════════════════════════════════════════════════════════════════════════ */
1542
+
1543
+ .preview-changed-bar {
1544
+ display: flex;
1545
+ align-items: center;
1546
+ gap: var(--sp-8);
1547
+ padding: var(--sp-6) var(--sp-12);
1548
+ background: rgba(180, 140, 50, 0.15);
1549
+ border-bottom: 1px solid rgba(180, 140, 50, 0.3);
1550
+ font-size: var(--font-size-sm);
1551
+ flex-shrink: 0;
1552
+ }
1553
+
1554
+ .preview-changed-text {
1555
+ color: #d4a94c;
1556
+ flex: 1;
1557
+ }
1558
+
1559
+ .preview-changed-btn {
1560
+ padding: var(--sp-2) var(--sp-10);
1561
+ border-radius: var(--radius-sm);
1562
+ border: 1px solid rgba(255, 255, 255, 0.15);
1563
+ background: rgba(255, 255, 255, 0.06);
1564
+ color: var(--text-secondary, #aaa);
1565
+ font-size: var(--font-size-xs);
1566
+ cursor: pointer;
1567
+ }
1568
+
1569
+ .preview-changed-btn:hover {
1570
+ background: rgba(255, 255, 255, 0.12);
1571
+ color: var(--text-primary, #e0e0e0);
1572
+ }
1573
+
1574
+ .preview-changed-btn.reload {
1575
+ border-color: rgba(180, 140, 50, 0.4);
1576
+ color: #d4a94c;
1577
+ }
1578
+
1579
+ .preview-changed-btn.reload:hover {
1580
+ background: rgba(180, 140, 50, 0.2);
1581
+ }
1582
+
1583
+ /* ═══════════════════════════════════════════════════════════════════════════
1584
+ INLINE EDIT MODE (rendered markdown)
1585
+ ═══════════════════════════════════════════════════════════════════════════ */
1586
+
1587
+ /* Toggle button — sits in the preview toolbar beside search/download, so it
1588
+ * matches those buttons' metrics rather than floating over the content. */
1589
+ .inline-edit-toggle {
1590
+ display: flex;
1591
+ align-items: center;
1592
+ justify-content: center;
1593
+ width: var(--layout-btn-md);
1594
+ height: var(--layout-btn-md);
1595
+ padding: 0;
1596
+ background: transparent;
1597
+ border: 1px solid var(--border-color, #444);
1598
+ border-radius: var(--radius-md);
1599
+ color: var(--text-secondary, #888);
1600
+ cursor: pointer;
1601
+ transition: all 0.15s ease;
1602
+ flex-shrink: 0;
1603
+ }
1604
+
1605
+ .inline-edit-toggle svg {
1606
+ width: 16px;
1607
+ height: 16px;
1608
+ }
1609
+
1610
+ .inline-edit-toggle:hover {
1611
+ background: var(--surface-hover, #2a2a2a);
1612
+ color: var(--text-primary, #e0e0e0);
1613
+ }
1614
+
1615
+ .inline-edit-toggle.active {
1616
+ background: rgba(var(--accent-rgb), 0.25);
1617
+ border-color: rgba(var(--accent-rgb), 0.6);
1618
+ color: var(--accent);
1619
+ box-shadow: 0 0 6px rgba(var(--accent-rgb), 0.3);
1620
+ }
1621
+
1622
+ .inline-edit-toggle.active:hover {
1623
+ background: rgba(var(--accent-rgb), 0.35);
1624
+ }
1625
+
1626
+ /* Inline edit mode: selectable elements become clickable */
1627
+ .preview-rendered.inline-edit-mode [data-selectable] {
1628
+ cursor: text;
1629
+ border-radius: var(--radius-sm);
1630
+ }
1631
+
1632
+ /* :not(.inline-editing) matters. This selector is (0,5,0) — :not() contributes its
1633
+ argument's specificity — and the edit-state rule below is (0,4,0), so while you
1634
+ edit with the pointer resting on the block, hover would otherwise win and quietly
1635
+ downgrade the solid edit border to the dashed hover one — the state you were IN
1636
+ would look like the state you were merely pointing at.
1637
+ Note the nested-selectable rule just below is ALSO (0,5,0) and beats this one on
1638
+ source order alone; keep it after this rule. */
1639
+ .preview-rendered.inline-edit-mode [data-selectable]:not(.inline-editing):hover {
1640
+ background: rgba(var(--accent-rgb), 0.08);
1641
+ outline: 1px dashed rgba(var(--accent-rgb), 0.25);
1642
+ outline-offset: var(--sp-3);
1643
+ }
1644
+
1645
+ /* Nested selectables (e.g. sentence span inside a marked paragraph) are inert.
1646
+ * Outer block owns the edit; no hover outline, no trash. */
1647
+ .preview-rendered.inline-edit-mode [data-selectable].inline-edit-nested:hover {
1648
+ background: transparent;
1649
+ outline: none;
1650
+ }
1651
+
1652
+ /* Hide stash icons and suppress stash hover in inline edit mode */
1653
+ .preview-rendered.inline-edit-mode [data-selectable] .stash-bubble {
1654
+ display: none !important;
1655
+ }
1656
+
1657
+ /* Selectable blocks in edit mode need positioning context for the trash button.
1658
+ The button's footprint is declared here, once: every consumer (the button's own
1659
+ box, and the right-hand clearance the edit textarea has to leave for it) derives
1660
+ from these, so they cannot drift apart the way a hardcoded 34px did. */
1661
+ .preview-rendered.inline-edit-mode [data-selectable] {
1662
+ position: relative;
1663
+ --inline-edit-trash-size: 24px;
1664
+ --inline-edit-trash-inset: 2px;
1665
+ --inline-edit-trash-gutter: calc(var(--inline-edit-trash-size) + var(--inline-edit-trash-inset));
1666
+ }
1667
+
1668
+ /* Element being edited — has to read as unmistakably live.
1669
+ The breathing room around the highlight is padded OUTWARD, never inward: a
1670
+ spread box-shadow in the same colour as the background paints a ring outside
1671
+ the border box, and outline-offset pushes the border out to meet it. Visually
1672
+ identical to a padded box; structurally free, because box-shadow and
1673
+ outline-offset both paint outside the layout box and displace nothing.
1674
+ That distinction is the whole point. Real padding here (the original 6px/8px
1675
+ on the textarea) buys the same look by pushing the text you were mid-sentence
1676
+ on, so the act of starting an edit moved the thing you were reading.
1677
+ Sets no padding/margin of its own — the block keeps the exact box it had while
1678
+ rendered (.markdown-content [data-selectable] padding-right, li's narrower
1679
+ variant, its own margins), so flipping modes moves no character.
1680
+ list-style is the one exception: an editing <li> shows its raw source, which
1681
+ already begins with the literal "- " / "1. " marker, so leaving the CSS ::marker
1682
+ on would render it twice ("• - item"). Suppressing it moves nothing, because
1683
+ list-style-position is outside — the marker never occupied the content box. */
1684
+ .preview-rendered.inline-edit-mode [data-selectable].inline-editing {
1685
+ --inline-edit-halo: var(--sp-6);
1686
+ background: rgba(var(--accent-rgb), 0.11);
1687
+ box-shadow: 0 0 0 var(--inline-edit-halo) rgba(var(--accent-rgb), 0.11);
1688
+ outline: 1px solid rgba(var(--accent-rgb), 0.6);
1689
+ outline-offset: var(--inline-edit-halo);
1690
+ border-radius: var(--radius-sm);
1691
+ list-style: none;
1692
+ }
1693
+
1694
+ /* Trash button — persistent on each selectable, hidden until hover (in edit mode). */
1695
+ .inline-edit-trash-btn {
1696
+ position: absolute;
1697
+ top: var(--inline-edit-trash-inset, 2px);
1698
+ right: var(--inline-edit-trash-inset, 2px);
1699
+ z-index: 20;
1700
+ display: none;
1701
+ align-items: center;
1702
+ justify-content: center;
1703
+ width: var(--inline-edit-trash-size, 24px);
1704
+ height: var(--inline-edit-trash-size, 24px);
1705
+ background: var(--bg-tertiary);
1706
+ border: 1px solid var(--border-primary);
1707
+ border-radius: var(--radius-sm);
1708
+ color: var(--text-muted);
1709
+ cursor: pointer;
1710
+ padding: 0;
1711
+ opacity: 0;
1712
+ transition: opacity 0.12s, background 0.12s, color 0.12s, border-color 0.12s;
1713
+ }
1714
+
1715
+ .inline-edit-trash-btn svg {
1716
+ display: block;
1717
+ pointer-events: none;
1718
+ }
1719
+
1720
+ .preview-rendered.inline-edit-mode [data-selectable]:hover > .inline-edit-trash-btn,
1721
+ .preview-rendered.inline-edit-mode [data-selectable].inline-editing > .inline-edit-trash-btn {
1722
+ display: flex;
1723
+ opacity: 1;
1724
+ }
1725
+
1726
+ .inline-edit-trash-btn:hover {
1727
+ background: rgba(239, 68, 68, 0.18);
1728
+ border-color: rgba(239, 68, 68, 0.55);
1729
+ color: #ef4444;
1730
+ }
1731
+
1732
+ /* In-place placeholder shown where a deleted block used to be */
1733
+ .inline-edit-placeholder {
1734
+ display: flex;
1735
+ align-items: center;
1736
+ gap: var(--sp-10);
1737
+ padding: var(--sp-6) var(--sp-10);
1738
+ margin: var(--sp-4) 0;
1739
+ background: rgba(239, 68, 68, 0.06);
1740
+ border: 1px dashed rgba(239, 68, 68, 0.35);
1741
+ border-radius: var(--radius-sm);
1742
+ font-size: var(--font-size-sm);
1743
+ color: var(--text-muted, #888);
1744
+ font-weight: normal;
1745
+ }
1746
+
1747
+ .inline-edit-placeholder-label {
1748
+ font-style: italic;
1749
+ }
1750
+
1751
+ .inline-edit-restore-btn {
1752
+ display: inline-flex;
1753
+ align-items: center;
1754
+ gap: var(--sp-4);
1755
+ background: transparent;
1756
+ border: none;
1757
+ color: var(--accent);
1758
+ font-size: var(--font-size-sm);
1759
+ font-weight: 500;
1760
+ cursor: pointer;
1761
+ padding: var(--sp-2) var(--sp-8);
1762
+ border-radius: var(--radius-sm);
1763
+ transition: background 0.12s;
1764
+ }
1765
+
1766
+ .inline-edit-restore-btn:hover {
1767
+ background: rgba(var(--accent-rgb), 0.15);
1768
+ text-decoration: underline;
1769
+ }
1770
+
1771
+ .inline-edit-restore-btn svg {
1772
+ pointer-events: none;
1773
+ }
1774
+
1775
+ /* Inline edit textarea — genuinely blends with the rendered view.
1776
+ Every typographic property is inherited from the block being replaced, so the
1777
+ raw markdown lands on the same baseline, in the same face, at the same size as
1778
+ the prose it stands in for. A heading picks up its own 1.4em/600, a list item
1779
+ its own metrics — no per-element rules needed. Height is JS-driven off
1780
+ scrollHeight (autoResize), so any font-size/line-height/padding deviation here
1781
+ compounds into a visible vertical jump: keep this list inheriting.
1782
+ Padding is 0 on three sides — top/left padding is exactly what shifts glyphs,
1783
+ and the original 6px/8px was the shift this rule exists to remove. The right
1784
+ side still has to be reserved: the trash button is force-shown while editing
1785
+ and is opaque, and the block's own padding-right (--sp-24, --sp-20 on li, 0 on
1786
+ a table row) is SMALLER than the button's footprint in every density — 24px vs
1787
+ 26px at normal, 15px vs 26px at compact — so relying on it buries the tail of
1788
+ the first line under the button, where a click deletes the block instead of
1789
+ placing the caret. Reserve only the SHORTFALL, not the whole footprint: over-
1790
+ reserving would wrap the raw markdown earlier than the rendered prose and cost
1791
+ a whole extra line on long paragraphs, which is the same layout jump by another
1792
+ route. padding-RIGHT displaces no character, so this costs width and nothing
1793
+ else. (Yes, this couples to the padding-right tokens in 30-markdown.css — that
1794
+ coupling is real either way; naming it beats hiding it behind a bare 34px.) */
1795
+ .inline-edit-textarea {
1796
+ display: block;
1797
+ width: 100%;
1798
+ padding: 0 max(0px, calc(var(--inline-edit-trash-gutter, 26px) - var(--sp-24))) 0 0;
1799
+ margin: 0;
1800
+ background: transparent;
1801
+ border: none;
1802
+ color: inherit;
1803
+ font: inherit;
1804
+ letter-spacing: inherit;
1805
+ /* Accent caret: with the typography now identical to the rendered prose, the
1806
+ blinking caret is the clearest signal that this block is text you can type
1807
+ into rather than text you are looking at. */
1808
+ caret-color: var(--accent);
1809
+ resize: none;
1810
+ overflow: hidden;
1811
+ outline: none;
1812
+ box-sizing: border-box;
1813
+ }
1814
+
1815
+ /* A list item carries a narrower gutter than everything else, so it needs a
1816
+ correspondingly wider reservation. Same arithmetic, different token. */
1817
+ .preview-rendered.inline-edit-mode li[data-selectable] > .inline-edit-textarea {
1818
+ padding-right: max(0px, calc(var(--inline-edit-trash-gutter, 26px) - var(--sp-20)));
1819
+ }
1820
+
1821
+ /* No :focus ring on the textarea — the block's own .inline-editing outline already
1822
+ marks what's being edited, and a second ring hugging zero-padding text doubled up. */
1823
+
1824
+ /* Hint text — Enter to save, Esc to cancel.
1825
+ Taken out of flow (absolute, pinned just under the block) so showing it reflows
1826
+ nothing: previously an in-flow ::before with order:2 pushed every following
1827
+ paragraph down the moment an edit started. pointer-events:none keeps it from
1828
+ swallowing clicks on whatever it overlays.
1829
+ Scoped to .inline-edit-mode like every other rule in this state set — it reads
1830
+ --inline-edit-halo, which only the edit-state rule declares, so matching a wider
1831
+ set of elements would only ever render the chip detached from the halo it is
1832
+ meant to straddle. */
1833
+ .preview-rendered.inline-edit-mode [data-selectable].inline-editing::before {
1834
+ /* Literal glyphs, not \2192 / \00b7 escapes: CSS consumes the whitespace that
1835
+ terminates a hex escape, so `\2192 save` rendered as "→save". */
1836
+ content: 'Enter → save · Esc → cancel · Shift+Enter → newline';
1837
+ position: absolute;
1838
+ /* Straddle the halo's bottom edge, flush right. Centring it on that edge parks
1839
+ as much of the chip as will fit in the halo + the block's own bottom margin
1840
+ — dead space — rather than dropping it a full line lower. It still clips the
1841
+ top of the following line; that is the accepted cost of not displacing it.
1842
+ The offset is half of a ONE-line chip, not -50% of the actual box: once the
1843
+ chip wraps in a narrow pane, -50% would grow half the extra height UPWARD,
1844
+ over the caret line of the very block you are typing in. Anchored this way,
1845
+ wrapped lines only ever extend downward, into space the design already
1846
+ accepts covering. */
1847
+ --inline-edit-hint-line: calc(1.4 * var(--font-size-xs) + 2 * var(--sp-2) + 2px);
1848
+ top: calc(100% + var(--inline-edit-halo, var(--sp-6)));
1849
+ right: calc(-1 * var(--inline-edit-halo, var(--sp-6)));
1850
+ transform: translateY(calc(-0.5 * var(--inline-edit-hint-line)));
1851
+ /* Above the following block's prose (which is z-index: auto) so the chip
1852
+ occludes rather than overprints, but BELOW the trash buttons at 20 — the
1853
+ chip shares their x-column, and a hint must never hide the control the user
1854
+ is reaching for. */
1855
+ z-index: var(--z-internal);
1856
+ font-size: var(--font-size-xs);
1857
+ line-height: 1.4;
1858
+ color: var(--text-muted);
1859
+ /* Opaque chip, right-aligned: since it floats over the following paragraph
1860
+ instead of displacing it, it has to occlude that text rather than blend
1861
+ into it — a translucent hint here renders as two strings of prose
1862
+ overprinted on each other.
1863
+ It wraps rather than running off: the chip is anchored by its RIGHT edge, so
1864
+ a nowrap chip grows leftward, and left overflow is not scrollable overflow in
1865
+ LTR — in a narrow pane the "Enter -> save" half was clipped away permanently,
1866
+ hiding the one key that commits. */
1867
+ background: var(--bg-tertiary);
1868
+ border: 1px solid var(--border-primary);
1869
+ border-radius: var(--radius-sm);
1870
+ padding: var(--sp-2) var(--sp-6);
1871
+ pointer-events: none;
1872
+ max-width: calc(100% + 2 * var(--inline-edit-halo, var(--sp-6)));
1873
+ }