@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.
Files changed (386) hide show
  1. package/CHANGELOG.md +1296 -0
  2. package/README.md +126 -7
  3. package/dist/actions.d.ts +20 -8
  4. package/dist/actions.js +11 -4
  5. package/dist/adapters/cloudAdapters.js +4 -3
  6. package/dist/adapters/jsonAdapters.js +28 -6
  7. package/dist/auth.d.ts +7 -4
  8. package/dist/auth.js +8 -6
  9. package/dist/core/auth/api.d.ts +29 -1
  10. package/dist/core/auth/api.js +47 -13
  11. package/dist/core/auth/createAuth.d.ts +4 -0
  12. package/dist/core/auth/createAuth.js +3 -3
  13. package/dist/core/auth/credentials.d.ts +3 -0
  14. package/dist/core/auth/credentials.js +1 -1
  15. package/dist/core/auth/csrf.d.ts +25 -1
  16. package/dist/core/auth/csrf.js +29 -4
  17. package/dist/core/auth/renewal.d.ts +26 -0
  18. package/dist/core/auth/renewal.js +24 -0
  19. package/dist/core/auth/routes.js +8 -6
  20. package/dist/core/auth/session.d.ts +45 -0
  21. package/dist/core/auth/session.js +47 -1
  22. package/dist/core/auth/sessionActivity.d.ts +11 -0
  23. package/dist/core/auth/sessionActivity.js +14 -0
  24. package/dist/core/auth/throttle.d.ts +15 -0
  25. package/dist/core/auth/throttle.js +29 -1
  26. package/dist/core/auth/totp.d.ts +9 -1
  27. package/dist/core/auth/totp.js +11 -6
  28. package/dist/core/createServer.actions.test.d.ts +1 -0
  29. package/dist/core/createServer.d.ts +14 -1
  30. package/dist/core/createServer.functions.test.d.ts +1 -0
  31. package/dist/core/createServer.js +22 -12
  32. package/dist/core/fontStore.js +1 -1
  33. package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
  34. package/dist/core/handlers/authRouteHandlers.js +57 -21
  35. package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
  36. package/dist/core/handlers/csrfMiddleware.js +31 -7
  37. package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
  38. package/dist/core/handlers/oauthRouteHandlers.js +5 -3
  39. package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
  40. package/dist/core/handlers/socialRouteHandlers.js +2 -1
  41. package/dist/core/http/dispatcher.d.ts +9 -1
  42. package/dist/core/http/dispatcher.js +38 -11
  43. package/dist/core/http/navigation.d.ts +5 -2
  44. package/dist/core/http/navigation.js +7 -4
  45. package/dist/core/http/socketResponse.d.ts +18 -0
  46. package/dist/core/http/socketResponse.js +59 -0
  47. package/dist/core/http/sse.d.ts +26 -0
  48. package/dist/core/http/sse.js +78 -0
  49. package/dist/core/http/stages/authRoutes.js +3 -1
  50. package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
  51. package/dist/core/http/stages/middlewares.d.ts +1 -1
  52. package/dist/core/http/stages/middlewares.js +16 -10
  53. package/dist/core/http/stages/middlewares.test.d.ts +1 -0
  54. package/dist/core/http/stages/oauth.js +2 -2
  55. package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
  56. package/dist/core/http/stages/sessionRenewal.js +81 -0
  57. package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
  58. package/dist/core/http/stages/signInRoute.d.ts +2 -0
  59. package/dist/core/http/stages/signInRoute.js +139 -0
  60. package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
  61. package/dist/core/http/types.d.ts +9 -0
  62. package/dist/core/http/webExchange.d.ts +18 -0
  63. package/dist/core/http/webExchange.js +53 -0
  64. package/dist/core/oauth/authorize.js +37 -13
  65. package/dist/core/oauth/consentPage.js +11 -0
  66. package/dist/core/oauth/issueContext.d.ts +14 -0
  67. package/dist/core/oauth/issueContext.js +32 -0
  68. package/dist/core/oauth/records.d.ts +7 -0
  69. package/dist/core/oauth/register.d.ts +2 -0
  70. package/dist/core/oauth/register.js +32 -11
  71. package/dist/core/oauth/register.test.d.ts +1 -0
  72. package/dist/core/oauth/token.js +21 -6
  73. package/dist/core/oauth/token.test.d.ts +1 -0
  74. package/dist/core/requestParser.d.ts +4 -0
  75. package/dist/core/requestParser.js +14 -7
  76. package/dist/core/sdkAssets.js +1 -1
  77. package/dist/core/server/baseServer.d.ts +14 -1
  78. package/dist/core/server/baseServer.js +43 -11
  79. package/dist/core/server/closeOnSignals.d.ts +28 -0
  80. package/dist/core/server/closeOnSignals.js +47 -0
  81. package/dist/core/server/closeOnSignals.test.d.ts +1 -0
  82. package/dist/core/server/drain.d.ts +18 -0
  83. package/dist/core/server/drain.js +62 -0
  84. package/dist/core/server/drain.test.d.ts +1 -0
  85. package/dist/core/server/fleet/channel.d.ts +103 -0
  86. package/dist/core/server/fleet/channel.js +191 -0
  87. package/dist/core/server/fleet/channel.test.d.ts +1 -0
  88. package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
  89. package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
  90. package/dist/core/server/fleet/link.d.ts +23 -0
  91. package/dist/core/server/fleet/link.js +70 -0
  92. package/dist/core/server/fleet/relay.d.ts +11 -0
  93. package/dist/core/server/fleet/relay.js +104 -0
  94. package/dist/core/server/fleet/relay.test.d.ts +1 -0
  95. package/dist/core/server/fleet/role.d.ts +46 -0
  96. package/dist/core/server/fleet/role.js +59 -0
  97. package/dist/core/server/fleet/role.test.d.ts +1 -0
  98. package/dist/core/server/fleet/stores.d.ts +7 -0
  99. package/dist/core/server/fleet/stores.js +19 -0
  100. package/dist/core/server/fleet/supervisor.d.ts +26 -0
  101. package/dist/core/server/fleet/supervisor.js +108 -0
  102. package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
  103. package/dist/core/server/pageServer.js +36 -5
  104. package/dist/core/services/action.js +10 -2
  105. package/dist/core/services/functionRoutes.d.ts +9 -0
  106. package/dist/core/services/functionRoutes.js +47 -0
  107. package/dist/core/services/realtime.d.ts +8 -0
  108. package/dist/core/services/realtime.js +57 -0
  109. package/dist/core/services/registry.js +6 -2
  110. package/dist/core/staticFiles.js +12 -4
  111. package/dist/core/staticFiles.test.d.ts +1 -0
  112. package/dist/core/transports.d.ts +13 -3
  113. package/dist/core/transports.js +14 -5
  114. package/dist/functions-api.d.ts +281 -0
  115. package/dist/functions.d.ts +19 -0
  116. package/dist/functions.js +5 -0
  117. package/dist/functionsRunner.d.ts +15 -0
  118. package/dist/functionsRunner.js +6 -0
  119. package/dist/handlers.d.ts +2 -2
  120. package/dist/handlers.js +2 -2
  121. package/dist/helpers/atomicFile.d.ts +10 -0
  122. package/dist/helpers/atomicFile.js +33 -0
  123. package/dist/helpers/atomicFile.test.d.ts +1 -0
  124. package/dist/helpers/buildResponseHelpers.js +35 -11
  125. package/dist/helpers/buildServerInfo.js +4 -2
  126. package/dist/helpers/buildServerInfo.test.d.ts +1 -0
  127. package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
  128. package/dist/helpers/cache/CompressedFileCache.js +44 -0
  129. package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
  130. package/dist/helpers/cache/cacheManager.d.ts +1 -1
  131. package/dist/helpers/cache/cacheManager.js +2 -1
  132. package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
  133. package/dist/helpers/cache/index.d.ts +1 -1
  134. package/dist/helpers/cache/keys.d.ts +15 -1
  135. package/dist/helpers/cache/keys.js +47 -5
  136. package/dist/helpers/cache/serverCaches.d.ts +7 -1
  137. package/dist/helpers/compress.d.ts +3 -1
  138. package/dist/helpers/compress.js +11 -3
  139. package/dist/helpers/hydrationPayload.d.ts +11 -0
  140. package/dist/helpers/hydrationPayload.js +51 -0
  141. package/dist/helpers/hydrationPayload.test.d.ts +1 -0
  142. package/dist/helpers/metrics.d.ts +1 -1
  143. package/dist/helpers/metrics.js +3 -2
  144. package/dist/helpers/outboundGuard.d.ts +14 -6
  145. package/dist/helpers/outboundGuard.js +132 -8
  146. package/dist/helpers/paintedCookie.d.ts +8 -0
  147. package/dist/helpers/paintedCookie.js +13 -0
  148. package/dist/helpers/reportReactBuild.d.ts +9 -0
  149. package/dist/helpers/reportReactBuild.js +18 -0
  150. package/dist/helpers/reportReactBuild.test.d.ts +1 -0
  151. package/dist/helpers/serverLog.d.ts +25 -1
  152. package/dist/helpers/serverLog.js +68 -3
  153. package/dist/helpers/serverLog.test.d.ts +1 -0
  154. package/dist/helpers/wsFrame.d.ts +3 -0
  155. package/dist/helpers/wsFrame.js +5 -0
  156. package/dist/index.d.ts +8 -1
  157. package/dist/index.js +6 -2
  158. package/dist/kernel.d.ts +2 -1
  159. package/dist/kernel.js +3 -2
  160. package/dist/modules/actions/connectorWrite.js +2 -1
  161. package/dist/modules/actions/index.d.ts +41 -13
  162. package/dist/modules/actions/index.js +52 -9
  163. package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
  164. package/dist/modules/actions/jobs/failureStreak.js +47 -0
  165. package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
  166. package/dist/modules/actions/jobs/index.d.ts +33 -0
  167. package/dist/modules/actions/jobs/index.js +59 -0
  168. package/dist/modules/actions/jobs/memoryQueue.d.ts +33 -0
  169. package/dist/modules/actions/jobs/memoryQueue.js +194 -0
  170. package/dist/modules/actions/jobs/memoryStores.test.d.ts +1 -0
  171. package/dist/modules/actions/jobs/scheduler.d.ts +64 -0
  172. package/dist/modules/actions/jobs/scheduler.js +130 -0
  173. package/dist/modules/actions/jobs/scheduler.test.d.ts +1 -0
  174. package/dist/modules/actions/jobs/schedules.d.ts +23 -0
  175. package/dist/modules/actions/jobs/schedules.js +47 -0
  176. package/dist/modules/actions/jobs/storeClock.d.ts +16 -0
  177. package/dist/modules/actions/jobs/storeClock.js +32 -0
  178. package/dist/modules/actions/jobs/testing/jobQueueContract.d.ts +25 -0
  179. package/dist/modules/actions/jobs/worker.d.ts +64 -0
  180. package/dist/modules/actions/jobs/worker.js +267 -0
  181. package/dist/modules/actions/jobs/worker.test.d.ts +1 -0
  182. package/dist/modules/actions/moduleFor.js +6 -2
  183. package/dist/modules/actions/runtime/errors.d.ts +12 -0
  184. package/dist/modules/actions/runtime/errors.js +16 -1
  185. package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
  186. package/dist/modules/actions/runtime/kvChange.js +23 -0
  187. package/dist/modules/actions/runtime/kvList.d.ts +51 -0
  188. package/dist/modules/actions/runtime/kvList.js +52 -0
  189. package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
  190. package/dist/modules/actions/runtime/kvStore.js +46 -20
  191. package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
  192. package/dist/modules/actions/runtime/memoryKv.js +19 -1
  193. package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
  194. package/dist/modules/actions/runtime/namespaceKv.js +15 -3
  195. package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
  196. package/dist/modules/actions/runtime/precheck.js +5 -9
  197. package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
  198. package/dist/modules/actions/runtime/rateLimit.js +19 -0
  199. package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
  200. package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
  201. package/dist/modules/actions/runtime/redisKv.js +46 -0
  202. package/dist/modules/actions/runtime/renderResolver.js +2 -2
  203. package/dist/modules/actions/runtime/report.js +2 -1
  204. package/dist/modules/actions/runtime/runAction.d.ts +10 -2
  205. package/dist/modules/actions/runtime/runAction.js +52 -116
  206. package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
  207. package/dist/modules/actions/runtime/runLogger.js +8 -2
  208. package/dist/modules/actions/runtime/signing.d.ts +20 -0
  209. package/dist/modules/actions/runtime/signing.js +25 -0
  210. package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
  211. package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
  212. package/dist/modules/actions/runtime/taskContext.js +124 -0
  213. package/dist/modules/actions/tasks/builtins.js +5 -1
  214. package/dist/modules/actions/tasks/flow.js +81 -10
  215. package/dist/modules/actions/tasks/helpers.js +2 -2
  216. package/dist/modules/actions/tasks/http.js +2 -3
  217. package/dist/modules/actions/tasks/kv.js +39 -0
  218. package/dist/modules/actions/tasks/list.d.ts +2 -0
  219. package/dist/modules/actions/tasks/list.js +145 -0
  220. package/dist/modules/actions/tasks/realtime.d.ts +2 -0
  221. package/dist/modules/actions/tasks/realtime.js +104 -0
  222. package/dist/modules/actions/tasks/registry.d.ts +10 -1
  223. package/dist/modules/actions/tasks/registry.js +17 -6
  224. package/dist/modules/actions/transport/callHandler.js +6 -3
  225. package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
  226. package/dist/modules/actions/transport/catalogHandler.js +3 -2
  227. package/dist/modules/actions/transport/stream.d.ts +4 -5
  228. package/dist/modules/actions/transport/stream.js +12 -76
  229. package/dist/modules/actions/transport/webhookHandler.js +5 -4
  230. package/dist/modules/actions/types.d.ts +162 -4
  231. package/dist/modules/connectors/engine.js +3 -5
  232. package/dist/modules/functions/build.d.ts +22 -0
  233. package/dist/modules/functions/build.js +122 -0
  234. package/dist/modules/functions/build.test.d.ts +1 -0
  235. package/dist/modules/functions/capabilities.d.ts +22 -0
  236. package/dist/modules/functions/capabilities.js +228 -0
  237. package/dist/modules/functions/config.d.ts +34 -0
  238. package/dist/modules/functions/config.js +24 -0
  239. package/dist/modules/functions/context.d.ts +17 -0
  240. package/dist/modules/functions/context.js +88 -0
  241. package/dist/modules/functions/contract.d.ts +121 -0
  242. package/dist/modules/functions/contract.js +9 -0
  243. package/dist/modules/functions/driver.d.ts +36 -0
  244. package/dist/modules/functions/driver.js +150 -0
  245. package/dist/modules/functions/load.d.ts +15 -0
  246. package/dist/modules/functions/load.js +45 -0
  247. package/dist/modules/functions/load.test.d.ts +1 -0
  248. package/dist/modules/functions/local.d.ts +29 -0
  249. package/dist/modules/functions/local.js +61 -0
  250. package/dist/modules/functions/local.test.d.ts +1 -0
  251. package/dist/modules/functions/manifest.d.ts +24 -0
  252. package/dist/modules/functions/manifest.js +163 -0
  253. package/dist/modules/functions/manifest.test.d.ts +1 -0
  254. package/dist/modules/functions/native.d.ts +8 -0
  255. package/dist/modules/functions/native.js +21 -0
  256. package/dist/modules/functions/native.test.d.ts +1 -0
  257. package/dist/modules/functions/protocol.d.ts +216 -0
  258. package/dist/modules/functions/protocol.js +18 -0
  259. package/dist/modules/functions/routes.d.ts +36 -0
  260. package/dist/modules/functions/routes.js +181 -0
  261. package/dist/modules/functions/routes.test.d.ts +1 -0
  262. package/dist/modules/functions/runner/messages.d.ts +5 -0
  263. package/dist/modules/functions/runner/messages.js +188 -0
  264. package/dist/modules/functions/runner/remote.d.ts +19 -0
  265. package/dist/modules/functions/runner/remote.js +116 -0
  266. package/dist/modules/functions/runner/remote.test.d.ts +1 -0
  267. package/dist/modules/functions/runner/service.d.ts +32 -0
  268. package/dist/modules/functions/runner/service.js +209 -0
  269. package/dist/modules/functions/sandbox/guest.d.ts +45 -0
  270. package/dist/modules/functions/sandbox/guest.js +660 -0
  271. package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
  272. package/dist/modules/functions/sandbox/isolate.js +347 -0
  273. package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
  274. package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
  275. package/dist/modules/functions/sandbox/prelude.js +49 -0
  276. package/dist/modules/functions/space.d.ts +40 -0
  277. package/dist/modules/functions/space.js +162 -0
  278. package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
  279. package/dist/modules/functions/tryEntry.d.ts +9 -0
  280. package/dist/modules/functions/tryEntry.js +49 -0
  281. package/dist/modules/mongo/index.d.ts +4 -0
  282. package/dist/modules/mongo/jobQueue.d.ts +38 -0
  283. package/dist/modules/mongo/jobQueue.js +335 -0
  284. package/dist/modules/mongo/kv.d.ts +15 -0
  285. package/dist/modules/mongo/kv.js +129 -0
  286. package/dist/modules/mongo/stores.integration.test.d.ts +1 -0
  287. package/dist/modules/mysql/accounts.js +19 -9
  288. package/dist/modules/mysql/index.d.ts +2 -1
  289. package/dist/modules/mysql/jobs/index.d.ts +5 -0
  290. package/dist/modules/mysql/jobs/jobQueue.d.ts +40 -0
  291. package/dist/modules/mysql/jobs/jobQueue.js +375 -0
  292. package/dist/modules/mysql/jobs/kv.d.ts +18 -0
  293. package/dist/modules/mysql/jobs/kv.js +108 -0
  294. package/dist/modules/mysql/jobs/schema.d.ts +33 -0
  295. package/dist/modules/mysql/jobs/schema.js +113 -0
  296. package/dist/modules/mysql/jobs/stores.integration.test.d.ts +1 -0
  297. package/dist/modules/mysql/schema.js +25 -0
  298. package/dist/modules/mysql/sessions.d.ts +1 -11
  299. package/dist/modules/mysql/sessions.js +6 -1
  300. package/dist/modules/realtime/declarations.d.ts +11 -0
  301. package/dist/modules/realtime/declarations.js +26 -0
  302. package/dist/modules/realtime/grants.d.ts +23 -0
  303. package/dist/modules/realtime/grants.js +61 -0
  304. package/dist/modules/realtime/handlers.d.ts +74 -0
  305. package/dist/modules/realtime/handlers.js +224 -0
  306. package/dist/modules/realtime/hub.d.ts +62 -0
  307. package/dist/modules/realtime/hub.js +141 -0
  308. package/dist/modules/realtime/index.d.ts +51 -0
  309. package/dist/modules/realtime/index.js +62 -0
  310. package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
  311. package/dist/modules/realtime/memoryPubSub.js +43 -0
  312. package/dist/modules/realtime/realtime.test.d.ts +1 -0
  313. package/dist/modules/realtime/redisPubSub.d.ts +24 -0
  314. package/dist/modules/realtime/redisPubSub.js +36 -0
  315. package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
  316. package/dist/modules/realtime/socket.d.ts +25 -0
  317. package/dist/modules/realtime/socket.js +122 -0
  318. package/dist/modules/realtime/socket.test.d.ts +1 -0
  319. package/dist/modules/rsc/handler.d.ts +16 -0
  320. package/dist/modules/rsc/handler.js +17 -10
  321. package/dist/modules/rsc/handler.test.d.ts +1 -0
  322. package/dist/modules/rsc/resolveRscData.js +2 -1
  323. package/dist/modules/runtime/bundle.d.ts +24 -0
  324. package/dist/modules/runtime/bundle.js +102 -0
  325. package/dist/modules/runtime/contract.d.ts +37 -0
  326. package/dist/modules/runtime/contract.js +21 -0
  327. package/dist/modules/runtime/entry.test.d.ts +1 -0
  328. package/dist/modules/runtime/host.d.ts +38 -0
  329. package/dist/modules/runtime/host.js +68 -0
  330. package/dist/modules/runtime/inside.d.ts +15 -0
  331. package/dist/modules/runtime/inside.js +47 -0
  332. package/dist/modules/runtime/inside.test.d.ts +1 -0
  333. package/dist/modules/runtime/runner.d.ts +11 -0
  334. package/dist/modules/runtime/runner.js +38 -0
  335. package/dist/modules/runtime/runtime.test.d.ts +1 -0
  336. package/dist/modules/runtime/stages.d.ts +50 -0
  337. package/dist/modules/runtime/stages.js +117 -0
  338. package/dist/modules/ssr/Component.d.ts +4 -5
  339. package/dist/modules/ssr/Component.js +2 -2
  340. package/dist/modules/ssr/handler.js +10 -2
  341. package/dist/modules/ssr/loadPluginComponents.js +2 -1
  342. package/dist/modules/ssr/prepareRender.js +39 -13
  343. package/dist/modules/ssr/preview.d.ts +3 -0
  344. package/dist/modules/ssr/preview.js +17 -7
  345. package/dist/modules/ssr/redirect.test.d.ts +1 -0
  346. package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
  347. package/dist/modules/ssr/registerExternalPlugins.js +19 -16
  348. package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
  349. package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
  350. package/dist/modules/ssr/reportMissingPlugins.js +24 -0
  351. package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
  352. package/dist/modules/ssr/streamBody.d.ts +2 -2
  353. package/dist/modules/ssr/streamBody.js +4 -1
  354. package/dist/modules/ssr/template.js +1 -1
  355. package/dist/modules/ssr/views/template.ejs +8 -5
  356. package/dist/mongo.d.ts +25 -0
  357. package/dist/mongo.js +3 -0
  358. package/dist/mysql.d.ts +11 -0
  359. package/dist/mysql.js +4 -1
  360. package/dist/oauth.js +2 -2
  361. package/dist/plugins/compile.js +9 -4
  362. package/dist/plugins/copy.js +3 -3
  363. package/dist/plugins/manager.d.ts +35 -3
  364. package/dist/plugins/manager.js +109 -38
  365. package/dist/runtime.d.ts +15 -0
  366. package/dist/runtime.js +7 -0
  367. package/package.json +52 -11
  368. package/skills/plitzi-authoring/SKILL.md +153 -263
  369. package/skills/plitzi-authoring/reference/accessibility.md +111 -0
  370. package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
  371. package/skills/plitzi-authoring/reference/data-and-visibility.md +180 -0
  372. package/skills/plitzi-authoring/reference/elements-and-styles.md +161 -0
  373. package/skills/plitzi-authoring/reference/flows.md +171 -0
  374. package/skills/plitzi-authoring/reference/layouts.md +140 -0
  375. package/skills/plitzi-authoring/reference/lists.md +74 -0
  376. package/skills/plitzi-authoring/reference/performance.md +67 -0
  377. package/skills/plitzi-authoring/reference/plugins.md +167 -0
  378. package/skills/plitzi-authoring/reference/review-checklist.md +67 -0
  379. package/skills/plitzi-authoring/reference/structure.md +83 -0
  380. package/skills/plitzi-authoring/reference/templates-and-export.md +54 -0
  381. package/skills/plitzi-authoring/reference/templates.md +109 -0
  382. package/skills/plitzi-authoring/reference/testing.md +119 -0
  383. package/skills/plitzi-authoring/reference/validation.md +73 -0
  384. package/dist/modules/actions/runtime/schedule.d.ts +0 -35
  385. package/dist/modules/actions/runtime/schedule.js +0 -76
  386. /package/dist/{modules/actions/runtime/schedule.test.d.ts → adapters/jsonAdapters.test.d.ts} +0 -0
