@plitzi/sdk-server 0.37.1 → 0.37.3
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 +1296 -0
- package/README.md +126 -7
- package/dist/actions.d.ts +20 -8
- package/dist/actions.js +11 -4
- package/dist/adapters/cloudAdapters.js +4 -3
- package/dist/adapters/jsonAdapters.js +28 -6
- package/dist/auth.d.ts +7 -4
- package/dist/auth.js +8 -6
- package/dist/core/auth/api.d.ts +29 -1
- package/dist/core/auth/api.js +47 -13
- package/dist/core/auth/createAuth.d.ts +4 -0
- package/dist/core/auth/createAuth.js +3 -3
- package/dist/core/auth/credentials.d.ts +3 -0
- package/dist/core/auth/credentials.js +1 -1
- package/dist/core/auth/csrf.d.ts +25 -1
- package/dist/core/auth/csrf.js +29 -4
- package/dist/core/auth/renewal.d.ts +26 -0
- package/dist/core/auth/renewal.js +24 -0
- package/dist/core/auth/routes.js +8 -6
- package/dist/core/auth/session.d.ts +45 -0
- package/dist/core/auth/session.js +47 -1
- package/dist/core/auth/sessionActivity.d.ts +11 -0
- package/dist/core/auth/sessionActivity.js +14 -0
- package/dist/core/auth/throttle.d.ts +15 -0
- package/dist/core/auth/throttle.js +29 -1
- package/dist/core/auth/totp.d.ts +9 -1
- package/dist/core/auth/totp.js +11 -6
- package/dist/core/createServer.actions.test.d.ts +1 -0
- package/dist/core/createServer.d.ts +14 -1
- package/dist/core/createServer.functions.test.d.ts +1 -0
- package/dist/core/createServer.js +22 -12
- package/dist/core/fontStore.js +1 -1
- package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/authRouteHandlers.js +57 -21
- package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
- package/dist/core/handlers/csrfMiddleware.js +31 -7
- package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
- package/dist/core/handlers/oauthRouteHandlers.js +5 -3
- package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/socialRouteHandlers.js +2 -1
- package/dist/core/http/dispatcher.d.ts +9 -1
- package/dist/core/http/dispatcher.js +38 -11
- package/dist/core/http/navigation.d.ts +5 -2
- package/dist/core/http/navigation.js +7 -4
- package/dist/core/http/socketResponse.d.ts +18 -0
- package/dist/core/http/socketResponse.js +59 -0
- package/dist/core/http/sse.d.ts +26 -0
- package/dist/core/http/sse.js +78 -0
- package/dist/core/http/stages/authRoutes.js +3 -1
- package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
- package/dist/core/http/stages/middlewares.d.ts +1 -1
- package/dist/core/http/stages/middlewares.js +16 -10
- package/dist/core/http/stages/middlewares.test.d.ts +1 -0
- package/dist/core/http/stages/oauth.js +2 -2
- package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
- package/dist/core/http/stages/sessionRenewal.js +81 -0
- package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
- package/dist/core/http/stages/signInRoute.d.ts +2 -0
- package/dist/core/http/stages/signInRoute.js +139 -0
- package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
- package/dist/core/http/types.d.ts +9 -0
- package/dist/core/http/webExchange.d.ts +18 -0
- package/dist/core/http/webExchange.js +53 -0
- package/dist/core/oauth/authorize.js +37 -13
- package/dist/core/oauth/consentPage.js +11 -0
- package/dist/core/oauth/issueContext.d.ts +14 -0
- package/dist/core/oauth/issueContext.js +32 -0
- package/dist/core/oauth/records.d.ts +7 -0
- package/dist/core/oauth/register.d.ts +2 -0
- package/dist/core/oauth/register.js +32 -11
- package/dist/core/oauth/register.test.d.ts +1 -0
- package/dist/core/oauth/token.js +21 -6
- package/dist/core/oauth/token.test.d.ts +1 -0
- package/dist/core/requestParser.d.ts +4 -0
- package/dist/core/requestParser.js +14 -7
- package/dist/core/sdkAssets.js +1 -1
- package/dist/core/server/baseServer.d.ts +14 -1
- package/dist/core/server/baseServer.js +43 -11
- package/dist/core/server/closeOnSignals.d.ts +28 -0
- package/dist/core/server/closeOnSignals.js +47 -0
- package/dist/core/server/closeOnSignals.test.d.ts +1 -0
- package/dist/core/server/drain.d.ts +18 -0
- package/dist/core/server/drain.js +62 -0
- package/dist/core/server/drain.test.d.ts +1 -0
- package/dist/core/server/fleet/channel.d.ts +103 -0
- package/dist/core/server/fleet/channel.js +191 -0
- package/dist/core/server/fleet/channel.test.d.ts +1 -0
- package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
- package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
- package/dist/core/server/fleet/link.d.ts +23 -0
- package/dist/core/server/fleet/link.js +70 -0
- package/dist/core/server/fleet/relay.d.ts +11 -0
- package/dist/core/server/fleet/relay.js +104 -0
- package/dist/core/server/fleet/relay.test.d.ts +1 -0
- package/dist/core/server/fleet/role.d.ts +46 -0
- package/dist/core/server/fleet/role.js +59 -0
- package/dist/core/server/fleet/role.test.d.ts +1 -0
- package/dist/core/server/fleet/stores.d.ts +7 -0
- package/dist/core/server/fleet/stores.js +19 -0
- package/dist/core/server/fleet/supervisor.d.ts +26 -0
- package/dist/core/server/fleet/supervisor.js +108 -0
- package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
- package/dist/core/server/pageServer.js +36 -5
- package/dist/core/services/action.js +10 -2
- package/dist/core/services/functionRoutes.d.ts +9 -0
- package/dist/core/services/functionRoutes.js +47 -0
- package/dist/core/services/realtime.d.ts +8 -0
- package/dist/core/services/realtime.js +57 -0
- package/dist/core/services/registry.js +6 -2
- package/dist/core/staticFiles.js +12 -4
- package/dist/core/staticFiles.test.d.ts +1 -0
- package/dist/core/transports.d.ts +13 -3
- package/dist/core/transports.js +14 -5
- package/dist/functions-api.d.ts +281 -0
- package/dist/functions.d.ts +19 -0
- package/dist/functions.js +5 -0
- package/dist/functionsRunner.d.ts +15 -0
- package/dist/functionsRunner.js +6 -0
- package/dist/handlers.d.ts +2 -2
- package/dist/handlers.js +2 -2
- package/dist/helpers/atomicFile.d.ts +10 -0
- package/dist/helpers/atomicFile.js +33 -0
- package/dist/helpers/atomicFile.test.d.ts +1 -0
- package/dist/helpers/buildResponseHelpers.js +35 -11
- package/dist/helpers/buildServerInfo.js +4 -2
- package/dist/helpers/buildServerInfo.test.d.ts +1 -0
- package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
- package/dist/helpers/cache/CompressedFileCache.js +44 -0
- package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
- package/dist/helpers/cache/cacheManager.d.ts +1 -1
- package/dist/helpers/cache/cacheManager.js +2 -1
- package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
- package/dist/helpers/cache/index.d.ts +1 -1
- package/dist/helpers/cache/keys.d.ts +15 -1
- package/dist/helpers/cache/keys.js +47 -5
- package/dist/helpers/cache/serverCaches.d.ts +7 -1
- package/dist/helpers/compress.d.ts +3 -1
- package/dist/helpers/compress.js +11 -3
- package/dist/helpers/hydrationPayload.d.ts +11 -0
- package/dist/helpers/hydrationPayload.js +51 -0
- package/dist/helpers/hydrationPayload.test.d.ts +1 -0
- package/dist/helpers/metrics.d.ts +1 -1
- package/dist/helpers/metrics.js +3 -2
- package/dist/helpers/outboundGuard.d.ts +14 -6
- package/dist/helpers/outboundGuard.js +132 -8
- package/dist/helpers/paintedCookie.d.ts +8 -0
- package/dist/helpers/paintedCookie.js +13 -0
- package/dist/helpers/reportReactBuild.d.ts +9 -0
- package/dist/helpers/reportReactBuild.js +18 -0
- package/dist/helpers/reportReactBuild.test.d.ts +1 -0
- package/dist/helpers/serverLog.d.ts +25 -1
- package/dist/helpers/serverLog.js +68 -3
- package/dist/helpers/serverLog.test.d.ts +1 -0
- package/dist/helpers/wsFrame.d.ts +3 -0
- package/dist/helpers/wsFrame.js +5 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -2
- package/dist/kernel.d.ts +2 -1
- package/dist/kernel.js +3 -2
- package/dist/modules/actions/connectorWrite.js +2 -1
- package/dist/modules/actions/index.d.ts +41 -13
- package/dist/modules/actions/index.js +52 -9
- package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
- package/dist/modules/actions/jobs/failureStreak.js +47 -0
- package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
- package/dist/modules/actions/jobs/index.d.ts +33 -0
- package/dist/modules/actions/jobs/index.js +59 -0
- package/dist/modules/actions/jobs/memoryQueue.d.ts +33 -0
- package/dist/modules/actions/jobs/memoryQueue.js +194 -0
- package/dist/modules/actions/jobs/memoryStores.test.d.ts +1 -0
- package/dist/modules/actions/jobs/scheduler.d.ts +64 -0
- package/dist/modules/actions/jobs/scheduler.js +130 -0
- package/dist/modules/actions/jobs/scheduler.test.d.ts +1 -0
- package/dist/modules/actions/jobs/schedules.d.ts +23 -0
- package/dist/modules/actions/jobs/schedules.js +47 -0
- package/dist/modules/actions/jobs/storeClock.d.ts +16 -0
- package/dist/modules/actions/jobs/storeClock.js +32 -0
- package/dist/modules/actions/jobs/testing/jobQueueContract.d.ts +25 -0
- package/dist/modules/actions/jobs/worker.d.ts +64 -0
- package/dist/modules/actions/jobs/worker.js +267 -0
- package/dist/modules/actions/jobs/worker.test.d.ts +1 -0
- package/dist/modules/actions/moduleFor.js +6 -2
- package/dist/modules/actions/runtime/errors.d.ts +12 -0
- package/dist/modules/actions/runtime/errors.js +16 -1
- package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
- package/dist/modules/actions/runtime/kvChange.js +23 -0
- package/dist/modules/actions/runtime/kvList.d.ts +51 -0
- package/dist/modules/actions/runtime/kvList.js +52 -0
- package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
- package/dist/modules/actions/runtime/kvStore.js +46 -20
- package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
- package/dist/modules/actions/runtime/memoryKv.js +19 -1
- package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
- package/dist/modules/actions/runtime/namespaceKv.js +15 -3
- package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
- package/dist/modules/actions/runtime/precheck.js +5 -9
- package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
- package/dist/modules/actions/runtime/rateLimit.js +19 -0
- package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
- package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
- package/dist/modules/actions/runtime/redisKv.js +46 -0
- package/dist/modules/actions/runtime/renderResolver.js +2 -2
- package/dist/modules/actions/runtime/report.js +2 -1
- package/dist/modules/actions/runtime/runAction.d.ts +10 -2
- package/dist/modules/actions/runtime/runAction.js +52 -116
- package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
- package/dist/modules/actions/runtime/runLogger.js +8 -2
- package/dist/modules/actions/runtime/signing.d.ts +20 -0
- package/dist/modules/actions/runtime/signing.js +25 -0
- package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
- package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
- package/dist/modules/actions/runtime/taskContext.js +124 -0
- package/dist/modules/actions/tasks/builtins.js +5 -1
- package/dist/modules/actions/tasks/flow.js +81 -10
- package/dist/modules/actions/tasks/helpers.js +2 -2
- package/dist/modules/actions/tasks/http.js +2 -3
- package/dist/modules/actions/tasks/kv.js +39 -0
- package/dist/modules/actions/tasks/list.d.ts +2 -0
- package/dist/modules/actions/tasks/list.js +145 -0
- package/dist/modules/actions/tasks/realtime.d.ts +2 -0
- package/dist/modules/actions/tasks/realtime.js +104 -0
- package/dist/modules/actions/tasks/registry.d.ts +10 -1
- package/dist/modules/actions/tasks/registry.js +17 -6
- package/dist/modules/actions/transport/callHandler.js +6 -3
- package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
- package/dist/modules/actions/transport/catalogHandler.js +3 -2
- package/dist/modules/actions/transport/stream.d.ts +4 -5
- package/dist/modules/actions/transport/stream.js +12 -76
- package/dist/modules/actions/transport/webhookHandler.js +5 -4
- package/dist/modules/actions/types.d.ts +162 -4
- package/dist/modules/connectors/engine.js +3 -5
- package/dist/modules/functions/build.d.ts +22 -0
- package/dist/modules/functions/build.js +122 -0
- package/dist/modules/functions/build.test.d.ts +1 -0
- package/dist/modules/functions/capabilities.d.ts +22 -0
- package/dist/modules/functions/capabilities.js +228 -0
- package/dist/modules/functions/config.d.ts +34 -0
- package/dist/modules/functions/config.js +24 -0
- package/dist/modules/functions/context.d.ts +17 -0
- package/dist/modules/functions/context.js +88 -0
- package/dist/modules/functions/contract.d.ts +121 -0
- package/dist/modules/functions/contract.js +9 -0
- package/dist/modules/functions/driver.d.ts +36 -0
- package/dist/modules/functions/driver.js +150 -0
- package/dist/modules/functions/load.d.ts +15 -0
- package/dist/modules/functions/load.js +45 -0
- package/dist/modules/functions/load.test.d.ts +1 -0
- package/dist/modules/functions/local.d.ts +29 -0
- package/dist/modules/functions/local.js +61 -0
- package/dist/modules/functions/local.test.d.ts +1 -0
- package/dist/modules/functions/manifest.d.ts +24 -0
- package/dist/modules/functions/manifest.js +163 -0
- package/dist/modules/functions/manifest.test.d.ts +1 -0
- package/dist/modules/functions/native.d.ts +8 -0
- package/dist/modules/functions/native.js +21 -0
- package/dist/modules/functions/native.test.d.ts +1 -0
- package/dist/modules/functions/protocol.d.ts +216 -0
- package/dist/modules/functions/protocol.js +18 -0
- package/dist/modules/functions/routes.d.ts +36 -0
- package/dist/modules/functions/routes.js +181 -0
- package/dist/modules/functions/routes.test.d.ts +1 -0
- package/dist/modules/functions/runner/messages.d.ts +5 -0
- package/dist/modules/functions/runner/messages.js +188 -0
- package/dist/modules/functions/runner/remote.d.ts +19 -0
- package/dist/modules/functions/runner/remote.js +116 -0
- package/dist/modules/functions/runner/remote.test.d.ts +1 -0
- package/dist/modules/functions/runner/service.d.ts +32 -0
- package/dist/modules/functions/runner/service.js +209 -0
- package/dist/modules/functions/sandbox/guest.d.ts +45 -0
- package/dist/modules/functions/sandbox/guest.js +660 -0
- package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
- package/dist/modules/functions/sandbox/isolate.js +347 -0
- package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
- package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
- package/dist/modules/functions/sandbox/prelude.js +49 -0
- package/dist/modules/functions/space.d.ts +40 -0
- package/dist/modules/functions/space.js +162 -0
- package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
- package/dist/modules/functions/tryEntry.d.ts +9 -0
- package/dist/modules/functions/tryEntry.js +49 -0
- package/dist/modules/mongo/index.d.ts +4 -0
- package/dist/modules/mongo/jobQueue.d.ts +38 -0
- package/dist/modules/mongo/jobQueue.js +335 -0
- package/dist/modules/mongo/kv.d.ts +15 -0
- package/dist/modules/mongo/kv.js +129 -0
- package/dist/modules/mongo/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/accounts.js +19 -9
- package/dist/modules/mysql/index.d.ts +2 -1
- package/dist/modules/mysql/jobs/index.d.ts +5 -0
- package/dist/modules/mysql/jobs/jobQueue.d.ts +40 -0
- package/dist/modules/mysql/jobs/jobQueue.js +375 -0
- package/dist/modules/mysql/jobs/kv.d.ts +18 -0
- package/dist/modules/mysql/jobs/kv.js +108 -0
- package/dist/modules/mysql/jobs/schema.d.ts +33 -0
- package/dist/modules/mysql/jobs/schema.js +113 -0
- package/dist/modules/mysql/jobs/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/schema.js +25 -0
- package/dist/modules/mysql/sessions.d.ts +1 -11
- package/dist/modules/mysql/sessions.js +6 -1
- package/dist/modules/realtime/declarations.d.ts +11 -0
- package/dist/modules/realtime/declarations.js +26 -0
- package/dist/modules/realtime/grants.d.ts +23 -0
- package/dist/modules/realtime/grants.js +61 -0
- package/dist/modules/realtime/handlers.d.ts +74 -0
- package/dist/modules/realtime/handlers.js +224 -0
- package/dist/modules/realtime/hub.d.ts +62 -0
- package/dist/modules/realtime/hub.js +141 -0
- package/dist/modules/realtime/index.d.ts +51 -0
- package/dist/modules/realtime/index.js +62 -0
- package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
- package/dist/modules/realtime/memoryPubSub.js +43 -0
- package/dist/modules/realtime/realtime.test.d.ts +1 -0
- package/dist/modules/realtime/redisPubSub.d.ts +24 -0
- package/dist/modules/realtime/redisPubSub.js +36 -0
- package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
- package/dist/modules/realtime/socket.d.ts +25 -0
- package/dist/modules/realtime/socket.js +122 -0
- package/dist/modules/realtime/socket.test.d.ts +1 -0
- package/dist/modules/rsc/handler.d.ts +16 -0
- package/dist/modules/rsc/handler.js +17 -10
- package/dist/modules/rsc/handler.test.d.ts +1 -0
- package/dist/modules/rsc/resolveRscData.js +2 -1
- package/dist/modules/runtime/bundle.d.ts +24 -0
- package/dist/modules/runtime/bundle.js +102 -0
- package/dist/modules/runtime/contract.d.ts +37 -0
- package/dist/modules/runtime/contract.js +21 -0
- package/dist/modules/runtime/entry.test.d.ts +1 -0
- package/dist/modules/runtime/host.d.ts +38 -0
- package/dist/modules/runtime/host.js +68 -0
- package/dist/modules/runtime/inside.d.ts +15 -0
- package/dist/modules/runtime/inside.js +47 -0
- package/dist/modules/runtime/inside.test.d.ts +1 -0
- package/dist/modules/runtime/runner.d.ts +11 -0
- package/dist/modules/runtime/runner.js +38 -0
- package/dist/modules/runtime/runtime.test.d.ts +1 -0
- package/dist/modules/runtime/stages.d.ts +50 -0
- package/dist/modules/runtime/stages.js +117 -0
- package/dist/modules/ssr/Component.d.ts +4 -5
- package/dist/modules/ssr/Component.js +2 -2
- package/dist/modules/ssr/handler.js +10 -2
- package/dist/modules/ssr/loadPluginComponents.js +2 -1
- package/dist/modules/ssr/prepareRender.js +39 -13
- package/dist/modules/ssr/preview.d.ts +3 -0
- package/dist/modules/ssr/preview.js +17 -7
- package/dist/modules/ssr/redirect.test.d.ts +1 -0
- package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
- package/dist/modules/ssr/registerExternalPlugins.js +19 -16
- package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
- package/dist/modules/ssr/reportMissingPlugins.js +24 -0
- package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/streamBody.d.ts +2 -2
- package/dist/modules/ssr/streamBody.js +4 -1
- package/dist/modules/ssr/template.js +1 -1
- package/dist/modules/ssr/views/template.ejs +8 -5
- package/dist/mongo.d.ts +25 -0
- package/dist/mongo.js +3 -0
- package/dist/mysql.d.ts +11 -0
- package/dist/mysql.js +4 -1
- package/dist/oauth.js +2 -2
- package/dist/plugins/compile.js +9 -4
- package/dist/plugins/copy.js +3 -3
- package/dist/plugins/manager.d.ts +35 -3
- package/dist/plugins/manager.js +109 -38
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.js +7 -0
- package/package.json +52 -11
- package/skills/plitzi-authoring/SKILL.md +153 -263
- package/skills/plitzi-authoring/reference/accessibility.md +111 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
- package/skills/plitzi-authoring/reference/data-and-visibility.md +180 -0
- package/skills/plitzi-authoring/reference/elements-and-styles.md +161 -0
- package/skills/plitzi-authoring/reference/flows.md +171 -0
- package/skills/plitzi-authoring/reference/layouts.md +140 -0
- package/skills/plitzi-authoring/reference/lists.md +74 -0
- package/skills/plitzi-authoring/reference/performance.md +67 -0
- package/skills/plitzi-authoring/reference/plugins.md +167 -0
- package/skills/plitzi-authoring/reference/review-checklist.md +67 -0
- package/skills/plitzi-authoring/reference/structure.md +83 -0
- package/skills/plitzi-authoring/reference/templates-and-export.md +54 -0
- package/skills/plitzi-authoring/reference/templates.md +109 -0
- package/skills/plitzi-authoring/reference/testing.md +119 -0
- package/skills/plitzi-authoring/reference/validation.md +73 -0
- package/dist/modules/actions/runtime/schedule.d.ts +0 -35
- package/dist/modules/actions/runtime/schedule.js +0 -76
- /package/dist/{modules/actions/runtime/schedule.test.d.ts → adapters/jsonAdapters.test.d.ts} +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Lists
|
|
2
|
+
|
|
3
|
+
A `list` renders its children once per item. Its rows come from its `items` — an array written or bound on the list:
|
|
4
|
+
a provider's answer, `state`, a fixed set. **Only `source: 'controlled'` reads them.** The default, `'none'`, renders
|
|
5
|
+
its children once as a plain `<ul>` and never looks at `items`, so a list with items and any other source is refused
|
|
6
|
+
(`list-items-ignored`). A CMS collection arrives the same way: a provider with its `connector` around the list, and the
|
|
7
|
+
list bound to its records (see the provider in data-and-visibility).
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
list({
|
|
11
|
+
id: 'games',
|
|
12
|
+
source: 'controlled',
|
|
13
|
+
class: gameGrid,
|
|
14
|
+
bind: { items: 'catalog.data.games' }, // an array from the provider around it
|
|
15
|
+
children: [gameCard()] // rendered once per game
|
|
16
|
+
})
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- Each row publishes **`list_<id>.item`** (the row) and **`list_<id>.index`** (its position, as text). Inside the row,
|
|
20
|
+
bindings read the short form (`'games.item.title'`), templates and attribute tokens the full one
|
|
21
|
+
(`{{ list_games.item.slug }}`).
|
|
22
|
+
- A **nested list** sees the outer row: inside `list_features`, `list_games.item` is still the game.
|
|
23
|
+
- The list is a `<ul>` and its `class` styles that root; each child is rendered straight into it, with no wrapper.
|
|
24
|
+
A card grid needs `{ margin: '0px', padding: '0px', 'list-style-type': 'none' }` in its class.
|
|
25
|
+
- Fixed data is `items: [ … ]` on the list itself — no provider needed.
|
|
26
|
+
- Everything inside a row is `repeated` in the handles: a test addresses one copy with `.first()` / `.nth()`.
|
|
27
|
+
|
|
28
|
+
## Filtering and sorting
|
|
29
|
+
|
|
30
|
+
Bind `items` through a template that hands over its **value**:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
list({
|
|
34
|
+
id: 'shown',
|
|
35
|
+
source: 'controlled',
|
|
36
|
+
bind: [bindTemplate('items', 'catalog.data.games',
|
|
37
|
+
"{{ source|filter(g => (state.genre ?? '') == '' or g.genre == state.genre)|sort((a, b) => b.score - a.score) }}",
|
|
38
|
+
{ returns: 'value' })],
|
|
39
|
+
children: [gameCard()]
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Only the rows that match are rendered. A counter and an empty state read the same predicate over the same source:
|
|
44
|
+
`{{ source|filter(…)|length }}` as text, and `visible: { source: 'catalog.data.games', template: "{{ source is defined
|
|
45
|
+
and source|filter(…)|length == 0 }}" }`.
|
|
46
|
+
|
|
47
|
+
Hiding rows one by one with a visibility binding also works, but every hidden row stays in the DOM (with its whole
|
|
48
|
+
subtree unless the row says `loadStrategy: 'visible'`). Filter the items instead.
|
|
49
|
+
|
|
50
|
+
## A detail page
|
|
51
|
+
|
|
52
|
+
A page with a route param (`slug: 'games/{{slug}}'`) shows one record. With a server provider, `singleRecord` and
|
|
53
|
+
`filters` fetch just that one. In the browser — an offline project, a `query` returning the whole collection — narrow
|
|
54
|
+
the list to the record the route names:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
apiContainer({ id: 'catalog', query: '/data/games.json', cache: true, children: [
|
|
58
|
+
list({
|
|
59
|
+
id: 'game',
|
|
60
|
+
source: 'controlled',
|
|
61
|
+
bind: [bindTemplate('items', 'catalog.data.games',
|
|
62
|
+
'{{ source|filter(g => g.slug == navigation.routeParams.slug) }}', { returns: 'value' })],
|
|
63
|
+
children: [gameDossier()] // reads list_game.item
|
|
64
|
+
}),
|
|
65
|
+
text('Signal lost — no game by that name.', {
|
|
66
|
+
id: 'game-missing',
|
|
67
|
+
visible: { source: 'catalog.data.games',
|
|
68
|
+
template: "{{ source is defined and not (source|find('slug', navigation.routeParams.slug)) }}" }
|
|
69
|
+
})
|
|
70
|
+
] })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
A link to it from a card is an attribute token read inside the card's row:
|
|
74
|
+
`link({ mode: 'internal', href: '/games/{{ list_games.item.slug }}' })`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Performance: what renders, and what it costs
|
|
2
|
+
|
|
3
|
+
A page that does the right thing can still do it by rendering everything, and nothing shows it but the frame rate — on
|
|
4
|
+
somebody else's slower machine. Measure it (`inspectRenders`, below) instead of guessing, and write the page so that a
|
|
5
|
+
change renders what shows it.
|
|
6
|
+
|
|
7
|
+
## What makes an element render
|
|
8
|
+
|
|
9
|
+
An element renders again when something it READS changes, and only then:
|
|
10
|
+
|
|
11
|
+
- **A binding, a `when` rule or an attribute template subscribes to each path it names** — `computed.tool`, not all of
|
|
12
|
+
`computed`; `state.cart.count`, not all of `state`. Name what you show: a template that reads `state.cart` renders on
|
|
13
|
+
every change to anything in the cart.
|
|
14
|
+
- **A computed value is evaluated again whenever the state, the variables, the route, the session, the host or the
|
|
15
|
+
theme changes.** A value that comes out the same keeps its object, so what reads it does not render — but the work of
|
|
16
|
+
evaluating it is done every time. Keep computed values cheap: a filter over a list of five thousand rows belongs in
|
|
17
|
+
the binding of the list that shows them (`bindTemplate(…, { returns: 'value' })`) or in the provider, where it runs
|
|
18
|
+
when the list changes and not on every keystroke anywhere on the page.
|
|
19
|
+
- **A parent rendering does not render its children** unless it hands them something new. What an element's own
|
|
20
|
+
bindings change is its own.
|
|
21
|
+
|
|
22
|
+
## What a flow costs
|
|
23
|
+
|
|
24
|
+
- **Synchronous steps are one change.** A `setState` after a `setState` after a `toggleState` renders once, when the
|
|
25
|
+
flow gives the page back — at its end, or at the first step that waits (a request, a `delay`), which renders what
|
|
26
|
+
came before it.
|
|
27
|
+
- **A trigger that fires on every pointer move or keystroke is a flow per event.** For a stream of events, keep the
|
|
28
|
+
moment-to-moment state inside a plugin and hand the space the outcome (`onCommit`, `onSelectionChange`), not every
|
|
29
|
+
step of it.
|
|
30
|
+
- **`whileRunning`** decides what a trigger fired again does while its flow runs: `skip` (the default) drops it,
|
|
31
|
+
`queue` runs it after, `parallel` alongside. A burst of events queued behind a slow request is a burst of renders
|
|
32
|
+
later.
|
|
33
|
+
|
|
34
|
+
## What mounts
|
|
35
|
+
|
|
36
|
+
- **A hidden element stays mounted** with its whole subtree — a modal's form, a closed panel's lists. `loadStrategy:
|
|
37
|
+
'visible'` mounts the contents only while it is shown, `'lazy'` from the first time it is shown. Use it for heavy
|
|
38
|
+
subtrees that start hidden.
|
|
39
|
+
- **Every list row is a subtree.** Filter the items the list is given; a row hidden by a condition is still a row.
|
|
40
|
+
|
|
41
|
+
## Measuring it: `inspectRenders`
|
|
42
|
+
|
|
43
|
+
Render the space with `debugMode` (the SDK profiles every element then), and ask what an interaction rendered:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { inspectRenders } from '@plitzi/sdk-authoring';
|
|
47
|
+
|
|
48
|
+
const report = await inspectRenders(page, () => page.click('[data-plitzi-el="save"]'), { max: 10 });
|
|
49
|
+
expect(report.problems).toEqual([]);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`report.elements` lists every element that rendered, how often, and what changed for it (`attributes`, `elementState`,
|
|
53
|
+
a prop's name); `report.causes` the store paths written meanwhile. `max` turns it into an assertion: over it,
|
|
54
|
+
`problems` names the elements that rendered most. A test that holds an interaction to its budget is how a regression
|
|
55
|
+
is caught before anybody feels it.
|
|
56
|
+
|
|
57
|
+
## When something is slow: where to look
|
|
58
|
+
|
|
59
|
+
1. **Count before changing anything.** `inspectRenders` around the interaction that feels slow. Hundreds of elements
|
|
60
|
+
for one click is the problem; three is not, and the cost is somewhere else (a plugin, a large list mounting).
|
|
61
|
+
2. **Read `changed` for the elements that rendered most.** Their own `attributes` or `elementState` changed: find what
|
|
62
|
+
they read in `causes` — a template naming more than it shows, a computed value that reads it. `nothing of its own`:
|
|
63
|
+
its context or its parent carried it; look at the element above it that did change.
|
|
64
|
+
3. **Read `causes`.** A path you did not expect written — a flow writing on every pointer move, a provider refetching —
|
|
65
|
+
is the change to stop at its source.
|
|
66
|
+
4. **Only then the browser's profiler**, for what a render costs rather than how many there are: a plugin drawing, a
|
|
67
|
+
list of thousands mounting at once.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Plugins
|
|
2
|
+
|
|
3
|
+
A plugin is a React component of the project's own — a chart, a map, a game — hosted in the space by a `custom`
|
|
4
|
+
element that names it by `renderType`:
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
custom({ id: 'arcade', renderType: 'nebulaRun', shipColor: 'amber', bind: { best: 'state.arcadeBest' } })
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Creating one
|
|
11
|
+
|
|
12
|
+
The CLI writes it, in the shape Plitzi's own elements are written in:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @plitzi/cli add plugin seat-picker legend # elements of this project (src/plugins/<Name> in a CLI project)
|
|
16
|
+
npx @plitzi/cli create seat-picker --plugin # a package of its own, with a preview
|
|
17
|
+
npx @plitzi/cli pack plugin # built, with its manifest and the zip the builder takes
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
One folder, four files: `SeatPicker.tsx` (the component), `declaration.ts` (its `type`, the `triggers` it fires, the
|
|
21
|
+
`callbacks` it answers to, and the element the builder adds — data only), `Settings.tsx` (its panel in the builder), and
|
|
22
|
+
`index.ts` (`Object.assign(Component, declaration, { pluginSettings: Settings })`). An event or an action is declared in
|
|
23
|
+
`declaration.ts` and registered by the component from there, never only in the component.
|
|
24
|
+
|
|
25
|
+
A plugin fires its events with `interactionsManager.interactionTrigger(id, action, payload)` whenever it has news —
|
|
26
|
+
from its first effect too: a flow starts once the page has finished mounting, so the `setState` it calls is there. No
|
|
27
|
+
`queueMicrotask` or delay of your own. An event that REPORTS a state, and may fire again before its flow ends (as a
|
|
28
|
+
component reads its real value right after mounting), wants `whileRunning('queue', …)` on the flow: by default a firing
|
|
29
|
+
while the flow runs is dropped, and the stale first report would stick.
|
|
30
|
+
|
|
31
|
+
## Props
|
|
32
|
+
|
|
33
|
+
**The host element's attributes ARE the component's props.** Whatever the space writes on the `custom` element arrives
|
|
34
|
+
by the same name, and so does whatever a binding writes — which is what makes a plugin live: bind an attribute to
|
|
35
|
+
`state`, a provider or a row, and the component re-renders with the answer. Prefer this to anything else.
|
|
36
|
+
|
|
37
|
+
`settings` is the other channel: a JSON string whose keys are merged into the props too. It is what the builder's
|
|
38
|
+
panel edits for a plugin it has no controls for; from code, write attributes. A `settings` that is not JSON renders
|
|
39
|
+
"Settings Malformed" in the page.
|
|
40
|
+
|
|
41
|
+
Every prop is optional and has a default: an attribute not authored yet, or a binding whose source has not answered,
|
|
42
|
+
is `undefined`, and a plugin that renders nothing then is a hole in the page.
|
|
43
|
+
|
|
44
|
+
## The component
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { RootElement } from '@plitzi/plitzi-sdk';
|
|
48
|
+
|
|
49
|
+
const NebulaRun = ({ shipColor = 'amber', best = 0, className }: NebulaRunProps) => (
|
|
50
|
+
<RootElement className={className}>…</RootElement>
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **Render `RootElement`**, not a `div`: it carries the element's id, classes and `data-plitzi-el`, so the CSS
|
|
55
|
+
authored on the element applies, the builder can select it and a test can find it.
|
|
56
|
+
- **Render the same thing on the server and on the first client pass.** A clock, a random number or anything read from
|
|
57
|
+
`window` belongs in an effect; a hydration mismatch discards the whole tree, not the plugin.
|
|
58
|
+
- **Borrow colours.** `currentColor` and the space's `var(--…)` tokens follow the theme; a hard-coded colour is
|
|
59
|
+
invisible in one of them. A canvas reads them with `getComputedStyle(element).getPropertyValue('--accent')` and
|
|
60
|
+
again when `theme.resolved` changes (bind it as a prop).
|
|
61
|
+
|
|
62
|
+
## Writing to the space
|
|
63
|
+
|
|
64
|
+
A plugin that produces something the page shows — a score, a selection — writes it to `runtime.state`, where every
|
|
65
|
+
binding reads it:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { getStateManager } from '@plitzi/plitzi-sdk';
|
|
69
|
+
|
|
70
|
+
const state = getStateManager();
|
|
71
|
+
state.setStateByKey('arcadeBest', Math.max(score, Number(state.state.arcadeBest ?? 0)));
|
|
72
|
+
const stop = state.subscribe(next => save(next)); // called after every change; call `stop()` to leave
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`state` is the current value, `setState` replaces it (or takes an updater), `setStateByKey` writes one key,
|
|
76
|
+
`clearState` empties it, `subscribe` listens. Bindings on `state.*` re-render at once.
|
|
77
|
+
|
|
78
|
+
## Talking to other pages
|
|
79
|
+
|
|
80
|
+
A plugin that moves at the speed of a cursor reads a realtime channel through `useChannel` rather than through flows —
|
|
81
|
+
messages arrive through a callback and re-render nothing:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useChannel } from '@plitzi/plitzi-sdk';
|
|
85
|
+
|
|
86
|
+
const room = useChannel(roomTopic, { onMessage: message => move(message.from, message.data) });
|
|
87
|
+
room.publish('pointer', { x, y }); // throttle it: ~20 a second, the latest position each time
|
|
88
|
+
room.members; // who is here, with the state each announced
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Take the topic as a prop (`bindTemplate('roomTopic', 'board.id', 'room:{{ source }}')`) so the space names its
|
|
92
|
+
channels, and trust a message you act on only when `message.from === 'server'`. The page has one connection and is one
|
|
93
|
+
member per topic, however many elements and plugins listen.
|
|
94
|
+
|
|
95
|
+
## Registering
|
|
96
|
+
|
|
97
|
+
A project `plitzi create` wrote registers every folder of `src/plugins` by itself, under the folder's name in
|
|
98
|
+
camelCase (`StatCard` → `statCard`). Anywhere else, the entry registers the component under its `renderType`: the
|
|
99
|
+
third argument to `render()`, `<PlitziSdk.Plugin>` in a React application, `plugins` on a page server of your own. A `renderType` nothing registered
|
|
100
|
+
renders "Custom Component … Not Found", and a page server logs the missing `renderType` at `error` once per
|
|
101
|
+
space: on a server, register it in `plugins` AND name it in the deployment's `pluginNames`.
|
|
102
|
+
|
|
103
|
+
A plugin PACKAGE, loaded by a space from its `plugin-manifest.json`, is an element TYPE of its own rather than a
|
|
104
|
+
`custom` host — it is how the builder adds one somebody dropped. Author it with a typed factory:
|
|
105
|
+
`defineElement<SeatPickerAttributes>(declaration)` from the plugin's own `declaration.ts`, or
|
|
106
|
+
`elementsFromManifest<{ seatPicker: SeatPickerAttributes }>(manifest)` from what it published — or untyped,
|
|
107
|
+
`element('seatPicker', { id: 'seats', start: 3 })`.
|
|
108
|
+
|
|
109
|
+
## Checked like a built-in element
|
|
110
|
+
|
|
111
|
+
Hand `authorSpace` the plugin's DECLARATION — the `declaration.ts` beside the component, or a manifest's
|
|
112
|
+
`pluginSchema` entry:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import declaration from './plugins/SeatPicker/declaration';
|
|
116
|
+
|
|
117
|
+
authorSpace(space, { plugins: [declaration] });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
It is then held to what it declares, whether it is authored as its own type or hosted by `custom({ renderType:
|
|
121
|
+
'seatPicker' })`: a flow on an event it never fires, a step sent to an action it does not answer, an attribute it does
|
|
122
|
+
not read — each is refused with the name it should have been. (`pluginTypes: ['seatPicker']` only tells the linter the
|
|
123
|
+
type exists; nothing about how the space uses it is checked.) A `custom` host whose component is NOT handed over is not
|
|
124
|
+
judged on its events at all — nothing here knows them.
|
|
125
|
+
|
|
126
|
+
Its events and actions have builders typed from the same declaration, so a name it does not declare is a compile error:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
seats({ id: 'seats', flows: [[named('picked', declaredTrigger(declaration, 'onPick')), setState({ key: 'seat', type: 'text', value: '{{ picked.seat }}' })]] })
|
|
130
|
+
button({ content: 'Clear', flows: [[onClick(), declaredCallback(declaration, 'reset', { on: 'seats' })]] })
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A plugin says what HAPPENED through its events (`onPick`, with the seat in the payload) and lets the space's flows
|
|
134
|
+
decide what that means — write `state`, open a modal, call a server action. Prefer that to writing `state` from inside
|
|
135
|
+
the component: the flow is visible in the space, the builder shows it, and the same act can come from a button too.
|
|
136
|
+
|
|
137
|
+
## Components that draw into DOM they do not render
|
|
138
|
+
|
|
139
|
+
A map, a chart library, anything that positions its own markers or popups: its roots are the library's to place. Never
|
|
140
|
+
give them a class that sets `position` — the element falls into the page's flow, offset by every marker before it. Put
|
|
141
|
+
the look in the plugin's own stylesheet (imported CSS ships beside the bundle) and take colours from custom properties
|
|
142
|
+
the space sets (`--seat-accent: var(--accent)` in `customCss`); a canvas or WebGL layer resolves them through a probe
|
|
143
|
+
element with `getComputedStyle(probe).color`, again whenever `theme.resolved` changes (bind it as a prop).
|
|
144
|
+
|
|
145
|
+
## Components that draw a lot
|
|
146
|
+
|
|
147
|
+
A canvas with thousands of shapes on it — a whiteboard, a diagram, a map of points — is judged on a slower machine than
|
|
148
|
+
yours: sixty frames a second here says nothing about a four-year-old laptop. What keeps one fast is doing work in
|
|
149
|
+
proportion to what CHANGED, not to what is there:
|
|
150
|
+
|
|
151
|
+
- **Two layers.** Paint what stands still on one canvas and leave it; clear and draw the one over it every frame, with
|
|
152
|
+
only what moves: the cursor, the selection, what is being dragged.
|
|
153
|
+
- **Repaint the part that changed.** Something added, edited or removed repaints the area it was and is in, clipped,
|
|
154
|
+
with whatever reaches into it — not the whole board. A pan moves the picture already painted by whole device pixels
|
|
155
|
+
and paints the edges it uncovers.
|
|
156
|
+
- **Draw a dragged group once.** Everything picked up moves by the same amount at every step: draw it to a canvas of its
|
|
157
|
+
own when the drag begins and copy that into place after.
|
|
158
|
+
- **Cache per object, not per frame.** Treat elements as immutable and key what is derived from one — its bounds, its
|
|
159
|
+
shape, a resolved connector — on the object itself (a `WeakMap`), so nothing is worked out twice for the same thing.
|
|
160
|
+
- **Hand the space outcomes, not motion.** A stroke finished, a selection changed: a trigger each. Firing one at every
|
|
161
|
+
pointer move runs a flow and renders the page at every pointer move.
|
|
162
|
+
- **Measure with a bench, throttled.** A script that drives the real thing in a browser with the CPU slowed
|
|
163
|
+
(`Emulation.setCPUThrottlingRate`) and reports script time per frame, not only frames per second — and a test that
|
|
164
|
+
counts WORK (full repaints, strokes drawn during a drag), which holds on any machine where a timing does not.
|
|
165
|
+
|
|
166
|
+
Pizarra, the collaborative whiteboard on the platform (`pizarra.plitzi.app`), does all of it on a board of thousands
|
|
167
|
+
of elements.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Before you say it is done
|
|
2
|
+
|
|
3
|
+
The feedback a reviewer gives on almost every change to a space, so you can give it to yourself first. Go through all
|
|
4
|
+
of it; each line is something that has shipped broken before.
|
|
5
|
+
|
|
6
|
+
## It renders, and says so
|
|
7
|
+
|
|
8
|
+
- [ ] `authorSpace` returns **zero warnings**. Each one is written code that will not do what it says.
|
|
9
|
+
- [ ] No console errors on any page you touched.
|
|
10
|
+
|
|
11
|
+
## Nothing flashes, nothing jumps
|
|
12
|
+
|
|
13
|
+
- [ ] Nothing appears only to be taken away while data loads: a condition that REVEALS an element starts hidden and
|
|
14
|
+
answers `'false'` until its source arrives. Checked from the FIRST frame, not a screenshot at the end.
|
|
15
|
+
- [ ] Nothing that is on screen by default disappears because a flag has not been set yet (a sidebar until it is
|
|
16
|
+
folded). Checked on a fresh visit, with no state kept from before.
|
|
17
|
+
- [ ] Empty states show only when the answer arrived and is empty — never while loading.
|
|
18
|
+
- [ ] No hidden element leaves a hole: the element itself is hidden, not a wrapper around it.
|
|
19
|
+
- [ ] Nothing shifts when the data lands: a loading area keeps its size, numbers use tabular figures.
|
|
20
|
+
|
|
21
|
+
## Every screen, every theme
|
|
22
|
+
|
|
23
|
+
- [ ] Desktop, tablet and mobile — and nothing scrolls sideways on a phone. A rule for tablets and phones alike is
|
|
24
|
+
written under `compact`.
|
|
25
|
+
- [ ] Light AND dark. Every colour is a token with both values; no theme-following text on a fixed background.
|
|
26
|
+
- [ ] Focus is visible on everything clickable (`focus-visible`), and `prefers-reduced-motion` stops what moves.
|
|
27
|
+
|
|
28
|
+
## Usable without sight
|
|
29
|
+
|
|
30
|
+
Screen readers and browser agents (Claude in Chrome) work a page through its accessibility tree. See
|
|
31
|
+
[accessibility](accessibility.md).
|
|
32
|
+
|
|
33
|
+
- [ ] Every button and link has words — an icon-only button a `title`, a link around a card a `label` — and every field
|
|
34
|
+
a `label` (`hideLabel: true` when the design shows what it is).
|
|
35
|
+
- [ ] Every image has an `alt` saying what it shows, or `decorative: true`.
|
|
36
|
+
- [ ] Every click is on a `button` or a `link`; the whole page can be worked from the keyboard.
|
|
37
|
+
- [ ] A toggle binds `ariaPressed`, a button that opens something `ariaExpanded`.
|
|
38
|
+
- [ ] Headings step down one level at a time; parts of the page a person jumps to are landmarks (`nav`, `main`, a
|
|
39
|
+
labelled `section`).
|
|
40
|
+
- [ ] What a canvas or a plugin draws is also there as elements, with a visible way to reach them.
|
|
41
|
+
|
|
42
|
+
## No copies
|
|
43
|
+
|
|
44
|
+
- [ ] Chrome shared by pages is a layout; the current menu entry comes from `activeOn`, not per-page styling.
|
|
45
|
+
- [ ] A look used twice is a class; a tree used twice is a function or a `map` over data; a list of pages, links or
|
|
46
|
+
plans is ONE array everything reads.
|
|
47
|
+
- [ ] No positional ids (`container-45`) in what you wrote; every referenced element is named, prefixed when a
|
|
48
|
+
helper runs more than once.
|
|
49
|
+
|
|
50
|
+
## True, and in the right place
|
|
51
|
+
|
|
52
|
+
- [ ] Every number, name and status on screen comes from a source. No placeholder figures, no invented logos or
|
|
53
|
+
testimonials. A panel with nothing true to show is an empty state.
|
|
54
|
+
- [ ] Times are formatted with an explicit zone and say which (`… 06:00 UTC`).
|
|
55
|
+
- [ ] Logic lives in binding templates or on the server, not in an attribute (where a condition is never evaluated).
|
|
56
|
+
- [ ] A filtered or sorted list binds its `items` to a template that returns its value — its rows are not hidden one
|
|
57
|
+
by one.
|
|
58
|
+
- [ ] Every link you wrote was followed at least once: an `href` built from a row goes where it says.
|
|
59
|
+
- [ ] Writes refresh what they changed — and only that (`invalidateQueries`); a multi-step form does not refresh the
|
|
60
|
+
provider that decides whether to leave.
|
|
61
|
+
- [ ] What only an administrator may read is shown only to administrators — and the check is a real boolean, not
|
|
62
|
+
a template left as text.
|
|
63
|
+
|
|
64
|
+
## Proven
|
|
65
|
+
|
|
66
|
+
- [ ] You looked at it: the visual check ran, on the pages you changed, in both themes.
|
|
67
|
+
- [ ] Both sides of each condition were seen: the empty account and the full one, the admin and the member.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Structuring a space that grows
|
|
2
|
+
|
|
3
|
+
A one-screen space fits in `src/space.ts`. Past a few pages, split it by what changes together — and keep every
|
|
4
|
+
repeated thing in exactly one place.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
src/space/
|
|
8
|
+
index.ts the SpaceSpec: name, variables, fonts, layouts, pages — assembly only
|
|
9
|
+
tokens.ts colour, shadow and radius variables, each with light and dark
|
|
10
|
+
styles.ts classes shared across areas (buttons, panels, page titles)
|
|
11
|
+
components.ts functions returning ElementSpec: panel(), statCard(), emptyState()
|
|
12
|
+
layouts/app.ts the application shell (+ its own classes)
|
|
13
|
+
docs/
|
|
14
|
+
nav.ts the section's pages as data: id, slug, title, lede, order
|
|
15
|
+
layout.ts the section shell, generated from nav.ts
|
|
16
|
+
page.ts docsPage(id, content) — the shape every page of the section shares
|
|
17
|
+
pages/
|
|
18
|
+
home.ts one file per page, or per page family
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Components are functions
|
|
22
|
+
|
|
23
|
+
The same block in two places is a function with parameters. Its ids come from a parameter, because ids are one
|
|
24
|
+
namespace for the whole space:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
export const panel = (params: { id: string; title: string; sub?: string; link?: { href: string; label: string }; children: ElementSpec[] }): ElementSpec =>
|
|
28
|
+
container({
|
|
29
|
+
id: params.id,
|
|
30
|
+
class: panelCard,
|
|
31
|
+
children: [
|
|
32
|
+
container({
|
|
33
|
+
id: `${params.id}-head`,
|
|
34
|
+
class: panelHead,
|
|
35
|
+
children: [
|
|
36
|
+
heading(params.title, { id: `${params.id}-title`, subType: 'h3', class: panelTitle }),
|
|
37
|
+
...(params.sub ? [text(params.sub, { id: `${params.id}-sub`, class: panelSub })] : []),
|
|
38
|
+
...(params.link ? [link({ id: `${params.id}-link`, href: params.link.href, class: panelLink, children: [text(params.link.label)] })] : [])
|
|
39
|
+
]
|
|
40
|
+
}),
|
|
41
|
+
...params.children
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A page then reads as what it shows: `panel({ id: 'hm-traffic', title: 'Traffic', link: { href: 'analytics', label:
|
|
47
|
+
'Open analytics →' }, children: [chart] })`.
|
|
48
|
+
|
|
49
|
+
## Lists are data
|
|
50
|
+
|
|
51
|
+
Four quick links, three pricing plans, a sidebar menu, the steps of an onboarding: an array and a `map`.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const QUICK = [
|
|
55
|
+
{ id: 'team', icon: 'fas fa-user-plus', title: 'Invite your team', href: 'workspace' },
|
|
56
|
+
{ id: 'docs', icon: 'fas fa-book-open', title: 'Quickstart', href: 'docs-quickstart' }
|
|
57
|
+
] as const;
|
|
58
|
+
|
|
59
|
+
container({ id: 'quick', class: quickGrid, children: QUICK.map(item => quickCard(item)) });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
When items differ in ONE detail (one link needs a binding), put that detail in the item's data as an optional field
|
|
63
|
+
— never an `if (item.id === 'team')` inside the map.
|
|
64
|
+
|
|
65
|
+
## Names
|
|
66
|
+
|
|
67
|
+
- **Ids:** `area-role` in kebab-case — `hm-traffic-title`, `docs-nav-docs-data`. Never the builder's positional
|
|
68
|
+
`container-45` in code you maintain: rename them when you touch the code.
|
|
69
|
+
- **Classes:** named after the role, not the look — `panel-title`, not `bold-16`.
|
|
70
|
+
- **State keys:** flat and descriptive — `docsClosedStart`, `onboardingDetails`.
|
|
71
|
+
|
|
72
|
+
## Code that came from an export
|
|
73
|
+
|
|
74
|
+
`specFromSpace` writes what the builder saved, faithfully: every element's own `css`, positional ids, `when` rules
|
|
75
|
+
per page. It renders; it is not maintainable. When you work on such a file, leave it better:
|
|
76
|
+
|
|
77
|
+
- repeated `css` blocks → one class;
|
|
78
|
+
- a nav with a hand-styled current entry per page → a layout and `activeOn`;
|
|
79
|
+
- the same tree pasted per item → a function and a `map`;
|
|
80
|
+
- `container-45` → a name.
|
|
81
|
+
|
|
82
|
+
Each of those is safe to do on its own: authoring refuses anything that would not render, and the visual check shows
|
|
83
|
+
the rest.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Templates, exports, and what authoring refuses
|
|
2
|
+
|
|
3
|
+
## Publishing a template
|
|
4
|
+
|
|
5
|
+
A template is ONE subtree, the style that dresses it and a name, published as a JSON someone drags onto a canvas you
|
|
6
|
+
will never see.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { authorTemplate } from '@plitzi/sdk-authoring';
|
|
10
|
+
|
|
11
|
+
const { template, warnings } = authorTemplate({
|
|
12
|
+
name: 'Pricing card',
|
|
13
|
+
description: 'A price, a list of features and a call to action.',
|
|
14
|
+
classes: { card: { padding: '24px', 'border-radius': '12px', 'background-color': 'var(--surface)' } },
|
|
15
|
+
root: container({ class: 'card', children: [heading('$19', { subType: 'h3' }), button({ content: 'Start' })] })
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **Everything it names, it carries.** A class declared in the space it was cut from does not travel: declare every
|
|
20
|
+
rule the subtree names (`validateTemplate` warns about one it does not carry).
|
|
21
|
+
- **A binding may not point outside the subtree.** Bring the provider into the template, or bind to a global.
|
|
22
|
+
|
|
23
|
+
## From an exported JSON
|
|
24
|
+
|
|
25
|
+
Never rewrite an exported space by hand. `specFromSpace({ schema, style })` reads it into the spec that authors it
|
|
26
|
+
(listing each repair in `corrections`); `specToSource(spec, { exportName })` writes that spec as code;
|
|
27
|
+
`compareSpaces(original, authorSpace(spec))` must list nothing but what `corrections` explains. The builder's
|
|
28
|
+
**Export** does all three. Edit the code from then on — and tidy it as you go ([structure.md](structure.md)).
|
|
29
|
+
|
|
30
|
+
For a document you did not author — an export, a JSON edited by hand — run the gate before serving it:
|
|
31
|
+
`validateSpace({ schema, style })`.
|
|
32
|
+
|
|
33
|
+
## What authoring refuses
|
|
34
|
+
|
|
35
|
+
`authorSpace` throws rather than hand back a space that would not render. Each error names the element and the fix:
|
|
36
|
+
|
|
37
|
+
- a CSS property the editor cannot read back; a class or slot the space does not declare (with the likely name)
|
|
38
|
+
- an element with a shared `class` AND rules of its own; one class declared twice with rules that disagree
|
|
39
|
+
- a binding source nothing answers to, or with the wrong prefix
|
|
40
|
+
- a source written by its short name inside a flow's params or a binding's template (`stats.x` for `apiContainer_stats.x`)
|
|
41
|
+
- a name that shadows a global (`variables`, `navigation`, `auth`, `state`, `theme`)
|
|
42
|
+
- two elements with one id — the error says where the first one is
|
|
43
|
+
- a step target that is not there; a broken flow chain, an orphan, a cycle
|
|
44
|
+
- a flow on a trigger its element never fires (`onSubmit` on a button), an element callback sent to a type that
|
|
45
|
+
does not answer it (`openModal` to a container)
|
|
46
|
+
- a `dropdownPopup` outside a `dropdown`, a tab container's parts outside a `tabContainer`
|
|
47
|
+
- a global callback on the wrong module, a utility given one
|
|
48
|
+
|
|
49
|
+
And it WARNS — returned in `warnings`, printed by `npm run author` — for what is written and will not do what it
|
|
50
|
+
says: `unknown-attribute`, `template-never-resolved`, `condition-starts-visible`, `state-key-has-runtime-prefix`,
|
|
51
|
+
`FORM_SUBMIT_UNMANAGED`, `STYLE_WITHOUT_TAG`, `tablet-rule-skips-mobile`. Treat a warning as a bug.
|
|
52
|
+
|
|
53
|
+
Ids are hashes of the path that produced them: re-authoring an unchanged space writes byte-identical documents, so a
|
|
54
|
+
seed can re-run and a diff stays readable.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Templates
|
|
2
|
+
|
|
3
|
+
Plitzi's templates are Twig, run by Plitzi's own interpreter. What a `{{ … }}` does depends on WHERE it is written.
|
|
4
|
+
|
|
5
|
+
## Where a template runs
|
|
6
|
+
|
|
7
|
+
| Written in | What is evaluated | What it can read |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| a binding's `twigTemplate` transformer | the whole template: conditions, filters, `{% set %}`, loops, tests | `source` (the bound value), `sourceTo`, the globals, the variables by bare name, and every source around the element |
|
|
10
|
+
| a flow step's params | the whole template | the trigger's payload and earlier steps by name, the globals, sources around the element |
|
|
11
|
+
| an ATTRIBUTE (`href`, `src`, `content`…) | only `{{ name }}` tokens (a filter after the name is allowed) | the globals, the variables and the page's route params by bare name (`{{ apiUrl }}`, `{{ slug }}`), and every source around the element (`{{ list_games.item.slug }}`) |
|
|
12
|
+
|
|
13
|
+
A **query parameter** is `navigation.queryParams.<name>` everywhere — never a bare name.
|
|
14
|
+
|
|
15
|
+
An attribute holding a condition — `href: "/x/{{ on ? 'a' : 'b' }}"` — is used **as written** (`authorSpace` warns
|
|
16
|
+
`template-never-resolved`). Move the expression into a binding with `bindTemplate`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
link({ href: '/', mode: 'internal', bind: [bindTemplate('href', 'state.workspace.id', "{{ source ? '/workspace/' ~ source : '/' }}")] })
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A value that came from a BINDING is data, never a template: a comment reading `{{ auth.accessToken }}` prints those
|
|
23
|
+
characters. Only what you authored is interpolated.
|
|
24
|
+
|
|
25
|
+
### A template's value, not its text
|
|
26
|
+
|
|
27
|
+
A binding's template renders TEXT by default. For an attribute that holds a list, a number or a flag — a list's
|
|
28
|
+
`items` above all — hand over the VALUE with `returns: 'value'` (`returnMode: 'value'` on the transformer). The
|
|
29
|
+
template must be a single `{{ expression }}`:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
list({ id: 'shown', source: 'controlled',
|
|
33
|
+
bind: [bindTemplate('items', 'games.data.games', '{{ source|filter(g => state.genre == "" or g.genre == state.genre) }}', { returns: 'value' })],
|
|
34
|
+
children: [ … ] })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A text template feeding `items` is refused: the list would receive the array written out as JSON and render nothing.
|
|
38
|
+
|
|
39
|
+
## Naming sources inside a template
|
|
40
|
+
|
|
41
|
+
Always the full name — the element's kind, an underscore, its id: `apiContainer_stats`, `list_rows`,
|
|
42
|
+
`modalContainer_credits`. The globals (`state`, `auth`, `navigation`, `variables`, `host`, `theme`, `computed`) are named
|
|
43
|
+
as themselves. In a flow, an earlier step is read by the name given with `named('quote', …)`: `{{ quote.output.total }}`.
|
|
44
|
+
|
|
45
|
+
An id with a hyphen is one name inside a template — `{{ apiContainer_tn-data.data.total }}` reads that provider. So
|
|
46
|
+
subtraction is written with spaces: `{{ total - used }}`, never `total-used`.
|
|
47
|
+
|
|
48
|
+
A source reaches only the elements **inside** the element that publishes it (a layout's providers reach every page in
|
|
49
|
+
it). A list publishes one scope per row: inside a row, `list_rows.item` is THAT row and `list_rows.index` its position;
|
|
50
|
+
a nested list's rows still see the outer row.
|
|
51
|
+
|
|
52
|
+
`authorSpace` refuses a name nothing answers to, a short name where the full one is needed, and a source read from
|
|
53
|
+
outside the element that publishes it — each with the name it should have been.
|
|
54
|
+
|
|
55
|
+
## The language
|
|
56
|
+
|
|
57
|
+
- Operators: `~` (concat), `+ - * / // % **`, `== != < > <= >=`, `and or not`, `in`, `starts with`, `ends with`,
|
|
58
|
+
`a ? b : c`, `a ? b` (empty when false), `a ?: b` (a when truthy), `a ?? b` (a when defined).
|
|
59
|
+
- Access: `a.b`, `a.0`, `a[i]`, `(expr).key`, `(expr)[i]`, `['x', 'y'][i]`, `{ k: 1 }.k`.
|
|
60
|
+
- Blocks: `{% set x = … %}`, `{% if %}…{% elseif %}…{% else %}…{% endif %}`, `{% for item in list %}…{% endfor %}`,
|
|
61
|
+
`{% apply upper %}…{% endapply %}`, `{% break %}`, `{% continue %}`.
|
|
62
|
+
- Arrow functions in filters, closing over `{% set %}` variables: `rows|sort(r => r.at)|first`,
|
|
63
|
+
`rows|sort((a, b) => b.score - a.score)`, `rows|filter(r => r.score >= min)`, `rows|map(r => r.name)`.
|
|
64
|
+
- Tests: `is defined`, `is empty`, `is null`, `is iterable`, `is even`, `is odd`, `is same as(x)` (strict: `false` is
|
|
65
|
+
not `'false'`, `0` or an unset value), `is divisible by(n)`, and their `is not` forms. `null` and `none` are literals.
|
|
66
|
+
- Functions: `range`, `min`, `max`, `cycle`.
|
|
67
|
+
- **Not supported**: `matches` (no regular expressions are evaluated — use `starts with`, `ends with`, `in`), macros,
|
|
68
|
+
`include`/`extends`, and any filter or function not listed here. `authorSpace` refuses them, and anything else the
|
|
69
|
+
interpreter would read past, rather than letting a template render a value nobody wrote.
|
|
70
|
+
|
|
71
|
+
Filters: `default upper lower trim capitalize title camelize kebab snake ltrim rtrim pad padRight replace slice split
|
|
72
|
+
join reverse length first last contains startswith endswith number number_format round abs format sort batch chunk map
|
|
73
|
+
reduce merge keys values filter column find pluck unique flatten sum without only index_by group_by url_encode nl2br
|
|
74
|
+
striptags spaceless json_encode to_json object_as_json raw date base64_encode base64_decode md5 random`.
|
|
75
|
+
|
|
76
|
+
`format` is sprintf: `'%02d'|format(n)` → `05`, `'%.1f'|format(x)`, `'%-8s'|format(name)`, `'%+d'|format(delta)`. A bare
|
|
77
|
+
`%f` keeps the number's own digits.
|
|
78
|
+
|
|
79
|
+
A lookup across two sources — a row joined to the stats around it:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
"{% set s = apiContainer_stats.data.spaces|find('id', list_spaces.item.id) %}{{ s ? s.traffic.views|number_format(0, '.', ',') ~ ' views' : '—' }}"
|
|
83
|
+
// or, in one expression:
|
|
84
|
+
"{{ (apiContainer_stats.data.spaces|find('id', list_spaces.item.id)).traffic.views ?? 0 }}"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Values to be careful with
|
|
88
|
+
|
|
89
|
+
- **An empty list is false**, like an absent one. To tell "arrived and empty" from "not arrived":
|
|
90
|
+
`{{ items is defined and items is empty }}`.
|
|
91
|
+
- **`json_encode` prints JSON for any value** — a string quoted and escaped, nothing as `null` — so a JSON document
|
|
92
|
+
is built by encoding each value: `'{ "board": {{ id|json_encode }}, "timer": {{ timer|json_encode }} }'`. In a flow
|
|
93
|
+
step's params and an action's output, `"{{ text }}"` in quotes is safe too — a value inside a string literal of a
|
|
94
|
+
param written as a JSON document is escaped for it. Anywhere else (an attribute, a binding's template) it is not:
|
|
95
|
+
encode.
|
|
96
|
+
- **Numbers**: `number_format(decimals, point, thousands)`; `round(precision)`.
|
|
97
|
+
- **A step param that is one `{{ expression }}` is that value, with its type**: a number stays a number, and a
|
|
98
|
+
string stays a string however it looks — a password typed `1234` arrives as `"1234"`. Nothing is guessed from the
|
|
99
|
+
text: to convert, declare the type where the value lands (`setState`'s `type`, the action's input field). Text around
|
|
100
|
+
the tokens is text, unless it makes a JSON object or array — then it is that document (see `json_encode` above).
|
|
101
|
+
|
|
102
|
+
## Dates
|
|
103
|
+
|
|
104
|
+
- `|date(format, zone)` — tokens `Y y m n d j H G h g A a i s l D F M N w U`; a backslash prints the next character
|
|
105
|
+
as it is (`\T`). **Always pass a zone** (`'UTC'`) and print it: with no zone the date is formatted in the local time
|
|
106
|
+
of whoever renders it — the server's for the HTML, the visitor's after hydration — and the two disagree.
|
|
107
|
+
- A timestamp can be seconds or milliseconds, a number or a string of digits — all read.
|
|
108
|
+
- For "3 minutes ago", the `dateConverter` transformer with `{ asAge: true }`; give it `isUnix: false` for an ISO
|
|
109
|
+
string. Its checkbox params accept real booleans (and `'true'`/`'false'`).
|