@webship.co/workspace 1.0.0-beta1 → 1.0.0-rc1

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 (632) 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} +9 -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/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
  169. package/{server.js → core/workspace-app/server.js} +18 -0
  170. package/core/workspace-app/tests/features/actions-rail.feature +44 -0
  171. package/core/workspace-app/tests/features/ai-items.feature +23 -0
  172. package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
  173. package/core/workspace-app/tests/features/checks.feature +40 -0
  174. package/core/workspace-app/tests/features/clone-project.feature +30 -0
  175. package/core/workspace-app/tests/features/command-authoring.feature +58 -0
  176. package/core/workspace-app/tests/features/dashboard.feature +57 -0
  177. package/core/workspace-app/tests/features/menus.feature +29 -0
  178. package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
  179. package/core/workspace-app/tests/features/plugins.feature +20 -0
  180. package/core/workspace-app/tests/features/project-actions.feature +41 -0
  181. package/core/workspace-app/tests/features/screens.feature +29 -0
  182. package/core/workspace-app/tests/features/settings.feature +46 -0
  183. package/core/workspace-app/tests/features/test-reports.feature +32 -0
  184. package/core/workspace-app/tests/features/theme.feature +40 -0
  185. package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
  186. package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +157 -0
  187. package/core/workspace-app/tests/step-definitions/clone-project.steps.js +47 -0
  188. package/core/workspace-app/tests/step-definitions/preconditions.steps.js +168 -0
  189. package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
  190. package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
  191. package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
  192. package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
  193. package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
  194. package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
  195. package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
  196. package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
  197. package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
  198. package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
  199. package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
  200. package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
  201. package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
  202. package/core/workspace-app/tests/unit/assistant.test.js +61 -0
  203. package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
  204. package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
  205. package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
  206. package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
  207. package/core/workspace-app/tests/unit/browsers.test.js +76 -0
  208. package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
  209. package/core/workspace-app/tests/unit/build-order.test.js +153 -0
  210. package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
  211. package/core/workspace-app/tests/unit/builders.test.js +158 -0
  212. package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
  213. package/core/workspace-app/tests/unit/changelog.test.js +223 -0
  214. package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
  215. package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
  216. package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
  217. package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
  218. package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
  219. package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
  220. package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
  221. package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
  222. package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
  223. package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
  224. package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
  225. package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
  226. package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
  227. package/core/workspace-app/tests/unit/create-card.test.js +85 -0
  228. package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
  229. package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
  230. package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
  231. package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
  232. package/core/workspace-app/tests/unit/ddev-domains.test.js +179 -0
  233. package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
  234. package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
  235. package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
  236. package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
  237. package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
  238. package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
  239. package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
  240. package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
  241. package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
  242. package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
  243. package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
  244. package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
  245. package/core/workspace-app/tests/unit/density.test.js +52 -0
  246. package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
  247. package/core/workspace-app/tests/unit/event-log.test.js +102 -0
  248. package/core/workspace-app/tests/unit/favicon.test.js +68 -0
  249. package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
  250. package/core/workspace-app/tests/unit/git-info.test.js +26 -0
  251. package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
  252. package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
  253. package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
  254. package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
  255. package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
  256. package/core/workspace-app/tests/unit/images-create.test.js +59 -0
  257. package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
  258. package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
  259. package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
  260. package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
  261. package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
  262. package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
  263. package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
  264. package/core/workspace-app/tests/unit/narration.test.js +186 -0
  265. package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
  266. package/core/workspace-app/tests/unit/navigation.test.js +254 -0
  267. package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
  268. package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
  269. package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
  270. package/core/workspace-app/tests/unit/notes.test.js +128 -0
  271. package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
  272. package/core/workspace-app/tests/unit/page-width.test.js +59 -0
  273. package/core/workspace-app/tests/unit/php-version.test.js +90 -0
  274. package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
  275. package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
  276. package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
  277. package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
  278. package/core/workspace-app/tests/unit/plugins.test.js +58 -0
  279. package/core/workspace-app/tests/unit/preconditions.test.js +21 -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 +93 -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 +220 -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 +436 -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/package.json +38 -36
  336. /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
  337. /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
  338. /package/{actions → core/workspace-app/actions}/audio.js +0 -0
  339. /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
  340. /package/{actions → core/workspace-app/actions}/checks.js +0 -0
  341. /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
  342. /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
  343. /package/{actions → core/workspace-app/actions}/composer.js +0 -0
  344. /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
  345. /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
  346. /package/{actions → core/workspace-app/actions}/folders.js +0 -0
  347. /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
  348. /package/{actions → core/workspace-app/actions}/images.js +0 -0
  349. /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
  350. /package/{actions → core/workspace-app/actions}/node.js +0 -0
  351. /package/{actions → core/workspace-app/actions}/notes.js +0 -0
  352. /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
  353. /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
  354. /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
  355. /package/{actions → core/workspace-app/actions}/screens.js +0 -0
  356. /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
  357. /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
  358. /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
  359. /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
  360. /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
  361. /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
  362. /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
  363. /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
  364. /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
  365. /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
  366. /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
  367. /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
  368. /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
  369. /package/{checks.js → core/workspace-app/checks.js} +0 -0
  370. /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
  371. /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
  372. /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
  373. /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
  374. /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
  375. /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
  376. /package/{commands.js → core/workspace-app/commands.js} +0 -0
  377. /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
  378. /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
  379. /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
  380. /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
  381. /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
  382. /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
  383. /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
  384. /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
  385. /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
  386. /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
  387. /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
  388. /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
  389. /package/{docs → core/workspace-app/docs}/README.md +0 -0
  390. /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
  391. /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
  392. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
  393. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
  394. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
  395. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
  396. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
  397. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  398. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
  399. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
  400. /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
  401. /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
  402. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
  403. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
  404. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
  405. /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
  406. /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
  407. /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
  408. /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
  409. /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
  410. /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
  411. /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
  412. /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
  413. /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
  414. /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
  415. /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
  416. /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
  417. /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
  418. /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
  419. /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
  420. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
  421. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
  422. /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
  423. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
  424. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
  425. /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
  426. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
  427. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
  428. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
  429. /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
  430. /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
  431. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
  432. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
  433. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
  434. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
  435. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
  436. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
  437. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
  438. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
  439. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
  440. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
  441. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
  442. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
  443. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
  444. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
  445. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
  446. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
  447. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
  448. /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
  449. /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
  450. /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
  451. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
  452. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
  453. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
  454. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
  455. /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
  456. /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
  457. /package/{folders.js → core/workspace-app/folders.js} +0 -0
  458. /package/{format.js → core/workspace-app/format.js} +0 -0
  459. /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
  460. /package/{html.js → core/workspace-app/html.js} +0 -0
  461. /package/{icons.js → core/workspace-app/icons.js} +0 -0
  462. /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
  463. /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
  464. /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
  465. /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
  466. /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
  467. /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
  468. /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
  469. /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
  470. /package/{images.js → core/workspace-app/images.js} +0 -0
  471. /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
  472. /package/{linger.js → core/workspace-app/linger.js} +0 -0
  473. /package/{lists.js → core/workspace-app/lists.js} +0 -0
  474. /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
  475. /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
  476. /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
  477. /package/{notes.js → core/workspace-app/notes.js} +0 -0
  478. /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
  479. /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
  480. /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
  481. /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
  482. /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
  483. /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
  484. /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
  485. /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
  486. /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
  487. /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
  488. /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
  489. /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
  490. /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
  491. /package/{public → core/workspace-app/public}/css/base.css +0 -0
  492. /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
  493. /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
  494. /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
  495. /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
  496. /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
  497. /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
  498. /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
  499. /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
  500. /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
  501. /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
  502. /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
  503. /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
  504. /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
  505. /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
  506. /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
  507. /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
  508. /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
  509. /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
  510. /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
  511. /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
  512. /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
  513. /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
  514. /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
  515. /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
  516. /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
  517. /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
  518. /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
  519. /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
  520. /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
  521. /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
  522. /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
  523. /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
  524. /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
  525. /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
  526. /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
  527. /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
  528. /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
  529. /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
  530. /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
  531. /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
  532. /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
  533. /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
  534. /package/{public → core/workspace-app/public}/favicon.ico +0 -0
  535. /package/{public → core/workspace-app/public}/favicon.png +0 -0
  536. /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
  537. /package/{public → core/workspace-app/public}/logo.png +0 -0
  538. /package/{public → core/workspace-app/public}/media-player.js +0 -0
  539. /package/{public → core/workspace-app/public}/mic.js +0 -0
  540. /package/{public → core/workspace-app/public}/narration.js +0 -0
  541. /package/{public → core/workspace-app/public}/palette.js +0 -0
  542. /package/{public → core/workspace-app/public}/sortable.js +0 -0
  543. /package/{public → core/workspace-app/public}/suggest.js +0 -0
  544. /package/{public → core/workspace-app/public}/terminal.js +0 -0
  545. /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
  546. /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
  547. /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
  548. /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
  549. /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
  550. /package/{public → core/workspace-app/public}/ui.js +0 -0
  551. /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
  552. /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
  553. /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
  554. /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
  555. /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
  556. /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
  557. /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
  558. /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
  559. /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
  560. /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
  561. /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
  562. /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
  563. /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
  564. /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
  565. /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
  566. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
  567. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  568. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  569. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
  570. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  571. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  572. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  573. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  574. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
  575. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  576. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
  577. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  578. /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
  579. /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
  580. /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
  581. /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
  582. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
  583. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
  584. /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
  585. /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
  586. /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
  587. /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
  588. /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
  589. /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
  590. /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
  591. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
  592. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
  593. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
  594. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
  595. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
  596. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
  597. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
  598. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
  599. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
  600. /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
  601. /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
  602. /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
  603. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
  604. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
  605. /package/{rag.js → core/workspace-app/rag.js} +0 -0
  606. /package/{reader.js → core/workspace-app/reader.js} +0 -0
  607. /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
  608. /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
  609. /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
  610. /package/{screenrc → core/workspace-app/screenrc} +0 -0
  611. /package/{screens.js → core/workspace-app/screens.js} +0 -0
  612. /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
  613. /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
  614. /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
  615. /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
  616. /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
  617. /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
  618. /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
  619. /package/{speech.js → core/workspace-app/speech.js} +0 -0
  620. /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
  621. /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
  622. /package/{themes.js → core/workspace-app/themes.js} +0 -0
  623. /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
  624. /package/{urls.js → core/workspace-app/urls.js} +0 -0
  625. /package/{validate.js → core/workspace-app/validate.js} +0 -0
  626. /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
  627. /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
  628. /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
  629. /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
  630. /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
  631. /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
  632. /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * How the assistant is shown: sidebar, wide, floating, full page — plus the switch that closes it.