@@ -1,283 +1,173 @@
1
1
  ---
2
2
  name: plitzi-authoring
3
3
  description: >-
4
- Write a Plitzi space or a publishable template in TypeScript — pages, elements, CSS, data bindings and flows —
5
- instead of hand-writing schema JSON. Use whenever the task is to create, extend or re-theme a space: adding a
6
- page or a section, styling one, binding an element to server data, wiring what happens on click, authoring a
7
- template to host, or turning an exported JSON into something maintainable.
4
+ Write or change a Plitzi space or template in TypeScript with @plitzi/sdk-authoring — pages, layouts, elements,
5
+ CSS classes, data bindings, providers and flows — instead of hand-writing schema JSON. Use whenever the task is to
6
+ create, extend, restyle or fix a space: adding a page or a section, sharing a header across pages, binding an
7
+ element to data, showing or hiding something, wiring what happens on click, or turning an exported JSON into code.
8
8
  ---
9
9
 
10
10
  # Authoring Plitzi spaces
11
11
 
12
- A Plitzi space is two documents — a schema and a style — of deeply cross-referenced ids. **Do not write them by
13
- hand.** Every id, class name, parent/root link, breakpoint map and interaction chain is derived from a much
14
- smaller declaration, and the surface that derives them also refuses a declaration that would not render.
12
+ A space is two JSON documents — a schema and a style — full of cross-referenced ids. **Never write them by hand.**
13
+ `@plitzi/sdk-authoring` derives every id, class name, parent link, breakpoint map and flow chain from a small
14
+ declaration, and refuses a declaration that would not render.
15
15
 
