@1agh/maude 0.42.0 → 0.44.0
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/apps/studio/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/acp/bridge.ts +44 -10
- package/apps/studio/acp/env.ts +23 -0
- package/apps/studio/acp/login-state.ts +263 -0
- package/apps/studio/acp/plugin-bootstrap.ts +31 -25
- package/apps/studio/acp/probe.ts +152 -1
- package/apps/studio/annotations-layer.tsx +1451 -324
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +568 -24
- package/apps/studio/bin/_audio-search.mjs +140 -0
- package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
- package/apps/studio/bin/_import-asset.mjs +815 -0
- package/apps/studio/bin/_import-brand.mjs +635 -0
- package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
- package/apps/studio/bin/_import-tokens.mjs +1201 -0
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_transcribe.mjs +419 -0
- package/apps/studio/bin/_transcribe.test.mjs +80 -0
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/audio-search.sh +28 -0
- package/apps/studio/bin/generate.sh +154 -0
- package/apps/studio/bin/import-asset.sh +34 -0
- package/apps/studio/bin/import-brand.sh +29 -0
- package/apps/studio/bin/import-tokens.sh +33 -0
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +258 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/bin/transcribe.sh +39 -0
- package/apps/studio/canvas-edit.ts +656 -12
- package/apps/studio/canvas-lib.tsx +637 -10
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +2385 -699
- package/apps/studio/client/export-center.jsx +35 -1
- package/apps/studio/client/generate-dialog.jsx +352 -0
- package/apps/studio/client/github.js +18 -0
- package/apps/studio/client/inspector-controls.jsx +781 -0
- package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
- package/apps/studio/client/panels/ChatPanel.jsx +15 -33
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
- package/apps/studio/client/panels/ReadinessList.jsx +227 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +432 -0
- package/apps/studio/client/styles/3-shell-maude.css +376 -28
- package/apps/studio/client/styles/4-components.css +223 -0
- package/apps/studio/client/styles/6-acp-chat.css +86 -0
- package/apps/studio/client/tour/quick-setup-tour.js +39 -0
- package/apps/studio/commands/annotation-strokes-command.ts +13 -3
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/config.schema.json +70 -0
- package/apps/studio/context-menu.tsx +42 -8
- package/apps/studio/context.ts +16 -0
- package/apps/studio/design-setup-readiness.ts +115 -0
- package/apps/studio/dist/client.bundle.js +5572 -21
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/draw/palette.ts +34 -0
- package/apps/studio/exporters/jobs.ts +47 -5
- package/apps/studio/exporters/video.ts +24 -4
- package/apps/studio/footage/schema.test.ts +287 -0
- package/apps/studio/footage/schema.ts +662 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
- package/apps/studio/generation/adapters/gemini.ts +542 -0
- package/apps/studio/generation/adapters/groq.test.ts +80 -0
- package/apps/studio/generation/adapters/groq.ts +247 -0
- package/apps/studio/generation/audio-library.test.ts +95 -0
- package/apps/studio/generation/audio-library.ts +156 -0
- package/apps/studio/generation/captions.test.ts +78 -0
- package/apps/studio/generation/captions.ts +132 -0
- package/apps/studio/generation/download.ts +76 -0
- package/apps/studio/generation/elevenlabs.test.ts +223 -0
- package/apps/studio/generation/gemini.test.ts +350 -0
- package/apps/studio/generation/jobs.test.ts +128 -0
- package/apps/studio/generation/jobs.ts +243 -0
- package/apps/studio/generation/keys.test.ts +75 -0
- package/apps/studio/generation/keys.ts +162 -0
- package/apps/studio/generation/prefs.test.ts +95 -0
- package/apps/studio/generation/prefs.ts +82 -0
- package/apps/studio/generation/registry.ts +88 -0
- package/apps/studio/generation/types.test.ts +123 -0
- package/apps/studio/generation/types.ts +266 -0
- package/apps/studio/generation/whisper-models.test.ts +152 -0
- package/apps/studio/generation/whisper-models.ts +257 -0
- package/apps/studio/generation/whisper-spike-results.md +61 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +199 -21
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +1039 -22
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/media/intro.mp4 +0 -0
- package/apps/studio/media-commit-chain.ts +119 -0
- package/apps/studio/paths.ts +20 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/readiness.ts +108 -20
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/scaffold-design.ts +1145 -0
- package/apps/studio/server.ts +18 -2
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/_helpers.ts +15 -2
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-bridge.test.ts +34 -1
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/acp-env.test.ts +30 -0
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
- package/apps/studio/test/acp-session-plugins.test.ts +72 -1
- package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
- package/apps/studio/test/annotations-layer.test.ts +174 -0
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +50 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
- package/apps/studio/test/csrf-write-guard.test.ts +23 -1
- package/apps/studio/test/design-setup-readiness.test.ts +155 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +154 -0
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/generate-route.test.ts +106 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/import-asset-browser.test.ts +64 -0
- package/apps/studio/test/import-asset-route.test.ts +71 -0
- package/apps/studio/test/import-asset.test.ts +399 -0
- package/apps/studio/test/import-brand-browser.test.ts +88 -0
- package/apps/studio/test/import-brand-route.test.ts +120 -0
- package/apps/studio/test/import-brand.test.ts +206 -0
- package/apps/studio/test/import-tokens.test.ts +722 -0
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/media-commit-chain.test.ts +210 -0
- package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/readiness.test.ts +13 -11
- package/apps/studio/test/scaffold-design.test.ts +122 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/tour-overlay.test.tsx +16 -0
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +49 -21
- package/apps/studio/ui-prefs.ts +130 -0
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +40 -1
- package/apps/studio/use-chrome-visibility.tsx +8 -2
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +149 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/bin/maude.mjs +6 -6
- package/cli/commands/design.mjs +67 -3
- package/cli/lib/gitignore-block.mjs +2 -0
- package/cli/lib/pkg-root.mjs +107 -0
- package/cli/lib/pkg-root.test.mjs +79 -0
- package/cli/lib/reconstruct-toolset.test.mjs +194 -0
- package/cli/lib/update-check.mjs +8 -0
- package/package.json +13 -11
- package/plugins/design/dependencies.json +18 -0
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -22,7 +22,13 @@ const PLUGINS: SdkPluginConfig[] = [
|
|
|
22
22
|
|
|
23
23
|
type Meta = {
|
|
24
24
|
systemPrompt?: { append?: string };
|
|
25
|
-
claudeCode?: {
|
|
25
|
+
claudeCode?: {
|
|
26
|
+
options?: {
|
|
27
|
+
plugins?: SdkPluginConfig[];
|
|
28
|
+
settingSources?: string[];
|
|
29
|
+
settings?: { enabledPlugins?: Record<string, boolean> };
|
|
30
|
+
};
|
|
31
|
+
};
|
|
26
32
|
};
|
|
27
33
|
|
|
28
34
|
describe('newSessionParams — plugin carrier shape', () => {
|
|
@@ -79,6 +85,27 @@ describe('newSessionParams — plugin carrier shape', () => {
|
|
|
79
85
|
expect((p._meta as Meta).claudeCode?.options?.settingSources).toEqual(['user']);
|
|
80
86
|
}
|
|
81
87
|
});
|
|
88
|
+
|
|
89
|
+
// DDR-168 — the structural double-registration guard: a non-empty `plugins`
|
|
90
|
+
// must also force off any natively-loaded user-level copy of the same id via
|
|
91
|
+
// the SDK's "flag" settings layer, so the bundled copy is the ONLY one that
|
|
92
|
+
// ever loads.
|
|
93
|
+
test('with plugins: forces design@maude off via options.settings.enabledPlugins (double-registration guard)', () => {
|
|
94
|
+
const p = newSessionParams('/repo', undefined, PLUGINS);
|
|
95
|
+
expect((p._meta as Meta).claudeCode?.options?.settings?.enabledPlugins).toEqual({
|
|
96
|
+
'design@maude': false,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('empty plugins: options.settings is entirely absent (web/npm path unaffected)', () => {
|
|
101
|
+
const p = newSessionParams('/repo', 'BRIEF', []);
|
|
102
|
+
expect((p._meta as Meta).claudeCode?.options?.settings).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('undefined plugins: options.settings is entirely absent', () => {
|
|
106
|
+
const p = newSessionParams('/repo', 'BRIEF');
|
|
107
|
+
expect((p._meta as Meta).claudeCode?.options?.settings).toBeUndefined();
|
|
108
|
+
});
|
|
82
109
|
});
|
|
83
110
|
|
|
84
111
|
describe('upgrade guard — installed adapter + SDK still honor the plugins contract', () => {
|
|
@@ -129,4 +156,48 @@ describe('upgrade guard — installed adapter + SDK still honor the plugins cont
|
|
|
129
156
|
expect(src).toMatch(/SdkPluginConfig\s*=\s*\{/);
|
|
130
157
|
expect(src).toMatch(/type:\s*'local'/);
|
|
131
158
|
});
|
|
159
|
+
|
|
160
|
+
// DDR-168 — the same style of guard, for the `options.settings` mechanism the
|
|
161
|
+
// double-registration fix depends on: `settings` must NOT be among the fields
|
|
162
|
+
// the adapter overrides after the `...userProvidedOptions` spread (else our
|
|
163
|
+
// `enabledPlugins: false` override would be silently discarded), and the SDK
|
|
164
|
+
// must still declare `Settings.enabledPlugins` as an accepted field.
|
|
165
|
+
test('adapter forwards `settings` from userProvidedOptions and only ever overrides it conditionally when the caller left it unset', () => {
|
|
166
|
+
expect(existsSync(adapterEntry)).toBe(true);
|
|
167
|
+
const src = readFileSync(adapterEntry, 'utf8');
|
|
168
|
+
// Locate the `...userProvidedOptions` spread and inspect what follows it up
|
|
169
|
+
// to the `query({ options })` call. Verified live at claude-agent-acp@0.57.0:
|
|
170
|
+
// the object DOES contain a later `settings:` assignment (a
|
|
171
|
+
// CLAUDE_MODEL_CONFIG env-var fallback) — but it's gated behind
|
|
172
|
+
// `!userProvidedOptions?.settings && …`, i.e. it only fires when the caller
|
|
173
|
+
// did NOT already provide a settings object. A caller-provided `settings`
|
|
174
|
+
// (ours, carrying `enabledPlugins`) always wins. A bump that drops that
|
|
175
|
+
// guard — making the fallback unconditional — must fail here, since it
|
|
176
|
+
// would silently clobber DDR-168's double-registration override.
|
|
177
|
+
const spreadIdx = src.indexOf('...userProvidedOptions');
|
|
178
|
+
expect(spreadIdx).toBeGreaterThan(-1);
|
|
179
|
+
const queryIdx = src.indexOf('query(', spreadIdx);
|
|
180
|
+
expect(queryIdx).toBeGreaterThan(spreadIdx);
|
|
181
|
+
const window = src.slice(spreadIdx, queryIdx);
|
|
182
|
+
expect(window).toMatch(/!userProvidedOptions\?\.settings\s*&&/);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("SDK Options declares settings (Settings.enabledPlugins is the field DDR-168's guard relies on)", () => {
|
|
186
|
+
let sdkPkg: string | null = null;
|
|
187
|
+
try {
|
|
188
|
+
const realAdapterDir = dirname(realpathSync(adapterEntry));
|
|
189
|
+
sdkPkg = Bun.resolveSync('@anthropic-ai/claude-agent-sdk/package.json', realAdapterDir);
|
|
190
|
+
} catch {
|
|
191
|
+
sdkPkg = null;
|
|
192
|
+
}
|
|
193
|
+
expect(sdkPkg).not.toBeNull();
|
|
194
|
+
const dts = join(dirname(sdkPkg as string), 'sdk.d.ts');
|
|
195
|
+
expect(existsSync(dts)).toBe(true);
|
|
196
|
+
const src = readFileSync(dts, 'utf8');
|
|
197
|
+
// sdk.d.ts:1831 `settings?: Settings;` (or similarly named field on Options) +
|
|
198
|
+
// :5193 `Settings.enabledPlugins` — a bump that renames/drops either must
|
|
199
|
+
// fail here, then adapt, rather than silently reopening double-registration.
|
|
200
|
+
expect(src).toMatch(/settings\?\s*:/);
|
|
201
|
+
expect(src).toMatch(/enabledPlugins\??\s*:/);
|
|
202
|
+
});
|
|
132
203
|
});
|
|
@@ -90,6 +90,23 @@ describe('createAnnotationStrokesCommand', () => {
|
|
|
90
90
|
expect(putFn.mock.calls[1]?.[0]).toEqual([pen1]);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
test("do() passes the pre-op BEFORE as putFn's second arg; undo() passes the post-op AFTER", async () => {
|
|
94
|
+
// The live-bug regression this locks in: putStrokes needs its own
|
|
95
|
+
// command's baseline to tell a delete (an id `before` had that `next`
|
|
96
|
+
// dropped) apart from a genuinely concurrent sibling addition (an id
|
|
97
|
+
// `prev` has that this command's OWN before/next never knew about).
|
|
98
|
+
const putFn = mock(() => Promise.resolve());
|
|
99
|
+
const cmd = createAnnotationStrokesCommand({
|
|
100
|
+
before: [pen1, pen2],
|
|
101
|
+
after: [pen1],
|
|
102
|
+
putFn,
|
|
103
|
+
});
|
|
104
|
+
await cmd.do();
|
|
105
|
+
expect(putFn.mock.calls[0]).toEqual([[pen1], [pen1, pen2]]);
|
|
106
|
+
await cmd.undo();
|
|
107
|
+
expect(putFn.mock.calls[1]).toEqual([[pen1, pen2], [pen1]]);
|
|
108
|
+
});
|
|
109
|
+
|
|
93
110
|
test('default label — add 2 strokes', () => {
|
|
94
111
|
const cmd = createAnnotationStrokesCommand({
|
|
95
112
|
before: [pen1],
|
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
polygonPoints,
|
|
22
22
|
polygonVertices,
|
|
23
23
|
type RectStroke,
|
|
24
|
+
reconcileCommit,
|
|
25
|
+
reconcileForeignEcho,
|
|
26
|
+
resolveImageUploadSwap,
|
|
24
27
|
rid,
|
|
25
28
|
STICKY_PALETTE,
|
|
26
29
|
type StickyStroke,
|
|
@@ -427,6 +430,177 @@ describe('annotations-layer / strokes round-trip is stable for arrays', () => {
|
|
|
427
430
|
});
|
|
428
431
|
});
|
|
429
432
|
|
|
433
|
+
describe('annotations-layer / reconcileCommit (live-bug regression — delete must stick)', () => {
|
|
434
|
+
const a: RectStroke = {
|
|
435
|
+
id: 'a',
|
|
436
|
+
tool: 'rect',
|
|
437
|
+
color: '#000',
|
|
438
|
+
width: 2,
|
|
439
|
+
x: 0,
|
|
440
|
+
y: 0,
|
|
441
|
+
w: 10,
|
|
442
|
+
h: 10,
|
|
443
|
+
};
|
|
444
|
+
const b: RectStroke = {
|
|
445
|
+
id: 'b',
|
|
446
|
+
tool: 'rect',
|
|
447
|
+
color: '#000',
|
|
448
|
+
width: 2,
|
|
449
|
+
x: 20,
|
|
450
|
+
y: 0,
|
|
451
|
+
w: 10,
|
|
452
|
+
h: 10,
|
|
453
|
+
};
|
|
454
|
+
const c: RectStroke = {
|
|
455
|
+
id: 'c',
|
|
456
|
+
tool: 'rect',
|
|
457
|
+
color: '#000',
|
|
458
|
+
width: 2,
|
|
459
|
+
x: 40,
|
|
460
|
+
y: 0,
|
|
461
|
+
w: 10,
|
|
462
|
+
h: 10,
|
|
463
|
+
};
|
|
464
|
+
const d: RectStroke = {
|
|
465
|
+
id: 'd',
|
|
466
|
+
tool: 'rect',
|
|
467
|
+
color: '#000',
|
|
468
|
+
width: 2,
|
|
469
|
+
x: 60,
|
|
470
|
+
y: 0,
|
|
471
|
+
w: 10,
|
|
472
|
+
h: 10,
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
test('a delete stays deleted even when the rendered `prev` has not caught up yet', () => {
|
|
476
|
+
// This is exactly the reported bug: Backspace computes next = before
|
|
477
|
+
// minus the deleted id, but React's `prev` (read by the functional
|
|
478
|
+
// updater) still shows the pre-delete set. The old reconcileIncoming
|
|
479
|
+
// treated "id missing from next" as "not caught up, fold it back" —
|
|
480
|
+
// reverting every delete locally while the smaller set still went out
|
|
481
|
+
// over PUT.
|
|
482
|
+
const prev = [a, b, c]; // rendered state, hasn't caught up to the delete
|
|
483
|
+
const opBefore = [a, b, c]; // this command's own baseline
|
|
484
|
+
const next = [a, b]; // c deleted
|
|
485
|
+
expect(reconcileCommit(prev, opBefore, next)).toEqual([a, b]);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test('a genuinely concurrent sibling addition (unknown to this commit) is still folded in', () => {
|
|
489
|
+
// `prev` has `d`, which neither this commit's `opBefore` nor `next` knows
|
|
490
|
+
// about — a different in-flight commit added it after `opBefore` was
|
|
491
|
+
// captured. It must survive.
|
|
492
|
+
const prev = [a, b, d];
|
|
493
|
+
const opBefore = [a, b];
|
|
494
|
+
const next = [a, b, c]; // this commit adds c
|
|
495
|
+
expect(reconcileCommit(prev, opBefore, next)).toEqual([a, b, c, d]);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test('delete + concurrent addition combine correctly (delete wins for its own id, addition still folds in)', () => {
|
|
499
|
+
const prev = [a, b, c, d]; // rendered: hasn't caught up to the delete OR seen `d` reconciled yet
|
|
500
|
+
const opBefore = [a, b, c];
|
|
501
|
+
const next = [a, b]; // deletes c
|
|
502
|
+
expect(reconcileCommit(prev, opBefore, next)).toEqual([a, b, d]);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
describe('annotations-layer / reconcileForeignEcho (deletes must sync across tabs/peers)', () => {
|
|
507
|
+
const a: RectStroke = {
|
|
508
|
+
id: 'a',
|
|
509
|
+
tool: 'rect',
|
|
510
|
+
color: '#000',
|
|
511
|
+
width: 2,
|
|
512
|
+
x: 0,
|
|
513
|
+
y: 0,
|
|
514
|
+
w: 10,
|
|
515
|
+
h: 10,
|
|
516
|
+
};
|
|
517
|
+
const b: RectStroke = {
|
|
518
|
+
id: 'b',
|
|
519
|
+
tool: 'rect',
|
|
520
|
+
color: '#000',
|
|
521
|
+
width: 2,
|
|
522
|
+
x: 20,
|
|
523
|
+
y: 0,
|
|
524
|
+
w: 10,
|
|
525
|
+
h: 10,
|
|
526
|
+
};
|
|
527
|
+
const optimisticImage: ImageStroke = {
|
|
528
|
+
id: 'img',
|
|
529
|
+
tool: 'image',
|
|
530
|
+
x: 0,
|
|
531
|
+
y: 0,
|
|
532
|
+
w: 10,
|
|
533
|
+
h: 10,
|
|
534
|
+
href: 'blob:local-preview',
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
test('a foreign echo missing a non-ephemeral id (a real delete) is NOT reverted', () => {
|
|
538
|
+
const prev = [a, b];
|
|
539
|
+
const incoming = [a]; // peer deleted b
|
|
540
|
+
expect(reconcileForeignEcho(prev, incoming)).toEqual([a]);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test('a still-uploading local optimistic image (ephemeral href) survives a foreign echo that predates it', () => {
|
|
544
|
+
const prev = [a, optimisticImage];
|
|
545
|
+
const incoming = [a]; // the peer's broadcast was authored before our upload started
|
|
546
|
+
expect(reconcileForeignEcho(prev, incoming)).toEqual([a, optimisticImage]);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
describe('annotations-layer / resolveImageUploadSwap (delete-during-upload must not resurrect)', () => {
|
|
551
|
+
// Adversarial-review finding: dropping an image, then Backspacing it
|
|
552
|
+
// before its upload resolves, used to resurrect it once the upload
|
|
553
|
+
// landed — the swap read "id absent from `before`" as render lag
|
|
554
|
+
// unconditionally, with no way to tell that apart from a genuine delete.
|
|
555
|
+
const optimistic: ImageStroke = {
|
|
556
|
+
id: 'img-1',
|
|
557
|
+
tool: 'image',
|
|
558
|
+
x: 0,
|
|
559
|
+
y: 0,
|
|
560
|
+
w: 10,
|
|
561
|
+
h: 10,
|
|
562
|
+
href: 'blob:optimistic-preview',
|
|
563
|
+
};
|
|
564
|
+
const sibling: RectStroke = {
|
|
565
|
+
id: 'sib',
|
|
566
|
+
tool: 'rect',
|
|
567
|
+
color: '#000',
|
|
568
|
+
width: 2,
|
|
569
|
+
x: 20,
|
|
570
|
+
y: 0,
|
|
571
|
+
w: 10,
|
|
572
|
+
h: 10,
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
test('deleted before the chain caught up to the optimistic insert (common case) → stays gone, no undo record', () => {
|
|
576
|
+
const before = [sibling]; // render lag — optimistic insert not reflected yet
|
|
577
|
+
const result = resolveImageUploadSwap(before, 'img-1', optimistic, 'assets/real.png', true);
|
|
578
|
+
expect(result).toBeNull();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test('deleted after the chain already had the optimistic entry → explicitly stripped from `after`', () => {
|
|
582
|
+
const before = [sibling, optimistic];
|
|
583
|
+
const result = resolveImageUploadSwap(before, 'img-1', optimistic, 'assets/real.png', true);
|
|
584
|
+
expect(result?.after).toEqual([sibling]);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test('NOT deleted + render lag (the original, still-valid case) → swaps to the real href', () => {
|
|
588
|
+
const before = [sibling]; // optimistic insert hasn't rendered into `before` yet
|
|
589
|
+
const result = resolveImageUploadSwap(before, 'img-1', optimistic, 'assets/real.png', false);
|
|
590
|
+
const img = result?.after.find((s) => s.id === 'img-1') as unknown as { href: string };
|
|
591
|
+
expect(img?.href).toBe('assets/real.png');
|
|
592
|
+
expect(result?.commitBefore).toEqual([sibling]); // undo excludes the never-committed blob: entry
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test('NOT deleted, already present in `before` → swaps in place, sibling order preserved', () => {
|
|
596
|
+
const before = [sibling, optimistic];
|
|
597
|
+
const result = resolveImageUploadSwap(before, 'img-1', optimistic, 'assets/real.png', false);
|
|
598
|
+
expect(result?.after.map((s) => s.id)).toEqual(['sib', 'img-1']);
|
|
599
|
+
const img = result?.after.find((s) => s.id === 'img-1') as unknown as { href: string };
|
|
600
|
+
expect(img?.href).toBe('assets/real.png');
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
|
|
430
604
|
describe('annotations-layer / strokesShallowEqual (drag no-op gate)', () => {
|
|
431
605
|
const pen: PenStroke = {
|
|
432
606
|
id: 'p',
|
|
@@ -11,7 +11,7 @@ import { describe, expect, test } from 'bun:test';
|
|
|
11
11
|
import { existsSync, readdirSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
13
|
|
|
14
|
-
import { sniffImageType } from '../api.ts';
|
|
14
|
+
import { ASSET_MAX_BYTES, sniffImageType } from '../api.ts';
|
|
15
15
|
import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
|
|
16
16
|
|
|
17
17
|
// ── Minimal valid magic-byte headers (padded so length checks pass). ──────────
|
|
@@ -107,9 +107,9 @@ describe('asset-api / POST /_api/asset (endpoint round-trip)', () => {
|
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
test('rejects an oversize body (>
|
|
110
|
+
test('rejects an oversize body (> ASSET_MAX_BYTES) with 413', async () => {
|
|
111
111
|
await withServer(async (port) => {
|
|
112
|
-
const big = new Uint8Array(
|
|
112
|
+
const big = new Uint8Array(ASSET_MAX_BYTES + 1);
|
|
113
113
|
big.set([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], 0); // valid PNG header
|
|
114
114
|
const res = await postAsset(port, big);
|
|
115
115
|
expect(res.status).toBe(413);
|
|
@@ -377,10 +377,13 @@ describe('canvas-edit / applyTextEdit', () => {
|
|
|
377
377
|
expect(() => applyTextEdit(CANVAS, src, id, 'x')).toThrow(CanvasEditError);
|
|
378
378
|
});
|
|
379
379
|
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
// unified-text-editing follow-up — a `{identifier}` bound to a const string
|
|
381
|
+
// is now traced back to that const and edited THERE (was a hard refusal).
|
|
382
|
+
test('edits an {identifier} child bound to a const string (traces to the const)', () => {
|
|
383
|
+
const src = 'const t = "x";\nfunction Demo() { return <h1>{t}</h1>; }';
|
|
382
384
|
const id = idsOf(src).h1 as string;
|
|
383
|
-
|
|
385
|
+
const out = applyTextEdit(CANVAS, src, id, 'New', { before: 'x' });
|
|
386
|
+
expect(out.source).toContain('const t = "New"');
|
|
384
387
|
});
|
|
385
388
|
|
|
386
389
|
test('refuses a self-closing / empty element (no text to edit)', () => {
|
|
@@ -412,10 +415,18 @@ describe('canvas-edit / applyTextEdit', () => {
|
|
|
412
415
|
expect(() => transpileCanvasSource(CANVAS, out.source)).not.toThrow();
|
|
413
416
|
});
|
|
414
417
|
|
|
415
|
-
test('still refuses a
|
|
416
|
-
|
|
418
|
+
test('still refuses a genuinely-computed expression child (routes to /design:edit)', () => {
|
|
419
|
+
// Call expression — no single source string to rewrite → hard refusal.
|
|
420
|
+
const src = 'const price = 9;\nfunction Demo() { return <h1>{price.toFixed(2)}</h1>; }';
|
|
417
421
|
const id = idsOf(src).h1 as string;
|
|
418
|
-
expect(() => applyTextEdit(CANVAS, src, id, 'x')).toThrow(CanvasEditError);
|
|
422
|
+
expect(() => applyTextEdit(CANVAS, src, id, 'x', { before: '9.00' })).toThrow(CanvasEditError);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('refuses an {identifier} that does not resolve to a string const', () => {
|
|
426
|
+
// `count` is a number const — no string to trace to → refuse.
|
|
427
|
+
const src = 'const count = 3;\nfunction Demo() { return <h1>{count}</h1>; }';
|
|
428
|
+
const id = idsOf(src).h1 as string;
|
|
429
|
+
expect(() => applyTextEdit(CANVAS, src, id, 'x', { before: '3' })).toThrow(CanvasEditError);
|
|
419
430
|
});
|
|
420
431
|
});
|
|
421
432
|
|
|
@@ -162,6 +162,56 @@ describe('canvas-origin gate — A1/A2 traversal + privilege containment', () =>
|
|
|
162
162
|
// untrusted canvas origin must never read (or write) the user's pasted
|
|
163
163
|
// chat images; a GET here 403s at the gate, not 404 from the handler.
|
|
164
164
|
'/_api/acp/attachment',
|
|
165
|
+
// Phase 4 (feature-whiteboard-annotation-improvements) — the sticker
|
|
166
|
+
// catalogue + bundled sticker PNGs are MAIN-ORIGIN ONLY, same posture
|
|
167
|
+
// as /_api/assets above: the StickerPicker is shell UI, absent from
|
|
168
|
+
// CANVAS_SAFE_API + startCanvasServer's routes. A GET here 403s at
|
|
169
|
+
// the gate on both — the catalogue route and the static PNG serve.
|
|
170
|
+
'/_api/stickers',
|
|
171
|
+
'/_stickers/some-pack/some-sticker.png',
|
|
172
|
+
// DDR-166 Phase 1 / T2 — the bundled intro-video media serve is
|
|
173
|
+
// MAIN-ORIGIN ONLY, same posture as /_stickers above: a Maude-product
|
|
174
|
+
// asset, absent from CANVAS_SAFE_API + startCanvasServer's routes.
|
|
175
|
+
'/_media/intro.mp4',
|
|
176
|
+
// feature-footage-analysis-director — the footage-analysis + EDL sidecar
|
|
177
|
+
// route is MAIN-ORIGIN ONLY (written by the analyst/director agents over
|
|
178
|
+
// loopback; absent from CANVAS_SAFE_API + startCanvasServer's routes). The
|
|
179
|
+
// untrusted canvas origin must never read/write the director's analysis —
|
|
180
|
+
// a GET here 403s at the gate, not 405 from a reached handler.
|
|
181
|
+
'/_api/footage',
|
|
182
|
+
// feature-ai-media-generation (DDR-16x) — the generation job queue +
|
|
183
|
+
// provider catalogue + key routes are MAIN-ORIGIN ONLY (they resolve a
|
|
184
|
+
// provider KEY and make outbound provider calls). The untrusted canvas
|
|
185
|
+
// origin must never reach them — it sees only the produced /assets/<sha8>.
|
|
186
|
+
// A GET here 403s at the gate, not 405/200 from a reached handler.
|
|
187
|
+
'/_api/generate-jobs',
|
|
188
|
+
'/_api/generate/providers',
|
|
189
|
+
'/_api/generate/keys',
|
|
190
|
+
// Task 2.6 — the non-secret transcription-engine preference writes into
|
|
191
|
+
// .design/config.json + hot-reloads; still MAIN-ORIGIN ONLY (the canvas
|
|
192
|
+
// must not steer the user's engine choice).
|
|
193
|
+
'/_api/generate/prefs',
|
|
194
|
+
// Task 2.5 — reuse-before-you-pay audio search + history re-download
|
|
195
|
+
// resolve the provider key server-side; MAIN-ORIGIN ONLY.
|
|
196
|
+
'/_api/generate/audio-search',
|
|
197
|
+
'/_api/generate/audio-reuse',
|
|
198
|
+
// Task 2.7 — managed whisper-model download (egress + local disk write);
|
|
199
|
+
// MAIN-ORIGIN ONLY.
|
|
200
|
+
'/_api/generate/whisper-model',
|
|
201
|
+
// DDR-166 plan, Phase 2 (T6) — the design-setup readiness probe (project/
|
|
202
|
+
// design-system/first-canvas/brand-assets progress) is a shell/onboarding
|
|
203
|
+
// concern, same posture as /_api/preflight above; MAIN-ORIGIN ONLY.
|
|
204
|
+
'/_api/setup-readiness',
|
|
205
|
+
// DDR-167 (Phase 3 / T10) — local-file SVG/PDF ingestion is a
|
|
206
|
+
// file-write + local-file-adjacent surface (Decision 4); MAIN-ORIGIN
|
|
207
|
+
// ONLY, same posture as /_api/asset's containment but privileged
|
|
208
|
+
// rather than canvas-reachable — the untrusted canvas iframe must
|
|
209
|
+
// never drive an import.
|
|
210
|
+
'/_api/import-asset',
|
|
211
|
+
// DDR-173 (Phase 3 / T12) — brand-file typed-cue extraction is a
|
|
212
|
+
// file-write surface (writes assets/logos/*), same privileged
|
|
213
|
+
// posture as /_api/import-asset above; MAIN-ORIGIN ONLY.
|
|
214
|
+
'/_api/import-brand',
|
|
165
215
|
'/package.json',
|
|
166
216
|
]) {
|
|
167
217
|
expect(await code(p)).toBe(403);
|
|
@@ -48,4 +48,15 @@ describe('cspForCanvasShell — A6 hardening', () => {
|
|
|
48
48
|
const csp = cspForCanvasShell(SHELL);
|
|
49
49
|
expect(csp).not.toContain('frame-ancestors');
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
// fix-photo-editor-followup-debt Task 22 — the narrow, documented
|
|
53
|
+
// `connect-src` exception for @imgly/background-removal's model-weight CDN.
|
|
54
|
+
// Regression guard against both directions of drift: the exception being
|
|
55
|
+
// silently reverted, AND further hosts creeping in beside it.
|
|
56
|
+
test('connect-src carries the exact staticimgly.com exception, no more', () => {
|
|
57
|
+
const csp = cspForCanvasShell(SHELL);
|
|
58
|
+
expect(csp).toContain("connect-src 'self' https://staticimgly.com");
|
|
59
|
+
const connectSrc = csp.split('; ').find((d) => d.startsWith('connect-src'));
|
|
60
|
+
expect(connectSrc).toBe("connect-src 'self' https://staticimgly.com");
|
|
61
|
+
});
|
|
51
62
|
});
|
|
@@ -18,7 +18,7 @@ import { describe, expect, test } from 'bun:test';
|
|
|
18
18
|
import { readFileSync } from 'node:fs';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
|
|
21
|
-
import { sameOriginWrite } from '../http.ts';
|
|
21
|
+
import { sameOriginRead, sameOriginWrite } from '../http.ts';
|
|
22
22
|
|
|
23
23
|
const SELF = 'http://localhost:4399';
|
|
24
24
|
const post = (origin?: string): Request =>
|
|
@@ -53,6 +53,28 @@ describe('CSRF Origin guard — sameOriginWrite (DDR-105)', () => {
|
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
describe('CSRF read guard — sameOriginRead (Task 2.5 F1, Fetch-Metadata)', () => {
|
|
57
|
+
const get = (secFetchSite?: string): Request =>
|
|
58
|
+
new Request(`${SELF}/_api/generate/audio-search?q=x`, {
|
|
59
|
+
method: 'GET',
|
|
60
|
+
headers: { ...(secFetchSite ? { 'sec-fetch-site': secFetchSite } : {}) },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('rejects a cross-site browser GET (key-bearing fan-out must not be CSRF-triggerable)', () => {
|
|
64
|
+
expect(sameOriginRead(get('cross-site'))).toBe(false);
|
|
65
|
+
expect(sameOriginRead(get('same-site'))).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('allows a same-origin browser GET', () => {
|
|
69
|
+
expect(sameOriginRead(get('same-origin'))).toBe(true);
|
|
70
|
+
expect(sameOriginRead(get('none'))).toBe(true); // direct navigation
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('allows a non-browser client (CLI / curl — no Sec-Fetch-Site header)', () => {
|
|
74
|
+
expect(sameOriginRead(get())).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
56
78
|
// phase-30 / DDR-120: the ai-activity bridge now projects `/_api/ai/*` POSTs
|
|
57
79
|
// onto room awareness, which crosses the hub to every connected peer. So a
|
|
58
80
|
// forged cross-origin POST to /start /heartbeat /end is no longer a harmless
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Design-setup readiness probe (DDR-166 plan, Phase 2 / T6). A pure function
|
|
2
|
+
// over a Context-shaped fixture — no server boot needed, `probeSetupReadiness`
|
|
3
|
+
// only touches `ctx.cfg` + `ctx.paths` (both destructured, so a minimal fake
|
|
4
|
+
// satisfies the type).
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import type { Context, DevServerConfig } from '../context.ts';
|
|
12
|
+
import { probeSetupReadiness } from '../design-setup-readiness.ts';
|
|
13
|
+
|
|
14
|
+
function fixtureCtx(opts: {
|
|
15
|
+
hasConfig?: boolean;
|
|
16
|
+
designSystems?: DevServerConfig['designSystems'];
|
|
17
|
+
canvasGroups?: DevServerConfig['canvasGroups'];
|
|
18
|
+
seedCanvas?: boolean;
|
|
19
|
+
seedTokens?: string[]; // ds names to write a real colors_and_type.css for
|
|
20
|
+
seedLogo?: string[]; // ds names to write a preview/logo.svg for
|
|
21
|
+
}): Context {
|
|
22
|
+
const root = mkdtempSync(join(tmpdir(), 'maude-setup-readiness-'));
|
|
23
|
+
const designRoot = join(root, '.design');
|
|
24
|
+
mkdirSync(designRoot, { recursive: true });
|
|
25
|
+
|
|
26
|
+
const designSystems = opts.designSystems ?? [];
|
|
27
|
+
const canvasGroups = opts.canvasGroups ?? [
|
|
28
|
+
{ label: 'Design system', path: 'system' },
|
|
29
|
+
{ label: 'UI kit', path: 'ui' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const ds of designSystems) {
|
|
33
|
+
mkdirSync(join(designRoot, ds.path), { recursive: true });
|
|
34
|
+
if (opts.seedTokens?.includes(ds.name)) {
|
|
35
|
+
writeFileSync(join(designRoot, ds.path, 'colors_and_type.css'), ':root{}');
|
|
36
|
+
}
|
|
37
|
+
if (opts.seedLogo?.includes(ds.name)) {
|
|
38
|
+
mkdirSync(join(designRoot, ds.path, 'preview'), { recursive: true });
|
|
39
|
+
writeFileSync(join(designRoot, ds.path, 'preview', 'logo.svg'), '<svg/>');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (opts.seedCanvas) {
|
|
43
|
+
mkdirSync(join(designRoot, 'ui'), { recursive: true });
|
|
44
|
+
writeFileSync(
|
|
45
|
+
join(designRoot, 'ui', 'Welcome.tsx'),
|
|
46
|
+
'export default function W(){return <main/>}\n'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const cfg: DevServerConfig = {
|
|
51
|
+
name: 'test',
|
|
52
|
+
projectLabel: null,
|
|
53
|
+
designRoot: '.design',
|
|
54
|
+
canvasGroups,
|
|
55
|
+
designSystems,
|
|
56
|
+
rootClass: 'app',
|
|
57
|
+
themeDefault: 'dark',
|
|
58
|
+
tokensCssRel: 'system/colors_and_type.css',
|
|
59
|
+
teamAccentDefault: null,
|
|
60
|
+
handoffTargets: [],
|
|
61
|
+
newCanvasDir: 'ui',
|
|
62
|
+
newComponentDir: 'ui/components',
|
|
63
|
+
_source: opts.hasConfig === false ? 'defaults' : '.design/config.json',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
cfg,
|
|
68
|
+
projectLabel: 'test',
|
|
69
|
+
paths: {
|
|
70
|
+
repoRoot: root,
|
|
71
|
+
designRel: '.design',
|
|
72
|
+
designRoot,
|
|
73
|
+
serverInfoFile: join(designRoot, '_server.json'),
|
|
74
|
+
activeFile: join(designRoot, '_active.json'),
|
|
75
|
+
commentsDir: join(designRoot, '_comments'),
|
|
76
|
+
canvasStateDir: join(designRoot, '_canvas-state'),
|
|
77
|
+
historyDir: join(designRoot, '_history'),
|
|
78
|
+
tokensUrlRel: 'system/colors_and_type.css',
|
|
79
|
+
systemDirRel: 'system',
|
|
80
|
+
},
|
|
81
|
+
bus: { on: () => () => {}, emit: () => {} },
|
|
82
|
+
} as Context;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe('probeSetupReadiness', () => {
|
|
86
|
+
test('bare write_minimal_design scaffold: config missing, everything else missing too', async () => {
|
|
87
|
+
const report = await probeSetupReadiness(fixtureCtx({ hasConfig: false }));
|
|
88
|
+
expect(report.ready).toBe(false);
|
|
89
|
+
const byId = Object.fromEntries(report.items.map((i) => [i.id, i.status]));
|
|
90
|
+
expect(byId).toEqual({
|
|
91
|
+
project: 'missing',
|
|
92
|
+
'design-system': 'missing',
|
|
93
|
+
'first-canvas': 'missing',
|
|
94
|
+
'brand-assets': 'missing',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('real config, no DS, no canvas: project present, rest missing', async () => {
|
|
99
|
+
const report = await probeSetupReadiness(fixtureCtx({ hasConfig: true }));
|
|
100
|
+
const byId = Object.fromEntries(report.items.map((i) => [i.id, i.status]));
|
|
101
|
+
expect(byId.project).toBe('present');
|
|
102
|
+
expect(byId['design-system']).toBe('missing');
|
|
103
|
+
expect(byId['first-canvas']).toBe('missing');
|
|
104
|
+
expect(byId['brand-assets']).toBe('missing');
|
|
105
|
+
expect(report.ready).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('DS declared but tokens file absent: still missing (declared != on-disk)', async () => {
|
|
109
|
+
const report = await probeSetupReadiness(
|
|
110
|
+
fixtureCtx({
|
|
111
|
+
hasConfig: true,
|
|
112
|
+
designSystems: [
|
|
113
|
+
{ name: 'acme', path: 'system/acme', tokensCssRel: 'system/acme/colors_and_type.css' },
|
|
114
|
+
],
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
const item = report.items.find((i) => i.id === 'design-system')!;
|
|
118
|
+
expect(item.status).toBe('missing');
|
|
119
|
+
expect(item.detail).toContain('Declared in config');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('all four present → ready: true', async () => {
|
|
123
|
+
const report = await probeSetupReadiness(
|
|
124
|
+
fixtureCtx({
|
|
125
|
+
hasConfig: true,
|
|
126
|
+
designSystems: [
|
|
127
|
+
{ name: 'acme', path: 'system/acme', tokensCssRel: 'system/acme/colors_and_type.css' },
|
|
128
|
+
],
|
|
129
|
+
seedTokens: ['acme'],
|
|
130
|
+
seedLogo: ['acme'],
|
|
131
|
+
seedCanvas: true,
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
expect(report.items.every((i) => i.status === 'present')).toBe(true);
|
|
135
|
+
expect(report.ready).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('a "system" canvas group never counts as the first user canvas', async () => {
|
|
139
|
+
const root = mkdtempSync(join(tmpdir(), 'maude-setup-readiness-'));
|
|
140
|
+
const designRoot = join(root, '.design');
|
|
141
|
+
const canvasGroups = [{ label: 'Design system', path: 'system' }];
|
|
142
|
+
mkdirSync(join(designRoot, 'system'), { recursive: true });
|
|
143
|
+
writeFileSync(
|
|
144
|
+
join(designRoot, 'system', 'specimen.tsx'),
|
|
145
|
+
'export default function S(){return <main/>}\n'
|
|
146
|
+
);
|
|
147
|
+
const ctx = fixtureCtx({ hasConfig: true, canvasGroups });
|
|
148
|
+
// Overwrite paths.designRoot/repoRoot to point at this second fixture dir
|
|
149
|
+
// (fixtureCtx already made its own scratch dir we don't need here).
|
|
150
|
+
ctx.paths.repoRoot = root;
|
|
151
|
+
ctx.paths.designRoot = designRoot;
|
|
152
|
+
const report = await probeSetupReadiness(ctx);
|
|
153
|
+
expect(report.items.find((i) => i.id === 'first-canvas')!.status).toBe('missing');
|
|
154
|
+
});
|
|
155
|
+
});
|