@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,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor modal fits the screen.
|
|
3
|
+
*
|
|
4
|
+
* At 1080px high New screen was 1205px tall (1565 with Claude Code) and its Start button sat 130 to 490px below
|
|
5
|
+
* the fold, with its title scrolled away above it; Sections of the video editor was 1404px. The dialog is now capped
|
|
6
|
+
* to the viewport and is a column: title and tabs pinned, the form scrolling, the action row pinned. The editors
|
|
7
|
+
* take the height that is left. A form opts in with `.modal-head` and `.modal-actions`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { test } = require('node:test');
|
|
11
|
+
const assert = require('node:assert');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const APP = path.join(__dirname, '../..');
|
|
16
|
+
const read = (f) => fs.readFileSync(path.join(APP, f), 'utf8');
|
|
17
|
+
const css = read('public/css/components/modal-fit.css');
|
|
18
|
+
const { assembleCss, listComponents } = require('../../themes');
|
|
19
|
+
|
|
20
|
+
test('the stylesheet is a component and comes after the rules it overrides', () => {
|
|
21
|
+
const list = listComponents();
|
|
22
|
+
assert.ok(list.includes('modal-fit'));
|
|
23
|
+
assert.ok(list.indexOf('modal-fit') > list.indexOf('settings-editor-core-config-yml'),
|
|
24
|
+
'the editor height rule it replaces is in the settings component, and a later one wins');
|
|
25
|
+
assert.match(assembleCss(''), /\/\* component: modal-fit \*\//);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('the dialog is capped to the screen, a column, with its own scroll that does not chain', () => {
|
|
29
|
+
assert.match(css, /#editor-modal \.uk-modal-dialog \{[^}]*display: flex;[^}]*flex-direction: column;[^}]*max-height: calc\(100dvh - 7rem\);/s);
|
|
30
|
+
assert.match(css, /#editor-modal \.uk-modal-body \{[^}]*overflow-y: auto;[^}]*overscroll-behavior: contain;/s);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('the title is pinned above and the actions below, with no strip for the form to scroll through', () => {
|
|
34
|
+
assert.match(css, /#editor-modal \.modal-head \{[^}]*position: sticky;[^}]*top: 0;/s);
|
|
35
|
+
assert.match(css, /\.modal-actions,\s*#editor-modal form > :last-child:has\(> button\[type="submit"\]\) \{[^}]*position: sticky;[^}]*bottom: 0;/s);
|
|
36
|
+
assert.match(css, /\.uk-modal-body:has\(\.modal-head\) \{ padding-top: 0; \}/);
|
|
37
|
+
assert.match(css, /\.uk-modal-body:has\(\.modal-actions\) \{ padding-bottom: 0; \}/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('editors take the height that is left of the screen, never a fixed share of it', () => {
|
|
41
|
+
assert.match(css, /\.ace-host:not\(\.ace-host-single\) \{\s*height: clamp\(12rem, calc\(100dvh - 19rem\), 46rem\) !important;/);
|
|
42
|
+
assert.match(css, /\.new-screen-card \.ace-host:not\(\.ace-host-single\) \{\s*height: clamp\(11rem, calc\(100dvh - 44rem\), 26rem\)/);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('New screen asks for the prompt before the Claude options', () => {
|
|
46
|
+
assert.match(css, /fieldset\[data-claude-only\] \{ order: 3; \}/);
|
|
47
|
+
assert.match(css, /fieldset:not\(\[data-claude-only\]\):last-of-type \{ order: 2; \}/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('the forms carry the two classes', () => {
|
|
51
|
+
assert.match(read('screens.js'), /<div class="modal-head">\s*<h3 class="uk-margin-small-bottom">New screen<\/h3>/);
|
|
52
|
+
assert.match(read('screens.js'), /<p class="modal-actions"><button class="uk-button uk-button-primary" type="submit">Start it<\/button><\/p>/);
|
|
53
|
+
assert.match(read('video-edit.js'), /const head = `<div class="modal-head"><h3[^>]*>Edit a video<\/h3>\$\{tabsHtml\(chosen, tab\)\}<\/div>`/);
|
|
54
|
+
assert.match(read('video-edit.js'), /<div class="modal-actions">\s*<button type="submit" class="uk-button uk-button-primary">/);
|
|
55
|
+
assert.match(read('audio-voice.js'), /<div class="modal-actions">/);
|
|
56
|
+
assert.match(read('audio-voice.js'), /<div class="modal-head"><h3[^>]*>\$\{video \? 'Voice over a video' : 'Generate a voice'\}<\/h3><\/div>/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('motion honours the system setting', () => {
|
|
60
|
+
assert.match(css, /@media \(prefers-reduced-motion: reduce\) \{[^}]*uk-accordion-content[^}]*transition-duration: 0\.01ms !important;/s);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('the dialog is named, and the page behind it is out of reach while it is open', () => {
|
|
64
|
+
const server = read('server.js');
|
|
65
|
+
const ui = read('public/ui.js');
|
|
66
|
+
assert.ok(!/id="editor-modal"[^>]*role="dialog"/.test(server), 'UIkit puts role="dialog" on the inner dialog, so a second one is not added outside it');
|
|
67
|
+
assert.match(ui, /function containEditorModal\(open\)/);
|
|
68
|
+
assert.match(ui, /el\.inert = true;/);
|
|
69
|
+
assert.match(ui, /el\.inert = el\.dataset\.wasInert === '1';/, 'what was inert before is left as it was');
|
|
70
|
+
assert.match(ui, /el\.id === 'confirm-modal'/, 'the confirm dialog that stacks above it stays reachable');
|
|
71
|
+
assert.match(ui, /dialog\.setAttribute\('aria-label', h\.textContent/, 'the inner .uk-modal-dialog is the one that is named');
|
|
72
|
+
assert.match(ui, /containEditorModal\(false\);/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('no inner scroll box inside the scrolling body, and the focus ring and checkbox edge are visible', () => {
|
|
76
|
+
assert.match(css, /#editor-modal \.video-edit-sections \{ max-height: none; overflow: visible; \}/);
|
|
77
|
+
assert.match(css, /\.modal-actions \.uk-button:focus-visible[\s\S]*outline: 2px solid var\(--brand-primary\)/);
|
|
78
|
+
assert.match(css, /#editor-modal \.uk-checkbox,\s*#editor-modal \.uk-radio \{ border-color: var\(--text-subtle\); \}/);
|
|
79
|
+
assert.match(css, /#editor-modal \.editor-card\.uk-margin-top \{ margin-top: 0; \}/, 'the pinned header sits flush at the top of the body');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('controls that sit beside their label are named, and a checkbox is a 24px target', () => {
|
|
83
|
+
assert.match(read('audio-voice.js'), /aria-label="\$\{esc\(NAMES\[name\] \|\| name\)\}"/);
|
|
84
|
+
assert.match(read('audio-voice.js'), /duration: 'Length of the sound', influence: 'How closely to follow the description', length: 'Length of the music'/);
|
|
85
|
+
assert.match(read('screens.js'), /name="resume" disabled aria-label="Claude conversation to resume"/);
|
|
86
|
+
assert.match(css, /#editor-modal \.uk-checkbox,\s*#editor-modal \.uk-radio \{ width: 1\.5rem; height: 1\.5rem; \}/);
|
|
87
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Every core module must load. A module the core no longer ships, still required somewhere,
|
|
3
|
+
// surfaces here instead of at the first start of the dashboard (issue #91: `./graphs` after the
|
|
4
|
+
// graph feature moved to its plugin).
|
|
5
|
+
const test = require('node:test');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const APP = path.join(__dirname, '..', '..');
|
|
11
|
+
const SKIP = new Set(['server.js']); // starts listening on require
|
|
12
|
+
|
|
13
|
+
function modules(dir) {
|
|
14
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith('.js') && !SKIP.has(f)).map((f) => path.join(dir, f));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test('every core module in the app root and actions/ loads', () => {
|
|
18
|
+
const failures = [];
|
|
19
|
+
for (const file of [...modules(APP), ...modules(path.join(APP, 'actions'))]) {
|
|
20
|
+
try { require(file); } catch (e) { failures.push(`${path.relative(APP, file)}: ${e.message.split('\n')[0]}`); }
|
|
21
|
+
}
|
|
22
|
+
assert.deepStrictEqual(failures, []);
|
|
23
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
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 { cleanSpoken, summaryFromHtml, createNarrator } = require('../../public/narration');
|
|
7
|
+
const { panelConfig } = require('../../speech');
|
|
8
|
+
|
|
9
|
+
const app = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
|
|
10
|
+
|
|
11
|
+
function fakes(pref) {
|
|
12
|
+
const store = new Map(pref ? Object.entries(pref) : []);
|
|
13
|
+
const spoken = [];
|
|
14
|
+
const synth = {
|
|
15
|
+
spoken, cancelled: 0,
|
|
16
|
+
speak: (u) => spoken.push(u), cancel() { this.cancelled += 1; },
|
|
17
|
+
getVoices: () => [{ name: 'Google US English' }],
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
store, synth, spoken,
|
|
21
|
+
opts: {
|
|
22
|
+
synth, Utterance: function (t) { this.text = t; },
|
|
23
|
+
getPref: (n) => (store.has(n) ? store.get(n) : null),
|
|
24
|
+
setPref: (n, v) => store.set(n, v),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test('a summary is the first sentences, without code blocks, tables, buttons or the plan card', () => {
|
|
30
|
+
const html = '<p>Started the build. It takes about <code>5 min</code>. See https://x.test/a for more.</p>'
|
|
31
|
+
+ '<pre>ddev start\nddev drush cr</pre><table><tr><td>a</td></tr></table>'
|
|
32
|
+
+ '<div class="plan-card"><p class="plan-summary">Run it</p><button>Run it</button></div>';
|
|
33
|
+
assert.strictEqual(summaryFromHtml(html), 'Started the build. It takes about 5 min.');
|
|
34
|
+
assert.ok(!/ddev|Run it|a link|https/.test(summaryFromHtml(html)));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('a summary reads entities, lists and headings as sentences, and is bounded', () => {
|
|
38
|
+
assert.strictEqual(summaryFromHtml('<h2>Done</h2><ul><li>One & two</li><li>Three</li></ul>'), 'Done. One & two.');
|
|
39
|
+
assert.ok(summaryFromHtml(`<p>${'word '.repeat(200)}</p>`).length <= 240);
|
|
40
|
+
assert.strictEqual(summaryFromHtml(''), '');
|
|
41
|
+
assert.strictEqual(summaryFromHtml(undefined), '');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('spoken text loses markdown marks and URLs', () => {
|
|
45
|
+
assert.strictEqual(cleanSpoken('**Done** — see https://a.test/x now'), 'Done — see a link now');
|
|
46
|
+
assert.ok(cleanSpoken('x '.repeat(400)).length <= 300);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('it starts as settings.yml says, and the saved choice wins', () => {
|
|
50
|
+
assert.strictEqual(createNarrator({ ...fakes().opts, defaultOn: true }).isOn(), true);
|
|
51
|
+
assert.strictEqual(createNarrator({ ...fakes().opts, defaultOn: false }).isOn(), false);
|
|
52
|
+
assert.strictEqual(createNarrator({ ...fakes({ 'ws-voice': '0' }).opts, defaultOn: true }).isOn(), false);
|
|
53
|
+
assert.strictEqual(createNarrator({ ...fakes({ 'ws-voice': '1' }).opts, defaultOn: false }).isOn(), true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('turning it off is remembered and stops what is being said', () => {
|
|
57
|
+
const f = fakes();
|
|
58
|
+
const n = createNarrator({ ...f.opts, defaultOn: true });
|
|
59
|
+
n.setOn(false);
|
|
60
|
+
assert.strictEqual(f.store.get('ws-voice'), '0');
|
|
61
|
+
assert.strictEqual(f.synth.cancelled, 1);
|
|
62
|
+
assert.strictEqual(n.isOn(), false);
|
|
63
|
+
n.setOn(true);
|
|
64
|
+
assert.strictEqual(f.store.get('ws-voice'), '1');
|
|
65
|
+
assert.strictEqual(n.isOn(), true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('with voice on, the assistant\'s own summary is spoken; without one, the first sentences are', () => {
|
|
69
|
+
const f = fakes();
|
|
70
|
+
const n = createNarrator({ ...f.opts, defaultOn: true });
|
|
71
|
+
assert.strictEqual(n.narrate({ spoken: 'The project is running.', html: '<p>Long reply here.</p>' }), true);
|
|
72
|
+
assert.strictEqual(f.spoken.at(-1).text, 'The project is running.');
|
|
73
|
+
n.narrate({ html: '<p>First one. Second one. Third one.</p>' });
|
|
74
|
+
assert.strictEqual(f.spoken.at(-1).text, 'First one. Second one.');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('with voice off, or nothing to say, nothing is spoken', () => {
|
|
78
|
+
const f = fakes({ 'ws-voice': '0' });
|
|
79
|
+
const off = createNarrator({ ...f.opts, defaultOn: true });
|
|
80
|
+
assert.strictEqual(off.narrate({ spoken: 'Hello there.' }), false);
|
|
81
|
+
assert.strictEqual(f.spoken.length, 0);
|
|
82
|
+
const on = createNarrator({ ...fakes().opts, defaultOn: true });
|
|
83
|
+
assert.strictEqual(on.narrate({ html: '<pre>only code</pre>' }), false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('a new utterance replaces the one being said, and carries the configured voice', () => {
|
|
87
|
+
const f = fakes();
|
|
88
|
+
const n = createNarrator({ ...f.opts, defaultOn: true, params: { rate: 1.2, pitch: 0.9, volume: 0.5, lang: 'ar-JO', voiceName: 'google us english' } });
|
|
89
|
+
n.speak('Hello there.');
|
|
90
|
+
const u = f.spoken[0];
|
|
91
|
+
assert.deepStrictEqual([u.rate, u.pitch, u.volume, u.lang], [1.2, 0.9, 0.5, 'ar-JO']);
|
|
92
|
+
assert.strictEqual(u.voice.name, 'Google US English');
|
|
93
|
+
assert.strictEqual(f.synth.cancelled, 1, 'the previous one is cancelled first');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('a browser without speech has the control hidden, not a switch that does nothing', () => {
|
|
97
|
+
const n = createNarrator({ getPref: () => null, setPref: () => {}, defaultOn: true });
|
|
98
|
+
assert.strictEqual(n.supported, false);
|
|
99
|
+
assert.strictEqual(n.isOn(), false);
|
|
100
|
+
assert.strictEqual(n.narrate({ spoken: 'Hi there.' }), false);
|
|
101
|
+
assert.ok(app('public/ui.js').includes('show: narrator.supported'));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('the page is given where the switch starts and the voice to use, even when the setting is off', () => {
|
|
105
|
+
const on = panelConfig({ assistant: { voice_output: true, speech_rate: 1.1 } });
|
|
106
|
+
assert.strictEqual(on.voiceOutput, true);
|
|
107
|
+
assert.strictEqual(on.narration.rate, 1.1);
|
|
108
|
+
const off = panelConfig({ assistant: { voice_output: false, speech_rate: 1.1 } });
|
|
109
|
+
assert.strictEqual(off.voiceOutput, false);
|
|
110
|
+
assert.strictEqual(off.textToSpeech, false);
|
|
111
|
+
assert.strictEqual(off.narration.rate, 1.1, 'turning the switch on still speaks in the configured voice');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('the checkbox is in the assistant options, and the helper loads before ui.js', () => {
|
|
115
|
+
const server = app('server.js');
|
|
116
|
+
const menu = server.slice(server.indexOf('function assistantMenuItemsHtml'), server.indexOf('const AI_MARK'));
|
|
117
|
+
assert.ok(menu.includes('class="uk-checkbox assistant-switch" type="checkbox" data-switch="${key}"'));
|
|
118
|
+
assert.ok(menu.includes("['voice', 'Voice summary'"));
|
|
119
|
+
assert.ok(server.indexOf("assetUrl('/narration.js')") > 0);
|
|
120
|
+
assert.ok(server.indexOf("assetUrl('/narration.js')") < server.indexOf("assetUrl('/ui.js')"));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('the flag reaches the server, and the summary line is asked for only when it is on', () => {
|
|
124
|
+
const ui = app('public/ui.js');
|
|
125
|
+
assert.ok(ui.includes('voice: narrator.isOn()'));
|
|
126
|
+
assert.ok(ui.includes('narrator.narrate({ spoken: d && d.spoken, html: response.html })'));
|
|
127
|
+
assert.ok(ui.includes('el.textToSpeech = false;'));
|
|
128
|
+
assert.ok(app('server.js').includes('{ voice: parsed.voice === true, avatar: parsed.avatar === true, avatarName: parsed.avatarName, avatarId: parsed.avatarId, page: parsed.page }'));
|
|
129
|
+
const assistant = app('assistant.js');
|
|
130
|
+
assert.ok(assistant.includes('...(voice ? ['), 'the SUMMARY instruction is conditional');
|
|
131
|
+
assert.ok(assistant.includes('/^\\s*SUMMARY:\\s*(.+)$/'), 'and the line never reaches the reply text');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('the voice tells whoever listens when it starts, each word, and when it stops', () => {
|
|
135
|
+
const f = fakes();
|
|
136
|
+
const heard = [];
|
|
137
|
+
const n = createNarrator({ ...f.opts, onSpeech: (d) => heard.push(d) });
|
|
138
|
+
assert.ok(n.speak('Two projects are running.'));
|
|
139
|
+
const u = f.spoken[0];
|
|
140
|
+
u.onstart();
|
|
141
|
+
u.onboundary({ name: 'word', charIndex: 4 });
|
|
142
|
+
u.onboundary({ name: 'sentence', charIndex: 0 });
|
|
143
|
+
u.onend();
|
|
144
|
+
assert.deepStrictEqual(heard.map((d) => d.word || d.type), ['end', 'start', 'projects', 'end'],
|
|
145
|
+
'speaking first cancels what was being said, then start, the word, and the end; a sentence boundary is not a word');
|
|
146
|
+
n.cancel();
|
|
147
|
+
assert.strictEqual(heard[heard.length - 1].type, 'end', 'a cancel is an end too, so a mouth never stays open');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('with no voice configured, the best free voice in the language is chosen, and a named one always wins', () => {
|
|
151
|
+
const { bestVoice } = require('../../public/narration');
|
|
152
|
+
const voices = [
|
|
153
|
+
{ name: 'eSpeak English', lang: 'en-US' },
|
|
154
|
+
{ name: 'Google US English', lang: 'en-US' },
|
|
155
|
+
{ name: 'Microsoft Aria Online (Natural) - English (United States)', lang: 'en-US' },
|
|
156
|
+
{ name: 'Microsoft Hamed Online (Natural) - Arabic', lang: 'ar-SA' },
|
|
157
|
+
];
|
|
158
|
+
assert.match(bestVoice(voices, 'en-US').name, /Aria Online \(Natural\)/);
|
|
159
|
+
assert.match(bestVoice(voices, 'ar-SA').name, /Hamed/);
|
|
160
|
+
assert.strictEqual(bestVoice([{ name: 'eSpeak English', lang: 'en-US' }], 'en-US'), null, 'nothing better than the default: leave the default');
|
|
161
|
+
const f = fakes();
|
|
162
|
+
f.synth.getVoices = () => voices;
|
|
163
|
+
createNarrator({ ...f.opts, params: { voiceName: 'Google US English', lang: 'en-US' } }).speak('Hello there.');
|
|
164
|
+
assert.strictEqual(f.spoken[0].voice.name, 'Google US English');
|
|
165
|
+
createNarrator({ ...f.opts, params: { lang: 'en-US' } }).speak('Hello there.');
|
|
166
|
+
assert.match(f.spoken[1].voice.name, /Natural/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('an ElevenLabs voice speaks through the server, and falls back to the browser when it cannot', () => {
|
|
170
|
+
const f = fakes();
|
|
171
|
+
const sent = [];
|
|
172
|
+
let stopped = 0;
|
|
173
|
+
let fallback = null;
|
|
174
|
+
const remote = (text, id, params, onSpeech, fb) => { sent.push([text, id]); fallback = fb; return () => { stopped += 1; }; };
|
|
175
|
+
const n = createNarrator({ ...f.opts, remote, params: { voiceName: 'elevenlabs:EXAVITQu4vr4xnSDxMaL' } });
|
|
176
|
+
assert.strictEqual(n.speak('Hello team.'), true);
|
|
177
|
+
assert.deepStrictEqual(sent, [['Hello team.', 'EXAVITQu4vr4xnSDxMaL']]);
|
|
178
|
+
assert.strictEqual(f.spoken.length, 0, 'the browser stays quiet while the server speaks');
|
|
179
|
+
n.cancel();
|
|
180
|
+
assert.strictEqual(stopped, 1, 'muting stops the audio');
|
|
181
|
+
fallback();
|
|
182
|
+
assert.strictEqual(f.spoken.length, 1, 'the same words in the browser voice when the server cannot');
|
|
183
|
+
const plain = createNarrator({ ...fakes().opts, remote, params: { voiceName: 'Google US English' } });
|
|
184
|
+
plain.speak('Hi.');
|
|
185
|
+
assert.strictEqual(sent.length, 1, 'a browser voice never goes to the server');
|
|
186
|
+
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Navigation editor and its endpoints.
|
|
3
|
+
*
|
|
4
|
+
* Two things here have already broken once and must not again. **A form's fields cannot share a name
|
|
5
|
+
* with the arrangement's**: the Add form posts the whole arrangement with it (hx-include), so an input
|
|
6
|
+
* called `group` merged with the sidebar's group order and the typed value arrived as the whole list
|
|
7
|
+
* joined by commas. And **the group an entry posts is rewritten on every drop**, because htmx collects
|
|
8
|
+
* a form's values when the request is TRIGGERED — rewriting them in htmx:before:request is already too
|
|
9
|
+
* late, and the save kept arriving with every entry still in the group it was rendered in.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const assert = require('node:assert');
|
|
13
|
+
const { test } = require('node:test');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'navigation-editor-'));
|
|
19
|
+
process.env.NAVIGATION_FILE = path.join(tmp, 'navigation.yml');
|
|
20
|
+
process.env.NAVIGATION_SEEN_FILE = path.join(tmp, 'navigation.seen.json');
|
|
21
|
+
process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
22
|
+
|
|
23
|
+
const read = (f) => fs.readFileSync(path.join(__dirname, '..', '..', f), 'utf8');
|
|
24
|
+
const nav = require('../../navigation');
|
|
25
|
+
const page = read('navigation-page.js');
|
|
26
|
+
const actions = read('actions/navigation.js');
|
|
27
|
+
const ui = read('public/ui.js');
|
|
28
|
+
const server = read('server.js');
|
|
29
|
+
const tabs = read('commands-page.js');
|
|
30
|
+
|
|
31
|
+
test('the Add form shares no field name with the arrangement it posts with it', () => {
|
|
32
|
+
const add = page.slice(page.indexOf('class="nav-add-form"'));
|
|
33
|
+
const names = [...add.matchAll(/name="([a-z_]+)"/g)].map((m) => m[1]);
|
|
34
|
+
assert.ok(names.length >= 5, 'the form still has its fields');
|
|
35
|
+
for (const n of names) {
|
|
36
|
+
assert.ok(!['group', 'item', 'scope', 'mtime'].includes(n),
|
|
37
|
+
`"${n}" is a field of the arrangement; hx-include would merge the two into one value`);
|
|
38
|
+
}
|
|
39
|
+
assert.ok(names.every((n) => n.startsWith('new_')), 'every field of the Add form is prefixed');
|
|
40
|
+
for (const n of names) assert.match(actions, new RegExp(`form\\.${n}\\b`), `the endpoint reads ${n}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('the group an entry posts is rewritten on every drop, not when the form is sent', () => {
|
|
44
|
+
assert.match(ui, /window\.navSyncGroups = function navSyncGroups/);
|
|
45
|
+
assert.match(ui, /for \(const ev of \['moved', 'added', 'removed', 'stop'\]\)/,
|
|
46
|
+
"UIkit's sortable events are the moment the values go stale");
|
|
47
|
+
assert.match(ui, /input\.value = `\$\{list\.dataset\.group\}:\$\{entry\.dataset\.id\}`/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('every list is a drop target of the same sortable group, so one drag reaches them all', () => {
|
|
51
|
+
const lists = [...page.matchAll(/class="nav-entries" data-group="([^"]+)"/g)].map((m) => m[1]);
|
|
52
|
+
assert.ok(lists.includes('hidden'), 'Hidden is a place you can drag into');
|
|
53
|
+
// The three regions are drawn from one template, so the source carries the interpolated name once.
|
|
54
|
+
assert.ok(lists.includes('topbar-${r}'), 'each top-bar region is a drop target');
|
|
55
|
+
assert.match(page, /rail\.REGIONS\.map\(\(r\) =>/, 'and all three are drawn, not one hardcoded');
|
|
56
|
+
const sortables = [...page.matchAll(/uk-sortable="group: nav-entries/g)];
|
|
57
|
+
assert.strictEqual(sortables.length, 3, "the regions, the sidebar groups and Hidden are all one sortable group — each drawn once, from a template");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('a reserved list is never saved back as a sidebar group', () => {
|
|
61
|
+
assert.match(actions, /\.filter\(\(g\) => !rail\.RESERVED\.has\(g\)\)/);
|
|
62
|
+
assert.match(actions, /const region = rail\.regionOf\(group\);/);
|
|
63
|
+
assert.match(actions, /if \(region\) \{ topbar\[region\]\.push\(id\); continue; \}/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('the group order is a set: a repeat would draw the same group twice', () => {
|
|
67
|
+
assert.match(actions, /\[\.\.\.new Set\(asArray\(form\.group\)/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('an added link is an https address or a path of this dashboard, and nothing else', () => {
|
|
71
|
+
assert.match(actions, /\/\^\(https\?:\\\/\\\/\|\\\/\)\[\^\\s"'<>\]\*\$\/i/,
|
|
72
|
+
'javascript: and data: never reach an href');
|
|
73
|
+
assert.match(actions, /An entry is a link or a command, not both/);
|
|
74
|
+
assert.match(actions, /No such command/, 'a use: must name a command of the catalog');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('every write carries the mtime and is refused with 409 when the file changed underneath', () => {
|
|
78
|
+
assert.match(actions, /if \(String\(form\.mtime \|\| ''\) !== String\(mtime\)\) \{/);
|
|
79
|
+
const refusals = [...actions.matchAll(/, 409\)/g)];
|
|
80
|
+
assert.ok(refusals.length >= 3, 'save, add and remove all refuse rather than overwrite');
|
|
81
|
+
assert.match(page, /name="mtime" value="\$\{mtime\}"/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('removing an entry asks first, the way every destructive action here does', () => {
|
|
85
|
+
assert.match(page, /class="uk-button uk-button-link nav-remove arm-step"/);
|
|
86
|
+
assert.match(page, /hx-trigger="confirmed-remove"/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('the navigation is a SETTING, and both the tabs it used to be land on it', () => {
|
|
90
|
+
// What the sidebar and the top bar show is configuration, not a command — so it is a Settings tab.
|
|
91
|
+
// It was Commands → Rail, then Commands → Navigation; neither bookmark is a dead link.
|
|
92
|
+
assert.match(server, /pathname === '\/commands\/rail' \|\| pathname === '\/commands\/navigation'[\s\S]{0,240}Location: `\/settings\/navigation/);
|
|
93
|
+
assert.match(server, /pathname === '\/settings\/navigation'/);
|
|
94
|
+
assert.match(server, /settingsPage\('navigation', readPageState\(req\), \{ scope, fragment: isFragment\(req\) \}\)/);
|
|
95
|
+
assert.match(server, /card\('\/settings\/navigation', 'tabler:layout-sidebar-right', 'Navigation',/,
|
|
96
|
+
'and it is one of the cards there, in The dashboard group rather than among the workspaces');
|
|
97
|
+
assert.ok(!tabs.includes('/commands/navigation'), 'and no longer one of the Commands tabs');
|
|
98
|
+
assert.match(page, /href="\/settings\/navigation\?scope=/, 'the scope tabs stay inside Settings');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('adding opens in the editor modal, and posts the arrangement with it', () => {
|
|
102
|
+
// The add card sat under a list of groups that can run past the fold, so the way to put something IN
|
|
103
|
+
// the navigation was the one thing you had to scroll to find.
|
|
104
|
+
assert.match(page, /hx-get="\/fragments\/nav-add\?scope=/);
|
|
105
|
+
assert.match(page, /hx-target="#editor-modal-body"/);
|
|
106
|
+
assert.match(server, /pathname === '\/fragments\/nav-add'/);
|
|
107
|
+
const form = page.slice(page.indexOf('function navAddFormHtml'));
|
|
108
|
+
assert.match(form, /hx-include="#nav-form"/, 'a drag that has not been saved is not lost by adding');
|
|
109
|
+
assert.match(form, /hx-target="#nav-editor" hx-swap="outerHTML"/, 'the answer is the editor behind it');
|
|
110
|
+
assert.match(form, /UIkit\.modal\('#editor-modal'\)\.hide\(\)/, 'which the modal stops covering');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('the Add form offers the two places by name, rather than asking for a group id', () => {
|
|
114
|
+
const form = page.slice(page.indexOf('function navAddFormHtml'));
|
|
115
|
+
assert.match(form, /optgroup label="The navigation topbar"/);
|
|
116
|
+
assert.match(form, /optgroup label="The navigation sidebar"/);
|
|
117
|
+
assert.match(form, /rail\.REGIONS\.map\(\(r\) => `<option value="topbar-\$\{r\}">/);
|
|
118
|
+
assert.match(form, /<option value="yours" selected>/, 'with a sensible default');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('the top bar renders only the regions that have something in them', () => {
|
|
122
|
+
assert.match(server, /const topbarHtml = \(region\) => \(topbar\[region\]\.length/,
|
|
123
|
+
'an empty region is three pieces of furniture for no one');
|
|
124
|
+
assert.match(server, /toolbar-nav toolbar-nav-\$\{region\}/);
|
|
125
|
+
assert.match(server, /\$\{topbarHtml\('start'\)\}/);
|
|
126
|
+
assert.match(server, /uk-navbar-center[\s\S]{0,80}topbarHtml\('center'\)/);
|
|
127
|
+
assert.match(server, /\$\{topbarHtml\('end'\)\}/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('two entries never share an id: a layout would move or hide both at once', () => {
|
|
131
|
+
assert.match(server, /id: 'open-screens'/,
|
|
132
|
+
"the Screens BUTTON is not the Go group's Screens link");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('a fresh install has the AI assistant at the start of the top bar too', () => {
|
|
136
|
+
assert.match(server, /\{ id: 'ai-assistant', topbar: 'start', show: 'icon', label: assistantName/,
|
|
137
|
+
'it applies to whatever page you are on, and it is the only way in when the panel is closed');
|
|
138
|
+
// Its menu is anchored below the control, because the control is PLACED now rather than fixed to the
|
|
139
|
+
// rail: centred on the left it opened across the wordmark with half of it off the top of the page.
|
|
140
|
+
assert.match(server, /uk-dropdown="mode: click; pos: bottom-right"/);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('a fresh install has the version at the start of the top bar and the screens count at the end', () => {
|
|
144
|
+
// Both are default PLACEMENTS in the code, so a machine with no core/config/navigation.yml — which is
|
|
145
|
+
// every fresh install — gets them, and arranging either in Settings → Navigation still wins.
|
|
146
|
+
assert.match(server, /id: 'version',\s*\n\s*topbar: 'start',/,
|
|
147
|
+
'the release is what the product IS: it belongs beside the wordmark');
|
|
148
|
+
assert.match(server, /id: 'open-screens',\s*\n\s*topbar: 'end',/,
|
|
149
|
+
'a live count of what is running is a status, not a page action');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('the screens status is on every page: what is running is a fact about the machine', () => {
|
|
153
|
+
const block = server.slice(server.indexOf('// Not gated on Jira'), server.indexOf("id: 'open-screens'"));
|
|
154
|
+
assert.ok(!block.includes("if (view === 'workspace'"),
|
|
155
|
+
'a count that appeared on /dev and vanished on /settings was telling you about the page');
|
|
156
|
+
assert.ok(/not gated on being on a[\s/]*workspace page/i.test(block), 'and it says why');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('the status button carries its own class rather than having one patched into its markup', () => {
|
|
160
|
+
assert.match(server, /action-btn actions-screens" type="button"/);
|
|
161
|
+
assert.ok(!/it\.html\.replace\('<\/button>'/.test(server),
|
|
162
|
+
'reaching into a button\'s markup to add a class is how this breaks');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('the button follows the count, the way a project row SCREENS button does', () => {
|
|
166
|
+
const css = read('public/css/components/settings-editor-core-config-yml.css');
|
|
167
|
+
assert.match(css, /\.toolbar-nav \.actions-screens:has\(\.uk-badge\)/,
|
|
168
|
+
'the count arrives on a poll, so the button follows the badge rather than a re-render');
|
|
169
|
+
assert.match(css, /\.actions-screens \.screens-count:empty \{ display: none; \}/,
|
|
170
|
+
'nothing running, nothing shouting');
|
|
171
|
+
assert.match(server, /n \? ` <span class="uk-badge">\$\{n\}<\/span>` : ''/,
|
|
172
|
+
'and the fragment says nothing at all when nothing is running');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("a plugin's group is given ids, so what a plugin adds can be arranged like anything else", () => {
|
|
176
|
+
assert.match(server, /id: g\.id \|\| rail\.slugId\(g\.label\)/);
|
|
177
|
+
assert.match(server, /return \{ id: rail\.slugId\(label\), label, html: it \};/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('Settings is grouped and shown as a grid, not 26 identical pills', () => {
|
|
181
|
+
// The hub's own settings, an editor that is not a file at all, and twenty-four workspaces were all
|
|
182
|
+
// weighted the same and wrapped onto three lines.
|
|
183
|
+
assert.match(server, /<h2 class="settings-group-head">The dashboard<\/h2>/);
|
|
184
|
+
assert.match(server, /<h2 class="settings-group-head">Workspace settings<\/h2>/);
|
|
185
|
+
// Every editor that is not a settings FILE has to be excluded here, or choosing it would fall
|
|
186
|
+
// through to the index. Matched loosely on purpose: the list grows (Command order joined it), and a
|
|
187
|
+
// test that pins the exact expression fails on the addition rather than on the behaviour.
|
|
188
|
+
assert.match(server, /const isIndex = !isScreens && !isNavigation && !isButtons &&[^;]*!current;/,
|
|
189
|
+
'with nothing chosen the page IS the index, rather than defaulting into one file');
|
|
190
|
+
assert.match(server, /class="settings-grid"/);
|
|
191
|
+
// Not `settings-card`: that is already the class of the settings FORM wrapper, and a grid rule on it
|
|
192
|
+
// squeezed that form's description into a 71px ribbon down the right of the page.
|
|
193
|
+
assert.match(server, /class="settings-tile\$\{active \? ' is-current' : ''\}"/);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('a workspace card carries that workspace own icon and name', () => {
|
|
197
|
+
assert.match(server, /const m = meta\[key\] \|\| \{\};/);
|
|
198
|
+
assert.match(server, /m\.icon \|\| 'tabler:folder',\s*\n?\s*m\.label \|\| key/);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('opening one gives it the width, with a line back rather than the whole list again', () => {
|
|
202
|
+
assert.match(server, /class="settings-back"/);
|
|
203
|
+
assert.match(server, /href="\/settings">\s*<span uk-icon="icon: arrow-left; ratio: \.8"><\/span> All settings<\/a>/);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('each entry says how much of itself is shown, and the control offers all three', () => {
|
|
207
|
+
const card = page.slice(page.indexOf('function entryCard'), page.indexOf('function navEditorHtml'));
|
|
208
|
+
assert.match(card, /<select class="nav-entry-show" name="show"/);
|
|
209
|
+
for (const how of ['both', 'icon', 'text']) {
|
|
210
|
+
assert.ok(card.includes(`:${how}"`), `${how} is one of the answers`);
|
|
211
|
+
}
|
|
212
|
+
assert.match(card, /aria-label="How much of \$\{esc\(label\)\} is shown"/, 'and the control has a name');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('the drawn state of that control is the layout answer, or the way the entry ships', () => {
|
|
216
|
+
assert.match(page, /const asked = own\.show\[id\];/);
|
|
217
|
+
assert.match(page, /return shipsAs\(id\) \|\| 'both';/);
|
|
218
|
+
assert.match(page, /const shipsAs = \(id\) => rail\.seenShips\(seenItem\(id\)\);/);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('both is not written down, except when it overrides something the dashboard ships', () => {
|
|
222
|
+
assert.match(actions, /if \(how === 'both' && ships\[id\]\) show\[id\] = 'both';/,
|
|
223
|
+
'without it there would be no way to put the assistant label back — leaving the key out lets its own default win again');
|
|
224
|
+
assert.match(actions, /function shippedShow\(scope\)/, 'and what ships is read from what the navigation drew');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('an entry shown one way is WRAPPED, never rewritten', () => {
|
|
228
|
+
assert.match(server, /const navItemHtml = \(it\) => \(it\.show === 'icon' \|\| it\.show === 'text'/,
|
|
229
|
+
"an entry's html is built by whatever added it; reaching into that string is how this breaks");
|
|
230
|
+
assert.match(server, /nav-show-\$\{it\.show\}/);
|
|
231
|
+
assert.match(server, /\$\{g\.items\.map\(\(it\) => railIcons\(navItemHtml\(it\)\)\)\.join\(''\)\}/, 'the sidebar draws it');
|
|
232
|
+
assert.match(server, /topbar\[region\]\.map\(\(it\) => navItemHtml\(it\)\)/, 'and so does the top bar');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('the assistant ships as its mark alone, and keeps its label in the markup', () => {
|
|
236
|
+
assert.match(server, /\{ id: 'ai-assistant', topbar: 'start', show: 'icon', label: assistantName/);
|
|
237
|
+
assert.match(server, /\$\{AI_MARK\}<span class="action-label">\$\{esc\(assistantName\)\}<\/span>/,
|
|
238
|
+
'a screen reader still reads the name; hiding it is presentation only');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('every place that names the assistant reads the same setting', () => {
|
|
242
|
+
// A heading, a button, a launcher and a full page, each with its own hardcoded 'AI Assistant',
|
|
243
|
+
// is four things to miss when somebody renames it — and the one that gets missed is the one that
|
|
244
|
+
// then contradicts the other three. They all go through assistantIdentityFor(context).
|
|
245
|
+
assert.match(server, /const assistantName = assistantIdentityFor\(context\)\.name;/,
|
|
246
|
+
'the navigation control');
|
|
247
|
+
assert.match(server, /<h3 class="uk-margin-remove">\$\{esc\(identity\.name\)\}<\/h3>/,
|
|
248
|
+
"the panel's heading");
|
|
249
|
+
assert.match(server, /title="Ask the \$\{esc\(assistantIdentityFor\(context\)\.name\)\}"/,
|
|
250
|
+
'the launcher, which is the way back in when the panel is closed');
|
|
251
|
+
assert.match(server, /<h1 class="uk-margin-remove">\$\{esc\(identity\.name\)\}<\/h1>/,
|
|
252
|
+
'the full page at /assistant');
|
|
253
|
+
assert.doesNotMatch(server, /\$\{scopeLabel\} AI Assistant/,
|
|
254
|
+
'and none of them prefixes the workspace: it is not the "Designs AI Assistant"');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('the panel and the composer are scoped to the page, not to the whole dashboard', () => {
|
|
258
|
+
assert.match(server, /data-settings="\$\{esc\(JSON\.stringify\(assistantSettings\(context\)\)\)\}"/,
|
|
259
|
+
'the placeholder is a per-workspace setting, so the panel has to be told which page it is on');
|
|
260
|
+
});
|