@webship.co/workspace 0.0.0-stage → 1.0.0-alpha1

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 (311) hide show
  1. package/PLUGINS.md +249 -0
  2. package/README.md +379 -2
  3. package/WORKSPACES.md +69 -0
  4. package/actions/ai-items.js +293 -0
  5. package/actions/audio.js +48 -0
  6. package/actions/build-order.js +79 -0
  7. package/actions/checks.js +95 -0
  8. package/actions/claude-session.js +104 -0
  9. package/actions/commands-center.js +258 -0
  10. package/actions/composer.js +267 -0
  11. package/actions/ddev-addons.js +90 -0
  12. package/actions/ddev-mail.js +80 -0
  13. package/actions/ddev.js +237 -0
  14. package/actions/folders.js +165 -0
  15. package/actions/graphs.js +107 -0
  16. package/actions/images-svg.js +33 -0
  17. package/actions/images.js +145 -0
  18. package/actions/navigation.js +190 -0
  19. package/actions/node.js +222 -0
  20. package/actions/notes.js +55 -0
  21. package/actions/obsidian.js +59 -0
  22. package/actions/plugins.js +36 -0
  23. package/actions/project-make.js +28 -0
  24. package/actions/rag.js +202 -0
  25. package/actions/row-actions.js +262 -0
  26. package/actions/screens.js +270 -0
  27. package/actions/testing.js +121 -0
  28. package/actions/video-edit.js +54 -0
  29. package/ai-inventory.js +182 -0
  30. package/ai-items.js +202 -0
  31. package/assistant-cast.js +176 -0
  32. package/assistant-suggestions.js +91 -0
  33. package/assistant.js +484 -0
  34. package/audio-voice.js +528 -0
  35. package/auth-google.js +260 -0
  36. package/build-ai.js +52 -0
  37. package/build-order-page.js +191 -0
  38. package/build-order.js +313 -0
  39. package/builder-args.js +117 -0
  40. package/changelog.js +302 -0
  41. package/chat-threads.js +134 -0
  42. package/checks.js +105 -0
  43. package/claude-args.js +282 -0
  44. package/claude-sessions.js +129 -0
  45. package/command-author.js +117 -0
  46. package/command-block.js +20 -0
  47. package/commands/defaults.yml +159 -0
  48. package/commands-page.js +460 -0
  49. package/commands.js +568 -0
  50. package/cucumber.js +43 -0
  51. package/ddev-addons-page.js +124 -0
  52. package/ddev-addons.js +154 -0
  53. package/ddev-custom-commands.js +96 -0
  54. package/ddev-domains.js +84 -0
  55. package/ddev-import.js +93 -0
  56. package/ddev-mail-page.js +167 -0
  57. package/ddev-mail.js +110 -0
  58. package/ddev-options.js +145 -0
  59. package/ddev-snapshots.js +126 -0
  60. package/ddev.js +145 -0
  61. package/docs/DEVELOPER-GUIDE.md +224 -0
  62. package/docs/README.md +252 -0
  63. package/docs/REFERENCE.md +684 -0
  64. package/docs/USER-GUIDE.md +697 -0
  65. package/docs/screenshots/assistant/avatar-board.png +0 -0
  66. package/docs/screenshots/assistant/avatar-coffee.png +0 -0
  67. package/docs/screenshots/assistant/avatar-colours.png +0 -0
  68. package/docs/screenshots/assistant/avatar-icons.png +0 -0
  69. package/docs/screenshots/assistant/avatar-moves.png +0 -0
  70. package/docs/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  71. package/docs/screenshots/assistant/avatar-settings-voices.png +0 -0
  72. package/docs/screenshots/assistant/avatar-status.png +0 -0
  73. package/docs/screenshots/create-card/create-card.png +0 -0
  74. package/docs/screenshots/create-card/start-ai.png +0 -0
  75. package/docs/screenshots/guide-clone-address.png +0 -0
  76. package/docs/screenshots/guide-clone-worktree-off.png +0 -0
  77. package/docs/screenshots/guide-clone-worktree-on.png +0 -0
  78. package/docs/screenshots/guide-ddev-menu.png +0 -0
  79. package/docs/screenshots/guide-domains.png +0 -0
  80. package/docs/screenshots/guide-home.png +0 -0
  81. package/docs/screenshots/guide-ide-menu.png +0 -0
  82. package/docs/screenshots/guide-remove-refusal.png +0 -0
  83. package/docs/screenshots/guide-rows-worktree.png +0 -0
  84. package/docs/screenshots/guide-screens.png +0 -0
  85. package/docs/screenshots/guide-settings.png +0 -0
  86. package/docs/screenshots/guide-workspace-page.png +0 -0
  87. package/docs/screenshots/home/card-settings.png +0 -0
  88. package/docs/screenshots/home/drag-reorder.png +0 -0
  89. package/docs/screenshots/home/edit-card.png +0 -0
  90. package/docs/screenshots/images/claude-code-image.png +0 -0
  91. package/docs/screenshots/images/edit-with-ai.png +0 -0
  92. package/docs/screenshots/images/gallery.png +0 -0
  93. package/docs/screenshots/images/generate-description.png +0 -0
  94. package/docs/screenshots/images/generate-plan.png +0 -0
  95. package/docs/screenshots/images/providers.png +0 -0
  96. package/docs/screenshots/images/svg-form.png +0 -0
  97. package/docs/screenshots/images/svg-result.png +0 -0
  98. package/docs/screenshots/images/upload.png +0 -0
  99. package/docs/screenshots/new-folder/2-form.png +0 -0
  100. package/docs/screenshots/new-folder/8-ddev-options.png +0 -0
  101. package/docs/screenshots/new-folder/files-folder.png +0 -0
  102. package/docs/screenshots/projects/ddev-custom-commands.png +0 -0
  103. package/docs/screenshots/projects/state-chip-stop.png +0 -0
  104. package/docs/screenshots/readme/dashboard-dark.png +0 -0
  105. package/docs/screenshots/readme/dashboard-light.png +0 -0
  106. package/docs/screenshots/readme/dev-dark.png +0 -0
  107. package/docs/screenshots/readme/dev-light.png +0 -0
  108. package/docs/screenshots/readme/docs-dark.png +0 -0
  109. package/docs/screenshots/readme/docs-light.png +0 -0
  110. package/docs/screenshots/readme/icons/ai.svg +4 -0
  111. package/docs/screenshots/readme/icons/code.svg +4 -0
  112. package/docs/screenshots/readme/icons/docs.svg +4 -0
  113. package/docs/screenshots/readme/icons/services.svg +4 -0
  114. package/docs/screenshots/readme/icons/sites.svg +4 -0
  115. package/docs/screenshots/readme/test-report-dark.png +0 -0
  116. package/docs/screenshots/readme/test-report-light.png +0 -0
  117. package/docs/screenshots/readme/test-scenarios.png +0 -0
  118. package/docs/screenshots/readme/test-steps.png +0 -0
  119. package/docs/screenshots/readme/videos-dark.png +0 -0
  120. package/docs/screenshots/readme/videos-light.png +0 -0
  121. package/docs/screenshots/screens-menu.png +0 -0
  122. package/docs/screenshots/screens-new.png +0 -0
  123. package/docs/screenshots/settings/auth-google-lists.png +0 -0
  124. package/docs/screenshots/settings/delete-1-row.png +0 -0
  125. package/docs/screenshots/settings/delete-2-confirm.png +0 -0
  126. package/docs/screenshots/settings/delete-3-type-name.png +0 -0
  127. package/docs/screenshots/settings/delete-4-type-yes.png +0 -0
  128. package/event-log.js +59 -0
  129. package/files-browser.js +247 -0
  130. package/folders.js +121 -0
  131. package/format.js +73 -0
  132. package/git-info.js +81 -0
  133. package/github-auth.js +62 -0
  134. package/graphs.js +104 -0
  135. package/html.js +10 -0
  136. package/icons.js +83 -0
  137. package/images-ai.js +447 -0
  138. package/images-create.js +226 -0
  139. package/images-page.js +417 -0
  140. package/images-records.js +39 -0
  141. package/images-render.js +36 -0
  142. package/images-svg-core.js +214 -0
  143. package/images-svg-sanitize.js +188 -0
  144. package/images-svg.js +241 -0
  145. package/images.js +270 -0
  146. package/jobs.js +430 -0
  147. package/linger.js +21 -0
  148. package/lists.js +292 -0
  149. package/media-info.js +136 -0
  150. package/navigation-page.js +264 -0
  151. package/navigation.js +394 -0
  152. package/notes.js +0 -0
  153. package/obsidian.js +405 -0
  154. package/package.json +47 -4
  155. package/palette-index.js +46 -0
  156. package/playwright.config.js +23 -0
  157. package/plugin-install.js +239 -0
  158. package/plugin-loader.js +448 -0
  159. package/plugin-manifest.js +83 -0
  160. package/plugin-settings.js +44 -0
  161. package/plugins/workspace-notes/index.js +59 -0
  162. package/plugins-page.js +103 -0
  163. package/project-make.js +241 -0
  164. package/project-name.js +210 -0
  165. package/public/assistant-avatar-moves.js +646 -0
  166. package/public/assistant-avatar-ui.js +1223 -0
  167. package/public/assistant-avatar.js +530 -0
  168. package/public/avatar/druplicon.vrm +0 -0
  169. package/public/css/base.css +260 -0
  170. package/public/css/components/ai-assistant.css +263 -0
  171. package/public/css/components/assistant-avatar.css +279 -0
  172. package/public/css/components/assistant-views.css +198 -0
  173. package/public/css/components/build-order.css +74 -0
  174. package/public/css/components/clone-as-a-git-worktree.css +30 -0
  175. package/public/css/components/command-palette.css +61 -0
  176. package/public/css/components/commands-center.css +139 -0
  177. package/public/css/components/confirm-dialog.css +40 -0
  178. package/public/css/components/create-card.css +8 -0
  179. package/public/css/components/ddev-menu.css +23 -0
  180. package/public/css/components/deep-chat.css +17 -0
  181. package/public/css/components/density.css +11 -0
  182. package/public/css/components/floating-assistant-workspace-pages.css +55 -0
  183. package/public/css/components/folders.css +5 -0
  184. package/public/css/components/full-height-left-assistant-sidebar-all-pages.css +39 -0
  185. package/public/css/components/full-height-right-actions-rail-pages-that-have-actions.css +267 -0
  186. package/public/css/components/grid-and-list.css +203 -0
  187. package/public/css/components/help-page.css +36 -0
  188. package/public/css/components/hero.css +16 -0
  189. package/public/css/components/images.css +70 -0
  190. package/public/css/components/layout-rhythm-and-spacing-polish.css +139 -0
  191. package/public/css/components/mic-on-any-field-that-wants-typing.css +416 -0
  192. package/public/css/components/modal-fit.css +113 -0
  193. package/public/css/components/page-width.css +23 -0
  194. package/public/css/components/plugins-page.css +9 -0
  195. package/public/css/components/project-cards.css +256 -0
  196. package/public/css/components/project-rows.css +390 -0
  197. package/public/css/components/reordering-the-workspace-cards.css +85 -0
  198. package/public/css/components/responsive-home-and-the-workspace-pages.css +457 -0
  199. package/public/css/components/running-commands-as-toasts.css +327 -0
  200. package/public/css/components/settings-editor-core-config-yml.css +828 -0
  201. package/public/css/components/short-results-as-a-toast-at-the-top.css +203 -0
  202. package/public/css/components/sound-player.css +84 -0
  203. package/public/css/components/suggest-agents-skills-commands.css +112 -0
  204. package/public/css/components/tab-modal.css +142 -0
  205. package/public/css/components/terminals.css +244 -0
  206. package/public/css/components/test-report-media-one-file-per-row-paged.css +144 -0
  207. package/public/css/components/top-toolbar-logo-breadcrumb.css +107 -0
  208. package/public/css/components/video-edit.css +6 -0
  209. package/public/css/components/web-ide-open-with.css +23 -0
  210. package/public/css/components/working-indicators.css +158 -0
  211. package/public/css/components/workspace-cards.css +79 -0
  212. package/public/favicon.ico +0 -0
  213. package/public/favicon.png +0 -0
  214. package/public/htmx.min.js +1 -0
  215. package/public/logo.png +0 -0
  216. package/public/media-player.js +215 -0
  217. package/public/mic.js +255 -0
  218. package/public/narration.js +110 -0
  219. package/public/palette.js +278 -0
  220. package/public/sortable.js +251 -0
  221. package/public/suggest.js +337 -0
  222. package/public/terminal.js +903 -0
  223. package/public/themes/default/theme.css +267 -0
  224. package/public/themes/ember/theme.css +180 -0
  225. package/public/themes/press/theme.css +290 -0
  226. package/public/themes/slate/theme.css +182 -0
  227. package/public/touch-icon.png +0 -0
  228. package/public/ui.js +2721 -0
  229. package/public/vendor/ace/LICENSE.txt +24 -0
  230. package/public/vendor/ace/ace.js +23 -0
  231. package/public/vendor/ace/ext-language_tools.js +8 -0
  232. package/public/vendor/ace/ext-searchbox.js +8 -0
  233. package/public/vendor/ace/mode-css.js +8 -0
  234. package/public/vendor/ace/mode-html.js +8 -0
  235. package/public/vendor/ace/mode-javascript.js +8 -0
  236. package/public/vendor/ace/mode-markdown.js +8 -0
  237. package/public/vendor/ace/mode-sh.js +8 -0
  238. package/public/vendor/ace/mode-yaml.js +8 -0
  239. package/public/vendor/ace/theme-textmate.js +8 -0
  240. package/public/vendor/ace/theme-tomorrow_night.js +8 -0
  241. package/public/vendor/ace/worker-yaml.js +1 -0
  242. package/public/vendor/deep-chat.LICENSE +21 -0
  243. package/public/vendor/deep-chat.bundle.js +1 -0
  244. package/public/vendor/fonts/dm-serif-text-LICENSE.txt +93 -0
  245. package/public/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  246. package/public/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  247. package/public/vendor/fonts/figtree-LICENSE.txt +93 -0
  248. package/public/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  249. package/public/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  250. package/public/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  251. package/public/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  252. package/public/vendor/fonts/jetbrains-mono-LICENSE.txt +93 -0
  253. package/public/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  254. package/public/vendor/fonts/ubuntu-mono-LICENSE.txt +98 -0
  255. package/public/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  256. package/public/vendor/tabler-LICENSE.txt +21 -0
  257. package/public/vendor/tabler-sprite.svg +1 -0
  258. package/public/vendor/three/LICENSE +21 -0
  259. package/public/vendor/three/addons/loaders/GLTFLoader.js +4860 -0
  260. package/public/vendor/three/addons/utils/BufferGeometryUtils.js +1501 -0
  261. package/public/vendor/three/addons/utils/SkeletonUtils.js +496 -0
  262. package/public/vendor/three/three.core.min.js +6 -0
  263. package/public/vendor/three/three.module.min.js +6 -0
  264. package/public/vendor/three-vrm/LICENSE +22 -0
  265. package/public/vendor/three-vrm/three-vrm.module.min.js +983 -0
  266. package/public/vendor/uikit-icons.min.js +1 -0
  267. package/public/vendor/uikit.min.css +1 -0
  268. package/public/vendor/uikit.min.js +1 -0
  269. package/public/vendor/webship-icons/ui/bookmark-outlined.svg +1 -0
  270. package/public/vendor/webship-icons/ui/headset.svg +1 -0
  271. package/public/vendor/webship-icons/ui/menu.svg +1 -0
  272. package/public/vendor/webship-icons/ui/pause.svg +1 -0
  273. package/public/vendor/webship-icons/ui/play.svg +1 -0
  274. package/public/vendor/webship-icons/ui/share.svg +1 -0
  275. package/public/vendor/webship-icons/ui/sound-off.svg +1 -0
  276. package/public/vendor/webship-icons/ui/sound-on.svg +1 -0
  277. package/public/vendor/webship-icons/ui/stop.svg +1 -0
  278. package/public/vendor/xterm/addon-fit.js +2 -0
  279. package/public/vendor/xterm/addon-unicode11.js +2 -0
  280. package/public/vendor/xterm/addon-web-links.js +2 -0
  281. package/public/vendor/xterm/xterm.css +218 -0
  282. package/public/vendor/xterm/xterm.js +2 -0
  283. package/rag.js +169 -0
  284. package/reader.js +132 -0
  285. package/repo-blobs.js +66 -0
  286. package/report-pdf.js +337 -0
  287. package/row-layout.js +1440 -0
  288. package/screenrc +37 -0
  289. package/screens.js +1063 -0
  290. package/scripts/code-server-start.sh +190 -0
  291. package/scripts/images-claude-finish.js +107 -0
  292. package/scripts/screen-host.sh +114 -0
  293. package/scripts/screen-runner.js +362 -0
  294. package/scripts/screen-then.js +47 -0
  295. package/scripts/update.sh +350 -0
  296. package/server.js +8140 -0
  297. package/settings-file.js +423 -0
  298. package/speech.js +297 -0
  299. package/tab-modal.js +75 -0
  300. package/test-reports.js +260 -0
  301. package/themes.js +381 -0
  302. package/transcripts.js +168 -0
  303. package/urls.js +13 -0
  304. package/validate.js +18 -0
  305. package/version-check.js +94 -0
  306. package/video-edit.js +588 -0
  307. package/waiting.js +85 -0
  308. package/workspace-card-tools.js +41 -0
  309. package/workspace-delete.js +139 -0
  310. package/workspace-types.js +85 -0
  311. package/workspaces.js +557 -0
