@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,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the server-side dock availability gate composer
|
|
3
|
+
* (happyvertical/smrt#1226 Phase 4c).
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* - tools without `gates` are unconditionally included (back-compat)
|
|
7
|
+
* - single passing / failing gate
|
|
8
|
+
* - AND semantics across multiple gates
|
|
9
|
+
* - sync and async evaluators
|
|
10
|
+
* - unknown-prefix error message (loud-fail)
|
|
11
|
+
* - context pass-through
|
|
12
|
+
* - edge cases (empty arrays / maps)
|
|
13
|
+
* - label / badge passthrough into AvailableTool
|
|
14
|
+
*/
|
|
15
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
16
|
+
import { composeDockAvailability } from '../compose-availability.js';
|
|
17
|
+
const alwaysTrue = () => true;
|
|
18
|
+
const alwaysFalse = () => false;
|
|
19
|
+
const asyncTrue = async () => true;
|
|
20
|
+
const asyncFalse = async () => false;
|
|
21
|
+
describe('composeDockAvailability', () => {
|
|
22
|
+
it('includes tools without a `gates` field unconditionally', async () => {
|
|
23
|
+
const result = await composeDockAvailability({
|
|
24
|
+
tools: [{ id: 'chat', label: 'Chat' }],
|
|
25
|
+
context: {},
|
|
26
|
+
evaluators: {},
|
|
27
|
+
});
|
|
28
|
+
expect(result).toHaveLength(1);
|
|
29
|
+
expect(result[0]?.id).toBe('chat');
|
|
30
|
+
});
|
|
31
|
+
it('includes tools with an empty `gates` array unconditionally', async () => {
|
|
32
|
+
const result = await composeDockAvailability({
|
|
33
|
+
tools: [{ id: 'chat', label: 'Chat', gates: [] }],
|
|
34
|
+
context: {},
|
|
35
|
+
evaluators: {},
|
|
36
|
+
});
|
|
37
|
+
expect(result).toHaveLength(1);
|
|
38
|
+
expect(result[0]?.id).toBe('chat');
|
|
39
|
+
});
|
|
40
|
+
it('includes a tool whose single gate evaluates to true', async () => {
|
|
41
|
+
const result = await composeDockAvailability({
|
|
42
|
+
tools: [{ id: 'video', label: 'Video', gates: ['feature:video-tools'] }],
|
|
43
|
+
context: {},
|
|
44
|
+
evaluators: { feature: alwaysTrue },
|
|
45
|
+
});
|
|
46
|
+
expect(result).toHaveLength(1);
|
|
47
|
+
expect(result[0]?.id).toBe('video');
|
|
48
|
+
});
|
|
49
|
+
it('excludes a tool whose single gate evaluates to false', async () => {
|
|
50
|
+
const result = await composeDockAvailability({
|
|
51
|
+
tools: [{ id: 'video', label: 'Video', gates: ['feature:video-tools'] }],
|
|
52
|
+
context: {},
|
|
53
|
+
evaluators: { feature: alwaysFalse },
|
|
54
|
+
});
|
|
55
|
+
expect(result).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
it('requires ALL gates to pass (AND semantics)', async () => {
|
|
58
|
+
const result = await composeDockAvailability({
|
|
59
|
+
tools: [
|
|
60
|
+
{
|
|
61
|
+
id: 'restricted',
|
|
62
|
+
label: 'Restricted',
|
|
63
|
+
gates: ['permission:foo', 'feature:bar'],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
context: {},
|
|
67
|
+
evaluators: { permission: alwaysTrue, feature: alwaysFalse },
|
|
68
|
+
});
|
|
69
|
+
expect(result).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
it('includes a tool only when EVERY gate passes', async () => {
|
|
72
|
+
const result = await composeDockAvailability({
|
|
73
|
+
tools: [
|
|
74
|
+
{
|
|
75
|
+
id: 'allowed',
|
|
76
|
+
label: 'Allowed',
|
|
77
|
+
gates: ['permission:foo', 'feature:bar'],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
context: {},
|
|
81
|
+
evaluators: { permission: alwaysTrue, feature: asyncTrue },
|
|
82
|
+
});
|
|
83
|
+
expect(result).toHaveLength(1);
|
|
84
|
+
expect(result[0]?.id).toBe('allowed');
|
|
85
|
+
});
|
|
86
|
+
it('accepts sync evaluators that return a plain boolean', async () => {
|
|
87
|
+
const result = await composeDockAvailability({
|
|
88
|
+
tools: [{ id: 't', label: 'T', gates: ['x:y'] }],
|
|
89
|
+
context: {},
|
|
90
|
+
evaluators: { x: (_id, _ctx) => true },
|
|
91
|
+
});
|
|
92
|
+
expect(result).toHaveLength(1);
|
|
93
|
+
});
|
|
94
|
+
it('accepts async evaluators that return a Promise<boolean>', async () => {
|
|
95
|
+
const result = await composeDockAvailability({
|
|
96
|
+
tools: [{ id: 't', label: 'T', gates: ['x:y'] }],
|
|
97
|
+
context: {},
|
|
98
|
+
evaluators: { x: async () => true },
|
|
99
|
+
});
|
|
100
|
+
expect(result).toHaveLength(1);
|
|
101
|
+
});
|
|
102
|
+
it('throws with a clear message when a gate prefix has no evaluator', async () => {
|
|
103
|
+
await expect(composeDockAvailability({
|
|
104
|
+
tools: [
|
|
105
|
+
{ id: 'video', label: 'Video', gates: ['feature:video-tools'] },
|
|
106
|
+
],
|
|
107
|
+
context: {},
|
|
108
|
+
evaluators: { permission: alwaysTrue },
|
|
109
|
+
})).rejects.toThrow(/No evaluator registered for gate prefix "feature".*tool "video".*gate "feature:video-tools".*Available prefixes: permission/);
|
|
110
|
+
});
|
|
111
|
+
it('reports `(none)` for available prefixes when the evaluators map is empty', async () => {
|
|
112
|
+
await expect(composeDockAvailability({
|
|
113
|
+
tools: [
|
|
114
|
+
{ id: 'video', label: 'Video', gates: ['feature:video-tools'] },
|
|
115
|
+
],
|
|
116
|
+
context: {},
|
|
117
|
+
evaluators: {},
|
|
118
|
+
})).rejects.toThrow(/Available prefixes: \(none\)/);
|
|
119
|
+
});
|
|
120
|
+
it('passes the supplied `context` through to each evaluator', async () => {
|
|
121
|
+
const ctx = { userId: 'u-1', tenantId: 't-1' };
|
|
122
|
+
const permission = vi.fn(() => true);
|
|
123
|
+
const feature = vi.fn(() => true);
|
|
124
|
+
await composeDockAvailability({
|
|
125
|
+
tools: [
|
|
126
|
+
{
|
|
127
|
+
id: 'tool',
|
|
128
|
+
label: 'Tool',
|
|
129
|
+
gates: ['permission:articles.publish', 'feature:video-tools'],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
context: ctx,
|
|
133
|
+
evaluators: { permission, feature },
|
|
134
|
+
});
|
|
135
|
+
expect(permission).toHaveBeenCalledWith('permission:articles.publish', ctx);
|
|
136
|
+
expect(feature).toHaveBeenCalledWith('feature:video-tools', ctx);
|
|
137
|
+
});
|
|
138
|
+
it('returns an empty array for an empty `tools` input', async () => {
|
|
139
|
+
const result = await composeDockAvailability({
|
|
140
|
+
tools: [],
|
|
141
|
+
context: {},
|
|
142
|
+
evaluators: {},
|
|
143
|
+
});
|
|
144
|
+
expect(result).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
it('handles an empty `evaluators` map fine when no tools have gates', async () => {
|
|
147
|
+
const result = await composeDockAvailability({
|
|
148
|
+
tools: [
|
|
149
|
+
{ id: 'a', label: 'A' },
|
|
150
|
+
{ id: 'b', label: 'B' },
|
|
151
|
+
],
|
|
152
|
+
context: {},
|
|
153
|
+
evaluators: {},
|
|
154
|
+
});
|
|
155
|
+
expect(result.map((t) => t.id)).toEqual(['a', 'b']);
|
|
156
|
+
});
|
|
157
|
+
it('passes through `label` and `badge` from the tool def to AvailableTool', async () => {
|
|
158
|
+
const tools = [
|
|
159
|
+
{ id: 'inbox', label: 'Inbox', badge: 3 },
|
|
160
|
+
{ id: 'tasks', label: 'Tasks', badge: 'new' },
|
|
161
|
+
{ id: 'plain', label: 'Plain' }, // no badge → omitted in output
|
|
162
|
+
];
|
|
163
|
+
const result = await composeDockAvailability({
|
|
164
|
+
tools,
|
|
165
|
+
context: {},
|
|
166
|
+
evaluators: {},
|
|
167
|
+
});
|
|
168
|
+
expect(result).toEqual([
|
|
169
|
+
{ id: 'inbox', label: 'Inbox', badge: 3 },
|
|
170
|
+
{ id: 'tasks', label: 'Tasks', badge: 'new' },
|
|
171
|
+
{ id: 'plain', label: 'Plain' },
|
|
172
|
+
]);
|
|
173
|
+
});
|
|
174
|
+
it('omits a missing `label` from the AvailableTool result (lets the dock fall back)', async () => {
|
|
175
|
+
const result = await composeDockAvailability({
|
|
176
|
+
tools: [{ id: 'no-label' }],
|
|
177
|
+
context: {},
|
|
178
|
+
evaluators: {},
|
|
179
|
+
});
|
|
180
|
+
// No `label` key at all — `defineToolsDock.applyAvailability` falls
|
|
181
|
+
// back to the registered ToolDef metadata.
|
|
182
|
+
expect(result[0]).toEqual({ id: 'no-label' });
|
|
183
|
+
expect(result[0]).not.toHaveProperty('label');
|
|
184
|
+
expect(result[0]).not.toHaveProperty('badge');
|
|
185
|
+
});
|
|
186
|
+
it('omits `badge` when the tool def has no badge (no implicit clear)', async () => {
|
|
187
|
+
const result = await composeDockAvailability({
|
|
188
|
+
tools: [{ id: 'chat', label: 'Chat', gates: [] }],
|
|
189
|
+
context: {},
|
|
190
|
+
evaluators: {},
|
|
191
|
+
});
|
|
192
|
+
// The dock treats `badge: null` as an explicit clear of the
|
|
193
|
+
// registered default badge. Absence must remain absence.
|
|
194
|
+
expect(result[0]).toEqual({ id: 'chat', label: 'Chat' });
|
|
195
|
+
expect(result[0]).not.toHaveProperty('badge');
|
|
196
|
+
});
|
|
197
|
+
it('preserves explicit `badge: null` when the caller does set it', async () => {
|
|
198
|
+
const result = await composeDockAvailability({
|
|
199
|
+
tools: [{ id: 'reset', label: 'Reset', badge: null }],
|
|
200
|
+
context: {},
|
|
201
|
+
evaluators: {},
|
|
202
|
+
});
|
|
203
|
+
expect(result[0]).toEqual({ id: 'reset', label: 'Reset', badge: null });
|
|
204
|
+
});
|
|
205
|
+
it('filters a mixed input correctly (gated + ungated, pass + fail)', async () => {
|
|
206
|
+
const result = await composeDockAvailability({
|
|
207
|
+
tools: [
|
|
208
|
+
{ id: 'always', label: 'Always' },
|
|
209
|
+
{ id: 'allowed', label: 'Allowed', gates: ['permission:foo'] },
|
|
210
|
+
{ id: 'denied', label: 'Denied', gates: ['permission:bar'] },
|
|
211
|
+
{ id: 'flagged', label: 'Flagged', gates: ['feature:on'] },
|
|
212
|
+
],
|
|
213
|
+
context: { userId: 'u-1' },
|
|
214
|
+
evaluators: {
|
|
215
|
+
permission: async (id) => id === 'permission:foo',
|
|
216
|
+
feature: asyncTrue,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
expect(result.map((t) => t.id)).toEqual(['always', 'allowed', 'flagged']);
|
|
220
|
+
});
|
|
221
|
+
it('still throws on unknown prefix even when other gates on the same tool would have passed', async () => {
|
|
222
|
+
await expect(composeDockAvailability({
|
|
223
|
+
tools: [
|
|
224
|
+
{
|
|
225
|
+
id: 'mixed',
|
|
226
|
+
label: 'Mixed',
|
|
227
|
+
gates: ['permission:ok', 'mystery:??'],
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
context: {},
|
|
231
|
+
evaluators: { permission: alwaysTrue },
|
|
232
|
+
})).rejects.toThrow(/gate "mystery:\?\?"/);
|
|
233
|
+
});
|
|
234
|
+
it('treats async evaluator rejections as errors (does not swallow)', async () => {
|
|
235
|
+
await expect(composeDockAvailability({
|
|
236
|
+
tools: [{ id: 't', label: 'T', gates: ['boom:bar'] }],
|
|
237
|
+
context: {},
|
|
238
|
+
evaluators: {
|
|
239
|
+
boom: async () => {
|
|
240
|
+
throw new Error('evaluator exploded');
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
})).rejects.toThrow('evaluator exploded');
|
|
244
|
+
});
|
|
245
|
+
it('respects falsy results from async evaluators (excludes the tool)', async () => {
|
|
246
|
+
const result = await composeDockAvailability({
|
|
247
|
+
tools: [{ id: 't', label: 'T', gates: ['x:y'] }],
|
|
248
|
+
context: {},
|
|
249
|
+
evaluators: { x: asyncFalse },
|
|
250
|
+
});
|
|
251
|
+
expect(result).toEqual([]);
|
|
252
|
+
});
|
|
253
|
+
// Regression: a gate prefix that names an inherited `Object.prototype`
|
|
254
|
+
// property (e.g. `constructor`, `toString`, `hasOwnProperty`, `valueOf`,
|
|
255
|
+
// `__proto__`) MUST NOT bypass the unknown-prefix fail-loud path. Prior
|
|
256
|
+
// to the fix, `evaluators[prefix]` resolved to a built-in function and
|
|
257
|
+
// got invoked, returning a truthy value and silently passing the gate.
|
|
258
|
+
it('throws on prototype-key gate prefixes (constructor, toString, hasOwnProperty, valueOf, __proto__)', async () => {
|
|
259
|
+
const evaluators = { permission: () => true };
|
|
260
|
+
for (const prefix of [
|
|
261
|
+
'constructor',
|
|
262
|
+
'toString',
|
|
263
|
+
'hasOwnProperty',
|
|
264
|
+
'valueOf',
|
|
265
|
+
'__proto__',
|
|
266
|
+
]) {
|
|
267
|
+
await expect(composeDockAvailability({
|
|
268
|
+
tools: [{ id: 'x', label: 'X', gates: [`${prefix}:any`] }],
|
|
269
|
+
context: {},
|
|
270
|
+
evaluators,
|
|
271
|
+
})).rejects.toThrow(/No evaluator registered for gate prefix/);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
it('throws when an evaluator entry is not a function', async () => {
|
|
275
|
+
await expect(composeDockAvailability({
|
|
276
|
+
tools: [{ id: 'x', label: 'X', gates: ['permission:any'] }],
|
|
277
|
+
context: {},
|
|
278
|
+
evaluators: {
|
|
279
|
+
// Intentional non-function evaluator — should fail loud.
|
|
280
|
+
permission: 'not-a-function',
|
|
281
|
+
},
|
|
282
|
+
})).rejects.toThrow(/No evaluator registered/);
|
|
283
|
+
});
|
|
284
|
+
// Regression for the TCtx generic parameterization (issue 1242 fix #5).
|
|
285
|
+
// Verifies the typed-context flow compiles end-to-end: the evaluator
|
|
286
|
+
// receives a typed `ctx` (string fields, not `unknown`) without a cast.
|
|
287
|
+
it('preserves the caller-supplied context type through to evaluators (TCtx generic)', async () => {
|
|
288
|
+
let observedUserId;
|
|
289
|
+
let observedTenantId;
|
|
290
|
+
const result = await composeDockAvailability({
|
|
291
|
+
tools: [{ id: 't', label: 'T', gates: ['permission:articles.publish'] }],
|
|
292
|
+
context: { userId: 'u-1', tenantId: 't-1' },
|
|
293
|
+
evaluators: {
|
|
294
|
+
permission: (gateId, ctx) => {
|
|
295
|
+
// No cast required — ctx.userId and ctx.tenantId are typed string.
|
|
296
|
+
observedUserId = ctx.userId;
|
|
297
|
+
observedTenantId = ctx.tenantId;
|
|
298
|
+
const [, slug] = gateId.split(':', 2);
|
|
299
|
+
return slug === 'articles.publish';
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
expect(result).toHaveLength(1);
|
|
304
|
+
expect(observedUserId).toBe('u-1');
|
|
305
|
+
expect(observedTenantId).toBe('t-1');
|
|
306
|
+
});
|
|
307
|
+
// Regression: an interface-style context (no string index signature) must
|
|
308
|
+
// be accepted by `TCtx`. The round-1 fixup parameterized
|
|
309
|
+
// `TCtx extends Record<string, unknown>`, which rejected interfaces under
|
|
310
|
+
// strict TS — the same trap we hit with `TActions` in #1239. The
|
|
311
|
+
// self-mapped constraint `{ [K in keyof TCtx]: unknown }` accepts
|
|
312
|
+
// interfaces without forcing the consumer to add an index signature.
|
|
313
|
+
//
|
|
314
|
+
// The compile-time guard lives in
|
|
315
|
+
// `./__tests__/typed-context-fixture/typed-context.ts` — see the JSDoc
|
|
316
|
+
// there. The runtime check below confirms the same interface-style
|
|
317
|
+
// context round-trips through evaluators at runtime.
|
|
318
|
+
it('accepts an interface-style TCtx (no string index signature)', async () => {
|
|
319
|
+
let observedUserId;
|
|
320
|
+
const result = await composeDockAvailability({
|
|
321
|
+
tools: [{ id: 't', label: 'T', gates: ['permission:any'] }],
|
|
322
|
+
context: { userId: 'u-1', tenantId: 't-1' },
|
|
323
|
+
evaluators: {
|
|
324
|
+
permission: (_gateId, ctx) => {
|
|
325
|
+
// No cast — ctx.userId is typed string.
|
|
326
|
+
observedUserId = ctx.userId;
|
|
327
|
+
return true;
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
expect(result).toHaveLength(1);
|
|
332
|
+
expect(observedUserId).toBe('u-1');
|
|
333
|
+
});
|
|
334
|
+
// Regression for fix #2 of round-3: `.every(Boolean)` fails OPEN for
|
|
335
|
+
// truthy non-booleans. `Boolean('false')`, `Boolean({})`, `Boolean([])`
|
|
336
|
+
// are all true, so an untyped evaluator returning a wrapped object, a
|
|
337
|
+
// sentinel string, or a number would silently grant access. The composer
|
|
338
|
+
// now uses strict `=== true`.
|
|
339
|
+
it('fails closed on truthy non-boolean evaluator returns', async () => {
|
|
340
|
+
// Evaluators MUST return boolean per the type, but consumers in plain
|
|
341
|
+
// JS or with untyped wrappers can slip non-boolean returns through.
|
|
342
|
+
// Verify that truthy non-booleans don't grant access.
|
|
343
|
+
const cases = [
|
|
344
|
+
{
|
|
345
|
+
id: 'string-true',
|
|
346
|
+
evaluator: () => 'true',
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: 'object',
|
|
350
|
+
evaluator: () => ({ allowed: false }),
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: 'array',
|
|
354
|
+
evaluator: () => [],
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: 'number-1',
|
|
358
|
+
evaluator: () => 1,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: 'falsestring',
|
|
362
|
+
evaluator: () => 'false',
|
|
363
|
+
},
|
|
364
|
+
];
|
|
365
|
+
for (const { id, evaluator } of cases) {
|
|
366
|
+
const result = await composeDockAvailability({
|
|
367
|
+
tools: [{ id, label: id, gates: ['permission:any'] }],
|
|
368
|
+
context: {},
|
|
369
|
+
evaluators: { permission: evaluator },
|
|
370
|
+
});
|
|
371
|
+
// fail-closed: gate doesn't pass
|
|
372
|
+
expect(result).toEqual([]);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
it('passes only on literal `true` (not truthy values)', async () => {
|
|
376
|
+
const result = await composeDockAvailability({
|
|
377
|
+
tools: [{ id: 'pass-true', label: 'Pass', gates: ['permission:any'] }],
|
|
378
|
+
context: {},
|
|
379
|
+
evaluators: { permission: () => true },
|
|
380
|
+
});
|
|
381
|
+
expect(result).toEqual([{ id: 'pass-true', label: 'Pass' }]);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time regression fixture for the `TCtx` generic on
|
|
3
|
+
* `composeDockAvailability` / `ComposeDockAvailabilityOptions` /
|
|
4
|
+
* `GateEvaluator`.
|
|
5
|
+
*
|
|
6
|
+
* This is the contract round-3 of PR #1242 is restoring:
|
|
7
|
+
*
|
|
8
|
+
* - `TCtx` must accept an INTERFACE (no string index signature). The
|
|
9
|
+
* round-1 fixup tightened the constraint to
|
|
10
|
+
* `TCtx extends Record<string, unknown>`, which silently rejected
|
|
11
|
+
* interface-style contexts — the same trap Copilot caught with
|
|
12
|
+
* `TActions` in PR #1239. The constraint is now a self-mapped
|
|
13
|
+
* `{ [K in keyof TCtx]: unknown }` (matching the `TActions` pattern in
|
|
14
|
+
* `../../types.ts`).
|
|
15
|
+
*
|
|
16
|
+
* - The default `TCtx = GateEvaluationContext` must keep existing
|
|
17
|
+
* untyped call sites compiling unchanged.
|
|
18
|
+
*
|
|
19
|
+
* - The factory's `<TCtx>` generic must flow through to each evaluator
|
|
20
|
+
* so `ctx.fieldName` is typed without a cast at the evaluator call
|
|
21
|
+
* site.
|
|
22
|
+
*
|
|
23
|
+
* Lives in a `.ts` file (NOT `.test.ts`) so:
|
|
24
|
+
* - `tsc --noEmit` (the package's `typecheck` script, run in CI via
|
|
25
|
+
* `pnpm turbo run typecheck`) does include it — the package's
|
|
26
|
+
* `tsconfig.svelte.json` includes `src/** /*.ts` and excludes only
|
|
27
|
+
* `**\/*.test.ts` and `**\/*.spec.ts`.
|
|
28
|
+
* - Vitest does NOT execute it (no `.test.ts` suffix), so the
|
|
29
|
+
* `_assertInterfaceTCtx*` symbols below never run — their bodies must
|
|
30
|
+
* just compile.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors the same pattern as
|
|
33
|
+
* `../../__tests__/typed-tool-fixture/register-typed-tool.ts` (PR #1239).
|
|
34
|
+
*/
|
|
35
|
+
import type { ComposeDockAvailabilityOptions, GateEvaluator } from '../types.js';
|
|
36
|
+
/**
|
|
37
|
+
* Interface-style context — no string index signature. Under a bare
|
|
38
|
+
* `Record<string, unknown>` constraint this fails to satisfy `TCtx`
|
|
39
|
+
* (TS2344). Under the self-mapped `{ [K in keyof TCtx]: unknown }`
|
|
40
|
+
* constraint it's accepted.
|
|
41
|
+
*/
|
|
42
|
+
interface MyInterfaceContext {
|
|
43
|
+
userId: string;
|
|
44
|
+
tenantId: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Type-only assertion: an `Options` literal parameterized over an
|
|
48
|
+
* interface-style context must compile, and the evaluator must receive a
|
|
49
|
+
* typed `ctx` (no cast) with the consumer's field types.
|
|
50
|
+
*
|
|
51
|
+
* If `TCtx` ever tightens back to `Record<string, unknown>`, this
|
|
52
|
+
* declaration fails with TS2344 ("Type 'MyInterfaceContext' does not
|
|
53
|
+
* satisfy the constraint 'Record<string, unknown>'").
|
|
54
|
+
*/
|
|
55
|
+
export declare const _assertInterfaceTCtxOptions: ComposeDockAvailabilityOptions<MyInterfaceContext>;
|
|
56
|
+
/**
|
|
57
|
+
* Type-only assertion: `GateEvaluator<MyInterfaceContext>` accepts an
|
|
58
|
+
* interface. Same regression as above, but exercised through the
|
|
59
|
+
* `GateEvaluator` alias directly (which `ComposeDockAvailabilityOptions`
|
|
60
|
+
* indexes into via `evaluators`).
|
|
61
|
+
*/
|
|
62
|
+
export declare const _assertInterfaceTCtxEvaluator: GateEvaluator<MyInterfaceContext>;
|
|
63
|
+
/**
|
|
64
|
+
* Type-only assertion: the `composeDockAvailability<TCtx>` factory
|
|
65
|
+
* accepts an interface-style `TCtx` and threads it through to evaluators
|
|
66
|
+
* without erasure. Never called at runtime — its body just has to
|
|
67
|
+
* compile.
|
|
68
|
+
*/
|
|
69
|
+
export declare function _assertInterfaceTCtxFactoryFlow(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Type-only assertion: the untyped (back-compat) call site must still
|
|
72
|
+
* compile with no generic argument and resolve `TCtx` to the default
|
|
73
|
+
* `GateEvaluationContext` (`Record<string, unknown>`). If the default
|
|
74
|
+
* ever drifted, every existing untyped consumer would break.
|
|
75
|
+
*/
|
|
76
|
+
export declare function _assertUntypedDefaultTCtx(): Promise<void>;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=typed-context-fixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typed-context-fixture.d.ts","sourceRoot":"","sources":["../../../../../src/components/workspace/server/__tests__/typed-context-fixture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,KAAK,EACV,8BAA8B,EAG9B,aAAa,EACd,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,EAAE,8BAA8B,CAAC,kBAAkB,CAYxF,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,EAAE,aAAa,CACvD,kBAAkB,CAInB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,+BAA+B,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBrE;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAO/D"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time regression fixture for the `TCtx` generic on
|
|
3
|
+
* `composeDockAvailability` / `ComposeDockAvailabilityOptions` /
|
|
4
|
+
* `GateEvaluator`.
|
|
5
|
+
*
|
|
6
|
+
* This is the contract round-3 of PR #1242 is restoring:
|
|
7
|
+
*
|
|
8
|
+
* - `TCtx` must accept an INTERFACE (no string index signature). The
|
|
9
|
+
* round-1 fixup tightened the constraint to
|
|
10
|
+
* `TCtx extends Record<string, unknown>`, which silently rejected
|
|
11
|
+
* interface-style contexts — the same trap Copilot caught with
|
|
12
|
+
* `TActions` in PR #1239. The constraint is now a self-mapped
|
|
13
|
+
* `{ [K in keyof TCtx]: unknown }` (matching the `TActions` pattern in
|
|
14
|
+
* `../../types.ts`).
|
|
15
|
+
*
|
|
16
|
+
* - The default `TCtx = GateEvaluationContext` must keep existing
|
|
17
|
+
* untyped call sites compiling unchanged.
|
|
18
|
+
*
|
|
19
|
+
* - The factory's `<TCtx>` generic must flow through to each evaluator
|
|
20
|
+
* so `ctx.fieldName` is typed without a cast at the evaluator call
|
|
21
|
+
* site.
|
|
22
|
+
*
|
|
23
|
+
* Lives in a `.ts` file (NOT `.test.ts`) so:
|
|
24
|
+
* - `tsc --noEmit` (the package's `typecheck` script, run in CI via
|
|
25
|
+
* `pnpm turbo run typecheck`) does include it — the package's
|
|
26
|
+
* `tsconfig.svelte.json` includes `src/** /*.ts` and excludes only
|
|
27
|
+
* `**\/*.test.ts` and `**\/*.spec.ts`.
|
|
28
|
+
* - Vitest does NOT execute it (no `.test.ts` suffix), so the
|
|
29
|
+
* `_assertInterfaceTCtx*` symbols below never run — their bodies must
|
|
30
|
+
* just compile.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors the same pattern as
|
|
33
|
+
* `../../__tests__/typed-tool-fixture/register-typed-tool.ts` (PR #1239).
|
|
34
|
+
*/
|
|
35
|
+
import { composeDockAvailability } from '../compose-availability.js';
|
|
36
|
+
/**
|
|
37
|
+
* Type-only assertion: an `Options` literal parameterized over an
|
|
38
|
+
* interface-style context must compile, and the evaluator must receive a
|
|
39
|
+
* typed `ctx` (no cast) with the consumer's field types.
|
|
40
|
+
*
|
|
41
|
+
* If `TCtx` ever tightens back to `Record<string, unknown>`, this
|
|
42
|
+
* declaration fails with TS2344 ("Type 'MyInterfaceContext' does not
|
|
43
|
+
* satisfy the constraint 'Record<string, unknown>'").
|
|
44
|
+
*/
|
|
45
|
+
export const _assertInterfaceTCtxOptions = {
|
|
46
|
+
tools: [],
|
|
47
|
+
context: { userId: 'u-1', tenantId: 't-1' },
|
|
48
|
+
evaluators: {
|
|
49
|
+
permission: (gateId, ctx) => {
|
|
50
|
+
// No cast required — ctx.userId / ctx.tenantId are typed `string`.
|
|
51
|
+
const _userId = ctx.userId;
|
|
52
|
+
const _tenantId = ctx.tenantId;
|
|
53
|
+
return gateId.startsWith('permission:');
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Type-only assertion: `GateEvaluator<MyInterfaceContext>` accepts an
|
|
59
|
+
* interface. Same regression as above, but exercised through the
|
|
60
|
+
* `GateEvaluator` alias directly (which `ComposeDockAvailabilityOptions`
|
|
61
|
+
* indexes into via `evaluators`).
|
|
62
|
+
*/
|
|
63
|
+
export const _assertInterfaceTCtxEvaluator = (gateId, ctx) => {
|
|
64
|
+
const _ = ctx.userId;
|
|
65
|
+
return gateId === 'permission:any';
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Type-only assertion: the `composeDockAvailability<TCtx>` factory
|
|
69
|
+
* accepts an interface-style `TCtx` and threads it through to evaluators
|
|
70
|
+
* without erasure. Never called at runtime — its body just has to
|
|
71
|
+
* compile.
|
|
72
|
+
*/
|
|
73
|
+
export async function _assertInterfaceTCtxFactoryFlow() {
|
|
74
|
+
const tools = [
|
|
75
|
+
{ id: 't', label: 'T', gates: ['permission:any'] },
|
|
76
|
+
];
|
|
77
|
+
await composeDockAvailability({
|
|
78
|
+
tools,
|
|
79
|
+
context: { userId: 'u-1', tenantId: 't-1' },
|
|
80
|
+
evaluators: {
|
|
81
|
+
permission: (_gateId, ctx) => {
|
|
82
|
+
// TYPED: ctx.userId is `string`, not `unknown`. If the factory
|
|
83
|
+
// dropped its generic on the evaluator signature, the next line
|
|
84
|
+
// would only compile as `unknown`.
|
|
85
|
+
const _userId = ctx.userId;
|
|
86
|
+
return true;
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Type-only assertion: the untyped (back-compat) call site must still
|
|
93
|
+
* compile with no generic argument and resolve `TCtx` to the default
|
|
94
|
+
* `GateEvaluationContext` (`Record<string, unknown>`). If the default
|
|
95
|
+
* ever drifted, every existing untyped consumer would break.
|
|
96
|
+
*/
|
|
97
|
+
export async function _assertUntypedDefaultTCtx() {
|
|
98
|
+
const ctx = { foo: 'bar' };
|
|
99
|
+
await composeDockAvailability({
|
|
100
|
+
tools: [{ id: 't', label: 'T' }],
|
|
101
|
+
context: ctx,
|
|
102
|
+
evaluators: {},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side dock availability gate composer.
|
|
3
|
+
*
|
|
4
|
+
* Consumer's `fetchAvailability` endpoint calls `composeDockAvailability`
|
|
5
|
+
* with the registered tools, a caller-supplied context, and a map of
|
|
6
|
+
* gate evaluators (typically wrapping `PermissionResolver` from
|
|
7
|
+
* smrt-users and `FeatureResolver` from smrt-features). It returns the
|
|
8
|
+
* filtered `AvailableTool[]` the dock's client-side `fetchAvailability`
|
|
9
|
+
* resolves to.
|
|
10
|
+
*
|
|
11
|
+
* Server-side by design: no Svelte / DOM imports, no client-only state.
|
|
12
|
+
* See happyvertical/smrt#1226 (Phase 4c) and #1235 for the registry
|
|
13
|
+
* audit that motivated this server-first pattern.
|
|
14
|
+
*/
|
|
15
|
+
import type { AvailableTool } from '../types.js';
|
|
16
|
+
import type { ComposeDockAvailabilityOptions, GateEvaluationContext } from './types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Compose the `availableTools` list for a tools dock by evaluating each
|
|
19
|
+
* tool's `gates` against the provided evaluators. Tools where ALL gates
|
|
20
|
+
* resolve to `true` are included (AND semantics).
|
|
21
|
+
*
|
|
22
|
+
* Tools without `gates` (or with an empty `gates` array) are
|
|
23
|
+
* unconditionally included — gating is opt-in, existing tool definitions
|
|
24
|
+
* keep working.
|
|
25
|
+
*
|
|
26
|
+
* Throws if a gate's prefix doesn't match any registered evaluator —
|
|
27
|
+
* misconfiguration should fail loudly, not silently leak tools to users
|
|
28
|
+
* who shouldn't see them.
|
|
29
|
+
*
|
|
30
|
+
* Gate evaluation is parallel within a tool and across tools (`Promise.all`
|
|
31
|
+
* fan-out), so a slow evaluator on one gate doesn't block unrelated tools.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import {
|
|
36
|
+
* composeDockAvailability,
|
|
37
|
+
* type GateEvaluationContext,
|
|
38
|
+
* } from '@happyvertical/smrt-svelte/workspace/server';
|
|
39
|
+
* import { PermissionResolver } from '@happyvertical/smrt-users';
|
|
40
|
+
* import { FeatureResolver } from '@happyvertical/smrt-features';
|
|
41
|
+
*
|
|
42
|
+
* // Caller narrows the context shape so evaluators get typed access
|
|
43
|
+
* // without casts:
|
|
44
|
+
* interface MyContext extends GateEvaluationContext {
|
|
45
|
+
* userId: string;
|
|
46
|
+
* tenantId: string;
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* const available = await composeDockAvailability<MyContext>({
|
|
50
|
+
* tools: [
|
|
51
|
+
* { id: 'governance', label: 'Claim Audit', gates: ['permission:content.governance.view'] },
|
|
52
|
+
* { id: 'video-gen', label: 'Video', gates: ['feature:video-tools'] },
|
|
53
|
+
* { id: 'chat', label: 'Chat' }, // no gates → always visible
|
|
54
|
+
* ],
|
|
55
|
+
* context: { userId: 'user-1', tenantId: 'tenant-1' },
|
|
56
|
+
* evaluators: {
|
|
57
|
+
* permission: async (gateId, ctx) => {
|
|
58
|
+
* const [, slug] = gateId.split(':', 2);
|
|
59
|
+
* return permissionResolver.hasPermission(ctx.userId, ctx.tenantId, slug);
|
|
60
|
+
* },
|
|
61
|
+
* feature: async (gateId, ctx) => {
|
|
62
|
+
* const [, key] = gateId.split(':', 2);
|
|
63
|
+
* return featureResolver.isEnabled(key, { tenantId: ctx.tenantId });
|
|
64
|
+
* },
|
|
65
|
+
* },
|
|
66
|
+
* });
|
|
67
|
+
* // → AvailableTool[] filtered by gates
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function composeDockAvailability<TCtx extends {
|
|
71
|
+
[K in keyof TCtx]: unknown;
|
|
72
|
+
} = GateEvaluationContext>(options: ComposeDockAvailabilityOptions<TCtx>): Promise<AvailableTool[]>;
|
|
73
|
+
//# sourceMappingURL=compose-availability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-availability.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/server/compose-availability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EACV,8BAA8B,EAC9B,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,SAAS;KAAG,CAAC,IAAI,MAAM,IAAI,GAAG,OAAO;CAAE,GAAG,qBAAqB,EACnE,OAAO,EAAE,8BAA8B,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAuDzE"}
|