@noego/wood 0.4.10 → 0.5.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/README.md +118 -42
- package/dist/bridge/backend_bridge.cjs.map +1 -1
- package/dist/bridge/backend_bridge.js.map +1 -1
- package/dist/client/index.cjs +78 -21
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +18 -3
- package/dist/client/index.d.ts +18 -3
- package/dist/client/index.js +80 -22
- package/dist/client/index.js.map +1 -1
- package/dist/client/renderer_log_transport.cjs +151 -0
- package/dist/client/renderer_log_transport.cjs.map +1 -0
- package/dist/client/renderer_log_transport.d.cts +54 -0
- package/dist/client/renderer_log_transport.d.ts +54 -0
- package/dist/client/renderer_log_transport.js +123 -0
- package/dist/client/renderer_log_transport.js.map +1 -0
- package/dist/codegen/controller_registration_generator.cjs +13 -2
- package/dist/codegen/controller_registration_generator.cjs.map +1 -1
- package/dist/codegen/controller_registration_generator.d.cts +12 -1
- package/dist/codegen/controller_registration_generator.d.ts +12 -1
- package/dist/codegen/controller_registration_generator.js +10 -1
- package/dist/codegen/controller_registration_generator.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -0
- package/dist/codegen/index.d.ts +1 -0
- package/dist/codegen/preload_generator.cjs +13 -6
- package/dist/codegen/preload_generator.cjs.map +1 -1
- package/dist/codegen/preload_generator.d.cts +11 -2
- package/dist/codegen/preload_generator.d.ts +11 -2
- package/dist/codegen/preload_generator.js +13 -6
- package/dist/codegen/preload_generator.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +78 -42
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +78 -42
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +1 -0
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +1 -0
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/config/config_parser.cjs +95 -4
- package/dist/config/config_parser.cjs.map +1 -1
- package/dist/config/config_parser.d.cts +10 -2
- package/dist/config/config_parser.d.ts +10 -2
- package/dist/config/config_parser.js +92 -3
- package/dist/config/config_parser.js.map +1 -1
- package/dist/config/index.cjs +6 -2
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.d.cts +2 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +8 -2
- package/dist/config/index.js.map +1 -1
- package/dist/controller/controller_resolver.cjs +51 -3
- package/dist/controller/controller_resolver.cjs.map +1 -1
- package/dist/controller/controller_resolver.d.cts +27 -4
- package/dist/controller/controller_resolver.d.ts +27 -4
- package/dist/controller/controller_resolver.js +48 -2
- package/dist/controller/controller_resolver.js.map +1 -1
- package/dist/controller/index.cjs +6 -2
- package/dist/controller/index.cjs.map +1 -1
- package/dist/controller/index.d.cts +1 -1
- package/dist/controller/index.d.ts +1 -1
- package/dist/controller/index.js +4 -2
- package/dist/controller/index.js.map +1 -1
- package/dist/dev/wood_dev_server.cjs +4 -1
- package/dist/dev/wood_dev_server.cjs.map +1 -1
- package/dist/dev/wood_dev_server.js +5 -2
- package/dist/dev/wood_dev_server.js.map +1 -1
- package/dist/dev/yaml_watcher.cjs +1 -1
- package/dist/dev/yaml_watcher.cjs.map +1 -1
- package/dist/dev/yaml_watcher.d.cts +4 -0
- package/dist/dev/yaml_watcher.d.ts +4 -0
- package/dist/dev/yaml_watcher.js +1 -1
- package/dist/dev/yaml_watcher.js.map +1 -1
- package/dist/frontend/frontend_lifecycle.cjs +74 -0
- package/dist/frontend/frontend_lifecycle.cjs.map +1 -0
- package/dist/frontend/frontend_lifecycle.d.cts +50 -0
- package/dist/frontend/frontend_lifecycle.d.ts +50 -0
- package/dist/frontend/frontend_lifecycle.js +50 -0
- package/dist/frontend/frontend_lifecycle.js.map +1 -0
- package/dist/frontend/frontend_page.cjs +122 -0
- package/dist/frontend/frontend_page.cjs.map +1 -0
- package/dist/frontend/frontend_page.d.cts +62 -0
- package/dist/frontend/frontend_page.d.ts +62 -0
- package/dist/frontend/frontend_page.js +98 -0
- package/dist/frontend/frontend_page.js.map +1 -0
- package/dist/frontend/frontend_surface.cjs +249 -0
- package/dist/frontend/frontend_surface.cjs.map +1 -0
- package/dist/frontend/frontend_surface.d.cts +64 -0
- package/dist/frontend/frontend_surface.d.ts +64 -0
- package/dist/frontend/frontend_surface.js +224 -0
- package/dist/frontend/frontend_surface.js.map +1 -0
- package/dist/frontend/index.cjs +52 -0
- package/dist/frontend/index.cjs.map +1 -0
- package/dist/frontend/index.d.cts +10 -0
- package/dist/frontend/index.d.ts +10 -0
- package/dist/frontend/index.js +19 -0
- package/dist/frontend/index.js.map +1 -0
- package/dist/frontend/page_catalog.cjs +135 -0
- package/dist/frontend/page_catalog.cjs.map +1 -0
- package/dist/frontend/page_catalog.d.cts +56 -0
- package/dist/frontend/page_catalog.d.ts +56 -0
- package/dist/frontend/page_catalog.js +108 -0
- package/dist/frontend/page_catalog.js.map +1 -0
- package/dist/frontend/tracked_transport.cjs +55 -0
- package/dist/frontend/tracked_transport.cjs.map +1 -0
- package/dist/frontend/tracked_transport.d.cts +36 -0
- package/dist/frontend/tracked_transport.d.ts +36 -0
- package/dist/frontend/tracked_transport.js +31 -0
- package/dist/frontend/tracked_transport.js.map +1 -0
- package/dist/frontend/work_registry.cjs +153 -0
- package/dist/frontend/work_registry.cjs.map +1 -0
- package/dist/frontend/work_registry.d.cts +70 -0
- package/dist/frontend/work_registry.d.ts +70 -0
- package/dist/frontend/work_registry.js +128 -0
- package/dist/frontend/work_registry.js.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -7
- package/dist/index.d.ts +10 -7
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/main/index.cjs +10 -0
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +14 -2
- package/dist/main/index.d.ts +14 -2
- package/dist/main/index.js +16 -0
- package/dist/main/index.js.map +1 -1
- package/dist/middleware/middleware_resolver.cjs +22 -1
- package/dist/middleware/middleware_resolver.cjs.map +1 -1
- package/dist/middleware/middleware_resolver.d.cts +13 -1
- package/dist/middleware/middleware_resolver.d.ts +13 -1
- package/dist/middleware/middleware_resolver.js +22 -1
- package/dist/middleware/middleware_resolver.js.map +1 -1
- package/dist/navigation/index.cjs +3 -0
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +25 -1
- package/dist/navigation/index.d.ts +25 -1
- package/dist/navigation/index.js +2 -0
- package/dist/navigation/index.js.map +1 -1
- package/dist/{middleware/__fixtures__/middleware/auth.cjs → navigation/layout_preservation.cjs} +18 -20
- package/dist/navigation/layout_preservation.cjs.map +1 -0
- package/dist/navigation/layout_preservation.d.cts +2 -0
- package/dist/navigation/layout_preservation.d.ts +2 -0
- package/dist/navigation/layout_preservation.js +18 -0
- package/dist/navigation/layout_preservation.js.map +1 -0
- package/dist/parser/index.cjs +2 -0
- package/dist/parser/index.cjs.map +1 -1
- package/dist/parser/index.d.cts +1 -1
- package/dist/parser/index.d.ts +1 -1
- package/dist/parser/index.js +2 -1
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/operations_parser.cjs +6 -0
- package/dist/parser/operations_parser.cjs.map +1 -1
- package/dist/parser/operations_parser.d.cts +9 -2
- package/dist/parser/operations_parser.d.ts +9 -2
- package/dist/parser/operations_parser.js +5 -0
- package/dist/parser/operations_parser.js.map +1 -1
- package/dist/parser/views_parser.cjs.map +1 -1
- package/dist/parser/views_parser.js.map +1 -1
- package/dist/router/index.d.cts +1 -0
- package/dist/router/index.d.ts +1 -0
- package/dist/router/wood_router.cjs +35 -3
- package/dist/router/wood_router.cjs.map +1 -1
- package/dist/router/wood_router.d.cts +15 -0
- package/dist/router/wood_router.d.ts +15 -0
- package/dist/router/wood_router.js +36 -4
- package/dist/router/wood_router.js.map +1 -1
- package/dist/runtime/get_container.cjs +9 -34
- package/dist/runtime/get_container.cjs.map +1 -1
- package/dist/runtime/get_container.d.cts +24 -4
- package/dist/runtime/get_container.d.ts +24 -4
- package/dist/runtime/get_container.js +8 -23
- package/dist/runtime/get_container.js.map +1 -1
- package/dist/runtime/index.cjs +9 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +5 -2
- package/dist/runtime/index.d.ts +5 -2
- package/dist/runtime/index.js +10 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/renderer_config_extension.cjs +50 -0
- package/dist/runtime/renderer_config_extension.cjs.map +1 -0
- package/dist/runtime/renderer_config_extension.d.cts +19 -0
- package/dist/runtime/renderer_config_extension.d.ts +19 -0
- package/dist/runtime/renderer_config_extension.js +24 -0
- package/dist/runtime/renderer_config_extension.js.map +1 -0
- package/dist/runtime/renderer_log_batch_receiver.cjs +73 -0
- package/dist/runtime/renderer_log_batch_receiver.cjs.map +1 -0
- package/dist/runtime/renderer_log_batch_receiver.d.cts +29 -0
- package/dist/runtime/renderer_log_batch_receiver.d.ts +29 -0
- package/dist/runtime/renderer_log_batch_receiver.js +48 -0
- package/dist/runtime/renderer_log_batch_receiver.js.map +1 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.cts +1 -1
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.ts +1 -1
- package/dist/runtime/runtime.cjs +14 -0
- package/dist/runtime/runtime.cjs.map +1 -1
- package/dist/runtime/runtime.d.cts +2 -1
- package/dist/runtime/runtime.d.ts +2 -1
- package/dist/runtime/runtime.js +14 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/stick/index.cjs.map +1 -1
- package/dist/stick/index.js.map +1 -1
- package/dist/testing/browser.cjs +104 -23
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +8 -3
- package/dist/testing/browser.d.ts +8 -3
- package/dist/testing/browser.js +102 -22
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.cjs +28 -8
- package/dist/testing/create_controller_harness.cjs.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +17 -2
- package/dist/testing/create_controller_harness.d.ts +17 -2
- package/dist/testing/create_controller_harness.js +28 -8
- package/dist/testing/create_controller_harness.js.map +1 -1
- package/dist/testing/create_router_bridge.d.cts +1 -0
- package/dist/testing/create_router_bridge.d.ts +1 -0
- package/dist/testing/create_test_router.d.cts +1 -0
- package/dist/testing/create_test_router.d.ts +1 -0
- package/dist/testing/index.cjs +31 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +20 -5
- package/dist/testing/index.d.ts +20 -5
- package/dist/testing/index.js +19 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/{controller/__fixtures__/controllers/not_a_class.cjs → testing/test_wood.cjs} +15 -7
- package/dist/testing/test_wood.cjs.map +1 -0
- package/dist/testing/test_wood.d.cts +16 -0
- package/dist/testing/test_wood.d.ts +16 -0
- package/dist/testing/test_wood.js +12 -0
- package/dist/testing/test_wood.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +272 -0
- package/dist/testing/test_wood_config.cjs.map +1 -0
- package/dist/testing/test_wood_config.d.cts +111 -0
- package/dist/testing/test_wood_config.d.ts +111 -0
- package/dist/testing/test_wood_config.js +247 -0
- package/dist/testing/test_wood_config.js.map +1 -0
- package/dist/testing/wood_config_source.cjs +146 -0
- package/dist/testing/wood_config_source.cjs.map +1 -0
- package/dist/testing/wood_config_source.d.cts +36 -0
- package/dist/testing/wood_config_source.d.ts +36 -0
- package/dist/testing/wood_config_source.js +112 -0
- package/dist/testing/wood_config_source.js.map +1 -0
- package/dist/testing/wood_env_renderer.cjs +210 -0
- package/dist/testing/wood_env_renderer.cjs.map +1 -0
- package/dist/testing/wood_env_renderer.d.cts +74 -0
- package/dist/testing/wood_env_renderer.d.ts +74 -0
- package/dist/testing/wood_env_renderer.js +188 -0
- package/dist/testing/wood_env_renderer.js.map +1 -0
- package/dist/testing/wood_test_selection.cjs +94 -0
- package/dist/testing/wood_test_selection.cjs.map +1 -0
- package/dist/testing/wood_test_selection.d.cts +39 -0
- package/dist/testing/wood_test_selection.d.ts +39 -0
- package/dist/testing/wood_test_selection.js +68 -0
- package/dist/testing/wood_test_selection.js.map +1 -0
- package/dist/trace-testing/index.d.cts +1 -2
- package/dist/trace-testing/index.d.ts +1 -2
- package/dist/trace-testing/trace_recorder.cjs +1 -1
- package/dist/trace-testing/trace_recorder.cjs.map +1 -1
- package/dist/trace-testing/trace_recorder.d.cts +1 -2
- package/dist/trace-testing/trace_recorder.d.ts +1 -2
- package/dist/trace-testing/trace_recorder.js +1 -1
- package/dist/trace-testing/trace_recorder.js.map +1 -1
- package/dist/trace-transport/index.d.cts +1 -1
- package/dist/trace-transport/index.d.ts +1 -1
- package/dist/{trace_hub-Cob54qRx.d.cts → trace_hub-Cpo_nVze.d.cts} +21 -1
- package/dist/{trace_hub-Cob54qRx.d.ts → trace_hub-Cpo_nVze.d.ts} +21 -1
- package/dist/tracing/index.cjs +9 -2
- package/dist/tracing/index.cjs.map +1 -1
- package/dist/tracing/index.d.cts +3 -2
- package/dist/tracing/index.d.ts +3 -2
- package/dist/tracing/index.js +7 -1
- package/dist/tracing/index.js.map +1 -1
- package/dist/tracing/renderer_trace_batch_host.d.cts +1 -1
- package/dist/tracing/renderer_trace_batch_host.d.ts +1 -1
- package/dist/tracing/renderer_trace_batch_transport.cjs +48 -6
- package/dist/tracing/renderer_trace_batch_transport.cjs.map +1 -1
- package/dist/tracing/renderer_trace_batch_transport.d.cts +17 -1
- package/dist/tracing/renderer_trace_batch_transport.d.ts +17 -1
- package/dist/tracing/renderer_trace_batch_transport.js +48 -6
- package/dist/tracing/renderer_trace_batch_transport.js.map +1 -1
- package/dist/tracing/trace_batch_adapter.cjs +110 -0
- package/dist/tracing/trace_batch_adapter.cjs.map +1 -0
- package/dist/tracing/trace_batch_adapter.d.cts +36 -0
- package/dist/tracing/trace_batch_adapter.d.ts +36 -0
- package/dist/tracing/trace_batch_adapter.js +86 -0
- package/dist/tracing/trace_batch_adapter.js.map +1 -0
- package/dist/tracing/trace_commit_writer.d.cts +1 -1
- package/dist/tracing/trace_commit_writer.d.ts +1 -1
- package/dist/tracing/trace_hub.cjs +87 -20
- package/dist/tracing/trace_hub.cjs.map +1 -1
- package/dist/tracing/trace_hub.d.cts +1 -1
- package/dist/tracing/trace_hub.d.ts +1 -1
- package/dist/tracing/trace_hub.js +85 -20
- package/dist/tracing/trace_hub.js.map +1 -1
- package/dist/tracing/trace_provider.cjs +21 -6
- package/dist/tracing/trace_provider.cjs.map +1 -1
- package/dist/tracing/trace_provider.d.cts +13 -4
- package/dist/tracing/trace_provider.d.ts +13 -4
- package/dist/tracing/trace_provider.js +22 -7
- package/dist/tracing/trace_provider.js.map +1 -1
- package/dist/tracing/types/wood_trace_batch.types.cjs.map +1 -1
- package/dist/tracing/types/wood_trace_batch.types.d.cts +15 -1
- package/dist/tracing/types/wood_trace_batch.types.d.ts +15 -1
- package/dist/tracing/types/wood_trace_sink.types.d.cts +1 -1
- package/dist/tracing/types/wood_trace_sink.types.d.ts +1 -1
- package/dist/tracing/wood_trace_batch_contract.cjs +2 -0
- package/dist/tracing/wood_trace_batch_contract.cjs.map +1 -1
- package/dist/tracing/wood_trace_batch_contract.d.cts +1 -1
- package/dist/tracing/wood_trace_batch_contract.d.ts +1 -1
- package/dist/tracing/wood_trace_batch_contract.js +2 -0
- package/dist/tracing/wood_trace_batch_contract.js.map +1 -1
- package/dist/transport/build_transport_bridge.cjs +4 -2
- package/dist/transport/build_transport_bridge.cjs.map +1 -1
- package/dist/transport/build_transport_bridge.d.cts +7 -1
- package/dist/transport/build_transport_bridge.d.ts +7 -1
- package/dist/transport/build_transport_bridge.js +2 -1
- package/dist/transport/build_transport_bridge.js.map +1 -1
- package/dist/transport/in_process_router_transport.d.cts +1 -0
- package/dist/transport/in_process_router_transport.d.ts +1 -0
- package/dist/transport/index.d.cts +1 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +47 -1
- package/dist/types/config.d.ts +47 -1
- package/dist/types/context.cjs +12 -0
- package/dist/types/context.cjs.map +1 -1
- package/dist/types/context.d.cts +27 -1
- package/dist/types/context.d.ts +27 -1
- package/dist/types/context.js +4 -0
- package/dist/types/context.js.map +1 -1
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/operations.cjs.map +1 -1
- package/dist/types/operations.d.cts +2 -0
- package/dist/types/operations.d.ts +2 -0
- package/docs/axe.md +706 -0
- package/package.json +13 -5
- package/src/components/NavigationShell.scopes.browser.test.ts +363 -0
- package/src/components/NavigationShell.svelte +126 -49
- package/dist/controller/__fixtures__/controllers/chat.cjs +0 -32
- package/dist/controller/__fixtures__/controllers/chat.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/chat.d.cts +0 -11
- package/dist/controller/__fixtures__/controllers/chat.d.ts +0 -11
- package/dist/controller/__fixtures__/controllers/chat.js +0 -12
- package/dist/controller/__fixtures__/controllers/chat.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/not_a_class.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/not_a_class.d.cts +0 -3
- package/dist/controller/__fixtures__/controllers/not_a_class.d.ts +0 -3
- package/dist/controller/__fixtures__/controllers/not_a_class.js +0 -5
- package/dist/controller/__fixtures__/controllers/not_a_class.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/settings.controller.cjs +0 -32
- package/dist/controller/__fixtures__/controllers/settings.controller.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/settings.controller.d.cts +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.d.ts +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.js +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/test.cjs +0 -29
- package/dist/controller/__fixtures__/controllers/test.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/test.d.cts +0 -7
- package/dist/controller/__fixtures__/controllers/test.d.ts +0 -7
- package/dist/controller/__fixtures__/controllers/test.js +0 -9
- package/dist/controller/__fixtures__/controllers/test.js.map +0 -1
- package/dist/loader/__fixtures__/layouts/app.load.cjs +0 -27
- package/dist/loader/__fixtures__/layouts/app.load.cjs.map +0 -1
- package/dist/loader/__fixtures__/layouts/app.load.d.cts +0 -5
- package/dist/loader/__fixtures__/layouts/app.load.d.ts +0 -5
- package/dist/loader/__fixtures__/layouts/app.load.js +0 -7
- package/dist/loader/__fixtures__/layouts/app.load.js.map +0 -1
- package/dist/loader/__fixtures__/views/test/page.load.cjs +0 -27
- package/dist/loader/__fixtures__/views/test/page.load.cjs.map +0 -1
- package/dist/loader/__fixtures__/views/test/page.load.d.cts +0 -6
- package/dist/loader/__fixtures__/views/test/page.load.d.ts +0 -6
- package/dist/loader/__fixtures__/views/test/page.load.js +0 -7
- package/dist/loader/__fixtures__/views/test/page.load.js.map +0 -1
- package/dist/middleware/__fixtures__/middleware/auth.cjs.map +0 -1
- package/dist/middleware/__fixtures__/middleware/auth.d.cts +0 -6
- package/dist/middleware/__fixtures__/middleware/auth.d.ts +0 -6
- package/dist/middleware/__fixtures__/middleware/auth.js +0 -19
- package/dist/middleware/__fixtures__/middleware/auth.js.map +0 -1
- package/dist/middleware/__fixtures__/middleware/sequence.cjs +0 -47
- package/dist/middleware/__fixtures__/middleware/sequence.cjs.map +0 -1
- package/dist/middleware/__fixtures__/middleware/sequence.d.cts +0 -7
- package/dist/middleware/__fixtures__/middleware/sequence.d.ts +0 -7
- package/dist/middleware/__fixtures__/middleware/sequence.js +0 -21
- package/dist/middleware/__fixtures__/middleware/sequence.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noego/wood",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -125,20 +125,22 @@
|
|
|
125
125
|
"prepublishOnly": "npm run build && npm run typecheck && npm test"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@noego/ioc": "^0.
|
|
129
|
-
"@noego/logger": "^0.0
|
|
128
|
+
"@noego/ioc": "^0.7.0",
|
|
129
|
+
"@noego/logger": "^0.2.0",
|
|
130
130
|
"@noego/stitch": "^1.0.3",
|
|
131
|
-
"@noego/telemetry": "^0.0
|
|
132
|
-
"@noego/trace": "^0.0
|
|
131
|
+
"@noego/telemetry": "^0.1.0",
|
|
132
|
+
"@noego/trace": "^0.2.0",
|
|
133
133
|
"ajv": "^8.x",
|
|
134
134
|
"ajv-formats": "^3.x",
|
|
135
135
|
"chokidar": "^4.x",
|
|
136
136
|
"glob": "^11.x",
|
|
137
137
|
"js-yaml": "^4.x",
|
|
138
138
|
"path-to-regexp": "^8.x",
|
|
139
|
+
"reflect-metadata": "^0.2.2",
|
|
139
140
|
"rxjs": "^7.x"
|
|
140
141
|
},
|
|
141
142
|
"peerDependencies": {
|
|
143
|
+
"@noego/testing": ">=0.5.0",
|
|
142
144
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
|
143
145
|
"electron": ">=28.0.0",
|
|
144
146
|
"playwright": "^1.40.0",
|
|
@@ -150,12 +152,18 @@
|
|
|
150
152
|
},
|
|
151
153
|
"playwright": {
|
|
152
154
|
"optional": true
|
|
155
|
+
},
|
|
156
|
+
"@noego/testing": {
|
|
157
|
+
"optional": true
|
|
153
158
|
}
|
|
154
159
|
},
|
|
155
160
|
"devDependencies": {
|
|
161
|
+
"@noego/testing": "^0.5.0",
|
|
156
162
|
"@sveltejs/vite-plugin-svelte": "^4.0.4",
|
|
163
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
157
164
|
"@types/js-yaml": "^4.x",
|
|
158
165
|
"@types/node": "^20.10.4",
|
|
166
|
+
"better-sqlite3": "^12.11.1",
|
|
159
167
|
"electron-builder": "^25.x",
|
|
160
168
|
"electron-vite": "^2.x",
|
|
161
169
|
"esbuild": "^0.25.x",
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, test } from 'vitest';
|
|
5
|
+
import { browser } from '../testing/browser';
|
|
6
|
+
|
|
7
|
+
type ScopeReproSnapshot = {
|
|
8
|
+
scopeDisposeCounts: Record<string, number>;
|
|
9
|
+
controllerDisposeCounts: Record<string, number>;
|
|
10
|
+
setCreateCalls: string[];
|
|
11
|
+
createdIds: string[];
|
|
12
|
+
rendered: string;
|
|
13
|
+
pageErrors: string[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('NavigationShell renderer scope ownership', () => {
|
|
17
|
+
const harnesses: Array<{ destroy(): Promise<void> }> = [];
|
|
18
|
+
const tempRoots: string[] = [];
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
while (harnesses.length > 0) {
|
|
22
|
+
await harnesses.pop()?.destroy();
|
|
23
|
+
}
|
|
24
|
+
while (tempRoots.length > 0) {
|
|
25
|
+
rmSync(tempRoots.pop()!, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test(
|
|
30
|
+
'disposes page scopes, preserves layout scopes, and retains borrowed page scopes across transitions',
|
|
31
|
+
async () => {
|
|
32
|
+
const woodRoot = path.resolve(__dirname, '../..');
|
|
33
|
+
const rootDir = mkdtempSync(path.join(os.tmpdir(), 'wood-navigation-shell-scopes-repro-'));
|
|
34
|
+
tempRoots.push(rootDir);
|
|
35
|
+
mkdirSync(path.join(rootDir, 'ui'), { recursive: true });
|
|
36
|
+
|
|
37
|
+
writeFileSync(
|
|
38
|
+
path.join(rootDir, 'ui', 'ReproView.svelte'),
|
|
39
|
+
`<script lang="ts">
|
|
40
|
+
let { data = {} } = $props<{ data?: { id?: string } }>();
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<main data-repro-view>controller={data.id ?? 'missing'}</main>`,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
writeFileSync(
|
|
47
|
+
path.join(rootDir, 'ui', 'ReproLayout.svelte'),
|
|
48
|
+
`<script lang="ts">
|
|
49
|
+
import type { Snippet } from 'svelte';
|
|
50
|
+
let { children }: { children: Snippet } = $props();
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<section data-repro-layout>{@render children()}</section>`,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
writeFileSync(
|
|
57
|
+
path.join(rootDir, 'ui', 'NavigationShellScopesRepro.svelte'),
|
|
58
|
+
`<script lang="ts">
|
|
59
|
+
import '/@fs/${woodRoot}/src/client/global_polyfill.ts';
|
|
60
|
+
import NavigationShell from '/@fs/${woodRoot}/src/components/NavigationShell.svelte';
|
|
61
|
+
import { getNavigation } from '/@fs/${woodRoot}/src/navigation/index.ts';
|
|
62
|
+
import ReproLayout from './ReproLayout.svelte';
|
|
63
|
+
import ReproView from './ReproView.svelte';
|
|
64
|
+
|
|
65
|
+
type ScopeReproState = {
|
|
66
|
+
scopeDisposeCounts: Record<string, number>;
|
|
67
|
+
controllerDisposeCounts: Record<string, number>;
|
|
68
|
+
setCreateCalls: string[];
|
|
69
|
+
createdIds: string[];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const state: ScopeReproState = {
|
|
73
|
+
scopeDisposeCounts: {},
|
|
74
|
+
controllerDisposeCounts: {},
|
|
75
|
+
setCreateCalls: [],
|
|
76
|
+
createdIds: [],
|
|
77
|
+
};
|
|
78
|
+
(window as unknown as { __WOOD_SCOPE_REPRO__: ScopeReproState })
|
|
79
|
+
.__WOOD_SCOPE_REPRO__ = state;
|
|
80
|
+
|
|
81
|
+
function makeScope(id: string) {
|
|
82
|
+
state.scopeDisposeCounts[id] = 0;
|
|
83
|
+
state.createdIds.push(id);
|
|
84
|
+
return {
|
|
85
|
+
id,
|
|
86
|
+
disposed: false,
|
|
87
|
+
async dispose() {
|
|
88
|
+
this.disposed = true;
|
|
89
|
+
state.scopeDisposeCounts[id] += 1;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function makeController(id: string) {
|
|
95
|
+
state.controllerDisposeCounts[id] = 0;
|
|
96
|
+
state.createdIds.push(id);
|
|
97
|
+
return {
|
|
98
|
+
data: { id },
|
|
99
|
+
async dispose() {
|
|
100
|
+
state.controllerDisposeCounts[id] += 1;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function makeEntry(
|
|
106
|
+
pageName: string,
|
|
107
|
+
layoutControllerName: string | null,
|
|
108
|
+
preserve: boolean,
|
|
109
|
+
) {
|
|
110
|
+
return {
|
|
111
|
+
key: \`main.\${pageName}\`,
|
|
112
|
+
windowName: 'main',
|
|
113
|
+
pageName,
|
|
114
|
+
layoutPaths: layoutControllerName ? ['ui/ReproLayout.svelte'] : [],
|
|
115
|
+
layoutControllers: layoutControllerName ? [layoutControllerName] : [],
|
|
116
|
+
layoutPreserve: layoutControllerName ? [preserve] : [],
|
|
117
|
+
view: ReproView,
|
|
118
|
+
layouts: layoutControllerName ? [ReproLayout] : [],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// A/B share a preservable layout L1; C has no layouts; D/E share a
|
|
123
|
+
// NON-preservable layout L2 (page-scope owned); F has no layouts.
|
|
124
|
+
const entries = [
|
|
125
|
+
makeEntry('A', 'L1', true),
|
|
126
|
+
makeEntry('B', 'L1', true),
|
|
127
|
+
makeEntry('C', null, false),
|
|
128
|
+
makeEntry('D', 'L2', false),
|
|
129
|
+
makeEntry('E', 'L2', false),
|
|
130
|
+
makeEntry('F', null, false),
|
|
131
|
+
];
|
|
132
|
+
const manifest = Object.fromEntries(entries.map((entry) => [entry.key, entry]));
|
|
133
|
+
|
|
134
|
+
// Mirrors the generated createControllerSet factory in
|
|
135
|
+
// src/codegen/renderer_app_generator.ts: one page scope per set, a
|
|
136
|
+
// dedicated scope per preservable layout, null (page-scope owned)
|
|
137
|
+
// otherwise, and undefined/null placeholders below preservedDepth.
|
|
138
|
+
function buildControllerSet(entry: any, preservedDepth = 0) {
|
|
139
|
+
state.setCreateCalls.push(\`\${entry.pageName}:\${preservedDepth}\`);
|
|
140
|
+
const pageScope = makeScope(\`scope:\${entry.pageName}\`);
|
|
141
|
+
const layoutControllers: unknown[] = [];
|
|
142
|
+
const layoutScopes: Array<unknown> = [];
|
|
143
|
+
for (let i = 0; i < preservedDepth; i += 1) {
|
|
144
|
+
layoutControllers.push(undefined);
|
|
145
|
+
layoutScopes.push(null);
|
|
146
|
+
}
|
|
147
|
+
for (let i = preservedDepth; i < entry.layoutControllers.length; i += 1) {
|
|
148
|
+
const name = entry.layoutControllers[i];
|
|
149
|
+
const preservable = entry.layoutPreserve?.[i] === true;
|
|
150
|
+
layoutControllers.push(makeController(\`ctrl:\${name}\`));
|
|
151
|
+
layoutScopes.push(preservable ? makeScope(\`scope:\${name}\`) : null);
|
|
152
|
+
}
|
|
153
|
+
const controller = makeController(\`ctrl:\${entry.pageName}\`);
|
|
154
|
+
return { controller, layoutControllers, pageScope, layoutScopes };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const initialControllerSet = buildControllerSet(entries[0], 0);
|
|
158
|
+
|
|
159
|
+
async function createControllerSetFn(entry: any, preservedDepth = 0) {
|
|
160
|
+
return buildControllerSet(entry, preservedDepth);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getViewManifestEntryFn(viewKey: string) {
|
|
164
|
+
return manifest[viewKey as keyof typeof manifest] ?? null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const navigation = getNavigation();
|
|
168
|
+
navigation.replace(entries[0].key, {}, {});
|
|
169
|
+
(window as unknown as {
|
|
170
|
+
__WOOD_SCOPE_REPRO_GO__: (pageName: string, options?: Record<string, unknown>) => void;
|
|
171
|
+
}).__WOOD_SCOPE_REPRO_GO__ = (pageName: string, options?: Record<string, unknown>) => {
|
|
172
|
+
navigation.go(\`main.\${pageName}\`, {}, {}, options ?? {});
|
|
173
|
+
};
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<NavigationShell
|
|
177
|
+
{manifest}
|
|
178
|
+
initialEntry={entries[0]}
|
|
179
|
+
{initialControllerSet}
|
|
180
|
+
{createControllerSetFn}
|
|
181
|
+
{getViewManifestEntryFn}
|
|
182
|
+
/>`,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const pageErrors: string[] = [];
|
|
186
|
+
const consoleErrors: string[] = [];
|
|
187
|
+
const harness = await browser.createHarness({
|
|
188
|
+
rootDir: woodRoot,
|
|
189
|
+
browser: { headless: true },
|
|
190
|
+
page: {
|
|
191
|
+
beforeMount({ page }) {
|
|
192
|
+
page.on('pageerror', (error: Error) => pageErrors.push(error.message));
|
|
193
|
+
page.on('console', (message: { type(): string; text(): string }) => {
|
|
194
|
+
if (message.type() === 'error') consoleErrors.push(message.text());
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
harnesses.push(harness);
|
|
200
|
+
const mounted = await harness.mountComponent(
|
|
201
|
+
path.join(rootDir, 'ui', 'NavigationShellScopesRepro.svelte'),
|
|
202
|
+
);
|
|
203
|
+
await mounted.locator('[data-repro-view]').waitFor({ state: 'visible', timeout: 5_000 }).catch(
|
|
204
|
+
async (error: unknown) => {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`${error instanceof Error ? error.message : String(error)}\n` +
|
|
207
|
+
`Page errors: ${pageErrors.join(' | ')}\n` +
|
|
208
|
+
`Console errors: ${consoleErrors.join(' | ')}\n` +
|
|
209
|
+
`HTML: ${await mounted.page.content()}`,
|
|
210
|
+
);
|
|
211
|
+
},
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const go = async (pageName: string, options?: Record<string, unknown>) => {
|
|
215
|
+
await mounted.page.evaluate(
|
|
216
|
+
({ pageName, options }: { pageName: string; options?: Record<string, unknown> }) => {
|
|
217
|
+
(window as unknown as {
|
|
218
|
+
__WOOD_SCOPE_REPRO_GO__: (pageName: string, options?: Record<string, unknown>) => void;
|
|
219
|
+
}).__WOOD_SCOPE_REPRO_GO__(pageName, options);
|
|
220
|
+
},
|
|
221
|
+
{ pageName, options },
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const renderedController = async () =>
|
|
226
|
+
(await mounted.locator('[data-repro-view]').textContent())?.replace(/\s+/g, ' ').trim();
|
|
227
|
+
|
|
228
|
+
const snapshot = async (): Promise<ScopeReproSnapshot> => {
|
|
229
|
+
const result = await mounted.evaluate<Omit<ScopeReproSnapshot, 'rendered' | 'pageErrors'> & { rendered: string }>(() => {
|
|
230
|
+
const state = (window as unknown as {
|
|
231
|
+
__WOOD_SCOPE_REPRO__: Omit<ScopeReproSnapshot, 'rendered' | 'pageErrors'>;
|
|
232
|
+
}).__WOOD_SCOPE_REPRO__;
|
|
233
|
+
return {
|
|
234
|
+
scopeDisposeCounts: { ...state.scopeDisposeCounts },
|
|
235
|
+
controllerDisposeCounts: { ...state.controllerDisposeCounts },
|
|
236
|
+
setCreateCalls: [...state.setCreateCalls],
|
|
237
|
+
createdIds: [...state.createdIds],
|
|
238
|
+
rendered: document.querySelector('[data-repro-view]')?.textContent?.replace(/\s+/g, ' ').trim() ?? '',
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
return { ...result, pageErrors: [...pageErrors] };
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
await expect.poll(renderedController).toBe('controller=ctrl:A');
|
|
245
|
+
|
|
246
|
+
// --- Behavior 1: A -> B with a preserved layout (layoutPreserve [true],
|
|
247
|
+
// default options): A's page scope disposed, layout scope kept, layout
|
|
248
|
+
// controller carried over (factory called with preservedDepth 1, no
|
|
249
|
+
// ctrl:L1 re-creation, no ctrl:L1/scope:L1 disposal).
|
|
250
|
+
await go('B');
|
|
251
|
+
await expect.poll(renderedController).toBe('controller=ctrl:B');
|
|
252
|
+
const afterB = await snapshot();
|
|
253
|
+
expect(afterB.pageErrors, 'no page errors after A -> B').toEqual([]);
|
|
254
|
+
expect(afterB.setCreateCalls, 'B set built with preservedDepth 1').toEqual(['A:0', 'B:1']);
|
|
255
|
+
expect(
|
|
256
|
+
afterB.createdIds.filter((id) => id === 'ctrl:L1'),
|
|
257
|
+
'preserved layout controller must not be re-created',
|
|
258
|
+
).toHaveLength(1);
|
|
259
|
+
expect(afterB.scopeDisposeCounts, 'A page scope disposed; L1 layout scope preserved').toEqual({
|
|
260
|
+
'scope:A': 1,
|
|
261
|
+
'scope:L1': 0,
|
|
262
|
+
'scope:B': 0,
|
|
263
|
+
});
|
|
264
|
+
expect(afterB.controllerDisposeCounts, 'A page controller disposed; L1 layout controller preserved').toEqual({
|
|
265
|
+
'ctrl:A': 1,
|
|
266
|
+
'ctrl:L1': 0,
|
|
267
|
+
'ctrl:B': 0,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// --- Behavior 2: B -> C drops the preserved layout: the layout scope
|
|
271
|
+
// and layout controller are disposed along with B's page scope.
|
|
272
|
+
await go('C');
|
|
273
|
+
await expect.poll(renderedController).toBe('controller=ctrl:C');
|
|
274
|
+
const afterC = await snapshot();
|
|
275
|
+
expect(afterC.pageErrors, 'no page errors after B -> C').toEqual([]);
|
|
276
|
+
expect(afterC.scopeDisposeCounts, 'dropping the layout disposes its scope and B page scope').toEqual({
|
|
277
|
+
'scope:A': 1,
|
|
278
|
+
'scope:L1': 1,
|
|
279
|
+
'scope:B': 1,
|
|
280
|
+
'scope:C': 0,
|
|
281
|
+
});
|
|
282
|
+
expect(afterC.controllerDisposeCounts).toEqual({
|
|
283
|
+
'ctrl:A': 1,
|
|
284
|
+
'ctrl:L1': 1,
|
|
285
|
+
'ctrl:B': 1,
|
|
286
|
+
'ctrl:C': 0,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// --- Behavior 3 setup: C -> D. D's layout L2 is non-preservable, so its
|
|
290
|
+
// controller lives in D's page scope (layoutScopes entry null).
|
|
291
|
+
await go('D');
|
|
292
|
+
await expect.poll(renderedController).toBe('controller=ctrl:D');
|
|
293
|
+
const afterD = await snapshot();
|
|
294
|
+
expect(afterD.pageErrors, 'no page errors after C -> D').toEqual([]);
|
|
295
|
+
expect(afterD.scopeDisposeCounts['scope:C'], 'C page scope disposed').toBe(1);
|
|
296
|
+
expect(afterD.controllerDisposeCounts['ctrl:C'], 'C page controller disposed').toBe(1);
|
|
297
|
+
expect(afterD.createdIds, 'no dedicated scope created for a non-preservable layout')
|
|
298
|
+
.not.toContain('scope:L2');
|
|
299
|
+
|
|
300
|
+
// --- Behavior 3: D -> E with forced { preserveLayouts: true }. The
|
|
301
|
+
// borrowed layout controller (owned by D's page scope) is carried
|
|
302
|
+
// forward, so D's page scope must be RETAINED, not disposed.
|
|
303
|
+
await go('E', { preserveLayouts: true });
|
|
304
|
+
await expect.poll(renderedController).toBe('controller=ctrl:E');
|
|
305
|
+
const afterE = await snapshot();
|
|
306
|
+
expect(afterE.pageErrors, 'no page errors after D -> E').toEqual([]);
|
|
307
|
+
expect(afterE.setCreateCalls.at(-1), 'E set built with preservedDepth 1').toBe('E:1');
|
|
308
|
+
expect(
|
|
309
|
+
afterE.createdIds.filter((id) => id === 'ctrl:L2'),
|
|
310
|
+
'borrowed layout controller must not be re-created',
|
|
311
|
+
).toHaveLength(1);
|
|
312
|
+
expect(
|
|
313
|
+
afterE.scopeDisposeCounts['scope:D'],
|
|
314
|
+
'D page scope must be retained while its layout controller is borrowed',
|
|
315
|
+
).toBe(0);
|
|
316
|
+
expect(afterE.controllerDisposeCounts['ctrl:L2'], 'borrowed layout controller kept alive').toBe(0);
|
|
317
|
+
expect(afterE.controllerDisposeCounts['ctrl:D'], 'D page controller still disposed').toBe(1);
|
|
318
|
+
|
|
319
|
+
// --- Behavior 3 release: E -> F drops the borrowed layout, so the
|
|
320
|
+
// retained page scope of D is finally disposed, along with ctrl:L2.
|
|
321
|
+
await go('F');
|
|
322
|
+
await expect.poll(renderedController).toBe('controller=ctrl:F');
|
|
323
|
+
const finalSnapshot = await snapshot();
|
|
324
|
+
expect(
|
|
325
|
+
finalSnapshot,
|
|
326
|
+
'Final scope/controller ledger after A->B->C->D->E(forced)->F.',
|
|
327
|
+
).toEqual({
|
|
328
|
+
scopeDisposeCounts: {
|
|
329
|
+
'scope:A': 1,
|
|
330
|
+
'scope:L1': 1,
|
|
331
|
+
'scope:B': 1,
|
|
332
|
+
'scope:C': 1,
|
|
333
|
+
'scope:D': 1,
|
|
334
|
+
'scope:E': 1,
|
|
335
|
+
'scope:F': 0,
|
|
336
|
+
},
|
|
337
|
+
controllerDisposeCounts: {
|
|
338
|
+
'ctrl:A': 1,
|
|
339
|
+
'ctrl:L1': 1,
|
|
340
|
+
'ctrl:B': 1,
|
|
341
|
+
'ctrl:C': 1,
|
|
342
|
+
'ctrl:D': 1,
|
|
343
|
+
'ctrl:L2': 1,
|
|
344
|
+
'ctrl:E': 1,
|
|
345
|
+
'ctrl:F': 0,
|
|
346
|
+
},
|
|
347
|
+
setCreateCalls: ['A:0', 'B:1', 'C:0', 'D:0', 'E:1', 'F:0'],
|
|
348
|
+
createdIds: [
|
|
349
|
+
'scope:A', 'ctrl:L1', 'scope:L1', 'ctrl:A',
|
|
350
|
+
'scope:B', 'ctrl:B',
|
|
351
|
+
'scope:C', 'ctrl:C',
|
|
352
|
+
'scope:D', 'ctrl:L2', 'ctrl:D',
|
|
353
|
+
'scope:E', 'ctrl:E',
|
|
354
|
+
'scope:F', 'ctrl:F',
|
|
355
|
+
],
|
|
356
|
+
rendered: 'controller=ctrl:F',
|
|
357
|
+
pageErrors: [],
|
|
358
|
+
});
|
|
359
|
+
expect(consoleErrors, 'no console errors across the whole flow').toEqual([]);
|
|
360
|
+
},
|
|
361
|
+
60_000,
|
|
362
|
+
);
|
|
363
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { getNavigation, type NavigationEntry } from '@noego/wood/navigation';
|
|
4
4
|
import { disposeControllerLifecycle } from '../controller/controller_lifecycle';
|
|
5
5
|
import { createNavigationMountKey } from '../navigation/navigation_identity';
|
|
6
|
+
import { computePreservedLayoutDepth as computeSharedPreservedLayoutDepth } from '../navigation/layout_preservation';
|
|
6
7
|
import WoodRecursiveRender from './WoodRecursiveRender.svelte';
|
|
7
8
|
import { getWoodAppEnv } from '../client/app_env';
|
|
8
9
|
|
|
@@ -20,6 +21,21 @@
|
|
|
20
21
|
|
|
21
22
|
type CreateViewControllerFn = (entry: ViewManifestEntry) => Promise<unknown>;
|
|
22
23
|
type CreateLayoutControllersFn = (entry: ViewManifestEntry, startIndex?: number) => Promise<Array<unknown>>;
|
|
24
|
+
type ScopeLike = { dispose?: () => Promise<void> };
|
|
25
|
+
/**
|
|
26
|
+
* A view lifecycle's controllers plus the IoC scopes that own them:
|
|
27
|
+
* one page scope shared by the view controller and non-preservable layout
|
|
28
|
+
* controllers, and a dedicated scope per preservable layout (null entries
|
|
29
|
+
* mean "owned by the page scope"). Legacy factory props produce sets with
|
|
30
|
+
* null scopes — controller-only lifecycle, no scope disposal.
|
|
31
|
+
*/
|
|
32
|
+
type ControllerSet = {
|
|
33
|
+
controller: unknown;
|
|
34
|
+
layoutControllers: Array<unknown>;
|
|
35
|
+
pageScope?: ScopeLike | null;
|
|
36
|
+
layoutScopes?: Array<ScopeLike | null>;
|
|
37
|
+
};
|
|
38
|
+
type CreateControllerSetFn = (entry: ViewManifestEntry, preservedDepth?: number) => Promise<ControllerSet>;
|
|
23
39
|
type GetViewManifestEntryFn = (viewKey: string) => ViewManifestEntry | null;
|
|
24
40
|
type NavigationOptions = {
|
|
25
41
|
preserveLayouts?: boolean;
|
|
@@ -41,27 +57,50 @@
|
|
|
41
57
|
initialLayoutControllers,
|
|
42
58
|
createViewControllerFn,
|
|
43
59
|
createLayoutControllersFn,
|
|
60
|
+
initialControllerSet,
|
|
61
|
+
createControllerSetFn,
|
|
44
62
|
getViewManifestEntryFn,
|
|
45
63
|
}: {
|
|
46
64
|
manifest: Record<string, ViewManifestEntry>;
|
|
47
65
|
initialEntry: ViewManifestEntry;
|
|
48
|
-
initialController
|
|
49
|
-
initialLayoutControllers
|
|
50
|
-
createViewControllerFn
|
|
51
|
-
createLayoutControllersFn
|
|
66
|
+
initialController?: unknown;
|
|
67
|
+
initialLayoutControllers?: Array<unknown>;
|
|
68
|
+
createViewControllerFn?: CreateViewControllerFn;
|
|
69
|
+
createLayoutControllersFn?: CreateLayoutControllersFn;
|
|
70
|
+
initialControllerSet?: ControllerSet;
|
|
71
|
+
createControllerSetFn?: CreateControllerSetFn;
|
|
52
72
|
getViewManifestEntryFn: GetViewManifestEntryFn;
|
|
53
73
|
} = $props();
|
|
54
74
|
|
|
75
|
+
// Unified controller-set factory: prefer the scope-aware contract; wrap
|
|
76
|
+
// the legacy split factories (scope-less) for backward compatibility.
|
|
77
|
+
const buildControllerSet: CreateControllerSetFn = createControllerSetFn
|
|
78
|
+
?? (async (entry, preservedDepth = 0) => ({
|
|
79
|
+
controller: await createViewControllerFn?.(entry),
|
|
80
|
+
layoutControllers: (await createLayoutControllersFn?.(entry, preservedDepth)) ?? [],
|
|
81
|
+
pageScope: null,
|
|
82
|
+
layoutScopes: [],
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
async function disposeScope(scope: ScopeLike | null | undefined, context: string): Promise<void> {
|
|
86
|
+
if (!scope || typeof scope.dispose !== 'function') return;
|
|
87
|
+
try {
|
|
88
|
+
await scope.dispose();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
reportNavigationError(context, error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
55
94
|
function getInitialEntry(): ViewManifestEntry {
|
|
56
95
|
return initialEntry;
|
|
57
96
|
}
|
|
58
97
|
|
|
59
98
|
function getInitialController(): unknown {
|
|
60
|
-
return initialController;
|
|
99
|
+
return initialControllerSet ? initialControllerSet.controller : initialController;
|
|
61
100
|
}
|
|
62
101
|
|
|
63
102
|
function getInitialLayoutControllers(): Array<unknown> {
|
|
64
|
-
return initialLayoutControllers;
|
|
103
|
+
return initialControllerSet ? initialControllerSet.layoutControllers : (initialLayoutControllers ?? []);
|
|
65
104
|
}
|
|
66
105
|
|
|
67
106
|
let activeEntry = $state(getInitialEntry());
|
|
@@ -78,6 +117,13 @@
|
|
|
78
117
|
let navigationTransitionQueue: Promise<void> = Promise.resolve();
|
|
79
118
|
let destroyed = false;
|
|
80
119
|
|
|
120
|
+
// Scope ownership (null = owned by the page scope / legacy scope-less set)
|
|
121
|
+
let activePageScope: ScopeLike | null = initialControllerSet?.pageScope ?? null;
|
|
122
|
+
let activeLayoutScopes: Array<ScopeLike | null> = [...(initialControllerSet?.layoutScopes ?? [])];
|
|
123
|
+
// Outgoing page scopes kept alive because forced preservation carried one
|
|
124
|
+
// of their layout controllers forward; disposed when those layouts leave.
|
|
125
|
+
let retainedPageScopes: Array<{ scope: ScopeLike; keys: Set<string> }> = [];
|
|
126
|
+
|
|
81
127
|
let activePage = $derived({
|
|
82
128
|
key: activeEntry.key,
|
|
83
129
|
windowName: activeEntry.windowName,
|
|
@@ -161,49 +207,14 @@
|
|
|
161
207
|
);
|
|
162
208
|
}
|
|
163
209
|
|
|
164
|
-
function layoutIdentityMatches(
|
|
165
|
-
left: ViewManifestEntry,
|
|
166
|
-
right: ViewManifestEntry,
|
|
167
|
-
index: number,
|
|
168
|
-
): boolean {
|
|
169
|
-
return (
|
|
170
|
-
left.layoutPaths[index] === right.layoutPaths[index] &&
|
|
171
|
-
left.layoutControllers[index] === right.layoutControllers[index]
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function shouldPreserveLayout(
|
|
176
|
-
from: ViewManifestEntry,
|
|
177
|
-
to: ViewManifestEntry,
|
|
178
|
-
index: number,
|
|
179
|
-
options: NavigationOptions,
|
|
180
|
-
): boolean {
|
|
181
|
-
if (options.preserveLayouts === false) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
if (options.preserveLayouts === true) {
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
return from.layoutPreserve?.[index] === true && to.layoutPreserve?.[index] === true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
210
|
function computePreservedLayoutDepth(
|
|
191
211
|
from: ViewManifestEntry,
|
|
192
212
|
to: ViewManifestEntry,
|
|
193
213
|
options: NavigationOptions,
|
|
194
214
|
): number {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (!layoutIdentityMatches(from, to, index)) {
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
if (!shouldPreserveLayout(from, to, index, options)) {
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
depth += 1;
|
|
205
|
-
}
|
|
206
|
-
return depth;
|
|
215
|
+
// The rule itself lives in navigation/layout_preservation so the Node
|
|
216
|
+
// application slice preserves exactly what this component preserves.
|
|
217
|
+
return computeSharedPreservedLayoutDepth(from, to, options);
|
|
207
218
|
}
|
|
208
219
|
|
|
209
220
|
async function disposeControllerSet(
|
|
@@ -250,16 +261,24 @@
|
|
|
250
261
|
async function createTransitionControllers(
|
|
251
262
|
entry: ViewManifestEntry,
|
|
252
263
|
preservedDepth: number,
|
|
253
|
-
): Promise<
|
|
264
|
+
): Promise<ControllerSet> {
|
|
265
|
+
if (createControllerSetFn) {
|
|
266
|
+
// The scope-aware factory owns its own failure cleanup (it disposes
|
|
267
|
+
// the scopes it created before rethrowing).
|
|
268
|
+
return createControllerSetFn(entry, preservedDepth);
|
|
269
|
+
}
|
|
270
|
+
|
|
254
271
|
const [controllerResult, layoutControllersResult] = await Promise.allSettled([
|
|
255
|
-
createViewControllerFn(entry),
|
|
256
|
-
createLayoutControllersFn(entry, preservedDepth),
|
|
272
|
+
createViewControllerFn!(entry),
|
|
273
|
+
createLayoutControllersFn!(entry, preservedDepth),
|
|
257
274
|
]);
|
|
258
275
|
|
|
259
276
|
if (controllerResult.status === 'fulfilled' && layoutControllersResult.status === 'fulfilled') {
|
|
260
277
|
return {
|
|
261
278
|
controller: controllerResult.value,
|
|
262
279
|
layoutControllers: layoutControllersResult.value,
|
|
280
|
+
pageScope: null,
|
|
281
|
+
layoutScopes: [],
|
|
263
282
|
};
|
|
264
283
|
}
|
|
265
284
|
|
|
@@ -311,10 +330,15 @@
|
|
|
311
330
|
const preservedLayoutKeys = activeLayoutKeys.slice(0, preservedDepth);
|
|
312
331
|
const previousController = activeController;
|
|
313
332
|
const previousLayoutControllers = [...activeLayoutControllers];
|
|
314
|
-
const
|
|
333
|
+
const previousPageScope = activePageScope;
|
|
334
|
+
const previousLayoutScopes = [...activeLayoutScopes];
|
|
335
|
+
const nextSet = await createTransitionControllers(entry, preservedDepth);
|
|
336
|
+
const { controller, layoutControllers } = nextSet;
|
|
315
337
|
|
|
316
338
|
if (destroyed) {
|
|
317
339
|
await disposeStagedControllers(controller, layoutControllers);
|
|
340
|
+
for (const scope of nextSet.layoutScopes ?? []) await disposeScope(scope, 'staged layout scope disposal failed');
|
|
341
|
+
await disposeScope(nextSet.pageScope, 'staged page scope disposal failed');
|
|
318
342
|
return;
|
|
319
343
|
}
|
|
320
344
|
|
|
@@ -325,6 +349,15 @@
|
|
|
325
349
|
);
|
|
326
350
|
const nextLayoutKeys = createLayoutMountKeys(entry, nextPageKey, preservedLayoutKeys);
|
|
327
351
|
|
|
352
|
+
// Merge scope ownership the same way controllers merge: preserved prefix
|
|
353
|
+
// keeps its scopes, the created suffix brings the new set's scopes.
|
|
354
|
+
const nextLayoutScopes: Array<ScopeLike | null> = [];
|
|
355
|
+
for (let index = 0; index < entry.layoutPaths.length; index += 1) {
|
|
356
|
+
nextLayoutScopes[index] = index < preservedDepth
|
|
357
|
+
? (previousLayoutScopes[index] ?? null)
|
|
358
|
+
: ((nextSet.layoutScopes ?? [])[index] ?? null);
|
|
359
|
+
}
|
|
360
|
+
|
|
328
361
|
activeEntry = entry;
|
|
329
362
|
activeController = controller;
|
|
330
363
|
activeLayoutControllers = nextLayoutControllers;
|
|
@@ -332,8 +365,44 @@
|
|
|
332
365
|
activeQuery = { ...navEntry.query };
|
|
333
366
|
activePageKey = nextPageKey;
|
|
334
367
|
activeLayoutKeys = nextLayoutKeys;
|
|
368
|
+
activePageScope = nextSet.pageScope ?? null;
|
|
369
|
+
activeLayoutScopes = nextLayoutScopes;
|
|
335
370
|
|
|
336
371
|
await disposeControllerSet(previousController, previousLayoutControllers, preservedDepth);
|
|
372
|
+
|
|
373
|
+
// Scope lifecycle for the outgoing page:
|
|
374
|
+
// - dedicated layout scopes past the preserved prefix are done -> dispose;
|
|
375
|
+
// - the page scope is done UNLESS forced preservation carried one of its
|
|
376
|
+
// page-scope layout controllers forward -> retain it until they leave.
|
|
377
|
+
for (let index = preservedDepth; index < previousLayoutScopes.length; index += 1) {
|
|
378
|
+
await disposeScope(previousLayoutScopes[index], 'layout scope disposal failed');
|
|
379
|
+
}
|
|
380
|
+
const borrowedKeys = new Set<string>();
|
|
381
|
+
for (let index = 0; index < preservedDepth; index += 1) {
|
|
382
|
+
if (previousLayoutControllers[index] !== undefined && (previousLayoutScopes[index] ?? null) === null) {
|
|
383
|
+
borrowedKeys.add(preservedLayoutKeys[index]);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (previousPageScope) {
|
|
387
|
+
if (borrowedKeys.size > 0) {
|
|
388
|
+
retainedPageScopes.push({ scope: previousPageScope, keys: borrowedKeys });
|
|
389
|
+
} else {
|
|
390
|
+
await disposeScope(previousPageScope, 'page scope disposal failed');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Retained scopes whose borrowed layouts are no longer preserved are done.
|
|
395
|
+
const liveKeys = new Set(nextLayoutKeys);
|
|
396
|
+
const stillRetained: typeof retainedPageScopes = [];
|
|
397
|
+
for (const retained of retainedPageScopes) {
|
|
398
|
+
retained.keys = new Set([...retained.keys].filter((key) => liveKeys.has(key)));
|
|
399
|
+
if (retained.keys.size > 0) {
|
|
400
|
+
stillRetained.push(retained);
|
|
401
|
+
} else {
|
|
402
|
+
await disposeScope(retained.scope, 'retained page scope disposal failed');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
retainedPageScopes = stillRetained;
|
|
337
406
|
}
|
|
338
407
|
|
|
339
408
|
function enqueueNavigationEntry(navEntry: NavigationEntry | null): void {
|
|
@@ -408,7 +477,15 @@
|
|
|
408
477
|
unsubscribe();
|
|
409
478
|
unsubscribeWindowNavigate?.();
|
|
410
479
|
void navigationTransitionQueue
|
|
411
|
-
.then(() =>
|
|
480
|
+
.then(async () => {
|
|
481
|
+
await disposeActiveControllers();
|
|
482
|
+
for (const scope of activeLayoutScopes) await disposeScope(scope, 'teardown layout scope disposal failed');
|
|
483
|
+
await disposeScope(activePageScope, 'teardown page scope disposal failed');
|
|
484
|
+
for (const retained of retainedPageScopes) {
|
|
485
|
+
await disposeScope(retained.scope, 'teardown retained scope disposal failed');
|
|
486
|
+
}
|
|
487
|
+
retainedPageScopes = [];
|
|
488
|
+
})
|
|
412
489
|
.catch((error: unknown) => {
|
|
413
490
|
reportNavigationError('navigation teardown failed', error);
|
|
414
491
|
});
|