@plitzi/sdk-dev-tools 0.37.9 → 0.38.1

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 (190) hide show
  1. package/CHANGELOG.md +621 -0
  2. package/dist/DevToolsContainer.d.ts +7 -1
  3. package/dist/DevToolsContainer.mjs +66 -60
  4. package/dist/agentInspector/flowRuns.d.ts +22 -0
  5. package/dist/agentInspector/flowRuns.mjs +26 -0
  6. package/dist/agentInspector/inspector.d.ts +27 -0
  7. package/dist/agentInspector/inspector.mjs +20 -0
  8. package/dist/agentInspector/report.d.ts +36 -0
  9. package/dist/agentInspector/report.mjs +53 -0
  10. package/dist/agentInspector/useAgentInspector.d.ts +15 -0
  11. package/dist/agentInspector/useAgentInspector.mjs +36 -0
  12. package/dist/components/DevToolsOverlay/DevToolsOverlay.d.ts +6 -1
  13. package/dist/components/DevToolsOverlay/DevToolsOverlay.mjs +25 -15
  14. package/dist/components/DevToolsPanel/DevToolsBody.d.ts +3 -1
  15. package/dist/components/DevToolsPanel/DevToolsBody.mjs +35 -31
  16. package/dist/components/DevToolsPanel/DevToolsHeader.d.ts +3 -1
  17. package/dist/components/DevToolsPanel/DevToolsHeader.mjs +20 -10
  18. package/dist/components/DevToolsPanel/DevToolsPanel.d.ts +3 -1
  19. package/dist/components/DevToolsPanel/DevToolsPanel.mjs +25 -23
  20. package/dist/components/DevToolsPanel/DevToolsSubHeader.mjs +41 -45
  21. package/dist/components/DevToolsPanel/tabs/ActionsViewer/ActionsViewer.mjs +10 -10
  22. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementsViewer.d.ts +4 -0
  23. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementsViewer.mjs +33 -18
  24. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsAttributes/DetailsAttributes.mjs +16 -0
  25. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsAttributes/index.d.ts +3 -0
  26. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsAttributes/index.mjs +5 -0
  27. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsDefinition/DetailsDefinition.mjs +18 -0
  28. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsDefinition/index.d.ts +3 -0
  29. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsDefinition/index.mjs +5 -0
  30. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRow/DetailsRow.d.ts +8 -0
  31. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRow/DetailsRow.mjs +14 -0
  32. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRow/index.d.ts +3 -0
  33. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRow/index.mjs +5 -0
  34. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRuntime/DetailsRuntime.d.ts +9 -0
  35. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRuntime/DetailsRuntime.mjs +54 -0
  36. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRuntime/index.d.ts +3 -0
  37. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsRuntime/index.mjs +5 -0
  38. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsValue/DetailsValue.d.ts +9 -0
  39. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsValue/DetailsValue.mjs +15 -0
  40. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsValue/index.d.ts +3 -0
  41. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/DetailsValue/index.mjs +5 -0
  42. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementDetails/ElementDetails.d.ts +7 -0
  43. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementDetails/ElementDetails.mjs +46 -0
  44. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementDetails/index.d.ts +3 -0
  45. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementDetails/index.mjs +5 -0
  46. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementLink/ElementLink.d.ts +7 -0
  47. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementLink/ElementLink.mjs +14 -0
  48. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementLink/index.d.ts +3 -0
  49. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementLink/index.mjs +5 -0
  50. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementsTree/ElementsTree.d.ts +12 -0
  51. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementsTree/ElementsTree.mjs +39 -0
  52. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementsTree/index.d.ts +3 -0
  53. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/ElementsTree/index.mjs +5 -0
  54. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeRow/TreeRow.d.ts +10 -0
  55. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeRow/TreeRow.mjs +36 -0
  56. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeRow/index.d.ts +3 -0
  57. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeRow/index.mjs +5 -0
  58. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeSection/TreeSection.d.ts +13 -0
  59. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeSection/TreeSection.mjs +39 -0
  60. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeSection/index.d.ts +3 -0
  61. package/dist/components/DevToolsPanel/tabs/ElementsViewer/components/TreeSection/index.mjs +5 -0
  62. package/dist/components/DevToolsPanel/tabs/ElementsViewer/helpers/renderTree.d.ts +31 -0
  63. package/dist/components/DevToolsPanel/tabs/ElementsViewer/helpers/renderTree.mjs +61 -0
  64. package/dist/components/DevToolsPanel/tabs/FlagsViewer/FlagSource.d.ts +7 -0
  65. package/dist/components/DevToolsPanel/tabs/FlagsViewer/FlagSource.mjs +23 -0
  66. package/dist/components/DevToolsPanel/tabs/FlagsViewer/FlagsListItem.d.ts +11 -0
  67. package/dist/components/DevToolsPanel/tabs/FlagsViewer/FlagsListItem.mjs +66 -0
  68. package/dist/components/DevToolsPanel/tabs/FlagsViewer/FlagsViewer.d.ts +10 -0
  69. package/dist/components/DevToolsPanel/tabs/FlagsViewer/FlagsViewer.mjs +43 -0
  70. package/dist/components/DevToolsPanel/tabs/FlagsViewer/index.d.ts +3 -0
  71. package/dist/components/DevToolsPanel/tabs/FlagsViewer/index.mjs +5 -0
  72. package/dist/components/DevToolsPanel/tabs/Logs/Log.d.ts +3 -8
  73. package/dist/components/DevToolsPanel/tabs/Logs/Log.mjs +42 -31
  74. package/dist/components/DevToolsPanel/tabs/Logs/Logs.mjs +1 -6
  75. package/dist/components/DevToolsPanel/tabs/Logs/categories/LogInteraction/BodyHeader.mjs +53 -54
  76. package/dist/components/DevToolsPanel/tabs/Logs/categories/LogInteraction/LogInteraction.mjs +17 -17
  77. package/dist/components/DevToolsPanel/tabs/Logs/categories/LogNavigation/LogNavigationBody.mjs +26 -26
  78. package/dist/components/DevToolsPanel/tabs/Logs/categories/LogRealtime/LogRealtime.d.ts +10 -0
  79. package/dist/components/DevToolsPanel/tabs/Logs/categories/LogRealtime/LogRealtime.mjs +58 -0
  80. package/dist/components/DevToolsPanel/tabs/Logs/categories/LogRealtime/index.d.ts +3 -0
  81. package/dist/components/DevToolsPanel/tabs/Logs/categories/LogRealtime/index.mjs +5 -0
  82. package/dist/components/DevToolsPanel/tabs/QaViewer/QaAllChecksButton.d.ts +3 -0
  83. package/dist/components/DevToolsPanel/tabs/QaViewer/QaAllChecksButton.mjs +16 -0
  84. package/dist/components/DevToolsPanel/tabs/QaViewer/QaCheckRow.d.ts +7 -0
  85. package/dist/components/DevToolsPanel/tabs/QaViewer/QaCheckRow.mjs +47 -0
  86. package/dist/components/DevToolsPanel/tabs/QaViewer/QaChecks.d.ts +3 -0
  87. package/dist/components/DevToolsPanel/tabs/QaViewer/QaChecks.mjs +24 -0
  88. package/dist/components/DevToolsPanel/tabs/QaViewer/QaDivider.d.ts +3 -0
  89. package/dist/components/DevToolsPanel/tabs/QaViewer/QaDivider.mjs +5 -0
  90. package/dist/components/DevToolsPanel/tabs/QaViewer/QaFindingItem.d.ts +7 -0
  91. package/dist/components/DevToolsPanel/tabs/QaViewer/QaFindingItem.mjs +32 -0
  92. package/dist/components/DevToolsPanel/tabs/QaViewer/QaInspectorDetails.d.ts +9 -0
  93. package/dist/components/DevToolsPanel/tabs/QaViewer/QaInspectorDetails.mjs +135 -0
  94. package/dist/components/DevToolsPanel/tabs/QaViewer/QaInspectorEmpty.d.ts +3 -0
  95. package/dist/components/DevToolsPanel/tabs/QaViewer/QaInspectorEmpty.mjs +17 -0
  96. package/dist/components/DevToolsPanel/tabs/QaViewer/QaInspectorPanel.d.ts +3 -0
  97. package/dist/components/DevToolsPanel/tabs/QaViewer/QaInspectorPanel.mjs +22 -0
  98. package/dist/components/DevToolsPanel/tabs/QaViewer/QaProperty.d.ts +8 -0
  99. package/dist/components/DevToolsPanel/tabs/QaViewer/QaProperty.mjs +14 -0
  100. package/dist/components/DevToolsPanel/tabs/QaViewer/QaRescanButton.d.ts +3 -0
  101. package/dist/components/DevToolsPanel/tabs/QaViewer/QaRescanButton.mjs +15 -0
  102. package/dist/components/DevToolsPanel/tabs/QaViewer/QaRuleItem.d.ts +7 -0
  103. package/dist/components/DevToolsPanel/tabs/QaViewer/QaRuleItem.mjs +38 -0
  104. package/dist/components/DevToolsPanel/tabs/QaViewer/QaToolChip.d.ts +13 -0
  105. package/dist/components/DevToolsPanel/tabs/QaViewer/QaToolChip.mjs +27 -0
  106. package/dist/components/DevToolsPanel/tabs/QaViewer/QaToolbar.d.ts +3 -0
  107. package/dist/components/DevToolsPanel/tabs/QaViewer/QaToolbar.mjs +58 -0
  108. package/dist/components/DevToolsPanel/tabs/QaViewer/QaViewer.d.ts +8 -0
  109. package/dist/components/DevToolsPanel/tabs/QaViewer/QaViewer.mjs +14 -0
  110. package/dist/components/DevToolsPanel/tabs/QaViewer/QaVisionSelect.d.ts +3 -0
  111. package/dist/components/DevToolsPanel/tabs/QaViewer/QaVisionSelect.mjs +50 -0
  112. package/dist/components/DevToolsPanel/tabs/QaViewer/index.d.ts +3 -0
  113. package/dist/components/DevToolsPanel/tabs/QaViewer/index.mjs +5 -0
  114. package/dist/components/DevToolsPanel/tabs/TracingViewer/TracingViewer.mjs +31 -31
  115. package/dist/components/DevToolsPanel/tabs/VariablesViewer/VariablesStyleList.mjs +7 -10
  116. package/dist/components/DevToolsPanel/tabs/VariablesViewer/VariablesViewer.mjs +12 -12
  117. package/dist/highlight/index.d.ts +2 -0
  118. package/dist/highlight/index.mjs +5 -0
  119. package/dist/highlight/useHighlightElement.d.ts +6 -0
  120. package/dist/{components/DevToolsPanel/tabs/TracingViewer → highlight}/useHighlightElement.mjs +1 -1
  121. package/dist/qa/QaContext.d.ts +29 -0
  122. package/dist/qa/QaContext.mjs +73 -0
  123. package/dist/qa/QaLayer.d.ts +9 -0
  124. package/dist/qa/QaLayer.mjs +69 -0
  125. package/dist/qa/checks/contrast.d.ts +6 -0
  126. package/dist/qa/checks/contrast.mjs +23 -0
  127. package/dist/qa/checks/dom.d.ts +15 -0
  128. package/dist/qa/checks/dom.mjs +4 -0
  129. package/dist/qa/checks/headings.d.ts +6 -0
  130. package/dist/qa/checks/headings.mjs +28 -0
  131. package/dist/qa/checks/images.d.ts +6 -0
  132. package/dist/qa/checks/images.mjs +20 -0
  133. package/dist/qa/checks/index.d.ts +15 -0
  134. package/dist/qa/checks/index.mjs +47 -0
  135. package/dist/qa/checks/names.d.ts +9 -0
  136. package/dist/qa/checks/names.mjs +27 -0
  137. package/dist/qa/checks/overflow.d.ts +3 -0
  138. package/dist/qa/checks/overflow.mjs +24 -0
  139. package/dist/qa/checks/targets.d.ts +8 -0
  140. package/dist/qa/checks/targets.mjs +17 -0
  141. package/dist/qa/findings.d.ts +5 -0
  142. package/dist/qa/findings.mjs +11 -0
  143. package/dist/qa/inspect/colour.d.ts +33 -0
  144. package/dist/qa/inspect/colour.mjs +111 -0
  145. package/dist/qa/inspect/describe.d.ts +32 -0
  146. package/dist/qa/inspect/describe.mjs +42 -0
  147. package/dist/qa/inspect/measure.d.ts +20 -0
  148. package/dist/qa/inspect/measure.mjs +23 -0
  149. package/dist/qa/inspect/rules.d.ts +12 -0
  150. package/dist/qa/inspect/rules.mjs +29 -0
  151. package/dist/qa/overlays/QaBoxModel.d.ts +9 -0
  152. package/dist/qa/overlays/QaBoxModel.mjs +49 -0
  153. package/dist/qa/overlays/QaContrastBadge.d.ts +7 -0
  154. package/dist/qa/overlays/QaContrastBadge.mjs +13 -0
  155. package/dist/qa/overlays/QaGrid.d.ts +10 -0
  156. package/dist/qa/overlays/QaGrid.mjs +24 -0
  157. package/dist/qa/overlays/QaInspector.d.ts +7 -0
  158. package/dist/qa/overlays/QaInspector.mjs +50 -0
  159. package/dist/qa/overlays/QaMeasure.d.ts +7 -0
  160. package/dist/qa/overlays/QaMeasure.mjs +39 -0
  161. package/dist/qa/overlays/QaSwatch.d.ts +6 -0
  162. package/dist/qa/overlays/QaSwatch.mjs +11 -0
  163. package/dist/qa/overlays/QaTabOrder.d.ts +6 -0
  164. package/dist/qa/overlays/QaTabOrder.mjs +39 -0
  165. package/dist/qa/overlays/QaTooltip.d.ts +6 -0
  166. package/dist/qa/overlays/QaTooltip.mjs +65 -0
  167. package/dist/qa/overlays/QaViewport.d.ts +10 -0
  168. package/dist/qa/overlays/QaViewport.mjs +27 -0
  169. package/dist/qa/qaCss.d.ts +12 -0
  170. package/dist/qa/qaCss.mjs +45 -0
  171. package/dist/qa/qaSettings.d.ts +38 -0
  172. package/dist/qa/qaSettings.mjs +28 -0
  173. package/dist/qa/tabOrder.d.ts +2 -0
  174. package/dist/qa/tabOrder.mjs +8 -0
  175. package/dist/qa/usePageBox.d.ts +13 -0
  176. package/dist/qa/usePageBox.mjs +23 -0
  177. package/package.json +373 -33
  178. package/dist/components/DevToolsPanel/tabs/ElementsViewer/DetailsAttributes.mjs +0 -19
  179. package/dist/components/DevToolsPanel/tabs/ElementsViewer/DetailsDefinition.mjs +0 -20
  180. package/dist/components/DevToolsPanel/tabs/ElementsViewer/DetailsValue.d.ts +0 -10
  181. package/dist/components/DevToolsPanel/tabs/ElementsViewer/DetailsValue.mjs +0 -31
  182. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementDetails.d.ts +0 -8
  183. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementDetails.mjs +0 -21
  184. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementsList.d.ts +0 -8
  185. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementsList.mjs +0 -42
  186. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementsListItem.d.ts +0 -9
  187. package/dist/components/DevToolsPanel/tabs/ElementsViewer/ElementsListItem.mjs +0 -24
  188. package/dist/components/DevToolsPanel/tabs/TracingViewer/useHighlightElement.d.ts +0 -2
  189. /package/dist/components/DevToolsPanel/tabs/ElementsViewer/{DetailsAttributes.d.ts → components/DetailsAttributes/DetailsAttributes.d.ts} +0 -0
  190. /package/dist/components/DevToolsPanel/tabs/ElementsViewer/{DetailsDefinition.d.ts → components/DetailsDefinition/DetailsDefinition.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,626 @@
1
1
  # @plitzi/sdk-dev-tools
2
2
 
3
+ ## 0.38.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 266e691: ## A shutdown that does not wait on idle connections
8
+
9
+ A server shutting down while it answered a request waited, once the answer was sent, for the client to let go of the
10
+ keep-alive connection it came on — three seconds for Node's own `fetch`, longer for others — before it could stop. The
11
+ connection is now closed as soon as its answer is finished.
12
+
13
+ - Updated dependencies [266e691]
14
+ - @plitzi/sdk-navigation@0.38.1
15
+ - @plitzi/sdk-schema@0.38.1
16
+ - @plitzi/sdk-shared@0.38.1
17
+ - @plitzi/sdk-style@0.38.1
18
+
19
+ ## 0.38.0
20
+
21
+ ### Minor Changes
22
+
23
+ - v0.38.0
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies
28
+ - @plitzi/sdk-navigation@0.38.0
29
+ - @plitzi/sdk-schema@0.38.0
30
+ - @plitzi/sdk-shared@0.38.0
31
+ - @plitzi/sdk-style@0.38.0
32
+
33
+ ## 0.37.10
34
+
35
+ ### Patch Changes
36
+
37
+ - 8d1cc02: ## The builder's sidebar, one entry per subject
38
+
39
+ From 21 entries to 9. **Elements** ends with the space's **Components**, one search for both — each is dragged
40
+ onto the canvas the same way. **Server** gathers Actions, Functions, Connectors, Credentials and Runtime behind tabs, with
41
+ the warning that a space has no server-rendered deployment said once above them. **Variables** holds Feature Flags,
42
+ **Assets** holds files and fonts, **Settings** holds Visitors, the Pages panel opens the **Sitemap** in place of the
43
+ canvas, and **History** moved to the header beside undo and redo. Each grouped entry remembers the tab left open.
44
+
45
+ The builder is drawn with the website's design system: Geist and Geist Mono, the `#5b3df5` violet, cool neutrals and
46
+ 8 / 10 / 14px radii, from the tokens `@plitzi/plitzi-ui/theme.css` now ships. The published SDK stylesheet does not
47
+ take them, so a space's own elements look as they did.
48
+
49
+ The Sitemap is drawn by `TreeCanvas`, a new `@plitzi/plitzi-ui` component: the site laid out as a tree on its own, panned
50
+ and zoomed like a design canvas, a page moved by dropping it onto a folder or onto the top level. `@xyflow/react` — and
51
+ zustand and d3 with it — is no longer a dependency of the builder.
52
+ From the map a page is found (search lights it and the folders leading to it), opened in the canvas (double click,
53
+ Enter or its card), and created inside a folder; folders fold away what they hold, remembered between visits; arrows walk
54
+ it. Each card says who may open the page, its layout, the flag it exists under, where it sends somebody it refuses, and
55
+ marks its dynamic segments and the page being edited. Fixed on the way: a page with no access level was labelled
56
+ "Public", which in Plitzi means guests only — it is open to everyone, and now says so.
57
+
58
+ ## Feature flags
59
+ - **What they are:** `schema.flags`, a space's switches by name — a default and rules over the environment, the host,
60
+ the URL and the visitor. Read with the document, stored apart from its snapshots: one set per environment, turned
61
+ without a new revision; a snapshot keeps a copy, read only when the environment's flags (and their Redis copy)
62
+ cannot be. See `docs/en/feature-flags.md`.
63
+ - **Caches follow them:** `SSRSpaceDeployment.flagsVersion` (the flags' hash) keys the HTML, RSC and `offlineData`
64
+ caches of `@plitzi/sdk-server`; `createCloudAdapters` probes `flagsHash` and fetches `SpaceFlags` only when it moved
65
+ — a pinned revision included — and keeps the last flags in its shared cache for a cold start with Plitzi down.
66
+ - **Who decides:** the space, then the server rendering it (`createServer({ flags })`), then the SDK embedding it (the
67
+ `flags` prop), then a tester (the dev tools' Flags tab, only where debugging is authorized) — each only for flags the
68
+ space declares.
69
+ - **Gating:** `definition.flag: { name, is }` renders an element only while the flag agrees — not a visibility: gated
70
+ off, none of it is rendered, on the server or in the browser, and RSC resolves no data for it. A gated page is not
71
+ found. Its declaration still ships with the space's document: a flag switches a feature off, it does not hide it.
72
+ - **Reading:** the `flags` global source (`{{ flags.x }}`), `useFlag(name)` for plugins, and `flags` in a server
73
+ action's scope (the `getFlags` action lookup).
74
+ - **Builder:** Feature Flags beside the variables (declare, rule, force in the canvas, publish), the gate in an element's tools, a
75
+ marker in the tree. Its own flags come from the platform (`PlatformFlags`) instead of a constant.
76
+ - **Authoring and MCP:** `SpaceSpec.flags`, `flag: 'name' | '!name'` on elements and pages, linter codes
77
+ `flag-undeclared`, `flag-unknown`, `flag-unused`, `flag-rule-empty`; MCP `upsertFlag`, `deleteFlag`, `flag` on
78
+ element and page ops, `plitzi://flags/{env}`.
79
+ - **Global sources** are one list now (`@plitzi/sdk-shared/dataSource/globalSources`), read by the runtime and the
80
+ authoring validator alike.
81
+
82
+ ## Element templates are Snippets
83
+
84
+ What the builder saves from a subtree and drops into a page was called a template, the word a space's own starting
85
+ point already goes by. It is a **snippet** now, everywhere, with no alias for the old names:
86
+
87
+ - **Builder:** "Save as snippet" on an element, **Snippets** in the resources list. A snippet has an icon of its own
88
+ (an object group) beside the component's cube, in the canvas overlay and the context menu alike, and each says on
89
+ hover what sets it apart: a component stays linked, a snippet is a copy.
90
+ - **CDN:** a snippet is uploaded to `snippets/` in the space's folder, with the resource type `snippet`. A file already
91
+ in `templates/` is no longer listed as one: upload it again.
92
+ - **Authoring:** `authorSnippet`, `validateSnippet`, `SnippetSpec` and `AuthoredSnippet`, which returns `{ snippet,
93
+ warnings }`. The validator codes are `SNIPPET_*`.
94
+ - **Shared and schema:** the `Snippet` type (`@plitzi/sdk-shared/types/SnippetTypes`), `SpaceAddSnippet` and
95
+ `SPACE_ADD_SNIPPET`, `SCHEMA_ADD_SNIPPET` and `STYLE_ADD_SNIPPET`, `schemaAddSnippet` / `styleAddSnippet` on the
96
+ event bridge, and `FlatMap.flatAsSnippet`.
97
+ - **Plugins:** the builder config key `canTemplate` is `canSnippet`.
98
+ - **One document, whoever writes it:** a snippet's `schema` is only what travels — `flat` and `variables` — whether
99
+ `authorSnippet` wrote it or the builder saved it. Until now an authored one carried a whole space (`pages: []`, its
100
+ settings), which the builder's preview laid over the space being edited, and one the builder saved failed
101
+ `validateSnippet` (`INVALID_PAGES`).
102
+ - **The same snippet, dropped twice:** the second drop renamed its elements in the editor while the server was sent
103
+ the names it arrived with, refused them as taken, and the drop was undone. The names are now fitted where the
104
+ snippet is dropped (`fitSnippet`, `@plitzi/sdk-schema/helpers/fitSnippet`) and carried by the insert to the server
105
+ and every collaborator; `SCHEMA_ADD_SNIPPET` inserts under them and refuses a name taken since, as the server does.
106
+ `SpaceAddSnippet` checks what it is sent against the `SPACE_ADD_SNIPPET` event before applying it.
107
+ - **A snippet never restyles the space it lands in:** a class it brings under a name the space uses for something
108
+ else is renamed (`card` → `card-2`) on its rule, its elements and the rules naming it as an ancestor, and its CSS
109
+ recompiled; one that says the same is shared. The space keeps its rules for element types and its tokens, and a
110
+ snippet's tokens the space lacks are now added — the editor merged its rules only, and the server neither
111
+ (`mergeSnippetStyle`, `@plitzi/sdk-shared/style/snippetStyle`; `SnippetStyle`; `STYLE_ADD_SNIPPET` carries `style`).
112
+ - **A snippet is known by what it holds:** `isSnippet` (`@plitzi/sdk-shared/schema/snippet`). A JSON uploaded in
113
+ **Assets** that is a snippet goes among the snippets — before, an authored one landed as a plain file — and an upload
114
+ declared a snippet that is not one is refused. A file among the snippets the builder cannot read is shown as such,
115
+ with a way to remove it, instead of breaking the panel.
116
+ - **Saving says how it went:** "Save as snippet" announces the snippet once the upload answered, and says why when it
117
+ did not — it used to report it created before knowing, and from the context menu said nothing at all.
118
+
119
+ Space templates — what a new space starts as — keep their name.
120
+
121
+ ## A link to a section of a page
122
+ - **`anchor`** on any element is its `id` in the DOM — the element's own id only ever reached it as `data-id` — so
123
+ `/page#plans` has somewhere to land. Written by `authorSpace` (`anchor: 'plans'`), the builder (the element's
124
+ **Anchor** field), the MCP (`upsertElement` / `patchElement`) and read back by `specFromSpace`.
125
+ - **`link` takes `hash`**: `link({ href: 'home', hash: 'plans' })` goes to `/#plans`; a flow's `navigate('home#plans')`
126
+ resolves the page and keeps the fragment.
127
+ - **The router scrolls to the fragment** after every client-side navigation and on arrival, and waits up to 3 s for a
128
+ section that renders once its data arrives — a visitor who scrolls first is left where they are.
129
+ - **Refused while authoring:** an anchor that is not lowercase letters, digits and `-` (`anchor-invalid`), one on an
130
+ element with no tag (`anchor-no-tag`), inside a list row or a component (`anchor-repeated`), twice on one page with
131
+ its layouts (`anchor-duplicate`), and a link to a section the page does not have (`anchor-missing`).
132
+
133
+ ## Every problem in one run
134
+ - **`authorSpace` reports everything it cannot write at once**, as a `SpaceRefusedError` whose `refusals` list each
135
+ one — instead of stopping at the first. An element it cannot write is left out and its siblings carry on; a class
136
+ worn with `css`, `states` or a `selector` of its own is reported and written with the class, so the linter still
137
+ reads the rest of the space and its findings join the same report.
138
+ - **Each problem says where:** the line of your own code that called the factory (`src/site/home.ts:417`) and the
139
+ nearest named element with the steps from it (`"store-footer" › container[1]`) — not a path of indices.
140
+ - **The project's `npm run author`** prints one line on success, the numbered problems (no stack from inside the
141
+ package) on failure, and one JSON object with `--json`.
142
+
143
+ ## A class, plus one thing
144
+
145
+ `class: [cover, { opacity: '0.25' }]` puts rules of the element's own on top of the classes it wears — the commonest
146
+ shape there is, which until now took a class of its own every time. The rules become the class `<id>--own`, declared
147
+ after every shared class so it wins over them, editable in the builder like any class; they take states and
148
+ breakpoints as `styles()` does, need the element's `id`, and read back from a document as the same inline object.
149
+
150
+ ## Every few seconds, with no plugin
151
+
152
+ `onInterval(ms)` is a trigger every element has: a flow that repeats every `ms` milliseconds — an autoplay, a clock, a
153
+ refresh — while the element is mounted and the tab is in view, and never in the builder outside preview. Each flow
154
+ names its own interval and counts its own ticks (`{{ <step>.count }}`); one below 250 ms is refused (`trigger-interval`).
155
+ It is in the builder's flow editor beside `onKey`, and `@plitzi/sdk-shared/helpers/interval` holds the rule all of them
156
+ read.
157
+
158
+ ## Data in `public/`, in the page from the first byte
159
+ - **`plitzi create --mode server`** writes `public/data/` and passes `publicDir` to `createServer`: `public/data/*.json`
160
+ is served with no change to `src/main.ts`.
161
+ - **A server provider reads a file of the server's own:** an `apiContainer` with `runtime: 'server'` and a `query`
162
+ that is a plain path in `publicDir` (`/data/home.json`) is resolved by the page server from disk — no connector or
163
+ action configured — so the page arrives with the section in it rather than fetching it after load. A URL, a path
164
+ outside `publicDir` or a `query` with `{{tokens}}` is left to the browser as before.
165
+
166
+ ## A project's server, findable
167
+ - **`npm start` starts beside whatever holds 8080** while developing: the next free port (`freePort`, from
168
+ `@plitzi/sdk-server`), printed and written to `.plitzi/dev-server.json`. With `PORT` set, that port or an error.
169
+ - **`/health` answers with the space's name**, and `npm run shot` checks it before taking a picture — another server
170
+ on the port is reported, instead of photographed. `playwright.config.ts` and `shot` read the port from `PORT`, else
171
+ from `.plitzi/dev-server.json`.
172
+
173
+ ## Every document type, from the package you author with
174
+
175
+ `@plitzi/sdk-authoring` exports the types of what it writes — `Schema`, `Style`, `SpaceFont`, `Element` and the rest
176
+ of the documents, plus `SchemaValidationError`, `SchemaValidationOptions` and `SchemaValidationResult` — so a project
177
+ types its own helpers without reaching into `@plitzi/sdk-shared`.
178
+
179
+ ## Breakpoint warnings that read `display: none`
180
+
181
+ `tablet-rule-skips-mobile` no longer warns about a tablet rule the phone hides anyway: an element with
182
+ `display: none` under `mobile` never shows the rule it skips. When it does warn, it points to `compact`, which reaches
183
+ both.
184
+
185
+ ## An inline container
186
+
187
+ `container({ subType: 'span' })` renders a `<span>`: a dot before a title, a word dressed apart, a badge in a line of
188
+ text — inline by default, and in the builder's container settings as "Span (inline)". A span holding a heading, a
189
+ paragraph, a list, a form or prose is warned about (`span-holds-block`).
190
+
191
+ ## Every problem has a code, and a page that cannot miss one
192
+ - **Every refusal and warning carries a code** — `[class-and-css]`, `[id-taken]`, `[tablet-rule-skips-mobile]` — in
193
+ the message and in `SpaceRefusedError.refusals[].code`. `AUTHORING_CODES` (exported) is the one table they are raised
194
+ from: whether each is refused or warned, what was wrong and what to write instead. A check raised with a code that is
195
+ not in it does not compile.
196
+ - **The skill's `authoring-errors.md` is generated from that table**, as is the website's Authoring errors page
197
+ (`authoringCodesTable`), so neither can miss a code; a test fails when the page and the table disagree.
198
+ - `AuthoringError` is what a factory or `authorSpace` throws for one problem on its own, with its `code` and `reason`.
199
+
200
+ ## The skills, packaged for a project
201
+ - **A cheatsheet to start from** (`CHEATSHEET.md`): the factories, fields, steps and the problems met most, on one
202
+ page. `SKILL.md` starts there, says what to read for each kind of task, and what never to read whole.
203
+ - **Recipes by intent, as files** (`recipes/*.ts`): show data from a file, filter a list, a detail page, link to a
204
+ section, something every few seconds (a carousel), a marquee, a link built from a row, forms and modals, a feature
205
+ flag, a plugin, controls usable without sight, styling, and an embed or an SVG. CI authors each one: a recipe that
206
+ stops authoring fails the build.
207
+ - **Every link in a skill resolves inside a project**, and none names a file only the workspace has; a test walks them
208
+ as `plitzi create` copies them. Realtime channels have a reference of their own.
209
+ - **Each skill file has a budget** — about 4k tokens for a `SKILL.md`, 3k for a reference — held by a test.
210
+ - **When to use the MCP and when the CLI**, in both skills: the MCP for a space that lives on Plitzi, the CLI for one
211
+ that lives in code; a sign-in nobody can give never blocks the second.
212
+ - New guidance: animations (keyframes, entering with `visible`, pausing on hover, staggering), a list's `index` as
213
+ text, rows kept by position, a provider around a layout's slot, checking motion in a test.
214
+
215
+ ## A project an agent finds its way around
216
+ - **`AGENTS.md` says the port, where data goes, how to look at a page, and what not to read**: the generated
217
+ `space/offline-data.json`, `.sdk-plugins/`, a large `public/data/*.json` and the bundles in `node_modules`.
218
+ - **`plitzi data describe <file>`** prints a JSON file's shape — every field, its type, how many rows have it — and one
219
+ row of its longest list; `--json` for a tool.
220
+ - **Quiet by default:** the server prints only what goes wrong (`npm start -- --verbose` for every request), and
221
+ `typecheck` one line per error.
222
+ - **The welcome space follows the skill's rules:** a box one class owns whole is a shorthand; a test holds it to that.
223
+ - `.claude` is left out of a project's lint and formatting.
224
+
225
+ ## Problems over MCP come with their code and their fix
226
+
227
+ An agent editing a space over MCP has no skill page to look a code up in: each problem a batch meets now leads with its
228
+ `[code]` and its hint says what to write instead, from the same `AUTHORING_CODES` row `authorSpace` raises it from
229
+ (`authoringCodeEntry(code)` looks one up).
230
+
231
+ ## Scrolling, as steps
232
+ - **`scrollBy`, `scrollTo` and `scrollIntoView`** are callbacks every element answers: `scrollBy('cards', { x: '80%' })`
233
+ moves a box by most of what it shows, `scrollTo('cards', { x: 'end' })` to an end or a place, `scrollIntoView` brings
234
+ an element into view. In the builder's flow editor, in authoring and over MCP.
235
+ - **`onScroll`** fires as an element's box moves — at most once a frame, and once on mount — with
236
+ `{ x, y, atStart, atEnd }`, so an arrow can hide at the end already reached (recipe: `recipes/scroll-a-row.ts`).
237
+
238
+ ## A list names its rows by the field you choose
239
+
240
+ `list({ itemKey: 'slug' })` keys each row by that field of its item, so a row's state follows its item when the list is
241
+ filtered or reordered, and a row whose item changes mounts again — a one-row list showing the current slide replays its
242
+ entrance. Left out, rows follow the items' `id` (when every item has its own), else their position. In the builder it
243
+ is the list's "Row key"; `list-item-key-missing` warns when fixed items lack it or share one.
244
+
245
+ ## A skeleton while a provider loads
246
+
247
+ `apiContainer({ loadingSlot: 'catalog-skeleton' })` names one of its children to show in place of the others until the
248
+ first answer arrives — the shape of what is coming — and to drop after it. In the builder every child shows, so the
249
+ slot is edited beside what it stands for; `loading-slot-unknown` refuses a slot no child answers to.
250
+
251
+ ## `plitzi create --template blank`
252
+
253
+ A space written in the project can start empty: tokens for both themes, a layout whose `site-main` the pages render in,
254
+ one page, `public/data/` and no example plugin — for a project that is about to be a specific site, where the welcome
255
+ tour is the first thing that would be deleted. `emptySpaceSpec` / `emptySpaceSource` in `@plitzi/sdk-authoring`.
256
+
257
+ ## `plitzi add plugin` writes the shape it is told
258
+
259
+ `--prop interval:number=5000`, `--trigger onTick:count`, `--callback reset` and `--headless` write an element in its
260
+ final shape — typed props with defaults, bindable and with a control each in its panel; a `use<Name>Events()` hook that
261
+ fires its events with typed payloads; a function per action; and, headless, hidden on a page and a badge in the builder
262
+ — instead of the counter example, which is what it writes without them. Flags that cannot make an element (a type that
263
+ is not one, an event every element already fires, a name used twice) are refused with how to write them, and the files
264
+ are written as the project's Prettier writes them.
265
+
266
+ ## Skills that say their version, and `plitzi skills update`
267
+
268
+ The skills `plitzi create` copies into a project carry the version of the package they came from (`version:` in
269
+ `SKILL.md`). `npm run author` says when the authoring skill is older than the `@plitzi/sdk-authoring` installed, and
270
+ `plitzi skills update` replaces each Plitzi skill with the installed package's — whole, leaving any other skill alone.
271
+ The CLI skill's references for `create --from` / `pull` and for a space's functions are files of their own now, read
272
+ when a task names them.
273
+
274
+ ## `plitzi explain`
275
+
276
+ What a name means when authoring, in a few lines: an element (its attributes and their values, what it fires and
277
+ answers, its slots), a step (its params and the function that writes it), a trigger (what it hands its flow, what fires
278
+ it), a problem's code (what was wrong, what to write instead) or a transformer. `--list steps` names every one of a
279
+ kind, `--json` answers in one object, and over MCP it is the resource `plitzi://explain/{name}`. Read from the same
280
+ catalogues the checks use (`explain`, `explainList`, `explanationText` in `@plitzi/sdk-authoring`).
281
+
282
+ ## `plitzi check` and `plitzi shot`: a page in numbers before pictures
283
+ - **`plitzi check / --width 1440,390`** says whether a page of the running project is whole, in text: every element the
284
+ space owes it on screen (or why not), no broken image, no sideways scroll, no text in the colour behind it, no console
285
+ error, no refused request — per width, `--json` for a tool. A space in code is checked against what each page owes;
286
+ any other, against what every page does.
287
+ - **`plitzi shot`** takes the picture, and `--compare <url>` puts the same page of another site beside it — the
288
+ differences in red, and the share that differs in each landmark (`section#plans 14%`); `--frames 4 --every 500` says
289
+ what moves; `--wait-for` and `--reduced-motion` set it up. The comparison runs in the browser
290
+ (`comparePictures`, `pageRegions` in `@plitzi/sdk-authoring`), so nothing is installed for it.
291
+ - Both use the project's own Playwright and refuse a port that answers as another project. A project `create` writes
292
+ installs `@plitzi/cli` and runs them as `npm run check` / `npm run shot`, which replaces its `scripts/shot.ts`.
293
+
294
+ ## The builder's problems panel says the fix
295
+
296
+ Each problem in the builder's panel shows what to write instead, from the same `AUTHORING_CODES` row `authorSpace` and
297
+ the MCP use — `SpaceIssue.fix` over GraphQL, its code set apart.
298
+
299
+ ## Scroll snap, and anchors under a fixed header
300
+
301
+ `scroll-snap-type`, `scroll-snap-align` and `scroll-snap-stop` — a row of cards that comes to rest on a card — and
302
+ `scroll-padding-top` / `scroll-margin-top` — an anchor that lands below a fixed header rather than under it — are part
303
+ of the style vocabulary, with a "Scroll snap" section in the style editor.
304
+
305
+ ## CSS as a React style object, and a value per breakpoint in place
306
+ - **camelCase keys and numbers:** `{ paddingTop: 8, fontWeight: 800, WebkitLineClamp: 2 }` is `padding-top: 8px`,
307
+ `font-weight: 800`, `-webkit-line-clamp: 2` — a bare number on a length is pixels. The document keeps kebab-case; one
308
+ property written under both spellings is refused (`css-property-twice`).
309
+ - **One property per breakpoint:** `{ fontSize: { desktop: '24px', compact: '18px' }, fontWeight: 700 }` changes the
310
+ size on tablet and phone without splitting the rule set; it mixes with the per-breakpoint form.
311
+
312
+ ## A link's mode follows from its href
313
+
314
+ `link({ href: '/games/nebula' })` is internal, `link({ href: 'https://…' })` (or `mailto:`, `tel:`) external, and
315
+ `link({ href: 'about' })` a page — `mode` is written only to say otherwise. A link that opens another tab gets
316
+ `rel="noopener noreferrer"`.
317
+
318
+ ## `from` and `as`: an element shows its data in a line
319
+ - **`from`** binds the attribute a type shows its data in — a text's or heading's `content`, an image's `src`, a link's
320
+ `href`, a list's `items` — and leaves it empty until the data answers: `heading({ from: 'site.data.hero.title' })`.
321
+ It writes the same binding `bind` does; `bind` keeps the other attributes.
322
+ - **`as`** shows it through a template (`as: '{{ source }} left'`) or a format the space names once:
323
+ `formats: { price: "{{ source|currency('USD', 'en', { trimZeros: true }) }}" }`, then `as: 'price'` anywhere.
324
+ - **New template filters:** `currency('USD', locale?, { trimZeros })` and `percent(decimals?)`, in `en` unless told,
325
+ so a server and a browser in different locales write the same text.
326
+ - Refused: `from` on a type with no main attribute, the main attribute bound twice, a format nobody declared, `as`
327
+ without `from`.
328
+
329
+ ## A list in one line: `items` and `row`
330
+
331
+ `list({ id: 'grid', items: 'catalog.data.products', row: 'product-card' })` is a controlled list fed by that source,
332
+ placing the component once per item with the row bound to its `item` prop (or its only prop). `row` can also be a
333
+ function handed the row's names — `row: r => text({ from: `${r.item}.title` })`, `r.inTemplate.item` for a template.
334
+ `items` (an array, or a source's name) and `from` make a list controlled without saying so. Refused: a row and
335
+ children at once, a function row on a list with no `id`, a row naming a component that cannot take it.
336
+
337
+ ## `activeWhen`, and a list's index is a number
338
+ - **`activeWhen(dot, '{{ list_dots.index == state.slide }}')`** wears a class's `active` variant while a condition
339
+ holds (`idle` otherwise) — the general form of `activeOn`, without a hand-written ternary.
340
+ - **`list_<id>.index` is a number** from 0, so a template counts with it (`index + 1`). `==` compares text and numbers
341
+ alike, so templates that compared it as text read the same.
342
+
343
+ ## `cycleState` and `stepState`
344
+
345
+ `cycleState({ key: 'slide', length: 4 })` moves a number in state round a cycle — after the last the first, `by: -1`
346
+ before the first the last — and `stepState({ key: 'shown', by: 40, max: 'apiContainer_site.data.total' })` adds and
347
+ stops at its bounds. Both are the `setState` they stand for, with the arithmetic written once; a length or a bound is a
348
+ number or a template expression for one.
349
+
350
+ ## `scope()` for what a helper builds
351
+
352
+ `scope('promos', ref => container({ id: 'panel', … }))` prefixes every `id` given inside it (`promos-panel`), so a
353
+ helper that builds the same block twice writes two sets of names instead of being refused `id-taken`. `ref('slides')` is
354
+ the full name, for a binding, a step's target or a template; scopes nest. `id-taken` now points at it.
355
+
356
+ ## Typed tokens, and `unknown-variable`
357
+ - **`tokens(variables)`** turns the space's variables into the values a rule writes — `t.surface === 'var(--surface)'`
358
+ — so a token that does not exist is a type error where it is written.
359
+ - **`unknown-variable`** warns of a bare `var(--x)` nothing declares (a space variable, a selector's variable, a custom
360
+ property a rule sets, or `customCss`): the browser drops the property and the page shows what it inherits. A
361
+ `var(--x, fallback)` is taken as meant.
362
+
363
+ ## Typed sources: `source()` and `twig`
364
+
365
+ `const site = source('site', home)` names a provider's source from a sample of its answer — the JSON file it reads,
366
+ imported — so every path is completed by the editor and checked: `site.data.hero.titel` is a type error, and refused
367
+ `source-field-unknown` where the types were not looking. A path is the full source name, so it goes in `from`, `items`,
368
+ `bind` and `visible` as it is, and into a template through `` twig`{{ ${site.data.total} + 1 }}` ``. A list fed by a
369
+ path hands its `row` the item typed (`row: g => text({ from: g.item.title })`). Nothing of the sample is written into
370
+ the space; inside a `scope()` the id is the scoped one.
371
+
372
+ ## `tw()`: Tailwind classes as Plitzi styles
373
+
374
+ `styles('pill', tw('inline-flex items-center gap-2 px-5 rounded-full bg-slate-950/90 hover:scale-105 md:text-sm'))`
375
+ writes the rules the classes mean when the space is authored — Tailwind v4's scales and palette, arbitrary values,
376
+ `[property:value]`. Its breakpoints become Plitzi's ranges (`md:` tablet and desktop, `lg:` desktop, `max-md:`,
377
+ `max-lg:`), its states the class's states, `group-hover/<class>:` an ancestor's. Composed properties (transform,
378
+ filter, gradient, ring and shadow) are put together once. `createTw({ colors: tokens(variables) })` names the space's
379
+ tokens. What has no exact equivalent (`sm:`, `dark:`, `space-x-*`, `animate-*`) is refused with what to write instead.
380
+
381
+ ## `create --template catalog`, and a file per part
382
+
383
+ `npx @plitzi/cli create shop --template catalog` writes a complete small site to read and change: a layout with a menu,
384
+ a product card component, `public/data/products.json` read as a typed source, a catalog filtered by category and a page
385
+ per product — each part a short file of its own under `src/site/`, assembled by `src/space.ts`. The template is
386
+ authored by sdk-authoring's tests, and a generated project authors, typechecks and lints clean. The skills point to it,
387
+ and `AGENTS.md` asks for a file per part.
388
+
389
+ ## `embed` and `svg` elements
390
+ - **`embed({ src, title })`** puts another page in a frame — a map, a video player — lazily loaded, with `allow`,
391
+ `sandbox` and `referrerPolicy` when it needs them. Only a web address or a path of the site is loaded, and in the
392
+ builder the frame does not swallow the click that selects it. `embed-without-title` warns of one a screen reader
393
+ cannot describe.
394
+ - **`svg('<svg …>…</svg>', { label })`** draws SVG markup inside a box its class colours (`currentColor`): checked to be
395
+ one `<svg>` (`svg-not-svg` otherwise) and sanitised as rich text is, plus what only SVG can carry (`foreignObject`,
396
+ animations writing a `javascript:` link). Decorative unless it has a `label`, then `role="img"`. `{{ }}` tokens
397
+ resolve in it like in any attribute.
398
+
399
+ ## Pictures resized by the page server
400
+
401
+ `createServer({ images: { domains: ['images.example.com'] } })` resizes other sites' pictures at `/_plitzi/img`: an
402
+ `image` whose `src` is one of them offers a `srcset` from 320 to 1920 px, in AVIF or WebP when the browser takes them.
403
+ Each original is downloaded once and each size made once, both kept on disk; a week on they keep answering while the
404
+ original is revalidated with its `ETag` / `Last-Modified`, so an unchanged picture is never resized again. Only the listed hosts (`*.example.com` for subdomains) are fetched, every redirect is
405
+ held to the list and to the outbound guard, and SVG is refused. `sharp` is an optional peer: without it pictures are
406
+ passed through and kept. `image` also takes `sizes`, and `width`/`height` so the browser keeps the picture's space.
407
+
408
+ ## `plitzi check` says what the page holds
409
+
410
+ Every `plitzi check` now lists the flows that failed while the page loaded, each with the step that failed and why.
411
+ `--state` adds the page's state and every source by its full name (with the shape of what it holds), and `--element
412
+ <id>` one element: what it reads, its own state, how many copies, whether it is on screen and its box. Read from the
413
+ page's dev tools, which keep the flows a page runs before they mount; in a test, `readDevTools(page)` from
414
+ `@plitzi/sdk-authoring` answers the same.
415
+
416
+ ## `carousel`
417
+
418
+ A structure element for slides that change, a marquee and a row that swipes: `carousel({ id: 'hero', items, row,
419
+ autoplay: 5000, children: [arrows, dots] })`. Its `row` is written into a `carouselTrack`, and its other children are
420
+ its controls, reading `carousel_<id>.index`, `.count`, `.item` and `.items`. `mode: 'slide'` shows one at a time
421
+ (`transition` `slide`, `fade` or `none`, back entering from the left); `'marquee'` scrolls the items past at `speed`
422
+ px/s with no seam; `'scroll'` is a snapping row a visitor swipes. Steps `carouselNext`, `carouselPrevious`,
423
+ `carouselGoTo`, `carouselPlay` and `carouselPause`; trigger `onChange`. Autoplay holds still under the pointer, with
424
+ keyboard focus inside, in a hidden tab and for reduced motion, and never in the builder. Slides are announced as "2
425
+ of 5" in a region named by `label`.
426
+
427
+ ## `plitzi fix`: the fixes authoring knows, written in your source
428
+
429
+ `plitzi fix` shows what authoring would fix — a key the element never reads, `'true'` where a boolean goes, a URL in
430
+ page mode, a `state.` prefix on a state key, a binding nothing reads — as a diff of the project's own source: each
431
+ edit made in the call that wrote the element (found by the line and column it remembers), and only where the value is
432
+ a literal. `--write` writes them, formatted as the project formats, authors the space again in a fresh process and
433
+ keeps them only if every fix is gone and no problem was added; one that would add a problem is put back and said with
434
+ the reason. `npm run author` says how many of its problems have one fix. From `@plitzi/sdk-authoring`, `planFixes`
435
+ returns the plan: every problem, and each fix with its place and edit.
436
+
437
+ `fixSpace` now reads an element with the linter's own context, so a component instance's props and slot are no longer
438
+ taken for attributes nobody reads — it used to remove the binding of a list row to its component's `item`.
439
+
440
+ `plitzi import <url>` measures a page you own in the project's Playwright and writes it into the project as a place to
441
+ start from: `tokens.ts` (the page's custom properties by name, then its dominant colours, each with the value the same
442
+ place shows in the dark scheme; repeated corners and shadows; Google fonts), `outline.ts` (`container()`s for its
443
+ landmarks and blocks, with their layout per breakpoint as what each narrower width changes), its repeated lists as
444
+ `data/*.json`, `assets.json`, a screenshot per width and `IMPORT.md`. Never its words. It imports only a site that is
445
+ the person's: a verified domain of one of their spaces covering the host (the `_plitzi` TXT record, asked of the
446
+ platform's new `GET /account/domains/covering`), or one served from this machine; `--out`, `--widths`, `--force`,
447
+ `--json`, `--api`. From `@plitzi/sdk-authoring`: `importProbe` (runs
448
+ in the page), `importedFiles` and `darkScheme`.
449
+
450
+ A compound element without the part it shows its content through — a `carousel` with no `carouselTrack`, a
451
+ `tabContainer` missing its header or body, a `dropdown` with no `dropdownPopup` — is now refused (`part-missing`):
452
+ it rendered nothing of what it held, without a word. The parts are read off the declarations (`elementPartTypes`, the
453
+ `partTypes` catalog), so the builder's issues and publish gate, the MCP's validate and `authorSpace` all hold to it.
454
+ The MCP guide says how a compound element is written.
455
+
456
+ The dev tools catch up. **Elements** shows what is on screen as the trees it comes from — the layouts around the page,
457
+ outermost first, the page, and every component an instance places — at their depth, searched together (a match is kept
458
+ with what holds it), each element outlined on the page while it is pointed at. Selected, its **Runtime** tab is the
459
+ report `window.__plitzi.element()` and `plitzi check --element` give: own state, what it reads, the component it places
460
+ or sits in, copies and box, read again every second. That report now finds an element inside a component, which it
461
+ missed. **Logs** has a `realtime` category: the page's connection opening and dropping, each message in (←) and out
462
+ (→), and every topic the server refused, with why. One hook outlines an element for every tab.
463
+
464
+ A `webHook` step that gets no answer at all — offline, refused, blocked by CORS — now fails, with the request and the
465
+ reason, so the flow stops and its `onFailure` runs. It used to succeed with an empty response, and the flow carried on
466
+ as if the request had been made. Any answer is still the step's result, an error status included: a flow reads
467
+ `{{ <step>.response.status }}` to tell a 401 from a 200. `response.data` is typed as what the body parsed to.
468
+
469
+ The server checks what a deployment's config hands it in one place (`configSeam`): each action document and connector
470
+ manifest a lookup returns goes through the validator the builder saves with, as it is read — an action that is not a
471
+ document is refused by name, and one in a list is left out and said rather than failing the space's schedule — and the
472
+ database drivers and functions config are checked once, as the server starts. Four unchecked casts are gone.
473
+
474
+ `@plitzi/sdk-shared/helpers/eventTarget` reads an event's target as a node (`nodeOf`, `isNodeTarget`) or an element
475
+ (`elementOf`, `isElementTarget`) by its own type rather than `instanceof`, which is false for a target in the builder's
476
+ canvas iframe. The builder, the style inspector and the dev tools use it, and type their change and key handlers by
477
+ the input they listen on; the casts of `event.target` are gone.
478
+
479
+ The builder's flag form checks each rule's `when` is a group of conditions before it saves, rather than passing
480
+ whatever the field held.
481
+
482
+ The CLI's commands now work the same way: the answer on stdout and errors and sign-in prompts on stderr, so `--json`
483
+ is only the answer (`import --json` printed the sign-in prompt into it); exit 1 whenever a command did not do what it
484
+ was asked (`fix --json` with a problem exited 0); and a flag's value checked where the flag is declared, refused with
485
+ what it takes — `--width abc` or `--scheme darkk` used to check at no width or in light, saying nothing. One flag means
486
+ one thing everywhere: `import --widths` is `--width`, like `check`, and `-o`, `-f` and `-e` are the short forms on
487
+ every command that writes, overwrites or names an environment. `import` no longer repeats a sign-in error. `whoami`
488
+ and `runtime status` take `--json` too; `functions` and `runtime push` refuse outside a project, as every other command
489
+ that works on one does, instead of writing `functions/` into whatever folder they were run from; and every command
490
+ says a finished action the same way, in green.
491
+
492
+ The SDK's production build keeps `console.warn` and `console.error`, and drops only `log`, `info` and `debug`. It used
493
+ to drop them all: an override of a flag the space does not declare, or a render that failed, said nothing on a
494
+ published site — exactly where nobody can attach a debugger.
495
+
496
+ A link to the page being shown says so: it carries `aria-current="page"` — read from the address the page was rendered
497
+ at, so the first paint has it — and a class dresses it with the new `current` style state (`states: { current: … }`,
498
+ a tab in the style editor, folded from `[aria-current="page"]` rules when a space is exported). A site's header now
499
+ goes in a layout once, instead of a copy per page to mark the right navigation item.
500
+
501
+ `notifications` dresses the whole toast, not only its colours: `font`, `fontSize`, `border`, `shadow` and `padding`
502
+ join `radius` — the library's variables where it has them, a rule on the toast where it has none. A space no longer
503
+ writes `.Toastify__toast { … }` into its `customCss` for them.
504
+
505
+ A visitor whose machine asks for less motion gets it on every space: the SDK's base layer cuts animations and
506
+ transitions to an instant (each still ends where it would) and turns smooth scrolling off. Spaces used to copy that
507
+ rule into their own custom CSS, and one that did not moved anyway.
508
+
509
+ Authoring suggests, beside what it refuses and warns: `authorSpace` returns `suggestions` — a shorter way to the same
510
+ page, each with its code, the elements it is about and how many it would save, the largest first. The same header in
511
+ every page is a layout (`repeated-on-pages`, which also names the `current` state when the copies differ only in the
512
+ active link), one structure copied with other words a component or a list (`repeated-shape`), a `text` alone inside a
513
+ button or a link the element's own `content` (`content-attribute`), and `customCss` that a class's states, the SDK or
514
+ `notifications` already say (`custom-css-*`). Copies are compared by what they read too — a block reading its own
515
+ provider is still one block, two pagers over two lists are not — a block only some pages of a layout carry is offered as
516
+ a component rather than a layout of its own, and a text that is a shape drawn inside a button is left alone.
517
+ `suggestSpace({ schema, style })` gives them for any document; they never block. `plitzi_validate` and `plitzi_apply`
518
+ answer with the ones a batch opened up, `npm run author` prints them under the warnings (and `--json` carries them), and
519
+ the authoring skill's new `reference/efficiency.md` teaches the short way first.
520
+
521
+ A `link` has words of its own: `content`, drawn before or after its children (`contentPlacement`), as a button's — a
522
+ link with only a label no longer needs a `text` inside it.
523
+
524
+ A style that writes its rules beside `states`, `variants` or `ancestors` — `{ desktop: { … }, ancestors: { … } }` — is
525
+ refused with what to write (`rule-set-mixed`: the rules go under `css`), instead of reporting `desktop: [object Object]`
526
+ as a CSS value it could not read.
527
+
528
+ `plitzi explain` and `plitzi://explain` name a suggestion's code `suggested`, with its short way, instead of calling it
529
+ `warned`. The MCP server's guide teaches the short way first — a link's own `content`, the `current` state for the link
530
+ to the page being shown, a component for a block only some pages of a layout carry — and how to read the
531
+ `suggestions` `plitzi_validate` and `plitzi_apply` answer with.
532
+
533
+ The MCP server dresses the notifications too: `patchSettings { notifications: { background, border, … } }`, merged
534
+ field by field (`null` removes one), and `plitzi://settings` reads them apart from the space's own `customCss` — the
535
+ same rule authoring writes for a space's `notifications`, kept when either changes. The builder's Export gives that
536
+ rule back as `notifications` instead of leaving it in `customCss` (`splitNotificationsCss`, `withNotificationsCss`).
537
+
538
+ A `button` and a `link` draw an icon beside their words: `icon` (Font Awesome classes, `'fa-solid fa-arrow-right'`) and
539
+ `iconPlacement` (`before` or `after` the words), dressed through a new `icon` slot; it takes the size of the words and
540
+ no space of its own — the box's `gap`, or a margin on the slot, separates them. One element instead of the element, a
541
+ `text` and a `fontAwesome`; alone, with a `title`, it is an icon button. The builder offers the icon picker in both
542
+ elements' settings (shared now with `fontAwesome`'s), and the `content-attribute` suggestion points at a plain
543
+ `fontAwesome` beside the words too.
544
+
545
+ The builder lists the suggestions: `SpaceIssues` answers `suggestions` beside `errors` and `warnings`, the problems
546
+ panel shows them last — the short way, how many elements it saves, and each element it is about, a link to it — and
547
+ the header's issues button, with nothing wrong, shows a light bulb and how many there are.
548
+
549
+ The builder edits the notifications' look in the space settings (Notifications: surface, text, accents, radius, font,
550
+ size, border, shadow, padding), checked as it is typed; the custom CSS editor shows the space's own CSS without the
551
+ rule they are stored as, and keeps it. The mechanism moved to `@plitzi/sdk-shared/style/notifications`
552
+ (`notificationsCss`, `notificationsProblem`, `splitNotificationsCss`, `withNotificationsCss`); `@plitzi/sdk-authoring`
553
+ re-exports it, refusing a spec that is not sound as before.
554
+
555
+ ## Motion that waits for the page to wake
556
+
557
+ The SDK's root carries `data-hydrated` once the page is hydrated, so a space can hold decorative motion that runs on
558
+ the main thread — a custom property, a `background-position`, a `top` — until then, when it would stutter behind the
559
+ hydration's long tasks: `animation-play-state: paused` on the element, and `[data-hydrated] .x { animation-play-state:
560
+ running }`. Opacity and transform animations run on the compositor and need no gate.
561
+
562
+ Hydration is lighter on the way: the fonts' Google URLs sort their families by code point instead of `localeCompare`,
563
+ whose first call built a collator in the middle of the page's first render, and twig's compiled-template cache holds
564
+ 1024 templates instead of a number a large space outgrew, compiling the same ones again on every render.
565
+
566
+ Good practices for motion, said wherever a space is written: `docs/en/motion.md`, the authoring skill (a rule in
567
+ `SKILL.md` and `reference/colours-and-motion.md`), the MCP server's guide and quickstart, and the website's styling
568
+ docs — animate `opacity` and `transform`, never blur, a shadow or a size; fake the expensive ones with the cheap ones;
569
+ hold main-thread decoration until `data-hydrated`; short entrances, one slow loop per screen, no `transition: all`.
570
+
571
+ `heavy-animation`, a new suggestion: keyframes that something runs and that animate a size or a position, a blur or a
572
+ shadow — or, in a loop, a colour, a gradient or a custom property — are named with each property and the way out of
573
+ its cost. A loop that starts `paused` and runs under `[data-hydrated]` is let through, a property that only switches
574
+ (`visibility`) is not counted, and keyframes nothing runs are not mentioned. It reaches `authorSpace`, `npm run
575
+ author`, `plitzi_validate`/`plitzi_apply`, `plitzi explain` and the builder's problems panel like every suggestion. The
576
+ stylesheet scanner `customCss` folding used is shared now (`style/stylesheet`), and reads at-rules' blocks.
577
+
578
+ `authorSpace` suggests `heavy-animation` for keyframes something runs off the compositor — a size, a position, a
579
+ blur, a shadow every time; a colour, a gradient or a custom property in a loop — naming each property and the way out
580
+ of its cost. A loop that starts `paused` and runs under `[data-hydrated]` is let through; a property that only
581
+ switches (`visibility`) is not counted. It reaches `plitzi_validate`, `npm run author` and the builder's problems
582
+ panel like every suggestion, and never blocks. The scanner `customCss` is read with is shared now
583
+ (`style/stylesheet`), so the fold and this read the same segments.
584
+
585
+ `@plitzi/plitzi-ui` 1.6.29: every field is labelled by its `label` and described by its error message, the code
586
+ editor included.
587
+
588
+ ## A layout grid over the canvas
589
+
590
+ The builder's header has a layout grid switch beside the element outlines: the columns a page is laid out on, drawn
591
+ over the canvas — twelve on a desktop, eight on a tablet, four on a phone, with their gutters and margins — so an
592
+ element is lined up by eye with the rest of the page — switching at the page's own breakpoints. It follows the canvas
593
+ zoom, lets every click through, and is remembered between visits like the outlines.
594
+
595
+ ## A QA tab in the dev tools
596
+
597
+ The dev tools — wherever debugging is authorized, a pre-production deployment included — have a **QA** tab for whoever
598
+ checks a build in the browser it will be used in. A bar of tools over the page: an **inspector** — point at any element
599
+ for its box model, type, colours and their contrast; click to keep it in the tab with its computed box, type, classes
600
+ and every CSS rule that reaches it as written (copyable); hold Alt over another to measure the distance between them —
601
+ the builder's layout grid, every element's outline, the order the Tab key walks the controls in, the viewport's size
602
+ and the breakpoint showing, animations paused, the page with reduced motion, and the page as seen without one kind of
603
+ colour, in grayscale or out of focus. Beside it, six checks that outline what they find and scroll to it: whatever
604
+ sticks out past the page's sides, controls and pictures with no name, text under AA contrast, pictures stretched past
605
+ their pixels or far bigger than shown, a heading outline with no h1 or a skipped level, and touch targets under 24 × 24
606
+ px with another too close (WCAG 2.2). The tab lays the checks and the inspector side by side when the panel is wide.
607
+ Folding the panel away stops the inspector and the checks; the views — grid, outlines, vision — stay, in this browser.
608
+ The tab is offered where `DevToolsContainer` is given `qa` — the SDK does, over its page; an application shell such as
609
+ the builder does not, so its own UI is never walked by the checks. Its settings are kept under a key of their own
610
+ (`plitzi-sdk-dev-tools-qa`), the checks run when the page is idle, and the tab order is worked out again only when the
611
+ page changes.
612
+
613
+ The layout grid and the breakpoints are one definition in `@plitzi/sdk-shared/style` (`LAYOUT_GRIDS`,
614
+ `layoutGridLook`, `layoutGridCss`, `DISPLAY_MODE_MIN_WIDTH`, `displayModeAt`), which `@plitzi/sdk-style` compiles its
615
+ media queries from and the builder's grid draws with. The SDK's stylesheet applies its reduced-motion rule under the
616
+ class `plitzi-reduced-motion` on the document too, from the same mixin as the media query.
617
+
618
+ - Updated dependencies [8d1cc02]
619
+ - @plitzi/sdk-navigation@0.37.10
620
+ - @plitzi/sdk-schema@0.37.10
621
+ - @plitzi/sdk-shared@0.37.10
622
+ - @plitzi/sdk-style@0.37.10
623
+
3
624
  ## 0.37.9
4
625
 
5
626
  ### Patch Changes