@webship.co/workspace 1.0.0-beta1 → 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 (630) 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/core/workspace-app/README.md +380 -0
  160. package/{actions → core/workspace-app/actions}/ddev.js +0 -1
  161. package/{actions → core/workspace-app/actions}/rag.js +0 -1
  162. package/{actions → core/workspace-app/actions}/testing.js +0 -1
  163. package/{assistant.js → core/workspace-app/assistant.js} +1 -10
  164. package/{git-info.js → core/workspace-app/git-info.js} +17 -1
  165. package/core/workspace-app/package.json +29 -0
  166. package/core/workspace-app/plugin-catalog.js +50 -0
  167. package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
  168. package/{server.js → core/workspace-app/server.js} +18 -0
  169. package/core/workspace-app/tests/features/actions-rail.feature +44 -0
  170. package/core/workspace-app/tests/features/ai-items.feature +23 -0
  171. package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
  172. package/core/workspace-app/tests/features/checks.feature +39 -0
  173. package/core/workspace-app/tests/features/clone-project.feature +30 -0
  174. package/core/workspace-app/tests/features/command-authoring.feature +58 -0
  175. package/core/workspace-app/tests/features/dashboard.feature +57 -0
  176. package/core/workspace-app/tests/features/menus.feature +29 -0
  177. package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
  178. package/core/workspace-app/tests/features/plugins.feature +20 -0
  179. package/core/workspace-app/tests/features/project-actions.feature +40 -0
  180. package/core/workspace-app/tests/features/screens.feature +29 -0
  181. package/core/workspace-app/tests/features/settings.feature +46 -0
  182. package/core/workspace-app/tests/features/test-reports.feature +32 -0
  183. package/core/workspace-app/tests/features/theme.feature +40 -0
  184. package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
  185. package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +152 -0
  186. package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -0
  187. package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
  188. package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
  189. package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
  190. package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
  191. package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
  192. package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
  193. package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
  194. package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
  195. package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
  196. package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
  197. package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
  198. package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
  199. package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
  200. package/core/workspace-app/tests/unit/assistant.test.js +61 -0
  201. package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
  202. package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
  203. package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
  204. package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
  205. package/core/workspace-app/tests/unit/browsers.test.js +76 -0
  206. package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
  207. package/core/workspace-app/tests/unit/build-order.test.js +153 -0
  208. package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
  209. package/core/workspace-app/tests/unit/builders.test.js +158 -0
  210. package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
  211. package/core/workspace-app/tests/unit/changelog.test.js +177 -0
  212. package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
  213. package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
  214. package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
  215. package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
  216. package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
  217. package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
  218. package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
  219. package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
  220. package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
  221. package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
  222. package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
  223. package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
  224. package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
  225. package/core/workspace-app/tests/unit/create-card.test.js +85 -0
  226. package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
  227. package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
  228. package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
  229. package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
  230. package/core/workspace-app/tests/unit/ddev-domains.test.js +153 -0
  231. package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
  232. package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
  233. package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
  234. package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
  235. package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
  236. package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
  237. package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
  238. package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
  239. package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
  240. package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
  241. package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
  242. package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
  243. package/core/workspace-app/tests/unit/density.test.js +52 -0
  244. package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
  245. package/core/workspace-app/tests/unit/event-log.test.js +102 -0
  246. package/core/workspace-app/tests/unit/favicon.test.js +68 -0
  247. package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
  248. package/core/workspace-app/tests/unit/git-info.test.js +26 -0
  249. package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
  250. package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
  251. package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
  252. package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
  253. package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
  254. package/core/workspace-app/tests/unit/images-create.test.js +59 -0
  255. package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
  256. package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
  257. package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
  258. package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
  259. package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
  260. package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
  261. package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
  262. package/core/workspace-app/tests/unit/narration.test.js +186 -0
  263. package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
  264. package/core/workspace-app/tests/unit/navigation.test.js +254 -0
  265. package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
  266. package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
  267. package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
  268. package/core/workspace-app/tests/unit/notes.test.js +128 -0
  269. package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
  270. package/core/workspace-app/tests/unit/page-width.test.js +59 -0
  271. package/core/workspace-app/tests/unit/php-version.test.js +90 -0
  272. package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
  273. package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
  274. package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
  275. package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
  276. package/core/workspace-app/tests/unit/plugins.test.js +58 -0
  277. package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
  278. package/core/workspace-app/tests/unit/project-make.test.js +143 -0
  279. package/core/workspace-app/tests/unit/project-name.test.js +202 -0
  280. package/core/workspace-app/tests/unit/reader.test.js +62 -0
  281. package/core/workspace-app/tests/unit/release.test.js +92 -0
  282. package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
  283. package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
  284. package/core/workspace-app/tests/unit/round-11.test.js +31 -0
  285. package/core/workspace-app/tests/unit/round-12.test.js +37 -0
  286. package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
  287. package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
  288. package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
  289. package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
  290. package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
  291. package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
  292. package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
  293. package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
  294. package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
  295. package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
  296. package/core/workspace-app/tests/unit/screens.test.js +74 -0
  297. package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
  298. package/core/workspace-app/tests/unit/session-name.test.js +64 -0
  299. package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
  300. package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
  301. package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
  302. package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
  303. package/core/workspace-app/tests/unit/speech.test.js +290 -0
  304. package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
  305. package/core/workspace-app/tests/unit/status-language.test.js +47 -0
  306. package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
  307. package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
  308. package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
  309. package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
  310. package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
  311. package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
  312. package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
  313. package/core/workspace-app/tests/unit/themes.test.js +453 -0
  314. package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
  315. package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
  316. package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
  317. package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
  318. package/core/workspace-app/tests/unit/version-check.test.js +98 -0
  319. package/core/workspace-app/tests/unit/version-link.test.js +50 -0
  320. package/core/workspace-app/tests/unit/video-borrow.test.js +198 -0
  321. package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
  322. package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
  323. package/core/workspace-app/tests/unit/video-edit.test.js +417 -0
  324. package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
  325. package/core/workspace-app/tests/unit/waiting.test.js +48 -0
  326. package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
  327. package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
  328. package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
  329. package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
  330. package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
  331. package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
  332. package/package.json +38 -36
  333. /package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +0 -0
  334. /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
  335. /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
  336. /package/{actions → core/workspace-app/actions}/audio.js +0 -0
  337. /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
  338. /package/{actions → core/workspace-app/actions}/checks.js +0 -0
  339. /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
  340. /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
  341. /package/{actions → core/workspace-app/actions}/composer.js +0 -0
  342. /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
  343. /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
  344. /package/{actions → core/workspace-app/actions}/folders.js +0 -0
  345. /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
  346. /package/{actions → core/workspace-app/actions}/images.js +0 -0
  347. /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
  348. /package/{actions → core/workspace-app/actions}/node.js +0 -0
  349. /package/{actions → core/workspace-app/actions}/notes.js +0 -0
  350. /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
  351. /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
  352. /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
  353. /package/{actions → core/workspace-app/actions}/screens.js +0 -0
  354. /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
  355. /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
  356. /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
  357. /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
  358. /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
  359. /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
  360. /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
  361. /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
  362. /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
  363. /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
  364. /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
  365. /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
  366. /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
  367. /package/{checks.js → core/workspace-app/checks.js} +0 -0
  368. /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
  369. /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
  370. /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
  371. /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
  372. /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
  373. /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
  374. /package/{commands.js → core/workspace-app/commands.js} +0 -0
  375. /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
  376. /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
  377. /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
  378. /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
  379. /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
  380. /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
  381. /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
  382. /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
  383. /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
  384. /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
  385. /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
  386. /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
  387. /package/{docs → core/workspace-app/docs}/README.md +0 -0
  388. /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
  389. /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
  390. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
  391. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
  392. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
  393. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
  394. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
  395. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  396. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
  397. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
  398. /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
  399. /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
  400. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
  401. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
  402. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
  403. /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
  404. /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
  405. /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
  406. /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
  407. /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
  408. /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
  409. /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
  410. /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
  411. /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
  412. /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
  413. /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
  414. /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
  415. /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
  416. /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
  417. /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
  418. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
  419. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
  420. /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
  421. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
  422. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
  423. /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
  424. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
  425. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
  426. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
  427. /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
  428. /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
  429. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
  430. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
  431. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
  432. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
  433. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
  434. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
  435. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
  436. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
  437. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
  438. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
  439. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
  440. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
  441. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
  442. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
  443. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
  444. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
  445. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
  446. /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
  447. /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
  448. /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
  449. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
  450. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
  451. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
  452. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
  453. /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
  454. /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
  455. /package/{folders.js → core/workspace-app/folders.js} +0 -0
  456. /package/{format.js → core/workspace-app/format.js} +0 -0
  457. /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
  458. /package/{html.js → core/workspace-app/html.js} +0 -0
  459. /package/{icons.js → core/workspace-app/icons.js} +0 -0
  460. /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
  461. /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
  462. /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
  463. /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
  464. /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
  465. /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
  466. /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
  467. /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
  468. /package/{images.js → core/workspace-app/images.js} +0 -0
  469. /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
  470. /package/{linger.js → core/workspace-app/linger.js} +0 -0
  471. /package/{lists.js → core/workspace-app/lists.js} +0 -0
  472. /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
  473. /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
  474. /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
  475. /package/{notes.js → core/workspace-app/notes.js} +0 -0
  476. /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
  477. /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
  478. /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
  479. /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
  480. /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
  481. /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
  482. /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
  483. /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
  484. /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
  485. /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
  486. /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
  487. /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
  488. /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
  489. /package/{public → core/workspace-app/public}/css/base.css +0 -0
  490. /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
  491. /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
  492. /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
  493. /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
  494. /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
  495. /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
  496. /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
  497. /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
  498. /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
  499. /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
  500. /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
  501. /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
  502. /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
  503. /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
  504. /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
  505. /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
  506. /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
  507. /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
  508. /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
  509. /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
  510. /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
  511. /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
  512. /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
  513. /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
  514. /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
  515. /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
  516. /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
  517. /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
  518. /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
  519. /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
  520. /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
  521. /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
  522. /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
  523. /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
  524. /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
  525. /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
  526. /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
  527. /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
  528. /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
  529. /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
  530. /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
  531. /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
  532. /package/{public → core/workspace-app/public}/favicon.ico +0 -0
  533. /package/{public → core/workspace-app/public}/favicon.png +0 -0
  534. /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
  535. /package/{public → core/workspace-app/public}/logo.png +0 -0
  536. /package/{public → core/workspace-app/public}/media-player.js +0 -0
  537. /package/{public → core/workspace-app/public}/mic.js +0 -0
  538. /package/{public → core/workspace-app/public}/narration.js +0 -0
  539. /package/{public → core/workspace-app/public}/palette.js +0 -0
  540. /package/{public → core/workspace-app/public}/sortable.js +0 -0
  541. /package/{public → core/workspace-app/public}/suggest.js +0 -0
  542. /package/{public → core/workspace-app/public}/terminal.js +0 -0
  543. /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
  544. /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
  545. /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
  546. /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
  547. /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
  548. /package/{public → core/workspace-app/public}/ui.js +0 -0
  549. /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
  550. /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
  551. /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
  552. /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
  553. /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
  554. /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
  555. /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
  556. /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
  557. /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
  558. /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
  559. /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
  560. /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
  561. /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
  562. /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
  563. /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
  564. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
  565. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  566. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  567. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
  568. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  569. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  570. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  571. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  572. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
  573. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  574. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
  575. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  576. /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
  577. /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
  578. /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
  579. /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
  580. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
  581. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
  582. /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
  583. /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
  584. /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
  585. /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
  586. /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
  587. /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
  588. /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
  589. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
  590. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
  591. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
  592. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
  593. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
  594. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
  595. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
  596. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
  597. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
  598. /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
  599. /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
  600. /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
  601. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
  602. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
  603. /package/{rag.js → core/workspace-app/rag.js} +0 -0
  604. /package/{reader.js → core/workspace-app/reader.js} +0 -0
  605. /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
  606. /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
  607. /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
  608. /package/{screenrc → core/workspace-app/screenrc} +0 -0
  609. /package/{screens.js → core/workspace-app/screens.js} +0 -0
  610. /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
  611. /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
  612. /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
  613. /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
  614. /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
  615. /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
  616. /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
  617. /package/{speech.js → core/workspace-app/speech.js} +0 -0
  618. /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
  619. /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
  620. /package/{themes.js → core/workspace-app/themes.js} +0 -0
  621. /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
  622. /package/{urls.js → core/workspace-app/urls.js} +0 -0
  623. /package/{validate.js → core/workspace-app/validate.js} +0 -0
  624. /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
  625. /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
  626. /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
  627. /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
  628. /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
  629. /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
  630. /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Commands → Navigation: the sidebar and the top bar, arranged.