16
16
  ```ts
17
- import { authorSpace, container, heading, text } from '@plitzi/sdk-authoring';
17
+ import { authorSpace, container, heading, styles, text } from '@plitzi/sdk-authoring';
18
18
 
19
- const { schema, style, warnings } = authorSpace({
19
+ const page = styles('page', { display: 'flex', 'flex-direction': 'column', padding: '96px 24px', gap: '16px' });
20
+
21
+ export const space = {
20
22
  name: 'My space',
21
23
  permanentUrl: 'my-space',
22
- classes: { page: { desktop: { display: 'flex', 'flex-direction': 'column', padding: '96px 24px', gap: '16px' } } },
23
- pages: [
24
- { name: 'Home', slug: '', class: 'page', body: [heading('Hello', { subType: 'h1' }), text('A paragraph.')] }
25
- ]
26
- });
27
- ```
28
-
29
- Serve it with the adapters the page server already takes:
24
+ pages: [{ name: 'Home', slug: '', class: page, body: [heading('Hello', { subType: 'h1' }), text('A paragraph.')] }]
25
+ };
30
26
 
31
- ```ts
32
- createServer({ port: 3001, adapters: createJsonAdapters({ offlineData: { schema, style } }) });
27
+ const { schema, style, warnings } = authorSpace(space);
33
28
  ```
