@webship.co/workspace 1.0.0-alpha1 → 1.0.0-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +339 -0
- package/README.md +28 -364
- package/core/bin/workspace.js +135 -0
- package/core/config/settings.labels.yml +514 -0
- package/core/config/settings.template.yml +292 -0
- package/core/config/style.yml +53 -0
- package/core/config/theme.default.yml +161 -0
- package/core/config/theme.dispatch.yml +166 -0
- package/core/config/theme.drupal.yml +175 -0
- package/core/config/theme.ember.yml +160 -0
- package/core/config/theme.github.yml +172 -0
- package/core/config/theme.gitlab.yml +168 -0
- package/core/config/theme.press.yml +161 -0
- package/core/config/theme.slate.yml +160 -0
- package/core/config/theme.upsun.yml +174 -0
- package/core/docs/architecture.svg +65 -0
- package/core/docs/getting-started.md +51 -0
- package/core/docs/plugins.md +27 -0
- package/core/docs/releases.md +23 -0
- package/core/docs/testing.md +15 -0
- package/core/docs/workspaces.md +25 -0
- package/core/scripts/args/arg-ai-items.sh +65 -0
- package/core/scripts/args/arg-autostart.sh +31 -0
- package/core/scripts/args/arg-avatar-agent.sh +28 -0
- package/core/scripts/args/arg-avatar-catalog.sh +12 -0
- package/core/scripts/args/arg-avatar-character.sh +68 -0
- package/core/scripts/args/arg-avatar-voice.sh +24 -0
- package/core/scripts/args/arg-avatar.sh +22 -0
- package/core/scripts/args/arg-checks.sh +36 -0
- package/core/scripts/args/arg-clone.sh +42 -0
- package/core/scripts/args/arg-commands.sh +51 -0
- package/core/scripts/args/arg-cucumber.sh +34 -0
- package/core/scripts/args/arg-distribution.sh +30 -0
- package/core/scripts/args/arg-drupal.sh +39 -0
- package/core/scripts/args/arg-drupal_cms.sh +18 -0
- package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
- package/core/scripts/args/arg-graphify.sh +36 -0
- package/core/scripts/args/arg-graphs.sh +12 -0
- package/core/scripts/args/arg-hostnames.sh +23 -0
- package/core/scripts/args/arg-milvus-backup.sh +8 -0
- package/core/scripts/args/arg-milvus-mcp.sh +12 -0
- package/core/scripts/args/arg-milvus.sh +32 -0
- package/core/scripts/args/arg-obsidian.sh +17 -0
- package/core/scripts/args/arg-odoo.sh +18 -0
- package/core/scripts/args/arg-penpot-backup.sh +8 -0
- package/core/scripts/args/arg-penpot-mcp.sh +15 -0
- package/core/scripts/args/arg-penpot-token.sh +17 -0
- package/core/scripts/args/arg-penpot.sh +44 -0
- package/core/scripts/args/arg-rag.sh +21 -0
- package/core/scripts/args/arg-ragify.sh +35 -0
- package/core/scripts/args/arg-reclaim.sh +18 -0
- package/core/scripts/args/arg-release.sh +21 -0
- package/core/scripts/args/arg-screen.sh +28 -0
- package/core/scripts/args/arg-site-template.sh +13 -0
- package/core/scripts/args/arg-spdd.sh +16 -0
- package/core/scripts/args/arg-specs.sh +22 -0
- package/core/scripts/args/arg-storage.sh +36 -0
- package/core/scripts/args/arg-testing.sh +36 -0
- package/core/scripts/args/arg-webship.sh +34 -0
- package/core/scripts/args/arg-webships-project.sh +22 -0
- package/core/scripts/args/arg-webships-site-template.sh +17 -0
- package/core/scripts/args/arg-webships.sh +34 -0
- package/core/scripts/args/arg-website-site-template.sh +17 -0
- package/core/scripts/args/arg-website.sh +25 -0
- package/core/scripts/args/arg-wordpress.sh +18 -0
- package/core/scripts/autostart-run.sh +162 -0
- package/core/scripts/autostart-stop.sh +30 -0
- package/core/scripts/autostart-watch.sh +43 -0
- package/core/scripts/autostart.sh +26 -0
- package/core/scripts/bootstrap-functions.sh +117 -0
- package/core/scripts/bootstrap-libraries.sh +10 -0
- package/core/scripts/bootstrap.sh +71 -0
- package/core/scripts/ddev-shim/ddev +122 -0
- package/core/scripts/docker-gid.sh +119 -0
- package/core/scripts/functions/arg-site-template.sh +28 -0
- package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
- package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
- package/core/scripts/functions/fun-ai-items.sh +621 -0
- package/core/scripts/functions/fun-ai-providers.sh +72 -0
- package/core/scripts/functions/fun-autostart.sh +429 -0
- package/core/scripts/functions/fun-avatar.sh +441 -0
- package/core/scripts/functions/fun-backup-item.sh +50 -0
- package/core/scripts/functions/fun-build-distribution.sh +226 -0
- package/core/scripts/functions/fun-build-site-template.sh +104 -0
- package/core/scripts/functions/fun-build-webships.sh +161 -0
- package/core/scripts/functions/fun-build-website.sh +199 -0
- package/core/scripts/functions/fun-checks.sh +243 -0
- package/core/scripts/functions/fun-clone-project.sh +289 -0
- package/core/scripts/functions/fun-commands.sh +521 -0
- package/core/scripts/functions/fun-database.sh +63 -0
- package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
- package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
- package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
- package/core/scripts/functions/fun-default-settings.sh +28 -0
- package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
- package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
- package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
- package/core/scripts/functions/fun-distribution-webship.sh +28 -0
- package/core/scripts/functions/fun-distribution-webships.sh +33 -0
- package/core/scripts/functions/fun-distribution-website.sh +19 -0
- package/core/scripts/functions/fun-drush.sh +48 -0
- package/core/scripts/functions/fun-elevenlabs.sh +179 -0
- package/core/scripts/functions/fun-files.sh +19 -0
- package/core/scripts/functions/fun-git-worktrees.sh +142 -0
- package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
- package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
- package/core/scripts/functions/fun-milvus.sh +584 -0
- package/core/scripts/functions/fun-odoo.sh +121 -0
- package/core/scripts/functions/fun-penpot.sh +724 -0
- package/core/scripts/functions/fun-project-name.sh +20 -0
- package/core/scripts/functions/fun-rag.sh +253 -0
- package/core/scripts/functions/fun-ragify.sh +165 -0
- package/core/scripts/functions/fun-reclaim.sh +297 -0
- package/core/scripts/functions/fun-release.sh +159 -0
- package/core/scripts/functions/fun-remove-project.sh +319 -0
- package/core/scripts/functions/fun-screen.sh +96 -0
- package/core/scripts/functions/fun-spdd.sh +676 -0
- package/core/scripts/functions/fun-specs-share.sh +237 -0
- package/core/scripts/functions/fun-storage.sh +347 -0
- package/core/scripts/functions/fun-testing.sh +329 -0
- package/core/scripts/functions/fun-users.sh +87 -0
- package/core/scripts/functions/fun-version.sh +72 -0
- package/core/scripts/functions/fun-video-edit.sh +48 -0
- package/core/scripts/functions/fun-voice.sh +45 -0
- package/core/scripts/functions/fun-wordpress.sh +59 -0
- package/core/scripts/host-mounts.sh +161 -0
- package/core/scripts/install.sh +602 -0
- package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
- package/core/scripts/libs/argparse.sh +93 -0
- package/core/scripts/libs/bash-yaml.sh +52 -0
- package/core/scripts/libs/checks_plan.js +312 -0
- package/core/scripts/libs/elevenlabs.py +627 -0
- package/core/scripts/libs/progress-bar.sh +19 -0
- package/core/scripts/libs/rag_index.py +446 -0
- package/core/scripts/libs/video_edit.py +636 -0
- package/core/scripts/libs/voice.py +430 -0
- package/core/scripts/plugins/jira/host-install.sh +119 -0
- package/core/scripts/plugins/jira/host-mounts.txt +2 -0
- package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
- package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
- package/core/scripts/plugins/upsun/host-install.sh +99 -0
- package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
- package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
- package/core/scripts/plugins.sh +152 -0
- package/core/scripts/release.sh +69 -0
- package/core/scripts/storage.sh +39 -0
- package/core/scripts/sync-tooling.sh +246 -0
- package/core/scripts/tests/cmd-smoke-test.sh +109 -0
- package/core/scripts/trust-https.sh +257 -0
- package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
- package/core/workspace-app/.ddev/config.yaml +400 -0
- package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
- package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
- package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
- package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
- package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
- package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +1 -1
- package/core/workspace-app/README.md +380 -0
- package/{actions → core/workspace-app/actions}/ddev.js +0 -1
- package/{actions → core/workspace-app/actions}/rag.js +0 -1
- package/{actions → core/workspace-app/actions}/testing.js +0 -1
- package/{assistant.js → core/workspace-app/assistant.js} +1 -10
- package/{git-info.js → core/workspace-app/git-info.js} +17 -1
- package/core/workspace-app/package.json +29 -0
- package/core/workspace-app/plugin-catalog.js +50 -0
- package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +19 -1
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{row-layout.js → core/workspace-app/row-layout.js} +0 -1
- package/{server.js → core/workspace-app/server.js} +26 -225
- package/core/workspace-app/tests/features/actions-rail.feature +44 -0
- package/core/workspace-app/tests/features/ai-items.feature +23 -0
- package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
- package/core/workspace-app/tests/features/checks.feature +39 -0
- package/core/workspace-app/tests/features/clone-project.feature +30 -0
- package/core/workspace-app/tests/features/command-authoring.feature +58 -0
- package/core/workspace-app/tests/features/dashboard.feature +57 -0
- package/core/workspace-app/tests/features/menus.feature +29 -0
- package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
- package/core/workspace-app/tests/features/plugins.feature +20 -0
- package/core/workspace-app/tests/features/project-actions.feature +40 -0
- package/core/workspace-app/tests/features/screens.feature +29 -0
- package/core/workspace-app/tests/features/settings.feature +46 -0
- package/core/workspace-app/tests/features/test-reports.feature +32 -0
- package/core/workspace-app/tests/features/theme.feature +40 -0
- package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
- package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +152 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -0
- package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
- package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
- package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
- package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
- package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
- package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
- package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
- package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
- package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
- package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
- package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
- package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
- package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
- package/core/workspace-app/tests/unit/assistant.test.js +61 -0
- package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
- package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
- package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
- package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
- package/core/workspace-app/tests/unit/browsers.test.js +76 -0
- package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
- package/core/workspace-app/tests/unit/build-order.test.js +153 -0
- package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
- package/core/workspace-app/tests/unit/builders.test.js +158 -0
- package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
- package/core/workspace-app/tests/unit/changelog.test.js +177 -0
- package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
- package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
- package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
- package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
- package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
- package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
- package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
- package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
- package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
- package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
- package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
- package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
- package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
- package/core/workspace-app/tests/unit/create-card.test.js +85 -0
- package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
- package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
- package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-domains.test.js +153 -0
- package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
- package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
- package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
- package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
- package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
- package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
- package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
- package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
- package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
- package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
- package/core/workspace-app/tests/unit/density.test.js +52 -0
- package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
- package/core/workspace-app/tests/unit/event-log.test.js +102 -0
- package/core/workspace-app/tests/unit/favicon.test.js +68 -0
- package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
- package/core/workspace-app/tests/unit/git-info.test.js +26 -0
- package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
- package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
- package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
- package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
- package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
- package/core/workspace-app/tests/unit/images-create.test.js +59 -0
- package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
- package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
- package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
- package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
- package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
- package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
- package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
- package/core/workspace-app/tests/unit/narration.test.js +186 -0
- package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
- package/core/workspace-app/tests/unit/navigation.test.js +254 -0
- package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
- package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
- package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
- package/core/workspace-app/tests/unit/notes.test.js +128 -0
- package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
- package/core/workspace-app/tests/unit/page-width.test.js +59 -0
- package/core/workspace-app/tests/unit/php-version.test.js +90 -0
- package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
- package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
- package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
- package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
- package/core/workspace-app/tests/unit/plugins.test.js +58 -0
- package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
- package/core/workspace-app/tests/unit/project-make.test.js +143 -0
- package/core/workspace-app/tests/unit/project-name.test.js +202 -0
- package/core/workspace-app/tests/unit/reader.test.js +62 -0
- package/core/workspace-app/tests/unit/release.test.js +92 -0
- package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
- package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
- package/core/workspace-app/tests/unit/round-11.test.js +31 -0
- package/core/workspace-app/tests/unit/round-12.test.js +37 -0
- package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
- package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
- package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
- package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
- package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
- package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
- package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
- package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
- package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
- package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
- package/core/workspace-app/tests/unit/screens.test.js +74 -0
- package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
- package/core/workspace-app/tests/unit/session-name.test.js +64 -0
- package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
- package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
- package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
- package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
- package/core/workspace-app/tests/unit/speech.test.js +290 -0
- package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
- package/core/workspace-app/tests/unit/status-language.test.js +47 -0
- package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
- package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
- package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
- package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
- package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
- package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
- package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
- package/core/workspace-app/tests/unit/themes.test.js +453 -0
- package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
- package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
- package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
- package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
- package/core/workspace-app/tests/unit/version-check.test.js +98 -0
- package/core/workspace-app/tests/unit/version-link.test.js +50 -0
- package/core/workspace-app/tests/unit/video-borrow.test.js +198 -0
- package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
- package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
- package/core/workspace-app/tests/unit/video-edit.test.js +417 -0
- package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
- package/core/workspace-app/tests/unit/waiting.test.js +48 -0
- package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
- package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
- package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
- package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
- package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
- package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
- package/{workspace-types.js → core/workspace-app/workspace-types.js} +2 -2
- package/{workspaces.js → core/workspace-app/workspaces.js} +1 -5
- package/package.json +38 -36
- package/actions/graphs.js +0 -107
- package/actions/obsidian.js +0 -59
- package/graphs.js +0 -104
- package/obsidian.js +0 -405
- /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
- /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
- /package/{actions → core/workspace-app/actions}/audio.js +0 -0
- /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
- /package/{actions → core/workspace-app/actions}/checks.js +0 -0
- /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
- /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
- /package/{actions → core/workspace-app/actions}/composer.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
- /package/{actions → core/workspace-app/actions}/folders.js +0 -0
- /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
- /package/{actions → core/workspace-app/actions}/images.js +0 -0
- /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
- /package/{actions → core/workspace-app/actions}/node.js +0 -0
- /package/{actions → core/workspace-app/actions}/notes.js +0 -0
- /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
- /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
- /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
- /package/{actions → core/workspace-app/actions}/screens.js +0 -0
- /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
- /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
- /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
- /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
- /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
- /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
- /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
- /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
- /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
- /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
- /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
- /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
- /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
- /package/{checks.js → core/workspace-app/checks.js} +0 -0
- /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
- /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
- /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
- /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
- /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
- /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
- /package/{commands.js → core/workspace-app/commands.js} +0 -0
- /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
- /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
- /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
- /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
- /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
- /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
- /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
- /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
- /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
- /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
- /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
- /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/README.md +0 -0
- /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
- /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
- /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
- /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
- /package/{folders.js → core/workspace-app/folders.js} +0 -0
- /package/{format.js → core/workspace-app/format.js} +0 -0
- /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
- /package/{html.js → core/workspace-app/html.js} +0 -0
- /package/{icons.js → core/workspace-app/icons.js} +0 -0
- /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
- /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
- /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
- /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
- /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
- /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
- /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
- /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
- /package/{images.js → core/workspace-app/images.js} +0 -0
- /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
- /package/{linger.js → core/workspace-app/linger.js} +0 -0
- /package/{lists.js → core/workspace-app/lists.js} +0 -0
- /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
- /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
- /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
- /package/{notes.js → core/workspace-app/notes.js} +0 -0
- /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
- /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
- /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
- /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
- /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
- /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
- /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
- /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
- /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
- /package/{public → core/workspace-app/public}/css/base.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/favicon.ico +0 -0
- /package/{public → core/workspace-app/public}/favicon.png +0 -0
- /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
- /package/{public → core/workspace-app/public}/logo.png +0 -0
- /package/{public → core/workspace-app/public}/media-player.js +0 -0
- /package/{public → core/workspace-app/public}/mic.js +0 -0
- /package/{public → core/workspace-app/public}/narration.js +0 -0
- /package/{public → core/workspace-app/public}/palette.js +0 -0
- /package/{public → core/workspace-app/public}/sortable.js +0 -0
- /package/{public → core/workspace-app/public}/suggest.js +0 -0
- /package/{public → core/workspace-app/public}/terminal.js +0 -0
- /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
- /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
- /package/{public → core/workspace-app/public}/ui.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
- /package/{rag.js → core/workspace-app/rag.js} +0 -0
- /package/{reader.js → core/workspace-app/reader.js} +0 -0
- /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
- /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
- /package/{screenrc → core/workspace-app/screenrc} +0 -0
- /package/{screens.js → core/workspace-app/screens.js} +0 -0
- /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
- /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
- /package/{speech.js → core/workspace-app/speech.js} +0 -0
- /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
- /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
- /package/{themes.js → core/workspace-app/themes.js} +0 -0
- /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
- /package/{urls.js → core/workspace-app/urls.js} +0 -0
- /package/{validate.js → core/workspace-app/validate.js} +0 -0
- /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
- /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
- /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
- /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
- /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The favicon, asserted on the artwork rather than on the page.
|
|
3
|
+
*
|
|
4
|
+
* The page's link tags are checked in the browser suite; what cannot be checked there is the file
|
|
5
|
+
* itself — a browser handed an icon renders it as an image, so there is no text to read and a
|
|
6
|
+
* navigation to it is not a page. Here it is a file, which is what it is.
|
|
7
|
+
*
|
|
8
|
+
* Why any of this is worth a test: the favicon is the one piece of brand that appears in a place we
|
|
9
|
+
* do not control, at 16px, beside example.com's own tab. The dashboard wears the Webship mark, from
|
|
10
|
+
* the product's own image set, and ships it as PNGs: there is no SVG emblem, so the shipped default
|
|
11
|
+
* must not point at one.
|
|
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 PUBLIC = path.join(__dirname, '..', '..', 'public');
|
|
20
|
+
|
|
21
|
+
// A PNG header carries its dimensions and colour type at fixed offsets; no image library needed.
|
|
22
|
+
const png = (f) => {
|
|
23
|
+
const b = fs.readFileSync(path.join(PUBLIC, f));
|
|
24
|
+
assert.equal(b.toString('latin1', 1, 4), 'PNG', `${f} is a PNG`);
|
|
25
|
+
return { width: b.readUInt32BE(16), height: b.readUInt32BE(20), colour: b.readUInt8(25) };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
test('the icons are the sizes the places that use them ask for', () => {
|
|
29
|
+
// 64 for the tab, which browsers scale down from; 180 for the home screen, which iOS asks for exactly.
|
|
30
|
+
const tab = png('favicon.png');
|
|
31
|
+
assert.deepEqual([tab.width, tab.height], [64, 64], 'the tab icon');
|
|
32
|
+
const touch = png('touch-icon.png');
|
|
33
|
+
assert.deepEqual([touch.width, touch.height], [180, 180], 'the home-screen icon');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('the home-screen icon carries no alpha, because it lands on somebody\'s wallpaper', () => {
|
|
37
|
+
// A knocked-out mark over a wallpaper is whatever the wallpaper is. Asserted on the IHDR colour
|
|
38
|
+
// type, which is the fact and not a proxy for it: byte 25 of a PNG is 2 for RGB and 6 for RGBA,
|
|
39
|
+
// so 2 means there is no transparency left to get wrong.
|
|
40
|
+
assert.equal(png('touch-icon.png').colour, 2, 'touch-icon.png still has an alpha channel — flatten it');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('the one logo serves both surfaces, and the legacy .ico is there for what asks for it by name', () => {
|
|
44
|
+
// One logo for the light page and the dark one: the mark reads on either, so there is no second file
|
|
45
|
+
// to drift from the first. favicon.ico is what an old client fetches without reading the page.
|
|
46
|
+
const logo = png('logo.png');
|
|
47
|
+
assert.ok(logo.width >= 180 && logo.width === logo.height, 'the mark is square and large enough to scale down');
|
|
48
|
+
const ico = fs.readFileSync(path.join(PUBLIC, 'favicon.ico'));
|
|
49
|
+
assert.equal(ico.readUInt16LE(0), 0, 'favicon.ico is an ICO');
|
|
50
|
+
assert.equal(ico.readUInt16LE(2), 1);
|
|
51
|
+
assert.ok(ico.readUInt16LE(4) >= 1, 'with at least one image in it');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('the shipped default is the PNGs, with no SVG to point at', () => {
|
|
55
|
+
const { styleSettings } = require('../../workspaces');
|
|
56
|
+
const style = styleSettings();
|
|
57
|
+
assert.equal(style.faviconSvg, '', 'an empty SVG means no <link rel="icon" type="image/svg+xml"> at all');
|
|
58
|
+
assert.equal(style.faviconPng, 'favicon.png');
|
|
59
|
+
assert.equal(style.touchIcon, 'touch-icon.png');
|
|
60
|
+
assert.equal(style.logoOnLight, 'logo.png');
|
|
61
|
+
assert.equal(style.logoOnDark, 'logo.png');
|
|
62
|
+
for (const f of [style.faviconPng, style.touchIcon, style.logoOnLight]) {
|
|
63
|
+
assert.ok(fs.existsSync(path.join(PUBLIC, f)), `${f} ships with the dashboard`);
|
|
64
|
+
}
|
|
65
|
+
// And the page only writes the SVG tag when there is an SVG to write.
|
|
66
|
+
const server = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
67
|
+
assert.match(server, /\$\{style\.faviconSvg \? `<link rel="icon" type="image\/svg\+xml"[^`]*` : ''\}/);
|
|
68
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browsing a documents folder, not just the top of it.
|
|
3
|
+
*
|
|
4
|
+
* `docs/` holds 29 folders and 8,105 files and the page listed 21 rows: everything in a subfolder was
|
|
5
|
+
* invisible, and so was every file whose extension was not on a curated list. The folder on disk and
|
|
6
|
+
* the page describing it disagreed, which is the one thing a file listing must not do.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { test } = require('node:test');
|
|
10
|
+
const assert = require('node:assert');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const fb = require('../../files-browser');
|
|
16
|
+
|
|
17
|
+
const APP = path.join(__dirname, '../..');
|
|
18
|
+
const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
19
|
+
const lists = fs.readFileSync(path.join(APP, 'lists.js'), 'utf8');
|
|
20
|
+
const del = fs.readFileSync(path.join(APP, 'actions/ai-items.js'), 'utf8');
|
|
21
|
+
|
|
22
|
+
/** A workspace-shaped fixture: two folders, a file, some noise, and a symlink pointing out of it. */
|
|
23
|
+
function fixture() {
|
|
24
|
+
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-files-'));
|
|
25
|
+
const root = path.join(base, 'docs');
|
|
26
|
+
const outside = path.join(base, 'secrets');
|
|
27
|
+
fs.mkdirSync(path.join(root, 'shots'), { recursive: true });
|
|
28
|
+
fs.mkdirSync(path.join(root, 'shots', 'raw'), { recursive: true });
|
|
29
|
+
fs.mkdirSync(path.join(root, 'node_modules'), { recursive: true });
|
|
30
|
+
fs.mkdirSync(path.join(root, '.hidden'), { recursive: true });
|
|
31
|
+
fs.mkdirSync(outside, { recursive: true });
|
|
32
|
+
fs.writeFileSync(path.join(outside, 'keys.txt'), 'nope');
|
|
33
|
+
fs.writeFileSync(path.join(root, 'guide.pdf'), 'x');
|
|
34
|
+
fs.writeFileSync(path.join(root, 'shots', 'one.png'), 'x');
|
|
35
|
+
fs.writeFileSync(path.join(root, 'shots', 'two.png'), 'x');
|
|
36
|
+
fs.symlinkSync(outside, path.join(root, 'escape'));
|
|
37
|
+
return { base, root, outside };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('a path from the browser is split, resolved, and refused when it leaves the workspace', () => {
|
|
41
|
+
const { root } = fixture();
|
|
42
|
+
assert.equal(fb.safeSubIn(root, '').rel, '', 'no path is the top of the workspace');
|
|
43
|
+
assert.equal(fb.safeSubIn(root, 'shots/raw').rel, 'shots/raw');
|
|
44
|
+
// The segment check, which catches the obvious attempts before anything touches the filesystem.
|
|
45
|
+
for (const bad of ['..', '../..', 'shots/../..', 'a//b', '.', './x']) {
|
|
46
|
+
assert.equal(fb.safeSubIn(root, bad).ok, false, `refused: ${bad}`);
|
|
47
|
+
}
|
|
48
|
+
// And the one the segment check cannot catch: every segment is innocent, and the link leads out.
|
|
49
|
+
assert.equal(fb.safeSubIn(root, 'escape').ok, false, 'a symlink out of the workspace is refused');
|
|
50
|
+
assert.equal(fb.safeSubIn(root, 'no-such-folder').ok, false);
|
|
51
|
+
assert.equal(fb.safeSubIn(root, 'guide.pdf').ok, false, 'a file is not a folder to browse');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a folder row says how many entries are inside, counting direct children only', () => {
|
|
55
|
+
// A recursive count would walk thousands of files to render one line, and "8 items" is what tells
|
|
56
|
+
// you whether opening it is worth it anyway.
|
|
57
|
+
const { root } = fixture();
|
|
58
|
+
const subs = fb.subfolders(root, '');
|
|
59
|
+
const byName = Object.fromEntries(subs.map((s) => [s.name, s]));
|
|
60
|
+
assert.deepEqual(subs.map((s) => s.name).sort(), ['shots'], 'noise and the escape link are not folders to browse');
|
|
61
|
+
assert.equal(byName.shots.count, 3, 'two pictures and a folder');
|
|
62
|
+
assert.equal(byName.shots.folders, 1);
|
|
63
|
+
assert.equal(byName.shots.rel, 'shots');
|
|
64
|
+
assert.equal(fb.subfolders(path.join(root, 'shots'), 'shots')[0].rel, 'shots/raw', 'a nested folder keeps its full path');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('noise is named, not guessed at', () => {
|
|
68
|
+
// The workspace's own plumbing is tooling rather than content, and the three folder documents are
|
|
69
|
+
// already rendered under the list. Everything else shows, whatever its extension.
|
|
70
|
+
for (const n of ['node_modules', '.git', 'dev-ai-agents', '.hidden']) assert.ok(fb.isNoise(n, true), n);
|
|
71
|
+
for (const n of ['cmd-tool-backup-doc.sh', 'docs.code-workspace', 'README.md', 'AGENTS.md', 'CLAUDE.md', '.DS_Store']) {
|
|
72
|
+
assert.ok(fb.isNoise(n, false), n);
|
|
73
|
+
}
|
|
74
|
+
for (const n of ['guide.pdf', 'shot.png', 'demo.mp4', 'export.zip', 'notes.txt', 'data.json']) {
|
|
75
|
+
assert.equal(fb.isNoise(n, false), false, `${n} is content`);
|
|
76
|
+
}
|
|
77
|
+
assert.equal(fb.isNoise('screenshots', true), false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('the trail back out names every level', () => {
|
|
81
|
+
const trail = fb.crumbs('docs', 'a/b/c', 'Docs');
|
|
82
|
+
assert.deepEqual(trail.map((c) => c.name), ['Docs', 'a', 'b', 'c']);
|
|
83
|
+
assert.deepEqual(trail.map((c) => c.rel), ['', 'a', 'a/b', 'a/b/c']);
|
|
84
|
+
assert.deepEqual(fb.crumbs('docs', '', 'Docs'), [{ name: 'Docs', rel: '' }]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('a file says what KIND it is, and every icon exists in the sprite', () => {
|
|
88
|
+
// A missing symbol renders an empty box rather than failing, so the names are checked against the
|
|
89
|
+
// sprite rather than trusted.
|
|
90
|
+
const sprite = fs.readFileSync(path.join(APP, 'public/vendor/tabler-sprite.svg'), 'utf8');
|
|
91
|
+
const cases = { 'a.pdf': 'file-type-pdf', 'a.docx': 'file-type-doc', 'a.csv': 'table', 'a.pptx': 'presentation',
|
|
92
|
+
'a.html': 'world', 'a.png': 'photo', 'a.mp4': 'player-play', 'a.mp3': 'music', 'a.zip': 'file-zip',
|
|
93
|
+
'a.md': 'file-text', 'a.yml': 'code', 'a.unknown-ext': 'file' };
|
|
94
|
+
for (const [file, icon] of Object.entries(cases)) {
|
|
95
|
+
assert.equal(fb.kindIcon(file), `tabler:${icon}`, file);
|
|
96
|
+
assert.ok(sprite.includes(`id="${icon}"`), `the sprite carries ${icon}`);
|
|
97
|
+
}
|
|
98
|
+
for (const extra of ['folder', 'chevron-right']) assert.ok(sprite.includes(`id="${extra}"`), extra);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('only the folder workspaces browse; the item ones are left alone', () => {
|
|
102
|
+
// The ai-* workspaces list items by the name declared in their frontmatter, and a subdirectory
|
|
103
|
+
// there is a skill they already handle — folders would be a second, competing idea of a row.
|
|
104
|
+
assert.deepEqual([...fb.BROWSABLE].sort(), ['audio', 'designs', 'docs', 'images', 'specs', 'videos']);
|
|
105
|
+
for (const k of ['ai-agents', 'ai-skills', 'ai-commands', 'ai-prompts']) assert.equal(fb.BROWSABLE.has(k), false, k);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('specs browses, because a spec IS a folder', () => {
|
|
109
|
+
// A spec is products/<project>/NNN-topic/ holding four files, and a product's constitution is
|
|
110
|
+
// products/<project>/00-constitution.md — everything is two levels down. Without browsing, the
|
|
111
|
+
// flat read of the top level found nothing and the page said "No specs here yet" on a workspace
|
|
112
|
+
// that had twelve of them.
|
|
113
|
+
assert.equal(fb.BROWSABLE.has('specs'), true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("a recording's poster is its thumbnail, never a row of its own", () => {
|
|
117
|
+
// The rule that used to keep videos out of browsing altogether: a poster frame is an input to the
|
|
118
|
+
// player, and listing it doubles the rows and makes half of them stills. Both shapes the recorder
|
|
119
|
+
// writes are covered — `demo-poster.jpg` beside `demo.mp4`, and `demo.jpg` beside `demo.mp4`.
|
|
120
|
+
const groups = new Map([
|
|
121
|
+
['demo', { name: 'demo', formats: [{ file: 'demo.mp4', path: 'demo.mp4', video: true }] }],
|
|
122
|
+
['demo-poster', { name: 'demo-poster', formats: [{ file: 'demo-poster.jpg', path: 'demo-poster.jpg', video: false }] }],
|
|
123
|
+
['talk', { name: 'talk', formats: [
|
|
124
|
+
{ file: 'talk.mp4', path: 'talk.mp4', video: true },
|
|
125
|
+
{ file: 'talk.jpg', path: 'talk.jpg', video: false }] }],
|
|
126
|
+
// A picture that belongs to nothing is still a picture, and keeps its row.
|
|
127
|
+
['diagram', { name: 'diagram', formats: [{ file: 'diagram.png', path: 'diagram.png', video: false }] }],
|
|
128
|
+
]);
|
|
129
|
+
fb.attachPosters(groups);
|
|
130
|
+
assert.equal(groups.get('demo').poster, 'demo-poster.jpg');
|
|
131
|
+
assert.equal(groups.has('demo-poster'), false, 'the poster is not a row');
|
|
132
|
+
assert.equal(groups.get('talk').poster, 'talk.jpg');
|
|
133
|
+
assert.deepEqual(groups.get('talk').formats.map((f) => f.file), ['talk.mp4'], 'and not a format to offer a button for');
|
|
134
|
+
assert.equal(groups.get('diagram').poster, undefined);
|
|
135
|
+
assert.equal(groups.has('diagram'), true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('the grid views show the picture itself, and the list does not', () => {
|
|
139
|
+
// A folder of screenshots is a contact sheet; a column of identical photo icons answers none of the
|
|
140
|
+
// questions you open it with. In a list a row is one line of text, and a thumbnail there would set
|
|
141
|
+
// the row height for every file whether or not it has one.
|
|
142
|
+
const css = fs.readFileSync(path.join(APP, 'public/css/components/project-rows.css'), 'utf8');
|
|
143
|
+
assert.match(server, /class="fx-thumb\$\{it\.poster \? ' is-video' : ''\}"/);
|
|
144
|
+
assert.match(server, /loading="lazy"/, 'a folder can hold two hundred of them');
|
|
145
|
+
assert.match(css, /\.fx-thumb \{ display: none; \}/);
|
|
146
|
+
assert.match(css, /html\[data-files-view\^="grid"\] \.fx-thumb \{[^}]*display: block/);
|
|
147
|
+
assert.match(css, /aspect-ratio: 4 \/ 3/, 'a fixed box, so cards do not jump as shots load');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('the layout and the filter are the list\'s own controls', () => {
|
|
151
|
+
// List, or a grid one to six across — a folder of screenshots wants a contact sheet and a folder of
|
|
152
|
+
// reports wants a list of names.
|
|
153
|
+
assert.match(server, /const files = kind === 'files';/);
|
|
154
|
+
assert.match(server, /pref = home \? 'ws-home-view' : files \? 'ws-files-view' : 'ws-rows-view'/);
|
|
155
|
+
assert.match(server, /\['grid-6', 'Six across — the most files on one screen'\]/);
|
|
156
|
+
const ui = fs.readFileSync(path.join(APP, 'public/ui.js'), 'utf8');
|
|
157
|
+
assert.match(ui, /'ws-files-view': \{ attr: 'filesView', allowed: \['list', 'grid-1', 'grid-2', 'grid-3', 'grid-4', 'grid-5', 'grid-6'\] \}/);
|
|
158
|
+
assert.match(server, /d\.dataset\.filesView=fv/, 'applied before first paint, like every other layout');
|
|
159
|
+
|
|
160
|
+
// And what the folder is narrowed to. The first two are about ORDER: a folder of thirty subfolders
|
|
161
|
+
// and a folder of thirty files want opposite things at the top.
|
|
162
|
+
assert.match(server, /const FILE_FILTERS = \{/);
|
|
163
|
+
for (const k of ['all', 'files-first', 'folders', 'files', 'documents', 'images', 'video', 'archives']) {
|
|
164
|
+
assert.match(server, new RegExp(`(^|\\s|')${k.replace('-', '-')}'?: \\{ label:`, 'm'), `the filter offers ${k}`);
|
|
165
|
+
}
|
|
166
|
+
assert.match(server, /foldersFirst \? \(!!b\.folder - !!a\.folder\) : \(!!a\.folder - !!b\.folder\)/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('the listing draws folders first, as one row that opens', () => {
|
|
170
|
+
assert.match(server, /if \(browsable\) items\.push\(\.\.\.filesBrowser\.subfolders\(dir, rel\)\)/);
|
|
171
|
+
assert.match(server, /class="fx-row fx-folder"/);
|
|
172
|
+
assert.match(server, /hx-get="\/fragments\/\$\{esc\(key\)\}\/items\?path=\$\{encodePath\(it\.rel\)\}"/);
|
|
173
|
+
// Folders stay first whatever the sort: you scan a file list for a name and a folder list for
|
|
174
|
+
// where to go next, and mixing them by date makes both slower.
|
|
175
|
+
assert.match(server, /const kept = browsable \? matched\.filter\(\(it\) => filter\.keep\(it\)\) : matched;/);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('the folder travels with every request the list makes', () => {
|
|
179
|
+
// Otherwise page 2 of a subfolder is page 2 of the workspace root, and a delete throws you back to
|
|
180
|
+
// the top.
|
|
181
|
+
assert.match(server, /if \(rel\) qs\.set\('path', rel\)/, 'the pager and the sort carry it');
|
|
182
|
+
assert.match(server, /listControlsHtml\(`\/fragments\/\$\{key\}\/items`, target, st, meta\.nounPlural, browsable \? kindFilter : null, \{/);
|
|
183
|
+
assert.match(server, /if \(st\.kind && FILE_FILTERS\[st\.kind\] && st\.kind !== defaultKind\(key\)\) qs\.set\('kind', st\.kind\);/,
|
|
184
|
+
'the filter travels with the folder, unless it is the workspace\'s own default');
|
|
185
|
+
assert.match(server, /hx-include="find input\[name='path'\]"/, 'a refresh comes back to this folder');
|
|
186
|
+
assert.match(lists, /const carriedInputs = /, 'the controls render it as a hidden input');
|
|
187
|
+
assert.match(lists, /hx-get="\$\{esc\(url\)\}\?\$\{esc\(resetQs\.toString\(\)\)\}"/, 'Reset clears the search where you stand');
|
|
188
|
+
assert.match(lists, /path: \(url\.searchParams\.get\('path'\) \|\| ''\)/, 'and it is read back off the query');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('a file is reached by its path in the tree, not by its bare name', () => {
|
|
192
|
+
// encodeURIComponent on the whole path turns the separators into %2F and the file server then
|
|
193
|
+
// looks for one file whose name has slashes in it.
|
|
194
|
+
assert.match(server, /const encodePath = \(rel\) => String\(rel\)\.split\('\/'\)\.map\(encodeURIComponent\)\.join\('\/'\)/);
|
|
195
|
+
assert.match(server, /const filePath = \(key, rel\) => `\/files\/\$\{encodeURIComponent\(key\)\}\/\$\{encodePath\(rel\)\}`/);
|
|
196
|
+
// Review and play take a path now, and check it the same way the listing does.
|
|
197
|
+
assert.match(server, /pathname\.match\(\/\^\\\/fragments\\\/\(\[a-z0-9_-\]\+\)\\\/review\\\/\(\.\+\)\$\/\)/);
|
|
198
|
+
assert.match(server, /pathname\.match\(\/\^\\\/fragments\\\/\(\[a-z0-9_-\]\+\)\\\/play\\\/\(\.\+\)\$\/\)/);
|
|
199
|
+
const review = server.slice(server.indexOf('const reviewMatch'), server.indexOf('const cmdEditMatch'));
|
|
200
|
+
assert.match(review, /filesBrowser\.safeSub\(key, dirRel\)/, 'the folder half is resolved, not trusted');
|
|
201
|
+
assert.match(review, /!\/\^\[\^\/\\\\\]\+\$\/\.test\(name\)/, 'and the file half cannot be a path');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('a text file is read in the modal, bounded', () => {
|
|
205
|
+
// A browser shows a .md or a .yml as a download or as raw text in a tab; neither is reading it
|
|
206
|
+
// where you found it, which is the point of a folder you can look inside.
|
|
207
|
+
assert.match(server, /const REVIEW_TEXT_RE = /);
|
|
208
|
+
assert.match(server, /REVIEW_TEXT_MAX/);
|
|
209
|
+
const re = server.match(/const REVIEWABLE_RE = (\/[^\n]+\/i);/)[1];
|
|
210
|
+
// eslint-disable-next-line no-eval
|
|
211
|
+
const reviewable = eval(re);
|
|
212
|
+
for (const f of ['a.md', 'a.txt', 'a.log', 'a.json', 'a.yml', 'a.csv', 'a.pdf', 'a.png']) {
|
|
213
|
+
assert.ok(reviewable.test(f), `${f} can be reviewed`);
|
|
214
|
+
}
|
|
215
|
+
assert.equal(reviewable.test('a.zip'), false, 'an archive has nothing to show');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('deleting reaches into the folder being browsed, and nowhere else', () => {
|
|
219
|
+
assert.match(del, /filesBrowser\.safeSub\(workspace, form\.dir \|\| ''\)/);
|
|
220
|
+
assert.match(del, /if \(!where\.ok\) return send/, 'a folder that does not resolve is refused');
|
|
221
|
+
assert.match(del, /if \(inSub \|\| DOC_ARTIFACT_RE\.test\(name\) \|\| VIDEO_RE\.test\(name\)\)/,
|
|
222
|
+
'inside a folder every row is a file, whatever its extension');
|
|
223
|
+
assert.match(server, /"dir":"\$\{esc\(rel\)\}"/, 'the row tells the action which folder it was in');
|
|
224
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the dashboard asks git about a folder is answered once per row per render: the worktree
|
|
3
|
+
* answer is memoised for a few seconds and forgotten on demand.
|
|
4
|
+
*/
|
|
5
|
+
const assert = require('node:assert');
|
|
6
|
+
const { test } = require('node:test');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execFileSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
const g = require('../../git-info');
|
|
13
|
+
|
|
14
|
+
test('gitWorktreeInfo answers from the memo within the window, and forgets when told', () => {
|
|
15
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-info-'));
|
|
16
|
+
execFileSync('git', ['init', '-q', dir]);
|
|
17
|
+
const a = g.gitWorktreeInfo(dir);
|
|
18
|
+
assert.strictEqual(a.role, 'plain');
|
|
19
|
+
assert.strictEqual(a.dirty, 0);
|
|
20
|
+
fs.writeFileSync(path.join(dir, 'x.txt'), 'x');
|
|
21
|
+
assert.strictEqual(g.gitWorktreeInfo(dir).dirty, 0, 'the memo answers within the window');
|
|
22
|
+
assert.strictEqual(g.gitWorktreeInfoNow(dir).dirty, 1, 'the fresh answer sees the change');
|
|
23
|
+
g.gitWorktreeInfoForget(dir);
|
|
24
|
+
assert.strictEqual(g.gitWorktreeInfo(dir).dirty, 1, 'forgotten, it asks git again');
|
|
25
|
+
assert.strictEqual(g.gitWorktreeInfo(path.join(dir, 'nope')).role, 'none');
|
|
26
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid or list, on the home page and inside a workspace.
|
|
3
|
+
*
|
|
4
|
+
* Two different questions wearing one control: a workspace card is small and four fit across, while a
|
|
5
|
+
* project row carries a dozen buttons — so a workspace opens as a LIST and the home page as a grid.
|
|
6
|
+
* What is checked here is what would break quietly: the layout must be decided before first paint, the
|
|
7
|
+
* choice must be the same on every workspace subdomain, and UIKit's utility classes must not be the
|
|
8
|
+
* thing holding the layout, since those carry !important and cannot be overridden.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const assert = require('node:assert');
|
|
12
|
+
const { test } = require('node:test');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const app = path.join(__dirname, '..', '..');
|
|
17
|
+
const server = fs.readFileSync(path.join(app, 'server.js'), 'utf8');
|
|
18
|
+
const ui = fs.readFileSync(path.join(app, 'public', 'ui.js'), 'utf8');
|
|
19
|
+
const css = fs.readFileSync(path.join(app, 'public', 'css', 'components', 'grid-and-list.css'), 'utf8');
|
|
20
|
+
|
|
21
|
+
test('the layout is applied before first paint, from the shared preference', () => {
|
|
22
|
+
const head = server.slice(server.indexOf("var hv=wsPref('ws-home-view'"), server.indexOf('})();</script>'));
|
|
23
|
+
assert.match(head, /wsPref\('ws-home-view','grid-4'\)/, 'the home page keeps the four-across it has');
|
|
24
|
+
assert.match(head, /wsPref\('ws-rows-view','list'\)/, 'a workspace opens as a list');
|
|
25
|
+
assert.match(head, /indexOf\(hv\)<0\)hv='grid-4'/, 'an unknown value falls back rather than rendering nothing');
|
|
26
|
+
assert.match(head, /indexOf\(rv\)<0\)rv='list'/);
|
|
27
|
+
assert.match(head, /d\.dataset\.homeView=hv/);
|
|
28
|
+
assert.match(head, /d\.dataset\.rowsView=rv/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('the two lists offer the counts that suit what they hold', () => {
|
|
32
|
+
const fn = server.slice(server.indexOf('function viewSwitcherHtml'), server.indexOf('/* ---------------- pages ---------------- */'));
|
|
33
|
+
for (const v of ['list', 'grid-1', 'grid-2', 'grid-3', 'grid-4', 'grid-5', 'grid-6']) {
|
|
34
|
+
assert.match(fn, new RegExp(`'${v}'`), `home: no ${v}`);
|
|
35
|
+
}
|
|
36
|
+
// Four across is a home-page answer only: three project rows are already tight.
|
|
37
|
+
const rowsPart = fn.slice(fn.indexOf(': [['));
|
|
38
|
+
assert.ok(!/grid-4/.test(rowsPart), 'a workspace must not offer four across');
|
|
39
|
+
assert.match(fn, /aria-label="\$\{esc\(v === 'list' \?/, 'each button says what it does');
|
|
40
|
+
// The icon is the answer, drawn to Tabler's rules — the set every workspace icon already comes from,
|
|
41
|
+
// which ships columns-1/2/3 and nothing past it and nothing for a page width.
|
|
42
|
+
assert.match(server, /const ICON_FRAME = '<rect x="3" y="3" width="18" height="18" rx="2" \/>'/,
|
|
43
|
+
"Tabler's own frame, the one columns-2 and columns-3 are built on");
|
|
44
|
+
assert.match(server, /stroke-width="1\.9" stroke-linecap="round" stroke-linejoin="round"/,
|
|
45
|
+
'a stroked outline like the sprite, not a filled blob');
|
|
46
|
+
assert.match(server, /function viewIcon\(view\)/);
|
|
47
|
+
assert.match(server, /Array\.from\(\{ length: n - 1 \}/,
|
|
48
|
+
'N columns are N-1 dividers, drawn from the count rather than picked from a set');
|
|
49
|
+
assert.match(server, /function widthIcon\(width\)/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('a choice is remembered for the whole dashboard, not one workspace', () => {
|
|
53
|
+
assert.match(ui, /wsPrefSet\(btn\.dataset\.pref, btn\.dataset\.view\)/,
|
|
54
|
+
'through the hub-domain cookie, like the theme');
|
|
55
|
+
assert.match(ui, /allowed\.includes\(btn\.dataset\.view\)/, 'and only a value we render');
|
|
56
|
+
// The rows arrive in fragments, so the marking has to be redone after a swap.
|
|
57
|
+
assert.match(ui, /document\.addEventListener\('htmx:after:settle', markCurrentViews\)/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('the card does not depend on UIKit utilities for its layout', () => {
|
|
61
|
+
// uk-text-center and uk-display-block are utilities and carry !important: with them on the card the
|
|
62
|
+
// list view could not turn it into a row at all.
|
|
63
|
+
const card = server.slice(server.indexOf('class="uk-card uk-card-default uk-card-hover'), server.indexOf('class="uk-card uk-card-default uk-card-hover') + 200);
|
|
64
|
+
assert.ok(!/uk-text-center|uk-display-block/.test(card), 'those belong in the stylesheet now');
|
|
65
|
+
assert.match(css, /\.workspace-card \{ display: block; text-align: center; \}/, 'and here is what they did');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('the grids collapse where a column would be a sliver', () => {
|
|
69
|
+
assert.match(css, /@media \(max-width: 63\.99em\)/);
|
|
70
|
+
const narrow = css.slice(css.indexOf('@media (max-width: 63.99em)'));
|
|
71
|
+
assert.match(narrow, /html\[data-rows-view="grid-3"\] #webship-workspace-projects \{ grid-template-columns: 1fr; \}/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
test('the width control is on every page, exactly once', () => {
|
|
76
|
+
// It used to be a row in the rail, which every page has. Moved beside the grid control, it has to
|
|
77
|
+
// reach the pages that have no grid control — and must not appear twice on the ones that do.
|
|
78
|
+
assert.match(server, /function widthSwitcherHtml\(\)/);
|
|
79
|
+
const heads = (server.match(/page-heading">\$\{widthSwitcherHtml\(\)\}/g) || []).length;
|
|
80
|
+
assert.ok(heads >= 4, `only ${heads} page headings carry it`);
|
|
81
|
+
// A workspace of projects carries it beside its rows, so its heading must not repeat it — and a
|
|
82
|
+
// workspace of FILES has no such row, so there the heading is where it goes.
|
|
83
|
+
assert.match(server, /\$\{meta\.kind === 'files' \? widthSwitcherHtml\(\) : ''\}/,
|
|
84
|
+
'the workspace page decides by kind, or it is either missing or doubled');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('the list is separated from the Build form, not flush against it', () => {
|
|
88
|
+
// Measured before the fix: on /dev, /test and /projects the form's last control and "Projects · how
|
|
89
|
+
// to lay them out" touched at ZERO pixels, so one question ran straight into the next. A rule as well
|
|
90
|
+
// as the space, because they are two different things to do rather than one thing with a gap in it.
|
|
91
|
+
const rule = css.slice(css.indexOf('.build-form + #webship-workspace-projects'));
|
|
92
|
+
assert.ok(css.includes('.build-form + #webship-workspace-projects'), 'the pairing is what is spaced');
|
|
93
|
+
assert.match(rule, /margin-top: 1\.75rem;/);
|
|
94
|
+
assert.match(rule, /padding-top: 1\.5rem;/);
|
|
95
|
+
assert.match(rule, /border-top: 1px solid var\(--line\);/, 'the hairline every card here uses');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('a workspace with no Build form is not given a rule to nothing', () => {
|
|
99
|
+
// A files workspace has no form above the list, so the pairing simply does not match there —
|
|
100
|
+
// which is the point of spacing the RELATIONSHIP rather than the element.
|
|
101
|
+
assert.ok(!/^#webship-workspace-projects \{[^}]*border-top/m.test(css),
|
|
102
|
+
'the wrapper itself carries no border of its own');
|
|
103
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The image offers `twg` on PATH through a wrapper that execs the host's binary from the mounted bin
|
|
3
|
+
* directory: a single-file bind mount binds an inode, so `twg update` on the host would never reach a
|
|
4
|
+
* container that mounted the file itself. The Jira plugin mounts the directory; the wrapper is the
|
|
5
|
+
* image's half of that arrangement, and a machine without twg is told so.
|
|
6
|
+
*/
|
|
7
|
+
const { test } = require('node:test');
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const dockerfile = fs.readFileSync(path.join(__dirname, '../../.ddev/web-build/Dockerfile.workspace-app'), 'utf8');
|
|
13
|
+
|
|
14
|
+
test('PATH finds twg through a wrapper that execs whatever the host has now', () => {
|
|
15
|
+
assert.match(dockerfile, /exec \/host-local-bin\/twg "\$@"/);
|
|
16
|
+
assert.match(dockerfile, /chmod 0755 \/usr\/local\/bin\/twg/);
|
|
17
|
+
assert.match(dockerfile, /twg is not installed on the host/, 'a machine without twg says so');
|
|
18
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every `pattern` attribute has to be a regular expression the BROWSER accepts.
|
|
3
|
+
*
|
|
4
|
+
* HTML compiles `pattern` with the `v` flag, which is stricter inside a character class than anything
|
|
5
|
+
* Node applies by default: a bare `-` or `/` there is a SyntaxError. The browser then throws, drops
|
|
6
|
+
* the attribute, and the field silently has no validation at all — while every render logs
|
|
7
|
+
* "Pattern attribute value … is not a valid regular expression".
|
|
8
|
+
*
|
|
9
|
+
* Found by opening a page with the console open, which is the only place it can be found: the server
|
|
10
|
+
* renders the attribute perfectly happily, and Node's own RegExp accepts it too.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { test } = require('node:test');
|
|
14
|
+
const assert = require('node:assert');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const APP = path.join(__dirname, '../..');
|
|
19
|
+
|
|
20
|
+
test('every pattern in the app compiles the way the browser compiles it', () => {
|
|
21
|
+
const files = ['server.js', 'screens.js', 'commands-page.js', 'ddev-addons-page.js', 'ddev-import.js',
|
|
22
|
+
'build-order-page.js', 'navigation-page.js'].filter((f) => fs.existsSync(path.join(APP, f)));
|
|
23
|
+
const bad = [];
|
|
24
|
+
for (const f of files) {
|
|
25
|
+
const src = fs.readFileSync(path.join(APP, f), 'utf8');
|
|
26
|
+
for (const m of src.matchAll(/pattern="([^"]+)"/g)) {
|
|
27
|
+
// A template expression is checked where its values are DEFINED (below) rather than guessed at
|
|
28
|
+
// here: this test reads source, and `${esc(lm.pattern || '…')}` has no value until it runs.
|
|
29
|
+
if (m[1].includes('${')) continue;
|
|
30
|
+
// What reaches the browser: the JS source escapes a backslash, the HTML attribute does not.
|
|
31
|
+
const asServed = m[1].replace(/\\\\/g, '\\');
|
|
32
|
+
try { new RegExp(asServed, 'v'); } catch (_) { bad.push(`${f}: ${m[1]}`); }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
assert.deepEqual(bad, [], 'a pattern the browser cannot compile is a field with no validation');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('the patterns a list editor can emit compile too', () => {
|
|
39
|
+
// These reach the same attribute through `${esc(lm.pattern || '…')}`, so they are the other half of
|
|
40
|
+
// the check above — and the fallback is the one every list without its own pattern gets.
|
|
41
|
+
const src = fs.readFileSync(path.join(APP, 'server.js'), 'utf8')
|
|
42
|
+
+ fs.readFileSync(path.join(APP, 'lists.js'), 'utf8');
|
|
43
|
+
const candidates = [...src.matchAll(/pattern: '([^']+)'/g)].map((m) => m[1]);
|
|
44
|
+
candidates.push("[a-zA-Z0-9_.\\-]+"); // the fallback, spelled as the source spells it
|
|
45
|
+
const bad = [];
|
|
46
|
+
for (const p of candidates) {
|
|
47
|
+
const asServed = p.replace(/\\\\/g, '\\');
|
|
48
|
+
try { new RegExp(asServed, 'v'); } catch (_) { bad.push(p); }
|
|
49
|
+
}
|
|
50
|
+
assert.deepEqual(bad, []);
|
|
51
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Web IDE's infrastructure stays with the dashboard image: code-server as a supervised daemon
|
|
3
|
+
* bound to loopback, and the nginx vhost that proxies the ide. host to it. The menu that opens it
|
|
4
|
+
* is the IDE plugin's (@webship.co/workspace-ide), which expects this same address.
|
|
5
|
+
*/
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const { test } = require('node:test');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const BIND = '127.0.0.1:8443';
|
|
12
|
+
|
|
13
|
+
test('the start script binds code-server to loopback only, where the plugin and nginx expect it', () => {
|
|
14
|
+
const sh = fs.readFileSync(path.join(__dirname, '..', '..', 'scripts', 'code-server-start.sh'), 'utf8');
|
|
15
|
+
assert.ok(sh.includes(`--bind-addr ${BIND}`), 'the start script binds elsewhere');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('the daemon runs that script', () => {
|
|
19
|
+
const cfg = fs.readFileSync(path.join(__dirname, '..', '..', '.ddev', 'config.yaml'), 'utf8');
|
|
20
|
+
assert.match(cfg, /name: "code-server"/);
|
|
21
|
+
assert.match(cfg, /command: "bash scripts\/code-server-start\.sh"/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('nginx proxies the IDE hostname to that same port, with websocket upgrade', () => {
|
|
25
|
+
const conf = fs.readFileSync(path.join(__dirname, '..', '..', '.ddev', 'nginx_full', 'nginx-site.conf'), 'utf8');
|
|
26
|
+
assert.ok(conf.includes(`proxy_pass http://${BIND}`), 'vhost points elsewhere');
|
|
27
|
+
assert.match(conf, /proxy_set_header Upgrade/);
|
|
28
|
+
});
|