3
+ *
4
+ * What must never drift: with no file the navigation is exactly what the code built; hiding, reordering
5
+ * and adding are presentation only; an entry lives in exactly ONE place, so moving it to the top bar
6
+ * takes it out of its sidebar group; a workspace's own settings are applied after the global ones; an
7
+ * added link can only be an https address or a path of this dashboard; and the reserved lists (the three
8
+ * top-bar regions and Hidden) are never written back as sidebar groups.
9
+ */
10
+
11
+ const assert = require('node:assert');
12
+ const { test } = require('node:test');
13
+ const fs = require('fs');
14
+ const os = require('os');
15
+ const path = require('path');
16
+
17
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'navigation-'));
18
+ process.env.NAVIGATION_FILE = path.join(tmp, 'navigation.yml');
19
+ process.env.NAVIGATION_SEEN_FILE = path.join(tmp, 'navigation.seen.json');
20
+ process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
21
+
22
+ const nav = require('../../navigation');
23
+
24
+ const entry = (id, label = id) => ({ id, label, html: `<a class="action-btn" href="/${id}"><span class="action-label">${label}</span></a>` });
25
+ const built = () => ([
26
+ { id: 'create', label: 'Create', items: [entry('new-project', 'New project')] },
27
+ { id: 'go', label: 'Go', items: [entry('all-workspaces', 'All workspaces'), entry('commands', 'Commands'), entry('help', 'Help')] },
28
+ ]);
29
+ const write = (raw) => nav.saveLayout(raw);
30
+ const reset = () => { try { fs.unlinkSync(process.env.NAVIGATION_FILE); } catch (_) { /* already gone */ } };
31
+
32
+ test('with no file the navigation is what the code built, and the top bar is empty', () => {
33
+ reset();
34
+ const out = nav.arrange(built(), 'global');
35
+ assert.deepStrictEqual(out.groups.map((g) => g.id), ['create', 'go']);
36
+ assert.deepStrictEqual(out.groups[1].items.map((i) => i.id), ['all-workspaces', 'commands', 'help']);
37
+ assert.deepStrictEqual(nav.REGIONS.map((r) => out.topbar[r].length), [0, 0, 0]);
38
+ });
39
+
40
+ test('an id slugs from the label with any count stripped, so a backup does not forget the layout', () => {
41
+ assert.strictEqual(nav.slugId('Backups (2)'), 'backups');
42
+ assert.strictEqual(nav.slugId('Run a tool (13)'), 'run-a-tool');
43
+ assert.strictEqual(nav.slugId('All workspaces'), 'all-workspaces');
44
+ });
45
+
46
+ test('hidden is presentation only: the entry goes, the group survives its last entry leaving', () => {
47
+ write({ global: { hidden: ['commands'] } });
48
+ const out = nav.arrange(built(), 'global');
49
+ assert.deepStrictEqual(out.groups[1].items.map((i) => i.id), ['all-workspaces', 'help']);
50
+
51
+ write({ global: { hidden: ['new-project'] } });
52
+ const only = nav.arrange(built(), 'global');
53
+ assert.ok(!only.groups.some((g) => g.id === 'create'), 'a group with nothing left in it is not drawn');
54
+ });
55
+
56
+ test('order names what it names; anything unlisted keeps its place AFTER it', () => {
57
+ write({ global: { groups: ['go', 'create'], order: { go: ['help', 'commands'] } } });
58
+ const out = nav.arrange(built(), 'global');
59
+ assert.deepStrictEqual(out.groups.map((g) => g.id), ['go', 'create'], 'the group order is the one named');
60
+ assert.deepStrictEqual(out.groups[0].items.map((i) => i.id), ['help', 'commands', 'all-workspaces'],
61
+ 'an entry the layout has never seen must not vanish into the middle of it');
62
+ });
63
+
64
+ test('an entry moved to the top bar leaves its sidebar group: it is in one place, not two', () => {
65
+ write({ global: { topbar: { end: ['commands'] } } });
66
+ const out = nav.arrange(built(), 'global');
67
+ assert.deepStrictEqual(out.topbar.end.map((i) => i.id), ['commands']);
68
+ assert.deepStrictEqual(out.groups.find((g) => g.id === 'go').items.map((i) => i.id), ['all-workspaces', 'help']);
69
+ assert.deepStrictEqual(out.topbar.start, []);
70
+ assert.deepStrictEqual(out.topbar.center, []);
71
+ });
72
+
73
+ test('the three regions are kept apart, and each is in the order the layout names', () => {
74
+ write({ global: { topbar: { start: ['help'], center: ['commands'], end: ['all-workspaces', 'new-project'] } } });
75
+ const out = nav.arrange(built(), 'global');
76
+ assert.deepStrictEqual(out.topbar.start.map((i) => i.id), ['help']);
77
+ assert.deepStrictEqual(out.topbar.center.map((i) => i.id), ['commands']);
78
+ assert.deepStrictEqual(out.topbar.end.map((i) => i.id), ['all-workspaces', 'new-project'],
79
+ 'the order inside a region is the order it was saved in');
80
+ assert.deepStrictEqual(out.groups, [], 'everything moved up leaves nothing behind');
81
+ });
82
+
83
+ test('an entry the dashboard ships in the top bar stays there until someone says otherwise', () => {
84
+ reset();
85
+ const groups = [{ id: 'screens', label: 'Screens', items: [{ ...entry('open-screens', 'Screens'), topbar: 'end' }] }];
86
+ const shipped = nav.arrange(groups, 'global');
87
+ assert.deepStrictEqual(shipped.topbar.end.map((i) => i.id), ['open-screens'],
88
+ 'a live count is a status, glanced at rather than gone to');
89
+ assert.deepStrictEqual(shipped.groups, [], 'and it is not also down the side');
90
+
91
+ write({ global: { order: { screens: ['open-screens'] } } });
92
+ const moved = nav.arrange(groups, 'global');
93
+ assert.deepStrictEqual(moved.topbar.end, [], 'naming it anywhere is saying otherwise');
94
+ assert.deepStrictEqual(moved.groups.map((g) => g.id), ['screens']);
95
+
96
+ write({ global: { topbar: { start: ['open-screens'] } } });
97
+ assert.deepStrictEqual(nav.arrange(groups, 'global').topbar.start.map((i) => i.id), ['open-screens'],
98
+ 'and so is moving it to another region');
99
+
100
+ write({ global: { hidden: ['open-screens'] } });
101
+ const gone = nav.arrange(groups, 'global');
102
+ assert.deepStrictEqual(gone.topbar.end, []);
103
+ assert.deepStrictEqual(gone.groups, [], 'hiding it takes it out of the default placement too');
104
+ });
105
+
106
+ test('what the dashboard ships in the top bar: the assistant and the version, then the screens count', () => {
107
+ reset();
108
+ const groups = [
109
+ { id: 'screens', label: 'Screens', items: [{ ...entry('open-screens', 'Screens'), topbar: 'end' }] },
110
+ { id: 'go', label: 'Go', items: [
111
+ entry('commands', 'Commands'),
112
+ { ...entry('ai-assistant', 'AI Assistant'), topbar: 'start' },
113
+ { ...entry('version', '1.0.12'), topbar: 'start' },
114
+ ] },
115
+ ];
116
+ const out = nav.arrange(groups, 'global');
117
+ assert.deepStrictEqual(out.topbar.start.map((i) => i.id), ['ai-assistant', 'version'],
118
+ 'in the order the page built them, so a default placement does not need an order of its own');
119
+ assert.deepStrictEqual(out.topbar.end.map((i) => i.id), ['open-screens']);
120
+ assert.deepStrictEqual(out.groups.map((g) => g.id), ['go'], 'and neither is also down the side');
121
+ assert.deepStrictEqual(out.groups[0].items.map((i) => i.id), ['commands']);
122
+ });
123
+
124
+ test('hidden beats the top bar: an entry hidden is nowhere at all', () => {
125
+ write({ global: { hidden: ['commands'], topbar: { end: ['commands'] } } });
126
+ const out = nav.arrange(built(), 'global');
127
+ assert.deepStrictEqual(out.topbar.end, []);
128
+ assert.ok(!out.groups.some((g) => g.items.some((i) => i.id === 'commands')));
129
+ });
130
+
131
+ test("a workspace's own settings are applied after the global ones", () => {
132
+ write({ global: { hidden: ['help'] }, workspaces: { dev: { topbar: { start: ['commands'] } } } });
133
+ const onDev = nav.arrange(built(), 'dev');
134
+ assert.deepStrictEqual(onDev.topbar.start.map((i) => i.id), ['commands'], "the workspace's own move applies");
135
+ assert.ok(!onDev.groups.some((g) => g.items.some((i) => i.id === 'help')), 'and the global hide still applies');
136
+
137
+ const elsewhere = nav.arrange(built(), 'global');
138
+ assert.deepStrictEqual(elsewhere.topbar.start, [], "a workspace's move is that workspace's alone");
139
+ });
140
+
141
+ test('scopeFor reads the page context, and anything else is the global scope', () => {
142
+ assert.strictEqual(nav.scopeFor('workspace:dev'), 'dev');
143
+ assert.strictEqual(nav.scopeFor('backups:dev'), 'dev');
144
+ assert.strictEqual(nav.scopeFor('commands:navigation'), 'global');
145
+ assert.strictEqual(nav.scopeFor('home'), 'global');
146
+ assert.strictEqual(nav.scopeFor(undefined), 'global');
147
+ });
148
+
149
+ test('an added entry joins the group it names, or a group of its own at the end', () => {
150
+ write({ global: { items: [{ id: 'jira', label: 'Jira', href: 'https://CHANGE_ME.atlassian.net', group: 'go' }] } });
151
+ const inGo = nav.arrange(built(), 'global').groups.find((g) => g.id === 'go');
152
+ assert.deepStrictEqual(inGo.items.map((i) => i.id), ['all-workspaces', 'commands', 'help', 'jira']);
153
+
154
+ write({ global: { items: [{ id: 'jira', label: 'Jira', href: 'https://CHANGE_ME.atlassian.net' }] } });
155
+ const own = nav.arrange(built(), 'global').groups;
156
+ assert.strictEqual(own[own.length - 1].id, 'yours', 'with no group named it is Yours, at the end');
157
+ });
158
+
159
+ test('an added entry can name a top-bar region as its group and land there', () => {
160
+ write({ global: { items: [{ id: 'jira', label: 'Jira', href: 'https://x.example', group: 'topbar-center' }] } });
161
+ const out = nav.arrange(built(), 'global');
162
+ assert.deepStrictEqual(out.topbar.center.map((i) => i.id), ['jira']);
163
+ assert.ok(!out.groups.some((g) => g.id === 'topbar-center'), 'a region is never a sidebar group');
164
+ });
165
+
166
+ test('an added entry that is a link off this dashboard opens in a new tab, and one inside it does not', () => {
167
+ const away = nav.customItemHtml({ id: 'jira', label: 'Jira', href: 'https://CHANGE_ME.atlassian.net' });
168
+ assert.match(away, /target="_blank"/);
169
+ assert.match(away, /rel="noopener"/);
170
+ const here = nav.customItemHtml({ id: 'dev', label: 'Dev', href: '/dev' });
171
+ assert.ok(!here.includes('target="_blank"'), 'replacing the page you are working on is not what a link here does');
172
+ });
173
+
174
+ test('an added entry that runs a command goes through the same runner and the same confirm dialog', () => {
175
+ const plain = nav.customItemHtml({ id: 'cr', label: 'Rebuild', use: 'drush-cr' });
176
+ assert.match(plain, /hx-post="\/actions\/nav-run"/);
177
+ assert.ok(!plain.includes('arm-step'), 'a command that does not ask is not armed');
178
+ const asks = nav.customItemHtml({ id: 'cr', label: 'Rebuild', use: 'drush-cr', confirm: true });
179
+ assert.match(asks, /arm-step/);
180
+ assert.match(asks, /hx-trigger="confirmed-remove"/, 'the request fires on the dialog alone');
181
+ });
182
+
183
+ test('an icon that is not a name we know falls back rather than reaching the markup', () => {
184
+ const html = nav.customItemHtml({ id: 'x', label: 'X', href: '/x', icon: '"><script>alert(1)</script>' });
185
+ assert.ok(!html.includes('<script>'), 'nothing a person types becomes markup');
186
+ assert.match(html, /icon: link;/, 'and it draws the fallback icon');
187
+ });
188
+
189
+ test('withScope drops what is empty, so the file stays a record of what someone changed', () => {
190
+ const raw = nav.withScope({}, 'global', { hidden: [], groups: [], order: {}, topbar: nav.emptyTopbar(), items: [] });
191
+ assert.deepStrictEqual(raw, {}, 'a scope with nothing in it is not written at all');
192
+
193
+ const one = nav.withScope({}, 'dev', { hidden: ['help'], topbar: { start: ['commands'], center: [], end: [] } });
194
+ assert.deepStrictEqual(one, { workspaces: { dev: { hidden: ['help'], topbar: { start: ['commands'] } } } },
195
+ 'and an empty region is not written either');
196
+ });
197
+
198
+ test('an id is a slug, and anything else is refused rather than escaped into unreadability', () => {
199
+ assert.ok(nav.isId('all-workspaces'));
200
+ assert.ok(!nav.isId('Go To'));
201
+ assert.ok(!nav.isId('../etc'));
202
+ assert.ok(!nav.isId(''));
203
+ assert.strictEqual(nav.regionOf('topbar-end'), 'end');
204
+ assert.strictEqual(nav.regionOf('go'), null);
205
+ assert.ok(nav.RESERVED.has('hidden'), 'Hidden is a place, not a group someone can name');
206
+ });
207
+
208
+ test('the saved file can be read back exactly, and reading it is what a later render sees', () => {
209
+ const raw = { global: { hidden: ['help'], topbar: { end: ['commands'] }, items: [{ id: 'jira', label: 'Jira', href: '/x' }] } };
210
+ write(raw);
211
+ const back = nav.scopeLayout(nav.rawLayout().raw, 'global');
212
+ assert.deepStrictEqual(back.hidden, ['help']);
213
+ assert.deepStrictEqual(back.topbar.end, ['commands']);
214
+ assert.deepStrictEqual(back.items.map((i) => i.id), ['jira']);
215
+ assert.deepStrictEqual(back.topbar.start, [], 'every key is present, so no caller has to test for undefined');
216
+ });
217
+
218
+ test('an entry shows its icon, its text, or both — wherever it sits', () => {
219
+ reset();
220
+ const groups = [{ id: 'go', label: 'Go', items: [entry('commands', 'Commands'), entry('help', 'Help'), entry('settings', 'Settings')] }];
221
+ assert.deepStrictEqual(nav.arrange(groups, 'global').groups[0].items.map((i) => i.show), ['', '', ''],
222
+ 'both is what every entry is until somebody says otherwise');
223
+
224
+ write({ global: { show: { commands: 'icon', help: 'text' } } });
225
+ const out = nav.arrange(groups, 'global').groups[0].items;
226
+ assert.deepStrictEqual(out.map((i) => `${i.id}:${i.show || 'both'}`), ['commands:icon', 'help:text', 'settings:both']);
227
+ });
228
+
229
+ test('it applies in the top bar as much as in the sidebar: it is about room, not about which strip', () => {
230
+ write({ global: { show: { commands: 'icon' }, topbar: { end: ['commands'] } } });
231
+ const out = nav.arrange([{ id: 'go', label: 'Go', items: [entry('commands', 'Commands')] }], 'global');
232
+ assert.deepStrictEqual(out.topbar.end.map((i) => i.show), ['icon']);
233
+ });
234
+
235
+ test('an entry the dashboard ships one way keeps it, and an explicit both is what puts it back', () => {
236
+ reset();
237
+ const shipped = [{ id: 'go', label: 'Go', items: [{ ...entry('ai-assistant', 'AI Assistant'), show: 'icon' }] }];
238
+ assert.strictEqual(nav.arrange(shipped, 'global').groups[0].items[0].show, 'icon',
239
+ 'the assistant is its mark and nothing else until the layout says otherwise');
240
+
241
+ write({ global: { show: { 'ai-assistant': 'both' } } });
242
+ const back = nav.arrange(shipped, 'global').groups[0].items[0];
243
+ assert.strictEqual(back.show, '', 'both, written down, is what overrides a shipped default');
244
+ assert.strictEqual(back.ships, 'icon', 'and the shipped value is still reported, so the editor can offer it');
245
+
246
+ write({ global: { show: { 'ai-assistant': 'text' } } });
247
+ assert.strictEqual(nav.arrange(shipped, 'global').groups[0].items[0].show, 'text');
248
+ });
249
+
250
+ test('a value that is not one of the three is not a value', () => {
251
+ write({ global: { show: { commands: 'sideways', help: 'icon' } } });
252
+ const layout = nav.scopeLayout(nav.rawLayout().raw, 'global');
253
+ assert.deepStrictEqual(layout.show, { help: 'icon' });
254
+ });
@@ -0,0 +1,121 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const folders = require('../../folders');
8
+ const { typeActions } = require('../../workspace-types');
9
+
10
+ const root = path.join(__dirname, '..', '..');
11
+ const server = fs.readFileSync(path.join(root, 'server.js'), 'utf8');
12
+
13
+ test('a folder name is lowercase letters, digits, - and _', () => {
14
+ for (const ok of ['folder1', 'my-app', 'a_b', '9lives']) assert.equal(folders.nameProblem(ok), '', ok);
15
+ for (const bad of ['', 'My App', '../x', '.hidden', '-x', 'a/b', 'x.y', 'A1', 'a'.repeat(64)]) {
16
+ assert.notEqual(folders.nameProblem(bad), '', bad);
17
+ }
18
+ assert.match(folders.nameProblem('node_modules'), /reserved/);
19
+ });
20
+
21
+ test('createFolder makes the folder once, inside the workspace, and refuses the rest', () => {
22
+ const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'));
23
+ try {
24
+ const made = folders.createFolder(ws, 'demo1');
25
+ assert.equal(made.ok, true);
26
+ assert.ok(fs.statSync(path.join(ws, 'demo1')).isDirectory());
27
+ assert.equal(folders.createFolder(ws, 'demo1').status, 409);
28
+ assert.equal(folders.createFolder(ws, '../out').status, 400);
29
+ assert.equal(folders.createFolder(path.join(ws, 'missing'), 'x').status, 404);
30
+ assert.equal(folders.suggestName(ws, 'demo'), 'demo2');
31
+ } finally {
32
+ fs.rmSync(ws, { recursive: true, force: true });
33
+ }
34
+ });
35
+
36
+ test('the form posts the name, the note and what to start, and shows the Claude message only for Claude', () => {
37
+ const html = folders.folderFormHtml('dev', { label: 'Dev', suggestion: 'folder1' });
38
+ assert.match(html, /hx-post="\/actions\/folder-new"/);
39
+ assert.match(html, /name="workspace" value="dev"/);
40
+ assert.match(html, /name="name"[^>]*value="folder1"/s);
41
+ for (const v of ['none', 'shell', 'claude']) assert.match(html, new RegExp(`name="then" value="${v}"`));
42
+ const css = fs.readFileSync(path.join(root, 'public/css/components/folders.css'), 'utf8');
43
+ assert.match(css, /:has\(input\[name="then"\]\[value="claude"\]:checked\) \.folder-prompt \{ display: block; \}/);
44
+ });
45
+
46
+ test('every workspace type takes new folders except the AI items, where a folder is a skill', () => {
47
+ for (const t of ['sites', 'code', 'files', 'media']) assert.ok(typeActions(t).has('folder'), t);
48
+ assert.ok(!typeActions('ai-items').has('folder'));
49
+ });
50
+
51
+ test('a files workspace makes the folder inside the one being viewed, with readable names and nothing to set up', () => {
52
+ assert.equal(folders.nameProblem('Webship Demo Guide 2.0', { files: true }), '');
53
+ assert.notEqual(folders.nameProblem('Webship Demo Guide', {}), '');
54
+ for (const bad of ['..', '.x', 'a/b', 'ends.', 'ends ']) assert.notEqual(folders.nameProblem(bad, { files: true }), '', bad);
55
+ const html = folders.filesFolderFormHtml('docs', { label: 'Docs', sub: 'guides', suggestion: 'new-folder-1' });
56
+ assert.match(html, /New folder in Docs \/ guides/);
57
+ assert.match(html, /name="sub" value="guides"/);
58
+ assert.doesNotMatch(html, /name="ddev"|name="then"/);
59
+ const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'));
60
+ try {
61
+ assert.equal(folders.createFolder(ws, 'My Guide', { files: true }).ok, true);
62
+ assert.equal(folders.createFolder(ws, 'My Guide').status, 400);
63
+ } finally { fs.rmSync(ws, { recursive: true, force: true }); }
64
+ });
65
+
66
+ test('the files workspaces offer New folder in their Create group, and the form reads the folder being viewed', () => {
67
+ assert.match(server, /create\.push\(btn\('default', 'tabler:folder-plus', 'New folder', `hx-get="\/fragments\/\$\{esc\(key\)\}\/folder-new" \$\{modal\}`/);
68
+ assert.match(server, /meta\.kind !== 'files' && require\('\.\/actions\/folders'\)\.canMakeFolder\(key\)/);
69
+ assert.match(server, /require\('\.\/actions\/folders'\)\.folderFormFor\(fm\[1\], req, \{ readPageState \}\)/);
70
+ const src = fs.readFileSync(path.join(root, 'actions/folders.js'), 'utf8');
71
+ assert.match(src, /req\.headers\['hx-current-url'\]/);
72
+ assert.match(src, /if \(isFilesWorkspace\(ws\)\) return filesFolderNew/);
73
+ assert.match(src, /safeSub\(ws, String\(form\.sub \|\| ''\)\)/);
74
+ });
75
+
76
+ test('the Create group, the form route and the action are wired in', () => {
77
+ assert.match(server, /btn\('default', 'tabler:folder-plus', 'New folder'/);
78
+ assert.match(server, /\\\/fragments\\\/\(\[a-z0-9_-\]\+\)\\\/folder-new\$/);
79
+ assert.match(server, /\.\.\.require\('\.\/actions\/folders'\)/);
80
+ const action = require('../../actions/folders');
81
+ assert.equal(typeof action['/actions/folder-new'], 'function');
82
+ });
83
+
84
+ test('the form offers Configure DDEV, and webship-js only where the workspace can test', () => {
85
+ const types = require('../../actions/folders').projectTypes();
86
+ assert.ok(types.includes('php') && types.includes('drupal11'));
87
+ const withTests = folders.folderFormHtml('dev', { projectTypes: types, canTest: true });
88
+ assert.match(withTests, /name="ddev" value="1"/);
89
+ assert.match(withTests, /name="e2e" value="1"/);
90
+ assert.match(withTests, /<option value="php" selected>/);
91
+ assert.doesNotMatch(folders.folderFormHtml('modules', { projectTypes: types, canTest: false }), /name="e2e"/);
92
+ const css = fs.readFileSync(path.join(root, 'public/css/components/folders.css'), 'utf8');
93
+ assert.match(css, /:has\(\.folder-setup input:checked\) \.folder-ddev-type \{ display: block; \}/);
94
+ });
95
+
96
+ test('the setup line chains DDEV, the webship-js setup and what the plugins add, in that order', () => {
97
+ const fa = require('../../actions/folders');
98
+ const line = fa.setupLine('dev', 'demo1', { ddev: '1', e2e: '1', project_type: 'php' });
99
+ assert.ok(line.startsWith("( cd 'demo1' && ddev 'config' '--auto=false' '--project-name=demo1' '--project-type=php' '--docroot=' )"), line);
100
+ assert.ok(line.indexOf("bash cmd-tools-testing.sh 'demo1'") > line.indexOf("'config'"));
101
+ // webship-js alone still configures DDEV first, and an unknown type falls back to php.
102
+ assert.match(fa.setupLine('dev', 'demo1', { e2e: '1', project_type: 'nope' }), /'--project-type=php'/);
103
+ assert.equal(fa.setupLine('dev', 'demo1', {}), '');
104
+ });
105
+
106
+ test('the AI tasks go to one Claude Code prompt, and the form is checked before anything is made', () => {
107
+ const fa = require('../../actions/folders');
108
+ const p = fa.claudePrompt('dev', 'demo1', { note: 'Try search', prompt: 'Then say hello' });
109
+ assert.ok(p.endsWith('Then say hello'));
110
+ assert.equal(fa.claudePrompt('dev', 'demo1', {}), '');
111
+ assert.equal(fa.formProblem('dev', {}), '');
112
+ });
113
+
114
+ test('the form offers the sections plugins hand it, under Connect it', () => {
115
+ const html = folders.folderFormHtml('dev', { pluginSetups: '<div class="x-plugin-setup">cloud</div>' });
116
+ assert.match(html, /Connect it/, 'the section opens when a plugin hands something in');
117
+ assert.match(html, /x-plugin-setup/, 'a plugin section is under Connect it');
118
+ const bare = folders.folderFormHtml('modules', {});
119
+ assert.doesNotMatch(bare, /Connect it/);
120
+ const css = fs.readFileSync(path.join(root, 'public/css/components/folders.css'), 'utf8');
121
+ });
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The New screen form.
3
+ *
4
+ * It asked four questions in one row of controls, and one of them was a select of 309 entries — every
5
+ * workspace folder and every project in the tree, flat. Now it asks them in the order they are answered:
6
+ * where it runs (the workspace, then what is inside it), what it starts, its arguments, and the prompt.
7
+ *
8
+ * Three things must not drift. The place is picked in TWO lists, so neither is long. The session name is
9
+ * pre-filled with where it runs, because that is what `screen -ls` shows and what `screen -r` takes —
10
+ * but a name typed by hand is never overwritten. And the Claude arguments come from `claude --help`,
11
+ * so a free-text box of flags nobody checks is not the way back in.
12
+ */
13
+
14
+ const assert = require('node:assert');
15
+ const { test } = require('node:test');
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const read = (f) => fs.readFileSync(path.join(__dirname, '..', '..', f), 'utf8');
20
+ const screens = require('../../screens');
21
+ const src = read('screens.js');
22
+ const ui = read('public/ui.js');
23
+ const action = read('actions/screens.js');
24
+ const server = read('server.js');
25
+
26
+ test('the place is two lists, not one of three hundred', () => {
27
+ // One list of the workspaces, then one of what is inside the chosen one. The workspace list is short
28
+ // whatever the tree holds; the long one only ever shows a single workspace's projects.
29
+ const workspaces = screens.workspaceOptionsHtml('root');
30
+ assert.ok((workspaces.match(/<option/g) || []).length < 40, 'the workspace list is short enough to read');
31
+ assert.match(workspaces, /value="root"/, 'the workspace root is one of them');
32
+ assert.match(src, /const inside = screenTargets\(\)\.filter\(\(t\) => t\.value === `ws:\$\{key\}` \|\| t\.value\.startsWith\(`p:\$\{key\}:`\)\);/,
33
+ 'and the second list is scoped to that workspace, not the whole tree');
34
+ });
35
+
36
+ test('a workspace folder is a place in its own right, listed first', () => {
37
+ assert.match(src, /const folder = t\.value === `ws:\$\{key\}`;/);
38
+ assert.match(src, /folder \? `the \$\{key\}\/ folder itself`/,
39
+ 'the folder itself is an option that says what it is, not something to notice is missing');
40
+ });
41
+
42
+ test('every place carries where it is, so the path follows the choice with no request', () => {
43
+ assert.match(src, /data-path="\$\{esc\(tildePath\(t\.dir\)\)\}"/);
44
+ assert.match(ui, /line\.textContent = path\.path;/);
45
+ });
46
+
47
+ test('reopening the form lands on the list the target was picked from', () => {
48
+ assert.strictEqual(screens.workspaceOfTarget('p:dev:ha1x00b1'), 'dev');
49
+ assert.strictEqual(screens.workspaceOfTarget('ws:modules'), 'modules');
50
+ assert.strictEqual(screens.workspaceOfTarget('root'), 'root');
51
+ assert.strictEqual(screens.workspaceOfTarget(''), 'root');
52
+ });
53
+
54
+ test('the session name is pre-filled with where it runs, and a short id of its own', () => {
55
+ assert.strictEqual(screens.defaultSessionName('p:dev:ha1x00b1', '7f3a'), 'dev--ha1x00b1--7f3a');
56
+ assert.strictEqual(screens.defaultSessionName('ws:modules', '7f3a'), 'modules--7f3a');
57
+ assert.strictEqual(screens.defaultSessionName('root', '7f3a'), 'workspace-7f3a',
58
+ "not 'root': in screen -ls that reads as the user it runs as, which it is not");
59
+ assert.match(screens.defaultSessionName('p:dev:Weird Name!'), /^[a-z0-9][a-z0-9._-]*$/,
60
+ 'and it is a name screen(1) accepts');
61
+ });
62
+
63
+ test('the id makes it startable twice: two names for one place are not the same name', () => {
64
+ const a = screens.defaultSessionName('p:dev:ha1x00b1');
65
+ const b = screens.defaultSessionName('p:dev:ha1x00b1');
66
+ assert.notStrictEqual(a, b, 'a second session in one project was refused as a name that already exists');
67
+ assert.match(a, /^dev--ha1x00b1--[0-9a-f]{4}$/);
68
+ });
69
+
70
+ test('the pre-filled name already says where it runs, so the endpoint does not say it again', () => {
71
+ const name = screens.defaultSessionName('p:dev:ha1x00b1', '7f3a');
72
+ assert.ok(name.startsWith('dev--ha1x00b1--'), 'which is exactly what the endpoint checks for');
73
+ });
74
+
75
+ test('a name typed by hand is never overwritten by the place changing', () => {
76
+ assert.match(ui, /if \(field\.value && field\.value !== field\.dataset\.derived\) return;/);
77
+ assert.match(src, /data-derived="\$\{esc\(prefilled\)\}"/,
78
+ 'the rendered default says it was derived, or the first change would look hand-typed');
79
+ assert.match(src, /data-tail="\$\{esc\(prefilled\.slice\(-4\)\)\}"/,
80
+ 'and carries its id, so changing the place does not roll a new one every time');
81
+ });
82
+
83
+ test('a name that already says where it runs is not made to say it twice', () => {
84
+ assert.match(action, /const said = prefix && \(`\$\{session\}--` === prefix \|\| session\.startsWith\(prefix\)\);/,
85
+ 'dev--ha1x00b1 must not become dev--ha1x00b1--dev--ha1x00b1');
86
+ });
87
+
88
+ test('the free-text flags box is gone, and nothing reads it any more', () => {
89
+ assert.ok(!/name="options"/.test(src), 'the box that meant knowing every flag by heart is not rendered');
90
+ assert.ok(!action.includes("form.options"), 'and the endpoint no longer takes a string of flags');
91
+ assert.match(action, /claudeArgs\.flagsFromForm\(options, form\)/);
92
+ assert.match(action, /if \(problems\.length\)/, 'a value it will not pass on is said out loud');
93
+ });
94
+
95
+ test('the arguments are the installed CLI own, not a list kept here by hand', () => {
96
+ assert.match(src, /await claudeArgs\.claudeOptions\(\)/);
97
+ assert.match(src, /Read from <code>claude --help<\/code> on this machine/);
98
+ assert.match(src, /The Claude Code options could not be read from/,
99
+ 'a machine with no claude still gets a form, and is told why it is shorter');
100
+ });
101
+
102
+ test('each argument gets the control its own shape asks for', () => {
103
+ assert.match(src, /if \(opt\.takes === 'none'\) \{[\s\S]{0,200}type="checkbox"/);
104
+ assert.match(src, /opt\.choices\.length[\s\S]{0,80}<select/);
105
+ assert.match(src, /opt\.repeatable \? ' — space separated' : ''/);
106
+ });
107
+
108
+ test('the four reached for every time are in front of the other thirty', () => {
109
+ assert.match(src, /claudeArgs\.COMMON\.map/);
110
+ assert.match(src, /<summary>All Claude Code options \(\$\{rest\.length\}\)<\/summary>/);
111
+ });
112
+
113
+ test('a prompt template puts its flags on the controls that own them', () => {
114
+ assert.match(ui, /window\.screenPromptTemplate = function screenPromptTemplate/);
115
+ assert.match(ui, /if \(field\.type === 'checkbox'\) \{ field\.checked = true; continue; \}/);
116
+ assert.ok(!src.includes('f.options.value = fl'), 'not into a text box that no longer exists');
117
+ });
118
+
119
+ test('the kind is three named choices, and it decides what the rest of the form asks', () => {
120
+ assert.match(src, /type="radio" name="kind" value="\$\{value\}"/);
121
+ assert.match(ui, /for \(const el of form\.querySelectorAll\('\[data-claude-only\]'\)\) el\.hidden = kind !== 'claude';/);
122
+ assert.match(ui, /for \(const el of form\.querySelectorAll\('\[data-not-claude\]'\)\) el\.hidden = kind === 'claude';/);
123
+ });
124
+
125
+ test('the form arrives in a modal, so its state is settled on the swap rather than on load', () => {
126
+ assert.match(ui, /document\.body\.addEventListener\('htmx:after:settle'/);
127
+ assert.match(ui, /el\.id === 'screen-where' \? el : el\.querySelector\('#screen-where'\)/);
128
+ });
129
+
130
+ test('changing the workspace or the place does not close the modal', () => {
131
+ const where = src.slice(src.indexOf('function screenWhereHtml'), src.indexOf('function claudeArgFieldHtml'));
132
+ assert.match(where, /hx-on::after:request="event\.stopPropagation\(\)"/,
133
+ "the form's own after:request hides the modal — these requests must not reach it");
134
+ assert.match(src, /hx-get="\/fragments\/screen-where"/);
135
+ assert.match(server, /pathname === '\/fragments\/screen-where'/);
136
+ });
137
+
138
+ test('the resume row is per DIRECTORY, so a workspace folder has one exactly as a project does', () => {
139
+ assert.match(src, /a workspace folder has its own sessions exactly\s*\n?\s*\*? ?as a project does|folder has its own sessions exactly as a project does/,
140
+ 'stated where somebody would look for it');
141
+ assert.match(src, /<div id="claude-resume-row">\$\{claudeResumeHtml\(p\.target\)\}<\/div>/);
142
+ assert.match(src, /hx-target="#claude-resume-row"/, 'and it reloads when the place changes');
143
+ });
144
+
145
+ test('an empty session name is filled in, not refused', () => {
146
+ assert.match(action, /String\(form\.session \|\| ''\)\.trim\(\) \|\| defaultSessionName\(form\.target\)/,
147
+ 'refusing it made the one field nobody wants to think about the one that fails');
148
+ assert.ok(!/name="session" id="screen-session" required/.test(src), 'so the field is not required either');
149
+ });