34
29
 
35
- One package, and it installs nothing: `@plitzi/sdk-authoring` has an empty dependency tree, no React and nothing
36
- that touches a browser, so a seed, a migration, a build script or a project that only publishes templates depends
37
- on it alone.
38
-
39
- ## The types are the reference
40
-
41
- Every factory, spec field and step builder carries its documentation in the published `.d.ts`. Read it rather
42
- than guessing — `node_modules/@plitzi/sdk-authoring/dist/index.d.ts` is the whole surface in one file. Attribute
43
- types come from each element's own component, so an editor completes them and a wrong value is a compile error.
44
-
45
- ## Elements
46
-
47
- One factory per element, named after it. Attributes and the authoring fields go in ONE flat object:
30
+ The published `.d.ts` is the reference for every factory, field and step builder
31
+ (`node_modules/@plitzi/sdk-authoring/dist/index.d.ts`) — read it instead of guessing a name or a param. For one element
32
+ type, the exported catalogues answer faster (`elementDefaultAttributes`, `elementTriggers`, `elementSlots`… — see
33
+ [elements and styles](reference/elements-and-styles.md)).
34
+
35
+ ## How to work
36
+
37
+ 1. **Read before you write.** Open `src/space.ts` (and whatever it imports) and find the layout, the classes and the
38
+ helpers already there. Extend them; do not add a second way of doing something the space already does.
39
+ 2. **Change the declaration, then author it.** `npm run author` (or restart the server) runs `authorSpace`. It checks
40
+ everything — every field, value, template, name, param and page link — and a refusal says what to write instead:
41
+ do exactly that. Never work around a check, cast past it, or move the logic into a plugin to escape it. The first
42
+ refusals come one at a time; the linter's come as ONE list — fix every line of it before running again. How it
43
+ checks, a one-file author script for any project, and what it cannot see: [validation](reference/validation.md).
44
+ 3. **Read every warning.** Each one names something written that will not do what it says. Zero warnings is the bar.
45
+ 4. **Look at it.** `npm run shot -- /about --width 390 --scheme dark` saves a picture of one page; `npm run visual`
46
+ runs the checks. Look at desktop, tablet and mobile, light and dark, and the page while its data is still loading.
47
+ 5. **Go through the [review checklist](reference/review-checklist.md) before saying it is done.** It is the feedback a
48
+ reviewer gives on every change, written down so you do not need to hear it.
49
+
50
+ ## The rules that matter most
51
+
52
+ 1. **Name what is referred to.** Give an `id` to every element a binding, a flow or a test addresses. Ids are ONE
53
+ namespace for the whole space — layouts and every page share it — so an element built by a helper that runs more
54
+ than once gets its id prefixed by what it is for: `` `${pageId}-foot` ``.
55
+ 2. **Share with classes and layouts, never with copies.** A look used twice is a `styles()` class. Chrome shown on
56
+ several pages — a header, a sidebar, a footer — is a **layout** the pages name, written once. A menu, a card grid,
57
+ a list of steps is DATA mapped to elements (`entries.map(entry => link(…))`), not a block pasted per item. See
58
+ [layouts and duplication](reference/layouts.md).
59
+ 3. **One element, one base selector.** An element takes a shared `class` OR its own `css`, never both — authoring
60
+ refuses the pair. A look that never changes is its own class.
61
+ 4. **Visible by default; decide which way the logic flips.** An element is on screen unless something hides it. When
62
+ the logic REVEALS it (hidden → shown: an empty state, a "get started" card, an admin-only panel), it starts hidden —
63
+ `visible: 'source'`, or `visible: false` plus a binding for a computed condition — so it never flashes while data
64
+ loads (`condition-starts-visible` warns). When a flag HIDES it (shown → hidden: a sidebar label until the sidebar
65
+ is folded), it keeps the default and an absent flag must leave it shown. See
66
+ [data and visibility](reference/data-and-visibility.md).
67
+ 5. **Know where a template is evaluated.** A binding's template (`bindTemplate`) and a step's params evaluate full
68
+ Twig. An ATTRIBUTE resolves a name with filters (`{{ list_games.item.slug }}`, `{{ post.slug|url_encode }}`) against
69
+ the sources around the element; a condition there is used as written. Inside any template a source is spelled in
70
+ full — `apiContainer_stats`, `list_rows` — and a query param is `navigation.queryParams.x`. A template that feeds a
71
+ list its `items` hands over its value (`returns: 'value'`). `authorSpace` refuses a template it cannot read and a
72
+ name nothing answers to. See [templates](reference/templates.md).
73
+ 6. **Tokens, not colours.** Every colour is a variable of the space with a `light` and a `dark` value; both themes are
74
+ checked from the first commit.
75
+ 7. **Times in UTC, and say so.** Format with an explicit zone (`|date('j M · H:i', 'UTC')`) and print "UTC" beside it:
76
+ a page rendered on a server and hydrated in a browser in another zone must agree on the hour.
77
+ 8. **Breakpoints are ranges.** `tablet` (48–64rem) and `mobile` (below 48rem) each inherit only from `desktop`; a rule
78
+ meant for both is written under `compact`.
79
+ 9. **Never invent data.** Numbers, names and states on screen come from a source. A panel with nothing true to say is
80
+ an empty state, not a placeholder figure. With no backend, the data is JSON the project serves
81
+ (`public/data/*.json`) read by an `apiContainer` — see [data and visibility](reference/data-and-visibility.md).
82
+ 10. **Never hand-write** `flat`, derived ids, `styleSelectors`, `beforeNode`/`afterNode`/`flowId`, or a
83
+ `styleVariant` binding's key — use the factories, `variantFrom` and `activeOn`.
84
+ 11. **A switch is named for how it leaves its default.** `toggleState` turns a key nobody has set yet ON, so a key
85
+ named for the default (`showPlates`, on by default) takes a first click to do nothing: name it `platesOff`, and read
86
+ every switch through `computed` (`plates: '{{ state.platesOff ? false : true }}'`) so its default shows before
87
+ anybody touches it.
88
+ 12. **Hand plugins over as their declarations**: `authorSpace(space, { plugins: [declaration] })` holds a plugin —
89
+ its own type, or a `custom({ renderType })` host — to the events, actions and attributes it declares. See
90
+ [plugins](reference/plugins.md).
91
+ 13. **Usable without sight.** Screen readers and browser agents (Claude in Chrome) find a page's controls in its
92
+ accessibility tree: every button and link has words (an icon-only button a `title`), every field a `label`
93
+ (`hideLabel: true` hides it and keeps the name), every image an `alt` or `decorative: true`. Clicks go on a
94
+ `button` — it holds children, so a whole card can be one — or a `link`, never on a container. See
95
+ [accessibility](reference/accessibility.md).
96
+
97
+ ## Recipes
98
+
99
+ Copy the one that fits, then change the names. Each is the shape `authorSpace` accepts on the first try.
48
100
 
