@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,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The theme system: what a theme is, what a bad name does, and the property that matters most —
|
|
3
|
+
* every token a component asks for is one some theme actually defines.
|
|
4
|
+
*
|
|
5
|
+
* That last one is not pedantry. An undefined var() drops the whole declaration silently, which is
|
|
6
|
+
* how thirty rules in the old stylesheet came to be dead without anybody noticing.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { test } = require('node:test');
|
|
10
|
+
const assert = require('node:assert');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const { listThemes, themeLabel, resolveTheme, assembleCss, cssVersion,
|
|
15
|
+
readThemeOverrides, themeOverridesCss, themeOverridesFile,
|
|
16
|
+
createTheme, isYamlOnlyTheme, themePreview, isMadeHere} = require('../../themes');
|
|
17
|
+
|
|
18
|
+
const PUBLIC = path.join(__dirname, '..', '..', 'public');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The tokens a style actually renders with, from whichever source it has.
|
|
22
|
+
*
|
|
23
|
+
* A style can ship as a directory with a `theme.css`, or exist only as `core/config/theme.<n>.yml`
|
|
24
|
+
* — which is what a style created from the interface is. A test that reads only the stylesheet
|
|
25
|
+
* would pass for the shipped four and say nothing about the ones people make.
|
|
26
|
+
*/
|
|
27
|
+
const styleTokens = (name) => {
|
|
28
|
+
const file = path.join(PUBLIC, 'themes', name, 'theme.css');
|
|
29
|
+
if (fs.existsSync(file)) {
|
|
30
|
+
const clean = fs.readFileSync(file, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
31
|
+
const grab = (selector) => {
|
|
32
|
+
const out = new Map();
|
|
33
|
+
const re = new RegExp(`${selector}\\s*\\{([^}]*)\\}`, 'g');
|
|
34
|
+
let m;
|
|
35
|
+
while ((m = re.exec(clean))) {
|
|
36
|
+
for (const d of m[1].matchAll(/(--[a-zA-Z0-9-]+)\s*:\s*([^;]+);/g)) out.set(d[1], d[2].trim());
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
return { root: grab(':root'), dark: grab('html\\.dark'), fromCss: true };
|
|
41
|
+
}
|
|
42
|
+
const { tokens, dark } = readThemeOverrides(name);
|
|
43
|
+
return { root: new Map(Object.entries(tokens)), dark: new Map(Object.entries(dark)), fromCss: false };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
test('the themes on disk are the themes offered', () => {
|
|
48
|
+
// Both kinds: a directory with a stylesheet, and a style that is only YAML.
|
|
49
|
+
const onDisk = fs.readdirSync(path.join(PUBLIC, 'themes'), { withFileTypes: true })
|
|
50
|
+
.filter((e) => e.isDirectory() && fs.existsSync(path.join(PUBLIC, 'themes', e.name, 'theme.css')))
|
|
51
|
+
.map((e) => e.name).sort();
|
|
52
|
+
for (const t of onDisk) assert.ok(listThemes().includes(t), `${t} is on disk but not offered`);
|
|
53
|
+
assert.ok(onDisk.includes('default'), 'there is always a default');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('a theme that does not exist falls back to default', () => {
|
|
57
|
+
assert.equal(resolveTheme('no-such-theme'), 'default');
|
|
58
|
+
assert.equal(resolveTheme(''), 'default');
|
|
59
|
+
assert.equal(resolveTheme(undefined), 'default');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('a theme names itself for the settings form', () => {
|
|
63
|
+
for (const t of listThemes()) {
|
|
64
|
+
assert.ok(themeLabel(t).length, `${t} should have a label`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('the sheet is assembled base first and theme last', () => {
|
|
69
|
+
const css = assembleCss('default');
|
|
70
|
+
assert.ok(css.includes('/* base */'));
|
|
71
|
+
assert.ok(css.includes('/* theme: default */'));
|
|
72
|
+
assert.ok(css.indexOf('/* base */') < css.indexOf('/* theme: default */'),
|
|
73
|
+
'the theme has to come last or it cannot overrule a component');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('every component is included', () => {
|
|
77
|
+
const files = fs.readdirSync(path.join(PUBLIC, 'css', 'components')).filter((f) => f.endsWith('.css'));
|
|
78
|
+
const css = assembleCss('default');
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
assert.ok(css.includes(`/* component: ${f.replace(/\.css$/, '')} */`), `${f} is missing from the sheet`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Set on an element by a script at runtime, never by a theme: the terminal's own palette, which xterm needs
|
|
85
|
+
// as values too, and the swatches in its menu.
|
|
86
|
+
// Layout variables a component declares for itself (the project card's sizes, set on its own list), not colours a theme owns.
|
|
87
|
+
const CARD_VARS = ['--pcard-min', '--pcard-gap', '--pcard-pad', '--pcard-radius', '--pcard-tool', '--pcard-chip',
|
|
88
|
+
'--pcard-title-lines', '--pcard-cols', '--pcard-stripe'];
|
|
89
|
+
// sortable.js sets where a dragged card's open slot starts, past its column's gutter.
|
|
90
|
+
const RUNTIME_TOKENS = ['--flash-ms', '--term-bg', '--term-fg', '--term-bar', '--term-bar-ink', '--sw-bg', '--sw-fg', '--sw-ink',
|
|
91
|
+
'--sort-inset-left', ...CARD_VARS];
|
|
92
|
+
|
|
93
|
+
test('EVERY token a component uses is defined by EVERY theme', () => {
|
|
94
|
+
const used = new Set();
|
|
95
|
+
const read = (f) => fs.readFileSync(f, 'utf8');
|
|
96
|
+
for (const f of [path.join(PUBLIC, 'css', 'base.css'),
|
|
97
|
+
...fs.readdirSync(path.join(PUBLIC, 'css', 'components')).map((n) => path.join(PUBLIC, 'css', 'components', n))]) {
|
|
98
|
+
for (const m of read(f).matchAll(/var\((--[a-z0-9-]+)/g)) used.add(m[1]);
|
|
99
|
+
}
|
|
100
|
+
for (const theme of listThemes()) {
|
|
101
|
+
const defined = new Set(styleTokens(theme).root.keys());
|
|
102
|
+
RUNTIME_TOKENS.forEach((t) => defined.add(t));
|
|
103
|
+
const missing = [...used].filter((t) => !defined.has(t));
|
|
104
|
+
assert.deepEqual(missing, [], `${theme} does not define: ${missing.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// The debt this split inherited — now paid, and held at zero.
|
|
109
|
+
//
|
|
110
|
+
// A colour written into a component cannot be themed: it looks right in exactly one palette and
|
|
111
|
+
// silently wrong in every other. The single stylesheet carried 117 of them when it was split,
|
|
112
|
+
// almost all in `html.dark` overrides that existed only because the light rule underneath named a
|
|
113
|
+
// colour instead of a token. Every one is now a token, so the map is empty and the test is a
|
|
114
|
+
// simple rule rather than a ratchet: a component may not name a colour at all.
|
|
115
|
+
//
|
|
116
|
+
// A file may still be listed here if a genuine exception ever appears. Nothing qualifies today:
|
|
117
|
+
// the surfaces that legitimately ignore the theme (a terminal, a video's letterbox, rendered
|
|
118
|
+
// paper) are tokens too — --code-bg, --media-bg, --paper — so a theme can still overrule them.
|
|
119
|
+
const COLOUR_LITERAL_BASELINE = {};
|
|
120
|
+
|
|
121
|
+
test('no component gains a new hardcoded colour', () => {
|
|
122
|
+
const files = fs.readdirSync(path.join(PUBLIC, 'css', 'components')).map((n) => path.join(PUBLIC, 'css', 'components', n));
|
|
123
|
+
files.push(path.join(PUBLIC, 'css', 'base.css'));
|
|
124
|
+
const worse = [];
|
|
125
|
+
for (const f of files) {
|
|
126
|
+
const name = path.basename(f);
|
|
127
|
+
const hits = (fs.readFileSync(f, 'utf8').match(/#[0-9a-fA-F]{3,8}\b/g) || []).length;
|
|
128
|
+
const allowed = COLOUR_LITERAL_BASELINE[name] || 0;
|
|
129
|
+
if (hits > allowed) worse.push(`${name}: ${hits} colours, baseline ${allowed}`);
|
|
130
|
+
}
|
|
131
|
+
assert.deepEqual(worse, [], 'use a token — a hardcoded colour cannot be themed');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('the baseline is honest: it lists no file that is already clean', () => {
|
|
135
|
+
for (const name of Object.keys(COLOUR_LITERAL_BASELINE)) {
|
|
136
|
+
const f = name === 'base.css'
|
|
137
|
+
? path.join(PUBLIC, 'css', 'base.css')
|
|
138
|
+
: path.join(PUBLIC, 'css', 'components', name);
|
|
139
|
+
const hits = (fs.readFileSync(f, 'utf8').match(/#[0-9a-fA-F]{3,8}\b/g) || []).length;
|
|
140
|
+
assert.ok(hits > 0, `${name} is clean now — take it out of the baseline`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// A theme is a palette. It says what --surface IS; it never says what has one.
|
|
145
|
+
//
|
|
146
|
+
// The default theme used to end with sixty `html.dark .something { … }` rules, which is how a
|
|
147
|
+
// palette file quietly becomes a second stylesheet: those rules existed only under ONE theme, so
|
|
148
|
+
// switching to Ember or Slate lost the toggle's styling, the modal's dark surface and the editor's
|
|
149
|
+
// code face. Selecting an element from inside a palette is the mistake — this catches it.
|
|
150
|
+
// The assistant's input lives in deep-chat's shadow root, where the sheet does not reach, so ui.js carries its CSS
|
|
151
|
+
// as strings. A custom property does cross into a shadow root, so a token there follows the theme; a literal does
|
|
152
|
+
// not — which is how the send button came to be UIKit blue, resting on a pale grey that stayed light after dark.
|
|
153
|
+
test('the chat panel\'s own CSS paints with tokens, not colours', () => {
|
|
154
|
+
const ui = fs.readFileSync(path.join(PUBLIC, 'ui.js'), 'utf8');
|
|
155
|
+
for (const name of ['auxiliaryStyle', 'shadowCss']) {
|
|
156
|
+
const at = ui.indexOf(name === 'auxiliaryStyle' ? 'el.auxiliaryStyle = `' : 'const shadowCss = `');
|
|
157
|
+
assert.ok(at > 0, `no ${name} in ui.js`);
|
|
158
|
+
const css = ui.slice(at, ui.indexOf('`;', at)).replace(/\/\*[\s\S]*?\*\//g, '');
|
|
159
|
+
const literals = css.match(/#[0-9a-fA-F]{3,8}\b|rgba?\(\s*\d/g) || [];
|
|
160
|
+
assert.deepEqual(literals, [], `${name} paints with colour literals`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('a theme declares values and selects nothing', () => {
|
|
165
|
+
for (const theme of listThemes()) {
|
|
166
|
+
// A style with no stylesheet cannot select anything, which is the point of it.
|
|
167
|
+
if (!fs.existsSync(path.join(PUBLIC, 'themes', theme, 'theme.css'))) continue;
|
|
168
|
+
const css = fs.readFileSync(path.join(PUBLIC, 'themes', theme, 'theme.css'), 'utf8')
|
|
169
|
+
.replace(/\/\*[\s\S]*?\*\//g, ''); // comments are prose, not rules
|
|
170
|
+
const selectors = [...css.matchAll(/(^|})\s*([^{}]+)\{/g)].map((m) => m[2].trim());
|
|
171
|
+
const offenders = selectors.filter((s) => !/^(:root|html\.dark)$/.test(s));
|
|
172
|
+
assert.deepEqual(offenders, [], `${theme} styles elements: ${offenders.join(' | ')}`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Every theme has to answer every question, or a component asking one of them gets nothing back
|
|
177
|
+
// and drops the declaration in silence. Same names everywhere; only the values differ.
|
|
178
|
+
test('every theme defines exactly the same tokens', () => {
|
|
179
|
+
// Names, not the Map itself — a Map spreads into [key, value] pairs and every `has` would miss.
|
|
180
|
+
const tokensOf = (t) => new Set(styleTokens(t).root.keys());
|
|
181
|
+
const themes = listThemes();
|
|
182
|
+
const reference = tokensOf('default');
|
|
183
|
+
for (const theme of themes.filter((t) => t !== 'default')) {
|
|
184
|
+
const mine = tokensOf(theme);
|
|
185
|
+
assert.deepEqual([...reference].filter((t) => !mine.has(t)), [], `${theme} is missing tokens`);
|
|
186
|
+
assert.deepEqual([...mine].filter((t) => !reference.has(t)), [], `${theme} invents tokens the default does not have`);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Both halves, not one. A token that only the light half sets keeps its light value after dark,
|
|
191
|
+
// which is the failure that leaves navy text on a navy surface.
|
|
192
|
+
test('the dark half restates every token that has to change', () => {
|
|
193
|
+
const css = fs.readFileSync(path.join(PUBLIC, 'themes', 'default', 'theme.css'), 'utf8');
|
|
194
|
+
const dark = css.slice(css.indexOf('html.dark'));
|
|
195
|
+
for (const token of ['--surface', '--surface-card', '--text-default', '--line', '--link', '--chip-bg']) {
|
|
196
|
+
assert.ok(new RegExp(`^\\s*${token}:`, 'm').test(dark), `the dark half never sets ${token}`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('the version changes when a file does, so the browser refetches', () => {
|
|
201
|
+
const before = cssVersion('default');
|
|
202
|
+
const f = path.join(PUBLIC, 'css', 'base.css');
|
|
203
|
+
const original = fs.statSync(f).mtime;
|
|
204
|
+
fs.utimesSync(f, new Date(), new Date(Date.now() + 5000));
|
|
205
|
+
assert.notEqual(cssVersion('default'), before);
|
|
206
|
+
fs.utimesSync(f, original, original);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
/* ---------- type roles ---------- */
|
|
210
|
+
|
|
211
|
+
// The role tokens are the point of the type layer: one token per role holding the weight, the
|
|
212
|
+
// size, the line-height and the family together, so a component cannot set three of the four.
|
|
213
|
+
// A role that is only a size would let the drift back in.
|
|
214
|
+
test('every theme states each heading as a complete role, not a bare size', () => {
|
|
215
|
+
const roles = ['--type-h1', '--type-h2', '--type-h3', '--type-h4', '--type-h5', '--type-h6', '--type-body'];
|
|
216
|
+
for (const t of listThemes()) {
|
|
217
|
+
const tokens = styleTokens(t).root;
|
|
218
|
+
for (const role of roles) {
|
|
219
|
+
const value = tokens.get(role);
|
|
220
|
+
assert.ok(value, `${t} does not define ${role}`);
|
|
221
|
+
assert.match(value, /var\(--weight-/, `${t} ${role} has no weight`);
|
|
222
|
+
assert.match(value, /var\(--size-[a-z0-9-]+\)\//, `${t} ${role} has no size followed by a line-height`);
|
|
223
|
+
assert.match(value, /var\(--font-/, `${t} ${role} has no family`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// A heading must not have to remember to bring its own tracking, so the token exists in every
|
|
229
|
+
// theme even where it is `normal`.
|
|
230
|
+
test('every theme states tracking for every heading role', () => {
|
|
231
|
+
for (const t of listThemes()) {
|
|
232
|
+
const tokens = styleTokens(t).root;
|
|
233
|
+
for (const n of [1, 2, 3, 4, 5, 6]) {
|
|
234
|
+
assert.ok(tokens.has(`--tracking-h${n}`), `${t} does not define --tracking-h${n}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Press is the Webship palette on a different type scale. Its own file says the colours are
|
|
240
|
+
// identical to default's, and a claim in a comment is worth what a test makes it worth: a colour
|
|
241
|
+
// that drifts apart here is a second palette nobody decided to have.
|
|
242
|
+
test('press is default’s palette — only the type differs', () => {
|
|
243
|
+
const read = (t) => {
|
|
244
|
+
const css = fs.readFileSync(path.join(PUBLIC, 'themes', t, 'theme.css'), 'utf8');
|
|
245
|
+
const out = new Map();
|
|
246
|
+
for (const m of css.matchAll(/(--[a-z0-9-]+):\s*([^;]+);/g)) out.set(m[1], m[2].trim());
|
|
247
|
+
return out;
|
|
248
|
+
};
|
|
249
|
+
const a = read('default');
|
|
250
|
+
const b = read('press');
|
|
251
|
+
const TYPE = /^--(size|leading|tracking|type|weight|font)-/;
|
|
252
|
+
const differ = [];
|
|
253
|
+
for (const [k, v] of a) {
|
|
254
|
+
if (TYPE.test(k)) continue;
|
|
255
|
+
if (b.has(k) && b.get(k) !== v) differ.push(k);
|
|
256
|
+
}
|
|
257
|
+
assert.deepEqual(differ, [], 'these colour tokens differ between default and press');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/* ---------- tokens as YAML ---------- */
|
|
261
|
+
|
|
262
|
+
// A theme file declares custom properties and selects nothing. That is what makes every value in
|
|
263
|
+
// it configuration — and configuration belongs beside settings.yml, not inside a stylesheet.
|
|
264
|
+
test('a theme file only ever declares tokens', () => {
|
|
265
|
+
for (const t of listThemes()) {
|
|
266
|
+
const file = path.join(PUBLIC, 'themes', t, 'theme.css');
|
|
267
|
+
if (!fs.existsSync(file)) continue; // a YAML-only style has nothing to paint with
|
|
268
|
+
const css = fs.readFileSync(file, 'utf8');
|
|
269
|
+
const stripped = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
270
|
+
const declarations = stripped.match(/^\s+[a-zA-Z-]+\s*:/gm) || [];
|
|
271
|
+
const painting = declarations.filter((d) => !d.trim().startsWith('--'));
|
|
272
|
+
assert.deepEqual(painting, [], `${t} paints something; a theme is a palette`);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// The override layer: names without the leading dashes, and anything that could close the block
|
|
277
|
+
// and start a rule of its own is dropped rather than written into the sheet.
|
|
278
|
+
test('YAML tokens are normalised, and nothing that is not a token gets through', () => {
|
|
279
|
+
const css = themeOverridesCss('press');
|
|
280
|
+
if (css) {
|
|
281
|
+
assert.match(css, /^:root \{/m, 'the tokens are emitted as a :root block');
|
|
282
|
+
assert.doesNotMatch(css.replace(/^:root \{|^html\.dark \{|^\}/gm, ''), /[{}]/,
|
|
283
|
+
'a value must not be able to close the block');
|
|
284
|
+
}
|
|
285
|
+
// The reader itself, on values it should refuse.
|
|
286
|
+
const { tokens } = readThemeOverrides('press');
|
|
287
|
+
for (const k of Object.keys(tokens)) {
|
|
288
|
+
assert.match(k, /^--[a-zA-Z][a-zA-Z0-9-]*$/, `${k} is not a token name`);
|
|
289
|
+
assert.doesNotMatch(tokens[k], /[{};]/, `${k} carries something that is not a value`);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// The YAML wins, or it is not an override.
|
|
294
|
+
test('the override block is emitted after the theme', () => {
|
|
295
|
+
const css = assembleCss('press');
|
|
296
|
+
if (css.includes('/* theme overrides:')) {
|
|
297
|
+
assert.ok(css.indexOf('/* theme: press */') < css.indexOf('/* theme overrides:'),
|
|
298
|
+
'a token set in YAML has to come after the same token in the theme file');
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Editing the YAML has to reach the browser: /style.css is cached by this key.
|
|
303
|
+
test('the cache key follows the YAML, not just the CSS', () => {
|
|
304
|
+
const f = themeOverridesFile('press');
|
|
305
|
+
if (!fs.existsSync(f)) return;
|
|
306
|
+
const before = cssVersion('press');
|
|
307
|
+
const original = fs.statSync(f).mtime;
|
|
308
|
+
fs.utimesSync(f, new Date(), new Date(Date.now() + 5000));
|
|
309
|
+
assert.notEqual(cssVersion('press'), before, 'editing the tokens would serve yesterday’s sheet');
|
|
310
|
+
fs.utimesSync(f, original, original);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Two sources for one value is a drift waiting to happen. The theme file is what you get with no
|
|
314
|
+
// YAML at all, so the two have to agree on anything the YAML sets.
|
|
315
|
+
test('every theme’s YAML agrees with its CSS, so deleting the YAML changes nothing', () => {
|
|
316
|
+
// Read the two halves SEPARATELY. A flat map of the whole file would let html.dark's values
|
|
317
|
+
// overwrite :root's, and the comparison would then hold the light tokens against the dark ones —
|
|
318
|
+
// which is how this test failed the first time it was run.
|
|
319
|
+
const halves = (css) => {
|
|
320
|
+
const clean = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
321
|
+
const grab = (selector) => {
|
|
322
|
+
const out = new Map();
|
|
323
|
+
const re = new RegExp(`${selector}\\s*\\{([^}]*)\\}`, 'g');
|
|
324
|
+
let m;
|
|
325
|
+
while ((m = re.exec(clean))) {
|
|
326
|
+
for (const d of m[1].matchAll(/(--[a-zA-Z0-9-]+)\s*:\s*([^;]+);/g)) out.set(d[1], d[2].trim());
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
};
|
|
330
|
+
return { root: grab(':root'), dark: grab('html\\.dark') };
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
for (const t of listThemes()) {
|
|
334
|
+
const { tokens, dark } = readThemeOverrides(t);
|
|
335
|
+
if (!Object.keys(tokens).length && !Object.keys(dark).length) continue;
|
|
336
|
+
const file = path.join(PUBLIC, 'themes', t, 'theme.css');
|
|
337
|
+
if (!fs.existsSync(file)) continue; // nothing to disagree with
|
|
338
|
+
const css = halves(fs.readFileSync(file, 'utf8'));
|
|
339
|
+
const differ = [];
|
|
340
|
+
const compare = (yamlBlock, cssBlock, half) => {
|
|
341
|
+
for (const [k, v] of Object.entries(yamlBlock)) {
|
|
342
|
+
if (cssBlock.has(k) && cssBlock.get(k) !== v) differ.push(`${half} ${k}: ${cssBlock.get(k)} vs ${v}`);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
compare(tokens, css.root, 'light');
|
|
346
|
+
compare(dark, css.dark, 'dark');
|
|
347
|
+
assert.deepEqual(differ, [], `${t}: the theme file and its YAML disagree`);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
/* ---------- styles made from the interface ---------- */
|
|
352
|
+
|
|
353
|
+
// A style used to be something only a release could add: a directory and a stylesheet inside the
|
|
354
|
+
// app. With every token in YAML there is nothing left for a stylesheet to say, so one can be made
|
|
355
|
+
// from the Styles page — and it has to be a complete style, not a fragment that leans on another.
|
|
356
|
+
test('a style created from an existing one is complete on its own', () => {
|
|
357
|
+
const name = `test-made-${process.pid}`;
|
|
358
|
+
const file = themeOverridesFile(name);
|
|
359
|
+
try {
|
|
360
|
+
const made = createTheme({ name, label: 'Test Made', from: 'default' });
|
|
361
|
+
assert.ok(made.ok, made.error);
|
|
362
|
+
assert.equal(made.from, 'default');
|
|
363
|
+
|
|
364
|
+
// Every token the source renders with, copied — not referenced.
|
|
365
|
+
const source = new Set(styleTokens('default').root.keys());
|
|
366
|
+
const mine = new Set(Object.keys(readThemeOverrides(name).tokens));
|
|
367
|
+
assert.deepEqual([...source].filter((t) => !mine.has(t)), [], 'the copy is missing tokens');
|
|
368
|
+
|
|
369
|
+
assert.ok(listThemes().includes(name), 'it is not offered');
|
|
370
|
+
assert.ok(isYamlOnlyTheme(name), 'it should have no stylesheet');
|
|
371
|
+
assert.equal(themeLabel(name), 'Test Made', 'it names itself in its own file');
|
|
372
|
+
|
|
373
|
+
// And it renders: no theme.css block, but every token present.
|
|
374
|
+
const css = assembleCss(name);
|
|
375
|
+
assert.ok(!css.includes(`/* theme: ${name} */`), 'there is no stylesheet to include');
|
|
376
|
+
assert.match(css, /--brand-primary:/, 'its tokens are in the sheet');
|
|
377
|
+
|
|
378
|
+
// Editing the style it came from must not change it later.
|
|
379
|
+
assert.notStrictEqual(readThemeOverrides(name).tokens['--brand-primary'], undefined);
|
|
380
|
+
} finally {
|
|
381
|
+
try { fs.unlinkSync(file); } catch (_) { /* already gone */ }
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('a style will not be created with a name that is not a name, or one already taken', () => {
|
|
386
|
+
for (const bad of ['', 'Has Caps', '9leading', 'has space', '../escape', 'dot.dot']) {
|
|
387
|
+
assert.equal(createTheme({ name: bad, from: 'default' }).ok, false, `${bad} should be refused`);
|
|
388
|
+
}
|
|
389
|
+
assert.equal(createTheme({ name: 'default', from: 'default' }).ok, false, 'default is taken');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// The page shows a style before you wear it, which only works if the preview reads real values.
|
|
393
|
+
test('a style previews with its own colours and its own type', () => {
|
|
394
|
+
for (const t of listThemes()) {
|
|
395
|
+
const p = themePreview(t);
|
|
396
|
+
assert.ok(p, `${t} has no preview`);
|
|
397
|
+
assert.match(p.brand, /^(#|rgb|oklch|hsl)/, `${t} brand is not a colour: ${p.brand}`);
|
|
398
|
+
assert.ok(p.sizeBody, `${t} has no body size to show`);
|
|
399
|
+
assert.ok(p.fontPrimary, `${t} has no family to set the card in`);
|
|
400
|
+
// An alias has to be resolved or the swatch paints nothing.
|
|
401
|
+
assert.doesNotMatch(p.brand, /^var\(/, `${t} brand is an unresolved alias`);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// A colour that is not a colour paints nothing and says nothing: the browser drops the declaration
|
|
406
|
+
// and the element keeps whatever it inherited, so the page looks almost right. `#2b3cf` — five hex
|
|
407
|
+
// digits — got into a style this way and no test noticed until the value was read by eye.
|
|
408
|
+
test('every colour token in every style is a colour a browser will accept', () => {
|
|
409
|
+
const COLOUR = /^(#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgba?\(|hsla?\(|oklch\(|oklab\(|color\(|var\(|transparent$|currentColor$|inherit$|none$)/;
|
|
410
|
+
const LOOKS_LIKE_COLOUR = /^(#|rgb|hsl|oklch|oklab)/;
|
|
411
|
+
const bad = [];
|
|
412
|
+
for (const t of listThemes()) {
|
|
413
|
+
const { root, dark } = styleTokens(t);
|
|
414
|
+
for (const [half, map] of [['light', root], ['dark', dark]]) {
|
|
415
|
+
for (const [k, v] of map) {
|
|
416
|
+
if (!LOOKS_LIKE_COLOUR.test(v)) continue; // a size, a shadow, a font stack
|
|
417
|
+
if (!COLOUR.test(v)) bad.push(`${t} ${half} ${k}: ${v}`);
|
|
418
|
+
// A hex has to be 3, 4, 6 or 8 digits. Five is the one that slips through by eye.
|
|
419
|
+
const hex = /^#([0-9a-fA-F]+)$/.exec(v);
|
|
420
|
+
if (hex && ![3, 4, 6, 8].includes(hex[1].length)) bad.push(`${t} ${half} ${k}: ${v} is ${hex[1].length} hex digits`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
assert.deepEqual(bad, [], 'these are not colours');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test('a style that ships with the workspace is not credited to whoever opened the page', () => {
|
|
428
|
+
// The card used to read `isYamlOnlyTheme` and label the answer "made here". Five styles that ship
|
|
429
|
+
// in the repository — Dispatch, UPSUN, GitHub, GitLab and Drupal — are YAML and nothing else,
|
|
430
|
+
// because a style is a set of tokens and a stylesheet would be a second place to keep them. All
|
|
431
|
+
// nine were therefore split between two labels on a machine that had created none of them.
|
|
432
|
+
for (const name of listThemes()) {
|
|
433
|
+
assert.equal(isMadeHere(name), false,
|
|
434
|
+
`${name} ships with the workspace but claims to have been made here`);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('a style made here says so, and the marker is on the file', () => {
|
|
439
|
+
const fs = require('fs');
|
|
440
|
+
const name = `unittest${process.pid}`;
|
|
441
|
+
const made = createTheme({ name, label: 'Unit Test', from: 'slate' });
|
|
442
|
+
try {
|
|
443
|
+
assert.ok(made.ok, made.error);
|
|
444
|
+
assert.equal(isMadeHere(name), true);
|
|
445
|
+
assert.equal(themePreview(name).madeHere, true);
|
|
446
|
+
// On the file, not inferred from its shape — which is the whole point.
|
|
447
|
+
assert.match(fs.readFileSync(made.file, 'utf8'), /^made_here: true$/m);
|
|
448
|
+
// And the marker must not have cost it any tokens.
|
|
449
|
+
assert.equal(themePreview(name).tokens, themePreview('slate').tokens);
|
|
450
|
+
} finally {
|
|
451
|
+
try { fs.unlinkSync(made.file); } catch (_) { /* nothing to clean */ }
|
|
452
|
+
}
|
|
453
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
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 read = (...p) => fs.readFileSync(path.join(root, ...p), 'utf8');
|
|
8
|
+
const server = read('server.js');
|
|
9
|
+
const ui = read('public', 'ui.js');
|
|
10
|
+
const css = read('public', 'css', 'components', 'project-cards.css');
|
|
11
|
+
|
|
12
|
+
test('words or icons on the tool buttons is a style option: every style states it, and icons is what ships', () => {
|
|
13
|
+
const themes = fs.readdirSync(path.join(root, 'public', 'themes'));
|
|
14
|
+
assert.ok(themes.length >= 4);
|
|
15
|
+
for (const t of themes) assert.match(read('public', 'themes', t, 'theme.css'), /--pcard-tool-label: 0rem;/, `${t} theme.css`);
|
|
16
|
+
const config = path.join(root, '..', 'config');
|
|
17
|
+
const yml = fs.readdirSync(config).filter((f) => /^theme\..+\.yml$/.test(f));
|
|
18
|
+
assert.ok(yml.length >= 4);
|
|
19
|
+
for (const f of yml) assert.match(fs.readFileSync(path.join(config, f), 'utf8'), /^\s*pcard-tool-label: 0rem$/m, f);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('the card sizes its tool buttons from that token, so a style can choose words with one value', () => {
|
|
23
|
+
assert.match(css, /font-size: var\(--pcard-tool-label, 0rem\);/);
|
|
24
|
+
assert.match(css, /padding: 0 calc\(var\(--pcard-tool-label, 0rem\) \* 0\.67\);/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('the browser can choose for itself, and the choice is applied before first paint', () => {
|
|
28
|
+
assert.match(css, /html\[data-tool-labels="words"\] #webship-workspace-projects \{ --pcard-tool-label: 0\.75rem; \}/);
|
|
29
|
+
assert.match(css, /html\[data-tool-labels="icons"\] #webship-workspace-projects \{ --pcard-tool-label: 0rem; \}/);
|
|
30
|
+
assert.match(server, /wsPref\('ws-tool-labels',null\)/);
|
|
31
|
+
assert.match(server, /toolLabelsSwitcherHtml\(\)/);
|
|
32
|
+
assert.match(ui, /wsPrefSet\('ws-tool-labels', mode\)/);
|
|
33
|
+
assert.match(ui, /--pcard-tool-label/, 'with no choice the switch shows what the style says');
|
|
34
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Screens icon in the top bar, and the terminals icon beside it.
|
|
3
|
+
*
|
|
4
|
+
* UIKit's `tv` is a 1px hairline on a 20-unit grid. The top bar renders it at 16px — a 0.8 scale — so
|
|
5
|
+
* the stroke lands between pixels and comes out soft, and differently on different displays: the
|
|
6
|
+
* "sometimes it is not clear" report. Its colour was never the problem; measured, it takes the
|
|
7
|
+
* button's own at 4.98:1 in light and 6.30:1 in dark.
|
|
8
|
+
*
|
|
9
|
+
* Tabler is the set every other icon in this dashboard uses, its stroke is set in CSS (1.75) rather
|
|
10
|
+
* than baked into the shape, and the Screens PAGE already heads itself with this same icon — so the
|
|
11
|
+
* same thing now looks the same in both places.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { test } = require('node:test');
|
|
15
|
+
const assert = require('node:assert');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const server = fs.readFileSync(path.join(__dirname, '../../server.js'), 'utf8');
|
|
20
|
+
const sprite = fs.readFileSync(path.join(__dirname, '../../public/vendor/tabler-sprite.svg'), 'utf8');
|
|
21
|
+
|
|
22
|
+
test('the Screens button uses the same icon as the Screens page', () => {
|
|
23
|
+
// Sliced to the END of the entry rather than a byte count: a comment added inside it should not
|
|
24
|
+
// decide whether this test can see the icon.
|
|
25
|
+
const from = server.indexOf("id: 'open-screens'");
|
|
26
|
+
const entry = server.slice(from, server.indexOf('},', server.indexOf('action-label">Screens', from)));
|
|
27
|
+
assert.match(entry, /iconHtml\('tabler:device-tv', 0\.8\)/);
|
|
28
|
+
assert.doesNotMatch(entry, /uk-icon="icon: tv/, 'the hairline one is gone');
|
|
29
|
+
// The page heading it now matches.
|
|
30
|
+
assert.match(server, /tabler:device-tv/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('the terminals icon beside it matches', () => {
|
|
34
|
+
// They sit side by side; one Tabler and one UIKit next to each other reads as a mistake.
|
|
35
|
+
const from = server.indexOf("id: 'terminals'");
|
|
36
|
+
const entry = server.slice(from, server.indexOf('},', server.indexOf('term-close-all', from)));
|
|
37
|
+
assert.match(entry, /iconHtml\('tabler:device-laptop', 0\.8\)/);
|
|
38
|
+
assert.doesNotMatch(entry, /uk-icon="icon: laptop/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('both icons are really in the vendored sprite', () => {
|
|
42
|
+
// An icon name the sprite does not carry renders an empty box — that is how `commands` shipped with
|
|
43
|
+
// `terminal` and drew nothing.
|
|
44
|
+
for (const id of ['device-tv', 'device-laptop']) {
|
|
45
|
+
assert.ok(sprite.includes(`id="${id}"`), `${id} is in the sprite`);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The updater must put this machine's docker GID back.
|
|
3
|
+
*
|
|
4
|
+
* .ddev/ is mirrored from the release, and two files in it are per-machine —
|
|
5
|
+
* docker-compose.dood.yaml (group_add: the GID the running process is granted)
|
|
6
|
+
* and web-build/Dockerfile.workspace-app (the GID the group is created with).
|
|
7
|
+
* The shipped values are placeholders.
|
|
8
|
+
*
|
|
9
|
+
* Nothing restored them. The project name, ports and fqdns were carefully put
|
|
10
|
+
* back and the GID was not, so every supported update left a docker GID that is
|
|
11
|
+
* not the host's — silently, until the next `ddev restart`, when the container
|
|
12
|
+
* loses docker.sock and the dashboard reports EVERY project as Stopped while they
|
|
13
|
+
* all run. Reproduced by setting it to 999 on a host whose group is 973.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const assert = require('node:assert');
|
|
17
|
+
const { test } = require('node:test');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const ROOT = path.join(__dirname, '..', '..', '..', '..');
|
|
22
|
+
const UPDATE = path.join(ROOT, 'core', 'workspace-app', 'scripts', 'update.sh');
|
|
23
|
+
const SYNC = path.join(ROOT, 'core', 'scripts', 'sync-tooling.sh');
|
|
24
|
+
|
|
25
|
+
test('update.sh re-applies the docker GID after mirroring the release', () => {
|
|
26
|
+
const src = fs.readFileSync(UPDATE, 'utf8');
|
|
27
|
+
assert.match(src, /docker-gid\.sh/, 'the updater never re-applies the docker GID');
|
|
28
|
+
// After the mirror, not before it: the release's .ddev/ lands on top.
|
|
29
|
+
const rsyncAt = src.indexOf('rsync ${rsync_flags} --delete');
|
|
30
|
+
const gidAt = src.indexOf('core/scripts/docker-gid.sh');
|
|
31
|
+
assert.ok(rsyncAt > 0 && gidAt > rsyncAt, 'the GID must be re-applied AFTER the rsync');
|
|
32
|
+
// And it must not be silent when it cannot: a dashboard that will go blind on
|
|
33
|
+
// its next restart, with no clue why, is the worst outcome.
|
|
34
|
+
assert.match(src, /show every project as Stopped/);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('sync-tooling.sh does the same, for the machines that carry the clone', () => {
|
|
38
|
+
// The two install paths must not disagree: one of them getting this right and
|
|
39
|
+
// the other not is how it went unnoticed.
|
|
40
|
+
assert.match(fs.readFileSync(SYNC, 'utf8'), /docker-gid\.sh/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('the GID placeholder is what ships, so the restore is not optional', () => {
|
|
44
|
+
// If the committed value ever became a real host's GID, this test is the thing
|
|
45
|
+
// that says so — a machine-specific number in a shared repo is a different bug.
|
|
46
|
+
const compose = path.join(ROOT, 'core', 'workspace-app', '.ddev', 'docker-compose.dood.yaml');
|
|
47
|
+
const txt = fs.readFileSync(compose, 'utf8');
|
|
48
|
+
const m = txt.match(/group_add:\s*\n\s*-\s*"(\d+)"/);
|
|
49
|
+
assert.ok(m, 'group_add is not in the expected shape any more');
|
|
50
|
+
assert.ok(/^\d+$/.test(m[1]), 'group_add must be a numeric GID');
|
|
51
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
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 ROOT = path.join(__dirname, '..', '..', '..', '..');
|
|
7
|
+
// The guide moved to the app's own docs folder, beside the screenshots it uses, so
|
|
8
|
+
// that the file /help renders and the file people read on GitHub are one file.
|
|
9
|
+
const GUIDE = path.join(__dirname, '..', '..', 'docs', 'USER-GUIDE.md');
|
|
10
|
+
const APP = path.join(__dirname, '..', '..');
|
|
11
|
+
|
|
12
|
+
// The /help route renders this file. If it moves or is renamed the page 404s with a message,
|
|
13
|
+
// but a test is how that is noticed before someone opening Help is.
|
|
14
|
+
test('the user guide exists where /help reads it', () => {
|
|
15
|
+
assert.ok(fs.existsSync(GUIDE), `no guide at ${GUIDE}`);
|
|
16
|
+
const md = fs.readFileSync(GUIDE, 'utf8');
|
|
17
|
+
assert.ok(md.length > 2000, 'the guide is suspiciously short');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('it answers the questions a new person actually has', () => {
|
|
21
|
+
const md = fs.readFileSync(GUIDE, 'utf8').toLowerCase();
|
|
22
|
+
// Not a style check — each of these is a thing seventy people will need on day one, and a
|
|
23
|
+
// guide that omits one sends them to a person instead.
|
|
24
|
+
for (const topic of [
|
|
25
|
+
'install.sh', // how do I install it
|
|
26
|
+
'log out', // the docker group, which blocks everything until you do
|
|
27
|
+
'trust-https', // the browser refusing every URL
|
|
28
|
+
'workspace.ddev.site', // where do I go
|
|
29
|
+
'backups', // how do I not lose work
|
|
30
|
+
'web ide', // how do I edit code
|
|
31
|
+
'screens', // where did my long job go
|
|
32
|
+
'update.sh', // how do I stay current
|
|
33
|
+
'no login', // the security posture, in plain words
|
|
34
|
+
]) {
|
|
35
|
+
assert.ok(md.includes(topic), `the guide never mentions "${topic}"`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// The guide's pictures live in sub folders; the route once took a bare file name, so /help showed none of them.
|
|
40
|
+
test('every picture the guide shows exists and /help serves its path', () => {
|
|
41
|
+
const md = fs.readFileSync(GUIDE, 'utf8');
|
|
42
|
+
const src = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
43
|
+
const literal = src.match(/pathname\.match\((\/\^\\\/help\\\/screenshots\\\/.*?\$\/)\);/);
|
|
44
|
+
assert.ok(literal, 'no /help/screenshots route');
|
|
45
|
+
const route = new RegExp(literal[1].slice(1, -1));
|
|
46
|
+
const shots = [...md.matchAll(/\]\((screenshots\/[^)\s]+)\)/g)].map((m) => m[1]);
|
|
47
|
+
assert.ok(shots.length > 20, 'the guide has lost its pictures');
|
|
48
|
+
for (const s of shots) {
|
|
49
|
+
assert.ok(fs.existsSync(path.join(path.dirname(GUIDE), s)), `${s} is missing`);
|
|
50
|
+
assert.ok(route.test(`/help/${s}`), `/help would not serve ${s}`);
|
|
51
|
+
}
|
|
52
|
+
assert.ok(!route.test('/help/screenshots/../server.js') && !route.test('/help/screenshots/a/../../x.png'));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('/help is wired up and reachable from every page', () => {
|
|
56
|
+
const src = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
57
|
+
assert.ok(src.includes("pathname === '/help'"), 'no /help route');
|
|
58
|
+
assert.ok(src.includes("navLink('/help'"), 'no Help entry in the actions rail');
|
|
59
|
+
assert.ok(src.includes('USER-GUIDE.md'), '/help no longer reads the guide');
|
|
60
|
+
});
|