hyper-html-api 0.8.1 → 0.9.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 (218) hide show
  1. package/README.md +28 -0
  2. package/conformance/MANIFEST.json +2 -2
  3. package/conformance/cases/no-data-list-skipped.expected.json +6 -0
  4. package/conformance/cases/no-data-list-skipped.html +1 -0
  5. package/conformance/cases/no-data-list-skipped.meta +4 -0
  6. package/conformance/cases/no-data-list-skipped.parsed.json +3 -0
  7. package/conformance/cases/no-data-list-skipped.rules +1 -0
  8. package/conformance/cases/no-data-text-projected.expected.json +3 -0
  9. package/conformance/cases/no-data-text-projected.html +1 -0
  10. package/conformance/cases/no-data-text-projected.meta +4 -0
  11. package/conformance/cases/no-data-text-projected.parsed.json +3 -0
  12. package/conformance/cases/no-data-text-projected.rules +1 -0
  13. package/conformance/cases/props-type-is-attribute.expected.json +5 -0
  14. package/conformance/cases/props-type-is-attribute.meta +4 -0
  15. package/conformance/cases/write-attr-app-scheme.after.html +1 -0
  16. package/conformance/cases/write-attr-app-scheme.after.json +3 -0
  17. package/conformance/cases/write-attr-app-scheme.data.json +1 -0
  18. package/conformance/cases/write-attr-app-scheme.html +1 -0
  19. package/conformance/cases/write-attr-app-scheme.meta +4 -0
  20. package/conformance/cases/write-attr-app-scheme.write.json +4 -0
  21. package/conformance/cases/write-attr-href.after.html +1 -0
  22. package/conformance/cases/write-attr-href.after.json +3 -0
  23. package/conformance/cases/write-attr-href.data.json +1 -0
  24. package/conformance/cases/write-attr-href.html +1 -0
  25. package/conformance/cases/write-attr-href.meta +4 -0
  26. package/conformance/cases/write-attr-href.write.json +4 -0
  27. package/conformance/cases/write-checked-false.after.html +1 -0
  28. package/conformance/cases/write-checked-false.after.json +3 -0
  29. package/conformance/cases/write-checked-false.data.json +1 -0
  30. package/conformance/cases/write-checked-false.html +1 -0
  31. package/conformance/cases/write-checked-false.meta +4 -0
  32. package/conformance/cases/write-checked-false.write.json +4 -0
  33. package/conformance/cases/write-checked-true.after.html +1 -0
  34. package/conformance/cases/write-checked-true.after.json +3 -0
  35. package/conformance/cases/write-checked-true.data.json +1 -0
  36. package/conformance/cases/write-checked-true.html +1 -0
  37. package/conformance/cases/write-checked-true.meta +4 -0
  38. package/conformance/cases/write-checked-true.write.json +4 -0
  39. package/conformance/cases/write-crlf-multi.after.html +12 -0
  40. package/conformance/cases/write-crlf-multi.after.json +8 -0
  41. package/conformance/cases/write-crlf-multi.data.json +1 -0
  42. package/conformance/cases/write-crlf-multi.html +12 -0
  43. package/conformance/cases/write-crlf-multi.meta +4 -0
  44. package/conformance/cases/write-crlf-multi.write.json +4 -0
  45. package/conformance/cases/write-fallback-implied.after.html +1 -0
  46. package/conformance/cases/write-fallback-implied.after.json +3 -0
  47. package/conformance/cases/write-fallback-implied.data.json +1 -0
  48. package/conformance/cases/write-fallback-implied.html +1 -0
  49. package/conformance/cases/write-fallback-implied.meta +4 -0
  50. package/conformance/cases/write-fallback-implied.write.json +4 -0
  51. package/conformance/cases/write-flatten.after.html +1 -0
  52. package/conformance/cases/write-flatten.after.json +3 -0
  53. package/conformance/cases/write-flatten.data.json +1 -0
  54. package/conformance/cases/write-flatten.html +1 -0
  55. package/conformance/cases/write-flatten.meta +4 -0
  56. package/conformance/cases/write-flatten.write.json +4 -0
  57. package/conformance/cases/write-list-empty-no-seed.data.json +1 -0
  58. package/conformance/cases/write-list-empty-no-seed.error.json +8 -0
  59. package/conformance/cases/write-list-empty-no-seed.html +1 -0
  60. package/conformance/cases/write-list-empty-no-seed.meta +4 -0
  61. package/conformance/cases/write-list-grow-from-template.after.html +1 -0
  62. package/conformance/cases/write-list-grow-from-template.after.json +5 -0
  63. package/conformance/cases/write-list-grow-from-template.data.json +1 -0
  64. package/conformance/cases/write-list-grow-from-template.html +1 -0
  65. package/conformance/cases/write-list-grow-from-template.meta +4 -0
  66. package/conformance/cases/write-list-grow-from-template.write.json +4 -0
  67. package/conformance/cases/write-list-object-clone.after.html +1 -0
  68. package/conformance/cases/write-list-object-clone.after.json +12 -0
  69. package/conformance/cases/write-list-object-clone.data.json +1 -0
  70. package/conformance/cases/write-list-object-clone.html +1 -0
  71. package/conformance/cases/write-list-object-clone.meta +4 -0
  72. package/conformance/cases/write-list-object-clone.write.json +4 -0
  73. package/conformance/cases/write-list-scalar-grow.after.html +4 -0
  74. package/conformance/cases/write-list-scalar-grow.after.json +7 -0
  75. package/conformance/cases/write-list-scalar-grow.data.json +1 -0
  76. package/conformance/cases/write-list-scalar-grow.html +4 -0
  77. package/conformance/cases/write-list-scalar-grow.meta +4 -0
  78. package/conformance/cases/write-list-scalar-grow.write.json +4 -0
  79. package/conformance/cases/write-list-scalar-shrink.after.html +4 -0
  80. package/conformance/cases/write-list-scalar-shrink.after.json +5 -0
  81. package/conformance/cases/write-list-scalar-shrink.data.json +1 -0
  82. package/conformance/cases/write-list-scalar-shrink.html +4 -0
  83. package/conformance/cases/write-list-scalar-shrink.meta +4 -0
  84. package/conformance/cases/write-list-scalar-shrink.write.json +4 -0
  85. package/conformance/cases/write-list-shrink-zero.after.html +1 -0
  86. package/conformance/cases/write-list-shrink-zero.after.json +3 -0
  87. package/conformance/cases/write-list-shrink-zero.data.json +1 -0
  88. package/conformance/cases/write-list-shrink-zero.html +1 -0
  89. package/conformance/cases/write-list-shrink-zero.meta +4 -0
  90. package/conformance/cases/write-list-shrink-zero.write.json +4 -0
  91. package/conformance/cases/write-no-data-kept.after.html +1 -0
  92. package/conformance/cases/write-no-data-kept.after.json +3 -0
  93. package/conformance/cases/write-no-data-kept.data.json +1 -0
  94. package/conformance/cases/write-no-data-kept.html +1 -0
  95. package/conformance/cases/write-no-data-kept.meta +4 -0
  96. package/conformance/cases/write-no-data-kept.write.json +4 -0
  97. package/conformance/cases/write-no-rules-tag.data.json +1 -0
  98. package/conformance/cases/write-no-rules-tag.error.json +6 -0
  99. package/conformance/cases/write-no-rules-tag.html +1 -0
  100. package/conformance/cases/write-no-rules-tag.meta +4 -0
  101. package/conformance/cases/write-noop.after.html +3 -0
  102. package/conformance/cases/write-noop.after.json +3 -0
  103. package/conformance/cases/write-noop.data.json +1 -0
  104. package/conformance/cases/write-noop.html +3 -0
  105. package/conformance/cases/write-noop.meta +4 -0
  106. package/conformance/cases/write-noop.write.json +4 -0
  107. package/conformance/cases/write-outerhtml-unchanged.after.html +1 -0
  108. package/conformance/cases/write-outerhtml-unchanged.after.json +3 -0
  109. package/conformance/cases/write-outerhtml-unchanged.data.json +1 -0
  110. package/conformance/cases/write-outerhtml-unchanged.html +1 -0
  111. package/conformance/cases/write-outerhtml-unchanged.meta +4 -0
  112. package/conformance/cases/write-outerhtml-unchanged.write.json +4 -0
  113. package/conformance/cases/write-readonly-changed.data.json +1 -0
  114. package/conformance/cases/write-readonly-changed.error.json +6 -0
  115. package/conformance/cases/write-readonly-changed.html +1 -0
  116. package/conformance/cases/write-readonly-changed.meta +4 -0
  117. package/conformance/cases/write-readonly-unchanged.after.html +1 -0
  118. package/conformance/cases/write-readonly-unchanged.after.json +4 -0
  119. package/conformance/cases/write-readonly-unchanged.data.json +1 -0
  120. package/conformance/cases/write-readonly-unchanged.html +1 -0
  121. package/conformance/cases/write-readonly-unchanged.meta +4 -0
  122. package/conformance/cases/write-readonly-unchanged.write.json +4 -0
  123. package/conformance/cases/write-refused-animate.data.json +1 -0
  124. package/conformance/cases/write-refused-animate.error.json +11 -0
  125. package/conformance/cases/write-refused-animate.html +1 -0
  126. package/conformance/cases/write-refused-animate.meta +4 -0
  127. package/conformance/cases/write-refused-attr-name.data.json +1 -0
  128. package/conformance/cases/write-refused-attr-name.error.json +11 -0
  129. package/conformance/cases/write-refused-attr-name.html +1 -0
  130. package/conformance/cases/write-refused-attr-name.meta +4 -0
  131. package/conformance/cases/write-refused-data-url.data.json +1 -0
  132. package/conformance/cases/write-refused-data-url.error.json +11 -0
  133. package/conformance/cases/write-refused-data-url.html +1 -0
  134. package/conformance/cases/write-refused-data-url.meta +4 -0
  135. package/conformance/cases/write-refused-innerhtml.data.json +1 -0
  136. package/conformance/cases/write-refused-innerhtml.error.json +11 -0
  137. package/conformance/cases/write-refused-innerhtml.html +1 -0
  138. package/conformance/cases/write-refused-innerhtml.meta +4 -0
  139. package/conformance/cases/write-refused-javascript-url.data.json +1 -0
  140. package/conformance/cases/write-refused-javascript-url.error.json +11 -0
  141. package/conformance/cases/write-refused-javascript-url.html +1 -0
  142. package/conformance/cases/write-refused-javascript-url.meta +4 -0
  143. package/conformance/cases/write-refused-list-removes-rules-tag.data.json +1 -0
  144. package/conformance/cases/write-refused-list-removes-rules-tag.error.json +11 -0
  145. package/conformance/cases/write-refused-list-removes-rules-tag.html +1 -0
  146. package/conformance/cases/write-refused-list-removes-rules-tag.meta +4 -0
  147. package/conformance/cases/write-refused-multiple.data.json +1 -0
  148. package/conformance/cases/write-refused-multiple.error.json +15 -0
  149. package/conformance/cases/write-refused-multiple.html +1 -0
  150. package/conformance/cases/write-refused-multiple.meta +4 -0
  151. package/conformance/cases/write-refused-onclick.data.json +1 -0
  152. package/conformance/cases/write-refused-onclick.error.json +11 -0
  153. package/conformance/cases/write-refused-onclick.html +1 -0
  154. package/conformance/cases/write-refused-onclick.meta +4 -0
  155. package/conformance/cases/write-refused-rules-tag-ancestor.data.json +1 -0
  156. package/conformance/cases/write-refused-rules-tag-ancestor.error.json +11 -0
  157. package/conformance/cases/write-refused-rules-tag-ancestor.html +1 -0
  158. package/conformance/cases/write-refused-rules-tag-ancestor.meta +4 -0
  159. package/conformance/cases/write-refused-script.data.json +1 -0
  160. package/conformance/cases/write-refused-script.error.json +11 -0
  161. package/conformance/cases/write-refused-script.html +1 -0
  162. package/conformance/cases/write-refused-script.meta +4 -0
  163. package/conformance/cases/write-shape-mismatch.data.json +1 -0
  164. package/conformance/cases/write-shape-mismatch.error.json +12 -0
  165. package/conformance/cases/write-shape-mismatch.html +1 -0
  166. package/conformance/cases/write-shape-mismatch.meta +4 -0
  167. package/conformance/cases/write-text-basic.after.html +3 -0
  168. package/conformance/cases/write-text-basic.after.json +3 -0
  169. package/conformance/cases/write-text-basic.data.json +1 -0
  170. package/conformance/cases/write-text-basic.html +3 -0
  171. package/conformance/cases/write-text-basic.meta +4 -0
  172. package/conformance/cases/write-text-basic.write.json +4 -0
  173. package/conformance/cases/write-text-escape.after.html +1 -0
  174. package/conformance/cases/write-text-escape.after.json +3 -0
  175. package/conformance/cases/write-text-escape.data.json +1 -0
  176. package/conformance/cases/write-text-escape.html +1 -0
  177. package/conformance/cases/write-text-escape.meta +4 -0
  178. package/conformance/cases/write-text-escape.write.json +4 -0
  179. package/conformance/cases/write-text-null-clears.after.html +1 -0
  180. package/conformance/cases/write-text-null-clears.after.json +3 -0
  181. package/conformance/cases/write-text-null-clears.data.json +1 -0
  182. package/conformance/cases/write-text-null-clears.html +1 -0
  183. package/conformance/cases/write-text-null-clears.meta +4 -0
  184. package/conformance/cases/write-text-null-clears.write.json +4 -0
  185. package/conformance/cases/write-type-attribute.after.html +1 -0
  186. package/conformance/cases/write-type-attribute.after.json +3 -0
  187. package/conformance/cases/write-type-attribute.data.json +1 -0
  188. package/conformance/cases/write-type-attribute.html +1 -0
  189. package/conformance/cases/write-type-attribute.meta +4 -0
  190. package/conformance/cases/write-type-attribute.write.json +4 -0
  191. package/conformance/cases/write-unknown-key.data.json +1 -0
  192. package/conformance/cases/write-unknown-key.error.json +11 -0
  193. package/conformance/cases/write-unknown-key.html +3 -0
  194. package/conformance/cases/write-unknown-key.meta +4 -0
  195. package/conformance/cases/write-unmatched.data.json +1 -0
  196. package/conformance/cases/write-unmatched.error.json +14 -0
  197. package/conformance/cases/write-unmatched.html +1 -0
  198. package/conformance/cases/write-unmatched.meta +4 -0
  199. package/conformance/write-policy.json +78 -0
  200. package/dist/hyper-html-api.engine.min.js +1 -1
  201. package/dist/hyper-html-api.min.js +6 -4
  202. package/dist/hyper-html-api.upgrade.min.js +2 -2
  203. package/package.json +4 -3
  204. package/packed-contract.json +1 -1
  205. package/src/adapters/cheerio.js +105 -10
  206. package/src/engine/apply.js +2 -0
  207. package/src/engine/errors.js +25 -0
  208. package/src/engine/index.js +3 -0
  209. package/src/engine/splice.js +118 -0
  210. package/src/engine/write-plan.js +52 -0
  211. package/src/engine/write-policy.js +136 -0
  212. package/src/lib/content-dom.js +4 -2
  213. package/src/write.js +29 -0
  214. package/conformance/cases/props-type-is-node-type.expected.json +0 -5
  215. package/conformance/cases/props-type-is-node-type.meta +0 -11
  216. /package/conformance/cases/{props-type-is-node-type.html → props-type-is-attribute.html} +0 -0
  217. /package/conformance/cases/{props-type-is-node-type.parsed.json → props-type-is-attribute.parsed.json} +0 -0
  218. /package/conformance/cases/{props-type-is-node-type.rules → props-type-is-attribute.rules} +0 -0
