@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.
- package/LICENSE +339 -0
- package/README.md +28 -364
- package/core/bin/workspace.js +135 -0
- package/core/config/settings.labels.yml +514 -0
- package/core/config/settings.template.yml +292 -0
- package/core/config/style.yml +53 -0
- package/core/config/theme.default.yml +161 -0
- package/core/config/theme.dispatch.yml +166 -0
- package/core/config/theme.drupal.yml +175 -0
- package/core/config/theme.ember.yml +160 -0
- package/core/config/theme.github.yml +172 -0
- package/core/config/theme.gitlab.yml +168 -0
- package/core/config/theme.press.yml +161 -0
- package/core/config/theme.slate.yml +160 -0
- package/core/config/theme.upsun.yml +174 -0
- package/core/docs/architecture.svg +65 -0
- package/core/docs/getting-started.md +51 -0
- package/core/docs/plugins.md +27 -0
- package/core/docs/releases.md +23 -0
- package/core/docs/testing.md +15 -0
- package/core/docs/workspaces.md +25 -0
- package/core/scripts/args/arg-ai-items.sh +65 -0
- package/core/scripts/args/arg-autostart.sh +31 -0
- package/core/scripts/args/arg-avatar-agent.sh +28 -0
- package/core/scripts/args/arg-avatar-catalog.sh +12 -0
- package/core/scripts/args/arg-avatar-character.sh +68 -0
- package/core/scripts/args/arg-avatar-voice.sh +24 -0
- package/core/scripts/args/arg-avatar.sh +22 -0
- package/core/scripts/args/arg-checks.sh +36 -0
- package/core/scripts/args/arg-clone.sh +42 -0
- package/core/scripts/args/arg-commands.sh +51 -0
- package/core/scripts/args/arg-cucumber.sh +34 -0
- package/core/scripts/args/arg-distribution.sh +30 -0
- package/core/scripts/args/arg-drupal.sh +39 -0
- package/core/scripts/args/arg-drupal_cms.sh +18 -0
- package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
- package/core/scripts/args/arg-graphify.sh +36 -0
- package/core/scripts/args/arg-graphs.sh +12 -0
- package/core/scripts/args/arg-hostnames.sh +23 -0
- package/core/scripts/args/arg-milvus-backup.sh +8 -0
- package/core/scripts/args/arg-milvus-mcp.sh +12 -0
- package/core/scripts/args/arg-milvus.sh +32 -0
- package/core/scripts/args/arg-obsidian.sh +17 -0
- package/core/scripts/args/arg-odoo.sh +18 -0
- package/core/scripts/args/arg-penpot-backup.sh +8 -0
- package/core/scripts/args/arg-penpot-mcp.sh +15 -0
- package/core/scripts/args/arg-penpot-token.sh +17 -0
- package/core/scripts/args/arg-penpot.sh +44 -0
- package/core/scripts/args/arg-rag.sh +21 -0
- package/core/scripts/args/arg-ragify.sh +35 -0
- package/core/scripts/args/arg-reclaim.sh +18 -0
- package/core/scripts/args/arg-release.sh +21 -0
- package/core/scripts/args/arg-screen.sh +28 -0
- package/core/scripts/args/arg-site-template.sh +13 -0
- package/core/scripts/args/arg-spdd.sh +16 -0
- package/core/scripts/args/arg-specs.sh +22 -0
- package/core/scripts/args/arg-storage.sh +36 -0
- package/core/scripts/args/arg-testing.sh +36 -0
- package/core/scripts/args/arg-webship.sh +34 -0
- package/core/scripts/args/arg-webships-project.sh +22 -0
- package/core/scripts/args/arg-webships-site-template.sh +17 -0
- package/core/scripts/args/arg-webships.sh +34 -0
- package/core/scripts/args/arg-website-site-template.sh +17 -0
- package/core/scripts/args/arg-website.sh +25 -0
- package/core/scripts/args/arg-wordpress.sh +18 -0
- package/core/scripts/autostart-run.sh +162 -0
- package/core/scripts/autostart-stop.sh +30 -0
- package/core/scripts/autostart-watch.sh +43 -0
- package/core/scripts/autostart.sh +26 -0
- package/core/scripts/bootstrap-functions.sh +117 -0
- package/core/scripts/bootstrap-libraries.sh +10 -0
- package/core/scripts/bootstrap.sh +71 -0
- package/core/scripts/ddev-shim/ddev +122 -0
- package/core/scripts/docker-gid.sh +119 -0
- package/core/scripts/functions/arg-site-template.sh +28 -0
- package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
- package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
- package/core/scripts/functions/fun-ai-items.sh +621 -0
- package/core/scripts/functions/fun-ai-providers.sh +72 -0
- package/core/scripts/functions/fun-autostart.sh +429 -0
- package/core/scripts/functions/fun-avatar.sh +441 -0
- package/core/scripts/functions/fun-backup-item.sh +50 -0
- package/core/scripts/functions/fun-build-distribution.sh +226 -0
- package/core/scripts/functions/fun-build-site-template.sh +104 -0
- package/core/scripts/functions/fun-build-webships.sh +161 -0
- package/core/scripts/functions/fun-build-website.sh +199 -0
- package/core/scripts/functions/fun-checks.sh +243 -0
- package/core/scripts/functions/fun-clone-project.sh +289 -0
- package/core/scripts/functions/fun-commands.sh +521 -0
- package/core/scripts/functions/fun-database.sh +63 -0
- package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
- package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
- package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
- package/core/scripts/functions/fun-default-settings.sh +28 -0
- package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
- package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
- package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
- package/core/scripts/functions/fun-distribution-webship.sh +28 -0
- package/core/scripts/functions/fun-distribution-webships.sh +33 -0
- package/core/scripts/functions/fun-distribution-website.sh +19 -0
- package/core/scripts/functions/fun-drush.sh +48 -0
- package/core/scripts/functions/fun-elevenlabs.sh +179 -0
- package/core/scripts/functions/fun-files.sh +19 -0
- package/core/scripts/functions/fun-git-worktrees.sh +142 -0
- package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
- package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
- package/core/scripts/functions/fun-milvus.sh +584 -0
- package/core/scripts/functions/fun-odoo.sh +121 -0
- package/core/scripts/functions/fun-penpot.sh +724 -0
- package/core/scripts/functions/fun-project-name.sh +20 -0
- package/core/scripts/functions/fun-rag.sh +253 -0
- package/core/scripts/functions/fun-ragify.sh +165 -0
- package/core/scripts/functions/fun-reclaim.sh +297 -0
- package/core/scripts/functions/fun-release.sh +159 -0
- package/core/scripts/functions/fun-remove-project.sh +319 -0
- package/core/scripts/functions/fun-screen.sh +96 -0
- package/core/scripts/functions/fun-spdd.sh +676 -0
- package/core/scripts/functions/fun-specs-share.sh +237 -0
- package/core/scripts/functions/fun-storage.sh +347 -0
- package/core/scripts/functions/fun-testing.sh +329 -0
- package/core/scripts/functions/fun-users.sh +87 -0
- package/core/scripts/functions/fun-version.sh +72 -0
- package/core/scripts/functions/fun-video-edit.sh +48 -0
- package/core/scripts/functions/fun-voice.sh +45 -0
- package/core/scripts/functions/fun-wordpress.sh +59 -0
- package/core/scripts/host-mounts.sh +161 -0
- package/core/scripts/install.sh +602 -0
- package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
- package/core/scripts/libs/argparse.sh +93 -0
- package/core/scripts/libs/bash-yaml.sh +52 -0
- package/core/scripts/libs/checks_plan.js +312 -0
- package/core/scripts/libs/elevenlabs.py +627 -0
- package/core/scripts/libs/progress-bar.sh +19 -0
- package/core/scripts/libs/rag_index.py +446 -0
- package/core/scripts/libs/video_edit.py +636 -0
- package/core/scripts/libs/voice.py +430 -0
- package/core/scripts/plugins/jira/host-install.sh +119 -0
- package/core/scripts/plugins/jira/host-mounts.txt +2 -0
- package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
- package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
- package/core/scripts/plugins/upsun/host-install.sh +99 -0
- package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
- package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
- package/core/scripts/plugins.sh +152 -0
- package/core/scripts/release.sh +69 -0
- package/core/scripts/storage.sh +39 -0
- package/core/scripts/sync-tooling.sh +246 -0
- package/core/scripts/tests/cmd-smoke-test.sh +109 -0
- package/core/scripts/trust-https.sh +257 -0
- package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
- package/core/workspace-app/.ddev/config.yaml +400 -0
- package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
- package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
- package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
- package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
- package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
- package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +9 -1
- package/core/workspace-app/README.md +380 -0
- package/{actions → core/workspace-app/actions}/ddev.js +0 -1
- package/{actions → core/workspace-app/actions}/rag.js +0 -1
- package/{actions → core/workspace-app/actions}/testing.js +0 -1
- package/{assistant.js → core/workspace-app/assistant.js} +1 -10
- package/{git-info.js → core/workspace-app/git-info.js} +17 -1
- package/core/workspace-app/package.json +29 -0
- package/core/workspace-app/plugin-catalog.js +50 -0
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{server.js → core/workspace-app/server.js} +18 -0
- package/core/workspace-app/tests/features/actions-rail.feature +44 -0
- package/core/workspace-app/tests/features/ai-items.feature +23 -0
- package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
- package/core/workspace-app/tests/features/checks.feature +40 -0
- package/core/workspace-app/tests/features/clone-project.feature +30 -0
- package/core/workspace-app/tests/features/command-authoring.feature +58 -0
- package/core/workspace-app/tests/features/dashboard.feature +57 -0
- package/core/workspace-app/tests/features/menus.feature +29 -0
- package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
- package/core/workspace-app/tests/features/plugins.feature +20 -0
- package/core/workspace-app/tests/features/project-actions.feature +41 -0
- package/core/workspace-app/tests/features/screens.feature +29 -0
- package/core/workspace-app/tests/features/settings.feature +46 -0
- package/core/workspace-app/tests/features/test-reports.feature +32 -0
- package/core/workspace-app/tests/features/theme.feature +40 -0
- package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
- package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +157 -0
- package/core/workspace-app/tests/step-definitions/clone-project.steps.js +47 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +168 -0
- package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
- package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
- package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
- package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
- package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
- package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
- package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
- package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
- package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
- package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
- package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
- package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
- package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
- package/core/workspace-app/tests/unit/assistant.test.js +61 -0
- package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
- package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
- package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
- package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
- package/core/workspace-app/tests/unit/browsers.test.js +76 -0
- package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
- package/core/workspace-app/tests/unit/build-order.test.js +153 -0
- package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
- package/core/workspace-app/tests/unit/builders.test.js +158 -0
- package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
- package/core/workspace-app/tests/unit/changelog.test.js +223 -0
- package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
- package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
- package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
- package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
- package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
- package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
- package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
- package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
- package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
- package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
- package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
- package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
- package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
- package/core/workspace-app/tests/unit/create-card.test.js +85 -0
- package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
- package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
- package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-domains.test.js +179 -0
- package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
- package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
- package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
- package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
- package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
- package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
- package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
- package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
- package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
- package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
- package/core/workspace-app/tests/unit/density.test.js +52 -0
- package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
- package/core/workspace-app/tests/unit/event-log.test.js +102 -0
- package/core/workspace-app/tests/unit/favicon.test.js +68 -0
- package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
- package/core/workspace-app/tests/unit/git-info.test.js +26 -0
- package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
- package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
- package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
- package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
- package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
- package/core/workspace-app/tests/unit/images-create.test.js +59 -0
- package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
- package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
- package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
- package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
- package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
- package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
- package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
- package/core/workspace-app/tests/unit/narration.test.js +186 -0
- package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
- package/core/workspace-app/tests/unit/navigation.test.js +254 -0
- package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
- package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
- package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
- package/core/workspace-app/tests/unit/notes.test.js +128 -0
- package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
- package/core/workspace-app/tests/unit/page-width.test.js +59 -0
- package/core/workspace-app/tests/unit/php-version.test.js +90 -0
- package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
- package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
- package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
- package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
- package/core/workspace-app/tests/unit/plugins.test.js +58 -0
- package/core/workspace-app/tests/unit/preconditions.test.js +21 -0
- package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
- package/core/workspace-app/tests/unit/project-make.test.js +143 -0
- package/core/workspace-app/tests/unit/project-name.test.js +202 -0
- package/core/workspace-app/tests/unit/reader.test.js +62 -0
- package/core/workspace-app/tests/unit/release.test.js +92 -0
- package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
- package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
- package/core/workspace-app/tests/unit/round-11.test.js +31 -0
- package/core/workspace-app/tests/unit/round-12.test.js +37 -0
- package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
- package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
- package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
- package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
- package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
- package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
- package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
- package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
- package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
- package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
- package/core/workspace-app/tests/unit/screens.test.js +93 -0
- package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
- package/core/workspace-app/tests/unit/session-name.test.js +64 -0
- package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
- package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
- package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
- package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
- package/core/workspace-app/tests/unit/speech.test.js +290 -0
- package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
- package/core/workspace-app/tests/unit/status-language.test.js +47 -0
- package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
- package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
- package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
- package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
- package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
- package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
- package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
- package/core/workspace-app/tests/unit/themes.test.js +453 -0
- package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
- package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
- package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
- package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
- package/core/workspace-app/tests/unit/version-check.test.js +98 -0
- package/core/workspace-app/tests/unit/version-link.test.js +50 -0
- package/core/workspace-app/tests/unit/video-borrow.test.js +220 -0
- package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
- package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
- package/core/workspace-app/tests/unit/video-edit.test.js +436 -0
- package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
- package/core/workspace-app/tests/unit/waiting.test.js +48 -0
- package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
- package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
- package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
- package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
- package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
- package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
- package/package.json +38 -36
- /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
- /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
- /package/{actions → core/workspace-app/actions}/audio.js +0 -0
- /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
- /package/{actions → core/workspace-app/actions}/checks.js +0 -0
- /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
- /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
- /package/{actions → core/workspace-app/actions}/composer.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
- /package/{actions → core/workspace-app/actions}/folders.js +0 -0
- /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
- /package/{actions → core/workspace-app/actions}/images.js +0 -0
- /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
- /package/{actions → core/workspace-app/actions}/node.js +0 -0
- /package/{actions → core/workspace-app/actions}/notes.js +0 -0
- /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
- /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
- /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
- /package/{actions → core/workspace-app/actions}/screens.js +0 -0
- /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
- /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
- /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
- /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
- /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
- /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
- /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
- /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
- /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
- /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
- /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
- /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
- /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
- /package/{checks.js → core/workspace-app/checks.js} +0 -0
- /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
- /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
- /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
- /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
- /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
- /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
- /package/{commands.js → core/workspace-app/commands.js} +0 -0
- /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
- /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
- /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
- /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
- /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
- /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
- /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
- /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
- /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
- /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
- /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
- /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/README.md +0 -0
- /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
- /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
- /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
- /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
- /package/{folders.js → core/workspace-app/folders.js} +0 -0
- /package/{format.js → core/workspace-app/format.js} +0 -0
- /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
- /package/{html.js → core/workspace-app/html.js} +0 -0
- /package/{icons.js → core/workspace-app/icons.js} +0 -0
- /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
- /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
- /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
- /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
- /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
- /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
- /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
- /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
- /package/{images.js → core/workspace-app/images.js} +0 -0
- /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
- /package/{linger.js → core/workspace-app/linger.js} +0 -0
- /package/{lists.js → core/workspace-app/lists.js} +0 -0
- /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
- /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
- /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
- /package/{notes.js → core/workspace-app/notes.js} +0 -0
- /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
- /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
- /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
- /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
- /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
- /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
- /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
- /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
- /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
- /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
- /package/{public → core/workspace-app/public}/css/base.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/favicon.ico +0 -0
- /package/{public → core/workspace-app/public}/favicon.png +0 -0
- /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
- /package/{public → core/workspace-app/public}/logo.png +0 -0
- /package/{public → core/workspace-app/public}/media-player.js +0 -0
- /package/{public → core/workspace-app/public}/mic.js +0 -0
- /package/{public → core/workspace-app/public}/narration.js +0 -0
- /package/{public → core/workspace-app/public}/palette.js +0 -0
- /package/{public → core/workspace-app/public}/sortable.js +0 -0
- /package/{public → core/workspace-app/public}/suggest.js +0 -0
- /package/{public → core/workspace-app/public}/terminal.js +0 -0
- /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
- /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
- /package/{public → core/workspace-app/public}/ui.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
- /package/{rag.js → core/workspace-app/rag.js} +0 -0
- /package/{reader.js → core/workspace-app/reader.js} +0 -0
- /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
- /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
- /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
- /package/{screenrc → core/workspace-app/screenrc} +0 -0
- /package/{screens.js → core/workspace-app/screens.js} +0 -0
- /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
- /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
- /package/{speech.js → core/workspace-app/speech.js} +0 -0
- /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
- /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
- /package/{themes.js → core/workspace-app/themes.js} +0 -0
- /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
- /package/{urls.js → core/workspace-app/urls.js} +0 -0
- /package/{validate.js → core/workspace-app/validate.js} +0 -0
- /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
- /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
- /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
- /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
- /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
- /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
- /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Steps for the assistant's voice configuration.
|
|
3
|
+
*
|
|
4
|
+
* These read the LIVE properties off the deep-chat element rather than the page source, because the
|
|
5
|
+
* page source cannot tell you whether the component accepted anything. deep-chat is configured with
|
|
6
|
+
* JavaScript properties (the objects cannot survive an HTML attribute), and it silently ignores keys
|
|
7
|
+
* it does not recognise — so "the config is in ui.js" and "the component is using it" are different
|
|
8
|
+
* facts, and only the second one matters.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here speaks or listens. An automated browser is not granted a microphone, and a scenario
|
|
11
|
+
* cannot talk. What CAN break without anyone noticing is the microphone being absent, or present
|
|
12
|
+
* with a configuration deep-chat quietly dropped, and that is what these assert.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const { Then } = require('@cucumber/cucumber');
|
|
18
|
+
const assert = require('assert');
|
|
19
|
+
|
|
20
|
+
/** The properties deep-chat is actually holding, read from inside the page. */
|
|
21
|
+
async function speechConfig(page) {
|
|
22
|
+
return page.evaluate(() => {
|
|
23
|
+
const el = document.querySelector('deep-chat');
|
|
24
|
+
if (!el) return null;
|
|
25
|
+
return {
|
|
26
|
+
speechToText: el.speechToText === undefined ? 'unset' : el.speechToText,
|
|
27
|
+
textToSpeech: el.textToSpeech === undefined ? 'unset' : el.textToSpeech,
|
|
28
|
+
placeholder: (el.textInput && el.textInput.placeholder && el.textInput.placeholder.text) || '',
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Then(/^the assistant should offer voice input$/, async function () {
|
|
34
|
+
const cfg = await speechConfig(this.page);
|
|
35
|
+
assert.ok(cfg, 'there is no deep-chat element on this page');
|
|
36
|
+
assert.notStrictEqual(cfg.speechToText, 'unset', 'deep-chat was never given a speechToText config');
|
|
37
|
+
assert.notStrictEqual(cfg.speechToText, false, 'voice input is switched off');
|
|
38
|
+
assert.ok(cfg.speechToText.webSpeech, 'no speech backend is configured, so the microphone does nothing');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
Then(/^the assistant should read its replies aloud$/, async function () {
|
|
42
|
+
// The replies are read by the panel's own narrator, not by deep-chat (whose textToSpeech the panel
|
|
43
|
+
// sets to false on purpose: it would speak the HTML). What must hold is that the panel was handed
|
|
44
|
+
// narration that is switched on and resolved: a voice with a rate.
|
|
45
|
+
const cfg = await this.page.evaluate(() => {
|
|
46
|
+
const el = document.querySelector('deep-chat');
|
|
47
|
+
if (!el) return null;
|
|
48
|
+
try { return JSON.parse(el.dataset.settings || '{}'); } catch (_) { return {}; }
|
|
49
|
+
});
|
|
50
|
+
assert.ok(cfg, 'there is no deep-chat element on this page');
|
|
51
|
+
assert.notStrictEqual(cfg.voiceOutput, false, 'voice output is switched off');
|
|
52
|
+
assert.ok(cfg.narration && Number.isFinite(cfg.narration.rate), 'no narration voice with a speech rate, so the config is not the resolved one');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* What the composer LOOKS like, from inside deep-chat's shadow root.
|
|
57
|
+
*
|
|
58
|
+
* Its input lives in that shadow root, and so does the microphone, so neither is reachable from the
|
|
59
|
+
* page's own DOM — and the placeholder is drawn as a pseudo-element, which has no node at all. The
|
|
60
|
+
* only honest way to ask "is anything drawn there" is the computed `content`, which is why this
|
|
61
|
+
* reads styles rather than text.
|
|
62
|
+
*/
|
|
63
|
+
async function composer(page) {
|
|
64
|
+
return page.evaluate(() => {
|
|
65
|
+
const el = document.querySelector('deep-chat');
|
|
66
|
+
const root = el && el.shadowRoot;
|
|
67
|
+
if (!root) return null;
|
|
68
|
+
const input = root.querySelector('#text-input');
|
|
69
|
+
const mic = root.querySelector('#microphone-button');
|
|
70
|
+
return {
|
|
71
|
+
drawn: input ? getComputedStyle(input, '::before').content : null,
|
|
72
|
+
name: input ? input.getAttribute('aria-label') : null,
|
|
73
|
+
mic: !!mic,
|
|
74
|
+
micName: mic ? (mic.getAttribute('aria-label') || '') : '',
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Then(/^the assistant should show a named microphone button$/, async function () {
|
|
80
|
+
const c = await composer(this.page);
|
|
81
|
+
assert.ok(c, 'there is no deep-chat element on this page');
|
|
82
|
+
assert.ok(c.mic, 'there is no microphone in the composer, so nothing says voice is available');
|
|
83
|
+
// deep-chat's own buttons are role="button" with an SVG inside and no accessible name, so this is
|
|
84
|
+
// a name ui.js puts on. Without it a screen reader announces an unnamed button.
|
|
85
|
+
assert.ok(c.micName.trim(), 'the microphone has no accessible name');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
Then(/^the assistant composer should draw no placeholder$/, async function () {
|
|
89
|
+
const c = await composer(this.page);
|
|
90
|
+
assert.ok(c, 'there is no deep-chat element on this page');
|
|
91
|
+
// An empty `content` is the only thing that means nothing is drawn. "none" would mean the
|
|
92
|
+
// pseudo-element is absent, which is also fine; a string means deep-chat's "Ask me anything!" is
|
|
93
|
+
// back, which is what an empty placeholder.text silently restores.
|
|
94
|
+
assert.ok(c.drawn === '""' || c.drawn === 'none' || c.drawn === '',
|
|
95
|
+
`the composer is drawing a placeholder: ${c.drawn}`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
Then(/^the assistant composer should have an accessible name$/, async function () {
|
|
99
|
+
const c = await composer(this.page);
|
|
100
|
+
assert.ok(c, 'there is no deep-chat element on this page');
|
|
101
|
+
assert.ok(c.name && c.name.trim(),
|
|
102
|
+
'the composer has no accessible name, so a screen reader announces an unlabelled text box');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
Then(/^the assistant speech language should be pinned$/, async function () {
|
|
106
|
+
const cfg = await speechConfig(this.page);
|
|
107
|
+
// A pinned language is the shipped default (en-US), because a recogniser left on the browser's
|
|
108
|
+
// language does not fail on the wrong one — it returns confident nonsense. `true` here would mean
|
|
109
|
+
// nothing is pinned, which is a valid choice but not what ships.
|
|
110
|
+
const ws = cfg.speechToText.webSpeech;
|
|
111
|
+
assert.ok(ws && typeof ws === 'object' && ws.language,
|
|
112
|
+
`no language is pinned (webSpeech is ${JSON.stringify(ws)}); the shipped default is en-US`);
|
|
113
|
+
assert.match(ws.language, /^[a-z]{2}-[A-Z]{2}$/, `"${ws.language}" is not a BCP-47 tag`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
Then(/^the assistant should have no voice commands$/, async function () {
|
|
117
|
+
const cfg = await speechConfig(this.page);
|
|
118
|
+
// Absent, not empty. An empty phrase is not a disabled command — it is one that fires on anything.
|
|
119
|
+
assert.ok(!('commands' in cfg.speechToText),
|
|
120
|
+
`voice commands are armed with nothing configured: ${JSON.stringify(cfg.speechToText.commands)}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
Then(/^the assistant speech colours should be design tokens$/, async function () {
|
|
124
|
+
const cfg = await speechConfig(this.page);
|
|
125
|
+
const { textColor } = cfg.speechToText;
|
|
126
|
+
assert.ok(textColor, 'interim and final speech text have no colours set');
|
|
127
|
+
for (const key of ['interim', 'final']) {
|
|
128
|
+
assert.match(textColor[key], /^var\(--/,
|
|
129
|
+
`speech ${key} colour is "${textColor[key]}", a literal that cannot follow the theme`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
Then(/^the voice list should report what this browser has$/, async function () {
|
|
134
|
+
// The reading voice is a dropdown the server cannot fill in: which voices exist belongs to the
|
|
135
|
+
// browser and the OS. Either it gained real options, or it says there are none — an empty select
|
|
136
|
+
// with no explanation is the failure this guards, because that reads as "still loading" forever.
|
|
137
|
+
const state = await this.page.evaluate(async () => {
|
|
138
|
+
const select = document.querySelector('select[data-voice-name]');
|
|
139
|
+
const note = document.querySelector('[data-voice-list]');
|
|
140
|
+
if (!select) return { missing: true };
|
|
141
|
+
for (let i = 0; i < 20 && select.dataset.done !== '1'; i += 1) {
|
|
142
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
resolved: select.dataset.done === '1',
|
|
146
|
+
options: select.options.length,
|
|
147
|
+
hasDefault: [...select.options].some((o) => o.value === 'default'),
|
|
148
|
+
warns: !!(note && note.querySelector('.uk-text-danger')),
|
|
149
|
+
note: (note ? note.textContent : '').trim().slice(0, 80),
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
assert.ok(!state.missing, 'the reading voice is not a dropdown');
|
|
153
|
+
assert.ok(state.resolved, 'the voice list never resolved — it waits for voiceschanged with a timeout');
|
|
154
|
+
assert.ok(state.hasDefault, 'there is no "default" option, so the browser default is unreachable');
|
|
155
|
+
assert.ok(state.options > 1 || state.warns,
|
|
156
|
+
`only the default option and no explanation of why: "${state.note}"`);
|
|
157
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clone action, exercised on whatever project the dev workspace holds first, so the scenarios
|
|
3
|
+
* name no project of one machine (#89). The project is read from the tree the dashboard serves.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { Given, When, Then } = require('@cucumber/cucumber');
|
|
11
|
+
const assert = require('assert');
|
|
12
|
+
|
|
13
|
+
const APP = path.join(__dirname, '..', '..');
|
|
14
|
+
const BASE = () => process.env.LAUNCH_URL || 'https://workspace.ddev.site';
|
|
15
|
+
|
|
16
|
+
function firstProject(wsKey) {
|
|
17
|
+
const { loadWorkspaces } = require(path.join(APP, 'workspaces'));
|
|
18
|
+
const dir = loadWorkspaces()[wsKey].dir;
|
|
19
|
+
const first = fs.readdirSync(dir).filter((p) => !p.startsWith('.') && fs.statSync(path.join(dir, p)).isDirectory()).sort()[0];
|
|
20
|
+
assert.ok(first, `the ${wsKey} workspace holds no project to clone`);
|
|
21
|
+
return first;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Given('{string} is the first project of the {string} workspace', function (alias, wsKey) {
|
|
25
|
+
this.cloneProject = firstProject(wsKey); this.cloneWorkspace = wsKey;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
When('I open the clone form of the first project in {string}', async function (wsKey) {
|
|
29
|
+
this.cloneProject = firstProject(wsKey); this.cloneWorkspace = wsKey;
|
|
30
|
+
await this.page.goto(`${BASE()}/fragments/projects/clone-form/${wsKey}/${encodeURIComponent(this.cloneProject)}`, { waitUntil: 'domcontentloaded' });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
Then('the clone form proposes a copy of that project', async function () {
|
|
34
|
+
await this.page.locator(`input[name='to'][value='${this.cloneProject}-copy']`).waitFor({ state: 'attached', timeout: 10000 });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
When('I ask the live check about the name of the first project in {string}', async function (wsKey) {
|
|
38
|
+
this.cloneProject = firstProject(wsKey); this.cloneWorkspace = wsKey;
|
|
39
|
+
await this.page.goto(`${BASE()}/fragments/projects/clone-check?to=${encodeURIComponent(this.cloneProject)}&toWorkspace=${wsKey}`, { waitUntil: 'domcontentloaded' });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
When('I post a clone of the first project in {string} over itself', async function (wsKey) {
|
|
43
|
+
this.cloneProject = firstProject(wsKey); this.cloneWorkspace = wsKey;
|
|
44
|
+
const form = { workspace: wsKey, projectName: this.cloneProject, to: this.cloneProject, toWorkspace: wsKey };
|
|
45
|
+
const response = await this.page.request.post(`${BASE()}/actions/clone-project`, { form });
|
|
46
|
+
this.lastActionResponse = await response.text();
|
|
47
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenarios that need a state this machine may not be in, skipped WITH THE REASON.
|
|
3
|
+
*
|
|
4
|
+
* The suite runs against the live dashboard by design, so some scenarios describe real
|
|
5
|
+
* behaviour that can only be observed when the machine is set up for it: Jira writes
|
|
6
|
+
* switched off, a project that has actually been tested, a project that declares
|
|
7
|
+
* individual check aliases. On a machine without that state they failed, and a suite with
|
|
8
|
+
* permanent red in it stops being read — which is how this one drifted for weeks without
|
|
9
|
+
* anyone noticing it could not even launch a browser.
|
|
10
|
+
*
|
|
11
|
+
* The alternative — excluding them in cucumber.js — hides them completely, and a test
|
|
12
|
+
* nobody can see is a test nobody will fix. Returning 'skipped' from the hook keeps them in
|
|
13
|
+
* the run, in the output and counted, with the reason attached to the report:
|
|
14
|
+
*
|
|
15
|
+
* - # tests/features/jira-tasks.feature:16
|
|
16
|
+
* SKIPPED: jira.allow_writes is true on this machine …
|
|
17
|
+
*
|
|
18
|
+
* (`this.skip()` is Mocha's API, not cucumber's — the first version of this file used it and
|
|
19
|
+
* the scenarios ran and failed exactly as before.)
|
|
20
|
+
*
|
|
21
|
+
* Each precondition is checked at run time against the real machine, so the moment the
|
|
22
|
+
* state exists the scenario becomes a real test again with no edit here.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const { Before } = require('@cucumber/cucumber');
|
|
28
|
+
|
|
29
|
+
const ROOT = path.join(__dirname, '..', '..', '..', '..');
|
|
30
|
+
const APP = path.join(__dirname, '..', '..');
|
|
31
|
+
|
|
32
|
+
/** A scalar out of a YAML file, read the way the dashboard reads it: line by line. */
|
|
33
|
+
function settingValue(key) {
|
|
34
|
+
try {
|
|
35
|
+
const yml = fs.readFileSync(path.join(ROOT, 'core', 'config', 'settings.yml'), 'utf8');
|
|
36
|
+
const m = yml.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
|
|
37
|
+
return m ? m[1].trim().replace(/^["']|["']$/g, '').replace(/\s+#.*$/, '') : '';
|
|
38
|
+
} catch (_) { return ''; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Before({ tags: '@needs-jira-writes-off' }, function () {
|
|
42
|
+
// The scenario asserts that write modes are REFUSED, which is only true when writes are
|
|
43
|
+
// off. With them on, refusing would be the bug.
|
|
44
|
+
if (settingValue('allow_writes') === 'true') {
|
|
45
|
+
this.attach('SKIPPED: jira.allow_writes is true on this machine, so write modes are '
|
|
46
|
+
+ 'allowed — this scenario describes the refusal that only happens when it is false.');
|
|
47
|
+
return 'skipped';
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
Before({ tags: '@needs-test-report' }, function () {
|
|
53
|
+
// The Tests menu links a report only where one exists; the report pages need one to read.
|
|
54
|
+
const anyReport = (() => {
|
|
55
|
+
try {
|
|
56
|
+
const dev = path.join(ROOT, 'dev');
|
|
57
|
+
return fs.readdirSync(dev).some((p) => {
|
|
58
|
+
const dir = path.join(dev, p, 'tests', 'reports');
|
|
59
|
+
try { return fs.readdirSync(dir).some((f) => f.endsWith('.json')); } catch (_) { return false; }
|
|
60
|
+
});
|
|
61
|
+
} catch (_) { return false; }
|
|
62
|
+
})();
|
|
63
|
+
if (!anyReport) {
|
|
64
|
+
this.attach('SKIPPED: no project in dev/ has a test report yet — run a suite on one '
|
|
65
|
+
+ '(its Tests menu, or cmd-tools-testing.sh) and this becomes a real test again.');
|
|
66
|
+
return 'skipped';
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
Before({ tags: '@needs-check-aliases' }, function () {
|
|
72
|
+
// /actions/checks renders only for an alias whose status is READY. Declaring one is not
|
|
73
|
+
// enough: every alias in products/ is currently "no node_modules/.bin and no .pnp.cjs —
|
|
74
|
+
// pass --install", so they render as disabled spans and the runnable control correctly
|
|
75
|
+
// does not exist. Asking the app's own checksRowsOf rather than re-deriving it from
|
|
76
|
+
// package.json, because a precondition that guesses at the rule drifts from the rule —
|
|
77
|
+
// the first version of this hook checked "declares a lint/test script" and let the
|
|
78
|
+
// scenario run against sixteen aliases that could not be run.
|
|
79
|
+
let anyReady = false;
|
|
80
|
+
try {
|
|
81
|
+
const { checksRowsOf } = require(path.join(APP, 'checks'));
|
|
82
|
+
const { loadWorkspaces } = require(path.join(APP, 'workspaces'));
|
|
83
|
+
const dir = loadWorkspaces().products.dir;
|
|
84
|
+
for (const p of fs.readdirSync(dir)) {
|
|
85
|
+
if (!fs.statSync(path.join(dir, p)).isDirectory()) continue;
|
|
86
|
+
const rows = checksRowsOf('products', dir, p) || [];
|
|
87
|
+
if (rows.some((r) => r.status === 'ready')) { anyReady = true; break; }
|
|
88
|
+
}
|
|
89
|
+
} catch (_) { anyReady = false; }
|
|
90
|
+
if (!anyReady) {
|
|
91
|
+
this.attach('SKIPPED: no project in products/ has a RUNNABLE check alias — they are '
|
|
92
|
+
+ 'declared but their dependencies are not installed, so every alias renders disabled '
|
|
93
|
+
+ 'and the per-alias control has nothing to offer. Install deps in one and this '
|
|
94
|
+
+ 'becomes a real test again.');
|
|
95
|
+
return 'skipped';
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
Before({ tags: '@needs-testing-stack' }, function () {
|
|
101
|
+
// A row offers "Run the tests" only when the project has a testing stack (a webship-js project,
|
|
102
|
+
// or the init-full-automated-testing command); the Tests menu of a bare site has nothing to run.
|
|
103
|
+
let any = false;
|
|
104
|
+
try {
|
|
105
|
+
const { testingStackOf } = require(path.join(APP, 'test-reports'));
|
|
106
|
+
const { loadWorkspaces } = require(path.join(APP, 'workspaces'));
|
|
107
|
+
const dir = loadWorkspaces().dev.dir;
|
|
108
|
+
any = fs.readdirSync(dir).some((p) => {
|
|
109
|
+
try { return fs.statSync(path.join(dir, p)).isDirectory() && testingStackOf(dir, p) !== 'none'; } catch (_) { return false; }
|
|
110
|
+
});
|
|
111
|
+
} catch (_) { any = false; }
|
|
112
|
+
if (!any) {
|
|
113
|
+
this.attach('SKIPPED: no project in dev/ has a testing stack (a cucumber.js, tests/step-definitions, '
|
|
114
|
+
+ 'or the init-full-automated-testing command), so no row offers "Run the tests". Set one up and '
|
|
115
|
+
+ 'this becomes a real test again.');
|
|
116
|
+
return 'skipped';
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
Before({ tags: '@needs-running-project' }, function () {
|
|
122
|
+
// The card shows a project's host only while DDEV reports it running.
|
|
123
|
+
let any = false;
|
|
124
|
+
try {
|
|
125
|
+
const { execFileSync } = require('child_process');
|
|
126
|
+
const { loadWorkspaces } = require(path.join(APP, 'workspaces'));
|
|
127
|
+
const dir = fs.realpathSync(loadWorkspaces().dev.dir);
|
|
128
|
+
const out = JSON.parse(execFileSync('ddev', ['list', '--json-output'], { encoding: 'utf8', timeout: 60000 }));
|
|
129
|
+
any = (out.raw || []).some((p) => p.status === 'running' && String(p.approot || '').startsWith(dir + path.sep));
|
|
130
|
+
} catch (_) { any = false; }
|
|
131
|
+
if (!any) {
|
|
132
|
+
this.attach('SKIPPED: no project in dev/ is running (ddev list), so no card shows a host. Start one '
|
|
133
|
+
+ 'and this becomes a real test again.');
|
|
134
|
+
return 'skipped';
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/** A scalar out of a plugin's own settings file (core/config/plugin.<name>.settings.yml), or ''. */
|
|
140
|
+
function pluginSettingValue(plugin, key) {
|
|
141
|
+
try {
|
|
142
|
+
const yml = fs.readFileSync(path.join(ROOT, 'core', 'config', `plugin.${plugin}.settings.yml`), 'utf8');
|
|
143
|
+
const m = yml.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
|
|
144
|
+
return m ? m[1].trim().replace(/^["']|["']$/g, '').replace(/\s+#.*$/, '') : '';
|
|
145
|
+
} catch (_) { return ''; }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// A plugin ships features into this suite but no hooks, so it states a precondition in its tag:
|
|
149
|
+
// `@needs-setting-<plugin>-<key>` skips the scenario, with the reason, while that key has no value in
|
|
150
|
+
// the plugin's settings file. The plugin name ends at the first hyphen that is followed by the key,
|
|
151
|
+
// so a plugin named with hyphens tags `@needs-setting-google-drive-folder_id` and the key is the part
|
|
152
|
+
// after the last plugin-name hyphen: the key itself never contains a hyphen (settings keys are snake_case).
|
|
153
|
+
const NEEDS_SETTING = /^@needs-setting-(.+)-([a-z0-9_]+)$/;
|
|
154
|
+
Before(function (scenario) {
|
|
155
|
+
for (const t of scenario.pickle.tags || []) {
|
|
156
|
+
const m = NEEDS_SETTING.exec(t.name);
|
|
157
|
+
if (!m) continue;
|
|
158
|
+
const [, plugin, key] = m;
|
|
159
|
+
if (!pluginSettingValue(plugin, key)) {
|
|
160
|
+
this.attach(`SKIPPED: core/config/plugin.${plugin}.settings.yml has no ${key} on this machine; `
|
|
161
|
+
+ `set it and this becomes a real test again.`);
|
|
162
|
+
return 'skipped';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
module.exports = { settingValue, pluginSettingValue, APP };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A copy of the workspace config for a test to run against, with the ElevenLabs settings put back to the
|
|
3
|
+
* shipped placeholders. A machine's own settings.yml holds its real key, voice and model, and a test that
|
|
4
|
+
* reads them passes on a clean clone and fails on the machine that uses the feature.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const PLACEHOLDERS = 'elevenlabs:\n api_key: CHANGE_ME\n voice_id: ask\n model_id: eleven_v4\n region: global\n';
|
|
11
|
+
|
|
12
|
+
/** The settings text without its top-level `elevenlabs:` block, whatever keys that block holds. */
|
|
13
|
+
function withoutElevenlabs(text) {
|
|
14
|
+
const out = [];
|
|
15
|
+
let skipping = false;
|
|
16
|
+
for (const line of text.split('\n')) {
|
|
17
|
+
if (/^elevenlabs:\s*(#.*)?$/.test(line)) { skipping = true; continue; }
|
|
18
|
+
if (skipping && /^(\s+\S|\s*$)/.test(line)) continue; // the block's own lines, and blanks inside it
|
|
19
|
+
skipping = false;
|
|
20
|
+
out.push(line);
|
|
21
|
+
}
|
|
22
|
+
return out.join('\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function copyConfig(from, to) {
|
|
26
|
+
fs.cpSync(from, to, { recursive: true });
|
|
27
|
+
const file = path.join(to, 'settings.yml');
|
|
28
|
+
fs.writeFileSync(file, `${withoutElevenlabs(fs.readFileSync(file, 'utf8')).replace(/\s*$/, '\n')}\n${PLACEHOLDERS}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { copyConfig, withoutElevenlabs, PLACEHOLDERS };
|
|
@@ -0,0 +1,124 @@
|
|
|
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 = path.join(__dirname, '..', '..');
|
|
7
|
+
const src = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
8
|
+
const css = fs.readdirSync(path.join(APP, 'public', 'css', 'components'))
|
|
9
|
+
.map((f) => fs.readFileSync(path.join(APP, 'public', 'css', 'components', f), 'utf8')).join('\n');
|
|
10
|
+
|
|
11
|
+
// Measured by walking Tab through /dev: the page's own content — the build form, the project
|
|
12
|
+
// list — was tab stop EIGHTEEN, after the toolbar, the whole assistant panel and eleven rail
|
|
13
|
+
// controls. With the skip link it is stop 2. That is the single biggest keyboard fix here, and
|
|
14
|
+
// it is invisible on screen, so nothing but a test notices it being removed.
|
|
15
|
+
test('a skip link is the first focusable thing, and points at the content', () => {
|
|
16
|
+
assert.ok(src.includes('class="skip-link" href="#main-content"'), 'no skip link');
|
|
17
|
+
assert.ok(src.includes('id="main-content"'), 'the skip link target is gone');
|
|
18
|
+
// It must never be display:none or visibility:hidden — both remove it from the tab order,
|
|
19
|
+
// which is the usual way a skip link ends up not working at all.
|
|
20
|
+
const rule = css.slice(css.indexOf('.skip-link'), css.indexOf('.skip-link') + 700);
|
|
21
|
+
assert.ok(!/display:\s*none/.test(rule), 'display:none takes the skip link out of the tab order');
|
|
22
|
+
assert.ok(!/visibility:\s*hidden/.test(rule), 'visibility:hidden takes it out of the tab order');
|
|
23
|
+
assert.ok(/\.skip-link:focus/.test(css), 'the skip link never becomes visible');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('the build form controls have accessible names', () => {
|
|
27
|
+
// A screen reader announced "edit text" and "combo box" with no indication of what either
|
|
28
|
+
// was for; they are the two controls the primary task runs through.
|
|
29
|
+
assert.match(src, /aria-label="Project name"/, 'the project name input has no accessible name');
|
|
30
|
+
assert.match(src, /aria-label="What to build"/, 'the builder select has no accessible name');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('the page heading level does not skip', () => {
|
|
34
|
+
// h1 "Dev" was followed by h3, leaving a level missing in the outline. uk-h3 keeps the size
|
|
35
|
+
// the design asks for: the LEVEL is structure, the class is appearance.
|
|
36
|
+
assert.match(src, /<h2 class="uk-h3 uk-margin-remove">What would you like to create\?<\/h2>/, 'the build heading skipped back to h3');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('checkboxes and radios show a focus ring', () => {
|
|
40
|
+
// UIKit draws none, so this was the one control a keyboard user could land on without
|
|
41
|
+
// seeing where they were.
|
|
42
|
+
assert.match(css, /\.uk-checkbox:focus-visible/, 'checkbox focus ring removed');
|
|
43
|
+
assert.match(css, /\.uk-radio:focus-visible/, 'radio focus ring removed');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('motion is guarded where it is used', () => {
|
|
47
|
+
// Three animations exist: the waiting badge, the mic while listening, and the skip link.
|
|
48
|
+
const guards = (css.match(/prefers-reduced-motion/g) || []).length;
|
|
49
|
+
assert.ok(guards >= 3, `only ${guards} reduced-motion guards for the animations in use`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// What an axe run over every page (light and dark, 36 combinations) found, and what must not come
|
|
55
|
+
// back. Each of these was a real violation, not a hypothetical.
|
|
56
|
+
|
|
57
|
+
test('no component fades text with opacity', () => {
|
|
58
|
+
// Every contrast failure but one came from the same habit: taking a token that was checked and
|
|
59
|
+
// dimming it again. --text-subtle at 75% on the dark rail is 4.4:1; brand blue at 60% on white is
|
|
60
|
+
// 2.42:1. Quiet is the token's job. Opacity on a whole control (a disabled pager arrow, a drag
|
|
61
|
+
// handle, a card being dragged) is fine — this is about opacity on a run of text.
|
|
62
|
+
const files = fs.readdirSync(path.join(APP, 'public', 'css', 'components'));
|
|
63
|
+
const offenders = [];
|
|
64
|
+
for (const f of files) {
|
|
65
|
+
// Comments first: they quote the very colours and opacities this is about.
|
|
66
|
+
const text = fs.readFileSync(path.join(APP, 'public', 'css', 'components', f), 'utf8')
|
|
67
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
68
|
+
// One rule at a time — a selector, then what it declares. A rule that sets BOTH a colour and a
|
|
69
|
+
// fractional opacity is the shape that keeps biting.
|
|
70
|
+
for (const m of text.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
|
|
71
|
+
const [, selector, body] = m;
|
|
72
|
+
if (/opacity:\s*0?\.\d/.test(body) && /(^|[\s;])color:\s*var\(--/.test(body)) {
|
|
73
|
+
offenders.push(`${f}: ${selector.trim().split('\n').pop().slice(0, 60)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
assert.deepEqual(offenders, [], 'set a quieter token, do not fade a checked one');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('the root element carries the page colour, and a short page fills the window', () => {
|
|
81
|
+
const base = fs.readFileSync(path.join(APP, 'public', 'css', 'base.css'), 'utf8');
|
|
82
|
+
// <html> was left at the browser's white while <body> went dark: the strip under a short page and
|
|
83
|
+
// the overscroll bounce both showed it.
|
|
84
|
+
assert.match(base, /html \{ background: var\(--surface\); min-height: 100%; \}/);
|
|
85
|
+
assert.match(base, /body \{ min-height: 100vh; \}/);
|
|
86
|
+
assert.match(base, /html\.dark \{ color-scheme: dark; \}/, 'so form controls and scrollbars follow too');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('keyboard focus is visible everywhere, not just where someone remembered it', () => {
|
|
90
|
+
const base = fs.readFileSync(path.join(APP, 'public', 'css', 'base.css'), 'utf8');
|
|
91
|
+
assert.match(base, /:focus-visible \{\s*\n\s*outline: 2px solid var\(--brand-primary\)/,
|
|
92
|
+
'there was no global focus style at all — the browser default is a dark dotted box');
|
|
93
|
+
// :focus-visible, not :focus: a mouse click must not draw a ring nobody asked for.
|
|
94
|
+
assert.ok(!/[^-]:focus \{[\s\S]{0,80}outline: 2px solid/.test(base), 'use :focus-visible');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('a card does not turn white under the cursor in dark mode', () => {
|
|
98
|
+
const cards = fs.readFileSync(path.join(APP, 'public', 'css', 'components', 'workspace-cards.css'), 'utf8');
|
|
99
|
+
// UIKit paints .uk-card-default.uk-card-hover:hover white — two classes, and its stylesheet is
|
|
100
|
+
// linked after ours, so a single-class rule of ours lost the tie.
|
|
101
|
+
assert.match(cards, /\.uk-card-default\.uk-card-hover:hover[\s\S]*?background-color: var\(--surface-card\)/);
|
|
102
|
+
assert.match(cards, /\.workspace-card:focus-visible/, 'and a card is a link: it needs a ring');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('every select the item rows render has a name', () => {
|
|
106
|
+
// 42 unnamed <select> on /ai-skills: a screen reader announced "combo box" forty-two times.
|
|
107
|
+
const rows = src.slice(src.indexOf('class="install-target"'), src.indexOf('class="install-target"') + 900);
|
|
108
|
+
assert.match(rows, /name="toWorkspace" aria-label="Install into: which workspace"/);
|
|
109
|
+
assert.match(rows, /name="toProject" aria-label="Install into: which project"/);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('the help page code blocks can be reached by keyboard', () => {
|
|
113
|
+
// pandoc writes <pre> that scrolls; a scrollable region with nothing focusable in it is a dead end.
|
|
114
|
+
assert.match(src, /replace\(\/<pre\(\?!\[\^>\]\*tabindex\)\/g, '<pre tabindex="0"'\)/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('deep-chat’s own buttons are named', () => {
|
|
118
|
+
const ui = fs.readFileSync(path.join(APP, 'public', 'ui.js'), 'utf8');
|
|
119
|
+
// They live in the component's shadow root with role="button" and an SVG inside, so they are named
|
|
120
|
+
// from our side rather than by forking the vendored bundle.
|
|
121
|
+
assert.match(ui, /const nameChatButtons/);
|
|
122
|
+
assert.match(ui, /'#microphone-button', 'Dictate the message'/);
|
|
123
|
+
assert.match(ui, /aria-label', label/);
|
|
124
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const root = path.join(__dirname, '..', '..');
|
|
7
|
+
const server = fs.readFileSync(path.join(root, 'server.js'), 'utf8');
|
|
8
|
+
const ui = fs.readFileSync(path.join(root, 'public', 'ui.js'), 'utf8');
|
|
9
|
+
const css = fs.readFileSync(path.join(root, 'public', 'css', 'components', 'full-height-right-actions-rail-pages-that-have-actions.css'), 'utf8');
|
|
10
|
+
|
|
11
|
+
test('only the home page marks All workspaces as the current page', () => {
|
|
12
|
+
assert.match(server, /navLink\(HOME_URL\(\), 'home', 'All workspaces', String\(context\) === 'home'\)/);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('the two actions that write into ~/.claude go through the confirm dialog and say so', () => {
|
|
16
|
+
assert.match(server, /sync\('install', 'push', 'Install all'.*into ~\/\.claude`, true\)/);
|
|
17
|
+
assert.match(server, /sync\('update', 'refresh', 'Update installed'.*into ~\/\.claude whose source moved`, true\)/);
|
|
18
|
+
assert.match(server, /hx-trigger="confirmed-remove"/);
|
|
19
|
+
assert.match(server, /writes \? 'arm-step' : ''/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('the rail draws every entry in one stroke, and Go is a navigation landmark', () => {
|
|
23
|
+
assert.match(server, /const RAIL_ICON_MAP = \{/);
|
|
24
|
+
assert.match(server, /railIcons\(navItemHtml\(it\)\)/);
|
|
25
|
+
assert.match(server, /aria-label="Workspace"/);
|
|
26
|
+
assert.match(server, /aria-label="Page actions"/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('a group of one that repeats its heading is marked, and its heading is not drawn twice', () => {
|
|
30
|
+
assert.match(server, /is-repeat/);
|
|
31
|
+
assert.match(css, /\.actions-group\.is-repeat > \.actions-group-label \{ display: none; \}/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('collapsed tiles are named and have a tooltip that shows on focus as well as hover', () => {
|
|
35
|
+
assert.match(ui, /setAttribute\('aria-label', name\)/);
|
|
36
|
+
assert.match(ui, /setAttribute\('aria-description', hint\)/);
|
|
37
|
+
assert.match(ui, /addEventListener\('focusin'/);
|
|
38
|
+
assert.match(ui, /el\.removeAttribute\('title'\)/);
|
|
39
|
+
assert.match(css, /#rail-tip \{/);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('keyboard focus is a ring, the toggle stays in view, and the rail says when there is more', () => {
|
|
43
|
+
assert.match(css, /\.actions-side \.action-btn:focus-visible \{\s*outline: 2px solid var\(--brand-primary\)/);
|
|
44
|
+
assert.match(css, /\.actions-head \{\s*position: sticky;/);
|
|
45
|
+
assert.match(css, /\.actions-side\.can-scroll \.actions-fade \{ opacity: 1; \}/);
|
|
46
|
+
assert.match(ui, /classList\.toggle\('can-scroll'/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('no hex colour is added to the rail', () => {
|
|
50
|
+
assert.doesNotMatch(css, /#[0-9a-fA-F]{3,8}\b(?![^{]*\})/);
|
|
51
|
+
assert.doesNotMatch(css.slice(css.indexOf('#rail-tip')), /#[0-9a-fA-F]{6}\b/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('the confirm dialog still names the action once the rail has moved its title to the tooltip', () => {
|
|
55
|
+
assert.match(ui, /btn\.getAttribute\('title'\) \|\| btn\.dataset\.tipHint \|\| btn\.textContent/);
|
|
56
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The actions rail's Go list.
|
|
3
|
+
*
|
|
4
|
+
* Rows management — the buttons a project row carries, what is inside them, and the actions on them — is a tab of the
|
|
5
|
+
* Commands page, so arranging the row you are looking at was three clicks away. Go carries it as **Action buttons**,
|
|
6
|
+
* between All workspaces and Commands, on every page.
|
|
7
|
+
*
|
|
8
|
+
* Writing a command is not a per-workspace action either: Commands → New scaffolds the same cmd-*.sh and asks which
|
|
9
|
+
* workspace it belongs to, so the workspace rail no longer repeats it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const assert = require('node:assert');
|
|
13
|
+
const { test } = require('node:test');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const src = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
18
|
+
|
|
19
|
+
test('Action buttons sits between All workspaces and Commands, and points at rows management', () => {
|
|
20
|
+
const go = src.slice(src.indexOf("groups.push({ id: 'go', label: 'Go'"), src.indexOf("navLink('/help'"));
|
|
21
|
+
const order = [...go.matchAll(/navLink\(HOME_URL\(\), 'home', 'All workspaces'|navLink\('\/settings\/buttons'|navLink\('\/commands'/g)].map((m) => m[0]);
|
|
22
|
+
assert.deepStrictEqual(order, ["navLink(HOME_URL(), 'home', 'All workspaces'", "navLink('/settings/buttons'", "navLink('/commands'"]);
|
|
23
|
+
assert.match(go, /navLink\('\/settings\/buttons', 'tabler:layout-grid', 'Action buttons', onButtons/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('the Buttons page is marked in Go, and still offered the Commands actions', () => {
|
|
27
|
+
// Buttons moved out of Commands and under Settings: what a project row offers is configuration.
|
|
28
|
+
assert.match(src, /const onButtons = String\(context\) === 'settings:buttons';/);
|
|
29
|
+
assert.match(src, /const ctx = String\(context\) === 'settings:buttons' \? 'settings' : String\(context\);/,
|
|
30
|
+
'the Buttons page gets the Settings groups, not an empty rail');
|
|
31
|
+
assert.match(src, /settingsPage\('buttons', readPageState\(req\), \{ fragment: isFragment\(req\) \}\)/, 'the page says which it is');
|
|
32
|
+
assert.match(src, /function commandsShell\(title, subtitle, active, body, context = 'commands'\)/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('writing a command lives with the commands, not on every workspace page', () => {
|
|
36
|
+
const groups = src.slice(src.indexOf('function pageActionGroups(context) {'), src.indexOf('function pageActionsHtml'));
|
|
37
|
+
assert.doesNotMatch(groups, /label: 'New commands'/, 'the workspace rail no longer repeats it');
|
|
38
|
+
const commandsPage = src.slice(src.indexOf("if (ctx === 'commands') {"), src.indexOf("groups.push({ id: 'go', label: 'Go'"));
|
|
39
|
+
assert.match(commandsPage, /'New command'/);
|
|
40
|
+
assert.match(commandsPage, /'Generate script with AI'/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('no page is left behind: the Actions page and its route are gone', () => {
|
|
44
|
+
assert.doesNotMatch(src, /function actionsPage\(/);
|
|
45
|
+
assert.doesNotMatch(src, /pathname === '\/actions' \|\| pathname === '\/actions\/'/);
|
|
46
|
+
assert.match(src, /if \(req\.method === 'POST' && pathname\.startsWith\('\/actions\/'\)\)/, 'the action namespace is untouched');
|
|
47
|
+
const ui = fs.readFileSync(path.join(__dirname, '..', '..', 'public', 'ui.js'), 'utf8');
|
|
48
|
+
assert.doesNotMatch(ui, /actions-jump/);
|
|
49
|
+
const css = fs.readFileSync(path.join(__dirname, '..', '..', 'public', 'css', 'components', 'full-height-right-actions-rail-pages-that-have-actions.css'), 'utf8');
|
|
50
|
+
assert.doesNotMatch(css, /actions-page/);
|
|
51
|
+
});
|