@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,69 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { tabModalHtml } = require('../../tab-modal');
|
|
6
|
+
|
|
7
|
+
const root = path.join(__dirname, '..', '..');
|
|
8
|
+
const read = (...p) => fs.readFileSync(path.join(root, ...p), 'utf8');
|
|
9
|
+
const groups = [
|
|
10
|
+
{ id: 'a', label: 'First', tabs: [
|
|
11
|
+
{ id: 'one', label: 'One', url: '/x/one', hint: 'The first' },
|
|
12
|
+
{ id: 'two', label: 'Two <b>', url: '/x/two', badge: '3' } ] },
|
|
13
|
+
{ id: 'b', label: 'Second', tabs: [{ id: 'three', label: 'Three', url: '/x/three' }] },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
test('tabs are buttons in a vertical tablist, one selected, each loading its own fragment', () => {
|
|
17
|
+
const html = tabModalHtml({ id: 'demo', title: 'Demo', groups, active: 'two' });
|
|
18
|
+
assert.match(html, /role="tablist" aria-orientation="vertical"/);
|
|
19
|
+
assert.equal((html.match(/role="tab"/g) || []).length, 3);
|
|
20
|
+
assert.equal((html.match(/aria-selected="true"/g) || []).length, 1);
|
|
21
|
+
assert.match(html, /id="tm-demo-two"[^>]*aria-selected="true"/);
|
|
22
|
+
assert.match(html, /hx-get="\/x\/three" hx-target="#tm-demo-pane"/);
|
|
23
|
+
assert.match(html, /id="tm-demo-pane" role="tabpanel"/);
|
|
24
|
+
assert.match(html, /hx-get="\/x\/two" hx-trigger="load"/, 'the pane loads the open tab');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('an unknown tab opens the first, and labels are escaped', () => {
|
|
28
|
+
const html = tabModalHtml({ id: 'demo', title: 'Demo', groups, active: 'nope' });
|
|
29
|
+
assert.match(html, /id="tm-demo-one"[^>]*aria-selected="true"/);
|
|
30
|
+
assert.doesNotMatch(html, /Two <b>/);
|
|
31
|
+
assert.match(html, /Two <b>/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('the address is kept only when the modal asks for it, and a back link is a button', () => {
|
|
35
|
+
assert.doesNotMatch(tabModalHtml({ id: 'demo', title: 'Demo', groups }), /data-hash/);
|
|
36
|
+
assert.match(tabModalHtml({ id: 'demo', title: 'Demo', groups, hash: true }), /data-tabmodal="demo" data-hash/);
|
|
37
|
+
const back = tabModalHtml({ id: 'demo', title: 'Demo', groups, back: { label: 'Workitems', url: '/list' } });
|
|
38
|
+
assert.match(back, /<button type="button" class="tabmodal-back" hx-get="\/list"/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('the settings modal is built from it, and plugins get it through api.lib', () => {
|
|
42
|
+
const server = read('server.js');
|
|
43
|
+
assert.match(server, /tabModal\.tabModalHtml\(\{ id: 'settings'/);
|
|
44
|
+
assert.match(read('plugin-loader.js'), /tabModalHtml,/, 'a plugin (the Jira workitem modal) builds on the same');
|
|
45
|
+
assert.match(server, /const isFragment = \(req\)/);
|
|
46
|
+
assert.match(server, /if \(options\.fragment\) return/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('the rail Settings tile opens the modal and keeps its address for a new tab', () => {
|
|
50
|
+
const server = read('server.js');
|
|
51
|
+
assert.match(server, /'\/fragments\/settings\/modal'\)/);
|
|
52
|
+
assert.match(server, /data-tabmodal-open=/);
|
|
53
|
+
const ui = read('public', 'ui.js');
|
|
54
|
+
assert.match(ui, /\[data-tabmodal-open\]/);
|
|
55
|
+
assert.match(ui, /\^#\(settings\)\\\/\(\[\^\/\]\+\)\$/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('behaviour: selection, arrow keys, a filter, links stay in the pane', () => {
|
|
59
|
+
const ui = read('public', 'ui.js');
|
|
60
|
+
assert.match(ui, /'ArrowDown', 'ArrowUp', 'Home', 'End'/);
|
|
61
|
+
assert.match(ui, /tabmodal-filter/);
|
|
62
|
+
assert.match(ui, /\.tabmodal-pane a\[href\^="\/settings"\]/);
|
|
63
|
+
assert.match(ui, /aria-selected/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('the styles use tokens only', () => {
|
|
67
|
+
const css = read('public', 'css', 'components', 'tab-modal.css');
|
|
68
|
+
assert.doesNotMatch(css, /#[0-9a-fA-F]{3,8}\b/);
|
|
69
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tab says when a session is waiting.
|
|
3
|
+
*
|
|
4
|
+
* A session that stops to ask something is a run that is not happening until somebody answers, and the dashboard
|
|
5
|
+
* is usually in another tab. Vercel shows a build's state in the tab; this does the same for the one state that
|
|
6
|
+
* needs a person. Only "waiting" goes in the title: a count of detached sessions is noise.
|
|
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 ui = fs.readFileSync(path.join(__dirname, '..', '..', 'public/ui.js'), 'utf8');
|
|
15
|
+
const block = ui.slice(ui.indexOf('const BASE_TITLE = document.title;'), ui.indexOf('/* ---------------- the rail, arranged'));
|
|
16
|
+
|
|
17
|
+
test('the page title is kept, and a waiting session is put in front of it', () => {
|
|
18
|
+
assert.match(block, /const BASE_TITLE = document\.title;/);
|
|
19
|
+
assert.match(block, /document\.title = `✋ \$\{n\} waiting · \$\{BASE_TITLE\}`;/);
|
|
20
|
+
assert.match(block, /if \(!w\) \{ document\.title = BASE_TITLE; return; \}/, 'and it goes back when nothing waits');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('it reads what the toolbar already shows, rather than asking the server again', () => {
|
|
24
|
+
assert.match(block, /document\.querySelector\('\.screens-count \.screens-waiting'\)/);
|
|
25
|
+
assert.match(block, /replace\(\/\[\^0-9\]\/g, ''\)/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('it runs whenever the badge changes, however it was rewritten, and once at load', () => {
|
|
29
|
+
assert.match(block, /new MutationObserver\(syncTitleState\)\.observe\(screensCountEl, \{ childList: true, subtree: true, characterData: true \}\)/);
|
|
30
|
+
assert.match(block, /document\.body\.addEventListener\('htmx:after:swap'/, 'the swap event still counts too');
|
|
31
|
+
assert.match(block, /\nsyncTitleState\(\);\n/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('the fragment it reads marks a waiting session, and only then', () => {
|
|
35
|
+
const server = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
36
|
+
assert.match(server, /waiting\.length \? ` <span class="uk-label uk-label-warning screens-waiting"/);
|
|
37
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const APP = path.join(__dirname, '..', '..');
|
|
8
|
+
const FILES = ['server.js', 'screens.js', 'jobs.js', 'assistant.js', 'ai-items.js', 'ddev.js'];
|
|
9
|
+
|
|
10
|
+
// A backtick inside an HTML comment inside a template literal ENDS THE LITERAL and takes the
|
|
11
|
+
// whole app down with a syntax error. CLAUDE.md warns about it; it has still happened three
|
|
12
|
+
// times, because the comment reads perfectly well to a person and the failure appears
|
|
13
|
+
// hundreds of lines away.
|
|
14
|
+
//
|
|
15
|
+
// This is the cheap guard: an HTML comment inside these files must not contain a backtick.
|
|
16
|
+
// It costs one rule — write `update.sh --check` as update.sh --check inside a comment — and
|
|
17
|
+
// removes a class of bug whose symptom is "the dashboard will not start".
|
|
18
|
+
test('no HTML comment carries a backtick', () => {
|
|
19
|
+
const offenders = [];
|
|
20
|
+
for (const f of FILES) {
|
|
21
|
+
const src = fs.readFileSync(path.join(APP, f), 'utf8');
|
|
22
|
+
const lines = src.split('\n');
|
|
23
|
+
let inComment = false;
|
|
24
|
+
lines.forEach((line, i) => {
|
|
25
|
+
if (line.includes('<!--')) inComment = true;
|
|
26
|
+
if (inComment && line.includes('`')) offenders.push(`${f}:${i + 1}: ${line.trim().slice(0, 70)}`);
|
|
27
|
+
if (line.includes('-->')) inComment = false;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
assert.deepEqual(offenders, [],
|
|
31
|
+
'a backtick in an HTML comment inside a template literal ends the literal — write it without one');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// The belt to that braces: whatever the rule above misses, the file must still parse. Cheap,
|
|
35
|
+
// and it is the actual property that matters.
|
|
36
|
+
test('every dashboard module parses', () => {
|
|
37
|
+
for (const f of FILES.concat(['workspaces.js', 'urls.js', 'checks.js', 'public/ui.js', 'public/suggest.js'])) {
|
|
38
|
+
execFileSync('node', ['--check', path.join(APP, f)]);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Security headers are set ONCE in the request handler, because they were previously set at
|
|
43
|
+
// three writeHead sites out of fifty-six — so every page but the graph viewer went without.
|
|
44
|
+
// A regression here is invisible in the UI, which is exactly why it needs a test.
|
|
45
|
+
test('the request handler sets the security headers centrally', () => {
|
|
46
|
+
const src = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
47
|
+
const handler = src.slice(src.indexOf('const server = http.createServer'), src.indexOf('const server = http.createServer') + 1600);
|
|
48
|
+
for (const h of ['X-Content-Type-Options', 'Referrer-Policy', 'X-Frame-Options']) {
|
|
49
|
+
assert.ok(handler.includes(h), `${h} is no longer set at the top of the request handler`);
|
|
50
|
+
}
|
|
51
|
+
assert.ok(handler.includes("'nosniff'"), 'nosniff value changed');
|
|
52
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrolling back in a terminal window.
|
|
3
|
+
*
|
|
4
|
+
* This looked impossible for a while, and the reason is worth keeping written down: the obvious
|
|
5
|
+
* implementation defeats itself. `Ctrl-A Esc` opens screen's copy mode, and then an ARROW KEY closes
|
|
6
|
+
* it again — the arrow sequence starts with Esc, and Esc in copy mode means abort — so the gesture
|
|
7
|
+
* opens and closes copy mode in one motion and types the rest of the sequence into the shell. The
|
|
8
|
+
* first attempt at this shipped that behaviour for a few minutes and was reverted: a window that
|
|
9
|
+
* appears to scroll and instead types into your session is worse than one that does not scroll.
|
|
10
|
+
*
|
|
11
|
+
* What works is screen's own movement keys, which are plain characters.
|
|
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 client = fs.readFileSync(path.join(__dirname, '../../public/terminal.js'), 'utf8');
|
|
20
|
+
const screens = fs.readFileSync(path.join(__dirname, '../../screens.js'), 'utf8');
|
|
21
|
+
|
|
22
|
+
test('xterm keeps no scrollback of its own, and says why', () => {
|
|
23
|
+
// screen holds its client on the alternate buffer whatever runs inside it, so nothing ever scrolls
|
|
24
|
+
// off into xterm: a buffer here would hold nothing and cost memory.
|
|
25
|
+
assert.match(client, /scrollback: 0,/);
|
|
26
|
+
assert.match(client, /ALTERNATE buffer/);
|
|
27
|
+
assert.match(client, /baseY stays 0/, 'measured, not assumed');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('copy mode is entered with Ctrl-A then Esc', () => {
|
|
31
|
+
assert.match(client, /const enterCopyMode = \(\) => \{ send\(String\.fromCharCode\(1\)\); send\(String\.fromCharCode\(27\)\); \};/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('the wheel moves with screen\'s own keys, never with an arrow', () => {
|
|
35
|
+
// The whole bug: an arrow starts with Esc, and Esc in copy mode aborts.
|
|
36
|
+
assert.match(client, /const HALF_UP = String\.fromCharCode\(21\);/, 'Ctrl-U');
|
|
37
|
+
assert.match(client, /const HALF_DOWN = String\.fromCharCode\(4\);/, 'Ctrl-D');
|
|
38
|
+
const wheel = client.slice(client.indexOf("el.addEventListener('wheel'"), client.indexOf('{ capture: true, passive: false }'));
|
|
39
|
+
assert.doesNotMatch(wheel, /\\x1b\[A|\\x1b\[B/, 'no arrow sequences reach a session in copy mode');
|
|
40
|
+
assert.match(client, /An ARROW KEY ABORTS IT/, 'and the reason is written where the next person will look');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('only scrolling UP opens copy mode', () => {
|
|
44
|
+
// There is nothing below the last line to reach, so a wheel-down at the bottom must not open it.
|
|
45
|
+
const wheel = client.slice(client.indexOf("el.addEventListener('wheel'"), client.indexOf('{ capture: true, passive: false }'));
|
|
46
|
+
assert.match(wheel, /if \(e\.deltaY < 0\) enterCopyMode\(\);/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('whether it is in copy mode is SCREEN\'s answer, not a hopeful flag', () => {
|
|
50
|
+
// The first version set the flag when it sent the keys, and was wrong whenever copy mode did not
|
|
51
|
+
// open — and then every wheel notch typed into the shell.
|
|
52
|
+
assert.match(client, /if \(\/Copy mode aborted\/\.test\(text\)\) \{/);
|
|
53
|
+
assert.match(client, /\} else if \(\/Copy mode\/\.test\(text\)\) \{/);
|
|
54
|
+
assert.match(client, /never a\s*\n?\s*\/\/ flag set hopefully/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('there is a button as well as a wheel', () => {
|
|
58
|
+
assert.match(screens, /term-scrollback/);
|
|
59
|
+
assert.match(client, /const back = e\.target\.closest\('\.term-scrollback'\);/);
|
|
60
|
+
assert.match(client, /win\.__scrollBack\(\)/);
|
|
61
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How big a terminal window opens, and what happens when it is resized.
|
|
3
|
+
*
|
|
4
|
+
* Three faults, each of which made the window show the wrong thing rather than merely look wrong:
|
|
5
|
+
*
|
|
6
|
+
* 1. **It opened too narrow.** The default was `innerWidth * 0.52` capped at 860px, which on a
|
|
7
|
+
* 1100px screen is 572px — 70 columns. 80x24 is what every TUI assumes, so everything inside
|
|
8
|
+
* folded, and wrapped output reads as a broken program rather than as a small window.
|
|
9
|
+
* 2. **A resize did not reach the session.** The PTY is sized once, by the `stty` the server runs
|
|
10
|
+
* before `screen -x`, and every socket message goes to the session's stdin — so there is no
|
|
11
|
+
* resize channel to use. xterm re-fitted locally while screen went on painting for the old width.
|
|
12
|
+
* 3. **Attaches leaked.** `SIGHUP` to `script` left the `screen -x` client behind, one per window
|
|
13
|
+
* and then one per resize — and screen fits a window to EVERY attached display, so a pile of
|
|
14
|
+
* stale clients pins the session to the smallest one and the size can never be corrected.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { test } = require('node:test');
|
|
18
|
+
const assert = require('node:assert');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const client = fs.readFileSync(path.join(__dirname, '../../public/terminal.js'), 'utf8');
|
|
23
|
+
const server = fs.readFileSync(path.join(__dirname, '../../server.js'), 'utf8');
|
|
24
|
+
const screens = fs.readFileSync(path.join(__dirname, '../../screens.js'), 'utf8');
|
|
25
|
+
|
|
26
|
+
test('the sizes offered are the ones the Ubuntu terminal offers', () => {
|
|
27
|
+
// A terminal is measured in columns and rows: 132x43 means something to anyone who has used one,
|
|
28
|
+
// and "1057 by 741" means nothing. These four are GNOME/Ubuntu's own profile presets, so the list
|
|
29
|
+
// is familiar rather than invented here.
|
|
30
|
+
assert.match(client, /const PRESETS = \[\s*\{ cols: 80, rows: 24 \},\s*\{ cols: 80, rows: 43 \},\s*\{ cols: 132, rows: 24 \},\s*\{ cols: 132, rows: 43 \},/);
|
|
31
|
+
// The menu is one loop over the same four, so assert the LIST and the template rather than the
|
|
32
|
+
// rendered strings — matching my own interpolation would test nothing.
|
|
33
|
+
assert.match(screens, /\[\[80, 24\], \[80, 43\], \[132, 24\], \[132, 43\]\]/);
|
|
34
|
+
assert.match(screens, /data-term-size="\$\{c\}x\$\{r\}"/);
|
|
35
|
+
assert.match(screens, /data-term-size="fit"/, 'and the default, said in words');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('a window opens at 132x43, clamped to the display', () => {
|
|
39
|
+
// 80x24 is the floor, not the default — it is cramped for a build log or an agent's output, which is
|
|
40
|
+
// most of what these windows hold. And NOT "the biggest that fits", which was tidy and wrong: the
|
|
41
|
+
// window is draggable and resizable, so a few pixels past the edge is something you move, while a
|
|
42
|
+
// terminal that opened too small is something you have to notice and correct. Measured: 1700x1000
|
|
43
|
+
// opens 132x43 and 1100x800 clamps to 129x42, both fully on screen.
|
|
44
|
+
assert.match(client, /const DEFAULT_PRESET = \{ cols: 132, rows: 43 \};/);
|
|
45
|
+
assert.match(client, /function defaultPreset\(\) \{ return DEFAULT_PRESET; \}/);
|
|
46
|
+
// From a measured cell, because the font is the user's monospace stack at whatever size the browser
|
|
47
|
+
// gives it — a character width is not something this file can know.
|
|
48
|
+
assert.match(client, /term\._core\._renderService\.dimensions\.css\.cell/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('a size somebody chose is never overridden', () => {
|
|
52
|
+
// The correction applies to a window that merely opened narrow, not to one deliberately made small.
|
|
53
|
+
assert.match(client, /if \(!pane\.dataset\.geom && term\.cols < defaultPreset\(\)\.cols\)/);
|
|
54
|
+
assert.match(client, /if \(remembered\) win\.dataset\.geom = '1';/);
|
|
55
|
+
assert.match(client, /win\.dataset\.geom = '1';\s*\/\/ it has a size somebody chose now/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('a resize re-attaches, because there is no resize channel', () => {
|
|
59
|
+
assert.match(client, /const wire = \(\) => \{/);
|
|
60
|
+
assert.match(client, /pane\.__resized = resized;/);
|
|
61
|
+
// Debounced, and only when the size in CHARACTERS changed: two pixels is not a reconnect.
|
|
62
|
+
assert.match(client, /if \(term\.cols === attachedAt\.cols && term\.rows === attachedAt\.rows\) return;/);
|
|
63
|
+
// The debounce body is long enough that a bounded [\s\S] window is a test about its length rather
|
|
64
|
+
// than its behaviour; assert the two things that matter instead.
|
|
65
|
+
assert.match(client, /resizeTimer = setTimeout\(/);
|
|
66
|
+
const body = client.slice(client.indexOf('resizeTimer = setTimeout('));
|
|
67
|
+
assert.match(body.slice(0, 700), /wire\(\);/, 'and it re-attaches');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('the old attach detaches itself before the new one opens', () => {
|
|
71
|
+
// Ctrl-A d is screen's own detach for THIS display. Without it the size correction is defeated by
|
|
72
|
+
// the clients left behind.
|
|
73
|
+
assert.match(client, /ws\.send\(String\.fromCharCode\(1\)\); ws\.send\('d'\);/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('the server adapts the window and reaps the whole attach', () => {
|
|
77
|
+
assert.match(server, /exec screen -A -x \$\{session\}/, '-A adapts the window to this display');
|
|
78
|
+
assert.match(server, /detached: true/, 'its own process group');
|
|
79
|
+
assert.match(server, /process\.kill\(-child\.pid, 'SIGHUP'\)/, 'so the group can be killed, not just script');
|
|
80
|
+
assert.match(server, /screen fits a window to EVERY attached display/, 'and the reason is written down');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('reset lands on the same size a window opens at', () => {
|
|
84
|
+
// The menu entry and the double-click go to the default, and the label says which size that is —
|
|
85
|
+
// "biggest that fits this display" described the old behaviour and would now be a lie.
|
|
86
|
+
assert.match(screens, /Reset to 132 × 43/);
|
|
87
|
+
assert.match(screens, /term-fit/);
|
|
88
|
+
assert.match(client, /const sizePick = e\.target\.closest\('\[data-term-size\]'\);/);
|
|
89
|
+
assert.match(client, /Double-click the title bar puts a window back/);
|
|
90
|
+
// And the stored geometry is forgotten, or it would come back the next time it opens.
|
|
91
|
+
assert.match(client, /delete all\[win\.dataset\.session\];/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('nothing but a real child may go in termChildren', () => {
|
|
95
|
+
// It is BOTH the shutdown list and what MAX_TERMINALS counts. A helper object was added to it so
|
|
96
|
+
// shutdown could kill the process group — it was never removed, so after eight opens every further
|
|
97
|
+
// WebSocket upgrade answered 503 and the terminal opened blank. The group kill belongs in the
|
|
98
|
+
// shutdown loop, over the real children.
|
|
99
|
+
const adds = [...server.matchAll(/termChildren\.add\(([^)]*)\)/g)].map((m) => m[1].trim());
|
|
100
|
+
assert.deepEqual(adds, ['child'], 'only the child itself');
|
|
101
|
+
assert.match(server, /for \(const c of termChildren\) \{[\s\S]{0,200}process\.kill\(-c\.pid, 'SIGHUP'\)/);
|
|
102
|
+
assert.match(server, /it is also what MAX_TERMINALS counts, so nothing else may ever be put in it/);
|
|
103
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a terminal looks: the Ubuntu face and palette by default, a larger text size, and a style
|
|
3
|
+
* the user can change the way the dashboard's own style is changed.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { test } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const PUBLIC = path.join(__dirname, '../../public');
|
|
12
|
+
const client = fs.readFileSync(path.join(PUBLIC, 'terminal.js'), 'utf8');
|
|
13
|
+
const base = fs.readFileSync(path.join(PUBLIC, 'css/base.css'), 'utf8');
|
|
14
|
+
const css = fs.readFileSync(path.join(PUBLIC, 'css/components/terminals.css'), 'utf8');
|
|
15
|
+
const { TERM_STYLES, termPaneHtml } = require('../../screens.js');
|
|
16
|
+
|
|
17
|
+
test('the face is Ubuntu Mono, vendored with its licence and waited for before xterm measures', () => {
|
|
18
|
+
assert.ok(fs.existsSync(path.join(PUBLIC, 'vendor/fonts/ubuntu-mono-latin-wght-normal.woff2')));
|
|
19
|
+
assert.ok(fs.existsSync(path.join(PUBLIC, 'vendor/fonts/ubuntu-mono-LICENSE.txt')));
|
|
20
|
+
assert.match(base, /font-family: 'Ubuntu Mono';[\s\S]{0,120}font-weight: 400 700;/);
|
|
21
|
+
assert.match(client, /const TERM_FONT = '"Ubuntu Mono", /);
|
|
22
|
+
assert.match(client, /document\.fonts\.load\(/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('the default text size is larger than the 13px it was', () => {
|
|
26
|
+
const m = client.match(/const FONT_DEFAULT = (\d+);/);
|
|
27
|
+
assert.ok(m && Number(m[1]) >= 16, 'at least the 16px of the Ubuntu terminal');
|
|
28
|
+
assert.match(client, /fontSize: look\.size,/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('the menu offers exactly the styles terminal.js has a palette for', () => {
|
|
32
|
+
const ids = TERM_STYLES.map(([id]) => id);
|
|
33
|
+
const order = (client.match(/const STYLE_ORDER = \[([^\]]+)\]/) || [])[1] || '';
|
|
34
|
+
assert.deepEqual(order.split(',').map((s) => s.trim().replace(/'/g, '')), ids);
|
|
35
|
+
assert.equal(ids[0], 'ubuntu', 'Ubuntu is the default and the first');
|
|
36
|
+
const html = termPaneHtml('s');
|
|
37
|
+
for (const id of ids) assert.match(html, new RegExp(`data-term-style="${id}"`));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('text size: buttons, Ctrl and plus, minus and zero, and Ctrl and the wheel', () => {
|
|
41
|
+
const html = termPaneHtml('s');
|
|
42
|
+
for (const how of ['down', 'up', 'reset']) assert.match(html, new RegExp(`data-term-font="${how}"`));
|
|
43
|
+
assert.match(client, /attachCustomKeyEventHandler/);
|
|
44
|
+
assert.match(client, /if \(e\.ctrlKey\) \{ stepFont\(/);
|
|
45
|
+
assert.match(client, /const FONT_MIN = 11;/);
|
|
46
|
+
assert.match(client, /const FONT_MAX = 32;/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('the look is a per-browser preference applied to every open window', () => {
|
|
50
|
+
assert.match(client, /const FONT_KEY = 'ws-term-font';/);
|
|
51
|
+
assert.match(client, /const STYLE_KEY = 'ws-term-style';/);
|
|
52
|
+
assert.match(client, /function paintLook\(\) \{[\s\S]{0,260}windows\(\)\.forEach/);
|
|
53
|
+
assert.match(client, /the Dashboard style is read from the page's tokens|The Dashboard style is read from the page's tokens/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('the window wears the palette, set as properties and never written as colours', () => {
|
|
57
|
+
for (const v of ['--term-bg', '--term-fg', '--term-bar', '--term-bar-ink']) {
|
|
58
|
+
assert.ok(css.includes(`var(${v}`), `${v} is read by the stylesheet`);
|
|
59
|
+
assert.ok(client.includes(`'${v}'`), `${v} is set by terminal.js`);
|
|
60
|
+
}
|
|
61
|
+
assert.doesNotMatch(css, /#[0-9a-fA-F]{3,8}\b/, 'no hex in a component');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('the header wraps and the buttons keep one square size at every width', () => {
|
|
65
|
+
assert.match(css, /\.term-window-head \{[^}]*flex-wrap: wrap;/);
|
|
66
|
+
assert.match(css, /@container term \(min-width: 56rem\)/);
|
|
67
|
+
assert.match(css, /container: term \/ inline-size;/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('a window never opens taller than the room under its top edge', () => {
|
|
71
|
+
assert.match(client, /window\.innerHeight - top - 8/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('a click inside a window is only taken by the look menu, because the window itself carries the style', () => {
|
|
75
|
+
assert.match(client, /closest\('\.term-look-menu \[data-term-style\]'\)/);
|
|
76
|
+
assert.match(client, /closest\('\.term-look-menu \[data-term-font\]'\)/);
|
|
77
|
+
assert.doesNotMatch(client, /closest\('\[data-term-style\]'\)/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('a terminal opened from the Screens modal closes the modal first, so the keyboard reaches it', () => {
|
|
81
|
+
assert.match(client, /modal && modal\.contains\(open\) && window\.UIkit/);
|
|
82
|
+
assert.match(client, /window\.UIkit\.modal\(modal\)\.hide\(\)/);
|
|
83
|
+
assert.match(client, /__term\.focus\(\)/);
|
|
84
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screen terminals: floating windows, several at once.
|
|
3
|
+
*
|
|
4
|
+
* Most of this is about the two things that made only ONE terminal possible, both of which look fine
|
|
5
|
+
* in isolation and neither of which a page renders differently:
|
|
6
|
+
*
|
|
7
|
+
* - the markup addressed its parts by ID, so a second terminal's script found the first one's status
|
|
8
|
+
* line, buttons and mount point;
|
|
9
|
+
* - the mount waited for `offsetParent`, which is null for a `position: fixed` element by
|
|
10
|
+
* definition — so with the panes turned into floating windows, every one of them reported itself
|
|
11
|
+
* hidden and none ever connected.
|
|
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 { termPaneHtml, termLayerHtml } = require('../../screens');
|
|
20
|
+
|
|
21
|
+
const app = (f) => fs.readFileSync(path.join(__dirname, '../..', f), 'utf8');
|
|
22
|
+
const server = app('server.js');
|
|
23
|
+
const client = app('public/terminal.js');
|
|
24
|
+
const css = app('public/css/components/terminals.css');
|
|
25
|
+
const screensJs = app('screens.js');
|
|
26
|
+
const screenActions = app('actions/screens.js');
|
|
27
|
+
|
|
28
|
+
const html = termPaneHtml('dev--blog1--claude-1234');
|
|
29
|
+
|
|
30
|
+
test('a terminal is a window of its own, named by the session', () => {
|
|
31
|
+
assert.match(html, /class="term-window" data-session="dev--blog1--claude-1234"/);
|
|
32
|
+
assert.match(html, /role="dialog"/, 'it is a window, and says so to a screen reader');
|
|
33
|
+
assert.match(html, /aria-label="Terminal — dev--blog1--claude-1234"/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('nothing in a terminal is addressed by id', () => {
|
|
37
|
+
// The whole reason only one could be open: a second window found the first one's elements.
|
|
38
|
+
assert.doesNotMatch(html, /\sid="/, 'ids are what two windows collide over');
|
|
39
|
+
for (const part of ['term-screen', 'term-status', 'term-reconnect', 'term-fullscreen', 'term-close']) {
|
|
40
|
+
assert.ok(html.includes(`class="${part}"`) || html.includes(`${part}`), `${part} is a class`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('the session\'s own verbs are on the window, not only on the row behind it', () => {
|
|
45
|
+
// Acting on the session you are watching used to start with closing the terminal you were watching
|
|
46
|
+
// it in.
|
|
47
|
+
assert.match(html, /term-interrupt/);
|
|
48
|
+
assert.match(html, /term-detach/);
|
|
49
|
+
assert.match(html, /term-kill/);
|
|
50
|
+
assert.match(html, /Interrupt what runs in dev--blog1--claude-1234/);
|
|
51
|
+
assert.match(html, /Kill the session dev--blog1--claude-1234/);
|
|
52
|
+
// And the window's own controls, which are a different thing — hence the divider between them.
|
|
53
|
+
assert.match(html, /term-verbs-sep/);
|
|
54
|
+
assert.match(html, /Close this terminal \(dev--blog1--claude-1234 keeps running\)/,
|
|
55
|
+
'closing a window must never read as ending the session');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('every verb that ends something goes through the one confirm dialog', () => {
|
|
59
|
+
for (const verb of ['term-interrupt', 'term-detach', 'term-kill']) {
|
|
60
|
+
const at = html.indexOf(verb);
|
|
61
|
+
assert.ok(at > 0, `${verb} is there`);
|
|
62
|
+
const button = html.slice(at, html.indexOf('</button>', at));
|
|
63
|
+
assert.match(button, /arm-step/, `${verb} is armed`);
|
|
64
|
+
}
|
|
65
|
+
// The two that go to the server fire on the dialog's Yes alone.
|
|
66
|
+
assert.match(html.slice(html.indexOf('term-detach')), /hx-trigger="confirmed-remove"/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('Ctrl-C goes down the window\'s own socket, not to an endpoint that cannot reach the host', () => {
|
|
70
|
+
// onHostScreen says it itself: "the runner does not proxy them yet" — so /actions/screen-interrupt
|
|
71
|
+
// answers 500 for a session on the host, which is every session. The window is already attached.
|
|
72
|
+
assert.match(screensJs, /the runner does not\s+proxy them yet/,
|
|
73
|
+
'the reason is written down where the button is made');
|
|
74
|
+
const button = html.slice(html.indexOf('term-interrupt'), html.indexOf('</button>', html.indexOf('term-interrupt')));
|
|
75
|
+
assert.doesNotMatch(button, /hx-post/, 'it does not post anywhere');
|
|
76
|
+
assert.match(client, /String\.fromCharCode\(3\)/, 'terminal.js sends the byte');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('a killed session closes its window', () => {
|
|
80
|
+
// A terminal left open on a dead session is one you can still type into, with nothing on the end.
|
|
81
|
+
assert.match(screenActions, /HX-Trigger.*screen-killed/s);
|
|
82
|
+
assert.match(client, /screen-killed/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('the mount tests the RECTANGLE, never offsetParent', () => {
|
|
86
|
+
// A fixed element's offsetParent is null by definition, so the obvious test reports every floating
|
|
87
|
+
// terminal as hidden and none of them ever connects. This one cost a debugging round.
|
|
88
|
+
assert.match(client, /getBoundingClientRect\(\);\s*\n\s*return r\.width > 0 && r\.height > 0/);
|
|
89
|
+
// Not a blunt search for the word: the comment above the fix names it, which is the point of it.
|
|
90
|
+
assert.doesNotMatch(client, /\.offsetParent\s*(!==|===)/,
|
|
91
|
+
'offsetParent cannot answer this for a fixed window, so nothing may branch on it');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('a window declares its own footprint', () => {
|
|
95
|
+
// Without border-box the measured rectangle was re-applied as a CONTENT width, so every drag grew
|
|
96
|
+
// the window by its padding and border — 18px a time. The rails hit the same trap.
|
|
97
|
+
assert.match(css, /\.term-window \{[^}]*box-sizing: border-box/s);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('the window remembers where it was put, and is clamped back into view', () => {
|
|
101
|
+
assert.match(client, /ws-term-windows/, 'geometry is per browser, like the theme');
|
|
102
|
+
assert.match(client, /ws-term-layout/);
|
|
103
|
+
assert.match(client, /window\.addEventListener\('resize'/,
|
|
104
|
+
'a display that got smaller must not leave a window off it');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('list and grid arrange every open window', () => {
|
|
108
|
+
assert.match(client, /function arrange\(how\)/);
|
|
109
|
+
assert.match(server, /data-term-layout="list"/);
|
|
110
|
+
assert.match(server, /data-term-layout="grid"/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('the terminals bar is in the top bar, beside Screens', () => {
|
|
114
|
+
// One glance, two facts: the sessions running on this machine, and the terminals open in this
|
|
115
|
+
// browser. It was a pill floating over the bottom right corner — a second place to look for a thing
|
|
116
|
+
// that belongs with Screens, sitting on top of whatever was in that corner of the page.
|
|
117
|
+
const group = server.slice(server.indexOf("groups.push({ id: 'screens'"));
|
|
118
|
+
const entry = group.slice(0, group.indexOf('] });'));
|
|
119
|
+
assert.match(entry, /id: 'terminals'/, 'in the same navigation group as the session count');
|
|
120
|
+
assert.match(entry, /id="term-bar"/);
|
|
121
|
+
assert.match(entry, /topbar: 'end'/);
|
|
122
|
+
// Its own entry, so it can be moved or hidden in Settings → Navigation without taking the session
|
|
123
|
+
// count with it.
|
|
124
|
+
assert.match(entry, /label: 'Terminals'/);
|
|
125
|
+
// And BEFORE it, so the pair reads inwards — the terminals you have open, then the sessions they are
|
|
126
|
+
// open on — and Screens keeps the end of the top bar, where it has always been.
|
|
127
|
+
assert.ok(entry.indexOf("id: 'terminals'") < entry.indexOf("id: 'open-screens'"),
|
|
128
|
+
'the terminals bar sits to the left of the session count');
|
|
129
|
+
assert.doesNotMatch(client, /createElement\('div'\);\s*\n\s*bar\.id/, 'the client no longer makes one');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('the bar says nothing until there is something to say', () => {
|
|
133
|
+
// The count is not the server's to know — a window exists in the browser that opened it — so the
|
|
134
|
+
// markup ships empty and hidden, and the client unhides it.
|
|
135
|
+
const group = server.slice(server.indexOf("groups.push({ id: 'screens'"));
|
|
136
|
+
assert.match(group.slice(0, group.indexOf('] });')), /id="term-bar"[^`]*hidden/);
|
|
137
|
+
assert.match(client, /bar\.hidden = !n;/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('the layer is empty until something is opened', () => {
|
|
141
|
+
assert.equal(termLayerHtml(), '<div id="term-windows"></div>');
|
|
142
|
+
assert.match(termLayerHtml(['a1']), /term-window" data-session="a1"/);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('every way in opens a window, and none of them fills the editor modal', () => {
|
|
146
|
+
// Two places in the core open a terminal — the Screens row and the SCREENS dropdown on a project —
|
|
147
|
+
// and plugins (Jira's agent button) use the same attribute; each used to replace the one modal, which
|
|
148
|
+
// is what made a second one impossible.
|
|
149
|
+
const openers = (server + screensJs).match(/data-open-terminal=/g) || [];
|
|
150
|
+
assert.ok(openers.length >= 2, `every opener is declarative (found ${openers.length})`);
|
|
151
|
+
assert.doesNotMatch(server + screensJs,
|
|
152
|
+
/screen-term\/\$\{[^}]*\}"\s+hx-target="#editor-modal-body"/,
|
|
153
|
+
'a terminal never goes into the editor modal again');
|
|
154
|
+
});
|