@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,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper that turns a SMRT manifest into the `NavItem[]` shape the
|
|
3
|
+
* existing `NavTree` / `RoleShell` primitives expect.
|
|
4
|
+
*
|
|
5
|
+
* This is the "manifest → admin UI" adapter called out as a friction gap in
|
|
6
|
+
* happyvertical/smrt#1235 / #1226 / #1248: every consumer hand-writes its nav
|
|
7
|
+
* config today, with dozens of `@smrt()` classes that boilerplate drifts.
|
|
8
|
+
* Opt in here to manifest-driven nav while keeping the primitives themselves
|
|
9
|
+
* SmrtObject-agnostic.
|
|
10
|
+
*
|
|
11
|
+
* Design constraints (see #1248 for the full brief):
|
|
12
|
+
* - Pure function. No SvelteKit imports, no SSR coupling, no module side
|
|
13
|
+
* effects. Data in → data out.
|
|
14
|
+
* - Cross-industry-safe. Never assumes apparel / furniture / automotive
|
|
15
|
+
* vocabulary; consumers supply their own `sectionHints`.
|
|
16
|
+
* - Deterministic output. Sections and items both sort alphabetically by
|
|
17
|
+
* title so manifest-order churn doesn't shuffle the rendered nav.
|
|
18
|
+
* - Decoupled from any concrete role/permission package. `permittedResources`
|
|
19
|
+
* is just a plain string array of qualified class names.
|
|
20
|
+
*
|
|
21
|
+
* The structural `SmrtManifestLike` / `SmrtManifestEntryLike` shapes here
|
|
22
|
+
* intentionally subset the real `SmartObjectManifest` from `@happyvertical/smrt-core`.
|
|
23
|
+
* Adding a peer dependency on core would create a circular reference (core
|
|
24
|
+
* pulls in svelte primitives downstream), so the helper accepts anything
|
|
25
|
+
* that matches the documented shape. Pass a `SmartObjectManifest` directly
|
|
26
|
+
* and TypeScript is happy.
|
|
27
|
+
*/
|
|
28
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
29
|
+
// Pluralization
|
|
30
|
+
//
|
|
31
|
+
// The brief explicitly says "use a tiny pluralization helper, don't pull
|
|
32
|
+
// in pluralize". This covers the common English suffix rules and is
|
|
33
|
+
// deterministic. Consumers who need exotic plurals override via
|
|
34
|
+
// `decoratorConfig.ui.label`.
|
|
35
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Minimal English pluralizer. Handles the common suffix rules sufficient
|
|
38
|
+
* for class-name → label conversion (Article → Articles, Category →
|
|
39
|
+
* Categories, Box → Boxes, etc.). For irregulars or exotic plurals,
|
|
40
|
+
* override with `@smrt({ ui: { label: 'Foo bars' } })`.
|
|
41
|
+
*
|
|
42
|
+
* Exported only for tests / advanced callers; most code should let
|
|
43
|
+
* `navTreeFromManifest` apply it.
|
|
44
|
+
*/
|
|
45
|
+
export function pluralizeClassName(name) {
|
|
46
|
+
if (!name)
|
|
47
|
+
return name;
|
|
48
|
+
// Already plural-ish (very rough — "Categories" passes through).
|
|
49
|
+
if (/(?:[^aeiouy]|qu)ies$/i.test(name))
|
|
50
|
+
return name;
|
|
51
|
+
if (/s$/i.test(name) && !/(?:ss|us|is)$/i.test(name))
|
|
52
|
+
return name;
|
|
53
|
+
// Words ending in consonant + 'y' → 'ies' (Category → Categories).
|
|
54
|
+
if (/[^aeiouy]y$/i.test(name)) {
|
|
55
|
+
return `${name.slice(0, -1)}ies`;
|
|
56
|
+
}
|
|
57
|
+
// Words ending in s, x, z, ch, sh → 'es' (Box → Boxes, Dish → Dishes).
|
|
58
|
+
if (/(?:s|x|z|ch|sh)$/i.test(name)) {
|
|
59
|
+
return `${name}es`;
|
|
60
|
+
}
|
|
61
|
+
return `${name}s`;
|
|
62
|
+
}
|
|
63
|
+
/** Convert a PascalCase class name to a kebab-case URL slug. */
|
|
64
|
+
function classNameToKebab(name) {
|
|
65
|
+
return name
|
|
66
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
67
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
68
|
+
.toLowerCase();
|
|
69
|
+
}
|
|
70
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
71
|
+
// Filtering — drop collection classes, internal/test visibility, etc.
|
|
72
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* True when an entry represents a `SmrtCollection` subclass rather than a
|
|
75
|
+
* `SmrtObject`. The manifest captures both — collections appear with
|
|
76
|
+
* `extends: 'SmrtCollection'` (or any ancestor whose own `extends` chain
|
|
77
|
+
* eventually hits one), but the nav helper only cares about the SmrtObject
|
|
78
|
+
* side. Heuristic: `className` ending in `Collection`. This matches the
|
|
79
|
+
* SMRT convention (`ProductCollection`, `CategoryCollection`) and avoids
|
|
80
|
+
* needing the full inheritance walk.
|
|
81
|
+
*/
|
|
82
|
+
function looksLikeCollectionClass(entry) {
|
|
83
|
+
if (entry.className.endsWith('Collection'))
|
|
84
|
+
return true;
|
|
85
|
+
if (entry.extends === 'SmrtCollection')
|
|
86
|
+
return true;
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether an entry is publicly visible in the manifest. Classes marked
|
|
91
|
+
* `@smrt({ visibility: 'internal' })` are package-only and should never
|
|
92
|
+
* surface in admin nav — they're typically join tables, sync helpers, or
|
|
93
|
+
* cross-package plumbing. `'test'` fixtures should obviously never leak
|
|
94
|
+
* either. Default (`undefined` / `'public'`) keeps the entry visible.
|
|
95
|
+
*
|
|
96
|
+
* Reads the top-level `visibility` field first (the shape published by
|
|
97
|
+
* `SmartObjectDefinition`) and falls back to `decoratorConfig.visibility`
|
|
98
|
+
* for older / hand-built manifests.
|
|
99
|
+
*/
|
|
100
|
+
function isPublicEntry(entry) {
|
|
101
|
+
const visibility = entry.visibility ?? entry.decoratorConfig?.visibility ?? 'public';
|
|
102
|
+
return visibility === 'public';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* True when an entry is an STI subtype whose REST route would be the
|
|
106
|
+
* same as some ancestor's. The SMRT scanner copies the STI parent's
|
|
107
|
+
* `collection` field onto every child entry — `Material`, `Style`,
|
|
108
|
+
* `Cart`, `Order`, `ProductionOrder` etc. all carry the parent's
|
|
109
|
+
* `collection` value (`products`, `contracts`). If we emitted a nav
|
|
110
|
+
* item per entry, the user would see "Materials" and "Products" both
|
|
111
|
+
* linking to `/api/v1/products`, with no way to tell which list they
|
|
112
|
+
* landed on. Suppressing the child entries keeps the base class's
|
|
113
|
+
* single nav link — the REST endpoint already exposes the full
|
|
114
|
+
* polymorphic list, which is the conventional shape for STI tables.
|
|
115
|
+
*
|
|
116
|
+
* **Walks the ancestor chain.** A two-level hierarchy like
|
|
117
|
+
* `Product → Material → FabricMaterial` may have its intermediate
|
|
118
|
+
* `Material` entry stripped from a partial manifest while `Product`
|
|
119
|
+
* remains. A one-level check would let `FabricMaterial` through
|
|
120
|
+
* (parent absent → return false → keep) and duplicate the `Product`
|
|
121
|
+
* link. Walking the chain handles this case: as long as any ancestor
|
|
122
|
+
* with a matching `collection` is reachable through the chain, the
|
|
123
|
+
* subtype is suppressed. Fully-orphaned entries (no resolvable
|
|
124
|
+
* ancestor in the manifest) stay visible — better to show one link
|
|
125
|
+
* than none.
|
|
126
|
+
*
|
|
127
|
+
* Detection: starting at `entry.extends`, look up that className in
|
|
128
|
+
* the manifest. If found and `collection` matches, suppress this
|
|
129
|
+
* entry. Otherwise recurse into THAT entry's `extends` and try again.
|
|
130
|
+
* Stop at `SmrtObject`/`SmrtClass`, when no manifest entry resolves,
|
|
131
|
+
* or when a cycle is detected.
|
|
132
|
+
*/
|
|
133
|
+
function looksLikeStiSubtypeOfParentRoute(entry, manifest) {
|
|
134
|
+
const byClassName = entryIndexByClassName(manifest);
|
|
135
|
+
const visited = new Set();
|
|
136
|
+
let cursor = entry.extends;
|
|
137
|
+
while (cursor && cursor !== 'SmrtObject' && cursor !== 'SmrtClass') {
|
|
138
|
+
if (visited.has(cursor))
|
|
139
|
+
return false; // pathological cycle
|
|
140
|
+
visited.add(cursor);
|
|
141
|
+
const ancestor = byClassName.get(cursor);
|
|
142
|
+
if (!ancestor)
|
|
143
|
+
return false; // chain broken, keep this entry visible
|
|
144
|
+
if (ancestor.collection && ancestor.collection === entry.collection) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
cursor = ancestor.extends;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build a className → entry lookup over the manifest. Memoised per
|
|
153
|
+
* manifest via WeakMap so a typical `navTreeFromManifest` call only
|
|
154
|
+
* walks `Object.values(manifest.objects)` once even when many entries
|
|
155
|
+
* trigger `looksLikeStiSubtypeOfParentRoute`. When two entries share
|
|
156
|
+
* a className (shouldn't happen in a well-formed manifest), the first
|
|
157
|
+
* one wins — duplicates would still produce the same dedup answer
|
|
158
|
+
* because both would carry the same parent chain.
|
|
159
|
+
*/
|
|
160
|
+
const manifestClassNameIndex = new WeakMap();
|
|
161
|
+
function entryIndexByClassName(manifest) {
|
|
162
|
+
const cached = manifestClassNameIndex.get(manifest);
|
|
163
|
+
if (cached)
|
|
164
|
+
return cached;
|
|
165
|
+
const idx = new Map();
|
|
166
|
+
for (const e of Object.values(manifest.objects)) {
|
|
167
|
+
if (!idx.has(e.className))
|
|
168
|
+
idx.set(e.className, e);
|
|
169
|
+
}
|
|
170
|
+
manifestClassNameIndex.set(manifest, idx);
|
|
171
|
+
return idx;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Whether an entry exposes a `list` route through the REST generator.
|
|
175
|
+
*
|
|
176
|
+
* Join-table / link / asset models commonly declare `@smrt({ api: false })`
|
|
177
|
+
* because they're plumbing, not catalog. Linking a nav item to them would
|
|
178
|
+
* 404 — the route literally isn't generated. Conservatively skip those.
|
|
179
|
+
*
|
|
180
|
+
* Handles the three shapes `@smrt({ api })` can take:
|
|
181
|
+
*
|
|
182
|
+
* - `undefined` / `true` → default routes (list included)
|
|
183
|
+
* - `false` → no routes
|
|
184
|
+
* - `{ include: [...] }` → only listed routes
|
|
185
|
+
* - `{ exclude: [...] }` → all except listed routes
|
|
186
|
+
*/
|
|
187
|
+
function hasListRoute(entry) {
|
|
188
|
+
const api = entry.decoratorConfig?.api;
|
|
189
|
+
if (api === undefined || api === true)
|
|
190
|
+
return true;
|
|
191
|
+
if (api === false)
|
|
192
|
+
return false;
|
|
193
|
+
if (typeof api !== 'object' || api === null)
|
|
194
|
+
return true;
|
|
195
|
+
const obj = api;
|
|
196
|
+
if (Array.isArray(obj.include)) {
|
|
197
|
+
return obj.include.includes('list');
|
|
198
|
+
}
|
|
199
|
+
if (Array.isArray(obj.exclude)) {
|
|
200
|
+
return !obj.exclude.includes('list');
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Derive a section title for an entry. Order of preference:
|
|
206
|
+
* 1. First `sectionHints` key whose substring appears in the qualified name.
|
|
207
|
+
* 2. The package suffix between the last `/` and the `:` of the qualified
|
|
208
|
+
* name (e.g. `@happyvertical/smrt-content:Article` → `smrt-content`).
|
|
209
|
+
* 3. The `packageName` field as-is, or `'Resources'` as a final fallback.
|
|
210
|
+
*/
|
|
211
|
+
function deriveSectionTitle(entry, qualifier, sectionHints) {
|
|
212
|
+
if (sectionHints) {
|
|
213
|
+
for (const [needle, title] of Object.entries(sectionHints)) {
|
|
214
|
+
if (qualifier.includes(needle))
|
|
215
|
+
return title;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// `@happyvertical/smrt-content:Article` → after last `/`, before `:`.
|
|
219
|
+
const colonIndex = qualifier.indexOf(':');
|
|
220
|
+
const beforeColon = colonIndex >= 0 ? qualifier.slice(0, colonIndex) : qualifier;
|
|
221
|
+
const lastSlash = beforeColon.lastIndexOf('/');
|
|
222
|
+
if (lastSlash >= 0 && lastSlash < beforeColon.length - 1) {
|
|
223
|
+
return beforeColon.slice(lastSlash + 1);
|
|
224
|
+
}
|
|
225
|
+
return entry.packageName || beforeColon || 'Resources';
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Pick the qualifier string for an entry. Prefers the explicit
|
|
229
|
+
* `qualifiedName`; falls back to a synthesised `packageName:className`
|
|
230
|
+
* pair so partial / hand-built manifests still work in tests.
|
|
231
|
+
*/
|
|
232
|
+
function entryQualifier(entry) {
|
|
233
|
+
if (entry.qualifiedName)
|
|
234
|
+
return entry.qualifiedName;
|
|
235
|
+
if (entry.packageName)
|
|
236
|
+
return `${entry.packageName}:${entry.className}`;
|
|
237
|
+
return entry.className;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Expand a permitted-qualifiers set so it includes the parent
|
|
241
|
+
* qualifier of every STI subtype that shares its parent's `collection`
|
|
242
|
+
* value. Without this expansion, a role permitted to access an STI
|
|
243
|
+
* subtype (e.g. `Cart`) would lose its nav link, because the dedup
|
|
244
|
+
* step suppresses subtypes that share the parent's REST route and
|
|
245
|
+
* the permission filter would then drop the parent. The expansion
|
|
246
|
+
* threads through the inheritance chain (handles multi-level STI)
|
|
247
|
+
* with a visited-set cycle guard.
|
|
248
|
+
*
|
|
249
|
+
* Pure: returns a new Set; never mutates the caller's set. The
|
|
250
|
+
* className index is the same memoised one used by
|
|
251
|
+
* `looksLikeStiSubtypeOfParentRoute`.
|
|
252
|
+
*/
|
|
253
|
+
function expandPermittedThroughStiParents(permitted, manifest) {
|
|
254
|
+
const byClassName = entryIndexByClassName(manifest);
|
|
255
|
+
// Also need to resolve qualifier → entry, because permitted set
|
|
256
|
+
// entries are qualified names (`@pkg/x:Class`), not raw classNames.
|
|
257
|
+
const byQualifier = new Map();
|
|
258
|
+
for (const entry of Object.values(manifest.objects)) {
|
|
259
|
+
byQualifier.set(entryQualifier(entry), entry);
|
|
260
|
+
}
|
|
261
|
+
const expanded = new Set(permitted);
|
|
262
|
+
for (const qualifier of permitted) {
|
|
263
|
+
const entry = byQualifier.get(qualifier);
|
|
264
|
+
if (!entry)
|
|
265
|
+
continue;
|
|
266
|
+
const visited = new Set();
|
|
267
|
+
let cursor = entry.extends;
|
|
268
|
+
let lastSeenCollection = entry.collection;
|
|
269
|
+
while (cursor && cursor !== 'SmrtObject' && cursor !== 'SmrtClass') {
|
|
270
|
+
if (visited.has(cursor))
|
|
271
|
+
break;
|
|
272
|
+
visited.add(cursor);
|
|
273
|
+
const ancestor = byClassName.get(cursor);
|
|
274
|
+
if (!ancestor)
|
|
275
|
+
break;
|
|
276
|
+
if (ancestor.collection && ancestor.collection === lastSeenCollection) {
|
|
277
|
+
expanded.add(entryQualifier(ancestor));
|
|
278
|
+
}
|
|
279
|
+
lastSeenCollection = ancestor.collection ?? lastSeenCollection;
|
|
280
|
+
cursor = ancestor.extends;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return expanded;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Walk a SMRT manifest and emit the `NavSection[]` shape that
|
|
287
|
+
* `<NavTree items={...}>` and `RoleConfig.sections` consume.
|
|
288
|
+
*
|
|
289
|
+
* Algorithm:
|
|
290
|
+
* 1. Drop collection classes (`*Collection` / `extends: 'SmrtCollection'`).
|
|
291
|
+
* 2. Drop entries marked `@smrt({ visibility: 'internal' | 'test' })` —
|
|
292
|
+
* they're plumbing / fixtures and never belong in admin nav.
|
|
293
|
+
* 3. Drop entries that don't expose a REST `list` route (`@smrt({ api: false })`,
|
|
294
|
+
* `include` without `list`, or `exclude` containing `list`).
|
|
295
|
+
* 4. Drop STI subtypes that share their parent's `collection` value —
|
|
296
|
+
* the REST endpoint at the shared collection URL is polymorphic and
|
|
297
|
+
* shows all subtypes through one link.
|
|
298
|
+
* 5. If `permittedResources` is provided, drop entries whose qualified
|
|
299
|
+
* name isn't in the list. Otherwise keep all remaining entries.
|
|
300
|
+
* 6. Group remaining entries by their resolved section title.
|
|
301
|
+
* 4. For each entry, emit a `NavItem` with:
|
|
302
|
+
* - `label` = `decoratorConfig.ui.label` ?? `pluralizeClassName(className)`
|
|
303
|
+
* - `href` = `${basePath}/${collection}` (defaults to `/api/v1/{collection}`)
|
|
304
|
+
* falling back to `${basePath}/{kebab-case-class}` if the
|
|
305
|
+
* manifest entry omits `collection`.
|
|
306
|
+
* - `icon` = `decoratorConfig.ui.icon` if present.
|
|
307
|
+
* 5. Sort items inside each section by label, then sort sections by title.
|
|
308
|
+
* The result is fully deterministic given a manifest input.
|
|
309
|
+
*
|
|
310
|
+
* @example Plug into RoleShell
|
|
311
|
+
* ```ts
|
|
312
|
+
* import { manifest } from '@my-app/manifest';
|
|
313
|
+
* import { navTreeFromManifest } from '@happyvertical/smrt-svelte/workspace';
|
|
314
|
+
*
|
|
315
|
+
* const sections = navTreeFromManifest(manifest, {
|
|
316
|
+
* sectionHints: {
|
|
317
|
+
* '@happyvertical/smrt-content': 'Content',
|
|
318
|
+
* '@happyvertical/smrt-commerce': 'Commerce',
|
|
319
|
+
* },
|
|
320
|
+
* });
|
|
321
|
+
* const roles: RoleConfig[] = [
|
|
322
|
+
* { id: 'admin', label: 'Admin', sections },
|
|
323
|
+
* ];
|
|
324
|
+
* ```
|
|
325
|
+
*
|
|
326
|
+
* @example Per-role filtering
|
|
327
|
+
* ```ts
|
|
328
|
+
* const editor = navTreeFromManifest(manifest, {
|
|
329
|
+
* permittedResources: [
|
|
330
|
+
* '@happyvertical/smrt-content:Article',
|
|
331
|
+
* '@happyvertical/smrt-content:Document',
|
|
332
|
+
* ],
|
|
333
|
+
* });
|
|
334
|
+
* // editor: [{ label: 'Content', children: [Articles, Documents] }]
|
|
335
|
+
* ```
|
|
336
|
+
*
|
|
337
|
+
* @example Unprefixed hrefs
|
|
338
|
+
* ```ts
|
|
339
|
+
* const sections = navTreeFromManifest(manifest, { basePath: '' });
|
|
340
|
+
* // items[0].href === '/articles' (not '/api/v1/articles')
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
export function navTreeFromManifest(manifest, options = {}) {
|
|
344
|
+
const { permittedResources, sectionHints, basePath = '/api/v1' } = options;
|
|
345
|
+
// Build the *effective* permitted set. When the caller permits a
|
|
346
|
+
// role to access an STI subtype (e.g. `@acme/shop:Cart`) but not
|
|
347
|
+
// its base (`Contract`), the role still needs the base's nav link
|
|
348
|
+
// because every STI subtype routes through the parent's shared
|
|
349
|
+
// collection URL. Walk each permitted qualifier up the inheritance
|
|
350
|
+
// chain — if any ancestor in the same manifest carries the same
|
|
351
|
+
// `collection` value, that ancestor is the actual link target, so
|
|
352
|
+
// add its qualifier to the permitted set. Without this remap the
|
|
353
|
+
// dedup step below would drop the subtype, the original permission
|
|
354
|
+
// check would drop the base, and the role would lose every link to
|
|
355
|
+
// the shared route.
|
|
356
|
+
const permitted = permittedResources
|
|
357
|
+
? expandPermittedThroughStiParents(new Set(permittedResources), manifest)
|
|
358
|
+
: undefined;
|
|
359
|
+
// Group items by their resolved section title. Map preserves insertion
|
|
360
|
+
// order, but we sort the final output anyway so callers don't have to
|
|
361
|
+
// care about it.
|
|
362
|
+
const sectionMap = new Map();
|
|
363
|
+
for (const entry of Object.values(manifest.objects)) {
|
|
364
|
+
if (looksLikeCollectionClass(entry))
|
|
365
|
+
continue;
|
|
366
|
+
if (!isPublicEntry(entry))
|
|
367
|
+
continue;
|
|
368
|
+
if (!hasListRoute(entry))
|
|
369
|
+
continue;
|
|
370
|
+
if (looksLikeStiSubtypeOfParentRoute(entry, manifest))
|
|
371
|
+
continue;
|
|
372
|
+
const qualifier = entryQualifier(entry);
|
|
373
|
+
if (permitted && !permitted.has(qualifier))
|
|
374
|
+
continue;
|
|
375
|
+
const ui = entry.decoratorConfig?.ui;
|
|
376
|
+
const label = ui?.label ?? pluralizeClassName(entry.className);
|
|
377
|
+
const slug = entry.collection ?? classNameToKebab(entry.className);
|
|
378
|
+
const href = `${basePath}/${slug}`;
|
|
379
|
+
const item = {
|
|
380
|
+
href,
|
|
381
|
+
label,
|
|
382
|
+
};
|
|
383
|
+
if (ui?.icon)
|
|
384
|
+
item.icon = ui.icon;
|
|
385
|
+
const sectionTitle = deriveSectionTitle(entry, qualifier, sectionHints);
|
|
386
|
+
const bucket = sectionMap.get(sectionTitle);
|
|
387
|
+
if (bucket) {
|
|
388
|
+
bucket.push(item);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
sectionMap.set(sectionTitle, [item]);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Deterministic ordering: sort items inside each section by label, then
|
|
395
|
+
// sort sections by title. Using `localeCompare` for predictable
|
|
396
|
+
// cross-locale behaviour without surprises around case.
|
|
397
|
+
const sections = [];
|
|
398
|
+
for (const [title, items] of sectionMap) {
|
|
399
|
+
items.sort((a, b) => a.label.localeCompare(b.label));
|
|
400
|
+
sections.push({
|
|
401
|
+
href: `#${title.toLowerCase().replace(/\s+/g, '-')}`,
|
|
402
|
+
label: title,
|
|
403
|
+
children: items,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
sections.sort((a, b) => a.label.localeCompare(b.label));
|
|
407
|
+
return sections;
|
|
408
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for nav state derivation.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so the recursive active/expanded logic can be unit-tested
|
|
5
|
+
* without needing to mount Svelte components.
|
|
6
|
+
*
|
|
7
|
+
* No DOM, no Svelte runes, no SvelteKit imports — these are SSR-safe and
|
|
8
|
+
* deterministic given their inputs.
|
|
9
|
+
*/
|
|
10
|
+
import type { NavItem } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Default activeness check for a nav item against a current path.
|
|
13
|
+
*
|
|
14
|
+
* - Exact match → active
|
|
15
|
+
* - `item.exact === true` → only exact match counts
|
|
16
|
+
* - `item.href === '/'` → only matches `currentPath === '/'`; otherwise
|
|
17
|
+
* the descendant-path fallback would degrade to `startsWith('//')`
|
|
18
|
+
* and never fire (and we don't want every path counting the root as
|
|
19
|
+
* active by ancestry).
|
|
20
|
+
* - Otherwise, active when `currentPath` starts with `item.href + '/'`
|
|
21
|
+
* (the trailing slash avoids `/foo` matching `/foobar`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function defaultIsActive(item: NavItem, currentPath: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* True if the item or any descendant is active under `isActive`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isItemOrChildActive(item: NavItem, currentPath: string, isActive?: (item: NavItem, currentPath: string) => boolean): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* True when an item's children container should render as expanded.
|
|
30
|
+
*
|
|
31
|
+
* Children are expanded when:
|
|
32
|
+
* - the item has children, AND
|
|
33
|
+
* - one of its descendants is active, OR `item.defaultExpanded` is set.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isExpanded(item: NavItem, currentPath: string, isActive?: (item: NavItem, currentPath: string) => boolean): boolean;
|
|
36
|
+
//# sourceMappingURL=nav-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-helpers.d.ts","sourceRoot":"","sources":["../../../src/components/workspace/nav-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAO3E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,MAAM,EACnB,QAAQ,GAAE,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,KAAK,OAAyB,GAC1E,OAAO,CAQT;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,MAAM,EACnB,QAAQ,GAAE,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,KAAK,OAAyB,GAC1E,OAAO,CAIT"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for nav state derivation.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so the recursive active/expanded logic can be unit-tested
|
|
5
|
+
* without needing to mount Svelte components.
|
|
6
|
+
*
|
|
7
|
+
* No DOM, no Svelte runes, no SvelteKit imports — these are SSR-safe and
|
|
8
|
+
* deterministic given their inputs.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Default activeness check for a nav item against a current path.
|
|
12
|
+
*
|
|
13
|
+
* - Exact match → active
|
|
14
|
+
* - `item.exact === true` → only exact match counts
|
|
15
|
+
* - `item.href === '/'` → only matches `currentPath === '/'`; otherwise
|
|
16
|
+
* the descendant-path fallback would degrade to `startsWith('//')`
|
|
17
|
+
* and never fire (and we don't want every path counting the root as
|
|
18
|
+
* active by ancestry).
|
|
19
|
+
* - Otherwise, active when `currentPath` starts with `item.href + '/'`
|
|
20
|
+
* (the trailing slash avoids `/foo` matching `/foobar`).
|
|
21
|
+
*/
|
|
22
|
+
export function defaultIsActive(item, currentPath) {
|
|
23
|
+
if (item.href === currentPath)
|
|
24
|
+
return true;
|
|
25
|
+
if (item.exact)
|
|
26
|
+
return false;
|
|
27
|
+
// The root href would produce `startsWith('//')`, which never matches a
|
|
28
|
+
// real path. Treat root as exact-only when not flagged with `exact`.
|
|
29
|
+
if (item.href === '/')
|
|
30
|
+
return false;
|
|
31
|
+
return currentPath.startsWith(`${item.href}/`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* True if the item or any descendant is active under `isActive`.
|
|
35
|
+
*/
|
|
36
|
+
export function isItemOrChildActive(item, currentPath, isActive = defaultIsActive) {
|
|
37
|
+
if (isActive(item, currentPath))
|
|
38
|
+
return true;
|
|
39
|
+
if (item.children) {
|
|
40
|
+
for (const child of item.children) {
|
|
41
|
+
if (isItemOrChildActive(child, currentPath, isActive))
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* True when an item's children container should render as expanded.
|
|
49
|
+
*
|
|
50
|
+
* Children are expanded when:
|
|
51
|
+
* - the item has children, AND
|
|
52
|
+
* - one of its descendants is active, OR `item.defaultExpanded` is set.
|
|
53
|
+
*/
|
|
54
|
+
export function isExpanded(item, currentPath, isActive = defaultIsActive) {
|
|
55
|
+
if (!item.children?.length)
|
|
56
|
+
return false;
|
|
57
|
+
if (item.defaultExpanded)
|
|
58
|
+
return true;
|
|
59
|
+
return isItemOrChildActive(item, currentPath, isActive);
|
|
60
|
+
}
|