@webship.co/workspace 1.0.0-alpha1 → 1.0.0-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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} +1 -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/{plugin-loader.js → core/workspace-app/plugin-loader.js} +19 -1
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{row-layout.js → core/workspace-app/row-layout.js} +0 -1
- package/{server.js → core/workspace-app/server.js} +26 -225
- 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 +39 -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 +40 -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 +152 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -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 +177 -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 +153 -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/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 +74 -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 +198 -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 +417 -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/{workspace-types.js → core/workspace-app/workspace-types.js} +2 -2
- package/{workspaces.js → core/workspace-app/workspaces.js} +1 -5
- package/package.json +38 -36
- package/actions/graphs.js +0 -107
- package/actions/obsidian.js +0 -59
- package/graphs.js +0 -104
- package/obsidian.js +0 -405
- /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-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/{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
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
module.exports = { settingValue, 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
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const { descriptionOf, suggestFor, aiInventory, TRIGGERS } = require('../../ai-inventory');
|
|
8
|
+
|
|
9
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-inv-'));
|
|
10
|
+
const write = (name, body) => { const f = path.join(tmp, name); fs.writeFileSync(f, body); return f; };
|
|
11
|
+
|
|
12
|
+
test('a plain frontmatter description is read', () => {
|
|
13
|
+
const f = write('a.md', '---\nname: a\ndescription: Build a Drupal project.\n---\n\n# body\n');
|
|
14
|
+
assert.equal(descriptionOf(f), 'Build a Drupal project.');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('a FOLDED description is joined back into one line', () => {
|
|
18
|
+
// YAML lets a long description continue on indented lines, and real agents use it —
|
|
19
|
+
// reading only the first line would cut the sentence that decides whether to use them.
|
|
20
|
+
const f = write('b.md', '---\nname: b\ndescription: Use this agent when\n the workspace needs a\n new builder script.\nmodel: opus\n---\n');
|
|
21
|
+
assert.equal(descriptionOf(f), 'Use this agent when the workspace needs a new builder script.');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('a following key does not get swallowed into the description', () => {
|
|
25
|
+
const f = write('c.md', '---\nname: c\ndescription: Short one.\nmodel: opus\ncolor: green\n---\n');
|
|
26
|
+
assert.equal(descriptionOf(f), 'Short one.');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('quotes are stripped, and a file with no frontmatter has no description', () => {
|
|
30
|
+
assert.equal(descriptionOf(write('d.md', '---\ndescription: "Quoted."\n---\n')), 'Quoted.');
|
|
31
|
+
assert.equal(descriptionOf(write('e.md', '# Just a heading\n')), '');
|
|
32
|
+
assert.equal(descriptionOf(path.join(tmp, 'missing.md')), '');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('each kind is offered by the right trigger', () => {
|
|
36
|
+
// The contract the popup and the Ace completer both rely on.
|
|
37
|
+
assert.deepEqual(TRIGGERS.agent, ['@']);
|
|
38
|
+
assert.deepEqual(TRIGGERS.command, ['/']);
|
|
39
|
+
assert.deepEqual(TRIGGERS.skill, ['@', '/'], 'a skill is both named and invoked');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('the inventory reads this machine and every item is well formed', () => {
|
|
43
|
+
const all = aiInventory();
|
|
44
|
+
assert.ok(all.length > 0, 'no items found at all');
|
|
45
|
+
for (const i of all.slice(0, 40)) {
|
|
46
|
+
assert.match(i.kind, /^(agent|skill|command)$/);
|
|
47
|
+
assert.ok(i.name && !i.name.endsWith('.md'), `bad name: ${i.name}`);
|
|
48
|
+
assert.ok(Array.isArray(i.triggers) && i.triggers.length, `no triggers: ${i.name}`);
|
|
49
|
+
assert.ok(i.where, `no provenance: ${i.name}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('a "/" query never returns an agent, and "@" never returns a command', () => {
|
|
54
|
+
assert.equal(suggestFor('/', '').filter((i) => i.kind === 'agent').length, 0);
|
|
55
|
+
assert.equal(suggestFor('@', '').filter((i) => i.kind === 'command').length, 0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('a name that starts with what was typed ranks above one that merely contains it', () => {
|
|
59
|
+
const hits = suggestFor('@', 'workspace');
|
|
60
|
+
const starts = hits.findIndex((i) => i.name.toLowerCase().startsWith('workspace'));
|
|
61
|
+
const contains = hits.findIndex((i) => !i.name.toLowerCase().startsWith('workspace')
|
|
62
|
+
&& i.name.toLowerCase().includes('workspace'));
|
|
63
|
+
if (starts !== -1 && contains !== -1) assert.ok(starts < contains, 'prefix match must come first');
|
|
64
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The illustrated guide, and its images.
|
|
3
|
+
*
|
|
4
|
+
* A screenshot that has been renamed or moved leaves a broken image, and a broken
|
|
5
|
+
* image in a guide is invisible to whoever wrote it and obvious to everybody else.
|
|
6
|
+
* Thirty-six screenshots sat in this folder referenced by nothing at all before the
|
|
7
|
+
* guide existed, which is the other half of the same problem.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const assert = require('node:assert');
|
|
11
|
+
const { test } = require('node:test');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const DOCS = path.join(__dirname, '..', '..', 'docs');
|
|
16
|
+
const GUIDE = path.join(DOCS, 'README.md');
|
|
17
|
+
|
|
18
|
+
test('every image the guides link to is actually there', () => {
|
|
19
|
+
const md = [GUIDE, path.join(DOCS, 'USER-GUIDE.md')].map((f) => fs.readFileSync(f, 'utf8')).join('\n');
|
|
20
|
+
const links = [...md.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)].map((m) => m[1]);
|
|
21
|
+
assert.ok(links.length >= 10, `expected the guide to be illustrated, found ${links.length} images`);
|
|
22
|
+
for (const rel of links) {
|
|
23
|
+
assert.ok(fs.existsSync(path.join(DOCS, rel)), `missing image: ${rel}`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('every image has alt text', () => {
|
|
28
|
+
// The guide is read on GitHub, in the repository, and by people who cannot see
|
|
29
|
+
// the picture. An empty alt is a picture that says nothing to them.
|
|
30
|
+
const md = fs.readFileSync(GUIDE, 'utf8');
|
|
31
|
+
for (const m of md.matchAll(/!\[([^\]]*)\]\(([^)]+)\)/g)) {
|
|
32
|
+
assert.ok(m[1].trim().length > 3, `no alt text for ${m[2]}`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('the guide links to the other docs, and they exist', () => {
|
|
37
|
+
const md = fs.readFileSync(GUIDE, 'utf8');
|
|
38
|
+
const root = path.join(__dirname, '..', '..', '..', '..');
|
|
39
|
+
for (const rel of ['docs/workspace-tips-and-rules.md', 'README.md']) {
|
|
40
|
+
assert.match(md, new RegExp(rel.replace(/[/.]/g, '\\$&')), `the guide never points at ${rel}`);
|
|
41
|
+
assert.ok(fs.existsSync(path.join(root, rel)), `${rel} does not exist`);
|
|
42
|
+
}
|
|
43
|
+
// The three documents point at each other, or the set is three orphans.
|
|
44
|
+
for (const f of ['USER-GUIDE.md', 'DEVELOPER-GUIDE.md']) {
|
|
45
|
+
assert.ok(fs.existsSync(path.join(DOCS, f)), `${f} is missing`);
|
|
46
|
+
assert.match(md, new RegExp(f), `the index never points at ${f}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('/help reads the guide from the app docs, and can serve its images', () => {
|
|
51
|
+
const src = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
52
|
+
assert.match(src, /APP_DOCS, 'USER-GUIDE\.md'/);
|
|
53
|
+
// Relative image paths work in the repository; over HTTP they need the route.
|
|
54
|
+
assert.match(src, /\/help\/screenshots\//);
|
|
55
|
+
// And the route must be contained: this is a docs folder, not a second static root.
|
|
56
|
+
assert.match(src, /realpathSync\(path\.join\(APP_DOCS, 'screenshots'\)\)/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('the README points at the guide, and says what it is', () => {
|
|
60
|
+
const md = fs.readFileSync(path.join(__dirname, '..', '..', '..', '..', 'README.md'), 'utf8');
|
|
61
|
+
assert.match(md, /core\/workspace-app\/docs/);
|
|
62
|
+
// One "New here" callout, not two: the second was a duplicate of the first.
|
|
63
|
+
assert.strictEqual((md.match(/New here/g) || []).length, 1);
|
|
64
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's avatar. What must never drift: every move the assistant is told it may write exists in
|
|
3
|
+
* the avatar; the 3D part is fetched only when the avatar is on (an import map, a module loaded on
|
|
4
|
+
* demand); `assistant.avatar: none` takes the control and the stage away; and the avatar's lines are
|
|
5
|
+
* asked for only while it is shown.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { test } = require('node:test');
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const app = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
|
|
14
|
+
const { AVATAR_GESTURES } = require('../../assistant');
|
|
15
|
+
|
|
16
|
+
test('every move the assistant may write is one the avatar can do', () => {
|
|
17
|
+
const avatar = app('public/assistant-avatar.js');
|
|
18
|
+
const moves = [...avatar.matchAll(/^ {2}([a-z]+): \{ duration:/gm)].map((m) => m[1]);
|
|
19
|
+
for (const g of AVATAR_GESTURES) assert.ok(moves.includes(g), `[${g}] is offered but the avatar has no such move`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('the libraries and the model are vendored, and fetched only when the avatar is shown', () => {
|
|
23
|
+
for (const f of ['public/vendor/three/three.module.min.js', 'public/vendor/three/three.core.min.js', 'public/vendor/three/LICENSE',
|
|
24
|
+
'public/vendor/three/addons/loaders/GLTFLoader.js', 'public/vendor/three-vrm/three-vrm.module.min.js', 'public/vendor/three-vrm/LICENSE',
|
|
25
|
+
'public/avatar/druplicon.vrm']) {
|
|
26
|
+
assert.ok(fs.existsSync(path.join(__dirname, '../..', f)), f);
|
|
27
|
+
}
|
|
28
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
29
|
+
assert.ok(ui.includes("import('/assistant-avatar.js')"), 'the 3D module is imported on demand');
|
|
30
|
+
const server = app('server.js');
|
|
31
|
+
assert.ok(server.includes('<script type="importmap">'), 'the import map that resolves three and three-vrm');
|
|
32
|
+
assert.ok(/\$\{avatarOn \? `<script src="\$\{assetUrl\('\/assistant-avatar-ui\.js'\)\}" defer>/.test(server), 'the wiring loads only when the avatar is on');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('the avatar lines are asked for only while it is shown, and never left in the reply', () => {
|
|
36
|
+
const assistant = app('assistant.js');
|
|
37
|
+
assert.ok(assistant.includes('...(avatar ? ['), 'the avatar instructions are conditional');
|
|
38
|
+
for (const line of ['BOARD', 'DRAW', 'SHOW', 'VIDEO', 'BROWSE', 'SEARCH', 'MOVE', 'SIZE']) {
|
|
39
|
+
assert.ok(assistant.includes(`/^\\s*${line}:\\s*`), `${line}: is taken out of the reply text`);
|
|
40
|
+
}
|
|
41
|
+
assert.ok(app('public/ui.js').includes("avatar: Boolean(window.wsAssistantAvatarOn && window.wsAssistantAvatarOn()), avatarName: window.wsAvatarName ? window.wsAvatarName() : '', avatarId: window.wsAvatarId ? window.wsAvatarId() : '', page: window.wsAvatarContext ? window.wsAvatarContext() : null"));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('every control is on the assistant side, and nothing is drawn on the avatar itself', () => {
|
|
45
|
+
const server = app('server.js');
|
|
46
|
+
const stage = server.slice(server.indexOf('<div class="assistant-avatar-stage"'), server.indexOf('<div class="assistant-avatar-chips"'));
|
|
47
|
+
assert.ok(stage.includes('<canvas'), 'the stage is the character');
|
|
48
|
+
assert.ok(!/<button/.test(stage), 'and carries no button: they are all in the panel bar');
|
|
49
|
+
for (const c of ['assistant-dock', 'dock-voice', 'dock-talk', 'dock-mic', 'data-kit="board"', 'data-pop="windows"', 'data-pop="settings"', 'assistant-avatar-chips']) {
|
|
50
|
+
assert.ok(server.includes(c), c);
|
|
51
|
+
}
|
|
52
|
+
assert.ok(!server.includes('data-pop="moves"'), 'the moves are the reply\'s to choose, not a list in the menu');
|
|
53
|
+
assert.ok(!server.includes('data-pop="open"'), 'no box for typing links in the panel: Druplicon opens pages when asked');
|
|
54
|
+
const menu = server.slice(server.indexOf('<div class="uk-inline assistant-avatar-menu">'), server.indexOf('<div class="uk-inline assistant-menu">'));
|
|
55
|
+
for (const c of ['assistant-avatar-toggle', 'data-kit="board"', 'data-pop="settings"', 'assistant-avatar-pops']) assert.ok(menu.includes(c), `${c} is in the robot button's menu`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('an empty saved volume never silences the voice', () => {
|
|
59
|
+
const ui = app('public/ui.js');
|
|
60
|
+
assert.ok(ui.includes("if (raw !== '' && raw !== null && Number.isFinite(v) && v >= min && v <= max) narrationParams[key] = v;"),
|
|
61
|
+
'volume, speed and pitch: an empty cookie means not chosen, never 0');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('every big move the assistant may write is one the page can do, and spin, walk and leap exist', () => {
|
|
65
|
+
const { AVATAR_BIG_MOVES } = require('../../assistant');
|
|
66
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
67
|
+
const big = ui.slice(ui.indexOf('const BIG = {'), ui.indexOf('};', ui.indexOf('const BIG = {')));
|
|
68
|
+
for (const m of Object.keys(AVATAR_BIG_MOVES)) assert.match(big, new RegExp(`(async )?${m}(\\(\\)|:)`), `[${m}] is offered but the page has no such big move`);
|
|
69
|
+
const avatar = app('public/assistant-avatar.js');
|
|
70
|
+
for (const m of ['walk', 'spin', 'leap']) assert.match(avatar, new RegExp(`^ ${m}: \\{ duration:`, 'm'), m);
|
|
71
|
+
assert.ok(/if \(reduced\(\)\) return still/.test(big), 'reduced motion keeps it on the spot');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('the avatar scripts parse: a clash in one leaves the page with no avatar at all', () => {
|
|
75
|
+
for (const f of ['public/assistant-avatar.js', 'public/assistant-avatar-moves.js', 'public/assistant-avatar-ui.js']) {
|
|
76
|
+
// Modules: their imports and exports taken off, the rest must compile as a function body.
|
|
77
|
+
const body = app(f).replace(/^import .*$/gm, '').replace(/^export (const|function|async function|let|class) /gm, '$1 ');
|
|
78
|
+
assert.doesNotThrow(() => new Function(body), f);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('every avatar may speak in an ElevenLabs voice, and none does by default', () => {
|
|
83
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
84
|
+
assert.ok(ui.includes("voice().set('voiceName', String(getPref(`ws-voice-name-${a.id}`, '') || ''))"), 'a voice per avatar, free until one is picked');
|
|
85
|
+
assert.ok(app('public/ui.js').includes("if (/^elevenlabs:/i.test(String(narrationParams.voiceName || ''))) delete narrationParams.voiceName;"),
|
|
86
|
+
'settings.yml cannot make a paid voice the default');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('the interface glyphs are vendored and used, never a temporary Figma link', () => {
|
|
90
|
+
const dir = path.join(__dirname, '../../public/vendor/webship-icons');
|
|
91
|
+
for (const f of ['ui/sound-on.svg', 'ui/sound-off.svg', 'ui/play.svg', 'ui/pause.svg', 'ui/stop.svg', 'ui/headset.svg', 'ui/share.svg', 'ui/menu.svg',
|
|
92
|
+
'ui/bookmark-outlined.svg']) {
|
|
93
|
+
const svg = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
94
|
+
assert.match(svg, /^<svg[^>]*width="[\d.]+" height="[\d.]+"/, `${f} is an SVG with its own size`);
|
|
95
|
+
}
|
|
96
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
97
|
+
for (const name of ['sound-on', 'sound-off', 'play', 'pause', 'stop', 'headset', 'share', 'bookmark-outlined']) assert.ok(ui.includes(`'${name}'`), `${name} is used`);
|
|
98
|
+
for (const f of ['public/assistant-avatar-ui.js', 'server.js', 'public/css/components/assistant-avatar.css']) assert.ok(!/figma\.com\/api\/mcp\/asset/.test(app(f)), `${f} links no temporary Figma asset`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('while it works the avatar shows a status as the Claude Code CLI does, and acts each one out', () => {
|
|
102
|
+
const ui = app('public/assistant-avatar-ui.js');
|
|
103
|
+
const list = ui.slice(ui.indexOf('const STATUSES = ['), ui.indexOf('];', ui.indexOf('const STATUSES = [')));
|
|
104
|
+
const moves = [...list.matchAll(/\['([A-Z][a-z]+)', '([a-z]+)'\]/g)];
|
|
105
|
+
assert.ok(moves.length >= 6 && moves.some(([, v]) => v === 'Tempering'), 'Thinking, Tempering and the rest');
|
|
106
|
+
const avatar = app('public/assistant-avatar.js');
|
|
107
|
+
for (const [, , m] of moves) assert.match(avatar, new RegExp(`^ ${m}: \\{ duration:`, 'm'), `${m} is a shared move, so every avatar can do it`);
|
|
108
|
+
assert.ok(ui.includes("avatar.gesture(failed ? 'shrug' : 'thumbsup')"), 'Done or Stopped, with a move');
|
|
109
|
+
assert.ok(app('public/ui.js').includes("el.onError = () => setWorking(false, true);"), 'a failed request says so');
|
|
110
|
+
});
|