3
+ *
4
+ * Modelled on Drupal AI's own chatbot (a toggle in the admin toolbar, a menu in the panel header
5
+ * carrying Clear history and Close, an expand control, and the state on the document element), so
6
+ * what is checked here is the part that would break quietly: the state must be applied BEFORE first
7
+ * paint, the view must be a default rather than an override, and the full-page view must not leave
8
+ * two copies of one conversation on the screen.
9
+ */
10
+
11
+ const assert = require('node:assert');
12
+ const { test } = require('node:test');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const app = path.join(__dirname, '..', '..');
17
+ const server = fs.readFileSync(path.join(app, 'server.js'), 'utf8');
18
+ const ui = fs.readFileSync(path.join(app, 'public', 'ui.js'), 'utf8');
19
+ const css = fs.readFileSync(path.join(app, 'public', 'css', 'components', 'assistant-views.css'), 'utf8');
20
+ const { styleSettings } = require(path.join(app, 'workspaces'));
21
+
22
+ test('the view and the open state are settings, with a fallback rather than a blank panel', () => {
23
+ const s = styleSettings();
24
+ assert.ok(['sidebar', 'wide', 'float', 'full'].includes(s.assistantView), s.assistantView);
25
+ assert.ok(['yes', 'no'].includes(s.assistantOpen), s.assistantOpen);
26
+ });
27
+
28
+ test('the state is applied before first paint, and a stored choice wins over the setting', () => {
29
+ const head = server.slice(server.indexOf("var d=document.documentElement"), server.indexOf('</script>'));
30
+ // Read through wsPref: the stored choice is a cookie on the hub domain, so it is the same choice on
31
+ // every workspace subdomain rather than one per origin.
32
+ assert.match(head, /wsPref\('ws-assistant-view',null\)\|\|'\$\{style\.assistantView\}'/,
33
+ 'the setting is the default, the stored choice wins — as with style.mode');
34
+ assert.match(head, /indexOf\(v\)<0\)v='sidebar'/, 'an unknown view must fall back, not render nothing');
35
+ assert.match(head, /d\.dataset\.assistantView=v/);
36
+ assert.match(head, /classList\.add\('assistant-closed'\)/);
37
+ // The panel is 25rem of the layout: deciding this in the deferred ui.js would draw the page one
38
+ // way and reflow it a frame later, which is the bug the rail's own pre-paint line exists for. So
39
+ // ui.js may only CHANGE the class, from the one function a click reaches.
40
+ const setsClosed = ui.match(/classList\.toggle\('assistant-closed'/g) || [];
41
+ assert.strictEqual(setsClosed.length, 1, 'only setAssistantOpen may write that class');
42
+ const fn = ui.slice(ui.indexOf('function setAssistantOpen'), ui.indexOf('function setAssistantOpen') + 400);
43
+ assert.match(fn, /classList\.toggle\('assistant-closed', !open\)/, 'and that is where it lives');
44
+ });
45
+
46
+ test('the panel header carries the menu, wider and close — and the menu carries the rest', () => {
47
+ // Up to the close button: the avatar's own menu (when it is on) comes first and is long.
48
+ const from = server.indexOf('class="assistant-controls"');
49
+ const head = server.slice(from, server.indexOf('assistant-hide', from) + 200);
50
+ assert.match(head, /assistant-ctl[^>]*Assistant options/, 'the kebab');
51
+ assert.match(head, /assistant-wider/, 'wider');
52
+ assert.match(head, /assistant-hide/, 'close');
53
+ // The items themselves are one function, since the toolbar hangs the same menu.
54
+ const menu = server.slice(server.indexOf('function assistantMenuItemsHtml'), server.indexOf('const AI_MARK'));
55
+ assert.match(menu, /hx-post="\/actions\/chat-reset"[\s\S]*?hx-trigger="confirmed-remove"/,
56
+ 'New chat must ask first, like every other action that throws something away');
57
+ assert.match(menu, /class="assistant-view-pick" data-view="\$\{v\}"/, 'the views are rendered from a list');
58
+ assert.match(menu, /assistant-close-pick/, 'and closing it is offered where the views are');
59
+ for (const [v, label] of [['sidebar', 'Sidebar'], ['wide', 'Wide'], ['float', 'Floating'], ['full', 'Full page']]) {
60
+ assert.match(menu, new RegExp(`\\['${v}', '${label}'`), `no way to choose ${v}`);
61
+ }
62
+ });
63
+
64
+ test('the control is in the rail, not in the toolbar', () => {
65
+ // The toolbar is the corner the brand and the page identity own. The rail is where the things that
66
+ // apply to whatever page you are on live, which is exactly what this is.
67
+ const left = server.slice(server.indexOf('<div class="uk-navbar-left">'), server.indexOf('<div class="uk-navbar-right">'));
68
+ assert.ok(!/assistant-toggle|assistant-switch/.test(left), 'the toolbar must not carry the switch');
69
+ const rail = server.slice(server.indexOf("label: 'Go'"), server.indexOf("] });", server.indexOf("label: 'Go'")));
70
+ assert.match(rail, /<button type="button" class="action-btn actions-assistant/,
71
+ 'the rail row is the control, and a real button — UIKit puts aria-haspopup on whatever precedes the '
72
+ + 'dropdown, and that attribute is not allowed on a span');
73
+ assert.match(rail, /\$\{assistantMenuItemsHtml\(\)\}/, 'and it carries the whole menu');
74
+ });
75
+
76
+ test('show and hide are two items the stylesheet picks between', () => {
77
+ const menu = server.slice(server.indexOf('function assistantMenuItemsHtml'), server.indexOf('const AI_MARK'));
78
+ assert.match(menu, /assistant-when-closed[\s\S]*?assistant-open-pick/, 'Show, for when it is hidden');
79
+ assert.match(menu, /assistant-when-open[\s\S]*?assistant-close-pick/, 'Hide, for when it is not');
80
+ // Neither needs a script to rename itself: which applies is a state the stylesheet already knows.
81
+ assert.match(css, /html\.assistant-closed \.assistant-when-open,\s*\n\s*html:not\(\.assistant-closed\) \.assistant-when-closed \{ display: none; \}/);
82
+ });
83
+
84
+ test('the full-page view is a place, not a layout', () => {
85
+ assert.match(ui, /if \(view === 'full'\)[\s\S]*?window\.location\.href = '\/assistant'/,
86
+ 'choosing it must go there');
87
+ assert.match(ui, /window\.location\.pathname === '\/assistant'[\s\S]*?referrer/,
88
+ 'leaving it must leave the page too — /assistant has no sidebar to become');
89
+ assert.match(server, /pathname === '\/assistant'/, 'the page itself');
90
+ // Two panels on one screen would be two views of one conversation, both live.
91
+ assert.match(server, /context === 'assistant' \? '' : `<aside class="assistant-side">/,
92
+ 'the page must not also carry the docked panel');
93
+ assert.match(css, /html\[data-assistant-view="full"\] \.assistant-side \{ display: none; \}/);
94
+ });
95
+
96
+ test('the full-page view is escapable from an ordinary page', () => {
97
+ // In that view no page but /assistant draws a panel, so the panel's own menu is not there to use.
98
+ // The toolbar carries the same menu, or picking Full page and then navigating anywhere would leave
99
+ // no way back to a sidebar — the tile alone only leads back to /assistant.
100
+ assert.match(server, /function assistantMenuItemsHtml\(\)/, 'one menu, hung from two places');
101
+ // To the end of the control, not a fixed window: the comments around it grow, and a slice that is
102
+ // one character short reads as the menu having been taken away.
103
+ const railStart = server.indexOf('class="uk-inline actions-assistant-wrap"');
104
+ const rail = server.slice(railStart, server.indexOf('</div>`', railStart));
105
+ assert.match(rail, /\$\{assistantMenuItemsHtml\(\)\}/, 'the rail carries the menu, so any page can change the view');
106
+ const header = server.slice(server.indexOf('class="uk-inline assistant-menu"'), server.indexOf('class="uk-inline assistant-menu"') + 600);
107
+ assert.match(header, /\$\{assistantMenuItemsHtml\(\)\}/, 'and it must be the same menu, not a second copy');
108
+ });
109
+
110
+ test('showing it does not pretend to open a page that is already open', () => {
111
+ // On /assistant the assistant IS the page: writing a closed/open state there set something nobody
112
+ // could see, which then decided the panel on the next ordinary page for no reason the user gave.
113
+ const handler = ui.slice(ui.indexOf("e.target.closest('.assistant-open-pick')"), ui.indexOf("e.target.closest('.assistant-open-pick')") + 500);
114
+ assert.match(handler, /if \(window\.location\.pathname === '\/assistant'\) return;/);
115
+ assert.ok(handler.indexOf("pathname === '/assistant'") < handler.indexOf('setAssistantOpen'),
116
+ 'it must return before writing the open state');
117
+ assert.match(handler, /assistantView\(\) === 'full'[\s\S]*?window\.location\.href = '\/assistant'/,
118
+ 'and in the full-page view showing it means going there');
119
+ });
120
+
121
+ test('the floating panel can be moved, and only that one', () => {
122
+ // Where a floating panel should sit depends on what is under it, and that changes page by page.
123
+ assert.match(ui, /function draggableAssistant/);
124
+ assert.match(ui, /dataset\.assistantView !== 'float'\) return;/, 'only the floating view is draggable');
125
+ // A drag that began on the close button would close the panel on release.
126
+ assert.match(ui, /e\.target\.closest\('button, a, input, select, \.assistant-dropdown'\)\) return;/,
127
+ 'the header is the handle, minus its controls');
128
+ assert.match(ui, /const maxX = Math\.max\(0, window\.innerWidth - r\.width\)/, 'it is clamped into the viewport');
129
+ assert.match(ui, /window\.addEventListener\('resize', applyStored\)/,
130
+ 'a window that got smaller must not leave it off-screen');
131
+ // Leftover inline positions would fight the stylesheet in every other view.
132
+ assert.match(ui, /el\.style\.left = el\.style\.top = el\.style\.bottom = el\.style\.right = '';/);
133
+ assert.match(css, /html\[data-assistant-view="float"\] \.assistant-side \.assistant-head \{\s*\n\s*cursor: grab;/,
134
+ 'and it has to say it can be dragged');
135
+ });
136
+
137
+ test('the launcher is the way back in, in every view', () => {
138
+ // The control lives in the rail now, and a collapsed rail is an icon strip a newcomer has no reason
139
+ // to search — so a closed assistant must leave something on the page that opens it.
140
+ assert.match(css, /\.assistant-launcher \{ display: none; \}/, 'not while the panel is open');
141
+ assert.match(css, /html\.assistant-closed \.assistant-launcher \{/, 'and in every view when it is closed');
142
+ assert.ok(!/html\[data-assistant-view="float"\]\.assistant-closed \.assistant-launcher \{/.test(css),
143
+ 'not the floating view alone any more');
144
+ assert.match(ui, /closest\('\.assistant-launcher'\)[\s\S]*?assistantView\(\) === 'full'[\s\S]*?'\/assistant'/,
145
+ 'and in the full-page view it leads to the page, since there is no panel to show');
146
+ });
@@ -0,0 +1,49 @@
1
+ const { test } = require('node:test');
2
+ const assert = require('node:assert');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const app = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
7
+ const ui = app('public/ui.js');
8
+ const css = app('public/css/components/working-indicators.css');
9
+
10
+ test('the Send button shows three animated dots while the assistant works, in place of the arrow', () => {
11
+ assert.ok(ui.includes(':host([data-assistant-working]) .input-button.inside-end::after'));
12
+ assert.ok(ui.includes('animation: ws-dots 1.2s ease-in-out infinite;'));
13
+ assert.ok(ui.includes(':host([data-assistant-working]) .input-button.inside-end svg { opacity: 0; }'));
14
+ assert.equal((ui.match(/radial-gradient\(circle, var\(--text-inverse\) 2\.4px/g) || []).length, 3, 'three dots, in the theme token and not a colour');
15
+ });
16
+
17
+ test('deep-chat\'s own dots on the button are hidden so only one animation shows', () => {
18
+ assert.ok(ui.includes(':host([data-assistant-working]) .loading-submit-button { display: none !important; }'));
19
+ });
20
+
21
+ test('the dots bounce one after another', () => {
22
+ const frames = ui.slice(ui.indexOf('@keyframes ws-dots'), ui.indexOf('@keyframes ws-dots') + 500);
23
+ const raised = [...frames.matchAll(/(\d+)% \{ background-position: ([^;]+);/g)].filter((m) => m[1] !== '0').map((m) => m[2].split(',').map((p) => p.trim()).findIndex((p) => p.endsWith('30%')));
24
+ assert.deepEqual(raised.filter((i) => i >= 0), [0, 1, 2]);
25
+ });
26
+
27
+ test('the panel header pulses the avatar and sweeps a light while it works', () => {
28
+ assert.ok(css.includes('.assistant-panel[data-working] .assistant-head::after'));
29
+ assert.ok(css.includes('.assistant-panel[data-working] .assistant-avatar { animation: assistant-pulse'));
30
+ assert.ok(css.includes('@keyframes assistant-sweep') && css.includes('@keyframes assistant-pulse'));
31
+ });
32
+
33
+ test('motion is switched off for people who prefer reduced motion', () => {
34
+ assert.ok(ui.includes('@media (prefers-reduced-motion: reduce) { :host([data-assistant-working]) .input-button.inside-end::after { animation: none; } }'));
35
+ assert.match(css, /prefers-reduced-motion: reduce\) \{\s*\.assistant-panel\[data-working\] \.assistant-head::after,\s*\.assistant-panel\[data-working\] \.assistant-avatar \{ animation: none; \}/);
36
+ });
37
+
38
+ test('working starts when a message is sent and ends on the answer, a reply of any kind, an error, or after a time limit', () => {
39
+ assert.ok(ui.includes("if (message.role === 'user') { conversationEmpty = false; narrator.cancel(); playSendAnimation(); setWorking(true); }"));
40
+ assert.ok(ui.includes('else setWorking(false);'), 'an assistant message ends it');
41
+ assert.ok(ui.includes('el.onError = () => setWorking(false, true);'), 'an error ends it, and says it failed');
42
+ assert.ok(ui.includes(' setWorking(false);\n if (response && response.html)'), 'the response interceptor ends it');
43
+ assert.ok(ui.includes('setTimeout(() => setWorking(false), 16 * 60 * 1000)'), 'a request that never answers cannot leave it running for ever');
44
+ });
45
+
46
+ test('the state is on the panel and the chat, so both the page and the shadow styles can read it', () => {
47
+ assert.ok(ui.includes("el.toggleAttribute('data-assistant-working', on)"));
48
+ assert.ok(ui.includes("document.querySelectorAll('.assistant-panel').forEach((p) => p.toggleAttribute('data-working', on))"));
49
+ });
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The assistant's page context: the part that reaches into the rest of the app.
3
+ *
4
+ * Every branch of it names something from another module, and a name that is not imported throws
5
+ * only when somebody asks a question from that page. That is not theoretical — after the module
6
+ * split the settings and workspace branches referenced `listSettingsFiles`, `CONFIG_DIR`,
7
+ * `findBuilderScripts` and `listBackups` without importing any of them, so the assistant worked on
8
+ * the home page and returned HTTP 500 on every workspace page and on settings.
9
+ *
10
+ * These tests call it for every context the dashboard can send. They cost nothing (no agent is
11
+ * spawned) and they fail on a missing import rather than leaving it for a person to find.
12
+ */
13
+
14
+ const { test } = require('node:test');
15
+ const assert = require('node:assert');
16
+
17
+ const { pageContextFor } = require('../../assistant');
18
+ const { loadWorkspaces, findBuilderScripts, commandFlags } = require('../../workspaces');
19
+
20
+ test('every context the dashboard sends produces a context line', () => {
21
+ const keys = Object.keys(loadWorkspaces());
22
+ assert.ok(keys.length, 'there is at least one workspace to ask about');
23
+ const contexts = ['home', 'settings', 'commands',
24
+ ...keys.map((k) => `workspace:${k}`), ...keys.map((k) => `backups:${k}`)];
25
+ for (const ctx of contexts) {
26
+ const out = pageContextFor(ctx);
27
+ assert.equal(typeof out, 'string', `${ctx} should produce a string`);
28
+ assert.ok(out.length > 20, `${ctx} produced nothing useful: ${out}`);
29
+ }
30
+ });
31
+
32
+ test('the settings context names the files but never a secret', () => {
33
+ const out = pageContextFor('settings');
34
+ assert.match(out, /Settings page/);
35
+ assert.match(out, /settings\.yml/);
36
+ // It has to TELL the agent not to reveal a secret — that instruction is the safeguard.
37
+ assert.match(out, /NEVER print, echo, cat, grep/);
38
+ });
39
+
40
+ test('a workspace context names that workspace and its builders', () => {
41
+ // NOT `Object.keys(...)[0]`: the first workspace is whichever one the cards are ordered to put
42
+ // first, which is a setting somebody can drag. When `designs` was moved to the top this test began
43
+ // failing on a Penpot builder that has no --install — a true statement about the wrong workspace.
44
+ // Pick one that actually has a builder taking --install, which is what the assertion is about.
45
+ const all = loadWorkspaces();
46
+ const key = Object.keys(all).find((k) => {
47
+ const dir = all[k].dir;
48
+ return dir && findBuilderScripts(dir).some((b) => commandFlags(dir, b).some((f) => f.long === '--install'));
49
+ });
50
+ assert.ok(key, 'some workspace builds something with --install');
51
+ const out = pageContextFor(`workspace:${key}`);
52
+ assert.ok(out.includes(all[key].label), `should name ${all[key].label}`);
53
+ // The AI Context carries each command's real flag contract, not just names.
54
+ assert.match(out, /Command contract/);
55
+ assert.match(out, /--install \(-i\)/);
56
+ });
57
+
58
+ test('an unknown workspace falls back to the home context rather than throwing', () => {
59
+ assert.match(pageContextFor('workspace:no-such-workspace'), /home page/);
60
+ assert.match(pageContextFor('nonsense'), /home page/);
61
+ });
@@ -0,0 +1,147 @@
1
+ /**
2
+ * The audio workspace and its sound player.
3
+ *
4
+ * A voice or a sound is opened from the same list a recording is, so the listing, the play route and
5
+ * the file route have to agree on what plays. The behavioural half starts the real server on a spare
6
+ * port against a temp tree, because a Play button that answers 404 is exactly what source checks miss.
7
+ */
8
+
9
+ const { test, before, after } = require('node:test');
10
+ const assert = require('node:assert');
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const { spawn } = require('child_process');
15
+
16
+ const APP = path.join(__dirname, '../..');
17
+ const REPO = path.join(APP, '../..');
18
+ const { copyConfig } = require('./_isolated-config');
19
+ const { AUDIO_RE, VIDEO_RE } = require('../../format');
20
+ const fb = require('../../files-browser');
21
+ const types = require('../../workspace-types');
22
+
23
+ const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
24
+
25
+ test('AUDIO_RE is the set of sound the browser plays, and no video is among it', () => {
26
+ for (const n of ['a.mp3', 'a.WAV', 'a.ogg', 'a.oga', 'a.opus', 'a.m4a', 'a.aac', 'a.flac', 'a.weba']) {
27
+ assert.ok(AUDIO_RE.test(n), n);
28
+ assert.ok(!VIDEO_RE.test(n), `${n} is not a video`);
29
+ }
30
+ for (const n of ['a.mp4', 'a.webm', 'a.txt', 'a.mp3.txt', 'mp3']) assert.ok(!AUDIO_RE.test(n), n);
31
+ });
32
+
33
+ test('audio is a registered workspace: listed, typed as media, presented and routed', () => {
34
+ assert.match(read(REPO, 'core/config/settings.yml'), /^workspaces:[\s\S]*?\n {2}- audio\n/m);
35
+ assert.match(read(REPO, 'core/config/workspace.audio.settings.yml'), /^type: media$/m);
36
+ assert.match(read(REPO, 'core/config/workspace.audio.settings.yml'), /^ {2}name: audio$/m);
37
+ assert.strictEqual(types.resolveType('audio', undefined, 'files'), 'media');
38
+ assert.match(read(APP, 'workspaces.js'), /audio:\s+\{ icon: 'tabler:headphones', subtitle: 'Voices & Sounds', noun: 'sound', kind: 'files' \}/);
39
+ assert.match(read(APP, '.ddev/config.yaml'), /- "\*\.audio\.workspace"/);
40
+ for (const f of ['README.md', 'AGENTS.md', 'CLAUDE.md']) assert.ok(fs.existsSync(path.join(REPO, 'audio', f)), f);
41
+ });
42
+
43
+ test('the audio folder is browsable and a sound row gets the music icon', () => {
44
+ assert.ok(fb.BROWSABLE.has('audio'));
45
+ for (const n of ['a.mp3', 'a.opus', 'a.flac', 'a.weba']) assert.strictEqual(fb.kindIcon(n), 'tabler:music', n);
46
+ });
47
+
48
+ test('the sound player controls act on the audio in their own card', () => {
49
+ const ui = read(APP, 'public/media-player.js');
50
+ assert.match(ui, /\[data-sound-speed\]/);
51
+ assert.match(ui, /media\.playbackRate = r/);
52
+ assert.match(ui, /\[data-sound-loop\]/);
53
+ assert.match(ui, /media\.loop = e\.target\.checked/);
54
+ assert.ok(fs.existsSync(path.join(APP, 'public/css/components/sound-player.css')));
55
+ });
56
+
57
+ // ---- the running server ------------------------------------------------------------------------
58
+
59
+ let proc;
60
+ let base;
61
+ let root;
62
+
63
+ function free() {
64
+ return new Promise((resolve) => {
65
+ const s = require('net').createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => resolve(p)); });
66
+ });
67
+ }
68
+
69
+ before(async () => {
70
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-audio-'));
71
+ fs.mkdirSync(path.join(root, 'core'), { recursive: true });
72
+ copyConfig(path.join(REPO, 'core/config'), path.join(root, 'core/config'));
73
+ fs.symlinkSync(path.join(REPO, 'core/scripts'), path.join(root, 'core/scripts'));
74
+ fs.symlinkSync(APP, path.join(root, 'core/workspace-app'));
75
+ fs.mkdirSync(path.join(root, 'audio/voices'), { recursive: true });
76
+ fs.writeFileSync(path.join(root, 'audio/voices/hello.mp3'), Buffer.alloc(5000, 1));
77
+ fs.writeFileSync(path.join(root, 'audio/chime.wav'), Buffer.alloc(300, 2));
78
+ fs.writeFileSync(path.join(root, 'audio/notes.txt'), 'not a sound');
79
+ fs.writeFileSync(path.join(root, 'secret.mp3'), 'outside the workspace');
80
+ const port = await free();
81
+ base = `http://127.0.0.1:${port}`;
82
+ proc = spawn(process.execPath, ['server.js'], { cwd: APP, env: { ...process.env, PORT: String(port), WORKSPACE_ROOT: root }, stdio: ['ignore', 'pipe', 'pipe'] });
83
+ await new Promise((resolve, reject) => {
84
+ const t = setTimeout(() => reject(new Error('server did not start')), 15000);
85
+ proc.stdout.on('data', (d) => { if (/listening/.test(String(d))) { clearTimeout(t); resolve(); } });
86
+ proc.on('exit', (c) => { clearTimeout(t); reject(new Error(`server exited ${c}`)); });
87
+ });
88
+ });
89
+
90
+ after(() => {
91
+ if (proc) proc.kill();
92
+ if (root) fs.rmSync(root, { recursive: true, force: true });
93
+ });
94
+
95
+ const get = (p, headers) => fetch(base + p, { headers, redirect: 'manual' });
96
+
97
+ test('the page lists the folder and its sound, each with a Play button', async () => {
98
+ const top = await (await get('/fragments/audio/items')).text();
99
+ assert.match(top, /items\?path=voices/);
100
+ assert.match(top, /title="Play chime\.wav/);
101
+ assert.match(top, /hx-get="\/fragments\/audio\/play\/chime\.wav"/);
102
+ assert.ok(!/Play notes\.txt/.test(top), 'a text file gets no Play button');
103
+ const sub = await (await get('/fragments/audio/items?path=voices')).text();
104
+ assert.match(sub, /hx-get="\/fragments\/audio\/play\/voices\/hello\.mp3"/);
105
+ });
106
+
107
+ test('the play route answers a sound with an audio player, speed and loop', async () => {
108
+ const r = await get('/fragments/audio/play/voices/hello.mp3');
109
+ assert.strictEqual(r.status, 200);
110
+ const html = await r.text();
111
+ assert.match(html, /<audio class="sound-player" controls preload="metadata" src="\/files\/audio\/voices\/hello\.mp3">/);
112
+ const rates = ['0.5', '0.75', ...Array.from({ length: 17 }, (_, i) => String(1 + i * 0.25))];
113
+ for (const rate of rates) assert.ok(html.includes(`data-sound-speed="${rate}"`), rate);
114
+ assert.strictEqual((html.match(/data-sound-speed=/g) || []).length, 19, 'every quarter step from 1x to 5x, and the two slower ones');
115
+ assert.match(html, /data-sound-loop/);
116
+ assert.ok(!/<video/.test(html));
117
+ });
118
+
119
+ test('the play route refuses a file that is not a sound, a missing one, and a path out of the workspace', async () => {
120
+ assert.strictEqual((await get('/fragments/audio/play/notes.txt')).status, 404);
121
+ assert.strictEqual((await get('/fragments/audio/play/voices/none.mp3')).status, 404);
122
+ assert.strictEqual((await get('/fragments/audio/play/..%2Fsecret.mp3')).status, 400);
123
+ assert.strictEqual((await get('/files/audio/..%2Fsecret.mp3')).status, 400);
124
+ });
125
+
126
+ test('a sound is served with an audio type and byte ranges, so it can be seeked', async () => {
127
+ const full = await get('/files/audio/voices/hello.mp3');
128
+ assert.strictEqual(full.status, 200);
129
+ assert.strictEqual(full.headers.get('content-type'), 'audio/mpeg');
130
+ assert.strictEqual(full.headers.get('accept-ranges'), 'bytes');
131
+ const part = await get('/files/audio/voices/hello.mp3', { Range: 'bytes=100-199' });
132
+ assert.strictEqual(part.status, 206);
133
+ assert.strictEqual(part.headers.get('content-range'), 'bytes 100-199/5000');
134
+ assert.strictEqual((await part.arrayBuffer()).byteLength, 100);
135
+ assert.strictEqual((await get('/files/audio/voices/hello.mp3', { Range: 'bytes=9000-' })).status, 416);
136
+ assert.strictEqual((await get('/files/audio/chime.wav')).headers.get('content-type'), 'audio/wav');
137
+ });
138
+
139
+ test('a video is still played as a video', async () => {
140
+ fs.mkdirSync(path.join(root, 'videos'), { recursive: true });
141
+ fs.writeFileSync(path.join(root, 'videos/demo.mp4'), Buffer.alloc(400, 3));
142
+ const html = await (await get('/fragments/videos/play/demo.mp4')).text();
143
+ assert.match(html, /<video class="video-player"/);
144
+ assert.ok(!/<audio/.test(html));
145
+ for (const rate of ['0.5', '1', '1.75', '2.25', '3.5', '4.75', '5']) assert.ok(html.includes(`data-sound-speed="${rate}"`), `video speed ${rate}`);
146
+ assert.match(html, /data-sound-loop/);
147
+ });