akanjs 3.0.2-rc.0 → 3.1.0-rc.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/CHANGELOG.md +26 -0
- package/base/baseEnv.ts +0 -4
- package/base/primitiveRegistry.ts +2 -2
- package/client/cookie.ts +5 -1
- package/client/csrLocation.ts +122 -0
- package/client/csrTypes.ts +32 -4
- package/client/device.ts +2 -1
- package/client/frameConfig.ts +78 -7
- package/client/index.ts +3 -0
- package/client/makePageProto.tsx +3 -2
- package/client/native/external-view.ts +1 -0
- package/client/route/RootLayoutDefinition.ts +7 -1
- package/client/route/RouteHead.ts +108 -0
- package/client/route/index.ts +1 -0
- package/client/router.ts +36 -23
- package/client/rscNavigation.ts +8 -2
- package/client/viewportMeta.ts +60 -0
- package/client/webManifest.ts +18 -0
- package/common/Logger.ts +7 -2
- package/common/PipelineNames.ts +31 -0
- package/common/PipelinePattern.ts +70 -0
- package/common/PipelinePlanSchema.ts +490 -0
- package/common/backoff.ts +14 -0
- package/common/index.ts +39 -0
- package/common/pipelinePlan.ts +259 -0
- package/common/routeConvention.ts +1 -0
- package/common/types.ts +17 -0
- package/constant/constantRegistry.ts +26 -6
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/trans.ts +9 -1
- package/document/into.ts +5 -0
- package/fetch/client/fetchClient.ts +135 -31
- package/fetch/client/httpClient.ts +45 -0
- package/fetch/client/wsClient.ts +125 -16
- package/fetch/fetchType/buildFetch.type.ts +1 -0
- package/index.ts +243 -10
- package/package.json +1 -5
- package/server/akanApp.ts +5 -2
- package/server/akanServer.ts +44 -8
- package/server/artifact/builderRpc.ts +8 -3
- package/server/artifact/ipcTypes.ts +5 -2
- package/server/di/diLifecycle.ts +39 -3
- package/server/di/utils.ts +28 -1
- package/server/ed25519Key.ts +48 -0
- package/server/index.ts +1 -0
- package/server/machine/index.ts +2 -0
- package/server/machine/machineAccount.ts +27 -0
- package/server/machine/machineToken.ts +151 -0
- package/server/mcp/McpApps.ts +256 -0
- package/server/mcp/McpDispatcher.ts +31 -7
- package/server/mcp/McpRouter.ts +209 -46
- package/server/ops/opsToken.ts +3 -10
- package/server/resolver/database.resolver.ts +3 -0
- package/server/resolver/signal.resolver.ts +40 -3
- package/server/routeElementComposer.tsx +78 -2
- package/server/routeState.ts +6 -0
- package/server/routeTreeBuilder.ts +26 -9
- package/server/routing/apiRouter.ts +6 -3
- package/server/routing/roomReplay.ts +88 -0
- package/server/rscClient.tsx +53 -8
- package/server/rscWorker.tsx +141 -27
- package/server/rscWorkerHost.ts +18 -5
- package/server/sitemap.ts +14 -0
- package/server/ssrFromRscRenderer.tsx +12 -6
- package/server/ssrTypes.ts +3 -1
- package/server/systemPageDocument.tsx +38 -30
- package/server/types.tsx +2 -0
- package/server/webRouter.ts +74 -5
- package/service/injectInfo.ts +9 -1
- package/service/ipcTypes.ts +2 -1
- package/service/predefinedAdaptor/database.adaptor.ts +1 -146
- package/service/predefinedAdaptor/queue.adaptor.ts +44 -6
- package/service/predefinedAdaptor/queueBackoff.ts +12 -0
- package/service/predefinedAdaptor/solidQueue.adaptor.ts +17 -13
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +44 -2
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +3 -0
- package/service/predefinedAdaptor/sql/dialect/sqlite.ts +5 -2
- package/service/predefinedAdaptor/sql/search/fts5.ts +1 -1
- package/service/predefinedAdaptor/sql/types.ts +8 -6
- package/service/predefinedAdaptor/sql/values.ts +0 -23
- package/service/serve.ts +5 -1
- package/signal/agent.signal.ts +1 -1
- package/signal/base.signal.ts +2 -2
- package/signal/endpointIdempotency.ts +53 -0
- package/signal/internalArg.ts +14 -0
- package/signal/mcp/McpDocument.ts +38 -1
- package/signal/mcp/mcpProtocol.ts +18 -0
- package/signal/signalContext.ts +58 -30
- package/signal/signalRegistry.ts +14 -4
- package/signal/types.ts +18 -3
- package/store/agent/AgentCursor.ts +2 -1
- package/store/agent/StoreSurfaceSource.ts +1 -1
- package/store/agentic/StToolBuilder.ts +3 -0
- package/test/conformance.ts +9 -33
- package/types/base/baseEnv.d.ts +0 -4
- package/types/base/primitiveRegistry.d.ts +1 -1
- package/types/client/csrLocation.d.ts +32 -0
- package/types/client/csrTypes.d.ts +40 -4
- package/types/client/frameConfig.d.ts +14 -2
- package/types/client/index.d.ts +3 -0
- package/types/client/native/external-view.d.ts +1 -0
- package/types/client/route/RootLayoutDefinition.d.ts +2 -1
- package/types/client/route/RouteHead.d.ts +13 -0
- package/types/client/route/index.d.ts +1 -0
- package/types/client/router.d.ts +6 -0
- package/types/client/rscNavigation.d.ts +4 -0
- package/types/client/viewportMeta.d.ts +18 -0
- package/types/client/webManifest.d.ts +4 -0
- package/types/common/PipelineNames.d.ts +6 -0
- package/types/common/PipelinePattern.d.ts +14 -0
- package/types/common/PipelinePlanSchema.d.ts +9 -0
- package/types/common/backoff.d.ts +10 -0
- package/types/common/index.d.ts +5 -0
- package/types/common/pipelinePlan.d.ts +224 -0
- package/types/common/types.d.ts +16 -0
- package/types/constant/constantRegistry.d.ts +5 -2
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/into.d.ts +9 -0
- package/types/fetch/client/fetchClient.d.ts +31 -4
- package/types/fetch/client/httpClient.d.ts +8 -0
- package/types/fetch/client/wsClient.d.ts +26 -2
- package/types/fetch/fetchType/buildFetch.type.d.ts +1 -0
- package/types/index.d.ts +230 -9
- package/types/server/akanServer.d.ts +16 -1
- package/types/server/artifact/builderRpc.d.ts +7 -2
- package/types/server/artifact/ipcTypes.d.ts +5 -1
- package/types/server/di/utils.d.ts +1 -0
- package/types/server/ed25519Key.d.ts +13 -0
- package/types/server/index.d.ts +1 -0
- package/types/server/machine/index.d.ts +2 -0
- package/types/server/machine/machineAccount.d.ts +5 -0
- package/types/server/machine/machineToken.d.ts +29 -0
- package/types/server/machine.d.ts +1 -0
- package/types/server/mcp/McpApps.d.ts +50 -0
- package/types/server/mcp/McpDispatcher.d.ts +4 -1
- package/types/server/mcp/McpRouter.d.ts +4 -0
- package/types/server/resolver/signal.resolver.d.ts +2 -1
- package/types/server/routeElementComposer.d.ts +24 -4
- package/types/server/routeState.d.ts +4 -1
- package/types/server/routing/roomReplay.d.ts +24 -0
- package/types/server/rscWorkerHost.d.ts +5 -2
- package/types/server/sitemap.d.ts +2 -0
- package/types/server/ssrFromRscRenderer.d.ts +3 -3
- package/types/server/ssrTypes.d.ts +4 -1
- package/types/server/systemPageDocument.d.ts +2 -0
- package/types/server/types.d.ts +4 -1
- package/types/server/webRouter.d.ts +3 -1
- package/types/service/ipcTypes.d.ts +2 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +1 -24
- package/types/service/predefinedAdaptor/queue.adaptor.d.ts +10 -3
- package/types/service/predefinedAdaptor/queueBackoff.d.ts +4 -0
- package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +3 -3
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +6 -1
- package/types/service/predefinedAdaptor/sql/dialect/postgres.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/dialect/sqlite.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/search/fts5.d.ts +1 -1
- package/types/service/predefinedAdaptor/sql/types.d.ts +9 -5
- package/types/service/predefinedAdaptor/sql/values.d.ts +0 -3
- package/types/service/serve.d.ts +3 -0
- package/types/signal/endpointIdempotency.d.ts +7 -0
- package/types/signal/internalArg.d.ts +10 -0
- package/types/signal/mcp/McpDocument.d.ts +8 -0
- package/types/signal/mcp/mcpProtocol.d.ts +17 -0
- package/types/signal/signalContext.d.ts +5 -3
- package/types/signal/signalRegistry.d.ts +4 -4
- package/types/signal/types.d.ts +21 -1
- package/types/store/agentic/StToolBuilder.d.ts +2 -0
- package/types/test/conformance.d.ts +7 -10
- package/types/ui/Agent/Chat.d.ts +5 -1
- package/types/ui/Agent/EmbedApproval.d.ts +1 -0
- package/types/ui/Agent/index.d.ts +3 -0
- package/types/ui/Agent/useKeyboardFrame.d.ts +2 -0
- package/types/ui/Agent/useScreenActions.d.ts +18 -0
- package/types/ui/BottomSheet.d.ts +1 -0
- package/types/ui/Constant/Doc.d.ts +11 -17
- package/types/ui/Constant/index.d.ts +7 -2
- package/types/ui/Constant/schemaDoc.d.ts +11 -0
- package/types/ui/Dialog/Modal.d.ts +6 -1
- package/types/ui/Dialog/index.d.ts +1 -1
- package/types/ui/Dialog/modalSurface.d.ts +12 -0
- package/types/ui/Layout/BottomTab.d.ts +11 -2
- package/types/ui/Layout/FrameSlotFallback.d.ts +9 -0
- package/types/ui/Layout/Navbar.d.ts +4 -1
- package/types/ui/Layout/TopInset.d.ts +1 -1
- package/types/ui/Layout/index.d.ts +3 -3
- package/types/ui/Link/Action.d.ts +5 -1
- package/types/ui/Link/Anchor.d.ts +5 -3
- package/types/ui/Link/activePath.d.ts +13 -0
- package/types/ui/Link/index.d.ts +1 -1
- package/types/ui/Modal.d.ts +6 -1
- package/types/ui/Model/EditModal.d.ts +6 -1
- package/types/ui/Model/New.d.ts +6 -1
- package/types/ui/Model/ViewEditModal.d.ts +6 -1
- package/types/ui/Model/ViewModal.d.ts +6 -1
- package/types/ui/Model/newFormOrigin.d.ts +7 -0
- package/types/ui/Portal.d.ts +3 -1
- package/types/ui/Reference/origin.d.ts +12 -0
- package/types/ui/Signal/Doc.d.ts +23 -18
- package/types/ui/Signal/Listener.d.ts +2 -6
- package/types/ui/Signal/Object.d.ts +6 -10
- package/types/ui/Signal/Response.d.ts +3 -6
- package/types/ui/Signal/RestApi.d.ts +11 -15
- package/types/ui/Signal/WebSocket.d.ts +11 -21
- package/types/ui/Signal/endpointEntries.d.ts +8 -0
- package/types/ui/Signal/index.d.ts +35 -12
- package/types/ui/Signal/signalText.d.ts +178 -0
- package/types/ui/System/CSR.d.ts +2 -2
- package/types/ui/System/Client.d.ts +4 -3
- package/types/ui/System/Common.d.ts +27 -6
- package/types/ui/System/SSR.d.ts +6 -6
- package/types/ui/Tooltip.d.ts +4 -2
- package/types/ui/index.d.ts +2 -1
- package/types/ui/overlayLayer.d.ts +6 -0
- package/types/ui/overlayPosition.d.ts +17 -0
- package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +14 -3
- package/types/vendor/@akanjs/native/packages/cli/src/commands/update.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +30 -5
- package/types/vendor/@akanjs/native/packages/cli/src/lib/compat.d.ts +8 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/distribution.d.ts +22 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +4 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +16 -10
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +51 -7
- package/types/vendor/@akanjs/native/packages/cli/src/lib/vectors.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +7 -0
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +36 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +2 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +11 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/desktop.d.ts +17 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/index.d.ts +132 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src.d.ts +1 -0
- package/types/vendor/@akanjs/native/plugins/updates/src/index.d.ts +8 -0
- package/types/vendor/@akanjs/native/plugins/window/src/desktop.d.ts +4 -1
- package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +49 -0
- package/types/vendor/use-agentic/types.d.ts +2 -0
- package/types/webkit/CsrStack.d.ts +1 -1
- package/types/webkit/McpAppAgent.d.ts +14 -0
- package/types/webkit/McpAppApprovals.d.ts +8 -0
- package/types/webkit/McpAppBridge.d.ts +101 -0
- package/types/webkit/McpAppView.d.ts +21 -0
- package/types/webkit/index.d.ts +1 -0
- package/types/webkit/useLocation.d.ts +1 -1
- package/types/webkit/useVisualViewportFrame.d.ts +13 -0
- package/ui/Agent/Chat.tsx +29 -10
- package/ui/Agent/Composer.tsx +2 -1
- package/ui/Agent/EmbedApproval.tsx +17 -0
- package/ui/Agent/RichInput.tsx +1 -1
- package/ui/Agent/index.ts +2 -0
- package/ui/Agent/useKeyboardFrame.ts +10 -0
- package/ui/Agent/useScreenActions.ts +45 -0
- package/ui/BottomSheet.tsx +82 -53
- package/ui/Constant/Doc.tsx +113 -60
- package/ui/Constant/index.ts +2 -2
- package/ui/Constant/schemaDoc.ts +86 -26
- package/ui/Dialog/LegacyModal.tsx +53 -52
- package/ui/Dialog/Modal.tsx +93 -49
- package/ui/Dialog/modalSurface.ts +61 -0
- package/ui/DragAction.tsx +1 -1
- package/ui/DraggableList.tsx +1 -1
- package/ui/Dropdown.tsx +4 -6
- package/ui/KeyboardAvoiding.tsx +1 -1
- package/ui/Layout/BottomInset.tsx +20 -14
- package/ui/Layout/BottomTab.tsx +42 -16
- package/ui/Layout/FrameSlotFallback.tsx +59 -0
- package/ui/Layout/Navbar.tsx +37 -11
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Layout/TopInset.tsx +15 -14
- package/ui/Link/Action.tsx +15 -5
- package/ui/Link/Anchor.tsx +25 -13
- package/ui/Link/activePath.ts +25 -0
- package/ui/Link/index.tsx +1 -1
- package/ui/Load/Pagination.tsx +1 -1
- package/ui/Load/Units.tsx +1 -1
- package/ui/Loading/ProgressBar.tsx +1 -1
- package/ui/Modal.tsx +9 -0
- package/ui/Model/EditModal.tsx +16 -1
- package/ui/Model/New.tsx +25 -2
- package/ui/Model/NewWrapper_Client.tsx +2 -0
- package/ui/Model/ViewEditModal.tsx +9 -0
- package/ui/Model/ViewModal.tsx +16 -1
- package/ui/Model/newFormOrigin.ts +45 -0
- package/ui/Popconfirm.tsx +5 -7
- package/ui/Portal.tsx +4 -7
- package/ui/Reference/origin.ts +31 -0
- package/ui/ScreenNavigator.tsx +1 -1
- package/ui/Select.tsx +2 -7
- package/ui/Signal/Arg.tsx +2 -2
- package/ui/Signal/Doc.tsx +125 -63
- package/ui/Signal/Endpoint.tsx +11 -10
- package/ui/Signal/Listener.tsx +7 -10
- package/ui/Signal/Object.tsx +7 -14
- package/ui/Signal/Response.tsx +24 -24
- package/ui/Signal/RestApi.tsx +63 -52
- package/ui/Signal/WebSocket.tsx +65 -72
- package/ui/Signal/endpointEntries.ts +25 -0
- package/ui/Signal/index.ts +23 -6
- package/ui/Signal/signalText.ts +59 -0
- package/ui/System/CSR.tsx +34 -8
- package/ui/System/Client.tsx +36 -5
- package/ui/System/Common.tsx +76 -32
- package/ui/System/SSR.tsx +73 -31
- package/ui/Toast.tsx +1 -1
- package/ui/Tooltip.tsx +84 -30
- package/ui/index.ts +11 -1
- package/ui/overlayLayer.ts +25 -2
- package/ui/overlayPosition.ts +90 -0
- package/ui/recipe/inputRecipe.ts +8 -1
- package/ui/styles.css +152 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +22 -2
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +37 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdateReports.kt +169 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdates.kt +110 -38
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +20 -0
- package/vendor/@akanjs/native/native/desktop/Cargo.lock +16 -16
- package/vendor/@akanjs/native/native/desktop/src/external.rs +651 -0
- package/vendor/@akanjs/native/native/desktop/src/json.rs +23 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +228 -5
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeApp.swift +2 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +47 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdateReports.swift +145 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdates.swift +112 -43
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +18 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +24 -2
- package/vendor/@akanjs/native/packages/cli/src/api.ts +22 -6
- package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +24 -8
- package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +11 -9
- package/vendor/@akanjs/native/packages/cli/src/config.ts +35 -7
- package/vendor/@akanjs/native/packages/cli/src/lib/compat.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +6 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +11 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/distribution.ts +73 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +15 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +70 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +159 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +4 -2
- package/vendor/@akanjs/native/packages/cli/src/platforms/ios.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +25 -11
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +14 -3
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +49 -0
- package/vendor/@akanjs/native/packages/core/vectors/updates.json +31 -0
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +3 -0
- package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +2 -0
- package/vendor/@akanjs/native/packages/desktop/src/server.ts +70 -6
- package/vendor/@akanjs/native/plugins/external-view/native-plugin.json +23 -0
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +321 -0
- package/vendor/@akanjs/native/plugins/external-view/src/index.ts +106 -0
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +133 -41
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +101 -36
- package/vendor/@akanjs/native/plugins/updates/native-plugin.json +4 -2
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +15 -1
- package/vendor/@akanjs/native/plugins/updates/src/index.ts +7 -1
- package/vendor/@akanjs/native/plugins/window/native-plugin.json +4 -0
- package/vendor/@akanjs/native/plugins/window/src/desktop.ts +68 -1
- package/vendor/@akanjs/native/plugins/window/src/index.ts +47 -0
- package/vendor/use-agentic/ToolRunner.ts +8 -1
- package/vendor/use-agentic/types.ts +2 -0
- package/webkit/CsrRouteTable.ts +1 -0
- package/webkit/CsrStack.ts +9 -2
- package/webkit/McpAppAgent.ts +138 -0
- package/webkit/McpAppApprovals.ts +56 -0
- package/webkit/McpAppBridge.ts +291 -0
- package/webkit/McpAppView.ts +258 -0
- package/webkit/bootCsr.tsx +54 -5
- package/webkit/index.ts +1 -0
- package/webkit/useCsrValues.ts +16 -10
- package/webkit/useFrameRuntime.ts +10 -1
- package/webkit/useLocation.ts +4 -3
- package/webkit/useVisualViewportFrame.tsx +52 -0
- package/service/predefinedAdaptor/sql/driver/libsql.ts +0 -45
- package/types/service/predefinedAdaptor/sql/driver/libsql.d.ts +0 -20
- package/types/ui/Agent/useKeyboardInset.d.ts +0 -1
- package/types/ui/ServerPortal.d.ts +0 -15
- package/ui/Agent/useKeyboardInset.ts +0 -19
- package/ui/ServerPortal.tsx +0 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# akanjs
|
|
2
2
|
|
|
3
|
+
## 3.0.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- be8ed50: Endpoints take a rate limit: `rateLimit: { calls, windowMs?, by?: "ip" | "account" } | false` in the signal option, and `option.setRateLimit({ budget, endpoints, accountKey, enabled })` in an app or lib `option.ts`. An `ip` budget is counted before the request body is read, an `account` budget after the middlewares and before the guards, and a refusal answers 429 `base.error.tooManyRequests` with `{ seconds }` and `Retry-After`. Counters share the cache across instances. `AKAN_RATE_LIMIT=off|on|<calls>[/<seconds>]`; unset, budgets apply everywhere but a `local` environment. A service counts a business key with `plug(RateLimit)` and `consume(key, { calls, windowMs })`. Fetches a page makes while rendering now carry the visitor's address, and the MCP limiter keys an anonymous caller by its trusted-proxy address instead of a raw `X-Real-IP` any caller could forge.
|
|
8
|
+
- af66669: The macOS dmg `build-desktop --installer true` makes opens on a drag-to-Applications window: no toolbar, sidebar or status bar, 128 pt icons of the app and the Applications link on a background with an arrow between them (1x and 2x in one multi-resolution TIFF), and the app's icon on the mounted disk (`.VolumeIcon.icns`). Its own files (`.background`, `.VolumeIcon.icns`, `.DS_Store`, `.fseventsd`) are hidden. The window is a `.DS_Store` written directly while a read-write image is mounted, never through Finder or AppleScript, so a headless CI builds the same image; the compressed image is made afterwards and is what gets signed, notarized and stapled. `native.desktop.dmg` (a target's too) replaces the background (`background`, `background2x`, or `false`) and moves the window and the icons (`window`, `iconSize`, `textSize`, `app`, `applications`, in points); the default background lines up with the default positions only.
|
|
9
|
+
- be8ed50: `buttonRecipe` gains `size: "xl"` and `inputRecipe` gains `kind: "select"` for a native `<select>`. Upgrade note: an app's own `recipes.button` / `recipes.input` override slot must accept the new values to typecheck.
|
|
10
|
+
- be8ed50: A slice's `get`/`cru`/`create`/`update`/`remove` guard may be `false`, which mounts no generated endpoint for that verb; `mcp: { cru: false }` only ever kept the verbs off the agent shelf. Upgrade note: a generated verb whose guards resolve to none is no longer mounted (it used to answer anyone over HTTP while the client never synthesized it). Resolving an endpoint class now warns about every endpoint that declares no guards.
|
|
11
|
+
- be8ed50: Reads and query-level writes can reach soft-removed rows with `{ withRemoved: true }` (`find`/`findOne`/`findById`'s third argument, `count`/`exists`/`updateMany`'s last, `updateOne`/`updateById`'s options). Upgrade note: a query with a `removedAt` condition only a removed row can meet (`q.exists("removedAt")`, a comparison) now throws `can never match` instead of silently matching nothing; add the flag where removed rows are meant. A removed document is read-only, and `updateById(id, { removedAt: null }, { withRemoved: true })` revives one.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- be8ed50: An HTTP query or mutation returning `Binary` now answers base64, which the client reads back as bytes; it used to answer `{"0":1,…}`. A return declared `Any` that turns out to be an ArrayBuffer or typed array now fails with a message pointing at `Binary` instead of sending `{}`.
|
|
16
|
+
- af66669: A CSR page or layout whose render fails no longer leaves a blank screen. The layer shows the error's message — "Cannot reach the server. Check your connection and try again." for `base.error.serverUnreachable`, a generic "This page could not be loaded." otherwise — and a Try again button that runs the render again. A render that redirected before it failed still shows nothing, since its page is leaving.
|
|
17
|
+
- be8ed50: `enumOf` infers its literal values without `as const`, so a value outside the list fails to compile. Upgrade note: code that relied on a widened `string`/`number` enum type may need a narrower type.
|
|
18
|
+
- be8ed50: `String(err)` of a dictionary `Err` appends its `data` as one line of JSON, secret-named keys masked, so a log line keeps what failed.
|
|
19
|
+
- be8ed50: Arguments are parsed after the `account` guards and before the `resource` guards, so a caller an account guard refuses never reaches an argument parser. A guard or middleware that reads an argument earlier still gets it. Upgrade note: an unauthenticated call with a malformed argument now answers 401/403 instead of 400. A subscribed room's guards are re-checked on a fresh context, so a guard that memoizes per context gives a new verdict.
|
|
20
|
+
- be8ed50: Changing an `immutable` field is refused with a 400 `base.error.immutableField` the client can translate, instead of a generic 500.
|
|
21
|
+
- be8ed50: A server caller's `limit: 0` reads every row again, like an omitted limit, instead of 20. A client's `limit: 0` is served the 500-row page ceiling with a one-time warn.
|
|
22
|
+
- af66669: A Linux desktop app's window is named after its app id (X11 `WM_CLASS`, Wayland `app_id`), so a dock or task bar finds its `<app id>.desktop` and shows that entry's name and icon instead of a generic one; the dock plugin's badge and progress name the same entry. The AppImage `build-desktop --installer true` makes carries `<app id>.desktop` with `StartupWMClass=<app id>` and `<app id>.png`, so the entry AppImageLauncher or appimaged installs under another file name still matches the window. The hidden entry that handles the app's deep links now shows the app's icon (installed as `~/.local/share/icons/hicolor/256x256/apps/<app id>.png`), and from an AppImage it starts the AppImage itself (`$APPIMAGE`) rather than the mount point it ran from, which is gone once the app quits, so a link opened after quitting the app no longer fails.
|
|
23
|
+
- bd55713: `Input.Number` (and `Field.Number`) keeps the decimal point when Enter or Escape settles a typed value: 0.25 used to display as 25 while the stored value stayed 0.25.
|
|
24
|
+
- be8ed50: On a server, `fetch.clone({ origin })` toward another origin no longer opens a websocket unless `connect: true`, so a server making one-off calls to another server no longer leaks an auto-reconnecting socket per clone.
|
|
25
|
+
- be8ed50: `setCookie` defaults to `path=/; SameSite=Lax; Secure` instead of `SameSite=None`, matching the auth cookie the server sets, and an options object overrides only the keys it names.
|
|
26
|
+
- a789952: In a signal test every `fetch.clone()` reports its own client address, so an `ip` rate limit counts each agent apart instead of every agent sharing loopback; `clone({ clientIp })` names one explicitly.
|
|
27
|
+
- be8ed50: A Mongo-style `schema.index({ x: "text" })` no longer builds a B-tree over the JSON value (which could fail long inserts on Postgres); it warns at boot and points at the `text` field role, and drops the index an earlier boot built for exactly that declaration.
|
|
28
|
+
|
|
3
29
|
## 3.0.1
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/base/baseEnv.ts
CHANGED
|
@@ -281,7 +281,7 @@ declare global {
|
|
|
281
281
|
[CLIENT_VALUE]: Dayjs;
|
|
282
282
|
[DEFAULT_VALUE]: Dayjs;
|
|
283
283
|
[PURIFIED_VALUE]: Dayjs;
|
|
284
|
-
[EXAMPLE_VALUE]:
|
|
284
|
+
[EXAMPLE_VALUE]: Dayjs;
|
|
285
285
|
validate(value: Date): boolean;
|
|
286
286
|
parseValue(input: Date): Dayjs;
|
|
287
287
|
serializeValue(value: Dayjs | Date): Date;
|
|
@@ -371,7 +371,7 @@ Object.assign(Date, {
|
|
|
371
371
|
...scalarPrimitiveStatics,
|
|
372
372
|
refName: "Date",
|
|
373
373
|
[DEFAULT_VALUE]: dayjs(new Date(-1)),
|
|
374
|
-
[EXAMPLE_VALUE]: dayjs(
|
|
374
|
+
[EXAMPLE_VALUE]: dayjs("2026-01-01T00:00:00.000Z"),
|
|
375
375
|
validate(value: Date | string | number) {
|
|
376
376
|
const date = new Date(value);
|
|
377
377
|
if (Number.isNaN(date.getTime())) return false;
|
package/client/cookie.ts
CHANGED
|
@@ -110,8 +110,12 @@ export const getStoredAuthToken = async (): Promise<string | undefined> => {
|
|
|
110
110
|
* honours — the app-scoped auth cookie (a CSR client's bearer token) and `Authorization: Bearer` — so the two never
|
|
111
111
|
* disagree on who is signed in. */
|
|
112
112
|
export const getAccount = <AddData = unknown>(): Account<AddData> => {
|
|
113
|
-
const jwt = getAuthToken() ?? getHeader("authorization")?.replace(/^Bearer\s+/i, "");
|
|
114
113
|
const defaultAccount = { appName: getEnv().appName, environment: getEnv().environment } as Account<AddData>;
|
|
114
|
+
|
|
115
|
+
const embedded = globalThis.__AKAN_EMBED__;
|
|
116
|
+
if (embedded)
|
|
117
|
+
return embedded.account ? ({ ...defaultAccount, ...embedded.account } as Account<AddData>) : defaultAccount;
|
|
118
|
+
const jwt = getAuthToken() ?? getHeader("authorization")?.replace(/^Bearer\s+/i, "");
|
|
115
119
|
if (!jwt) return defaultAccount;
|
|
116
120
|
const account = decodeJwtPayload<Account<AddData>>(jwt);
|
|
117
121
|
if (account.appName !== getEnv().appName || account.environment !== getEnv().environment) return defaultAccount;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
interface CsrLocationEntry {
|
|
2
|
+
href: string;
|
|
3
|
+
state: unknown;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CsrMemoryOwner {
|
|
7
|
+
leave: (href: string) => void;
|
|
8
|
+
reload: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
var __AKAN_CSR_LOCATION__: CsrLocation | undefined;
|
|
13
|
+
|
|
14
|
+
var __AKAN_EMBED__: { kind: "mcp-app"; origin?: string; account?: Record<string, unknown> | null } | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class CsrLocation {
|
|
18
|
+
#memory: { entries: CsrLocationEntry[]; idx: number } | null = null;
|
|
19
|
+
#owner: CsrMemoryOwner = { leave: () => undefined, reload: () => undefined };
|
|
20
|
+
|
|
21
|
+
get isMemory() {
|
|
22
|
+
return this.#memory !== null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The owner takes what a document load would do, which a view cannot: it has no document to load in its place. */
|
|
26
|
+
useMemory(href: string, owner: CsrMemoryOwner) {
|
|
27
|
+
this.#memory = { entries: [{ href, state: null }], idx: 0 };
|
|
28
|
+
this.#owner = owner;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Path, search and hash, without the origin. */
|
|
32
|
+
get href() {
|
|
33
|
+
const entry = this.#entry();
|
|
34
|
+
if (entry) return entry.href;
|
|
35
|
+
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get pathname() {
|
|
39
|
+
if (!this.#memory) return window.location.pathname;
|
|
40
|
+
return this.href.split(/[?#]/)[0] ?? "/";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get search() {
|
|
44
|
+
if (!this.#memory) return window.location.search;
|
|
45
|
+
const [beforeHash] = this.href.split("#");
|
|
46
|
+
const at = beforeHash?.indexOf("?") ?? -1;
|
|
47
|
+
return at >= 0 ? (beforeHash?.slice(at) ?? "") : "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get hash() {
|
|
51
|
+
if (!this.#memory) return window.location.hash;
|
|
52
|
+
const at = this.href.indexOf("#");
|
|
53
|
+
return at >= 0 ? this.href.slice(at) : "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get state(): unknown {
|
|
57
|
+
const entry = this.#entry();
|
|
58
|
+
return entry ? entry.state : window.history.state;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `href` as a route reads it: an absolute URL on this document's origin loses the origin, anything else is kept. */
|
|
62
|
+
strip(href: string) {
|
|
63
|
+
if (this.#memory) return href;
|
|
64
|
+
const origin = window.location.origin;
|
|
65
|
+
return origin !== "null" && href.startsWith(origin) ? href.slice(origin.length) : href;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
push(state: unknown, href: string) {
|
|
69
|
+
const memory = this.#memory;
|
|
70
|
+
if (!memory) {
|
|
71
|
+
window.history.pushState(state, "", href);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
memory.entries = [...memory.entries.slice(0, memory.idx + 1), { href, state }];
|
|
75
|
+
memory.idx = memory.entries.length - 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
replace(state: unknown, href?: string) {
|
|
79
|
+
const memory = this.#memory;
|
|
80
|
+
if (!memory) {
|
|
81
|
+
if (href === undefined) window.history.replaceState(state, "");
|
|
82
|
+
else window.history.replaceState(state, "", href);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const current = memory.entries[memory.idx];
|
|
86
|
+
memory.entries[memory.idx] = { href: href ?? current?.href ?? "/", state };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
back() {
|
|
90
|
+
const memory = this.#memory;
|
|
91
|
+
if (!memory) {
|
|
92
|
+
window.history.back();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (memory.idx === 0) return;
|
|
96
|
+
memory.idx -= 1;
|
|
97
|
+
const state = memory.entries[memory.idx]?.state ?? null;
|
|
98
|
+
|
|
99
|
+
setTimeout(() => window.dispatchEvent(new PopStateEvent("popstate", { state })), 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
assign(href: string) {
|
|
103
|
+
if (!this.#memory) {
|
|
104
|
+
window.location.assign(href);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.#owner.leave(href);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
reload() {
|
|
111
|
+
if (!this.#memory) window.location.reload();
|
|
112
|
+
else this.#owner.reload();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#entry() {
|
|
116
|
+
const memory = this.#memory;
|
|
117
|
+
return memory ? memory.entries[memory.idx] : undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
globalThis.__AKAN_CSR_LOCATION__ ??= new CsrLocation();
|
|
122
|
+
export const csrLocation: CsrLocation = globalThis.__AKAN_CSR_LOCATION__;
|
package/client/csrTypes.ts
CHANGED
|
@@ -22,13 +22,22 @@ export type PageSafeAreaConfig =
|
|
|
22
22
|
* `"block"` buffers the whole document, so an error in slow content can still render a clean error page. */
|
|
23
23
|
export type SsrRenderMode = "stream" | "block";
|
|
24
24
|
|
|
25
|
+
/** Px per Tailwind breakpoint (sm 40rem, md 48rem, lg 64rem, xl 80rem, 2xl 96rem), each from its own width up. */
|
|
26
|
+
export interface ResponsiveInset {
|
|
27
|
+
base: number;
|
|
28
|
+
sm?: number;
|
|
29
|
+
md?: number;
|
|
30
|
+
lg?: number;
|
|
31
|
+
xl?: number;
|
|
32
|
+
"2xl"?: number;
|
|
33
|
+
}
|
|
25
34
|
export interface PageConfig {
|
|
26
35
|
transition?: TransitionType;
|
|
27
36
|
safeArea?: PageSafeAreaConfig;
|
|
28
|
-
/** Top chrome reservation in px. Use true for the default 48px reservation. */
|
|
29
|
-
topInset?: number | boolean;
|
|
30
|
-
/** Bottom chrome reservation in px. Use true for the default 48px reservation. */
|
|
31
|
-
bottomInset?: number | boolean;
|
|
37
|
+
/** Top chrome reservation in px, or px per breakpoint. Use true for the default 48px reservation. */
|
|
38
|
+
topInset?: number | boolean | ResponsiveInset;
|
|
39
|
+
/** Bottom chrome reservation in px, or px per breakpoint. Use true for the default 48px reservation. */
|
|
40
|
+
bottomInset?: number | boolean | ResponsiveInset;
|
|
32
41
|
gesture?: boolean;
|
|
33
42
|
cache?: boolean;
|
|
34
43
|
/** Initial full-document SSR strategy. Defaults to `"stream"`. */
|
|
@@ -48,6 +57,10 @@ export interface CsrState {
|
|
|
48
57
|
bottomSafeArea: number;
|
|
49
58
|
topInset: number;
|
|
50
59
|
bottomInset: number;
|
|
60
|
+
/** The declared per-breakpoint insets; `topInset`/`bottomInset` hold their value at the width they resolved at. */
|
|
61
|
+
responsiveInset?: { top: ResponsiveInset; bottom: ResponsiveInset };
|
|
62
|
+
/** Web only: the sides whose safe area `env(safe-area-inset-*)` measures, since a browser tells script nothing. */
|
|
63
|
+
safeAreaEnv?: { top: boolean; bottom: boolean };
|
|
51
64
|
gesture: boolean;
|
|
52
65
|
cache: boolean;
|
|
53
66
|
ssr: SsrRenderMode;
|
|
@@ -114,13 +127,27 @@ export interface RouteRender {
|
|
|
114
127
|
resolveNotFound?: () => PromiseOrObject<LayoutNotFoundRender | undefined>;
|
|
115
128
|
resolveError?: () => PromiseOrObject<LayoutErrorRender | undefined>;
|
|
116
129
|
resolveHead?: ResolveHead;
|
|
130
|
+
/** Parses the declared args without rendering, so a path value the type refuses answers not-found before any byte. */
|
|
131
|
+
checkArgs?: (props: PageProps) => Promise<void>;
|
|
117
132
|
getPageConfig?: () => PromiseOrObject<PageConfig | undefined>;
|
|
118
133
|
getLayoutPageConfig?: () => PromiseOrObject<PageConfig | undefined>;
|
|
119
134
|
/** A root layout's `theme`, readable before it renders. */
|
|
120
135
|
getLayoutTheme?: () => PromiseOrObject<string | undefined>;
|
|
136
|
+
/** A root layout's `viewport`, readable before it renders: the document shell writes it ahead of the body. */
|
|
137
|
+
getLayoutViewport?: () => PromiseOrObject<RouteViewport | undefined>;
|
|
138
|
+
/** A root layout's `manifest`, which the server answers at the URL its documents link. */
|
|
139
|
+
getLayoutManifest?: () => PromiseOrObject<WebAppManifest | undefined>;
|
|
121
140
|
/** The `page()` chain behind a page render, when it was declared as one — what a page prompt is read off. */
|
|
122
141
|
getRouteDefinition?: () => PromiseOrObject<RouteDefinition | undefined>;
|
|
123
142
|
}
|
|
143
|
+
export interface RouteViewport {
|
|
144
|
+
/** `viewport-fit=cover`: the page runs under the notch and home indicator, and `env(safe-area-inset-*)` measures them. */
|
|
145
|
+
fitCover?: boolean;
|
|
146
|
+
/** What the on-screen keyboard resizes; `resizes-content` shrinks the layout viewport, and `dvh` with it. */
|
|
147
|
+
interactiveWidget?: "resizes-visual" | "resizes-content" | "overlays-content";
|
|
148
|
+
/** The browser chrome's color: one for every theme, or one per theme name the app switches between. */
|
|
149
|
+
themeColor?: string | { [theme: string]: string };
|
|
150
|
+
}
|
|
124
151
|
export interface WebAppManifestIcon {
|
|
125
152
|
src: string;
|
|
126
153
|
sizes?: string;
|
|
@@ -164,6 +191,7 @@ export interface LayoutModule {
|
|
|
164
191
|
reconnect?: boolean;
|
|
165
192
|
wsConnect?: boolean;
|
|
166
193
|
layoutStyle?: "mobile" | "web";
|
|
194
|
+
viewport?: RouteViewport;
|
|
167
195
|
Loading?: LayoutLoadingRender;
|
|
168
196
|
NotFound?: LayoutNotFoundRender;
|
|
169
197
|
Error?: LayoutErrorRender;
|
package/client/device.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import type { RefObject } from "react";
|
|
3
|
+
import { csrLocation } from "./csrLocation";
|
|
3
4
|
import { readCssSafeAreaInsets } from "./frameConfig";
|
|
4
5
|
import type { DeviceInfo } from "./native";
|
|
5
6
|
|
|
@@ -98,7 +99,7 @@ const createWebDevice = ({
|
|
|
98
99
|
lang?: string;
|
|
99
100
|
supportLanguages: string[] | readonly string[];
|
|
100
101
|
}) => {
|
|
101
|
-
const pathname = typeof window === "undefined" ? "" :
|
|
102
|
+
const pathname = typeof window === "undefined" ? "" : csrLocation.pathname;
|
|
102
103
|
return new Device({
|
|
103
104
|
lang: lang ?? langInPath(pathname, supportLanguages) ?? getBrowserLanguage(),
|
|
104
105
|
info: webDeviceInfo,
|
package/client/frameConfig.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { isRecord } from "akanjs/common";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
PageConfig,
|
|
4
|
+
PageSafeAreaConfig,
|
|
5
|
+
PageState,
|
|
6
|
+
ResponsiveInset,
|
|
7
|
+
SsrRenderMode,
|
|
8
|
+
TransitionType,
|
|
9
|
+
} from "./csrTypes";
|
|
3
10
|
|
|
4
11
|
export type DevicePlatform = "ios" | "android" | "web" | (string & {});
|
|
5
12
|
export type SafeAreaInsets = { top: number; bottom: number };
|
|
@@ -11,8 +18,20 @@ export interface ResolvePageStateOptions {
|
|
|
11
18
|
platform: DevicePlatform;
|
|
12
19
|
deviceSafeArea: SafeAreaInsets;
|
|
13
20
|
cssSafeArea?: SafeAreaInsets;
|
|
21
|
+
/** The viewport width in px a responsive inset resolves at; left out, it resolves at `base`. */
|
|
22
|
+
width?: number;
|
|
14
23
|
}
|
|
15
24
|
|
|
25
|
+
/** Tailwind's default breakpoints in rem, the widths a `ResponsiveInset` keys on. */
|
|
26
|
+
export const insetBreakpoints = [
|
|
27
|
+
["sm", 40],
|
|
28
|
+
["md", 48],
|
|
29
|
+
["lg", 64],
|
|
30
|
+
["xl", 80],
|
|
31
|
+
["2xl", 96],
|
|
32
|
+
] as const;
|
|
33
|
+
export type InsetBreakpoint = "base" | (typeof insetBreakpoints)[number][0];
|
|
34
|
+
|
|
16
35
|
const pageConfigKeys = new Set<keyof PageConfig>([
|
|
17
36
|
"transition",
|
|
18
37
|
"safeArea",
|
|
@@ -54,14 +73,42 @@ export function validatePageConfig(routeKey: string, config?: PageConfig) {
|
|
|
54
73
|
for (const key of ["topInset", "bottomInset"] as const) {
|
|
55
74
|
if (pageConfig[key] !== undefined && !isValidInsetValue(pageConfig[key]))
|
|
56
75
|
throw new Error(
|
|
57
|
-
`[route-convention] pageConfig.${key} in ${routeKey} must be a boolean
|
|
76
|
+
`[route-convention] pageConfig.${key} in ${routeKey} must be a boolean, a non-negative px number, or { base, sm?, md?, lg?, xl?, "2xl"? } of them.`,
|
|
58
77
|
);
|
|
59
78
|
}
|
|
60
79
|
validateSafeAreaConfig(routeKey, pageConfig.safeArea);
|
|
61
80
|
}
|
|
62
81
|
|
|
82
|
+
const isInsetPx = (value: unknown): value is number =>
|
|
83
|
+
typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
84
|
+
const responsiveInsetKeys = new Set<string>(["base", ...insetBreakpoints.map(([name]) => name)]);
|
|
85
|
+
|
|
63
86
|
function isValidInsetValue(value: unknown): value is NonNullable<PageConfig["topInset"]> {
|
|
64
|
-
|
|
87
|
+
if (typeof value === "boolean" || isInsetPx(value)) return true;
|
|
88
|
+
if (!isRecord(value) || !isInsetPx(value.base)) return false;
|
|
89
|
+
return Object.entries(value).every(([key, px]) => responsiveInsetKeys.has(key) && isInsetPx(px));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const isResponsiveInset = (inset: PageConfig["topInset"]): inset is ResponsiveInset => isRecord(inset);
|
|
93
|
+
|
|
94
|
+
/** The inset at each breakpoint, each one the nearest declared value at or below it. */
|
|
95
|
+
export function insetsByBreakpoint(inset: PageConfig["topInset"]): { [bp in InsetBreakpoint]: number } {
|
|
96
|
+
const base = isResponsiveInset(inset) ? inset.base : resolveInset(inset);
|
|
97
|
+
const values = { base } as { [bp in InsetBreakpoint]: number };
|
|
98
|
+
let current = base;
|
|
99
|
+
for (const [name] of insetBreakpoints) {
|
|
100
|
+
current = (isResponsiveInset(inset) ? inset[name] : undefined) ?? current;
|
|
101
|
+
values[name] = current;
|
|
102
|
+
}
|
|
103
|
+
return values;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The inset in force at `width` px, assuming a 16px rem. */
|
|
107
|
+
export function insetAt(inset: PageConfig["topInset"], width?: number): number {
|
|
108
|
+
if (!isResponsiveInset(inset)) return resolveInset(inset);
|
|
109
|
+
if (width === undefined) return inset.base;
|
|
110
|
+
const values = insetsByBreakpoint(inset);
|
|
111
|
+
return insetBreakpoints.reduce((value, [name, rem]) => (width >= rem * 16 ? values[name] : value), values.base);
|
|
65
112
|
}
|
|
66
113
|
|
|
67
114
|
function validateSafeAreaConfig(routeKey: string, safeArea?: PageSafeAreaConfig) {
|
|
@@ -132,6 +179,7 @@ export function resolvePageState({
|
|
|
132
179
|
platform,
|
|
133
180
|
deviceSafeArea,
|
|
134
181
|
cssSafeArea,
|
|
182
|
+
width,
|
|
135
183
|
}: ResolvePageStateOptions): PageState {
|
|
136
184
|
const profile = getPlatformFrameProfile(platform);
|
|
137
185
|
const routeRole = getRouteRoleFrameDefaults(path, platform, basePath);
|
|
@@ -144,12 +192,25 @@ export function resolvePageState({
|
|
|
144
192
|
cssSafeArea,
|
|
145
193
|
});
|
|
146
194
|
const transition = config.transition ?? "none";
|
|
195
|
+
const responsive = isResponsiveInset(config.topInset) || isResponsiveInset(config.bottomInset);
|
|
196
|
+
const safeAreaEnv = platform === "web" ? resolveSafeAreaEnv(config.safeArea) : undefined;
|
|
147
197
|
return {
|
|
148
198
|
transition,
|
|
149
199
|
topSafeArea: safeArea.top,
|
|
150
200
|
bottomSafeArea: safeArea.bottom,
|
|
151
|
-
topInset:
|
|
152
|
-
bottomInset:
|
|
201
|
+
topInset: insetAt(config.topInset, width),
|
|
202
|
+
bottomInset: insetAt(config.bottomInset, width),
|
|
203
|
+
...(responsive
|
|
204
|
+
? {
|
|
205
|
+
responsiveInset: {
|
|
206
|
+
top: isResponsiveInset(config.topInset) ? config.topInset : { base: resolveInset(config.topInset) },
|
|
207
|
+
bottom: isResponsiveInset(config.bottomInset)
|
|
208
|
+
? config.bottomInset
|
|
209
|
+
: { base: resolveInset(config.bottomInset) },
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
: {}),
|
|
213
|
+
...(safeAreaEnv ? { safeAreaEnv } : {}),
|
|
153
214
|
gesture: explicitKeys.gesture
|
|
154
215
|
? (config.gesture ?? false)
|
|
155
216
|
: transition === "none"
|
|
@@ -165,9 +226,17 @@ export function resolvePageState({
|
|
|
165
226
|
function resolveInset(inset: PageConfig["topInset"]): number {
|
|
166
227
|
if (inset === true) return DEFAULT_BOOLEAN_INSET;
|
|
167
228
|
if (inset === false || inset === undefined) return 0;
|
|
229
|
+
if (isResponsiveInset(inset)) return inset.base;
|
|
168
230
|
return inset;
|
|
169
231
|
}
|
|
170
232
|
|
|
233
|
+
function resolveSafeAreaEnv(safeArea?: PageSafeAreaConfig): { top: boolean; bottom: boolean } | undefined {
|
|
234
|
+
if (safeArea === undefined || safeArea === false) return undefined;
|
|
235
|
+
const top = safeArea === true || safeArea === "top" || (isRecord(safeArea) && safeArea.top !== false);
|
|
236
|
+
const bottom = safeArea === true || safeArea === "bottom" || (isRecord(safeArea) && safeArea.bottom !== false);
|
|
237
|
+
return top || bottom ? { top, bottom } : undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
171
240
|
function getPlatformFrameProfile(platform: DevicePlatform): PageConfig {
|
|
172
241
|
if (platform === "ios") return { safeArea: true, transition: "stack" };
|
|
173
242
|
if (platform === "android") return { safeArea: { android: "auto" }, transition: "scaleOut", gesture: false };
|
|
@@ -221,9 +290,11 @@ function resolveSafeArea({
|
|
|
221
290
|
bottom: bottomEnabled && source ? source.bottom : 0,
|
|
222
291
|
};
|
|
223
292
|
}
|
|
293
|
+
|
|
294
|
+
const source = platform === "web" && cssSafeArea ? cssSafeArea : deviceSafeArea;
|
|
224
295
|
return {
|
|
225
|
-
top: topEnabled ?
|
|
226
|
-
bottom: bottomEnabled ?
|
|
296
|
+
top: topEnabled ? source.top : 0,
|
|
297
|
+
bottom: bottomEnabled ? source.bottom : 0,
|
|
227
298
|
};
|
|
228
299
|
}
|
|
229
300
|
|
package/client/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./clientRuntime";
|
|
|
2
2
|
export * from "./cn";
|
|
3
3
|
export * from "./cookie";
|
|
4
4
|
export * from "./createFont";
|
|
5
|
+
export * from "./csrLocation";
|
|
5
6
|
export * from "./csrTypes";
|
|
6
7
|
export * from "./decorators";
|
|
7
8
|
export * from "./device";
|
|
@@ -17,3 +18,5 @@ export * from "./storage";
|
|
|
17
18
|
export * from "./translator";
|
|
18
19
|
export * from "./types";
|
|
19
20
|
export * from "./useClient";
|
|
21
|
+
export * from "./viewportMeta";
|
|
22
|
+
export * from "./webManifest";
|
package/client/makePageProto.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { getEnv } from "akanjs/base";
|
|
|
2
2
|
import { parseAkanI18nEnv } from "akanjs/common";
|
|
3
3
|
import { untrackedHeaders, untrackedRequest } from "akanjs/fetch";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
|
+
import { csrLocation } from "./csrLocation";
|
|
5
6
|
import { Translator } from "./translator";
|
|
6
7
|
|
|
7
8
|
type TransMessage<Locale extends Record<string, unknown>> = {
|
|
@@ -27,11 +28,11 @@ const getPageInfo = (): { locale: string; path: string } => {
|
|
|
27
28
|
const activePath = Translator.getActivePath();
|
|
28
29
|
if (activePath) return { locale: activeLocale ?? defaultLocale, path: activePath };
|
|
29
30
|
if (getEnv().side !== "server") {
|
|
30
|
-
const [, firstSegment = "", ...rest] =
|
|
31
|
+
const [, firstSegment = "", ...rest] = csrLocation.pathname.split("/");
|
|
31
32
|
const hasLocalePrefix = localeSet.has(firstSegment);
|
|
32
33
|
|
|
33
34
|
const locale = activeLocale ?? (hasLocalePrefix ? firstSegment : defaultLocale);
|
|
34
|
-
return { locale, path: activePath ?? (hasLocalePrefix ? `/${rest.join("/")}` :
|
|
35
|
+
return { locale, path: activePath ?? (hasLocalePrefix ? `/${rest.join("/")}` : csrLocation.pathname) };
|
|
35
36
|
}
|
|
36
37
|
const h = untrackedHeaders();
|
|
37
38
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../vendor/@akanjs/native/plugins/external-view/src/index.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LayoutModule, PageModule, WebAppManifest } from "../csrTypes";
|
|
1
|
+
import type { LayoutModule, PageModule, RouteViewport, WebAppManifest } from "../csrTypes";
|
|
2
2
|
import type { ReactFont } from "../types";
|
|
3
3
|
import { LayoutDefinition } from "./LayoutDefinition";
|
|
4
4
|
import type { RouteArgsShape, RouteKind } from "./RouteDefinition";
|
|
@@ -12,6 +12,7 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
|
|
|
12
12
|
#reconnect?: boolean;
|
|
13
13
|
#wsConnect?: boolean;
|
|
14
14
|
#layoutStyle?: "mobile" | "web";
|
|
15
|
+
#viewport?: RouteViewport;
|
|
15
16
|
|
|
16
17
|
fonts(fonts: ReactFont[]) {
|
|
17
18
|
this.#fonts = fonts;
|
|
@@ -37,6 +38,10 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
|
|
|
37
38
|
this.#layoutStyle = style;
|
|
38
39
|
return this;
|
|
39
40
|
}
|
|
41
|
+
viewport(viewport: RouteViewport) {
|
|
42
|
+
this.#viewport = viewport;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
40
45
|
|
|
41
46
|
protected override extendModule(module: PageModule & LayoutModule): PageModule & LayoutModule {
|
|
42
47
|
return {
|
|
@@ -47,6 +52,7 @@ export class RootLayoutDefinition<Args extends RouteArgsShape = Record<never, ne
|
|
|
47
52
|
...(this.#reconnect !== undefined ? { reconnect: this.#reconnect } : {}),
|
|
48
53
|
...(this.#wsConnect !== undefined ? { wsConnect: this.#wsConnect } : {}),
|
|
49
54
|
...(this.#layoutStyle ? { layoutStyle: this.#layoutStyle } : {}),
|
|
55
|
+
...(this.#viewport ? { viewport: this.#viewport } : {}),
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { cloneElement, createElement, Fragment, isValidElement, type ReactElement, type ReactNode } from "react";
|
|
2
|
+
import type { AkanHeadSnapshotNode, AkanHeadSnapshotV1, ResolvedHead, ResolveHeadResult } from "../csrTypes";
|
|
3
|
+
|
|
4
|
+
type TagProps = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
/** Merges the `.head()` of every route level, root first: a deeper level takes over each singleton tag it writes. */
|
|
7
|
+
export class RouteHead {
|
|
8
|
+
|
|
9
|
+
static readonly #singletonLinkRels = new Set([
|
|
10
|
+
"canonical",
|
|
11
|
+
"manifest",
|
|
12
|
+
"icon",
|
|
13
|
+
"shortcut icon",
|
|
14
|
+
"apple-touch-icon",
|
|
15
|
+
"mask-icon",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
static merge(levels: ResolveHeadResult[]): ResolveHeadResult {
|
|
19
|
+
const present = levels.filter((level) => RouteHead.#nodeOf(level) != null || RouteHead.#snapshotOf(level));
|
|
20
|
+
if (present.length <= 1) return present[0];
|
|
21
|
+
const node = RouteHead.mergeNodes(present.map((level) => RouteHead.#nodeOf(level)));
|
|
22
|
+
const snapshots = present.map((level) => RouteHead.#snapshotOf(level)).filter((snapshot) => !!snapshot);
|
|
23
|
+
if (!snapshots.length) return node;
|
|
24
|
+
return { node, headSnapshot: RouteHead.#mergeSnapshots(snapshots) } satisfies ResolvedHead;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What makes two tags the same tag; `null` for one that never replaces another. */
|
|
28
|
+
static keyOf(tag: string, props: TagProps): string | null {
|
|
29
|
+
if (tag === "title" || tag === "base") return tag;
|
|
30
|
+
if (tag === "meta") {
|
|
31
|
+
if (props.charSet !== undefined || props.charset !== undefined) return "meta:charset";
|
|
32
|
+
for (const attr of ["name", "property", "httpEquiv", "itemProp"]) {
|
|
33
|
+
const value = props[attr];
|
|
34
|
+
if (typeof value === "string" && value) return `meta:${attr}:${value.toLowerCase()}`;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (tag !== "link" || typeof props.rel !== "string") return null;
|
|
39
|
+
const rel = props.rel.toLowerCase();
|
|
40
|
+
if (RouteHead.#singletonLinkRels.has(rel)) return `link:${rel}`;
|
|
41
|
+
const hrefLang = props.hrefLang ?? props.hreflang;
|
|
42
|
+
if (rel === "alternate" && typeof hrefLang === "string") return `link:alternate:${hrefLang.toLowerCase()}`;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static #nodeOf(level: ResolveHeadResult): ReactNode {
|
|
47
|
+
return RouteHead.#isResolved(level) ? level.node : level;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static #snapshotOf(level: ResolveHeadResult): AkanHeadSnapshotV1 | undefined {
|
|
51
|
+
return RouteHead.#isResolved(level) ? level.headSnapshot : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static #isResolved(level: ResolveHeadResult): level is ResolvedHead {
|
|
55
|
+
return typeof level === "object" && level !== null && !isValidElement(level) && "node" in level;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `merge` for plain JSX heads, root first. */
|
|
59
|
+
static mergeNodes(nodes: ReactNode[]): ReactNode {
|
|
60
|
+
const present = nodes.filter((node) => node !== null && node !== undefined);
|
|
61
|
+
if (present.length <= 1) return present[0];
|
|
62
|
+
const levels = present.map((node) => RouteHead.#flatten(node));
|
|
63
|
+
const kept = RouteHead.#keepDeepest(levels, (child) =>
|
|
64
|
+
isValidElement(child) && typeof child.type === "string"
|
|
65
|
+
? RouteHead.keyOf(child.type, child.props as TagProps)
|
|
66
|
+
: null,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const children = kept.map(({ item, level, idx }) =>
|
|
70
|
+
isValidElement(item) ? cloneElement(item as ReactElement, { key: `head:${level}:${idx}` }) : item,
|
|
71
|
+
);
|
|
72
|
+
return createElement(Fragment, null, ...children);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static #mergeSnapshots(snapshots: AkanHeadSnapshotV1[]): AkanHeadSnapshotV1 {
|
|
76
|
+
const kept = RouteHead.#keepDeepest(
|
|
77
|
+
snapshots.map((snapshot) => snapshot.nodes),
|
|
78
|
+
(node: AkanHeadSnapshotNode) => RouteHead.keyOf(node.tag, node.attrs ?? {}),
|
|
79
|
+
);
|
|
80
|
+
return { version: 1, nodes: kept.map(({ item }) => item) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static #keepDeepest<T>(levels: T[][], keyOf: (item: T) => string | null) {
|
|
84
|
+
const owner = new Map<string, number>();
|
|
85
|
+
levels.forEach((items, level) => {
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
const key = keyOf(item);
|
|
88
|
+
if (key) owner.set(key, level);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return levels.flatMap((items, level) =>
|
|
92
|
+
items
|
|
93
|
+
.map((item, idx) => ({ item, level, idx }))
|
|
94
|
+
.filter(({ item }) => {
|
|
95
|
+
const key = keyOf(item);
|
|
96
|
+
return !key || owner.get(key) === level;
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static #flatten(node: ReactNode): ReactNode[] {
|
|
102
|
+
if (node === null || node === undefined || typeof node === "boolean") return [];
|
|
103
|
+
if (Array.isArray(node)) return node.flatMap((child: ReactNode) => RouteHead.#flatten(child));
|
|
104
|
+
if (isValidElement(node) && node.type === Fragment)
|
|
105
|
+
return RouteHead.#flatten((node.props as { children?: ReactNode }).children);
|
|
106
|
+
return [node];
|
|
107
|
+
}
|
|
108
|
+
}
|
package/client/route/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./LayoutDefinition";
|
|
|
2
2
|
export * from "./PageDefinition";
|
|
3
3
|
export * from "./RootLayoutDefinition";
|
|
4
4
|
export * from "./RouteDefinition";
|
|
5
|
+
export * from "./RouteHead";
|
|
5
6
|
export * from "./resolveRouteModule";
|
|
6
7
|
export * from "./routeArgs";
|
|
7
8
|
export * from "./routeBuilders";
|