@webship.co/workspace 1.0.0-beta1 → 1.0.0-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +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/package.json +38 -36
- /package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +0 -0
- /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,152 @@
|
|
|
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
|
+
const cfg = await speechConfig(this.page);
|
|
43
|
+
assert.ok(cfg, 'there is no deep-chat element on this page');
|
|
44
|
+
assert.notStrictEqual(cfg.textToSpeech, 'unset', 'deep-chat was never given a textToSpeech config');
|
|
45
|
+
assert.notStrictEqual(cfg.textToSpeech, false, 'voice output is switched off');
|
|
46
|
+
// Its presence IS the read-aloud behaviour; there is no separate switch to check.
|
|
47
|
+
assert.ok(Number.isFinite(cfg.textToSpeech.rate), 'no speech rate, so the config is not the resolved one');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* What the composer LOOKS like, from inside deep-chat's shadow root.
|
|
52
|
+
*
|
|
53
|
+
* Its input lives in that shadow root, and so does the microphone, so neither is reachable from the
|
|
54
|
+
* page's own DOM — and the placeholder is drawn as a pseudo-element, which has no node at all. The
|
|
55
|
+
* only honest way to ask "is anything drawn there" is the computed `content`, which is why this
|
|
56
|
+
* reads styles rather than text.
|
|
57
|
+
*/
|
|
58
|
+
async function composer(page) {
|
|
59
|
+
return page.evaluate(() => {
|
|
60
|
+
const el = document.querySelector('deep-chat');
|
|
61
|
+
const root = el && el.shadowRoot;
|
|
62
|
+
if (!root) return null;
|
|
63
|
+
const input = root.querySelector('#text-input');
|
|
64
|
+
const mic = root.querySelector('#microphone-button');
|
|
65
|
+
return {
|
|
66
|
+
drawn: input ? getComputedStyle(input, '::before').content : null,
|
|
67
|
+
name: input ? input.getAttribute('aria-label') : null,
|
|
68
|
+
mic: !!mic,
|
|
69
|
+
micName: mic ? (mic.getAttribute('aria-label') || '') : '',
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Then(/^the assistant should show a named microphone button$/, async function () {
|
|
75
|
+
const c = await composer(this.page);
|
|
76
|
+
assert.ok(c, 'there is no deep-chat element on this page');
|
|
77
|
+
assert.ok(c.mic, 'there is no microphone in the composer, so nothing says voice is available');
|
|
78
|
+
// deep-chat's own buttons are role="button" with an SVG inside and no accessible name, so this is
|
|
79
|
+
// a name ui.js puts on. Without it a screen reader announces an unnamed button.
|
|
80
|
+
assert.ok(c.micName.trim(), 'the microphone has no accessible name');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
Then(/^the assistant composer should draw no placeholder$/, async function () {
|
|
84
|
+
const c = await composer(this.page);
|
|
85
|
+
assert.ok(c, 'there is no deep-chat element on this page');
|
|
86
|
+
// An empty `content` is the only thing that means nothing is drawn. "none" would mean the
|
|
87
|
+
// pseudo-element is absent, which is also fine; a string means deep-chat's "Ask me anything!" is
|
|
88
|
+
// back, which is what an empty placeholder.text silently restores.
|
|
89
|
+
assert.ok(c.drawn === '""' || c.drawn === 'none' || c.drawn === '',
|
|
90
|
+
`the composer is drawing a placeholder: ${c.drawn}`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
Then(/^the assistant composer should have an accessible name$/, async function () {
|
|
94
|
+
const c = await composer(this.page);
|
|
95
|
+
assert.ok(c, 'there is no deep-chat element on this page');
|
|
96
|
+
assert.ok(c.name && c.name.trim(),
|
|
97
|
+
'the composer has no accessible name, so a screen reader announces an unlabelled text box');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
Then(/^the assistant speech language should be pinned$/, async function () {
|
|
101
|
+
const cfg = await speechConfig(this.page);
|
|
102
|
+
// A pinned language is the shipped default (en-US), because a recogniser left on the browser's
|
|
103
|
+
// language does not fail on the wrong one — it returns confident nonsense. `true` here would mean
|
|
104
|
+
// nothing is pinned, which is a valid choice but not what ships.
|
|
105
|
+
const ws = cfg.speechToText.webSpeech;
|
|
106
|
+
assert.ok(ws && typeof ws === 'object' && ws.language,
|
|
107
|
+
`no language is pinned (webSpeech is ${JSON.stringify(ws)}); the shipped default is en-US`);
|
|
108
|
+
assert.match(ws.language, /^[a-z]{2}-[A-Z]{2}$/, `"${ws.language}" is not a BCP-47 tag`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
Then(/^the assistant should have no voice commands$/, async function () {
|
|
112
|
+
const cfg = await speechConfig(this.page);
|
|
113
|
+
// Absent, not empty. An empty phrase is not a disabled command — it is one that fires on anything.
|
|
114
|
+
assert.ok(!('commands' in cfg.speechToText),
|
|
115
|
+
`voice commands are armed with nothing configured: ${JSON.stringify(cfg.speechToText.commands)}`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
Then(/^the assistant speech colours should be design tokens$/, async function () {
|
|
119
|
+
const cfg = await speechConfig(this.page);
|
|
120
|
+
const { textColor } = cfg.speechToText;
|
|
121
|
+
assert.ok(textColor, 'interim and final speech text have no colours set');
|
|
122
|
+
for (const key of ['interim', 'final']) {
|
|
123
|
+
assert.match(textColor[key], /^var\(--/,
|
|
124
|
+
`speech ${key} colour is "${textColor[key]}", a literal that cannot follow the theme`);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
Then(/^the voice list should report what this browser has$/, async function () {
|
|
129
|
+
// The reading voice is a dropdown the server cannot fill in: which voices exist belongs to the
|
|
130
|
+
// browser and the OS. Either it gained real options, or it says there are none — an empty select
|
|
131
|
+
// with no explanation is the failure this guards, because that reads as "still loading" forever.
|
|
132
|
+
const state = await this.page.evaluate(async () => {
|
|
133
|
+
const select = document.querySelector('select[data-voice-name]');
|
|
134
|
+
const note = document.querySelector('[data-voice-list]');
|
|
135
|
+
if (!select) return { missing: true };
|
|
136
|
+
for (let i = 0; i < 20 && select.dataset.done !== '1'; i += 1) {
|
|
137
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
resolved: select.dataset.done === '1',
|
|
141
|
+
options: select.options.length,
|
|
142
|
+
hasDefault: [...select.options].some((o) => o.value === 'default'),
|
|
143
|
+
warns: !!(note && note.querySelector('.uk-text-danger')),
|
|
144
|
+
note: (note ? note.textContent : '').trim().slice(0, 80),
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
assert.ok(!state.missing, 'the reading voice is not a dropdown');
|
|
148
|
+
assert.ok(state.resolved, 'the voice list never resolved — it waits for voiceschanged with a timeout');
|
|
149
|
+
assert.ok(state.hasDefault, 'there is no "default" option, so the browser default is unreachable');
|
|
150
|
+
assert.ok(state.options > 1 || state.warns,
|
|
151
|
+
`only the default option and no explanation of why: "${state.note}"`);
|
|
152
|
+
});
|
|
@@ -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
|
+
});
|