@happyvertical/smrt-svelte 0.30.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/AGENTS.md +317 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +185 -0
- package/dist/Provider.svelte +204 -0
- package/dist/Provider.svelte.d.ts +73 -0
- package/dist/Provider.svelte.d.ts.map +1 -0
- package/dist/__tests__/app-state.test.js +156 -0
- package/dist/__tests__/warm-clients.test.js +186 -0
- package/dist/browser-ai/adapters/llm/factory.d.ts +38 -0
- package/dist/browser-ai/adapters/llm/factory.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/factory.js +91 -0
- package/dist/browser-ai/adapters/llm/index.d.ts +7 -0
- package/dist/browser-ai/adapters/llm/index.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/index.js +6 -0
- package/dist/browser-ai/adapters/llm/types.d.ts +182 -0
- package/dist/browser-ai/adapters/llm/types.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/types.js +43 -0
- package/dist/browser-ai/adapters/llm/webllm.d.ts +33 -0
- package/dist/browser-ai/adapters/llm/webllm.d.ts.map +1 -0
- package/dist/browser-ai/adapters/llm/webllm.js +225 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts +31 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/browser-speech.js +217 -0
- package/dist/browser-ai/adapters/stt/factory.d.ts +49 -0
- package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/factory.js +110 -0
- package/dist/browser-ai/adapters/stt/index.d.ts +9 -0
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/index.js +8 -0
- package/dist/browser-ai/adapters/stt/types.d.ts +154 -0
- package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/types.js +4 -0
- package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts +46 -0
- package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/whisper-cpp.js +348 -0
- package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +51 -0
- package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/whisper-wasm.js +380 -0
- package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +42 -0
- package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/browser-synthesis.js +235 -0
- package/dist/browser-ai/adapters/tts/factory.d.ts +53 -0
- package/dist/browser-ai/adapters/tts/factory.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/factory.js +92 -0
- package/dist/browser-ai/adapters/tts/index.d.ts +7 -0
- package/dist/browser-ai/adapters/tts/index.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/index.js +6 -0
- package/dist/browser-ai/adapters/tts/types.d.ts +140 -0
- package/dist/browser-ai/adapters/tts/types.d.ts.map +1 -0
- package/dist/browser-ai/adapters/tts/types.js +4 -0
- package/dist/browser-ai/capabilities/detector.d.ts +38 -0
- package/dist/browser-ai/capabilities/detector.d.ts.map +1 -0
- package/dist/browser-ai/capabilities/detector.js +211 -0
- package/dist/browser-ai/core/errors.d.ts +62 -0
- package/dist/browser-ai/core/errors.d.ts.map +1 -0
- package/dist/browser-ai/core/errors.js +92 -0
- package/dist/browser-ai/core/index.d.ts +6 -0
- package/dist/browser-ai/core/index.d.ts.map +1 -0
- package/dist/browser-ai/core/index.js +5 -0
- package/dist/browser-ai/core/types.d.ts +115 -0
- package/dist/browser-ai/core/types.d.ts.map +1 -0
- package/dist/browser-ai/core/types.js +34 -0
- package/dist/browser-ai/index.d.ts +12 -0
- package/dist/browser-ai/index.d.ts.map +1 -0
- package/dist/browser-ai/index.js +16 -0
- package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte +77 -0
- package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts +16 -0
- package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/CapabilityGate.svelte +57 -0
- package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts +15 -0
- package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/DownloadProgress.svelte +141 -0
- package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts +15 -0
- package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/STTTest.svelte +379 -0
- package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts +9 -0
- package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/components/VoiceInput.svelte +200 -0
- package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts +16 -0
- package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts.map +1 -0
- package/dist/browser-ai/svelte/index.d.ts +15 -0
- package/dist/browser-ai/svelte/index.d.ts.map +1 -0
- package/dist/browser-ai/svelte/index.js +28 -0
- package/dist/browser-ai/ui.d.ts +16 -0
- package/dist/browser-ai/ui.d.ts.map +1 -0
- package/dist/browser-ai/ui.js +67 -0
- package/dist/components/admin/AgentAdminPanel.svelte +111 -0
- package/dist/components/admin/AgentAdminPanel.svelte.d.ts +25 -0
- package/dist/components/admin/AgentAdminPanel.svelte.d.ts.map +1 -0
- package/dist/components/admin/AgentAdminTabs.svelte +280 -0
- package/dist/components/admin/AgentAdminTabs.svelte.d.ts +23 -0
- package/dist/components/admin/AgentAdminTabs.svelte.d.ts.map +1 -0
- package/dist/components/admin/AgentSettingsShell.svelte +257 -0
- package/dist/components/admin/AgentSettingsShell.svelte.d.ts +33 -0
- package/dist/components/admin/AgentSettingsShell.svelte.d.ts.map +1 -0
- package/dist/components/admin/index.d.ts +5 -0
- package/dist/components/admin/index.d.ts.map +1 -0
- package/dist/components/admin/index.js +6 -0
- package/dist/components/forms/AddressInput.svelte +500 -0
- package/dist/components/forms/AddressInput.svelte.d.ts +36 -0
- package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/CheckboxInput.svelte +208 -0
- package/dist/components/forms/CheckboxInput.svelte.d.ts +20 -0
- package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/DateRangeInput.svelte +628 -0
- package/dist/components/forms/DateRangeInput.svelte.d.ts +33 -0
- package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/DateTimeInput.svelte +521 -0
- package/dist/components/forms/DateTimeInput.svelte.d.ts +24 -0
- package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/FileUpload.svelte +358 -0
- package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
- package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -0
- package/dist/components/forms/Form.svelte +771 -0
- package/dist/components/forms/Form.svelte.d.ts +26 -0
- package/dist/components/forms/Form.svelte.d.ts.map +1 -0
- package/dist/components/forms/FormGroup.svelte +86 -0
- package/dist/components/forms/FormGroup.svelte.d.ts +13 -0
- package/dist/components/forms/FormGroup.svelte.d.ts.map +1 -0
- package/dist/components/forms/FormMicButton.svelte +179 -0
- package/dist/components/forms/FormMicButton.svelte.d.ts +10 -0
- package/dist/components/forms/FormMicButton.svelte.d.ts.map +1 -0
- package/dist/components/forms/Input.svelte +83 -0
- package/dist/components/forms/Input.svelte.d.ts +9 -0
- package/dist/components/forms/Input.svelte.d.ts.map +1 -0
- package/dist/components/forms/MeasurementInput.svelte +505 -0
- package/dist/components/forms/MeasurementInput.svelte.d.ts +35 -0
- package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/MoneyInput.svelte +412 -0
- package/dist/components/forms/MoneyInput.svelte.d.ts +30 -0
- package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/NumberInput.svelte +310 -0
- package/dist/components/forms/NumberInput.svelte.d.ts +28 -0
- package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/PhoneInput.svelte +530 -0
- package/dist/components/forms/PhoneInput.svelte.d.ts +22 -0
- package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/SearchInput.svelte +358 -0
- package/dist/components/forms/SearchInput.svelte.d.ts +33 -0
- package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Select.svelte +83 -0
- package/dist/components/forms/Select.svelte.d.ts +11 -0
- package/dist/components/forms/Select.svelte.d.ts.map +1 -0
- package/dist/components/forms/SelectInput.svelte +254 -0
- package/dist/components/forms/SelectInput.svelte.d.ts +25 -0
- package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/TextInput.svelte +415 -0
- package/dist/components/forms/TextInput.svelte.d.ts +26 -0
- package/dist/components/forms/TextInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Textarea.svelte +85 -0
- package/dist/components/forms/Textarea.svelte.d.ts +10 -0
- package/dist/components/forms/Textarea.svelte.d.ts.map +1 -0
- package/dist/components/forms/TextareaInput.svelte +386 -0
- package/dist/components/forms/TextareaInput.svelte.d.ts +26 -0
- package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Toggle.svelte +217 -0
- package/dist/components/forms/Toggle.svelte.d.ts +37 -0
- package/dist/components/forms/Toggle.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/AddressInput.behavior.test.js +122 -0
- package/dist/components/forms/__tests__/CheckboxInput.test.js +92 -0
- package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +135 -0
- package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +103 -0
- package/dist/components/forms/__tests__/FileUpload.test.js +90 -0
- package/dist/components/forms/__tests__/Form.behavior.test.js +137 -0
- package/dist/components/forms/__tests__/Form.test.js +58 -0
- package/dist/components/forms/__tests__/FormGroup.test.js +48 -0
- package/dist/components/forms/__tests__/FormMicButton.test.js +86 -0
- package/dist/components/forms/__tests__/Input.test.js +49 -0
- package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +129 -0
- package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +124 -0
- package/dist/components/forms/__tests__/NumberInput.behavior.test.js +141 -0
- package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +96 -0
- package/dist/components/forms/__tests__/SearchInput.test.js +79 -0
- package/dist/components/forms/__tests__/Select.test.js +37 -0
- package/dist/components/forms/__tests__/SelectInput.behavior.test.js +132 -0
- package/dist/components/forms/__tests__/TextInput.behavior.test.js +131 -0
- package/dist/components/forms/__tests__/Textarea.test.js +39 -0
- package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +96 -0
- package/dist/components/forms/__tests__/Toggle.test.js +87 -0
- package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +69 -0
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte +16 -0
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +33 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +87 -0
- package/dist/components/forms/index.d.ts +25 -0
- package/dist/components/forms/index.d.ts.map +1 -0
- package/dist/components/forms/index.js +25 -0
- package/dist/components/forms/types.d.ts +33 -0
- package/dist/components/forms/types.d.ts.map +1 -0
- package/dist/components/forms/types.js +4 -0
- package/dist/components/module/ModulePanel.svelte +134 -0
- package/dist/components/module/ModulePanel.svelte.d.ts +22 -0
- package/dist/components/module/ModulePanel.svelte.d.ts.map +1 -0
- package/dist/components/module/index.d.ts +5 -0
- package/dist/components/module/index.d.ts.map +1 -0
- package/dist/components/module/index.js +4 -0
- package/dist/components/workspace/Breadcrumbs.svelte +141 -0
- package/dist/components/workspace/Breadcrumbs.svelte.d.ts +21 -0
- package/dist/components/workspace/Breadcrumbs.svelte.d.ts.map +1 -0
- package/dist/components/workspace/NavTree.svelte +354 -0
- package/dist/components/workspace/NavTree.svelte.d.ts +45 -0
- package/dist/components/workspace/NavTree.svelte.d.ts.map +1 -0
- package/dist/components/workspace/README.md +34 -0
- package/dist/components/workspace/RoleShell.svelte +309 -0
- package/dist/components/workspace/RoleShell.svelte.d.ts +91 -0
- package/dist/components/workspace/RoleShell.svelte.d.ts.map +1 -0
- package/dist/components/workspace/WorkspaceShell.svelte +951 -0
- package/dist/components/workspace/WorkspaceShell.svelte.d.ts +112 -0
- package/dist/components/workspace/WorkspaceShell.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/RoleShell.test.js +772 -0
- package/dist/components/workspace/__tests__/WorkspaceShell.test.js +630 -0
- package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +141 -0
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +45 -0
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +21 -0
- package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/define-tools-dock.test.js +1010 -0
- package/dist/components/workspace/__tests__/harness.svelte +25 -0
- package/dist/components/workspace/__tests__/harness.svelte.d.ts +14 -0
- package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/index.test.js +37 -0
- package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +24 -0
- package/dist/components/workspace/__tests__/manifest-nav.test.js +599 -0
- package/dist/components/workspace/__tests__/nav-helpers.test.js +95 -0
- package/dist/components/workspace/__tests__/render-harness.svelte +66 -0
- package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +32 -0
- package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/render-tools-dock.test.js +243 -0
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +58 -0
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +16 -0
- package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +41 -0
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +13 -0
- package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/test-icon.svelte +17 -0
- package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +19 -0
- package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +38 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +22 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +65 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +115 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +15 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +7 -0
- package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +115 -0
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte +9 -0
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +19 -0
- package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/use-harness.svelte +23 -0
- package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +8 -0
- package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/use-tools-dock.test.js +33 -0
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +43 -0
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +11 -0
- package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/breadcrumbs-helpers.d.ts +44 -0
- package/dist/components/workspace/breadcrumbs-helpers.d.ts.map +1 -0
- package/dist/components/workspace/breadcrumbs-helpers.js +88 -0
- package/dist/components/workspace/index.d.ts +16 -0
- package/dist/components/workspace/index.d.ts.map +1 -0
- package/dist/components/workspace/index.js +14 -0
- package/dist/components/workspace/manifest-nav.d.ts +200 -0
- package/dist/components/workspace/manifest-nav.d.ts.map +1 -0
- package/dist/components/workspace/manifest-nav.js +408 -0
- package/dist/components/workspace/nav-helpers.d.ts +36 -0
- package/dist/components/workspace/nav-helpers.d.ts.map +1 -0
- package/dist/components/workspace/nav-helpers.js +60 -0
- package/dist/components/workspace/server/__tests__/compose-availability.test.js +383 -0
- package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +78 -0
- package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +1 -0
- package/dist/components/workspace/server/__tests__/typed-context-fixture.js +104 -0
- package/dist/components/workspace/server/compose-availability.d.ts +73 -0
- package/dist/components/workspace/server/compose-availability.d.ts.map +1 -0
- package/dist/components/workspace/server/compose-availability.js +114 -0
- package/dist/components/workspace/server/index.d.ts +13 -0
- package/dist/components/workspace/server/index.d.ts.map +1 -0
- package/dist/components/workspace/server/index.js +11 -0
- package/dist/components/workspace/server/types.d.ts +108 -0
- package/dist/components/workspace/server/types.d.ts.map +1 -0
- package/dist/components/workspace/server/types.js +11 -0
- package/dist/components/workspace/tools-dock/ToolsDock.svelte +565 -0
- package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts +14 -0
- package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts.map +1 -0
- package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts +143 -0
- package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts.map +1 -0
- package/dist/components/workspace/tools-dock/define-tools-dock.svelte.js +487 -0
- package/dist/components/workspace/tools-dock/use-tools-dock.d.ts +41 -0
- package/dist/components/workspace/tools-dock/use-tools-dock.d.ts.map +1 -0
- package/dist/components/workspace/tools-dock/use-tools-dock.js +50 -0
- package/dist/components/workspace/types.d.ts +372 -0
- package/dist/components/workspace/types.d.ts.map +1 -0
- package/dist/components/workspace/types.js +6 -0
- package/dist/hooks/index.d.ts +11 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +10 -0
- package/dist/hooks/useAppState.svelte.d.ts +46 -0
- package/dist/hooks/useAppState.svelte.d.ts.map +1 -0
- package/dist/hooks/useAppState.svelte.js +59 -0
- package/dist/hooks/useAuth.svelte.d.ts +41 -0
- package/dist/hooks/useAuth.svelte.d.ts.map +1 -0
- package/dist/hooks/useAuth.svelte.js +43 -0
- package/dist/hooks/useLLM.svelte.d.ts +69 -0
- package/dist/hooks/useLLM.svelte.d.ts.map +1 -0
- package/dist/hooks/useLLM.svelte.js +85 -0
- package/dist/hooks/useSTT.svelte.d.ts +68 -0
- package/dist/hooks/useSTT.svelte.d.ts.map +1 -0
- package/dist/hooks/useSTT.svelte.js +97 -0
- package/dist/hooks/useSocket.svelte.d.ts +45 -0
- package/dist/hooks/useSocket.svelte.d.ts.map +1 -0
- package/dist/hooks/useSocket.svelte.js +54 -0
- package/dist/hooks/useTTS.svelte.d.ts +65 -0
- package/dist/hooks/useTTS.svelte.d.ts.map +1 -0
- package/dist/hooks/useTTS.svelte.js +93 -0
- package/dist/hooks/useTheme.d.ts +13 -0
- package/dist/hooks/useTheme.d.ts.map +1 -0
- package/dist/hooks/useTheme.js +16 -0
- package/dist/i18n/__tests__/server.spec.js +50 -0
- package/dist/i18n/server.d.ts +47 -0
- package/dist/i18n/server.d.ts.map +1 -0
- package/dist/i18n/server.js +58 -0
- package/dist/i18n/strings.forms.d.ts +33 -0
- package/dist/i18n/strings.forms.d.ts.map +1 -0
- package/dist/i18n/strings.forms.js +54 -0
- package/dist/i18n/strings.workspace.d.ts +34 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -0
- package/dist/i18n/strings.workspace.js +40 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/state/__tests__/warm-clients.test.js +40 -0
- package/dist/state/app-state.d.ts +308 -0
- package/dist/state/app-state.d.ts.map +1 -0
- package/dist/state/app-state.js +64 -0
- package/dist/state/app-state.svelte.d.ts +196 -0
- package/dist/state/app-state.svelte.d.ts.map +1 -0
- package/dist/state/app-state.svelte.js +774 -0
- package/dist/state/context.d.ts +23 -0
- package/dist/state/context.d.ts.map +1 -0
- package/dist/state/context.js +32 -0
- package/dist/state/form-context.d.ts +59 -0
- package/dist/state/form-context.d.ts.map +1 -0
- package/dist/state/form-context.js +31 -0
- package/dist/state/form-group-context.d.ts +13 -0
- package/dist/state/form-group-context.d.ts.map +1 -0
- package/dist/state/form-group-context.js +28 -0
- package/dist/state/index.d.ts +9 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +8 -0
- package/dist/state/warm-clients.d.ts +136 -0
- package/dist/state/warm-clients.d.ts.map +1 -0
- package/dist/state/warm-clients.js +231 -0
- package/package.json +137 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for NavTree's pure active/expanded helpers.
|
|
3
|
+
*
|
|
4
|
+
* The Svelte component itself is a thin renderer over these helpers,
|
|
5
|
+
* so covering the helpers gives us full behavioral coverage of:
|
|
6
|
+
* - 1-/2-/3-level active detection
|
|
7
|
+
* - `exact` flag
|
|
8
|
+
* - `isActive` override
|
|
9
|
+
* - expansion (default + descendant-driven)
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { defaultIsActive, isExpanded, isItemOrChildActive, } from '../nav-helpers.js';
|
|
13
|
+
const home = {
|
|
14
|
+
href: '/',
|
|
15
|
+
label: 'Home',
|
|
16
|
+
exact: true,
|
|
17
|
+
};
|
|
18
|
+
const content = {
|
|
19
|
+
href: '/content',
|
|
20
|
+
label: 'Content',
|
|
21
|
+
children: [
|
|
22
|
+
{ href: '/content/articles', label: 'Articles' },
|
|
23
|
+
{
|
|
24
|
+
href: '/content/media',
|
|
25
|
+
label: 'Media',
|
|
26
|
+
children: [
|
|
27
|
+
{ href: '/content/media/images', label: 'Images' },
|
|
28
|
+
{ href: '/content/media/videos', label: 'Videos' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
const settings = {
|
|
34
|
+
href: '/settings',
|
|
35
|
+
label: 'Settings',
|
|
36
|
+
defaultExpanded: true,
|
|
37
|
+
children: [{ href: '/settings/users', label: 'Users' }],
|
|
38
|
+
};
|
|
39
|
+
const _allItems = [home, content, settings];
|
|
40
|
+
describe('defaultIsActive', () => {
|
|
41
|
+
it('matches exact paths', () => {
|
|
42
|
+
expect(defaultIsActive({ href: '/content', label: '' }, '/content')).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
it('matches descendant paths when not exact', () => {
|
|
45
|
+
expect(defaultIsActive({ href: '/content', label: '' }, '/content/articles')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
it('respects the exact flag', () => {
|
|
48
|
+
expect(defaultIsActive({ href: '/', label: '', exact: true }, '/content')).toBe(false);
|
|
49
|
+
expect(defaultIsActive({ href: '/', label: '', exact: true }, '/')).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('does not match unrelated paths that share a prefix', () => {
|
|
52
|
+
// '/foo' should not match '/foobar' — the '/' boundary matters.
|
|
53
|
+
expect(defaultIsActive({ href: '/foo', label: '' }, '/foobar')).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
// Regression for #1231: the descendant-path fallback used to be
|
|
56
|
+
// `currentPath.startsWith(item.href + '/')`. For `item.href === '/'`,
|
|
57
|
+
// that expands to `startsWith('//')`, which never matches a real path —
|
|
58
|
+
// and even if it did, we don't want every nested route to count the
|
|
59
|
+
// root item as active by ancestry.
|
|
60
|
+
it('treats a root-href item as exact-only even without the exact flag', () => {
|
|
61
|
+
const root = { href: '/', label: 'Home' };
|
|
62
|
+
expect(defaultIsActive(root, '/')).toBe(true);
|
|
63
|
+
expect(defaultIsActive(root, '/content')).toBe(false);
|
|
64
|
+
expect(defaultIsActive(root, '/content/articles')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('isItemOrChildActive', () => {
|
|
68
|
+
it('returns true when the item itself is active', () => {
|
|
69
|
+
expect(isItemOrChildActive(content, '/content')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('returns true when a deep descendant is active', () => {
|
|
72
|
+
expect(isItemOrChildActive(content, '/content/media/images')).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it('returns false when neither item nor descendants are active', () => {
|
|
75
|
+
expect(isItemOrChildActive(content, '/settings/users')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
it('honors a custom isActive override', () => {
|
|
78
|
+
const customIsActive = (item, _path) => item.href === '/content/media/videos';
|
|
79
|
+
expect(isItemOrChildActive(content, '/anything', customIsActive)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('isExpanded', () => {
|
|
83
|
+
it('is false for items without children', () => {
|
|
84
|
+
expect(isExpanded({ href: '/x', label: '' }, '/x')).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
it('is true when defaultExpanded is set, regardless of path', () => {
|
|
87
|
+
expect(isExpanded(settings, '/unrelated')).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
it('is true when a descendant is active', () => {
|
|
90
|
+
expect(isExpanded(content, '/content/media/images')).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('is false when no descendant is active and not defaultExpanded', () => {
|
|
93
|
+
expect(isExpanded(content, '/unrelated')).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Render harness — wires defineToolsDock + <ToolsDock> into a single
|
|
4
|
+
* component so tests can render the actual DOM. Accepts a thin shape that
|
|
5
|
+
* lets each test express only the behavior it cares about.
|
|
6
|
+
*/
|
|
7
|
+
import { type Component, onMount } from 'svelte';
|
|
8
|
+
import { defineToolsDock } from '../tools-dock/define-tools-dock.svelte.js';
|
|
9
|
+
import ToolsDock from '../tools-dock/ToolsDock.svelte';
|
|
10
|
+
import type { AvailableTool, ToolsDockContext } from '../types.js';
|
|
11
|
+
|
|
12
|
+
interface ToolProp {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
badge?: number | string | null;
|
|
16
|
+
icon?: string;
|
|
17
|
+
iconComponent?: Component;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Props {
|
|
21
|
+
tools: ToolProp[];
|
|
22
|
+
fetchAvailability?: (
|
|
23
|
+
ctx: ToolsDockContext | null,
|
|
24
|
+
) => Promise<AvailableTool[]>;
|
|
25
|
+
initialOpen?: boolean;
|
|
26
|
+
layout?: 'rail' | 'topbar';
|
|
27
|
+
/** Activate this tool on mount via dock.open(id). */
|
|
28
|
+
activateOnMount?: string | null;
|
|
29
|
+
/** Call dock.setContext(...) on mount. */
|
|
30
|
+
setContextOnMount?: ToolsDockContext | null;
|
|
31
|
+
/** Force the dock open after setup (for empty-state assertions). */
|
|
32
|
+
forceOpen?: boolean;
|
|
33
|
+
/** Pass-through `context` prop to <ToolsDock>. */
|
|
34
|
+
context?: ToolsDockContext | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const props: Props = $props();
|
|
38
|
+
|
|
39
|
+
// Use a trivial inline component as the tool body; the renderer only checks
|
|
40
|
+
// that ActiveTool is defined and renders a header — the body is irrelevant
|
|
41
|
+
// for these tests.
|
|
42
|
+
const NoopBody = (() => null) as never;
|
|
43
|
+
|
|
44
|
+
// This harness intentionally snapshots initial render options during setup.
|
|
45
|
+
// svelte-ignore state_referenced_locally
|
|
46
|
+
const dock = defineToolsDock({
|
|
47
|
+
tools: props.tools.map((t) => ({ ...t, component: NoopBody })),
|
|
48
|
+
fetchAvailability: props.fetchAvailability,
|
|
49
|
+
initialOpen: props.initialOpen,
|
|
50
|
+
layout: props.layout,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
onMount(() => {
|
|
54
|
+
if (props.setContextOnMount !== undefined) {
|
|
55
|
+
dock.setContext(props.setContextOnMount);
|
|
56
|
+
}
|
|
57
|
+
if (props.activateOnMount) {
|
|
58
|
+
dock.open(props.activateOnMount);
|
|
59
|
+
}
|
|
60
|
+
if (props.forceOpen) {
|
|
61
|
+
dock.open();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<ToolsDock {dock} context={props.context} />
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render harness — wires defineToolsDock + <ToolsDock> into a single
|
|
3
|
+
* component so tests can render the actual DOM. Accepts a thin shape that
|
|
4
|
+
* lets each test express only the behavior it cares about.
|
|
5
|
+
*/
|
|
6
|
+
import { type Component } from 'svelte';
|
|
7
|
+
import type { AvailableTool, ToolsDockContext } from '../types.js';
|
|
8
|
+
interface ToolProp {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
badge?: number | string | null;
|
|
12
|
+
icon?: string;
|
|
13
|
+
iconComponent?: Component;
|
|
14
|
+
}
|
|
15
|
+
interface Props {
|
|
16
|
+
tools: ToolProp[];
|
|
17
|
+
fetchAvailability?: (ctx: ToolsDockContext | null) => Promise<AvailableTool[]>;
|
|
18
|
+
initialOpen?: boolean;
|
|
19
|
+
layout?: 'rail' | 'topbar';
|
|
20
|
+
/** Activate this tool on mount via dock.open(id). */
|
|
21
|
+
activateOnMount?: string | null;
|
|
22
|
+
/** Call dock.setContext(...) on mount. */
|
|
23
|
+
setContextOnMount?: ToolsDockContext | null;
|
|
24
|
+
/** Force the dock open after setup (for empty-state assertions). */
|
|
25
|
+
forceOpen?: boolean;
|
|
26
|
+
/** Pass-through `context` prop to <ToolsDock>. */
|
|
27
|
+
context?: ToolsDockContext | null;
|
|
28
|
+
}
|
|
29
|
+
declare const RenderHarness: Component<Props, {}, "">;
|
|
30
|
+
type RenderHarness = ReturnType<typeof RenderHarness>;
|
|
31
|
+
export default RenderHarness;
|
|
32
|
+
//# sourceMappingURL=render-harness.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-harness.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/__tests__/render-harness.svelte.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,QAAQ,CAAC;AAGjD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGnE,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B;AAED,UAAU,KAAK;IACb,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,iBAAiB,CAAC,EAAE,CAClB,GAAG,EAAE,gBAAgB,GAAG,IAAI,KACzB,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC9B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3B,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC5C,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kDAAkD;IAClD,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACnC;AA2CD,QAAA,MAAM,aAAa,0BAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the <ToolsDock> renderer.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that rail buttons are rendered for available tools, that clicking
|
|
5
|
+
* one opens the panel and activates the tool, that the close button closes
|
|
6
|
+
* the panel, and that the empty state renders when fetchAvailability filters
|
|
7
|
+
* everything away.
|
|
8
|
+
*/
|
|
9
|
+
import { flushSync, mount, tick, unmount } from 'svelte';
|
|
10
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
11
|
+
import ContextForwardingHarness from './context-forwarding-harness.svelte';
|
|
12
|
+
import RenderHarness from './render-harness.svelte';
|
|
13
|
+
import TestIcon from './test-icon.svelte';
|
|
14
|
+
const mountedComponents = [];
|
|
15
|
+
function render(props) {
|
|
16
|
+
const target = document.createElement('div');
|
|
17
|
+
document.body.appendChild(target);
|
|
18
|
+
const component = mount(RenderHarness, { target, props });
|
|
19
|
+
mountedComponents.push(component);
|
|
20
|
+
flushSync();
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
while (mountedComponents.length > 0) {
|
|
25
|
+
const c = mountedComponents.pop();
|
|
26
|
+
if (c)
|
|
27
|
+
unmount(c);
|
|
28
|
+
}
|
|
29
|
+
while (document.body.firstChild) {
|
|
30
|
+
document.body.removeChild(document.body.firstChild);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
describe('<ToolsDock> rail layout', () => {
|
|
34
|
+
it('renders a rail button for every available tool', () => {
|
|
35
|
+
const target = render({
|
|
36
|
+
tools: [
|
|
37
|
+
{ id: 'chat', label: 'Chat' },
|
|
38
|
+
{ id: 'jobs', label: 'Jobs' },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
const buttons = target.querySelectorAll('.tools-dock__rail-button');
|
|
42
|
+
expect(buttons.length).toBe(2);
|
|
43
|
+
expect(buttons[0].getAttribute('aria-label')).toBe('Chat tool');
|
|
44
|
+
expect(buttons[1].getAttribute('aria-label')).toBe('Jobs tool');
|
|
45
|
+
});
|
|
46
|
+
it('opens the panel and marks the active button with aria-pressed when clicked', () => {
|
|
47
|
+
const target = render({
|
|
48
|
+
tools: [
|
|
49
|
+
{ id: 'chat', label: 'Chat' },
|
|
50
|
+
{ id: 'jobs', label: 'Jobs' },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
const chatBtn = target.querySelectorAll('.tools-dock__rail-button')[0];
|
|
54
|
+
chatBtn.click();
|
|
55
|
+
flushSync();
|
|
56
|
+
expect(chatBtn.classList.contains('active')).toBe(true);
|
|
57
|
+
// aria-pressed is the right semantics for a toggle button. aria-current
|
|
58
|
+
// is reserved for navigation sets (breadcrumbs, paginators, etc.) — using
|
|
59
|
+
// both on the same control conflicts and should not appear here.
|
|
60
|
+
expect(chatBtn.getAttribute('aria-pressed')).toBe('true');
|
|
61
|
+
expect(chatBtn.getAttribute('aria-current')).toBeNull();
|
|
62
|
+
// Panel header reflects the active tool.
|
|
63
|
+
const heading = target.querySelector('.tools-dock__panel-header h3');
|
|
64
|
+
expect(heading?.textContent).toBe('Chat');
|
|
65
|
+
});
|
|
66
|
+
it('panel exposes role="dialog" and is inert when closed', () => {
|
|
67
|
+
const target = render({
|
|
68
|
+
tools: [{ id: 'chat', label: 'Chat' }],
|
|
69
|
+
});
|
|
70
|
+
const panel = target.querySelector('.tools-dock__panel');
|
|
71
|
+
expect(panel?.getAttribute('role')).toBe('dialog');
|
|
72
|
+
// Closed state: inert is set so tab order skips the off-screen panel.
|
|
73
|
+
// Svelte 5 sets `inert` via the IDL property; jsdom reflects the property
|
|
74
|
+
// rather than the HTML attribute, so check both.
|
|
75
|
+
expect(panel?.inert === true || panel?.hasAttribute('inert')).toBe(true);
|
|
76
|
+
const chatBtn = target.querySelector('.tools-dock__rail-button');
|
|
77
|
+
chatBtn?.click();
|
|
78
|
+
flushSync();
|
|
79
|
+
// Open: inert is cleared so descendants are focusable.
|
|
80
|
+
expect(panel?.inert === false && !panel?.hasAttribute('inert')).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
it('closes when the close button is clicked', () => {
|
|
83
|
+
const target = render({
|
|
84
|
+
tools: [{ id: 'chat', label: 'Chat' }],
|
|
85
|
+
initialOpen: true,
|
|
86
|
+
activateOnMount: 'chat',
|
|
87
|
+
});
|
|
88
|
+
const aside = target.querySelector('.tools-dock--rail');
|
|
89
|
+
expect(aside?.classList.contains('tools-dock--open')).toBe(true);
|
|
90
|
+
const closeBtn = target.querySelector('.tools-dock__close');
|
|
91
|
+
closeBtn?.click();
|
|
92
|
+
flushSync();
|
|
93
|
+
expect(aside?.classList.contains('tools-dock--open')).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it('forwards a late-populated `context` prop into the dock', async () => {
|
|
96
|
+
const target = document.createElement('div');
|
|
97
|
+
document.body.appendChild(target);
|
|
98
|
+
let dock = null;
|
|
99
|
+
let setContextProp = null;
|
|
100
|
+
const component = mount(ContextForwardingHarness, {
|
|
101
|
+
target,
|
|
102
|
+
props: {
|
|
103
|
+
onReady: (api) => {
|
|
104
|
+
dock = api.dock;
|
|
105
|
+
setContextProp = api.setContextProp;
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
mountedComponents.push(component);
|
|
110
|
+
flushSync();
|
|
111
|
+
// Initial value is undefined — nothing forwarded yet.
|
|
112
|
+
expect(dock?.context).toBeNull();
|
|
113
|
+
// Populate the prop after mount (simulating async route data arrival).
|
|
114
|
+
setContextProp?.({ type: 'route', title: 'Hello' });
|
|
115
|
+
flushSync();
|
|
116
|
+
await tick();
|
|
117
|
+
expect(dock?.context).toEqual({ type: 'route', title: 'Hello' });
|
|
118
|
+
// Change the prop again — the effect should run idempotently.
|
|
119
|
+
setContextProp?.({ type: 'route', title: 'World' });
|
|
120
|
+
flushSync();
|
|
121
|
+
await tick();
|
|
122
|
+
expect(dock?.context).toEqual({ type: 'route', title: 'World' });
|
|
123
|
+
});
|
|
124
|
+
describe('rail glyph rendering', () => {
|
|
125
|
+
it('renders ToolDef.iconComponent inside the rail glyph when provided', () => {
|
|
126
|
+
const target = render({
|
|
127
|
+
tools: [{ id: 'chat', label: 'Chat', iconComponent: TestIcon }],
|
|
128
|
+
});
|
|
129
|
+
const glyph = target.querySelector('.tools-dock__rail-glyph');
|
|
130
|
+
expect(glyph).not.toBeNull();
|
|
131
|
+
// iconComponent rendered → SVG present, first-letter fallback absent.
|
|
132
|
+
const svg = glyph?.querySelector('svg[data-testid="custom-icon"]');
|
|
133
|
+
expect(svg).not.toBeNull();
|
|
134
|
+
// No stray "C" letter text node — only the icon component.
|
|
135
|
+
expect(glyph?.textContent?.trim()).toBe('');
|
|
136
|
+
});
|
|
137
|
+
it('renders ToolDef.icon string when no iconComponent is provided', () => {
|
|
138
|
+
const target = render({
|
|
139
|
+
tools: [{ id: 'chat', label: 'Chat', icon: 'X' }],
|
|
140
|
+
});
|
|
141
|
+
const glyph = target.querySelector('.tools-dock__rail-glyph');
|
|
142
|
+
expect(glyph?.textContent?.trim()).toBe('X');
|
|
143
|
+
// No iconComponent rendered.
|
|
144
|
+
expect(glyph?.querySelector('svg')).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
it('falls back to label.charAt(0).toUpperCase() when neither iconComponent nor icon is set', () => {
|
|
147
|
+
const target = render({
|
|
148
|
+
tools: [{ id: 'chat', label: 'chat' }],
|
|
149
|
+
});
|
|
150
|
+
const glyph = target.querySelector('.tools-dock__rail-glyph');
|
|
151
|
+
// First letter, uppercased — existing behaviour preserved.
|
|
152
|
+
expect(glyph?.textContent?.trim()).toBe('C');
|
|
153
|
+
});
|
|
154
|
+
it('iconComponent takes precedence over icon string when both are provided', () => {
|
|
155
|
+
const target = render({
|
|
156
|
+
tools: [
|
|
157
|
+
{ id: 'chat', label: 'Chat', icon: 'X', iconComponent: TestIcon },
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
const glyph = target.querySelector('.tools-dock__rail-glyph');
|
|
161
|
+
// iconComponent wins; the icon string is not rendered.
|
|
162
|
+
expect(glyph?.querySelector('svg[data-testid="custom-icon"]')).not.toBeNull();
|
|
163
|
+
expect(glyph?.textContent?.trim()).toBe('');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('topbar button icon rendering', () => {
|
|
167
|
+
it('renders ToolDef.iconComponent before the label in topbar layout', () => {
|
|
168
|
+
const target = render({
|
|
169
|
+
layout: 'topbar',
|
|
170
|
+
tools: [{ id: 'chat', label: 'Chat', iconComponent: TestIcon }],
|
|
171
|
+
});
|
|
172
|
+
const btn = target.querySelector('.tools-dock__topbar-button');
|
|
173
|
+
expect(btn).not.toBeNull();
|
|
174
|
+
const icon = btn?.querySelector('.tools-dock__topbar-button-icon');
|
|
175
|
+
const label = btn?.querySelector('.tools-dock__topbar-button-label');
|
|
176
|
+
expect(icon).not.toBeNull();
|
|
177
|
+
expect(label?.textContent).toBe('Chat');
|
|
178
|
+
// Icon hosts the test SVG.
|
|
179
|
+
expect(icon?.querySelector('svg[data-testid="custom-icon"]')).not.toBeNull();
|
|
180
|
+
// Icon precedes the label in document order so screen-magnifier /
|
|
181
|
+
// visual layout matches reading order.
|
|
182
|
+
if (icon && label) {
|
|
183
|
+
expect(icon.compareDocumentPosition(label) &
|
|
184
|
+
Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
it('renders ToolDef.icon string before the label in topbar layout', () => {
|
|
188
|
+
const target = render({
|
|
189
|
+
layout: 'topbar',
|
|
190
|
+
tools: [{ id: 'chat', label: 'Chat', icon: 'X' }],
|
|
191
|
+
});
|
|
192
|
+
const btn = target.querySelector('.tools-dock__topbar-button');
|
|
193
|
+
const icon = btn?.querySelector('.tools-dock__topbar-button-icon');
|
|
194
|
+
expect(icon?.textContent?.trim()).toBe('X');
|
|
195
|
+
// No SVG when only the string icon was supplied.
|
|
196
|
+
expect(icon?.querySelector('svg')).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
it('omits the icon span entirely when neither iconComponent nor icon is set', () => {
|
|
199
|
+
const target = render({
|
|
200
|
+
layout: 'topbar',
|
|
201
|
+
tools: [{ id: 'chat', label: 'Chat' }],
|
|
202
|
+
});
|
|
203
|
+
const btn = target.querySelector('.tools-dock__topbar-button');
|
|
204
|
+
const icon = btn?.querySelector('.tools-dock__topbar-button-icon');
|
|
205
|
+
const label = btn?.querySelector('.tools-dock__topbar-button-label');
|
|
206
|
+
// Topbar buttons stay label-only when no icon is provided — unlike
|
|
207
|
+
// the rail, where we fall back to the first letter of the label.
|
|
208
|
+
expect(icon).toBeNull();
|
|
209
|
+
expect(label?.textContent).toBe('Chat');
|
|
210
|
+
});
|
|
211
|
+
it('iconComponent takes precedence over icon string in topbar layout', () => {
|
|
212
|
+
const target = render({
|
|
213
|
+
layout: 'topbar',
|
|
214
|
+
tools: [
|
|
215
|
+
{ id: 'chat', label: 'Chat', icon: 'X', iconComponent: TestIcon },
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
const btn = target.querySelector('.tools-dock__topbar-button');
|
|
219
|
+
const icon = btn?.querySelector('.tools-dock__topbar-button-icon');
|
|
220
|
+
// SVG wins, the string "X" must not appear.
|
|
221
|
+
expect(icon?.querySelector('svg[data-testid="custom-icon"]')).not.toBeNull();
|
|
222
|
+
expect(icon?.textContent?.trim()).toBe('');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
it('renders an empty state when no tools are available', async () => {
|
|
226
|
+
const target = render({
|
|
227
|
+
tools: [{ id: 'chat', label: 'Chat' }],
|
|
228
|
+
// fetchAvailability that returns nothing means no tools surface.
|
|
229
|
+
fetchAvailability: async () => [],
|
|
230
|
+
activateOnMount: null,
|
|
231
|
+
setContextOnMount: { type: 'route' },
|
|
232
|
+
forceOpen: true,
|
|
233
|
+
});
|
|
234
|
+
// Wait for the microtask resolving fetchAvailability.
|
|
235
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
236
|
+
flushSync();
|
|
237
|
+
const buttons = target.querySelectorAll('.tools-dock__rail-button');
|
|
238
|
+
expect(buttons.length).toBe(0);
|
|
239
|
+
// When forced open, the panel body renders the empty state.
|
|
240
|
+
const empty = target.querySelector('.tools-dock__empty');
|
|
241
|
+
expect(empty?.textContent).toContain('No tools');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Test harness for `bind:mobileNavOpen` on `RoleShell`. Mirrors the
|
|
4
|
+
* `workspace-shell-bind-harness` pattern — exposes the bindable drawer
|
|
5
|
+
* state so tests can drive and observe it from outside the component.
|
|
6
|
+
*/
|
|
7
|
+
import { createRawSnippet } from 'svelte';
|
|
8
|
+
import RoleShell from '../RoleShell.svelte';
|
|
9
|
+
import type { RoleConfig } from '../types.js';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
roles: RoleConfig[];
|
|
13
|
+
currentRole: string;
|
|
14
|
+
currentPath?: string;
|
|
15
|
+
initial?: boolean;
|
|
16
|
+
onNavigate?: () => void;
|
|
17
|
+
onReady?: (controls: {
|
|
18
|
+
setMobileNavOpen: (next: boolean) => void;
|
|
19
|
+
getMobileNavOpen: () => boolean;
|
|
20
|
+
}) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
roles,
|
|
25
|
+
currentRole,
|
|
26
|
+
currentPath = '/',
|
|
27
|
+
initial = false,
|
|
28
|
+
onNavigate,
|
|
29
|
+
onReady,
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
// This harness intentionally snapshots the initial bindable value.
|
|
33
|
+
// svelte-ignore state_referenced_locally
|
|
34
|
+
let mobileNavOpen = $state(initial);
|
|
35
|
+
|
|
36
|
+
const content = createRawSnippet(() => ({
|
|
37
|
+
render: () => '<span>content</span>',
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// This harness intentionally snapshots the callback during setup.
|
|
41
|
+
// svelte-ignore state_referenced_locally
|
|
42
|
+
onReady?.({
|
|
43
|
+
setMobileNavOpen: (next: boolean) => {
|
|
44
|
+
mobileNavOpen = next;
|
|
45
|
+
},
|
|
46
|
+
getMobileNavOpen: () => mobileNavOpen,
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<RoleShell
|
|
51
|
+
{roles}
|
|
52
|
+
{currentRole}
|
|
53
|
+
{currentPath}
|
|
54
|
+
{onNavigate}
|
|
55
|
+
bind:mobileNavOpen
|
|
56
|
+
>
|
|
57
|
+
{@render content()}
|
|
58
|
+
</RoleShell>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RoleConfig } from '../types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
roles: RoleConfig[];
|
|
4
|
+
currentRole: string;
|
|
5
|
+
currentPath?: string;
|
|
6
|
+
initial?: boolean;
|
|
7
|
+
onNavigate?: () => void;
|
|
8
|
+
onReady?: (controls: {
|
|
9
|
+
setMobileNavOpen: (next: boolean) => void;
|
|
10
|
+
getMobileNavOpen: () => boolean;
|
|
11
|
+
}) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const RoleShellBindHarness: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type RoleShellBindHarness = ReturnType<typeof RoleShellBindHarness>;
|
|
15
|
+
export default RoleShellBindHarness;
|
|
16
|
+
//# sourceMappingURL=role-shell-bind-harness.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-shell-bind-harness.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/__tests__/role-shell-bind-harness.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,UAAU,KAAK;IACb,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE;QACnB,gBAAgB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;QAC1C,gBAAgB,EAAE,MAAM,OAAO,CAAC;KACjC,KAAK,IAAI,CAAC;CACZ;AAyCD,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Test harness for verifying RoleShell re-renders when `currentRole` changes.
|
|
4
|
+
*
|
|
5
|
+
* Exposes a `setCurrentRole` setter via the `onReady` callback so the test
|
|
6
|
+
* can flip the active role from the outside, then assert the rendered nav
|
|
7
|
+
* tree + role color update accordingly.
|
|
8
|
+
*/
|
|
9
|
+
import { createRawSnippet } from 'svelte';
|
|
10
|
+
import RoleShell from '../RoleShell.svelte';
|
|
11
|
+
import type { RoleConfig } from '../types.js';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
roles: RoleConfig[];
|
|
15
|
+
initialRole: string;
|
|
16
|
+
currentPath: string;
|
|
17
|
+
onReady?: (controls: { setCurrentRole: (id: string) => void }) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { roles, initialRole, currentPath, onReady }: Props = $props();
|
|
21
|
+
|
|
22
|
+
// This harness intentionally snapshots the initial role.
|
|
23
|
+
// svelte-ignore state_referenced_locally
|
|
24
|
+
let currentRole = $state(initialRole);
|
|
25
|
+
|
|
26
|
+
const content = createRawSnippet(() => ({
|
|
27
|
+
render: () => '<span>switch-harness</span>',
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// This harness intentionally snapshots the callback during setup.
|
|
31
|
+
// svelte-ignore state_referenced_locally
|
|
32
|
+
onReady?.({
|
|
33
|
+
setCurrentRole: (id: string) => {
|
|
34
|
+
currentRole = id;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<RoleShell {roles} {currentRole} {currentPath}>
|
|
40
|
+
{@render content()}
|
|
41
|
+
</RoleShell>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RoleConfig } from '../types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
roles: RoleConfig[];
|
|
4
|
+
initialRole: string;
|
|
5
|
+
currentPath: string;
|
|
6
|
+
onReady?: (controls: {
|
|
7
|
+
setCurrentRole: (id: string) => void;
|
|
8
|
+
}) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const RoleShellSwitchHarness: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type RoleShellSwitchHarness = ReturnType<typeof RoleShellSwitchHarness>;
|
|
12
|
+
export default RoleShellSwitchHarness;
|
|
13
|
+
//# sourceMappingURL=role-shell-switch-harness.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"role-shell-switch-harness.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/__tests__/role-shell-switch-harness.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,UAAU,KAAK;IACb,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE;QAAE,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;CACxE;AAiCD,QAAA,MAAM,sBAAsB,2CAAwC,CAAC;AACrE,KAAK,sBAAsB,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACxE,eAAe,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Trivial icon component used by render-tools-dock tests to verify that
|
|
4
|
+
* `ToolDef.iconComponent` is rendered inside `.tools-dock__rail-glyph`
|
|
5
|
+
* instead of the first-letter fallback.
|
|
6
|
+
*/
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<svg
|
|
10
|
+
data-testid="custom-icon"
|
|
11
|
+
width="18"
|
|
12
|
+
height="18"
|
|
13
|
+
viewBox="0 0 24 24"
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
>
|
|
16
|
+
<circle cx="12" cy="12" r="10" />
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const TestIcon: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type TestIcon = InstanceType<typeof TestIcon>;
|
|
18
|
+
export default TestIcon;
|
|
19
|
+
//# sourceMappingURL=test-icon.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-icon.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/__tests__/test-icon.svelte.ts"],"names":[],"mappings":"AAiBA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,QAAQ;;kBAA+E,CAAC;AAC5E,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Fixture for the typed tool-component pattern documented on
|
|
4
|
+
* `ToolDef.component`. The dock erases tool-specific types at registration,
|
|
5
|
+
* but the tool component itself locally annotates `context` with the
|
|
6
|
+
* narrowed `ToolsDockContext<TData, TActions>` shape — see
|
|
7
|
+
* `register-typed-tool.ts` for the registration site that must accept this
|
|
8
|
+
* component without a cast.
|
|
9
|
+
*
|
|
10
|
+
* If any of the type assertions below fail at `svelte-check` / `tsc` time,
|
|
11
|
+
* either the `ToolDef.component` erasure relaxation or the
|
|
12
|
+
* `ToolsDockContext` action-map constraint regressed.
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolsDockApi, ToolsDockContext } from '../../types.js';
|
|
15
|
+
import type { MyActions, MyData } from './typed-tool-types.js';
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
context,
|
|
19
|
+
dock,
|
|
20
|
+
}: {
|
|
21
|
+
context: ToolsDockContext<MyData, MyActions> | null;
|
|
22
|
+
dock: ToolsDockApi;
|
|
23
|
+
} = $props();
|
|
24
|
+
|
|
25
|
+
// These reads must compile — they exercise the typed `data` / `actions`
|
|
26
|
+
// surface that motivated the U3/U4/U5 work.
|
|
27
|
+
const slug = $derived(context?.data?.siteSlug ?? '');
|
|
28
|
+
const onSave = () => context?.actions?.triggerSave();
|
|
29
|
+
const onReview = () => context?.actions?.triggerReview('manual');
|
|
30
|
+
|
|
31
|
+
// Dock reference must still be usable.
|
|
32
|
+
const isOpen = $derived(dock.isOpen);
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<section data-tool="typed-tool" data-slug={slug} data-open={isOpen}>
|
|
36
|
+
<button type="button" onclick={onSave}>Save</button>
|
|
37
|
+
<button type="button" onclick={onReview}>Review</button>
|
|
38
|
+
</section>
|