package/ddev.js ADDED
@@ -0,0 +1,145 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ /**
4
+ * The DDEV verbs a row's menu offers, as a table rather than as six near-identical branches.
5
+ *
6
+ * `needsRunning` is refused rather than attempted: `ddev drush` on a stopped project produces a
7
+ * wall of connection errors instead of a sentence. `toBackups` writes into the workspace's backups
8
+ * directory, so an export lands where the restore button looks for it.
9
+ */
10
+
11
+ /**
12
+ * ONE `ddev start` at a time, machine-wide.
13
+ *
14
+ * Every router-mutating verb (`start`, `stop`, `restart`, `poweroff`, `delete`) tears the
15
+ * SHARED `ddev-router` down and brings it back up with the routes of whatever is running.
16
+ * Run two at once and they race on one container: one removes it while the other has just
17
+ * created it, and the loser can leave it removed for good. What that looks like is the whole
18
+ * workspace going dark — every *.ddev.site address, the dashboard's own hub included, with
19
+ * healthy web containers behind it and DDEV then refusing to start anything at all:
20
+ *
21
+ * Failed to start workspace: Error response from daemon: No such container: ddev-router
22
+ *
23
+ * Recovering needs `ddev poweroff`. It is easy to trigger: five clicks on Start in
24
+ * thirty-eight seconds did it here, because each one is its own detached screen session.
25
+ *
26
+ * `flock` and not an in-process queue: the dashboard is not the only door. A person at a
27
+ * terminal, an agent, and a cmd-*.sh script all run `ddev` too, and a lock in this process
28
+ * would not be seen by any of them. The lock file lives at the root of the workspace tree,
29
+ * which is bind-mounted at the SAME absolute path inside the container — so the host and the
30
+ * dashboard take the same lock.
31
+ *
32
+ * The command runs EXACTLY ONCE. The obvious shape — `flock -n … cmd || flock -w … cmd` —
33
+ * is wrong: `||` fires on any non-zero exit, so a start that failed on its own merits would
34
+ * be run a second time. So the lock is probed with `true` purely to decide whether to say
35
+ * "waiting", and the real call is a single bounded acquire whose status is the session's.
36
+ *
37
+ * NO SHELL VARIABLES IN HERE. The line travels through two shells — the runner builds
38
+ * `bash -lc "… screen -dmS <s> bash -lc \"<this>\" …"` — so the OUTER one expands anything
39
+ * starting with `$` before the inner one ever sees it. A first version captured `rc=$?` and
40
+ * tested it, and the session logged `[: : integer expected` because `$rc` had already been
41
+ * expanded away to nothing by the outer shell. Keeping the string free of `$` makes it
42
+ * immune to however many quoting layers it passes through.
43
+ */
44
+ const ROUTER_VERBS = new Set(['start', 'stop', 'restart', 'poweroff', 'delete']);
45
+
46
+ function routerLockPath() {
47
+ const { ROOT } = require('./workspaces');
48
+ return path.join(ROOT, '.ddev-router.lock');
49
+ }
50
+
51
+ /** Wrap a command line so it holds the router lock while it runs. */
52
+ function serialiseRouter(cmdLine) {
53
+ const lock = JSON.stringify(routerLockPath());
54
+ const busy = 'Waiting: another ddev start/stop/restart holds the router lock. '
55
+ + 'They cannot run at once — each one recreates the shared ddev-router.';
56
+ // The probe cannot fail the session: `|| echo` only decides whether to explain the wait.
57
+ // The second flock is the last command, so ITS status is the session's status.
58
+ // DDEV_ROUTER_LOCK_HELD tells the `ddev` shim (core/scripts/ddev-shim/ddev, on PATH in
59
+ // this container) that the lock is ALREADY held by this very command, so it runs instead
60
+ // of taking the same lock again. Without it the two protections deadlock each other:
61
+ // proven — an outer flock plus an unaware shim hangs for the full 1800s timeout.
62
+ return `{ flock -n ${lock} true || echo ${JSON.stringify(busy)} ; } ; `
63
+ + `flock -w 1800 ${lock} env DDEV_ROUTER_LOCK_HELD=1 ${cmdLine}`;
64
+ }
65
+
66
+ /** Does this argv touch the router? */
67
+ function touchesRouter(args) { return ROUTER_VERBS.has(String((args || [])[0] || '')); }
68
+
69
+ const DDEV_ACTIONS = {
70
+ '/actions/ddev-restart': { icon: '🔄', label: 'ddev restart', args: ['restart'], ms: 5 * 60 * 1000 },
71
+ '/actions/ddev-describe': { icon: 'ℹ️', label: 'ddev describe', args: ['describe'], ms: 60 * 1000 },
72
+ '/actions/ddev-logs': { icon: '📜', label: 'ddev logs', args: ['logs', '-s', 'web', '--tail', '100'], ms: 60 * 1000 },
73
+ '/actions/ddev-drush-cr': { icon: '⚡', label: 'ddev drush cr', args: ['drush', 'cr'], ms: 5 * 60 * 1000, needsRunning: true },
74
+ '/actions/ddev-drush-uli': { icon: '🔑', label: 'ddev drush uli', args: ['drush', 'uli'], ms: 60 * 1000, needsRunning: true },
75
+ '/actions/ddev-export-db': { icon: '💾', label: 'ddev export-db', args: ['export-db'], ms: 10 * 60 * 1000, needsRunning: true, toBackups: true },
76
+ // Long ones run detached in a screen session: a composer update outlives any
77
+ // page, and it must be watchable and reattachable rather than tied to a toast.
78
+ '/actions/ddev-composer-install': { icon: '📦', label: 'ddev composer install', args: ['composer', 'install'], ms: 60 * 60 * 1000, inScreen: true },
79
+ '/actions/ddev-composer-update': { icon: '⬆️', label: 'ddev composer update', args: ['composer', 'update'], ms: 60 * 60 * 1000, inScreen: true },
80
+ '/actions/ddev-drush-updb': { icon: '🗃️', label: 'ddev drush updb', args: ['drush', 'updb', '-y'], ms: 30 * 60 * 1000, needsRunning: true, inScreen: true },
81
+ '/actions/ddev-drush-cim': { icon: '📥', label: 'ddev drush cim', args: ['drush', 'cim', '-y'], ms: 30 * 60 * 1000, needsRunning: true, inScreen: true },
82
+ '/actions/ddev-drush-cex': { icon: '📤', label: 'ddev drush cex', args: ['drush', 'cex', '-y'], ms: 30 * 60 * 1000, needsRunning: true, inScreen: true },
83
+ '/actions/ddev-drush-status': { icon: '🩺', label: 'ddev drush status', args: ['drush', 'status'], ms: 2 * 60 * 1000, needsRunning: true },
84
+ // A snapshot can be named, and one that is not gets DDEV's timestamp. `needsRunning` is honest
85
+ // rather than strict: `ddev snapshot` will START a stopped project to take one and stop it again,
86
+ // which is a surprise worth not springing on somebody who clicked a menu item.
87
+ '/actions/ddev-snapshot': { icon: '📸', label: 'ddev snapshot', args: ['snapshot'], ms: 15 * 60 * 1000, needsRunning: true, named: true },
88
+ // Restoring REPLACES the database — there is no merge and no undo, which is why it is confirmed
89
+ // and why it runs in a screen: a large database takes minutes and must be watchable.
90
+ '/actions/ddev-snapshot-restore': { icon: '⏮️', label: 'ddev snapshot restore', args: ['snapshot', 'restore'], ms: 30 * 60 * 1000, destructive: true, inScreen: true, named: true },
91
+ // Removing one is `--cleanup --name <n> -y`; it takes the same confirm, because a snapshot is
92
+ // somebody's only copy of a database often enough.
93
+ '/actions/ddev-snapshot-delete': { icon: '🗑️', label: 'ddev snapshot cleanup', args: ['snapshot', '--cleanup', '-y'], ms: 5 * 60 * 1000, destructive: true, named: true },
94
+ '/actions/ddev-mutagen-sync': { icon: '🔃', label: 'ddev mutagen sync', args: ['mutagen', 'sync'], ms: 10 * 60 * 1000 },
95
+ // Step debugging, on and off. It is a toggle in the web container rather than a config change, so
96
+ // it costs nothing and needs no restart — which is exactly why it belongs on a menu instead of in
97
+ // somebody's shell history. Xdebug makes PHP roughly three times slower while it is on, so turning
98
+ // it OFF is the half people forget, and the menu says which state it is in before either is clicked.
99
+ '/actions/ddev-xdebug-on': { icon: '🐞', label: 'ddev xdebug on', args: ['xdebug', 'on'], ms: 2 * 60 * 1000, needsRunning: true },
100
+ '/actions/ddev-xdebug-off': { icon: '🐞', label: 'ddev xdebug off', args: ['xdebug', 'off'], ms: 2 * 60 * 1000, needsRunning: true },
101
+ // Which it is now. `ddev describe` does not report it — checked — so a row cannot show the state
102
+ // without a subprocess of its own, and asking is a click rather than a cost on every render.
103
+ '/actions/ddev-xdebug-status': { icon: '🐞', label: 'ddev xdebug status', args: ['xdebug', 'status'], ms: 60 * 1000, needsRunning: true },
104
+ // Your SSH keys, into the container that lends them to every project. It is what makes a composer
105
+ // install against a private repository work, and it is INTERACTIVE — ssh-add asks for the
106
+ // passphrase — so it runs in a screen session you can open and type into. Nothing else here needs
107
+ // a human at the keyboard, which is exactly why it would be baffling as a fire-and-forget button.
108
+ '/actions/ddev-auth-ssh': { icon: '🔑', label: 'ddev auth ssh', args: ['auth', 'ssh'], ms: 10 * 60 * 1000, inScreen: true },
109
+ // A public tunnel to this project, for showing somebody the work. It RUNS UNTIL IT IS STOPPED and
110
+ // it puts a local site on the open internet, so: a screen session (the URL it prints is in there,
111
+ // and closing a browser tab must not take the tunnel down with it), and the confirm dialog — this
112
+ // is the only verb here whose effect is visible to people who are not on this machine.
113
+ '/actions/ddev-share': { icon: '🌍', label: 'ddev share', args: ['share'], ms: 8 * 60 * 60 * 1000, needsRunning: true, destructive: true, inScreen: true,
114
+ confirmWhy: 'puts this project on the open internet' },
115
+ // When a project is broken, these are the four questions worth asking before anyone starts
116
+ // guessing — Docker and the network, the certificate, the ports, and whether the database volume
117
+ // is the version the config claims. Each reads and reports; none of them changes anything, which
118
+ // is why they are one group and why none is confirmed.
119
+ '/actions/ddev-diagnose': { icon: '🩺', label: 'ddev utility diagnose', args: ['utility', 'diagnose'], ms: 5 * 60 * 1000 },
120
+ '/actions/ddev-tls-diagnose': { icon: '🔏', label: 'ddev utility tls-diagnose', args: ['utility', 'tls-diagnose'], ms: 5 * 60 * 1000 },
121
+ '/actions/ddev-port-diagnose': { icon: '🔌', label: 'ddev utility port-diagnose', args: ['utility', 'port-diagnose'], ms: 5 * 60 * 1000 },
122
+ // The one that answers "it started, and then the database was empty / the wrong version": the
123
+ // volume on disk against what config.yaml says it should be.
124
+ '/actions/ddev-db-match': { icon: '🗄️', label: 'ddev utility check-db-match', args: ['utility', 'check-db-match'], ms: 2 * 60 * 1000 },
125
+ // Mutagen beyond `sync`. Status answers the question a slow or stale project raises — is the sync
126
+ // behind, or is it broken — and it says plainly when Mutagen is not enabled at all, which is worth
127
+ // knowing before anybody blames it.
128
+ '/actions/ddev-mutagen-status': { icon: '🔃', label: 'ddev mutagen status', args: ['mutagen', 'status'], ms: 2 * 60 * 1000 },
129
+ // Reset STOPS THE PROJECT and removes the sync volume, which is why it is confirmed: it is the
130
+ // answer to a sync that cannot be repaired, and it is not a thing to click while somebody is
131
+ // working. The files on disk are the source; the volume is rebuilt on the next start.
132
+ '/actions/ddev-mutagen-reset': { icon: '♻️', label: 'ddev mutagen reset', args: ['mutagen', 'reset'], ms: 10 * 60 * 1000, destructive: true,
133
+ confirmWhy: 'stops the project and removes its sync volume' },
134
+ // Destructive: every table goes. It takes a dump into the workspace's backups
135
+ // folder first, because there is nothing else keeping that database.
136
+ '/actions/ddev-drush-sql-drop': { icon: '🧨', label: 'ddev drush sql:drop', args: ['drush', 'sql:drop', '-y'], ms: 10 * 60 * 1000, needsRunning: true, destructive: true, backupFirst: true },
137
+ };
138
+
139
+ // The project's OWN ddev commands, from every .ddev/commands/<service>/ folder. See ddev-custom-commands.js.
140
+ function projectDdevCommands(projectDir) {
141
+ return require('./ddev-custom-commands').customCommands(projectDir).map((c) => ({ ...c, help: c.description }));
142
+ }
143
+
144
+ module.exports = { DDEV_ACTIONS, projectDdevCommands,
145
+ serialiseRouter, touchesRouter, routerLockPath, ROUTER_VERBS };
@@ -0,0 +1,224 @@
1
+ # Developer guide
2
+
3
+ For changing the tooling itself: adding a command, a builder, a workspace, or a dashboard feature.
4
+ If you only want to *use* the workspace, [USER-GUIDE.md](USER-GUIDE.md) is the one you want.
5
+
6
+ Everything here is enforced somewhere — by `cmd-smoke-test.sh`, by a unit test, or by a failure that
7
+ has actually happened on a real machine. Where a rule exists because something broke, it says so.
8
+
9
+ ---
10
+
11
+ ## The shape of it
12
+
13
+ ```
14
+ ~/workspace/
15
+ ├── core/
16
+ │ ├── scripts/ the whole build chain, shared by every command
17
+ │ │ ├── bootstrap.sh locates the workspace from its own path
18
+ │ │ ├── bootstrap-libraries.sh argparse, YAML, progress bar
19
+ │ │ ├── bootstrap-functions.sh an explicit source list — NOT a glob
20
+ │ │ ├── args/arg-*.sh one argument surface per command family
21
+ │ │ ├── functions/fun-*.sh where the work actually happens
22
+ │ │ └── tests/cmd-smoke-test.sh the test suite for the commands
23
+ │ ├── config/
24
+ │ │ ├── settings.yml the registry: workspaces, secrets, style
25
+ │ │ └── workspace.<name>.settings.yml one per workspace
26
+ │ └── workspace-app/ the dashboard (node:http + HTMX, no framework)
27
+ └── <workspace>/ dev, test, products, … each with its own cmd-*.sh
28
+ ```
29
+
30
+ **A fresh clone works with no setup.** Every `cmd-*.sh` resolves `WORKSPACE_SCRIPTS` from its own
31
+ `${BASH_SOURCE[0]}` before sourcing `bootstrap.sh`, which derives `WORKSPACE_PATH`, `_ROOT` and
32
+ `_CONFIG` from itself. There is no installer step for this and no `.bashrc` edit; exported
33
+ `WORKSPACE_*` values still win, which is how the dashboard container points at its bind-mounted tree.
34
+
35
+ ---
36
+
37
+ ## Hard rules
38
+
39
+ These are not style preferences. Each one has cost somebody a rebuild or an outage.
40
+
41
+ | | |
42
+ | --- | --- |
43
+ | **DDEV only** | `ddev composer`, `ddev drush`, `ddev delete -y -O`, `ddev export-db`. Never host composer, drush or mysql |
44
+ | **Never write a `$databases` block into `settings.php`** | DDEV's `settings.ddev.php` owns it. `set_default_settings` only ensures the config sync directory |
45
+ | **`ddev start` takes `-y`; `ddev stop` does not** | |
46
+ | **Every `cmd-*-project.sh` carries `# workspace-name: <Human Name>`** | the smoke test enforces it and the Build dropdown shows it |
47
+ | **Spawned commands must close stdin** | `stdio: ['ignore','pipe','pipe']` — `ddev`'s `docker exec -i` hangs for ever on an open stdin pipe |
48
+ | **`--help` is only safe on argparse scripts** | never "test" a non-argparse `cmd-*.sh` by running it |
49
+ | **Never hardcode a person** | `contributor.name`/`.email` in `settings.yml`. A shared tool that names one author makes everyone else's commits claim someone else's work |
50
+
51
+ **A DDEV project does not come back by itself.** Its containers carry `restart: no`, so after a reboot
52
+ every workspace URL answers 404 from a router with nothing behind it. `core/scripts/autostart.sh
53
+ --enable` installs the systemd user service that fixes it. Do **not** reach for a Docker
54
+ `restart: unless-stopped` policy instead: DDEV re-pushes router config on `ddev start`, so a container
55
+ resurrected behind its back comes up with no route and answers nothing — the same 404, harder to find.
56
+
57
+ **Every router-mutating verb serialises.** `start`, `stop`, `restart`, `poweroff` and `delete` each
58
+ tear down and recreate the one shared `ddev-router`; two at once can leave it destroyed, which takes
59
+ every `*.ddev.site` address down — the dashboard included — while the web containers sit there
60
+ healthy. `core/scripts/ddev-shim/ddev` holds an `flock` for every caller, and `DDEV_ROUTER_LOCK_HELD`
61
+ stops a lock being taken twice by nested callers.
62
+
63
+ ---
64
+
65
+ ## Adding a command
66
+
67
+ A `cmd-*.sh` is a thin declaration; the work lives in `core/`.
68
+
69
+ ```bash
70
+ #!/usr/bin/env bash
71
+ # workspace-name: My Thing
72
+
73
+ WORKSPACE_SCRIPTS="${WORKSPACE_SCRIPTS:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../core/scripts" && pwd)}";
74
+ source ${WORKSPACE_SCRIPTS}/bootstrap.sh || exit 1 ;
75
+
76
+ eval $(parse_yaml ${WORKSPACE_CONFIG}/workspace.dev.settings.yml);
77
+
78
+ ARGPARSE_DESCRIPTION="What it does, in one line"
79
+ source ${WORKSPACE_SCRIPTS}/args/arg-my-thing.sh || exit 1 ;
80
+
81
+ shift $#;
82
+ cd ${WORKSPACE_ROOT}/${doc_name};
83
+ my_thing_command ;
84
+ ```
85
+
86
+ Then `args/arg-my-thing.sh` (an `argparse` heredoc) and `functions/fun-my-thing.sh`.
87
+
88
+ **Add the function file to `bootstrap-functions.sh`.** It is an explicit `source` list, not a glob, so
89
+ a new `fun-*.sh` stays invisible until it is listed there — and the failure is a "command not found"
90
+ a long way from the cause.
91
+
92
+ Three argparse traps, each found by hitting it:
93
+
94
+ - **`store_true` yields the string `yes`**, so test `[ "${FLAG}" == 'yes' ]`.
95
+ - **The dest becomes a shell variable**, so avoid names bash already owns. `--hostname` would clobber
96
+ `$HOSTNAME`; the clone command uses `--extra-hostname` for exactly that reason.
97
+ - **Several `store_const` aliases sharing one dest overwrite each other.** Use one option with a value
98
+ (`-a start|stop|status`) instead.
99
+
100
+ Read a flag that a caller may not set as `${FLAG:-}`: `remove_project` is called from the dashboard
101
+ and from other scripts that know nothing about `--force`.
102
+
103
+ ---
104
+
105
+ ## Adding a builder
106
+
107
+ A builder declares five distribution facts **inline** and lets `build_distribution` do the rest:
108
+
109
+ ```bash
110
+ site_version="~11.0.0"
111
+ distribution_name="webship"
112
+ distribution_title="Webship 11"
113
+ distribution_webroot="docroot"
114
+ distribution_profile_repo="..."
115
+ distribution_project_template="drupal/webship_project"
116
+ ```
117
+
118
+ There is no `core/config/distributions/` — a builder is readable on its own, and changing what one
119
+ builds touches only that file.
120
+
121
+ **Version strings follow the file name.** `cmd-<distro><major>-<minor>-0-project.sh` pins
122
+ `site_version="~<major>.<minor>.0"` — the three-segment form, so it means "that minor, latest patch".
123
+ A `-x-` builder tracks the branch (`11.0.x-dev`); latest-per-major builders use a caret (`^11`, `^2`).
124
+
125
+ A whole new distribution is one `args/arg-<name>.sh`, one `functions/fun-distribution-<name>.sh`
126
+ (with `install_<name>_with_drush`, `enable_<name>_extra_components`, `set_<name>_users`) and the
127
+ `cmd-*-project.sh` files that pick versions.
128
+
129
+ ---
130
+
131
+ ## Adding a workspace
132
+
133
+ Two entries and a directory:
134
+
135
+ 1. the name in the `workspaces:` list in `core/config/settings.yml` — that list is also the home
136
+ page's card order;
137
+ 2. `core/config/workspace.<name>.settings.yml`, holding `doc_name` and optionally
138
+ `presentation: icon:`/`subtitle:`.
139
+
140
+ The dashboard re-reads config on a 2-second memo, so there is no restart and no code change. Settings
141
+ → the `workspaces:` list does all of it from the browser, including the folder and the DDEV hostname
142
+ (which needs a `ddev restart` to resolve — the response says so).
143
+
144
+ **Deleting one** goes through the same list, and only for a workspace nothing uses (`workspace-delete.js`):
145
+ no item in its folder but its own tooling, no backups, no screen session running there, and not the last
146
+ workspace. It asks three times (the confirm dialog, the name typed, `YES` typed), checks again on the
147
+ server, and removes the list entry, the settings file, the tooling and the folder, and the DDEV hostname.
148
+ Test it on a throwaway `zz-*` workspace, never on one you use.
149
+
150
+ **Icons are Tabler**, referenced as `tabler:<name>` and parsed out of the vendored sprite, so a name
151
+ that does not exist is refused rather than rendering an empty box.
152
+
153
+ ---
154
+
155
+ ## The dashboard
156
+
157
+ `server.js` (single file, no framework) + `workspaces.js` (the config bridge) + `public/`.
158
+ Server-rendered HTML fragments, swapped by HTMX.
159
+
160
+ - **`workspaces.js` reads the config live.** The only hardcoded table is `PRESENTATION`; script
161
+ discovery is by filename regex, not a hand-maintained map.
162
+ - **Jobs are an in-memory `Map`**, so **do not restart the app while jobs are running** — running
163
+ builds get orphaned. Build jobs get a 60-minute timeout: a cold-cache `composer create-project` can
164
+ exceed fifteen.
165
+ - **Every action shells out to the real `cmd-*.sh`.** The button and the command line cannot drift,
166
+ and the collision checks, refusals and guards live in one place — the script.
167
+ - **The stylesheet is assembled** from `public/css/base.css` + `public/css/components/*.css` + a theme.
168
+ `public/style.css` is not the source; editing it changes nothing. `cssVersion()` stamps the URL from
169
+ the parts' mtimes, so a cached browser picks changes up.
170
+
171
+ Four things that have taken the app down or hidden a feature:
172
+
173
+ | | |
174
+ | --- | --- |
175
+ | **a backtick inside a template literal** | ends the literal and takes the whole app down with a `SyntaxError` hundreds of lines away. There is a test asserting no HTML comment in these files contains one |
176
+ | **a variable defined in the wrong IIFE** | renders as *nothing at all* — page rendering swallows errors, so a whole action group silently disappeared |
177
+ | **HTMX only swaps 2xx by default** | every 4xx this app returns used to be discarded silently, so a refused save looked like a broken button. `htmx:beforeSwap` in `ui.js` sets `shouldSwap` for 4xx/5xx |
178
+ | **UIkit closes a dropdown on an outside click only** | opening a modal is not one, so the menu stayed open on top of the dialog it had just opened |
179
+
180
+ ---
181
+
182
+ ## Testing
183
+
184
+ ```bash
185
+ bash core/scripts/tests/cmd-smoke-test.sh # every cmd-*.sh: syntax, bootstrap chain, header
186
+ cd core/workspace-app && npm run test:unit # node --test
187
+ cd core/workspace-app && npm test # unit + the cucumber browser suite
188
+ ```
189
+
190
+ The smoke test's exit code is the number of failures, and it needs no environment setup.
191
+
192
+ **A test that needs machine state skips with the reason**, at run time, rather than failing or being
193
+ excluded — see `tests/step-definitions/preconditions.steps.js`. A suite with permanent red in it stops
194
+ being read, which is how this one drifted for weeks without anyone noticing it could not even launch a
195
+ browser. (In cucumber that is `return 'skipped'`; `this.skip()` is Mocha's and does nothing here.)
196
+
197
+ ---
198
+
199
+ ## Shipping a change
200
+
201
+ 1. **An issue first**, then a PR referencing it. Both end with the Checkpoints checklist; never tick
202
+ the human-review box.
203
+ 2. Work in the git clone at `~/workspace/products/workspace` (branch `1.0.x`), not in the live tree.
204
+ 3. After a merge, bring it back with `bash ~/workspace/core/scripts/sync-tooling.sh --pull` — it
205
+ mirrors the scripts, the dashboard and each workspace's commands, prunes what the clone dropped,
206
+ re-applies this machine's docker GID, and runs the smoke test.
207
+ 4. A release is an annotated tag on `1.0.x` plus a GitHub release titled with the tag. There is no
208
+ version-bump commit; the installed version is a per-machine stamp, not a committed file.
209
+
210
+ **Never commit** real credentials, API keys, machine paths or project names. The published files are
211
+ team-installable templates: `CHANGE_ME` and `sk-change-this` are what ship.
212
+
213
+ ---
214
+
215
+ ## Where the rest is
216
+
217
+ | | |
218
+ | --- | --- |
219
+ | the dashboard screen by screen, with pictures | [README.md](README.md) |
220
+ | using it | [USER-GUIDE.md](USER-GUIDE.md) |
221
+ | every command and flag, and how to install | `README.md` at the top of the repository |
222
+ | the distilled rules and traps | `docs/workspace-tips-and-rules.md` |
223
+ | the dashboard's internals and DDEV wiring | `core/workspace-app/README.md` |
224
+ | what changed and what was learned, dated | `worklogs/` |
package/docs/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # Webship Workspace documentation
2
+
3
+ Three documents, and this is the index.
4
+
5
+ | | |
6
+ | --- | --- |
7
+ | **[USER-GUIDE.md](USER-GUIDE.md)** | using the workspace: day one, the things you will actually do, and what to try when something is wrong. This is also the **Help** page in the dashboard |
8
+ | **[DEVELOPER-GUIDE.md](DEVELOPER-GUIDE.md)** | changing the tooling: adding a command, a builder, a workspace or a dashboard feature, and the rules that exist because something broke |
9
+ | **this file** | the dashboard screen by screen, with pictures — the long way round |
10
+
11
+ Beyond these three: `README.md` at the top of the repository is the full reference (every command,
12
+ every flag, how to install), `docs/workspace-tips-and-rules.md` is the distilled rules,
13
+ `core/workspace-app/README.md` is the dashboard's internals, and `worklogs/` is what changed, dated.
14
+
15
+ Every screenshot here is of a real machine. If the interface you have does not match one, the
16
+ screenshot is the thing that is out of date — say so.
17
+
18
+ ---
19
+
20
+ ## The home page
21
+
22
+ ![The home page](screenshots/guide-home.png)
23
+
24
+ Every folder in `~/workspace` is a **workspace** with a card. The count under each is real — projects
25
+ on disk, videos, agents — and **BUILDABLE** means that folder carries builders, so a project can be
26
+ created there.
27
+
28
+ Three parts of the frame are on every page:
29
+
30
+ | | |
31
+ | --- | --- |
32
+ | **the assistant**, left | the **AI Assistant** on every page, scoped to the one you are on: on `/dev` it knows dev's projects, commands and rules. It can actually run things. Rename it, per workspace or for the whole dashboard, with `assistant: name:` |
33
+ | **the actions rail**, right | collapsed to icons by default, because a project table wants the width. It never disappears, so "what can I do here" is always in the same place |
34
+ | **the version**, in the toolbar | beside the product name. `version unknown` means the tree was installed a way that recorded none — the tooltip lists the commands that set it |
35
+
36
+ Each card carries **Edit** (its icon and description, in a window over the page) and **Settings** (its
37
+ own settings file, with the other files as tabs) in its corner:
38
+
39
+ ![Edit the Dev card](screenshots/home/edit-card.png)
40
+
41
+ **Drag a card** to reorder: it lifts and follows the pointer, the others slide aside, the page scrolls at
42
+ its edges, and the order is saved when you let go (Escape puts it back; Alt with an arrow key moves the
43
+ focused card). The order is the `workspaces:` list in `core/config/settings.yml`, which drags the same
44
+ way in Settings. The icons come from each workspace's own settings file.
45
+
46
+ ![Dragging a card](screenshots/home/drag-reorder.png)
47
+
48
+ ---
49
+
50
+ ## A workspace page
51
+
52
+ ![A workspace page](screenshots/guide-workspace-page.png)
53
+
54
+ **What would you like to create?** at the top (a builder and a name, or **New folder**), then the
55
+ projects. **Build** runs in the background in a screen session — close the tab if you like, it keeps
56
+ going. **Start AI** under it starts Claude Code in the new project once the build succeeds.
57
+
58
+ ![Start AI after the build](screenshots/create-card/start-ai.png)
59
+
60
+ The state on each card (Running, Stopped, Paused) is also the switch: click it to start or stop the
61
+ project.
62
+
63
+ Each row carries the project's state and its addresses. A project has two that always work:
64
+
65
+ - `https://<project>.ddev.site` — the canonical one DDEV gives it
66
+ - `https://<project>.<workspace>.workspace.ddev.site` — the hierarchical one the dashboard hands out
67
+
68
+ and can be given more (see [Domains](#domains-what-a-project-answers-at)).
69
+
70
+ The captions under the buttons are status, not decoration: `22 commands`, `not linked`, `no test
71
+ report yet`, `offline`, `none`. They are what tells you whether pressing the button will do anything.
72
+
73
+ ---
74
+
75
+ ## The DDEV menu
76
+
77
+ ![The DDEV menu](screenshots/guide-ddev-menu.png)
78
+
79
+ Everything DDEV can do to one project, grouped: start and stop, the build (composer), drush, the
80
+ database, and the project itself. Each entry runs the real command in a screen session and streams the
81
+ output — there is nothing here you could not type, which is the point: the button and the command line
82
+ cannot drift.
83
+
84
+ What the project defines itself is there too, each in its own folding group: its **custom commands**
85
+ (`.ddev/commands/`), its **composer.json scripts** and its **package.json scripts**.
86
+
87
+ ![A project's custom DDEV commands](screenshots/projects/ddev-custom-commands.png)
88
+
89
+ **Destructive entries ask before they fire.** Drop the database opens the confirm dialog. Remove names
90
+ what goes, offers a database backup first, and keeps its button off until the project's name is typed.
91
+
92
+ ---
93
+
94
+ ## Cloning: a copy, or a branch
95
+
96
+ ![The clone form](screenshots/guide-clone-worktree-off.png)
97
+
98
+ **Clone** makes a second working project from an existing one, and the form asks which of the two
99
+ things you mean.
100
+
101
+ A **copy** (the default) copies the files and sends the database through a dump. The two share
102
+ nothing and can diverge for ever. Right when they are two different sites.
103
+
104
+ A **git worktree** is offered only when the project is a git checkout, labelled with the branch that
105
+ checkout is on:
106
+
107
+ ![The worktree option](screenshots/guide-clone-worktree-on.png)
108
+
109
+ One shared `.git` and one history, a branch per folder — and still its own DDEV project with its own
110
+ containers and its own database. Right when they are two *branches of one piece of work*: somebody's
111
+ merge request running beside your own build, without a second copy of a repository whose history is
112
+ often larger than the checkout.
113
+
114
+ The branch field appears only once the option is ticked. It defaults to the clone name, and the branch
115
+ is created from the current HEAD if it does not exist yet.
116
+
117
+ Three things it does for you, each of which is a mistake people make by hand:
118
+
119
+ 1. **The folder names the project.** The `name:` key is removed from the worktree's
120
+ `.ddev/config.yaml`, so the directory decides — leave it and the worktree collides with its source.
121
+ 2. **The database is never shared.** Two DDEV projects on one database corrupts on the first
122
+ `drush updatedb`.
123
+ 3. **The build is copied.** `vendor/`, contrib, the files directory and `settings.local.php` are
124
+ git-ignored, so a bare worktree is a source tree and not a running site.
125
+
126
+ Rows say which is which, so a listing shows what belongs to what:
127
+
128
+ ![Rows showing a worktree and its main checkout](screenshots/guide-rows-worktree.png)
129
+
130
+ The **IDE** caption says whether there is a repository at all — `code`, `code · git`,
131
+ `code · worktree` — and the branch is inside the menu, with anything unpushed:
132
+
133
+ ![The IDE menu](screenshots/guide-ide-menu.png)
134
+
135
+ ### Removing one is guarded
136
+
137
+ ![Remove refuses to destroy unpushed work](screenshots/guide-remove-refusal.png)
138
+
139
+ Two removals are refused before anything is deleted, because neither is recoverable and `rm -rf`
140
+ warns about neither:
141
+
142
+ - **the main checkout, while worktrees still live off it** — they would be left files with no
143
+ repository at all;
144
+ - **anything holding commits that are on no remote**, or uncommitted changes.
145
+
146
+ Tick the extra box (or pass `--force`) to go ahead anyway. Removing a worktree normally also prunes
147
+ its registration in the main checkout, so a later clone to the same folder is not refused by git.
148
+
149
+ ---
150
+
151
+ ## Domains: what a project answers at
152
+
153
+ ![The domains dialog](screenshots/guide-domains.png)
154
+
155
+ **DDEV → Domains this answers at…** lists the canonical address, the hierarchical one, and any extra
156
+ ones, and offers `<project>-<workspace>` — the workspace in one flat hostname.
157
+
158
+ It **adds**; it never renames. The canonical URL keeps working, nothing is rebuilt, and the DDEV
159
+ project name goes on matching the folder. A new address reaches the router only when the project next
160
+ starts, so the dialog offers to do that and says why.
161
+
162
+ Two projects cannot share a hostname — the router gives it to whichever started last and the other
163
+ quietly stops answering — so that is refused, naming the project that already has it.
164
+
165
+ The clone form offers the same for the copy it is about to make, and the label follows the name as you
166
+ type it:
167
+
168
+ ![The clone form's address option](screenshots/guide-clone-address.png)
169
+
170
+ ---
171
+
172
+ ## Screens: anything that takes time
173
+
174
+ ![The screens page](screenshots/guide-screens.png)
175
+
176
+ Builds, installs, test suites and AI agents run in **screen sessions**, so they survive you closing
177
+ the tab, logging out, or the dashboard restarting. This page lists them all and opens a real terminal
178
+ on any of them — you can type into it.
179
+
180
+ The useful part is the state. A session that has **stopped to ask you something** is flagged
181
+ `✋ waiting on a choice` instead of looking busy for ever, which is the difference between a job that
182
+ is running and a job that has been waiting for you for an hour.
183
+
184
+ Every card has a **SCREENS** menu too: the sessions running in that project, **New screen here…**,
185
+ **Claude Code here…**, and **Exit Claude Code** or **Resume Claude Code**.
186
+
187
+ ![The SCREENS menu on a card](screenshots/screens-menu.png)
188
+
189
+ ---
190
+
191
+ ## Settings
192
+
193
+ ![The settings page](screenshots/guide-settings.png)
194
+
195
+ `core/config/settings.yml` and every `workspace.<name>.settings.yml`, edited from the browser. Two
196
+ things about it are deliberate and worth knowing:
197
+
198
+ - **A secret's value never leaves the server.** Anything whose key looks like a password, token or API
199
+ key renders as an empty field marked *set — leave blank to keep it*. The value is not in the page,
200
+ not in the response after saving, and not in `actions.log`.
201
+ - **The file is rewritten line by line**, not parsed and re-dumped, so your comments and formatting
202
+ survive and a one-key change is a one-line diff.
203
+
204
+ Every YAML list is edited as rows with an Add field, nested ones included
205
+ (`auth.google.allowed_domains`, `auth.google.allowed_emails`):
206
+
207
+ ![The Google sign-in lists](screenshots/settings/auth-google-lists.png)
208
+
209
+ The `workspaces:` list drags like the home page cards — that order is the card order. Adding one here
210
+ creates the settings file, the folder and the DDEV hostname together. A workspace nothing uses can be
211
+ deleted from its row, after three asks: the confirm dialog, its name typed, and `YES` typed.
212
+
213
+ ![Deleting an unused workspace: the last step](screenshots/settings/delete-4-type-yes.png)
214
+
215
+ ---
216
+
217
+ ## Keeping it up to date
218
+
219
+ ```bash
220
+ bash core/workspace-app/scripts/update.sh --check # what you have vs the latest release
221
+ bash core/workspace-app/scripts/update.sh # update the dashboard
222
+ bash core/scripts/sync-tooling.sh --pull # the cmd-*.sh commands and the shared core
223
+ ```
224
+
225
+ The version you are on is in the toolbar. The updater touches **only** `core/workspace-app`, and it
226
+ refuses the tooling job rather than half-doing it — so a release that adds or changes a `cmd-*.sh`
227
+ needs `sync-tooling.sh --pull` as well, and the release notes say when.
228
+
229
+ Some releases change the container image; the notes say so, and then
230
+ `cd core/workspace-app && ddev restart` is needed too.
231
+
232
+ > **If you are on 1.0.9 exactly**, run `bash core/scripts/docker-gid.sh` once. That release's updater
233
+ > mirrored the release's `.ddev/` over yours without putting this machine's docker GID back, and the
234
+ > symptom is delayed: nothing is wrong until the next `ddev restart`, when the dashboard shows **every
235
+ > project as Stopped** while they all run perfectly well. The next release re-applies it for you.
236
+
237
+ ---
238
+
239
+ ## Where the rest is
240
+
241
+ | | |
242
+ | --- | --- |
243
+ | changing the tooling | [DEVELOPER-GUIDE.md](DEVELOPER-GUIDE.md) |
244
+ | installing, updating, the command chain | `README.md` at the top of this repository |
245
+ | using it | [USER-GUIDE.md](USER-GUIDE.md) |
246
+ | the dashboard's internals, DDEV wiring, the security note | `core/workspace-app/README.md` |
247
+ | the hard rules and the operational traps | `docs/workspace-tips-and-rules.md` |
248
+ | what changed, dated | `worklogs/` |
249
+
250
+ Every picture in `screenshots/` is used by one of these guides or the top `README.md`, and all of them
251
+ were taken on 22 October 2026 at 1920×1080, in light mode (the README's dark copies aside). A pull
252
+ request carries its own pictures on its branch.