49
101
  ```ts
50
- heading({ content: 'Fieldnotes', subType: 'h2', class: 'title' })
51
- image({ src: '/fox.jpg', alt: 'A fox', css: { 'aspect-ratio': '3/2' } })
52
- container({ class: 'card', children: [ … ] })
53
- text('Wildlife, close up') // a string is the content
54
- container([hero, grid]) // an array is the children
102
+ // Data with no backend: a JSON file the project serves (public/data/games.json), read by a provider.
103
+ apiContainer({ id: 'catalog', query: '/data/games.json', cache: true, children: [ /* reads catalog.data.… */ ] })
104
+
105
+ // A list of rows from that data. Inside a row: short form in bindings, full name in templates and attributes.
106
+ list({ id: 'games', source: 'controlled', bind: { items: 'catalog.data.games' }, children: [
107
+ link({ mode: 'internal', href: '/games/{{ list_games.item.slug }}', children: [
108
+ text('', { bind: { content: 'games.item.title' } })
109
+ ] })
110
+ ] })
111
+
112
+ // A filtered list: a template that hands over its VALUE.
113
+ list({ id: 'shown', source: 'controlled',
114
+ bind: [bindTemplate('items', 'catalog.data.games', "{{ source|filter(g => g.genre == state.genre) }}", { returns: 'value' })],
115
+ children: [ … ] })
116
+
117
+ // Text computed from data.
118
+ text('', { bind: [bindTemplate('content', 'catalog.data.games', '{{ source|length }} games')] })
119
+
120
+ // Shown only when a computed condition says so (starts hidden, no flash).
121
+ text('Nothing here yet', { visible: { source: 'catalog.data.games', template: '{{ source is defined and source|length == 0 }}' } })
122
+
123
+ // A value used in many places: declare it once in the space, read it anywhere as computed.xp.
124
+ computed: { xp: '{{ (state.favourites|length) * 10 }}' }
125
+
126
+ // A button that does something: a flow is [trigger, steps…].
127
+ button({ content: 'Save', flows: [[onClick(), setState({ key: 'saved', type: 'boolean', value: true })]] })
128
+
129
+ // A form: the flow goes on the form, which manages its own submit.
130
+ form({ id: 'signup', managedByInteractions: true,
131
+ flows: [[named('sent', onSubmit()), setState({ key: 'email', type: 'text', value: '{{ sent.values.email }}' })]],
132
+ children: [formControl({ name: 'email', label: 'Email', subType: 'email' }), button({ content: 'Sign up', subType: 'submit' })] })
133
+
134
+ // A modal: hidden until a flow opens it.
135
+ modalContainer({ id: 'details', visible: false, title: 'Details', children: [ … ] })
136
+ button({ content: 'Open', flows: [[onClick(), openModal('details')]] })
137
+
138
+ // A plugin: author it from its own declaration, flow on its events, call its actions — all checked.
139
+ const seats = defineElement<SeatPickerAttributes>(declaration);
140
+ seats({ id: 'seats', flows: [[named('picked', declaredTrigger(declaration, 'onPick')), setState({ key: 'seat', type: 'text', value: '{{ picked.seat }}' })]] })
141
+ button({ content: 'Clear', flows: [[onClick(), declaredCallback(declaration, 'reset', { on: 'seats' })]] })
142
+ authorSpace(space, { plugins: [declaration] })
143
+
144
+ // Usable without sight: an icon-only button says what it does; a clickable card IS a button; a field keeps its label
145
+ // out of sight rather than dropping it; a picture says what it shows, or that it only decorates.
146
+ button({ content: '', title: 'Close', children: [fontAwesome({ icon: 'fa-solid fa-xmark' })], flows: [[onClick(), closeModal('details')]] })
147
+ button({ content: '', class: card, children: [text('Pro'), text('12 € a month')], flows: [[onClick(), setState({ key: 'plan', type: 'text', value: 'pro' })]] })
148
+ formControl({ name: 'q', label: 'Search the docs', hideLabel: true, placeholder: 'Search…' })
149
+ image({ src: '/team.jpg', alt: 'The team at the 2026 offsite' }); image({ src: '/grain.png', decorative: true })
150
+
151
+ // A link: to a page by its id, to a path with mode 'internal', to anything else with mode 'external'.
152
+ link({ href: 'about' }); link({ href: '/games/nebula', mode: 'internal' }); link({ href: 'mailto:hi@x.com', mode: 'external' })
55
153
  ```
56
154
 
57
- | Authoring field | What it does |
58
- | --- | --- |
59
- | `id` | the ONE name this element answers to — its key in the document, a binding's source, a step's target. **Name anything something else refers to**: a derived `<type>-<n>` is positional |
60
- | `class` | a shared class: a name from the space's `classes`, or a `styles()` declaration. Exclusive with `css` |
61
- | `css` | rules of this element's own: one set, or one per breakpoint (`{ desktop, tablet, mobile }`) |
62
- | `variant` | a style variant of the element's own vocabulary |
63
- | `slots` | a class for one of the element's OTHER selectors — a form control's `input`, `label`, `error` |
64
- | `bind` | where a value comes from |
65
- | `visible` | show it only while this source is true; `!source` for the inverse |
66
- | `flows` | what happens on click, on submit, on load |
67
- | `runtime` | `'server'` resolves this element's data on the server |
68
- | `children` | the tree |
69
- | `meta` | `meta.label` is the element's name in the builder's tree |
70
-
71
- Anything else in the object is an **attribute**. `label` belongs to the attribute (a link and a form control both
72
- have one); the builder's name for the element is `meta.label`.
73
-
74
- A type this SDK does not ship — a plugin, one the deployment brings — is authored the same way:
75
-
76
- ```ts
77
- const speciesStatus = defineElement<{ status?: string }>({ type: 'speciesStatus', content: { … } });
78
- element<{ status?: string }>('speciesStatus', { status: 'vulnerable' }); // or one at a time
79
- elementsFromManifest(manifest); // or a whole plugin manifest
80
- ```
81
-
82
- ## Style
83
-
84
- Write CSS the way you write CSS, shorthands included — they are expanded before they reach the document, because
85
- Plitzi's style editor reads a closed list of longhand properties:
86
-
87
- ```ts
88
- css({ padding: '96px 24px', border: '1px solid var(--line)', 'border-radius': '12px' })
89
- ```
90
-
91
- You rarely call `css` yourself: `authorSpace` runs every rule set through it. What you get is the refusal — a
92
- property outside the vocabulary is an error naming the correct key (`paddingTop` → `padding-top`).
93
-
94
- - `column(gap, extra?)`, `row(gap, extra?)`, `grid(columns, gap, extra?)` for the three layouts every space repeats.
95
- - **Breakpoints are ranges, not a cascade.** `tablet` is 48–64rem and `mobile` is below 48rem, and each inherits
96
- only from `desktop` — a rule written for `tablet` never reaches a phone. A grid that collapses to one column at
97
- tablet and should stay collapsed says so in `mobile` too. `authorSpace` warns (`tablet-rule-skips-mobile`) when it
98
- does not.
99
- - **Fonts are declared, not assumed.** A `font-family` loads only if the space lists the face in `fonts`
100
- (`{ source: 'google', family: 'Fraunces', fallback: 'Georgia, serif', weights: [400, 600], styles: ['normal'] }`);
101
- the page server writes exactly that list into the document, and anything else renders in the fallback.
102
- - Per element TYPE defaults go in `elements: { heading: { base: …, states: { … }, variants: { … }, slots: { … } } }`
103
- — `slots` dresses the type's other selectors (a modal's `rootContainer`) for every element of the type.
104
- - **`:hover` is part of the selector, not a rule in `customCss`.** `styles('card', { css: { … }, states: { hover: { … } },
105
- variants: { active: { … } } })`, and `states` beside an element's own `css`. Written in `customCss` it renders and
106
- then cannot be read back or edited per breakpoint.
107
-
108
- **Share a rule as a class, never as a spread.** Writing a rule set once in a `const` and spreading it into each
109
- element's `css` shares the source and duplicates the document — one selector per element, so re-theming the card
110
- in the builder re-themes one card. `styles()` declares the class where it is used and writes it once:
111
-
112
- ```ts
113
- const card = styles('card', { padding: '24px', 'border-radius': '12px', 'background-color': 'var(--surface)' });
114
-
115
- container({ class: card, children: [ … ] });
116
- ```
117
-
118
- Accepted anywhere a class name is — `class`, a `slot`, a page's `class` — and collected from wherever the tree
119
- names it. One name declared twice with rules that disagree is refused. `classes` at the top of the space is the
120
- same mechanism for what describes the space rather than one section of it.
121
-
122
- An element has exactly ONE base selector, so a look that never changes is its own class over a shared plain object
123
- (`styles('button-primary', { ...base, … })`), never two classes layered on top of each other. A class's `variants`
124
- are for what the page switches while it runs — a binding with the `styleVariant` transformer picks one.
125
-
126
- **Shells go in `layouts`.** A header or a sidebar every page shows is a layout the pages name —
127
- `layout: { id: 'app-shell', slot: 'main' }`, the slot being the element in the shell where the body goes — never a
128
- copy on each page. `visible: false` starts an element hidden for a flow to reveal.
129
-
130
- ## Data
131
-
132
- ```ts
133
- heading({ bind: { content: 'posts.title' } }) // short form: attributes
134
- paragraph({ visible: 'posts.hasPosts' }) // on screen only while this is true
135
- paragraph({ visible: '!posts.hasPosts' }) // …and its inverse
136
- container({ bind: [{ to: 'content', source: 'x.y', transformers: [ … ] }] }) // full form
137
- ```
138
-
139
- **A source names the id you gave the element** — `'posts.title'`, `'postList.item.cover'` — and the prefix is
140
- filled in. Only half of a source name is yours: the other half is the kind of source the ELEMENT publishes, and it
141
- is not always the word you can see (a `form` publishes under `apiContainer`). The four globals — `variables`,
142
- `navigation`, `auth`, `state` — are named as themselves. A full name still works and is checked the same way: a
143
- prefix that does not match the element, or a name nothing answers to, is refused when the space is authored. It
144
- is the quietest failure a space can carry.
145
-
146
- Server-resolved sections are an `apiContainer` with `runtime: 'server'` naming a connector or an action, and a
147
- space that has any needs `rsc: { enabled: true }`.
148
-
149
- **A provider has no box unless you give it a tag.** An `apiContainer` renders no element of its own while its
150
- `subType` is empty — the default, the builder's "Container Tag: None" — so its children sit directly in the parent's
151
- layout. That is usually what you want (the provider stays invisible to the layout), and it means a `class` or `css`
152
- on it styles nothing: the `gap` you wrote between its sections is simply not there. Put the layout on the parent, or
153
- give the provider a tag (`attributes: { subType: 'div' }`) when it should be the box. `validateSpace` — and so
154
- `authorSpace` and the MCP — warns with `STYLE_WITHOUT_TAG`.
155
-
156
- **Never ask the data for a field's opposite.** Both sides of one question are `visible: 'x'` and `visible: '!x'` —
157
- not an `x` and a `notX` beside it in the server's answer. The `!` is the `not` transformer, which reads a boolean
158
- that travelled as text (`"false"`, `"0"`) and treats an empty array as false; an empty object is true. Only for a
159
- real inverse — a three-state condition (`Boolean(post) && !canEdit`) still belongs where the data is made.
155
+ ## References
160
156
 