package/README.md CHANGED
@@ -130,6 +130,34 @@ const { rules } = findRulesIn(cheerioAdapter, $.root(), 'api')
130
130
  const data = extract(cheerioAdapter, $.root(), rules)
131
131
  ```
132
132
 
133
+ ### Writing content back
134
+
135
+ `writeDocument` applies JSON to a document's source through its own rules tag, and
136
+ returns the new source. It is the write half of a local data API: hyperclay-local's
137
+ `POST /_/api/<file>` and HTML Clay's Go port both follow it, pinned by the `face: write`
138
+ conformance cases.
139
+
140
+ ```js
141
+ import { writeDocument } from 'hyper-html-api/write'
142
+ import * as cheerio from 'cheerio'
143
+
144
+ const { html, changed, spliced } = writeDocument(cheerio.load, src, { title: 'Groceries' })
145
+ ```
146
+
147
+ - **Content only.** Every write goes through the policy in `conformance/write-policy.json`.
148
+ Text is written as text. A write refused by the policy (script and style targets,
149
+ `on*` handlers, `javascript:`/`vbscript:`/`data:` URLs, `@innerHTML`, `@outerHTML`, the
150
+ save and region markers) throws `WriteRefused`, which lists every refusal. Nothing is
151
+ written.
152
+ - **Strict keys.** An unknown key or a rule whose selector matches nothing throws
153
+ `WriteRejected` before anything is applied. The document must have a rules tag for the
154
+ token (default `api`), or `NoRulesTag` is thrown.
155
+ - **Byte-preserving.** Only the elements that changed are re-serialized and spliced into
156
+ the original source. The result is verified by reparsing, and when that proof fails the
157
+ whole document is rendered instead (`spliced: false`). A body that changes nothing
158
+ returns the source untouched with `changed: false`.
159
+ - `no-data` and `editor-ui` regions are invisible to writes, as they are to reads.
160
+
133
161
  ## License
134
162
 
135
163
  MIT-0 (MIT No Attribution).
@@ -1,6 +1,6 @@
1
1
  {
2
- "engine": "0.6.0",
2
+ "engine": "0.8.1",
3
3
  "cheerio": "1.2.0",
4
- "cases": 33,
4
+ "cases": 71,
5
5
  "note": "Versions the expectations were generated against. Parity means parity with THIS pair; regenerate deliberately, never as a side effect of an install."
6
6
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "items": [
3
+ "A",
4
+ "B"
5
+ ]
6
+ }
@@ -0,0 +1 @@
1
+ <ul><li editor-ui>Add</li><li>A</li><li>B</li></ul>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: query
3
+ expect: ok
4
+ note: editor-ui rows are not data, so find() skips them.
@@ -0,0 +1,3 @@
1
+ {
2
+ "items": "li[]"
3
+ }
@@ -0,0 +1 @@
1
+ {items:"li[]"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "t": "Hi there"
3
+ }
@@ -0,0 +1 @@
1
+ <p id="x">Hi <span no-data>secret</span>there</p>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: query
3
+ expect: ok
4
+ note: text reads project out no-data descendants.
@@ -0,0 +1,3 @@
1
+ {
2
+ "t": "#x"
3
+ }
@@ -0,0 +1 @@
1
+ {t:"#x"}
@@ -0,0 +1,5 @@
1
+ {
2
+ "input": "text",
3
+ "script": null,
4
+ "style": null
5
+ }
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: query
3
+ expect: ok
4
+ note: "@type" reads the type attribute, matching HTML Clay. Elements with no type attribute read as null.
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{h:"#a@href"}</script><a id="a" href="obsidian://open?vault=x">x</a>
@@ -0,0 +1,3 @@
1
+ {
2
+ "h": "obsidian://open?vault=x"
3
+ }
@@ -0,0 +1 @@
1
+ {"h":"obsidian://open?vault=x"}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{h:"#a@href"}</script><a id=a href="/">x</a>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: A custom app scheme is allowed; only javascript/vbscript/data are refused.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{h:"#a@href"}</script><a id="a" href="https://example.com/?a=1&amp;b=2">x</a>
@@ -0,0 +1,3 @@
1
+ {
2
+ "h": "https://example.com/?a=1&b=2"
3
+ }
@@ -0,0 +1 @@
1
+ {"h":"https://example.com/?a=1&b=2"}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{h:"#a@href"}</script><a id=a href="/">x</a>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Attribute write: an absolute https URL passes the scheme check.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{d:"#c@checked"}</script><input id="c" type="checkbox">
@@ -0,0 +1,3 @@
1
+ {
2
+ "d": "false"
3
+ }
@@ -0,0 +1 @@
1
+ {"d":false}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{d:"#c@checked"}</script><input id=c type=checkbox checked>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Boolean property write clears the checked attribute.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{d:"#c@checked"}</script><input id="c" type="checkbox" checked="">
@@ -0,0 +1,3 @@
1
+ {
2
+ "d": "true"
3
+ }
@@ -0,0 +1 @@
1
+ {"d":true}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{d:"#c@checked"}</script><input id=c type=checkbox>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Boolean property write sets the checked attribute.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang=en>
3
+ <head><script data-rules-name="api" data-rules-version="1">{title:"h1",link:"#a@href",items:"li[]"}</script></head>
4
+ <body>
5
+ <h1 class="x">World</h1>
6
+ <p>&copy; 2026<br/></p>
7
+ <a id="a" href="/next">x</a>
8
+ <ul>
9
+ <li>A</li>
10
+ <li>B</li></ul>
11
+ </body>
12
+ </html>
@@ -0,0 +1,8 @@
1
+ {
2
+ "title": "World",
3
+ "link": "/next",
4
+ "items": [
5
+ "A",
6
+ "B"
7
+ ]
8
+ }
@@ -0,0 +1 @@
1
+ {"title":"World","link":"/next","items":["A","B"]}
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang=en>
3
+ <head><script data-rules-name="api" data-rules-version="1">{title:"h1",link:"#a@href",items:"li[]"}</script></head>
4
+ <body>
5
+ <h1 class='x'>Hello</h1>
6
+ <p>&copy; 2026<br/></p>
7
+ <a id=a href="/">x</a>
8
+ <ul>
9
+ <li>A</li>
10
+ </ul>
11
+ </body>
12
+ </html>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Three writes at once across a CRLF document: text, an attribute and a list grow.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <html lang="en"><head><script data-rules-name="api" data-rules-version="1">{l:"html@lang"}</script></head><body><p>x</p></body></html>
@@ -0,0 +1,3 @@
1
+ {
2
+ "l": "en"
3
+ }
@@ -0,0 +1 @@
1
+ {"l":"en"}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{l:"html@lang"}</script><p>x</p>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Writing the root element forces the whole-document render: spliced is false.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": false
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{t:"#l"}</script><li id="l">Buy bread</li>
@@ -0,0 +1,3 @@
1
+ {
2
+ "t": "Buy bread"
3
+ }
@@ -0,0 +1 @@
1
+ {"t":"Buy bread"}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{t:"#l"}</script><li id=l><input type=checkbox> Buy milk</li>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: A text write into an element with mixed content flattens its children to one text node.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ {"items":["A"]}
@@ -0,0 +1,8 @@
1
+ {
2
+ "type": "EmptyListInsert",
3
+ "message": "cannot add items to empty list at \"items\" — no sibling to clone as template. Seed the list with a hidden item first.",
4
+ "status": 400,
5
+ "details": [
6
+ "items"
7
+ ]
8
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul></ul>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: error
4
+ note: Inserting into a list with no row to clone is a 400, not a silent no-op.
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul><li>First</li><li cms-template=""></li></ul>
@@ -0,0 +1,5 @@
1
+ {
2
+ "items": [
3
+ "First"
4
+ ]
5
+ }
@@ -0,0 +1 @@
1
+ {"items":["First"]}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul><li cms-template></li></ul>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: A cms-template row is the clone source for a list that starts with no data rows.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{rows:["li",{name:"b",n:"i"}]}</script><ul><li id="r1"><b>A</b><i>1</i></li><li><b>B</b><i>2</i></li></ul>
@@ -0,0 +1,12 @@
1
+ {
2
+ "rows": [
3
+ {
4
+ "name": "A",
5
+ "n": "1"
6
+ },
7
+ {
8
+ "name": "B",
9
+ "n": "2"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1 @@
1
+ {"rows":[{"name":"A","n":"1"},{"name":"B","n":"2"}]}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{rows:["li",{name:"b",n:"i"}]}</script><ul><li id=r1><b>A</b><i>1</i></li></ul>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Object list rows clone the last row's shape and fill each item's rules.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1,4 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul>
2
+ <li>A</li>
3
+ <li>B</li>
4
+ <li>C</li></ul>
@@ -0,0 +1,7 @@
1
+ {
2
+ "items": [
3
+ "A",
4
+ "B",
5
+ "C"
6
+ ]
7
+ }
@@ -0,0 +1 @@
1
+ {"items":["A","B","C"]}
@@ -0,0 +1,4 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul>
2
+ <li>A</li>
3
+ <li>B</li>
4
+ </ul>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Scalar list grow clones the last row and appends, preserving CRLF bytes.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1,4 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul>
2
+
3
+ <li>B</li>
4
+ </ul>
@@ -0,0 +1,5 @@
1
+ {
2
+ "items": [
3
+ "B"
4
+ ]
5
+ }
@@ -0,0 +1 @@
1
+ {"items":["B"]}
@@ -0,0 +1,4 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul>
2
+ <li>A</li>
3
+ <li>B</li>
4
+ </ul>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Scalar list shrink removes the leading row and leaves the survivors in place.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul></ul>
@@ -0,0 +1,3 @@
1
+ {
2
+ "items": []
3
+ }
@@ -0,0 +1 @@
1
+ {"items":[]}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{items:"li[]"}</script><ul><li>A</li></ul>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: An empty list body is a legal shrink to zero rows, unlike an insert.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{t:"#x"}</script><p id="x">Bye<span no-data="">UI</span></p>
@@ -0,0 +1,3 @@
1
+ {
2
+ "t": "Bye"
3
+ }
@@ -0,0 +1 @@
1
+ {"t":"Bye"}
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{t:"#x"}</script><p id=x>Hi <span no-data>UI</span></p>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: A no-data subtree is not part of the text write and survives it.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": true,
3
+ "spliced": true
4
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "NoRulesTag",
3
+ "message": "no <script data-rules-name~=\"api\"> rules tag in this document",
4
+ "status": 400,
5
+ "details": null
6
+ }
@@ -0,0 +1 @@
1
+ <p>x</p>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: error
4
+ note: A document without a rules tag cannot be written at all.
@@ -0,0 +1,3 @@
1
+ <script data-rules-name="api" data-rules-version="1">{title:"h1"}</script>
2
+ <h1 class='x'>Hello</h1>
3
+ <p>&copy;<br/></p>
@@ -0,0 +1,3 @@
1
+ {
2
+ "title": "Hello"
3
+ }
@@ -0,0 +1 @@
1
+ {"title":"Hello"}
@@ -0,0 +1,3 @@
1
+ <script data-rules-name="api" data-rules-version="1">{title:"h1"}</script>
2
+ <h1 class='x'>Hello</h1>
3
+ <p>&copy;<br/></p>
@@ -0,0 +1,4 @@
1
+ tier: 1
2
+ face: write
3
+ expect: ok
4
+ note: Writing the value already on disk changes nothing and still splices.
@@ -0,0 +1,4 @@
1
+ {
2
+ "changed": false,
3
+ "spliced": true
4
+ }
@@ -0,0 +1 @@
1
+ <script data-rules-name="api" data-rules-version="1">{o:"#h@outerHTML"}</script><h1 id=h>Hi</h1>
@@ -0,0 +1,3 @@
1
+ {
2
+ "o": "<h1 id=\"h\">Hi</h1>"
3
+ }
@@ -0,0 +1 @@
1
+ {"o":"<h1 id=\"h\">Hi</h1>"}