@webship.co/workspace 1.0.0-alpha1 → 1.0.0-beta2

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 (634) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +28 -364
  3. package/core/bin/workspace.js +135 -0
  4. package/core/config/settings.labels.yml +514 -0
  5. package/core/config/settings.template.yml +292 -0
  6. package/core/config/style.yml +53 -0
  7. package/core/config/theme.default.yml +161 -0
  8. package/core/config/theme.dispatch.yml +166 -0
  9. package/core/config/theme.drupal.yml +175 -0
  10. package/core/config/theme.ember.yml +160 -0
  11. package/core/config/theme.github.yml +172 -0
  12. package/core/config/theme.gitlab.yml +168 -0
  13. package/core/config/theme.press.yml +161 -0
  14. package/core/config/theme.slate.yml +160 -0
  15. package/core/config/theme.upsun.yml +174 -0
  16. package/core/docs/architecture.svg +65 -0
  17. package/core/docs/getting-started.md +51 -0
  18. package/core/docs/plugins.md +27 -0
  19. package/core/docs/releases.md +23 -0
  20. package/core/docs/testing.md +15 -0
  21. package/core/docs/workspaces.md +25 -0
  22. package/core/scripts/args/arg-ai-items.sh +65 -0
  23. package/core/scripts/args/arg-autostart.sh +31 -0
  24. package/core/scripts/args/arg-avatar-agent.sh +28 -0
  25. package/core/scripts/args/arg-avatar-catalog.sh +12 -0
  26. package/core/scripts/args/arg-avatar-character.sh +68 -0
  27. package/core/scripts/args/arg-avatar-voice.sh +24 -0
  28. package/core/scripts/args/arg-avatar.sh +22 -0
  29. package/core/scripts/args/arg-checks.sh +36 -0
  30. package/core/scripts/args/arg-clone.sh +42 -0
  31. package/core/scripts/args/arg-commands.sh +51 -0
  32. package/core/scripts/args/arg-cucumber.sh +34 -0
  33. package/core/scripts/args/arg-distribution.sh +30 -0
  34. package/core/scripts/args/arg-drupal.sh +39 -0
  35. package/core/scripts/args/arg-drupal_cms.sh +18 -0
  36. package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
  37. package/core/scripts/args/arg-graphify.sh +36 -0
  38. package/core/scripts/args/arg-graphs.sh +12 -0
  39. package/core/scripts/args/arg-hostnames.sh +23 -0
  40. package/core/scripts/args/arg-milvus-backup.sh +8 -0
  41. package/core/scripts/args/arg-milvus-mcp.sh +12 -0
  42. package/core/scripts/args/arg-milvus.sh +32 -0
  43. package/core/scripts/args/arg-obsidian.sh +17 -0
  44. package/core/scripts/args/arg-odoo.sh +18 -0
  45. package/core/scripts/args/arg-penpot-backup.sh +8 -0
  46. package/core/scripts/args/arg-penpot-mcp.sh +15 -0
  47. package/core/scripts/args/arg-penpot-token.sh +17 -0
  48. package/core/scripts/args/arg-penpot.sh +44 -0
  49. package/core/scripts/args/arg-rag.sh +21 -0
  50. package/core/scripts/args/arg-ragify.sh +35 -0
  51. package/core/scripts/args/arg-reclaim.sh +18 -0
  52. package/core/scripts/args/arg-release.sh +21 -0
  53. package/core/scripts/args/arg-screen.sh +28 -0
  54. package/core/scripts/args/arg-site-template.sh +13 -0
  55. package/core/scripts/args/arg-spdd.sh +16 -0
  56. package/core/scripts/args/arg-specs.sh +22 -0
  57. package/core/scripts/args/arg-storage.sh +36 -0
  58. package/core/scripts/args/arg-testing.sh +36 -0
  59. package/core/scripts/args/arg-webship.sh +34 -0
  60. package/core/scripts/args/arg-webships-project.sh +22 -0
  61. package/core/scripts/args/arg-webships-site-template.sh +17 -0
  62. package/core/scripts/args/arg-webships.sh +34 -0
  63. package/core/scripts/args/arg-website-site-template.sh +17 -0
  64. package/core/scripts/args/arg-website.sh +25 -0
  65. package/core/scripts/args/arg-wordpress.sh +18 -0
  66. package/core/scripts/autostart-run.sh +162 -0
  67. package/core/scripts/autostart-stop.sh +30 -0
  68. package/core/scripts/autostart-watch.sh +43 -0
  69. package/core/scripts/autostart.sh +26 -0
  70. package/core/scripts/bootstrap-functions.sh +117 -0
  71. package/core/scripts/bootstrap-libraries.sh +10 -0
  72. package/core/scripts/bootstrap.sh +71 -0
  73. package/core/scripts/ddev-shim/ddev +122 -0
  74. package/core/scripts/docker-gid.sh +119 -0
  75. package/core/scripts/functions/arg-site-template.sh +28 -0
  76. package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
  77. package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
  78. package/core/scripts/functions/fun-ai-items.sh +621 -0
  79. package/core/scripts/functions/fun-ai-providers.sh +72 -0
  80. package/core/scripts/functions/fun-autostart.sh +429 -0
  81. package/core/scripts/functions/fun-avatar.sh +441 -0
  82. package/core/scripts/functions/fun-backup-item.sh +50 -0
  83. package/core/scripts/functions/fun-build-distribution.sh +226 -0
  84. package/core/scripts/functions/fun-build-site-template.sh +104 -0
  85. package/core/scripts/functions/fun-build-webships.sh +161 -0
  86. package/core/scripts/functions/fun-build-website.sh +199 -0
  87. package/core/scripts/functions/fun-checks.sh +243 -0
  88. package/core/scripts/functions/fun-clone-project.sh +289 -0
  89. package/core/scripts/functions/fun-commands.sh +521 -0
  90. package/core/scripts/functions/fun-database.sh +63 -0
  91. package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
  92. package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
  93. package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
  94. package/core/scripts/functions/fun-default-settings.sh +28 -0
  95. package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
  96. package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
  97. package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
  98. package/core/scripts/functions/fun-distribution-webship.sh +28 -0
  99. package/core/scripts/functions/fun-distribution-webships.sh +33 -0
  100. package/core/scripts/functions/fun-distribution-website.sh +19 -0
  101. package/core/scripts/functions/fun-drush.sh +48 -0
  102. package/core/scripts/functions/fun-elevenlabs.sh +179 -0
  103. package/core/scripts/functions/fun-files.sh +19 -0
  104. package/core/scripts/functions/fun-git-worktrees.sh +142 -0
  105. package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
  106. package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
  107. package/core/scripts/functions/fun-milvus.sh +584 -0
  108. package/core/scripts/functions/fun-odoo.sh +121 -0
  109. package/core/scripts/functions/fun-penpot.sh +724 -0
  110. package/core/scripts/functions/fun-project-name.sh +20 -0
  111. package/core/scripts/functions/fun-rag.sh +253 -0
  112. package/core/scripts/functions/fun-ragify.sh +165 -0
  113. package/core/scripts/functions/fun-reclaim.sh +297 -0
  114. package/core/scripts/functions/fun-release.sh +159 -0
  115. package/core/scripts/functions/fun-remove-project.sh +319 -0
  116. package/core/scripts/functions/fun-screen.sh +96 -0
  117. package/core/scripts/functions/fun-spdd.sh +676 -0
  118. package/core/scripts/functions/fun-specs-share.sh +237 -0
  119. package/core/scripts/functions/fun-storage.sh +347 -0
  120. package/core/scripts/functions/fun-testing.sh +329 -0
  121. package/core/scripts/functions/fun-users.sh +87 -0
  122. package/core/scripts/functions/fun-version.sh +72 -0
  123. package/core/scripts/functions/fun-video-edit.sh +48 -0
  124. package/core/scripts/functions/fun-voice.sh +45 -0
  125. package/core/scripts/functions/fun-wordpress.sh +59 -0
  126. package/core/scripts/host-mounts.sh +161 -0
  127. package/core/scripts/install.sh +602 -0
  128. package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
  129. package/core/scripts/libs/argparse.sh +93 -0
  130. package/core/scripts/libs/bash-yaml.sh +52 -0
  131. package/core/scripts/libs/checks_plan.js +312 -0
  132. package/core/scripts/libs/elevenlabs.py +627 -0
  133. package/core/scripts/libs/progress-bar.sh +19 -0
  134. package/core/scripts/libs/rag_index.py +446 -0
  135. package/core/scripts/libs/video_edit.py +636 -0
  136. package/core/scripts/libs/voice.py +430 -0
  137. package/core/scripts/plugins/jira/host-install.sh +119 -0
  138. package/core/scripts/plugins/jira/host-mounts.txt +2 -0
  139. package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
  140. package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
  141. package/core/scripts/plugins/upsun/host-install.sh +99 -0
  142. package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
  143. package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
  144. package/core/scripts/plugins.sh +152 -0
  145. package/core/scripts/release.sh +69 -0
  146. package/core/scripts/storage.sh +39 -0
  147. package/core/scripts/sync-tooling.sh +246 -0
  148. package/core/scripts/tests/cmd-smoke-test.sh +109 -0
  149. package/core/scripts/trust-https.sh +257 -0
  150. package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
  151. package/core/workspace-app/.ddev/config.yaml +400 -0
  152. package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
  153. package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
  154. package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
  155. package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
  156. package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
  157. package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
  158. package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
  159. package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +1 -1
  160. package/core/workspace-app/README.md +380 -0
  161. package/{actions → core/workspace-app/actions}/ddev.js +0 -1
  162. package/{actions → core/workspace-app/actions}/rag.js +0 -1
  163. package/{actions → core/workspace-app/actions}/testing.js +0 -1
  164. package/{assistant.js → core/workspace-app/assistant.js} +1 -10
  165. package/{git-info.js → core/workspace-app/git-info.js} +17 -1
  166. package/core/workspace-app/package.json +29 -0
  167. package/core/workspace-app/plugin-catalog.js +50 -0
  168. package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +19 -1
  169. package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
  170. package/{row-layout.js → core/workspace-app/row-layout.js} +0 -1
  171. package/{server.js → core/workspace-app/server.js} +26 -225
  172. package/core/workspace-app/tests/features/actions-rail.feature +44 -0
  173. package/core/workspace-app/tests/features/ai-items.feature +23 -0
  174. package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
  175. package/core/workspace-app/tests/features/checks.feature +39 -0
  176. package/core/workspace-app/tests/features/clone-project.feature +30 -0
  177. package/core/workspace-app/tests/features/command-authoring.feature +58 -0
  178. package/core/workspace-app/tests/features/dashboard.feature +57 -0
  179. package/core/workspace-app/tests/features/menus.feature +29 -0
  180. package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
  181. package/core/workspace-app/tests/features/plugins.feature +20 -0
  182. package/core/workspace-app/tests/features/project-actions.feature +40 -0
  183. package/core/workspace-app/tests/features/screens.feature +29 -0
  184. package/core/workspace-app/tests/features/settings.feature +46 -0
  185. package/core/workspace-app/tests/features/test-reports.feature +32 -0
  186. package/core/workspace-app/tests/features/theme.feature +40 -0
  187. package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
  188. package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +152 -0
  189. package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -0
  190. package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
  191. package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
  192. package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
  193. package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
  194. package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
  195. package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
  196. package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
  197. package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
  198. package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
  199. package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
  200. package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
  201. package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
  202. package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
  203. package/core/workspace-app/tests/unit/assistant.test.js +61 -0
  204. package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
  205. package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
  206. package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
  207. package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
  208. package/core/workspace-app/tests/unit/browsers.test.js +76 -0
  209. package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
  210. package/core/workspace-app/tests/unit/build-order.test.js +153 -0
  211. package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
  212. package/core/workspace-app/tests/unit/builders.test.js +158 -0
  213. package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
  214. package/core/workspace-app/tests/unit/changelog.test.js +177 -0
  215. package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
  216. package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
  217. package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
  218. package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
  219. package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
  220. package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
  221. package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
  222. package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
  223. package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
  224. package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
  225. package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
  226. package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
  227. package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
  228. package/core/workspace-app/tests/unit/create-card.test.js +85 -0
  229. package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
  230. package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
  231. package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
  232. package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
  233. package/core/workspace-app/tests/unit/ddev-domains.test.js +153 -0
  234. package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
  235. package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
  236. package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
  237. package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
  238. package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
  239. package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
  240. package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
  241. package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
  242. package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
  243. package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
  244. package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
  245. package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
  246. package/core/workspace-app/tests/unit/density.test.js +52 -0
  247. package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
  248. package/core/workspace-app/tests/unit/event-log.test.js +102 -0
  249. package/core/workspace-app/tests/unit/favicon.test.js +68 -0
  250. package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
  251. package/core/workspace-app/tests/unit/git-info.test.js +26 -0
  252. package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
  253. package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
  254. package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
  255. package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
  256. package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
  257. package/core/workspace-app/tests/unit/images-create.test.js +59 -0
  258. package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
  259. package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
  260. package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
  261. package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
  262. package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
  263. package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
  264. package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
  265. package/core/workspace-app/tests/unit/narration.test.js +186 -0
  266. package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
  267. package/core/workspace-app/tests/unit/navigation.test.js +254 -0
  268. package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
  269. package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
  270. package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
  271. package/core/workspace-app/tests/unit/notes.test.js +128 -0
  272. package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
  273. package/core/workspace-app/tests/unit/page-width.test.js +59 -0
  274. package/core/workspace-app/tests/unit/php-version.test.js +90 -0
  275. package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
  276. package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
  277. package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
  278. package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
  279. package/core/workspace-app/tests/unit/plugins.test.js +58 -0
  280. package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
  281. package/core/workspace-app/tests/unit/project-make.test.js +143 -0
  282. package/core/workspace-app/tests/unit/project-name.test.js +202 -0
  283. package/core/workspace-app/tests/unit/reader.test.js +62 -0
  284. package/core/workspace-app/tests/unit/release.test.js +92 -0
  285. package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
  286. package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
  287. package/core/workspace-app/tests/unit/round-11.test.js +31 -0
  288. package/core/workspace-app/tests/unit/round-12.test.js +37 -0
  289. package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
  290. package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
  291. package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
  292. package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
  293. package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
  294. package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
  295. package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
  296. package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
  297. package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
  298. package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
  299. package/core/workspace-app/tests/unit/screens.test.js +74 -0
  300. package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
  301. package/core/workspace-app/tests/unit/session-name.test.js +64 -0
  302. package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
  303. package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
  304. package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
  305. package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
  306. package/core/workspace-app/tests/unit/speech.test.js +290 -0
  307. package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
  308. package/core/workspace-app/tests/unit/status-language.test.js +47 -0
  309. package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
  310. package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
  311. package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
  312. package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
  313. package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
  314. package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
  315. package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
  316. package/core/workspace-app/tests/unit/themes.test.js +453 -0
  317. package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
  318. package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
  319. package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
  320. package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
  321. package/core/workspace-app/tests/unit/version-check.test.js +98 -0
  322. package/core/workspace-app/tests/unit/version-link.test.js +50 -0
  323. package/core/workspace-app/tests/unit/video-borrow.test.js +198 -0
  324. package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
  325. package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
  326. package/core/workspace-app/tests/unit/video-edit.test.js +417 -0
  327. package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
  328. package/core/workspace-app/tests/unit/waiting.test.js +48 -0
  329. package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
  330. package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
  331. package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
  332. package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
  333. package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
  334. package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
  335. package/{workspace-types.js → core/workspace-app/workspace-types.js} +2 -2
  336. package/{workspaces.js → core/workspace-app/workspaces.js} +1 -5
  337. package/package.json +38 -36
  338. package/actions/graphs.js +0 -107
  339. package/actions/obsidian.js +0 -59
  340. package/graphs.js +0 -104
  341. package/obsidian.js +0 -405
  342. /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
  343. /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
  344. /package/{actions → core/workspace-app/actions}/audio.js +0 -0
  345. /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
  346. /package/{actions → core/workspace-app/actions}/checks.js +0 -0
  347. /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
  348. /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
  349. /package/{actions → core/workspace-app/actions}/composer.js +0 -0
  350. /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
  351. /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
  352. /package/{actions → core/workspace-app/actions}/folders.js +0 -0
  353. /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
  354. /package/{actions → core/workspace-app/actions}/images.js +0 -0
  355. /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
  356. /package/{actions → core/workspace-app/actions}/node.js +0 -0
  357. /package/{actions → core/workspace-app/actions}/notes.js +0 -0
  358. /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
  359. /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
  360. /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
  361. /package/{actions → core/workspace-app/actions}/screens.js +0 -0
  362. /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
  363. /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
  364. /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
  365. /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
  366. /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
  367. /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
  368. /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
  369. /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
  370. /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
  371. /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
  372. /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
  373. /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
  374. /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
  375. /package/{checks.js → core/workspace-app/checks.js} +0 -0
  376. /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
  377. /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
  378. /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
  379. /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
  380. /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
  381. /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
  382. /package/{commands.js → core/workspace-app/commands.js} +0 -0
  383. /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
  384. /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
  385. /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
  386. /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
  387. /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
  388. /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
  389. /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
  390. /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
  391. /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
  392. /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
  393. /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
  394. /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
  395. /package/{docs → core/workspace-app/docs}/README.md +0 -0
  396. /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
  397. /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
  398. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
  399. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
  400. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
  401. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
  402. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
  403. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  404. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
  405. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
  406. /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
  407. /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
  408. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
  409. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
  410. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
  411. /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
  412. /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
  413. /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
  414. /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
  415. /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
  416. /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
  417. /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
  418. /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
  419. /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
  420. /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
  421. /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
  422. /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
  423. /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
  424. /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
  425. /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
  426. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
  427. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
  428. /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
  429. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
  430. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
  431. /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
  432. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
  433. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
  434. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
  435. /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
  436. /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
  437. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
  438. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
  439. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
  440. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
  441. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
  442. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
  443. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
  444. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
  445. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
  446. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
  447. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
  448. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
  449. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
  450. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
  451. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
  452. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
  453. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
  454. /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
  455. /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
  456. /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
  457. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
  458. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
  459. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
  460. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
  461. /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
  462. /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
  463. /package/{folders.js → core/workspace-app/folders.js} +0 -0
  464. /package/{format.js → core/workspace-app/format.js} +0 -0
  465. /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
  466. /package/{html.js → core/workspace-app/html.js} +0 -0
  467. /package/{icons.js → core/workspace-app/icons.js} +0 -0
  468. /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
  469. /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
  470. /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
  471. /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
  472. /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
  473. /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
  474. /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
  475. /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
  476. /package/{images.js → core/workspace-app/images.js} +0 -0
  477. /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
  478. /package/{linger.js → core/workspace-app/linger.js} +0 -0
  479. /package/{lists.js → core/workspace-app/lists.js} +0 -0
  480. /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
  481. /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
  482. /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
  483. /package/{notes.js → core/workspace-app/notes.js} +0 -0
  484. /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
  485. /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
  486. /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
  487. /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
  488. /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
  489. /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
  490. /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
  491. /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
  492. /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
  493. /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
  494. /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
  495. /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
  496. /package/{public → core/workspace-app/public}/css/base.css +0 -0
  497. /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
  498. /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
  499. /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
  500. /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
  501. /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
  502. /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
  503. /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
  504. /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
  505. /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
  506. /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
  507. /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
  508. /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
  509. /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
  510. /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
  511. /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
  512. /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
  513. /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
  514. /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
  515. /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
  516. /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
  517. /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
  518. /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
  519. /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
  520. /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
  521. /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
  522. /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
  523. /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
  524. /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
  525. /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
  526. /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
  527. /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
  528. /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
  529. /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
  530. /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
  531. /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
  532. /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
  533. /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
  534. /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
  535. /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
  536. /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
  537. /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
  538. /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
  539. /package/{public → core/workspace-app/public}/favicon.ico +0 -0
  540. /package/{public → core/workspace-app/public}/favicon.png +0 -0
  541. /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
  542. /package/{public → core/workspace-app/public}/logo.png +0 -0
  543. /package/{public → core/workspace-app/public}/media-player.js +0 -0
  544. /package/{public → core/workspace-app/public}/mic.js +0 -0
  545. /package/{public → core/workspace-app/public}/narration.js +0 -0
  546. /package/{public → core/workspace-app/public}/palette.js +0 -0
  547. /package/{public → core/workspace-app/public}/sortable.js +0 -0
  548. /package/{public → core/workspace-app/public}/suggest.js +0 -0
  549. /package/{public → core/workspace-app/public}/terminal.js +0 -0
  550. /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
  551. /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
  552. /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
  553. /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
  554. /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
  555. /package/{public → core/workspace-app/public}/ui.js +0 -0
  556. /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
  557. /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
  558. /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
  559. /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
  560. /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
  561. /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
  562. /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
  563. /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
  564. /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
  565. /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
  566. /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
  567. /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
  568. /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
  569. /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
  570. /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
  571. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
  572. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  573. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  574. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
  575. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  576. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  577. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  578. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  579. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
  580. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  581. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
  582. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  583. /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
  584. /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
  585. /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
  586. /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
  587. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
  588. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
  589. /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
  590. /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
  591. /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
  592. /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
  593. /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
  594. /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
  595. /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
  596. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
  597. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
  598. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
  599. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
  600. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
  601. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
  602. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
  603. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
  604. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
  605. /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
  606. /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
  607. /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
  608. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
  609. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
  610. /package/{rag.js → core/workspace-app/rag.js} +0 -0
  611. /package/{reader.js → core/workspace-app/reader.js} +0 -0
  612. /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
  613. /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
  614. /package/{screenrc → core/workspace-app/screenrc} +0 -0
  615. /package/{screens.js → core/workspace-app/screens.js} +0 -0
  616. /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
  617. /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
  618. /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
  619. /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
  620. /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
  621. /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
  622. /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
  623. /package/{speech.js → core/workspace-app/speech.js} +0 -0
  624. /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
  625. /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
  626. /package/{themes.js → core/workspace-app/themes.js} +0 -0
  627. /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
  628. /package/{urls.js → core/workspace-app/urls.js} +0 -0
  629. /package/{validate.js → core/workspace-app/validate.js} +0 -0
  630. /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
  631. /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
  632. /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
  633. /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
  634. /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Add a row and add a button, in the dashboard's one modal.