161
- ## Flows
162
-
163
- ```ts
164
- button({
165
- id: 'cta',
166
- content: 'Get a quote',
167
- flows: [[
168
- onClick(),
169
- named('quote', runServerAction({ actionId: 'shipping-quote', input: '{"city":"Berlin"}', mode: 'await' })),
170
- setState({ key: 'quote', type: 'text', value: '{{quote.output.summary}}' })
171
- ]]
172
- })
173
- ```
174
-
175
- Use the step builders — they answer the three things that go wrong silently:
176
-
177
- - **Where a step runs.** A global callback registers under its source MODULE (`state`, `auth`, `actions`), an
178
- element callback under an element's id, a utility under nothing. The builders fill it in; a trigger and an
179
- untargeted `updateElement` are filled with the element the flow was declared on.
180
- - **Which `setState`.** `setState(…)` writes `runtime.state.<key>`; `updateElement(…)` changes one element's own
181
- attribute or state. Different params, different node kind. Each has a flip-it counterpart storing the opposite
182
- of what is there, which is how expand/collapse is ONE step on ONE trigger: `toggleState({ key })` for app state,
183
- and `toggleElement({ category: 'state', key: 'visibility' }, 'panel')` to show/hide an element (the second
184
- argument is the element it acts on; omitted, it acts on the one the flow is declared on). Never two branches
185
- under opposite `when` guards — those read the state as it was when the flow STARTED, so they run a click behind.
186
- - **What it takes.** Params are typed from the same declaration the builder's own panel is drawn from.
187
-
188
- `named(id, step)` is how a later step reads an earlier one — the flow scope is keyed by node id, so
189
- `{{quote.output.summary}}` resolves only when that step is called `quote`. `mode: 'await'` is what puts a run's
190
- answer in the scope at all, and it is the default.
191
-
192
- **Pass `input` as an object, not as a line of JSON text.** Both are accepted, and the text form fails silently: an
193
- interpolated value carrying a quotation mark or a newline — a post body, a comment — makes it unparseable, and
194
- unparseable input posts `{}` rather than refusing.
195
-
196
- A flow is a list and never a tree, so "only if" is expressed on the step:
197
-
198
- ```ts
199
- whenSucceeded('quote', navigate({ urlType: 'internal', url: '{{quote.output.url}}' })),
200
- whenFailed('quote', setState({ key: 'notice', type: 'text', value: '{{quote.reason}}' })),
201
- when({ field: 'state.count', operator: '>', value: 3 }, addNotification({ content: 'Enough' }))
202
- ```
203
-
204
- `whenFailed` matches every outcome that is not `completed` — a run also comes back `skipped` or `aborted`, and
205
- matching only `failed` is how those two end up doing nothing.
206
-
207
- ## What gets refused
208
-
209
- `authorSpace` throws rather than hand back a space that would not render:
210
-
211
- - a CSS property the style editor could not read back
212
- - a `class` or a `slot` naming a class the space does not declare (the error names the one you probably meant)
213
- - an element asking for a shared class AND rules of its own — an element has one base selector
214
- - one class name declared twice with rules that disagree
215
- - a binding source naming an element nothing answers to, or one whose prefix is not what that element publishes
216
- - a name that shadows a global data source (`variables`, `navigation`, `auth`, `state`)
217
- - a step target naming an element that is not there
218
- - two elements answering to one name, a broken flow chain, an orphan, a cycle
219
- - a global callback on the wrong module — or on none — and a utility given one. A global callback registers under
220
- its SOURCE MODULE (`auth`, `state`, `actions`), and the pair is what the runtime resolves a step by, so naming
221
- either half wrong is a control that does nothing at all with no error anywhere. An action no built-in source
222
- declares comes back in `warnings` instead of throwing, because a plugin may register a module of its own.
223
-
224
- Those last checks need the vocabulary, which only the composed package has. Import `authorSpace` from
225
- `@plitzi/sdk-authoring` — not straight from `@plitzi/sdk-schema` — and you get them.
226
-
227
- For documents you did not author here — an export from the builder, a JSON edited by hand — run the same gate
228
- before serving it:
229
-
230
- ```ts
231
- const { valid, errors, warnings } = validateSpace({ schema, style });
232
- ```
233
-
234
- ## Templates
235
-
236
- A template is not a space: it is ONE subtree, the style that dresses it and a name, published as a JSON someone
237
- fetches by URL and drags onto a canvas you will never see.
238
-
239
- ```ts
240
- import { authorTemplate } from '@plitzi/sdk-authoring';
241
-
242
- const { template, warnings } = authorTemplate({
243
- name: 'Pricing card',
244
- description: 'A price, a list of features and a call to action.',
245
- classes: { card: { padding: '24px', 'border-radius': '12px', 'background-color': 'var(--surface)' } },
246
- root: container({ class: 'card', children: [heading('$19', { subType: 'h3' }), button({ content: 'Start' })] })
247
- });
248
-
249
- await writeFile('pricing-card.json', JSON.stringify(template, null, 2));
250
- ```
251
-
252
- Host that file and add it to a space's resources as an `application/json` — nothing else is required of you.
253
-
254
- Everything a space is held to still applies, and two more things apply because a template LEAVES its space:
255
-
256
- - **Everything it names, it carries.** A class declared in the space it was cut from does not travel; the element
257
- keeps the class name, finds no rules wherever it is dropped, and renders unstyled. Declare in `classes` (or with
258
- `styles()`) every rule the subtree names — `validateTemplate` warns about a name the manifest does not carry.
259
- - **A binding may not point outside the subtree.** A source names an element by id, so binding to a provider
260
- that stayed behind is dead on arrival — refused. Bring the provider (the `apiContainer`, the `form`) into the
261
- template, or bind to one of the globals: `variables`, `navigation`, `auth`, `state`.
262
-
263
- `validateTemplate(template)` runs the same gate over a manifest you did not author here — one exported by the
264
- builder, or edited by hand — before you publish it.
265
-
266
- ## From an exported JSON
267
-
268
- Never rewrite an exported space by hand. `specFromSpace({ schema, style })` reads it into the spec that authors it,
269
- repairing what an older builder left behind and listing each repair in `corrections`; `specToSource(spec,
270
- { exportName })` writes that spec as factory calls; `compareSpaces(original, authorSpace(spec))` must list nothing but
271
- what `corrections` explains. The builder's **Export** (and `POST /utils/transform-to-authoring`) does all three. Edit
272
- the code it writes from then on, not the JSON.
273
-
274
- ## Rules
275
-
276
- 1. **Never hand-write `flat`, element ids, `styleSelectors`, `beforeNode`/`afterNode`/`flowId`.** They are derived.
277
- Writing them is how a space half-renders with nothing reporting why.
278
- 2. **Name what is referred to.** `id` on any element a binding, a step or a pager addresses.
279
- 3. **Read the error.** Every refusal above names the element, the class or the property at fault; it is a bug in
280
- the declaration, not a reason to work around the check.
281
- 4. **Ids are stable.** They are hashes of the path that produced them, so re-authoring an unchanged space writes
282
- byte-identical documents — a seed can re-run and a diff stays readable.
283
- 5. **`warnings` is returned, not printed.** Read it if the space is generated in a build.
157
+ | Read | When |
158
+ | --- | --- |
159
+ | [elements-and-styles.md](reference/elements-and-styles.md) | Any element or CSS: factories, fields, classes, states, variants, tokens, fonts, lists, links |
160
+ | [layouts.md](reference/layouts.md) | Anything shown on more than one page; menus; reducing duplication of elements and styles |
161
+ | [data-and-visibility.md](reference/data-and-visibility.md) | Bindings, providers, offline data, loading/empty/error states, live data, caching, showing and hiding, kept state |
162
+ | [lists.md](reference/lists.md) | Rendering rows, filtering and sorting them, a detail page for one record |
163
+ | [validation.md](reference/validation.md) | How `authorSpace` checks, the loop that wastes no attempts, and what it cannot see |
164
+ | [authoring-errors.md](reference/authoring-errors.md) | What `authorSpace` refuses or warns about, and what to write instead |
165
+ | [templates.md](reference/templates.md) | Any `{{ … }}` or `{% … %}`: where it runs, naming sources, filters, tests, dates |
166
+ | [flows.md](reference/flows.md) | Clicks, submits, page loads, server actions, realtime channels, modals, state |
167
+ | [plugins.md](reference/plugins.md) | A component of your own: props, binding them, writing state, channels, registering |
168
+ | [structure.md](reference/structure.md) | A space bigger than one screen: files, helpers, naming, keeping it short |
169
+ | [testing.md](reference/testing.md) | Any test: `inspectPage` (one call, every problem), handles, fixtures, catching a flash from the first frame, shortcuts, counting renders |
170
+ | [performance.md](reference/performance.md) | A page with many elements, a busy flow, something that feels slow: what renders, what it costs, how to measure it |
171
+ | [templates-and-export.md](reference/templates-and-export.md) | Publishing a template; turning an exported JSON into code |
172
+ | [accessibility.md](reference/accessibility.md) | Icon buttons, fields, images, clickable cards, toggles, headings, landmarks, a canvas — anything a screen reader or a browser agent has to use |
173
+ | [review-checklist.md](reference/review-checklist.md) | Before you say it is done |
@@ -0,0 +1,111 @@
1
+ # Usable without sight
2
+
3
+ A screen reader and a browser agent (Claude in Chrome) read a page the same way. They use the **accessibility
4
+ tree** the browser builds from the markup, where every control is a role and a name. If a control has no name, or a
5
+ click sits on a box that is not a control, those readers can't find it, even though the page looks fine. The linter
6
+ warns about each case below (`control-without-name`, `image-without-alt`, `click-on-static-element`,
7
+ `heading-level-skipped`, `label-ignored`). Fix them the same way you fix any other warning.
8
+
9
+ ## Controls have words
10
+
11
+ ```ts
12
+ // Words of its own: nothing to add.
13
+ button({ content: 'Save' })
14
+
15
+ // An icon only: `title` is its name, and also its tooltip. The icon is hidden from the tree by itself.
16
+ button({ content: '', title: 'Close', children: [fontAwesome({ icon: 'fa-solid fa-xmark' })] })
17
+
18
+ // Words that do not say what it does — a key hint, a count: `label` is read in their place.
19
+ button({ content: '', label: 'Select (V)', title: 'Select — V', children: [fontAwesome({ icon: 'fa-solid fa-arrow-pointer' }), text('V')] })
20
+
21
+ // A link around a whole card: without `label` it is announced as every word inside the card.
22
+ link({ href: '/posts/field-notes', mode: 'internal', label: 'Read “Field notes”', children: [ /* cover, topic, title, byline */ ] })
23
+
24
+ // A field whose design says what it is: the label stays, out of sight.
25
+ formControl({ name: 'q', label: 'Search the docs', hideLabel: true, placeholder: 'Search…' })
26
+ formControl({ name: 'tint', subType: 'color', label: 'Highlight colour', hideLabel: true })
27
+ ```
28
+
29
+ A `placeholder` names only a field you type into, and only until something is typed. A swatch, a date, a select or a
30
+ checkbox always needs its `label`.
31
+
32
+ ## Pictures say what they show, or that they only decorate
33
+
34
+ ```ts
35
+ image({ src: '/team.jpg', alt: 'The team at the 2026 offsite' }) // what it shows, for this page
36
+ image({ src: '/grain.png', decorative: true }) // an ornament: left out of the tree
37
+ image({ decorative: true, bind: { src: 'post.record.cover' } }) // a cover under its own headline adds nothing
38
+ image({ bind: { src: 'row.item.cover', alt: 'row.item.title' } }) // …unless it is alone in a link: then it names it
39
+ fontAwesome({ icon: 'fa-solid fa-triangle-exclamation', label: 'Overdue' }) // an icon that says something alone
40
+ ```
41
+
42
+ ## Clicks go on controls
43
+
44
+ A `button` holds children, so a clickable card is a button with the card's class:
45
+
46
+ ```ts
47
+ button({
48
+ content: '',
49
+ class: planCard,
50
+ children: [text('Pro', { class: planName }), text('12 € a month', { class: planPrice })],
51
+ flows: [[onClick(), setState({ key: 'plan', type: 'text', value: 'pro' })]]
52
+ })
53
+ ```
54
+
55
+ Never put a click on a `container`, `text`, `image` or list item. A keyboard can't reach it, and a browser agent
56
+ won't find it among the page's controls. A button's children are its name, so keep headings, links and other
57
+ buttons out of it. If a card needs any of those, make its title a link and let the rest of the card be plain
58
+ content. An empty backdrop that closes a panel is fine, because the panel's close button and Escape are the ways
59
+ everyone has.
60
+
61
+ A filter chip, a tab you built yourself, a row that selects: all of them are buttons. Remember that a class starts
62
+ with the SDK button look (padding, radius), so reset what you don't want.
63
+
64
+ A dropdown opens from a button too: put one among its children, outside the popup. The dropdown marks it
65
+ (`aria-haspopup`, `aria-expanded`) and moves the focus into the menu and back out.
66
+
67
+ ```ts
68
+ dropdown({ children: [dropdownPopup({ children: [ /* menu entries: buttons or links */ ] }), button({ content: '', title: 'Account', children: [avatar] })] })
69
+ ```
70
+
71
+ ## State is said, not only styled
72
+
73
+ ```ts
74
+ button({ content: 'Grid', bind: { ariaPressed: 'computed.gridOn' }, class: chip }) // a toggle
75
+ button({ content: 'Menu', bind: { ariaExpanded: 'state.menuOpen' }, flows: [[onClick(), toggleState({ key: 'menuOpen' })]] })
76
+ ```
77
+
78
+ An "active" variant is for the eyes. `ariaPressed` and `ariaExpanded` are for everyone else. Use both.
79
+
80
+ ## The page has an outline
81
+
82
+ - Headings go down one level at a time: h1, then h2, then h3. The class sets the size, so an `h2` can look small.
83
+ - Landmarks are containers with a tag: `header`, `nav`, `main`, `aside`, `footer`. When a page has two of one kind,
84
+ give each a `label`: `container({ subType: 'nav', label: 'Docs sections' })`. A `section` with a `label` becomes a
85
+ region an assistant can jump to.
86
+
87
+ ## Illustrations
88
+
89
+ A mock of a page, a diagram, some art built from elements: `container({ decorative: true, children: [ … ] })`. The
90
+ readers skip it, and the linter asks nothing of what is inside, except that nothing in it can be tabbed to
91
+ (`control-in-decorative`). Whatever it lets a mouse do needs a real control somewhere else.
92
+
93
+ ## Built in, nothing to add
94
+
95
+ Modals and dialogs (named by their title; they take the focus, keep Tab inside and close on Escape), tabs (arrow
96
+ keys), a field's error (marked invalid and described by its message), the pager's current page, and the theme
97
+ toggle's pressed option.
98
+
99
+ ## A canvas or a plugin
100
+
101
+ Anything drawn on a canvas is one picture to these readers. Render what it shows as elements too: a list with a
102
+ named button per action. Keep that list visible when someone asks for it, because an agent clicks by position and
103
+ can't click something that only appears on focus. See `docs/en/accessibility.md` and Pizarra's List view.
104
+
105
+ ## Checking
106
+
107
+ - `authorSpace` warnings: zero, as always.
108
+ - In a test, `page.getByRole('button', { name: 'Close' })` fails when the name is missing.
109
+ `expect(locator).toMatchAriaSnapshot(…)` pins the whole outline.
110
+ - Through MCP, `plitzi_screenshot` with `view: "accessibility"` returns the page's tree and lists everything that has
111
+ no name.
@@ -0,0 +1,70 @@
1
+ # When `authorSpace` refuses
2
+
3
+ `authorSpace` checks the whole declaration before it writes anything, and stops at the first thing that would not
4
+ render as written. The message names the element (`Element "text" (price) at shop/home/0/2`) and what to write
5
+ instead. **Do what the message says.** Never cast past a check, silence it, or move the logic into a plugin to avoid
6
+ it — the check exists because that declaration renders something other than what it says.
7
+
8
+ The one exception is a TEST fixture whose subject is the break itself — how the runtime copes with a document no author
9
+ would write. It names that break, and only that one: `authorSpace(spec, { allow: [{ code, element, why }] })`. See
10
+ [testing](testing.md#spaces-written-for-a-test). A space anybody visits never has an `allow`.
11
+
12
+ ## Refused
13
+
14
+ | The message says | What was wrong | Write instead |
15
+ | --- | --- | --- |
16
+ | `has "x", which it does not take` | a field the space, a page, an element, a binding or a step does not have | the field it suggests; an element's attribute goes inside the factory's props (`button({ content })`) |
17
+ | `sets "x", which a "button" never reads` | an attribute the element does not have | one it lists; an event (`onClick`) is a flow: `flows: [[onClick(), …]]` |
18
+ | `` `subType` is "h7" … It is one of `` | a value outside the attribute's list | one of the listed values |
19
+ | `reads it as true or false` / `as a list` | text where a flag or a list is read | `disabled: true`, not `'true'`; `items: [ … ]` |
20
+ | `holds none` | children on a type that renders only its own attributes | a `container` (for a heading made of parts, `container({ subType: 'h1', children })`) |
21
+ | `the template … cannot be read as written` | a template with an operator, filter or function that does not exist, or broken syntax | see [templates](templates.md) for what exists; `matches` never does |
22
+ | `reads "x", which nothing here answers to` | a name in a template that is not a source, a variable or a route param | the full source name (`list_rows.item`), `navigation.queryParams.x` for a query param, `source` for the bound value |
23
+ | `is not around it` | a source read by an element that is not inside the element publishing it | move the element inside it, or share the value through `state` |
24
+ | `lands on "x", which a "text" never reads` | a binding onto an attribute the element does not have — the value arrives and nothing shows it | one it lists (`content`); to follow data with a class, bind `styleSelectors.base` in `initialState` |
25
+ | `no element answers to the name "x"` | a binding whose source names an element that does not exist | the id of the element that publishes it |
26
+ | `renders text — and "items" holds a list` | a text template feeding a list | `bindTemplate('items', src, '{{ … }}', { returns: 'value' })` |
27
+ | `which a "x" never fires` / `never answers to it` | a flow on an event, or a step to an action, the element does not have | the element that fires it (the message names it); for a plugin, a name its declaration has — `declaredTrigger` / `declaredCallback` make a wrong one a compile error |
28
+ | `does not start with its trigger` | a flow whose first step is not the event that runs it | `[onClick(), setState(…)]` |
29
+ | `step "x" has the param "y"` / `"type" is "string"` | a step param that does not exist or a value outside its options | the params and values it lists |
30
+ | `step "setState" sets "x" on "y", which a "z" never reads` | an element `setState`/`toggleState` writing a field the element does not have | an attribute it lists, or for `category: 'state'` `visibility` / `styleSelectors.<selector>` |
31
+ | `names the page "x", and no page has that id` | a link or `navigate` to a page id that does not exist | an existing page id, or a path with its slash (`'/about'`) |
32
+ | `a full URL, in page mode` | a URL, `mailto:` or `tel:` in a link left in page mode | `mode: 'external'` |
33
+ | `is a controlled list with no items` | a list with nothing to render | `items: [ … ]` or `bind: { items: 'provider.data.rows' }` |
34
+ | `` has items, but its `source` is "none" `` | a list whose items nothing reads — it renders its children once | `source: 'controlled'` |
35
+ | `answers at /x for the same visitors as page` | two pages at one address | another slug — or `accessLevel` `'public'` on one and `'authenticated'` on the other |
36
+ | `is not one CSS value` | an empty CSS value, or one with `;` or `{}` | one value per property; leave a property out instead of writing it empty |
37
+ | `computed … declared after it` / `does not compute` | a computed value read before it is declared, or never declared | declare it in `computed`, above the one that reads it |
38
+
39
+ ## Warned
40
+
41
+ A warning means the space renders, and renders something you probably did not mean. Fix every one.
42
+
43
+ | Code | Meaning | Fix |
44
+ | --- | --- | --- |
45
+ | `condition-starts-visible` | a computed visibility shows until its data answers | `visible: { source, template }` |
46
+ | `template-never-resolved` | a condition in an attribute, used as written | move it into `bindTemplate` |
47
+ | `tablet-rule-skips-mobile` | a tablet rule phones never get | write it under `compact` |
48
+ | `default-content-beside-children` | a button prints "Button" beside its children | `content: ''` |
49
+ | `overlay-starts-open` | a modal or dialog is open when the page loads | `visible: false`, opened by `openModal` |
50
+ | `provider-without-source` | an `apiContainer` asks nothing | give it a `query` (or `action`, `connector`, `resource`) |
51
+ | `unknown-element-type` | a type no built-in element has | the built-in it suggests; a plugin's DECLARATION goes in `authorSpace(space, { plugins: [declaration] })`, which also checks its events, actions and attributes |
52
+ | `colour-without-dark` | a colour token with no dark value | `{ light, dark, default }` |
53
+ | `FORM_SUBMIT_UNMANAGED` | a form the browser would submit itself | `managedByInteractions: true` |
54
+ | `STYLE_WITHOUT_TAG` | style on a provider that renders no element | `subType: 'div'` on the provider, or style its parent |
55
+ | `server-data-without-rsc` | a `runtime: 'server'` provider with a `connector` or `action` in a space that does not turn server data on — it renders its mock data | `rsc: { enabled: true }` on the space |
56
+ | `route-param-undeclared` | `navigation.routeParams.x` read on a page whose slug has no `:x` — always empty | add `:x` to the slug, or read `navigation.queryParams.x` |
57
+ | `form-control-unnamed` | a control in a form with no `name` — its value never reaches `values` | `formControl({ name: 'email', … })` |
58
+ | `form-control-name-taken` | two controls in one form with one name — one overwrites the other | a name each |
59
+ | `while-running` | `whileRunning` on a step that is not the trigger, or a value other than `skip`, `parallel`, `queue` | `[whileRunning('queue', onClick()), …]` |
60
+ | `trigger-keys` | an `onKey` flow whose `keys` cannot fire: two keys in one shortcut, only modifiers, or a name that is not a key | `onKey('f')`, `onKey('shift+f')`, `onKey('mod+k, escape')` |
61
+ | `state-toggled-in-branches` | two `setState` steps of one key, each under a `when` on that key — the second flips back what the first wrote | `toggleState({ key })`; for something shown by default, a key named for hiding it |
62
+ | `form-value-compared-to-blank` | a `when` asking whether a submitted field (`….values.x`) `=` or `!=` `""` — a field nobody typed in is not sent, so it never matches | `operator: 'empty'` / `'notEmpty'` |
63
+ | `overlay-never-opened` | a modal or dialog that starts hidden and that no step opens | a flow with `openModal('id')` / `openDialog('id')` |
64
+ | `control-without-name` | a button or link with no words (an icon, a picture without `alt`, nothing), or a field nothing names — a screen reader says just "button", a browser agent cannot find it | `title` on the button, `label` on the link, `label` (with `hideLabel: true`) on the field — see [accessibility](accessibility.md) |
65
+ | `image-without-alt` | an image that is not `decorative` and has no `alt` | say what it shows, or `decorative: true` |
66
+ | `click-on-static-element` | a click flow on a container, text, heading, image or list item — no keyboard reaches it, no agent finds it | the flow on a `button` (it holds children) or a `link` |
67
+ | `heading-level-skipped` | an `h4` right after an `h2` on a page — the outline misses a level | the next level down; size it with its class |
68
+ | `label-ignored` | a `label` on a container whose tag is named by what it holds (`li`, a heading) | the words inside, or a landmark tag (`nav`, `section`, …) |
69
+ | `dropdown-without-control` | a dropdown opened from a box or an icon — no keyboard opens it | a `button` as what opens it (`title` if it is only an icon) |
70
+ | `control-in-decorative` | a control inside a `decorative` container — Tab lands on something nothing announces | move it out of the illustration |