3
+ *
4
+ * Both were controls at the bottom of a very long form — the add-a-button one carried eight fields there. In the modal
5
+ * they have to keep two properties the inline versions got for free by sitting inside the rows form: the unsaved
6
+ * arrangement travels with the write (a drag not yet saved is part of what the layout should become), and the editor
7
+ * behind the modal is what answers.
8
+ */
9
+
10
+ const assert = require('node:assert');
11
+ const { test } = require('node:test');
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+ const path = require('path');
15
+
16
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rowsmodal-'));
17
+ process.env.ROW_ACTIONS_FILE = path.join(tmp, 'row-actions.yml');
18
+ process.env.ROW_ACTIONS_SEEN_FILE = path.join(tmp, 'row-actions.seen.json');
19
+ process.env.ROW_ACTIONS_HISTORY_FILE = path.join(tmp, 'row-actions.history.jsonl');
20
+ process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
21
+
22
+ const rl = require('../../row-layout');
23
+ const APP = path.join(__dirname, '..', '..');
24
+
25
+ test('the editor opens both in the modal, and nothing is left inline', () => {
26
+ const html = rl.rowActionsEditorHtml();
27
+ assert.match(html, /hx-get="\/fragments\/row\/add-row"[\s\S]{0,120}hx-target="#editor-modal-body"/);
28
+ assert.match(html, /hx-get="\/fragments\/row\/add-button"[\s\S]{0,120}hx-target="#editor-modal-body"/);
29
+ assert.doesNotMatch(html, /for_button":"__new__/, 'the add-a-button form is no longer in the page');
30
+ });
31
+
32
+ test('both forms carry the rows form, so an unsaved arrangement is not lost', () => {
33
+ const row = rl.addRowFormHtml();
34
+ assert.match(row, /hx-post="\/actions\/row-rows"/);
35
+ assert.match(row, /hx-include="#row-arrange-form"/);
36
+ assert.match(row, /hx-vals='\{"op":"add","pane":"0","mtime":"[0-9.]+"\}'/, 'and the mtime it was drawn with');
37
+ assert.match(row, /hx-target="#row-actions-editor" hx-swap="outerHTML"/);
38
+
39
+ const button = rl.addButtonFormHtml();
40
+ assert.match(button, /hx-post="\/actions\/row-custom-add"/);
41
+ assert.match(button, /hx-include="#row-arrange-form, closest \.row-add-action"/);
42
+ assert.match(button, /name="add_button_new"/, 'it still names the button');
43
+ assert.match(button, /name="add_label_new"/);
44
+ assert.match(button, /hx-target="#row-actions-editor" hx-swap="outerHTML"/);
45
+ });
46
+
47
+ test('a full set of rows says so instead of offering a button that would be refused', () => {
48
+ const rows = Array.from({ length: rl.MAX_ROWS }, () => []);
49
+ rl.saveRowLayout(rl.normalizeLayout({ rows }), '');
50
+ const html = rl.addRowFormHtml();
51
+ assert.match(html, new RegExp(`All ${rl.MAX_ROWS} rows are in use`));
52
+ assert.doesNotMatch(html, /hx-post="\/actions\/row-rows"/);
53
+ rl.resetRowLayout('');
54
+ });
55
+
56
+ test('the fragments are served, and the modal closes on the event that names what was swapped', () => {
57
+ const src = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
58
+ assert.match(src, /pathname === '\/fragments\/row\/add-row' \|\| pathname === '\/fragments\/row\/add-button'/);
59
+ assert.match(src, /rowLayout\.addRowFormHtml\(\) : rowLayout\.addButtonFormHtml\(\)/);
60
+
61
+ const ui = fs.readFileSync(path.join(APP, 'public', 'ui.js'), 'utf8');
62
+ const at = ui.indexOf("e.target.id !== 'row-actions-editor'");
63
+ assert.ok(at > 0, 'nothing closes the modal when the editor comes back');
64
+ const handler = ui.slice(ui.lastIndexOf('document.body.addEventListener', at), ui.indexOf('});', at));
65
+ assert.match(handler, /htmx:after:settle/, "after:swap names the element that MADE the request in htmx 4, not the swapped one");
66
+ assert.match(handler, /UIkit\.modal\('#editor-modal'\)\.hide\(\)/);
67
+ assert.doesNotMatch(rl.addRowFormHtml(), /event\.detail\.successful/, 'that flag does not exist in htmx 4');
68
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The host screen runner's request queue (scripts/screen-runner.js). The queue is drained on a poll and on
3
+ * every folder-watch event, and one new file fires the watch more than once, so two drains could read the
4
+ * same request before its unlink landed: a burst of 12 start requests made 14 sessions. What must never
5
+ * drift: a request is claimed by an atomic rename before it is read, and a session already being started
6
+ * is not started again. The runner starts servers and timers when loaded, so this reads its source.
7
+ */
8
+
9
+ const assert = require('node:assert');
10
+ const { test } = require('node:test');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const src = fs.readFileSync(path.join(__dirname, '..', '..', 'scripts', 'screen-runner.js'), 'utf8');
15
+ const fn = src.slice(src.indexOf('function handleRequest(file) {'), src.indexOf('function drainQueue() {'));
16
+
17
+ test('a queued request is claimed with a rename before it is read, and a lost claim does nothing', () => {
18
+ assert.ok(fn.length > 0, 'handleRequest was not found');
19
+ const claim = fn.indexOf('fs.renameSync(file, claimed)');
20
+ const read = fn.indexOf('fs.readFileSync(');
21
+ assert.ok(claim > 0 && read > claim, 'the request must be renamed before it is read');
22
+ assert.match(fn, /try \{ fs\.renameSync\(file, claimed\); \} catch \(_\) \{ return; \}/, 'a drain that loses the rename returns');
23
+ assert.ok(!/fs\.readFileSync\(file,/.test(fn), 'the unclaimed file is never read');
24
+ // A claimed file no longer ends in .json, so the next drain does not pick it up.
25
+ assert.match(src, /filter\(\(f\) => f\.endsWith\('\.json'\)\)/);
26
+ });
27
+
28
+ test('a session already being started is answered "already", and the mark is cleared on every way out', () => {
29
+ assert.match(src, /const starting = new Set\(\);/);
30
+ assert.match(fn, /if \(starting\.has\(session\)\) return answer\(id, \{ ok: false, already: true \}\);\s*starting\.add\(session\);/);
31
+ const ways = (fn.match(/starting\.delete\(session\)/g) || []).length;
32
+ assert.ok(ways >= 3, `cleared when it already exists, when screen fails, and once it has started (found ${ways})`);
33
+ });
34
+
35
+ test('a session is started with no shell between the runner and screen, so its line is parsed once, in the session', () => {
36
+ const runner = require('fs').readFileSync(require('path').join(__dirname, '..', '..', 'scripts', 'screen-runner.js'), 'utf8');
37
+ assert.ok(!/execFile\('bash', \['-lc'/.test(runner), 'a bash -lc "…" wrapper expands $ and backticks on the host first');
38
+ // -c first: every session this runner starts takes the dashboard's own screenrc (10k lines of
39
+ // scrollback, alt-screen on, Ctrl-S as a keystroke), and never the machine's ~/.screenrc.
40
+ assert.match(runner, /execFile\('screen', \['-c', SCREENRC, '-T', 'xterm-256color', '-dmS', session, 'bash', '-lc', inner\]/);
41
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Opening a screen window, and the state a session leaves in a terminal.
3
+ *
4
+ * Three faults, all reported as "the terminal is weird":
5
+ *
6
+ * 1. A window sometimes opened blank and stayed blank. The repaint that makes an idle session show
7
+ * itself was one shot at 400ms — a guess about how long `script` + `stty` + the screen client
8
+ * take on a loaded machine.
9
+ * 2. Ctrl-C and Detach in that window did nothing but print a command to go and type, for sessions
10
+ * this process could drive all along: the guard refused every verb for a session marked `host`,
11
+ * which is every session, since that is what `listScreens` labels them.
12
+ * 3. Attaching from a real terminal and scrolling wrote mouse coordinates into the shell. screen
13
+ * replays the window's output state to each display that attaches, so a program that turned
14
+ * mouse reporting on and died without turning it off leaves every later terminal reporting the
15
+ * mouse at a shell that is not listening. Reproduced with `script -qfc "screen -x …"`: the
16
+ * attaching terminal receives `ESC[?1000h ESC[?1006h`.
17
+ */
18
+
19
+ const { test } = require('node:test');
20
+ const assert = require('node:assert');
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ const APP = path.join(__dirname, '../..');
25
+ const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
26
+ const jobs = fs.readFileSync(path.join(APP, 'jobs.js'), 'utf8');
27
+ const runner = fs.readFileSync(path.join(APP, 'scripts/screen-runner.js'), 'utf8');
28
+ const actions = fs.readFileSync(path.join(APP, 'actions/screens.js'), 'utf8');
29
+ const screens = fs.readFileSync(path.join(APP, 'screens.js'), 'utf8');
30
+ const terminal = fs.readFileSync(path.join(APP, 'public/terminal.js'), 'utf8');
31
+ const screenrc = fs.readFileSync(path.join(APP, 'screenrc'), 'utf8');
32
+
33
+ test('a blank window is repainted until it paints, not once and hopefully', () => {
34
+ const attach = server.slice(server.indexOf('termWss.handleUpgrade'));
35
+ assert.match(attach, /let painted = false;/);
36
+ assert.match(attach, /child\.stdout\.on\('data', \(d\) => \{ painted = true;/);
37
+ assert.match(attach, /\[250, 900, 2500, 5000\]\.map\(\(ms\) => setTimeout/);
38
+ assert.match(attach, /if \(painted\) return;/, 'and it stops as soon as there is something on screen');
39
+ // The timers are cleared when the attach ends, or a closed window keeps asking a session to repaint.
40
+ assert.match(attach, /const stopRepaints = \(\) => repaints\.forEach\(clearTimeout\);/);
41
+ assert.match(attach, /child\.on\('exit', \(\) => \{ stopRepaints\(\);/);
42
+ });
43
+
44
+ test('an attached window that never paints says so', () => {
45
+ // Connected is not painted: `screen -x` attaches to a window whose program may be sitting idle, and
46
+ // a window that came up with nothing in it looks exactly like one that stalled.
47
+ assert.match(terminal, /pane\.__silence = setTimeout\(/);
48
+ assert.match(terminal, /attached — nothing painted yet, press a key or use ↻/);
49
+ assert.match(terminal, /pane\.dataset\.painted = '1';/, 'and the first byte takes the message away');
50
+ assert.match(terminal, /clearTimeout\(pane\.__silence\);/);
51
+ });
52
+
53
+ test('the window verbs actually run, here, instead of printing a command to type', () => {
54
+ const fn = actions.slice(actions.indexOf('async function onHostScreen'), actions.indexOf('function runnerDownHtml'));
55
+ // Stopping still goes through the runner that owns the process group.
56
+ assert.match(fn, /row\.where === 'host' && args\.includes\('quit'\)/);
57
+ // Everything else is tried locally: /run/screen is shared with this container, which is the same
58
+ // reason `screen -x` attaches at all.
59
+ assert.match(fn, /const out = await run\('screen', args, process\.env\.HOME/);
60
+ assert.match(fn, /if \(out\.ok\) return out;/);
61
+ // And the fallback message is only for a real failure, with the command that was actually attempted.
62
+ assert.match(fn, /If this keeps failing, run it in your terminal: screen -S \$\{session\} -X \$\{verb\}/);
63
+ });
64
+
65
+ test('the mouse modes can be cleared, and it says what it does first', () => {
66
+ assert.match(actions, /'\/actions\/screen-reset-modes': screen_verb,/);
67
+ const branch = actions.slice(actions.indexOf("if (pathname === '/actions/screen-reset-modes')"));
68
+ assert.match(branch, /form\.confirm !== 'yes'/, 'it types a line into the session, so it asks');
69
+ assert.match(branch, /typed <code>printf<\/code> at the session's prompt/,
70
+ 'and says plainly that a full-screen program would have received it instead');
71
+ // The modes a TUI turns on: mouse (three trackings, two encodings) and bracketed paste.
72
+ for (const m of ['?1000l', '?1002l', '?1003l', '?1006l', '?1015l', '?2004l']) {
73
+ assert.ok(actions.includes(m), `clears ${m}`);
74
+ }
75
+ });
76
+
77
+ test("the escape is doubled, because screen's stuff eats one", () => {
78
+ // A single `\033` reaches the shell as a raw ESC byte, which readline takes as the start of a key
79
+ // sequence — leaving `printf '1000l'` on the prompt and the mode still set. Read back from the
80
+ // window with `screen -X hardcopy`, both ways round.
81
+ const line = actions.match(/const MODE_RESETS = [\s\S]*?join\(''\);/)[0];
82
+ // eslint-disable-next-line no-eval
83
+ const sent = eval(line.replace('const MODE_RESETS =', '')).toString();
84
+ assert.ok(sent.startsWith('\\\\033['), 'a literal backslash reaches the shell, not an ESC byte');
85
+ assert.ok(!sent.includes('\u001b'), 'no raw escape byte is stuffed');
86
+ });
87
+
88
+ test('the button is in the window, armed, and named after the symptom', () => {
89
+ assert.match(screens, /term-reset-modes arm-step/);
90
+ assert.match(screens, /hx-post="\/actions\/screen-reset-modes"/);
91
+ assert.match(screens, /hx-trigger="confirmed-remove"/, 'it goes through the confirm dialog');
92
+ assert.match(screens, /the wheel writes characters instead of scrolling/,
93
+ 'named for what you would search for, not for what it sends');
94
+ // The icon has to exist in the vendored set or it draws an empty box.
95
+ const icons = fs.readFileSync(path.join(APP, 'public/vendor/uikit-icons.min.js'), 'utf8');
96
+ assert.ok(icons.includes("crosshairs:'<svg"), 'crosshairs is a real UIkit icon');
97
+ });
98
+
99
+ test('every session the dashboard starts runs under its own screenrc', () => {
100
+ // There was none, so each session took screen's defaults: 100 lines of scrollback (one screen of a
101
+ // build log), no alt-screen, and Ctrl-S as flow control.
102
+ assert.match(jobs, /const SCREENRC = require\('path'\)\.join\(__dirname, 'screenrc'\);/);
103
+ assert.match(jobs, /const screenCmd = \(\) => `screen -c \$\{JSON\.stringify\(SCREENRC\)\}`;/);
104
+ assert.ok(!/(^|[^-])screen -dmS/.test(jobs), 'no launcher starts a bare screen');
105
+ assert.match(runner, /const SCREENRC = path\.join\(__dirname, '\.\.', 'screenrc'\);/);
106
+ assert.match(runner, /execFile\('screen', \['-c', SCREENRC,/);
107
+
108
+ // And each line in it is there for a symptom.
109
+ assert.match(screenrc, /^altscreen on$/m);
110
+ assert.match(screenrc, /^defscrollback 10000$/m);
111
+ assert.match(screenrc, /^defflow off$/m);
112
+ assert.match(screenrc, /^mousetrack off$/m);
113
+ assert.match(screenrc, /^defutf8 on$/m);
114
+ // It is the dashboard's, not the machine's: a person's own ~/.screenrc must not change.
115
+ assert.ok(!screenrc.includes('~/.screenrc'));
116
+ });
@@ -0,0 +1,19 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const root = path.join(__dirname, '..', '..');
7
+ const server = fs.readFileSync(path.join(root, 'server.js'), 'utf8');
8
+ const css = fs.readFileSync(path.join(root, 'public', 'css', 'components', 'project-cards.css'), 'utf8');
9
+
10
+ test('SCREENS is drawn beside the running state in the card head, not in the tool strip', () => {
11
+ assert.match(server, /<span class="pcard-state-line">\$\{statusBadge \?[^\n]*<span class="pcard-screens">\$\{screensMenu\}<\/span><\/span>/);
12
+ assert.match(server, /screens: screensOnTools,/);
13
+ assert.match(server, /const screensOnTools = '';/);
14
+ });
15
+
16
+ test('in the head it is the icon and its count, and the word stays its accessible name', () => {
17
+ assert.match(server, /aria-label="SCREENS\$\{n \? `, \$\{n\} running` : ''\}"/);
18
+ assert.match(css, /\.pcard-screens \.uk-button \{ font-size: 0;/);
19
+ });
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The Screens page: one session under another by default, two or three across on request.
3
+ *
4
+ * One is the default deliberately. A session row carries its name, its state, its log path and five
5
+ * verbs, and two of those side by side on a 1400px screen leaves each narrower than the buttons it
6
+ * holds. Two and three are for a machine with a dozen sessions, where the question is "which of these
7
+ * is waiting" rather than "what does this one say".
8
+ */
9
+
10
+ const { test } = require('node:test');
11
+ const assert = require('node:assert');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const read = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
16
+ const screens = read('screens.js');
17
+ const ui = read('public/ui.js');
18
+ const css = read('public/css/components/grid-and-list.css');
19
+
20
+ test('both children span the container, which is itself a grid', () => {
21
+ // #webship-workspace-projects is the same grid the project rows are laid out in, so a wrapper dropped
22
+ // into it becomes ONE CELL: the toolbar took the first column, the whole list the second, and half
23
+ // the page was empty. Found by looking at the page, not by reading the CSS.
24
+ assert.match(css, /\.screens-toolbar \{ grid-column: 1 \/ -1; \}/);
25
+ assert.match(css, /\.screens-list \{ grid-column: 1 \/ -1;/);
26
+ assert.match(screens, /uk-margin-small screens-toolbar/, 'the toolbar carries the class that spans');
27
+ });
28
+
29
+ test('the list ships at one across', () => {
30
+ assert.match(screens, /<div class="screens-list" data-cols="1">/,
31
+ 'the server renders the default, so the page is right before any script runs');
32
+ assert.match(css, /\.screens-list \{[^}]*grid-template-columns: minmax\(0, 1fr\);/,
33
+ 'one column unless asked otherwise');
34
+ });
35
+
36
+ test('two and three across are offered, and only those', () => {
37
+ // The buttons are one loop over the counts, so the counts are what to assert — the attribute itself
38
+ // is a template, and matching its rendered form would be matching my own interpolation.
39
+ assert.match(screens, /data-screens-cols="\$\{n\}"/);
40
+ const counts = screens.slice(screens.indexOf("[['1', 'list'"));
41
+ assert.match(counts.slice(0, 200), /\['1', 'list'[^\]]*\], \['2', 'grid'[^\]]*\], \['3', 'grid'[^\]]*\]/);
42
+ assert.doesNotMatch(counts.slice(0, 200), /\['[456]'/, 'a session row cannot take a quarter of the page');
43
+ assert.match(css, /\.screens-list\[data-cols="2"\]/);
44
+ assert.match(css, /\.screens-list\[data-cols="3"\]/);
45
+ });
46
+
47
+ test('a column is sized by its share, never by the longest session name', () => {
48
+ // `dev--amd-website_starter-1-0-x-site-template--build` is one unbroken token, and a track sized to its
49
+ // content is how a list of them ends up scrolling sideways.
50
+ assert.match(css, /repeat\(2, minmax\(0, 1fr\)\)/);
51
+ assert.match(css, /repeat\(3, minmax\(0, 1fr\)\)/);
52
+ });
53
+
54
+ test('the choice survives the refresh that replaces the list every 20 seconds', () => {
55
+ // A class set once would last exactly one poll.
56
+ const block = ui.slice(ui.indexOf('(function screensColumns()'));
57
+ assert.match(block, /htmx:after:swap/);
58
+ assert.match(block, /ws-screens-cols/, 'kept per browser, like the theme and the page width');
59
+ assert.match(block, /\['1', '2', '3'\]\.includes/, 'anything else falls back to one across');
60
+ });
61
+
62
+ test('the modal is as wide as what it is showing', () => {
63
+ // It was a flat 1100px whatever was in it, so three columns of session cards came out at 338px each
64
+ // — narrower than the row of buttons inside them — while 463px of a 1563px window went unused.
65
+ assert.match(css, /#editor-modal\[data-screens-cols\] \.uk-modal-dialog \{ width: min\(94vw, 1200px\); \}/);
66
+ assert.match(css, /#editor-modal\[data-screens-cols="2"\][^{]*\{ width: min\(94vw, 1500px\); \}/);
67
+ assert.match(css, /#editor-modal\[data-screens-cols="3"\][^{]*\{ width: min\(94vw, 1900px\); \}/);
68
+ // min(…, 94vw) is what keeps it inside the window: a fixed 1900px dialog on a 1440px laptop is a
69
+ // modal with its right-hand column off the screen.
70
+ assert.match(css, /94vw/);
71
+ });
72
+
73
+ test('the width belongs to the list, not to the modal', () => {
74
+ // #editor-modal is the ONE modal in this dashboard. An editor opened after the screens must not
75
+ // inherit a width chosen for three columns of session cards.
76
+ const block = ui.slice(ui.indexOf('(function screensColumns()'));
77
+ assert.match(block, /if \(modal\.contains\(list\)\) modal\.dataset\.screensCols = read\(\);/);
78
+ assert.match(block, /else delete modal\.dataset\.screensCols;/);
79
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The SCREENS menu: a pick closes it, and what it says is current.
3
+ *
4
+ * Choosing "Open the screen" started the terminal and left the menu open on top of it — a control that
5
+ * looks like it did not take, over the thing it just did. The close rule was scoped to items carrying
6
+ * `uk-toggle` (the modal openers), and a screens item carries `data-open-terminal` instead, so it was
7
+ * never covered.
8
+ *
9
+ * The timing had the opposite problem: every project row polled its own screens list every 15 seconds
10
+ * whether or not anybody looked at it — a request per row, forever — while the things that actually
11
+ * change the list (starting a session, stopping one) waited out that poll before the page admitted it.
12
+ * So: slower in the background, immediate when something changes and immediate when the menu opens.
13
+ */
14
+
15
+ const { test } = require('node:test');
16
+ const assert = require('node:assert');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const APP = path.join(__dirname, '../..');
21
+ const ui = fs.readFileSync(path.join(APP, 'public/ui.js'), 'utf8');
22
+ const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
23
+ const actions = fs.readFileSync(path.join(APP, 'actions/screens.js'), 'utf8');
24
+
25
+ test('a pick closes the menu it was made in, whatever the item does', () => {
26
+ // The general rule, not a list of the item kinds somebody remembered: opening a modal, opening a
27
+ // terminal and posting an action are all picks, and none of them is the outside click UIkit closes on.
28
+ const block = ui.slice(ui.indexOf('// A pick closes the menu it was made in.'));
29
+ assert.match(block, /e\.target\.closest\('\.uk-dropdown a, \.uk-dropdown button'\)/);
30
+ assert.match(block, /UIkit\.dropdown\(drop\)\.hide\(0\)/);
31
+ // The old rule was narrower, and that is exactly why the screens menu was missed.
32
+ assert.ok(!/closest\('\.act-menu \[uk-toggle\]'\)/.test(ui), 'the uk-toggle-only rule is gone');
33
+ });
34
+
35
+ test('a dropdown that is being typed in or paged stays open', () => {
36
+ const block = ui.slice(ui.indexOf('// A pick closes the menu it was made in.'));
37
+ // Only links and buttons close it, so a filter box inside a picker keeps its menu.
38
+ assert.ok(!/'\.uk-dropdown input/.test(block), 'inputs are not picks');
39
+ assert.match(block, /if \(item\.closest\('\[data-keep-open\]'\)\) return;/);
40
+ // And the one place that needs it today: switching npm/yarn changes which pane shows INSIDE the
41
+ // menu, so closing on it would make the other manager unreachable.
42
+ assert.match(server, /class="uk-tab node-tabs" data-keep-open/);
43
+ });
44
+
45
+ test('the row polls slowly, and catches up the moment anything happens', () => {
46
+ const menu = server.match(/hx-get="\/fragments\/projects\/screens\/[^"]+" hx-trigger="([^"]+)"/)[1];
47
+ assert.match(menu, /every 30s/, 'the background poll is the cheap one');
48
+ assert.ok(!/every 15s/.test(menu), 'not twice a minute per row, forever');
49
+ assert.match(menu, /screens-changed from:body/, 'a session starting or stopping reaches it');
50
+ assert.match(menu, /menu-opened/, 'and opening it asks for what is there now');
51
+ });
52
+
53
+ test('opening a menu asks for its contents', () => {
54
+ // A list of sessions is the one thing the menu is opened to read, and a stale one is how you click a
55
+ // session that has already finished.
56
+ const block = ui.slice(ui.indexOf('// Opening a menu asks for what is in it NOW.'));
57
+ assert.match(block, /addEventListener\('beforeshow'/, "UIkit's own event, before it is on screen");
58
+ assert.match(block, /querySelector\('\[hx-trigger\*="menu-opened"\]'\)/);
59
+ assert.match(block, /dispatchEvent\(new CustomEvent\('menu-opened'\)\)/);
60
+ });
61
+
62
+ test('starting and stopping a session both say so', () => {
63
+ // Otherwise the count in the topbar, the row's menu and the screens page each wait out their own
64
+ // poll before admitting to something you did a moment ago.
65
+ const started = actions.slice(actions.indexOf('async function screen_new'));
66
+ assert.match(started, /'screens-changed': \{ session: fullSession \}/);
67
+ assert.match(actions, /'screen-killed': \{ session \}, 'screens-changed': \{ session \}/);
68
+ // And the three places that listen.
69
+ assert.match(server, /screens-count" hx-get="\/fragments\/screens-count" hx-trigger="load, every 30s, screens-changed from:body/);
70
+ assert.match(server, /screens-grid" hx-trigger="refresh-projects from:body, screens-changed from:body/);
71
+ });
72
+
73
+ test('a session in the menu says what it is doing', () => {
74
+ // The row already tells you something is running; the menu is where you choose WHICH, so each line
75
+ // carries its state rather than only its name.
76
+ const items = server.slice(server.indexOf('const items = sessions.map((s) => {'), server.indexOf("}).join('');", server.indexOf('const items = sessions.map((s) => {')));
77
+ assert.match(items, /uk-label-warning" title="\$\{esc\(why\)\}">✋ waiting/);
78
+ assert.match(items, /uk-label-success">running/);
79
+ assert.match(items, /dead \? '<span class="uk-label">dead<\/span>'/, 'a session whose process is gone opens onto nothing');
80
+ assert.match(items, /isAgent \? iconHtml\('tabler:robot-face'/);
81
+ // The name is `<workspace>--<project>--<what>`, and the first two are the row it is already on.
82
+ assert.match(items, /s\.session\.startsWith\(`\$\{sm\[1\]\}--\$\{sm\[2\]\}--`\)/);
83
+ assert.match(items, /title="\$\{esc\(s\.session\)\}/, 'the whole name is still one hover away');
84
+ });
@@ -0,0 +1,14 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const ui = fs.readFileSync(path.join(__dirname, '..', '..', 'public', 'ui.js'), 'utf8');
7
+
8
+ test('the modal wears its column count for width, and is never one of the count buttons', () => {
9
+ // The modal element carries data-screens-cols; a bare attribute selector matched it and gave the
10
+ // overlay the primary button's class and aria-pressed, which painted the whole backdrop brand blue.
11
+ assert.match(ui, /document\.querySelectorAll\('button\[data-screens-cols\]'\)/);
12
+ assert.doesNotMatch(ui, /document\.querySelectorAll\('\[data-screens-cols\]'\)/);
13
+ assert.match(ui, /modal\.dataset\.screensCols = read\(\)/);
14
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * A screen that has stopped to ask you something raises its hand, and an agent at work says so.
3
+ *
4
+ * The point of a detached session is that nobody is watching it, so a question nobody sees is a run
5
+ * that is not happening. Two things had to be true for the hand to be worth having, and each cost a
6
+ * round of debugging:
7
+ *
8
+ * - it has to go DOWN again. An answered `(y/n)` went on matching, because the rule allows a few
9
+ * characters after the bracket for a cursor and a typed `y` is one of them — so the hand stayed up
10
+ * for the rest of the session's life, which trains you to ignore it;
11
+ * - the agent answer has to be per PROJECT. The first version cached the computed set rather than
12
+ * the process snapshot it came from, so on a workspace of eleven projects, ten of them claimed the
13
+ * first one's agent.
14
+ */
15
+
16
+ const { test } = require('node:test');
17
+ const assert = require('node:assert');
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ const { waitingState } = require('../../waiting');
22
+ const { sessionWaitingWhy, agentSessions } = require('../../screens');
23
+ const server = fs.readFileSync(path.join(__dirname, '../../server.js'), 'utf8');
24
+ const screensSrc = fs.readFileSync(path.join(__dirname, '../../screens.js'), 'utf8');
25
+
26
+ test('an ANSWERED question is not a question', () => {
27
+ const asked = 'rajab@host:~$ read -p x\nDo you want to continue? (y/n) ';
28
+ const answered = 'Do you want to continue? (y/n) y\nrajab@host:~/workspace/test/ruverify1$';
29
+ assert.equal(waitingState(asked).waiting, true);
30
+ assert.equal(waitingState(answered).waiting, false,
31
+ 'the shell has moved on; the hand has to go down or it is noise');
32
+ });
33
+
34
+ test('a cursor after the bracket is still a question', () => {
35
+ // Which is what the allowance was for — the fix must not take it away.
36
+ assert.equal(waitingState('Overwrite the file? (y/N) _').waiting, true);
37
+ assert.equal(waitingState('Overwrite the file? (y/N) ').waiting, true);
38
+ });
39
+
40
+ test('the rules that are drawn with a footer keep their three-line window', () => {
41
+ // A numbered choice and a "press Enter" have a footer under them, so they are not the last line.
42
+ assert.equal(waitingState('❯ 1. Yes\n 2. No\n ? for shortcuts').waiting, true);
43
+ assert.equal(waitingState('Press Enter to continue\n⏵⏵ bypass permissions on').waiting, true);
44
+ });
45
+
46
+ test('busy is not waiting, whatever else is on the screen', () => {
47
+ const busy = '6 Thinking… (6m 16s · 23.9k tokens)\n⏵⏵ bypass permissions on · esc to interrupt';
48
+ assert.equal(waitingState(busy).waiting, false, 'the interrupt hint means it is working');
49
+ });
50
+
51
+ test('the runner\'s reading of the visible screen wins over the log', () => {
52
+ // A Claude Code session writes no log at all — `exec claude` with no tee — so the log check can never
53
+ // fire for exactly the sessions where a question matters most.
54
+ assert.equal(sessionWaitingWhy({ session: 'nope', waiting: true, waitingWhy: 'waiting on a choice' }),
55
+ 'waiting on a choice');
56
+ assert.equal(sessionWaitingWhy({ session: 'nope', waiting: true }), 'waiting for you',
57
+ 'a runner that says waiting without a reason still raises the hand');
58
+ assert.equal(sessionWaitingWhy(null), '');
59
+ });
60
+
61
+ test('agentSessions answers per session, never from a cached answer', () => {
62
+ // The bug this pins: caching the computed set meant the second project asked within five seconds got
63
+ // the first project's agents back. Ten rows of eleven claimed an agent that was not theirs.
64
+ assert.deepEqual([...agentSessions([])], [], 'no sessions, no agents');
65
+ assert.deepEqual([...agentSessions([{ session: 'x', pid: '0' }])], [],
66
+ 'pid 0 is every top-level process: walking from it would claim every agent on the machine');
67
+ assert.deepEqual([...agentSessions([{ session: 'x' }])], [], 'a row with no pid claims nothing');
68
+ // The snapshot is what is memoised.
69
+ assert.match(screensSrc, /function processTable\(\)/);
70
+ assert.match(screensSrc, /psMemo = \{ at: now, kids, args \}/);
71
+ assert.doesNotMatch(screensSrc, /agentMemo/, 'the ANSWER is never cached');
72
+ });
73
+
74
+ test('the row states both at the right-hand end of its name line', () => {
75
+ // One glance answers "is anything happening here, and is the site up". Split to opposite ends, the
76
+ // eye had to cross the name to collect one of the two.
77
+ const line = server.slice(server.indexOf('<div class="pcard-head">'));
78
+ const block = line.slice(0, line.indexOf('</div>', line.indexOf('project-title-status')));
79
+ assert.match(block, /class="project-title-status pcard-status"/);
80
+ // The agent first, then what DDEV says — so the running state keeps the far right, where it was.
81
+ assert.ok(block.indexOf('project-title-agent') < block.indexOf('project-title-state'),
82
+ 'the agent sits next to the running state, not past it');
83
+ assert.match(server, /hx-swap-oob="true"/, 'the agent badge is filled by the poll the row already makes');
84
+ });
85
+
86
+ test('the hand and the agent badge are sent even when empty', () => {
87
+ // An out-of-band swap that is only sent sometimes leaves the last answer standing after the question
88
+ // has been answered or the agent has finished.
89
+ const frag = server.slice(server.indexOf('const hand = `<span class="screens-hand'));
90
+ assert.match(frag, /waiting\.length \? `✋/, 'the hand is conditional INSIDE the span, not around it');
91
+ assert.match(frag, /agents\.size\s*\n?\s*\? `<span class="uk-label/, 'and so is the badge');
92
+ });
93
+
94
+ test('both badges are the same pill, in different colours', () => {
95
+ const css = fs.readFileSync(path.join(__dirname, '../../public/css/components/mic-on-any-field-that-wants-typing.css'), 'utf8');
96
+ const rows = fs.readFileSync(path.join(__dirname, '../../public/css/components/project-rows.css'), 'utf8');
97
+ // The pill rule names the WRAPPER, so a badge that joins the line later is the same shape by
98
+ // construction. Scoped to .project-title-state alone, the agent badge fell back to bare UIkit — a
99
+ // 2px corner, 21px tall, UPPERCASE — next to a 26px pill, which reads as one of the two being broken.
100
+ assert.match(css, /\.project-title-status \.uk-label,\s*\n\s*\.project-title-state \.uk-label/);
101
+ // Different colours, because they answer different questions: green is "the site is up", brand blue
102
+ // is "something is working on it". Two green pills say one thing twice.
103
+ assert.match(rows, /\.project-title-status \.agent-live \{[^}]*background: var\(--brand-primary\)/s);
104
+ // And amber still wins when it has stopped to ask you something — the one state worth shouting.
105
+ assert.match(rows, /\.project-title-status \.agent-live\.uk-label-warning \{[^}]*var\(--warning\)/s);
106
+ });
107
+
108
+ test('a CLI session is offered as an id to resume, never as a link', () => {
109
+ // A Claude Code CLI session is a transcript in ~/.claude/projects on this machine. It has no page on
110
+ // claude.ai — the claude.ai/code ids that turn up inside transcripts belong to cloud sessions — so a
111
+ // link would either go nowhere or suggest the conversation is on the web when it is not. These are
112
+ // client projects: the id is something to resume with, never to publish.
113
+ assert.match(screensSrc, /claude --resume \$\{esc\(latest\.id\)\}/);
114
+ assert.doesNotMatch(screensSrc, /claude\.ai\/code\/session/, 'no link to a page that does not exist');
115
+ assert.match(screensSrc, /it has no page on claude\.ai/, 'and it says so where somebody would look for one');
116
+ // The NEWEST session for the folder, said as that: a screen started with `exec claude` does not
117
+ // record which conversation it ended up in.
118
+ assert.match(screensSrc, /the newest\s*\n?\s*Claude Code session in this folder/);
119
+ });
120
+
121
+ test('the robot is an icon from the sprite, not an emoji', () => {
122
+ // An emoji is drawn by the platform's own font: a grey outline on one machine, a yellow cartoon on
123
+ // another, at a size and weight nothing here controls. robot-face over robot because at the 14px a
124
+ // label gives it, a whole robot is a smudge — compared side by side at badge size.
125
+ assert.match(server, /iconHtml\('tabler:robot-face', 0\.8\)/);
126
+ // Scoped to the badges, not to the file: the three runJob labels that say an AI wrote the job are a
127
+ // different thing, and a blanket search would have this test fail on one of those instead.
128
+ assert.doesNotMatch(server, /🤖 agent running/, 'the badge is an icon now, here and on the Jira page');
129
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The screens manager's own logic: which places a session may be started in,
3
+ * how a target token resolves to a directory, and what the session-name rules
4
+ * actually admit. These are the guards that keep a browser-supplied name out
5
+ * of `screen`, so they are pinned here rather than left to the browser tests.
6
+ */
7
+
8
+ const { test } = require('node:test');
9
+ const assert = require('node:assert');
10
+ const path = require('path');
11
+
12
+ const { screenTargets, screenTargetDir } = require('../../screens');
13
+ const { ROOT } = require('../../workspaces');
14
+
15
+ test('the workspace root is always a place to start a session', () => {
16
+ const root = screenTargets().find((t) => t.value === 'root');
17
+ assert.ok(root);
18
+ assert.strictEqual(root.dir, ROOT);
19
+ });
20
+
21
+ test('every target resolves to a real directory inside the workspace', () => {
22
+ for (const t of screenTargets().slice(0, 40)) {
23
+ const dir = screenTargetDir(t.value);
24
+ assert.ok(dir, `${t.value} resolves`);
25
+ assert.ok(dir.startsWith(ROOT), `${dir} is inside the workspace`);
26
+ }
27
+ });
28
+
29
+ test('a target token the server did not offer resolves to nothing', () => {
30
+ for (const bad of ['', 'root/../..', 'p:dev:../../etc', '/etc', 'ws:no-such', 'p:no:no']) {
31
+ assert.strictEqual(screenTargetDir(bad), null, `${bad} is refused`);
32
+ }
33
+ });
34
+
35
+ test('workspace folders and projects are offered separately', () => {
36
+ const targets = screenTargets();
37
+ assert.ok(targets.some((t) => t.group === 'Workspace folders'));
38
+ assert.ok(targets.some((t) => t.group === 'Projects'), 'at least one project is offered');
39
+ const dev = targets.find((t) => t.value === 'ws:dev');
40
+ assert.ok(dev && dev.dir === path.join(ROOT, 'dev'));
41
+ });
42
+
43
+ // The name rule is shared by the form, the actions and both /fragments routes:
44
+ // it must never admit a leading '-', which `screen` would read as an option.
45
+ const NAME_RE = /^[a-z0-9][a-zA-Z0-9_.-]*$/;
46
+
47
+ test('a session name may not start with a dash, and holds no shell characters', () => {
48
+ for (const ok of ['build1', 'jira-webship-2307', 'a.b_c-d', 'x']) assert.ok(NAME_RE.test(ok), ok);
49
+ for (const bad of ['-d', '-S', '; rm -rf /', 'a b', '../x', 'a$b', 'A-first', '']) {
50
+ assert.ok(!NAME_RE.test(bad), `${bad} is refused`);
51
+ }
52
+ });
53
+
54
+ // The machine's sessions reach the dashboard through the folder both sides
55
+ // share: the runner republishes its list every two seconds, so a stale file
56
+ // means the runner is gone and nothing new may be started.
57
+ test('a published session list is trusted only while it is fresh', () => {
58
+ const fs = require('fs');
59
+ const path = require('path');
60
+ const os = require('os');
61
+ const { ROOT } = require('../../workspaces');
62
+ const file = path.join(ROOT, '.screens', 'host-sessions.json');
63
+ const had = fs.existsSync(file) ? fs.readFileSync(file) : null;
64
+ const { hostSessionsFile } = require('../../jobs');
65
+ try {
66
+ fs.mkdirSync(path.dirname(file), { recursive: true });
67
+ fs.writeFileSync(file, JSON.stringify({ at: Date.now(), host: os.hostname(), sessions: [{ session: 'a--b--c' }] }));
68
+ assert.equal(hostSessionsFile().sessions.length, 1, 'a list written now is the truth');
69
+ fs.writeFileSync(file, JSON.stringify({ at: Date.now() - 60000, sessions: [{ session: 'a--b--c' }] }));
70
+ assert.equal(hostSessionsFile(), null, 'a minute-old list means the runner stopped');
71
+ } finally {
72
+ if (had) fs.writeFileSync(file, had); else fs.rmSync(file, { force: true });
73
+ }
74
+ });