@panphora/clayjs 0.2.1 → 0.4.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 (45) hide show
  1. package/README.md +103 -7
  2. package/clay-data.js +1 -1
  3. package/clay-internals.js +19 -0
  4. package/clay.js +19 -3
  5. package/package.json +5 -2
  6. package/sap.js +5 -5
  7. package/src/attrs/onaftersave.js +12 -1
  8. package/src/attrs/refetch-on-save.js +4 -4
  9. package/src/attrs/save-freeze.js +47 -19
  10. package/src/core/admin-contenteditable.js +2 -2
  11. package/src/core/admin-inputs.js +2 -2
  12. package/src/core/admin-onclick.js +2 -2
  13. package/src/core/admin-resources.js +23 -14
  14. package/src/core/autosave.js +11 -1
  15. package/src/core/edit-mode.js +2 -2
  16. package/src/core/host-attrs.js +46 -0
  17. package/src/core/is-edit-mode.js +14 -6
  18. package/src/core/persist.js +12 -2
  19. package/src/core/save-core.js +199 -290
  20. package/src/core/save.js +121 -82
  21. package/src/core/snapshot.js +89 -36
  22. package/src/core/unsaved-warning.js +8 -2
  23. package/src/dom/all.js +4 -29
  24. package/src/dom/form-data.js +1 -1
  25. package/src/dom/nearest.js +11 -9
  26. package/src/internals/index.js +69 -0
  27. package/src/lib/cache-bust.js +10 -0
  28. package/src/lib/cookie.js +50 -15
  29. package/src/lib/extension-noise.js +5 -1
  30. package/src/lib/mutation.js +65 -18
  31. package/src/lib/region-policy.js +17 -2
  32. package/src/lib/root-attrs.js +45 -0
  33. package/src/lib/throttle.js +25 -13
  34. package/src/loader.js +18 -24
  35. package/src/options/options.js +1 -3
  36. package/src/plugins/demo.js +7 -1
  37. package/src/plugins/undo.js +3 -3
  38. package/src/sync/live-sync.js +77 -76
  39. package/src/ui/dialogs.js +10 -1
  40. package/src/ui/index.js +0 -10
  41. package/src/utils/debounce.js +24 -7
  42. package/src/vendor/control-serialize.vendor.js +2 -1
  43. package/src/vendor/hyper-undo.vendor.js +1 -1
  44. package/src/vendor/hypercms.vendor.js +29 -16
  45. package/src/vendor/richclay.vendor.js +29 -22
package/README.md CHANGED
@@ -21,7 +21,24 @@ query params on the script URL:
21
21
 
22
22
  - `?plugins=` — add optional plugins: `sync`, `cms` (richclay, indicator, sortable, undo load by default in edit mode).
23
23
  - `?exclude=` — drop a default plugin.
24
- - `?editmode=false` — force view mode (URL param wins over cookies/globals).
24
+ - `?editmode=false` — force view mode (URL param wins over everything below).
25
+
26
+ ## Host attributes
27
+
28
+ A host can put these on `<html>` in the response it serves. They are ephemeral:
29
+ the host injects them and strips them back out of whatever gets saved, so they
30
+ never reach the file on disk.
31
+
32
+ - `savetoken="…"` — the per-document save credential. clayjs posts to
33
+ `/_/save/{token}` and sends no cookies, because the token is the credential.
34
+ A token also implies edit mode, which is the only such signal a sandboxed
35
+ document can see. `htmlclaytoken` is the older spelling of the same thing.
36
+ - `clay-save-transport="desktop-json-v1"` — send the save as
37
+ `{content, snapshotHtml, userDriven}` JSON instead of plain text. Only declare
38
+ it on a host whose save lane reads that envelope.
39
+
40
+ Edit mode is decided in this order: the `?editmode` param, then
41
+ `window.clayEditMode`, then a save token, then the platform's owner cookie.
25
42
 
26
43
  ## Readiness
27
44
 
@@ -33,11 +50,64 @@ await clay.ready; // or: document.addEventListener("clay:ready", ...)
33
50
  clay.save();
34
51
  ```
35
52
 
36
- Edit mode exposes `clay.save()` (+ `clay.save.force()`), `clay.getHTML()`, `clay.beforeSave(fn)`,
53
+ Edit mode exposes `clay.save()` (+ `clay.save.force()`), `clay.getHTML()`, `clay.addDocumentTransform(fn)`,
37
54
  `clay.onSnapshot(fn)`, `clay.toggleEditMode()`, `clay.isEditMode`, `clay.isOwner`, `clay.Mutation`,
38
- `clay.cacheBust(el)`, plus `clay.undo` / `clay.cms` / `clay.morph` when those plugins load. View
39
- mode keeps only the always-available members (`toggleEditMode`, `isEditMode`, `isOwner`,
40
- `Mutation`, `ready`); edit-only members are simply absent.
55
+ `clay.region`, `clay.cacheBust(el)`, plus `clay.undo` / `clay.cms` / `clay.morph` / `clay.RichClay`
56
+ when those plugins load. View mode keeps only the always-available members (`toggleEditMode`, `isEditMode`, `isOwner`,
57
+ `Mutation`, `region`, `ready`); edit-only members are simply absent.
58
+
59
+ ## API
60
+
61
+ Three tiers. The first two are a promise; the third is not.
62
+
63
+ | Tier | What it is | Promise |
64
+ |---|---|---|
65
+ | `clay.*` from `clay.js` | the everyday surface | stable |
66
+ | `clay.*` from a satellite (`clay-ui`, `clay-utils`, `clay-dom`, `clay-events`, `clay-options`, `clay-internals`) | opt-in, one script tag each | stable |
67
+ | anything else under `src/` | reachable by direct import, because `src/` ships | **may change in any release** |
68
+
69
+ The contract starts at **0.3.0**: no name below changes without a major version.
70
+
71
+ **`clay.js`** — `ready`, `save()`, `save.force()`, `getHTML()`, `addDocumentTransform(fn)`, `onSnapshot(fn)`,
72
+ `toggleEditMode()`, `isEditMode`, `isOwner`, `Mutation`, `region`, `cacheBust(el)`, plus `undo` / `cms` / `morph` /
73
+ `RichClay` when those plugins load. View mode keeps only the always-available members, as above.
74
+
75
+ `addDocumentTransform(fn)` runs your callback over a detached clone whenever the page
76
+ prepares to save AND whenever it checks whether anything changed. Keep it pure and
77
+ repeatable: it is a transform, not a "a save is happening" event.
78
+
79
+ `region` is the region-policy model: `resolveRegionPolicy`, `isInert`, `skipForPolicy`,
80
+ `strictestPolicy`, the `PERSIST` and `REGION_ATTRS` token constants, and the
81
+ `STRIP_FROM_SAVE`, `FREEZE_SELECTOR` and `STRIP_FROM_COMPARISON` selectors.
82
+ `clay.internals.region` is a different shape, narrower in aim, built for snapshot work.
83
+
84
+ **Satellites** — one script tag each, and each resolves its own `clay.loaded.*` promise. `clay-ui` adds
85
+ `toast`, `toastPersistent`, `ask`, `confirm`, `tell`, `snippet`, `modal`; `clay-utils` adds `clay.utils`
86
+ (`throttle`, `debounce`, `cookie`, `slugify`, `copyToClipboard`); `clay-internals` adds `clay.internals`,
87
+ below. `clay-events`, `clay-dom`, `clay-options` and `all.js` add HTML attributes and DOM helpers rather
88
+ than members on `clay`.
89
+
90
+ **`clay.internals`** — the pieces the library builds itself out of, for code that needs to sit inside the
91
+ save lifecycle rather than call it. Lower level than `clay.*` deliberately: it assumes you know the
92
+ lifecycle.
93
+
94
+ ```html
95
+ <script src="https://clayjs.com/clay-internals.js"></script>
96
+ ```
97
+
98
+ - `captureSnapshot()`, `captureForSave()` — the snapshot pipeline, read side.
99
+ `captureSnapshot` gives you the clone before any stripping; `captureForSave` gives you the
100
+ bytes a save would send.
101
+ - `addDocumentTransform(fn)` — the same registry as `clay.addDocumentTransform`. Core only
102
+ publishes that name in edit mode, so reach for this one from a view-mode page or from the
103
+ satellite with no core loaded.
104
+ - `region.addRegionToken(el, token)`, `region.resolveRegionPolicy(node)`, `region.isInert(node)`,
105
+ `region.isSnapshotRemoved(el)`, `region.PERSIST`, `region.REGION_ATTRS`, and
106
+ `region.selectors.stripFromSave` / `.stripFromComparison` / `.snapshotRemove` / `.freeze` — write your
107
+ own attribute without hardcoding our selectors.
108
+ - `save.saveHtml(html, cb, opts)`, `save.replacePageWith(url, cb)`, `save.isSaveInProgress()` — the save
109
+ lane under `clay.save`. **`saveHtml` writes the bytes you hand it straight to the file**, bypassing the
110
+ snapshot pipeline entirely; check `isSaveInProgress()` first.
41
111
 
42
112
  ## Regions
43
113
 
@@ -54,6 +124,32 @@ Add `autosave` to `<html>` to save automatically on change.
54
124
  ## Develop
55
125
 
56
126
  ```bash
57
- npm test # jest unit suite
58
- npm run dev # stub save server on :4601 for the tests/fixtures pages
127
+ npm test # jest unit suite
128
+ npm run test:conformance # byte-for-byte fixture gate, in a real browser
129
+ npm run dev # stub save server on :4601 for the tests/fixtures pages
59
130
  ```
131
+
132
+ ## Deploy
133
+
134
+ `public/` is what wrangler serves to clayjs.com. It is a build output: gitignored,
135
+ disposable, and rebuilt from scratch every time.
136
+
137
+ ```bash
138
+ npm run build # rebuild public/ from source
139
+ npm run deploy # rebuild, then wrangler deploy
140
+ ```
141
+
142
+ Never edit `public/` by hand, and never copy a file into it. Its contents are
143
+ **derived**:
144
+
145
+ ```
146
+ public/ = package.json "files" (minus NOTICE, which nothing requests)
147
+ + website/* (flattened to the root, so /docs.html works)
148
+ ```
149
+
150
+ That derivation is the point. `files` is the list npm publishes, so it is already
151
+ the line you edit to ship a new satellite, and deriving from it means a file cannot
152
+ reach npm and miss the site. Before this script existed the mirror was hand-copied,
153
+ and it drifted exactly that way: `src/core/host-attrs.js` never made it across, and
154
+ since `loader.js` imports `is-edit-mode.js`, which imports it, the deployed
155
+ `clay.js` could not boot at all.
package/clay-data.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // hyper-html-api/dist/hyper-html-api.engine.min.js via hyper-html-api
3
3
  // `npm run copy-to-clayjs`. Edit the hyper-html-api source and re-run.
4
4
  (function () {
5
- var hyperHtmlApiData=(()=>{var k=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var ct=Object.getOwnPropertyNames;var ft=Object.prototype.hasOwnProperty;var K=(t,e)=>{for(var n in e)k(t,n,{get:e[n],enumerable:!0})},ut=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ct(e))!ft.call(t,r)&&r!==n&&k(t,r,{get:()=>e[r],enumerable:!(o=lt(e,r))||o.enumerable});return t};var at=t=>ut(k({},"__esModule",{value:!0}),t);var wt={};K(wt,{applyData:()=>X,default:()=>It,engine:()=>P,extractData:()=>G});var C=["textContent","innerText","innerHTML","outerHTML","value","checked","selected","disabled","readOnly","type","tagName","nodeName","nodeType","nodeValue","childElementCount","id","className","classList","baseURI","offsetWidth","offsetHeight","clientWidth","clientHeight","scrollWidth","scrollHeight","dataset","currentSrc","duration","paused","title","documentURI","contentType"],B=new Set(C),Y=new Set(["textContent","innerText","innerHTML","value","checked","selected","disabled","readOnly","type","id","className","title"]),Q=new Set(["tagName","nodeName","nodeType","nodeValue","childElementCount","classList","baseURI","documentURI","contentType","offsetWidth","offsetHeight","clientWidth","clientHeight","scrollWidth","scrollHeight","currentSrc","duration","paused","dataset"]);var j={};K(j,{EmptyListInsert:()=>I,MAX_RULE_DEPTH:()=>A,MaxRuleDepthExceeded:()=>E,RuleTargetReadOnly:()=>w,RulesParseError:()=>S,ShapeMismatch:()=>b,UnknownRulesVersion:()=>N});var S=class extends Error{constructor(e,n){super(e),this.name="RulesParseError",this.cause=n}},N=class extends Error{constructor(e){super(`unknown rules version: ${e}. Library supports "1".`),this.name="UnknownRulesVersion",this.version=e}},A=20,E=class extends Error{constructor(e){super(`rule depth exceeded ${A} at path: ${e.join(".")}`),this.name="MaxRuleDepthExceeded",this.path=e}},b=class extends Error{constructor(e){super(`shape mismatch: ${e.length} field(s) failed validation`),this.name="ShapeMismatch",this.mismatches=e}},I=class extends Error{constructor(e){super(`cannot add items to empty list at "${e.join(".")}" \u2014 no sibling to clone as template. Seed the list with a hidden item first.`),this.name="EmptyListInsert",this.path=e}},w=class extends Error{constructor(e){super(`cannot write to read-only DOM property "${e}"`),this.name="RuleTargetReadOnly",this.target=e}};function g(t,e,n,o={}){return U(t,e,n,{depth:0,path:[]},o)}function U(t,e,n,o,r){if(o.depth>A)throw new E(o.path);if(typeof n=="string")return ht(t,e,n,r);if(Array.isArray(n)){let[i,c]=n;return t.find(e,i,r).map((l,a)=>U(t,l,c,{depth:o.depth+1,path:[...o.path,a]},r))}if(typeof n=="object"&&n!==null){let i={};for(let[c,s]of Object.entries(n))i[c]=U(t,e,s,{depth:o.depth+1,path:[...o.path,c]},r);return i}return null}function ht(t,e,n,o){if(n.endsWith("[]")){let i=n.slice(0,-2);return t.find(e,i,o).map(c=>t.text(c))}if(n.startsWith("@"))return Z(t,e,n.slice(1));if(n.includes("@")){let i=n.lastIndexOf("@"),c=n.slice(0,i),s=n.slice(i+1),l=c?t.find(e,c,o):[e];return l.length===0?null:Z(t,l[0],s)}if(n===".")return t.text(e);let r=t.find(e,n,o);return r.length===0?null:t.text(r[0])}function Z(t,e,n){if(B.has(n)){let r=t.prop(e,n);return r==null?null:String(r)}let o=t.attr(e,n);return o||null}var dt=.5;function q(t,e,n,o,r,i,c,s={}){let l=t.find(e,n,s);if(r.length===0){l.forEach(h=>t.remove(h));return}let a=r.length>l.length,u=l[0]||null;if(a&&!u&&(u=Et(t,e,n,s),!u))throw new I(i.path);let d=l.map(h=>pt(t,h,o,s)),f=null;if(u){f=t.clone(u),s.templateAttr&&t.removeAttr(f,s.templateAttr);let h=t.stripIds(f);h>0&&console.warn(`[hyper-html-api] stripped ${h} id attribute(s) from cloned template at "${i.path.join(".")||"(root)"}"`)}let H=mt(r,d,o),R=l[0]||u,L=t.parent(R),it=l.length>0?gt(t,L,R):0,z=new Set,W=r.map((h,m)=>{let p=H[m];if(p>=0)return z.add(p),l[p];let O=t.clone(f);return t.stripIds(O),O});l.forEach((h,m)=>{z.has(m)||t.remove(h)}),W.forEach((h,m)=>{let p=it+m;t.children(L).findIndex(st=>t.sameNode(st,h))!==p&&t.insertAt(L,h,p)}),W.forEach((h,m)=>{if(o===null){let p=r[m],O=p==null?"":String(p);t.text(h)!==O&&t.text(h,O)}else{let p=c(t,h,o,r[m],{depth:i.depth+1,path:[...i.path,m]},s);p&&p!==h&&(W[m]=p)}})}function pt(t,e,n,o){return n===null?t.text(e):g(t,e,n,o)}function mt(t,e,n){let o=new Array(t.length).fill(-1),r=new Set;return t.forEach((i,c)=>{let s=-1,l=-1;e.forEach((a,u)=>{if(r.has(u))return;let d=yt(i,a,n),f=d===l&&s>=0?Math.abs(u-c)<Math.abs(s-c):!1;(d>l||f)&&(l=d,s=u)}),l>=dt&&(o[c]=s,r.add(s))}),o}function yt(t,e,n){if(n===null)return t===e?1:0;let o=Object.keys(n||{});if(o.length===0)return 0;let r=0;for(let i of o)JSON.stringify(t?.[i])===JSON.stringify(e?.[i])&&r++;return r/o.length}function gt(t,e,n){let o=t.children(e);for(let r=0;r<o.length;r++)if(t.sameNode(o[r],n))return r;return-1}function Et(t,e,n,o){if(!o.templateAttr)return null;let r=e;for(;r;){let i=t.find(r,n,{includeRulesTag:!1,templateAttr:null});for(let c of i)if(t.attr(c,o.templateAttr)!=null)return c;r=t.parent(r)}return null}var tt=new Set(["checked","selected","disabled","readOnly","paused"]);function T(t,e,n,o,r={}){let i=[];if(v(n,o,[],i),i.length)throw new b(i);$(t,e,n,o,{depth:0,path:[]},r)}function $(t,e,n,o,r,i={}){if(r.depth>A)throw new E(r.path);if(o===void 0)return e;if(typeof n=="string")return Rt(t,e,n,o,r,i);if(Array.isArray(n)){let[c,s]=n;return q(t,e,c,s,o,r,$,i),e}if(typeof n=="object"&&n!==null){for(let[c,s]of Object.entries(n)){let l=$(t,e,s,o==null?o:o[c],{depth:r.depth+1,path:[...r.path,c]},i);l&&l!==e&&(e=l)}return e}return e}function Rt(t,e,n,o,r,i){if(n.endsWith("[]")){let s=n.slice(0,-2);return q(t,e,s,null,o,r,$,i),e}if(n.startsWith("@"))return et(t,e,n.slice(1),o);if(n.includes("@")){let s=n.lastIndexOf("@"),l=n.slice(0,s),a=n.slice(s+1),u=l?t.find(e,l,i):[e];return u.length===0||et(t,u[0],a,o),e}if(n===".")return t.text(e,o==null?"":String(o)),e;let c=t.find(e,n,i);return c.length===0||t.text(c[0],o==null?"":String(o)),e}function et(t,e,n,o){if(Q.has(n))throw new w(n);if(n==="outerHTML"){let r=o==null?"":String(o);return t.replaceWith(e,r)}return Y.has(n)?(t.prop(e,n,Ot(n,o)),e):(t.attr(e,n,o==null?"":String(o)),e)}function Ot(t,e){return e==null?tt.has(t)?!1:"":tt.has(t)?!!e:e}function v(t,e,n,o){if(e!==void 0){if(typeof t=="string"){if(t.endsWith("[]")){Array.isArray(e)?e.forEach((r,i)=>{typeof r=="object"&&r!==null&&o.push({path:_([...n,i]),expected:"scalar",got:M(r)})}):o.push({path:_(n),expected:"array",got:M(e)});return}e!==null&&typeof e=="object"&&o.push({path:_(n),expected:"scalar",got:M(e)});return}if(Array.isArray(t)){if(!Array.isArray(e)){o.push({path:_(n),expected:"array",got:M(e)});return}let r=t[1];e.forEach((i,c)=>v(r,i,[...n,c],o));return}if(typeof t=="object"&&t!==null){if(e===null||Array.isArray(e)||typeof e!="object"){o.push({path:_(n),expected:"object",got:M(e)});return}for(let[r,i]of Object.entries(t))v(i,e[r],[...n,r],o)}}}function M(t){return t===null?"null":Array.isArray(t)?"array":typeof t}function _(t){return t.join(".")}function J(t){try{return JSON.parse(t)}catch(e){throw new S(`Invalid strict JSON: ${e.message}`,e)}}function D(t){try{return JSON.parse(t)}catch{}let e={BRACE_OPEN:"{",BRACE_CLOSE:"}",BRACKET_OPEN:"[",BRACKET_CLOSE:"]",COLON:":",COMMA:",",STRING:"STRING",SELECTOR:"SELECTOR",IDENTIFIER:"IDENTIFIER",NUMBER:"NUMBER",BOOLEAN:"BOOLEAN"};function n(r){let i=[],c=0;for(;c<r.length;){let s=r[c];if(/\s/.test(s)){c++;continue}if("{}".includes(s)){i.push({type:s,value:s}),c++;continue}if(s==="["){let d=!1,f=c+1;for(;f<r.length&&/\s/.test(r[f]);)f++;if(f<r.length&&/[a-zA-Z_]/.test(r[f])&&(d=!0),!d){i.push({type:s,value:s}),c++;continue}}if(s==="]"){i.push({type:s,value:s}),c++;continue}if(s===":"){i.push({type:e.COLON,value:s}),c++;continue}if(s===","){i.push({type:e.COMMA,value:s}),c++;continue}if(s==='"'||s==="'"){let d=s,f=c+1;for(;f<r.length&&r[f]!==d;)r[f]==="\\"&&f++,f++;i.push({type:e.STRING,value:r.substring(c+1,f),quoted:!0,sourceQuote:d}),c=f+1;continue}let l=c,a;for(;l<r.length&&!/[{},]/.test(r[l]);)if(r[l]===":"){let d=[":first",":last",":nth-child",":nth-of-type",":first-child",":last-child",":first-of-type",":last-of-type",":only-child",":only-of-type",":hover",":focus",":active",":visited",":disabled",":enabled",":checked",":empty",":root",":target",":not",":before",":after",":nth-last-child",":nth-last-of-type"],f=!1;for(let H of d){let R=H.substring(1);if(r.substring(l+1,l+1+R.length)===R){f=!0,l+=R.length;break}}if(!f)break}else if(r[l]==="["){for(l++;l<r.length&&r[l]!=="]";){if(r[l]==='"'||r[l]==="'"){let d=r[l];for(l++;l<r.length&&r[l]!==d;)r[l]==="\\"&&l++,l++}l++}l<r.length&&r[l]==="]"&&l++}else l++;a=r.substring(c,l);let u=e.IDENTIFIER;/^-?\d+(\.\d+)?$/.test(a)?u=e.NUMBER:a==="true"||a==="false"||a==="null"?u=e.BOOLEAN:/^[.#@\[]|[.#@\[]| /.test(a)&&(u=e.SELECTOR),i.push({type:u,value:a,quoted:!1}),c=l}return i}function o(r){let i="";for(let c=0;c<r.length;c++){let s=r[c];if("{}".includes(s.type)||"[]".includes(s.type)){i+=s.value;continue}if(s.type===e.COLON){i+=s.value;continue}if(s.type===e.COMMA){let l=r[c+1];if(l&&(l.type==="}"||l.type==="]"))continue;i+=s.value;continue}if(s.type===e.STRING&&s.quoted){let l=s.value;s.sourceQuote==="'"&&(l=l.replace(/\\'/g,"'"),l=l.replace(/(\\*)"/g,(a,u)=>u.length%2===0?u+'\\"':a)),i+=`"${l}"`;continue}if(s.type===e.NUMBER||s.type===e.BOOLEAN){i+=s.value;continue}if(s.type===e.SELECTOR||s.type===e.IDENTIFIER){i+=`"${s.value}"`;continue}i+=`"${s.value}"`}return i}try{let r=n(t),i=o(r);return JSON.parse(i)}catch(r){throw new S("Invalid extraction rules syntax: "+r.message,r)}}var St="1",nt=/^[a-zA-Z0-9_-]+$/;function x(t,e,n){let o;if(n===void 0)o="script[data-rules-name]";else{if(typeof n!="string"||!nt.test(n))throw new Error(`hyper-html-api: invalid rules token ${JSON.stringify(n)} (must match ${nt})`);o=`script[data-rules-name~="${n}"]`}let r=t.find(e,o,{includeRulesTag:!0});if(r.length===0)return null;n!==void 0&&r.length>1&&console.warn(`hyper-html-api: ${r.length} rules tags match data-rules-name~="${n}"; using the first.`);let i=r[0],c=t.attr(i,"data-rules-version");if(c!==St)throw new N(c);return{rules:D(t.text(i)),tagNode:i}}function V(t,e,n){if(n&&typeof n=="object")return{rules:n,tagNode:null};if(typeof n=="string"){let o=e&&e.ownerDocument?e.ownerDocument:e;return x(t,o,n)}return null}function rt(t,e,n,o){let r=V(t,e,n);if(!r){let s=typeof n=="string"?`data-rules-name~="${n}"`:"the provided rules object";throw new Error(`hyper-html-api: could not resolve rules for ${s}`)}let{rules:i,tagNode:c}=r;return{rules:i,tagNode:c,get:()=>g(t,e,i,o),set:s=>T(t,e,i,s,o)}}function Tt(t){return t&&t.nodeType===1&&t.tagName==="SCRIPT"&&t.hasAttribute&&t.hasAttribute("data-rules-name")}function xt(t){return t?(t.nodeType===9||t.nodeType===11,t):null}var Nt={find(t,e,n={}){let o=xt(t);if(!o||!o.querySelectorAll)return[];let r=Array.from(o.querySelectorAll(e));n.includeRulesTag||(r=r.filter(s=>!Tt(s)));let i=[];n.skip&&i.push(n.skip);let c=n.templateAttr===null?null:n.templateAttr||"cms-template";if(c&&i.push("["+c+"]"),i.length){let s=i.join(", ");r=r.filter(l=>!l.closest||!l.closest(s))}return r},parent(t){return t?t.parentElement:null},children(t){return t?Array.from(t.children):[]},text(t,e){if(e===void 0)return(t.textContent||"").trim();t.textContent=e},attr(t,e,n){if(n===void 0)return t.hasAttribute&&t.hasAttribute(e)?t.getAttribute(e):null;t.setAttribute(e,n)},removeAttr(t,e){t&&t.removeAttribute&&t.removeAttribute(e)},prop(t,e,n){if(n===void 0){let o=t?t[e]:void 0;return o!==void 0?o:null}t[e]=n},clone(t){return t.cloneNode(!0)},insertAt(t,e,n){let o=t.children[n]||null;t.insertBefore(e,o)},remove(t){t&&t.parentNode&&t.parentNode.removeChild(t)},replaceWith(t,e){if(!t||!t.parentNode)throw new Error("dom.replaceWith: node has no parent");let o=t.ownerDocument.createElement("template");o.innerHTML=e;let r=o.content.firstElementChild;if(!r)throw new Error("dom.replaceWith: html did not parse to an element");return t.parentNode.replaceChild(r,t),r},stripIds(t){let e=0;return t.id&&(t.removeAttribute("id"),e++),(t.querySelectorAll?t.querySelectorAll("[id]"):[]).forEach(o=>{o.removeAttribute("id"),e++}),e},sameNode(t,e){return t===e}},y=Nt;var P={extract:(t,e,n)=>g(y,t,e,n),apply:(t,e,n,o)=>T(y,t,e,n,o),findRulesIn:(t,e)=>x(y,t,e),findRules:(t,e)=>V(y,t,e),bind:(t,e,n)=>rt(y,t,e,n),parseStrict:J,parseRelaxed:D,errors:j,DOM_PROPERTIES:C},F=t=>!!t&&typeof t.nodeType=="number";function ot(t){let e=t&&t.ownerDocument?t.ownerDocument:t,n=y.find(e,"script[data-rules-name]",{includeRulesTag:!0});if(n.length===0)throw new Error('hyper-html-api: no rules tag found. Add <script type="application/json" data-rules-name="\u2026" data-rules-version="1"> or pass rules.');if(n.length>1)throw new Error('hyper-html-api: multiple rules tags found; pass a name, e.g. extractData("api").');return x(y,e).rules}function G(t,e){let n=F(t)?t:document,o=F(t)?e:t;return o===void 0?g(y,n,ot(n)):P.bind(n,o).get()}function X(t,e,n){if(!F(t))throw new Error("hyper-html-api: applyData(root, data, source?) needs a DOM root as the first argument.");return n===void 0?T(y,t,ot(t),e):P.bind(t,n).set(e),t}var bt={engine:P,extractData:G,applyData:X},It=bt;return at(wt);})();
5
+ var hyperHtmlApiData=(()=>{var U=Object.defineProperty;var pt=Object.getOwnPropertyDescriptor;var dt=Object.getOwnPropertyNames;var mt=Object.prototype.hasOwnProperty;var tt=(t,e)=>{for(var n in e)U(t,n,{get:e[n],enumerable:!0})},gt=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of dt(e))!mt.call(t,r)&&r!==n&&U(t,r,{get:()=>e[r],enumerable:!(o=pt(e,r))||o.enumerable});return t};var yt=t=>gt(U({},"__esModule",{value:!0}),t);var jt={};tt(jt,{applyData:()=>Y,default:()=>Ct,engine:()=>L,extractData:()=>K});var j=["textContent","innerText","innerHTML","outerHTML","value","checked","selected","disabled","readOnly","type","tagName","nodeName","nodeType","nodeValue","childElementCount","id","className","classList","baseURI","offsetWidth","offsetHeight","clientWidth","clientHeight","scrollWidth","scrollHeight","dataset","currentSrc","duration","paused","title","documentURI","contentType"],q=new Set(j),et=new Set(["textContent","innerText","innerHTML","value","checked","selected","disabled","readOnly","type","id","className","title"]),nt=new Set(["tagName","nodeName","nodeType","nodeValue","childElementCount","classList","baseURI","documentURI","contentType","offsetWidth","offsetHeight","clientWidth","clientHeight","scrollWidth","scrollHeight","currentSrc","duration","paused","dataset"]);var $={};tt($,{EmptyListInsert:()=>w,MAX_RULE_DEPTH:()=>O,MaxRuleDepthExceeded:()=>y,RuleTargetReadOnly:()=>M,RulesParseError:()=>A,ShapeMismatch:()=>N,UnknownRulesVersion:()=>b});var A=class extends Error{constructor(e,n){super(e),this.name="RulesParseError",this.cause=n}},b=class extends Error{constructor(e){super(`unknown rules version: ${e}. Library supports "1".`),this.name="UnknownRulesVersion",this.version=e}},O=20,y=class extends Error{constructor(e){super(`rule depth exceeded ${O} at path: ${e.join(".")}`),this.name="MaxRuleDepthExceeded",this.path=e}},N=class extends Error{constructor(e){super(`shape mismatch: ${e.length} field(s) failed validation`),this.name="ShapeMismatch",this.mismatches=e}},w=class extends Error{constructor(e){super(`cannot add items to empty list at "${e.join(".")}" \u2014 no sibling to clone as template. Seed the list with a hidden item first.`),this.name="EmptyListInsert",this.path=e}},M=class extends Error{constructor(e){super(`cannot write to read-only DOM property "${e}"`),this.name="RuleTargetReadOnly",this.target=e}};function m(t){let e=0,n=null,o=-1;for(let r=0;r<t.length;r++){let i=t[r];i==="\\"?r++:n?i===n&&(n=null):i==='"'||i==="'"?n=i:i==="["||i==="("?e++:i==="]"||i===")"?e>0&&e--:i==="@"&&e===0&&(o=r)}return o}function v(t){let e=m(t);return e===-1?{selector:t,prop:null}:{selector:t.slice(0,e),prop:t.slice(e+1)||null}}function g(t,e,n,o={}){return J(t,e,n,{depth:0,path:[]},o)}function J(t,e,n,o,r){if(o.depth>O)throw new y(o.path);if(typeof n=="string")return Et(t,e,n,r);if(Array.isArray(n)){let[i,c]=n;return t.find(e,i,r).map((l,f)=>J(t,l,c,{depth:o.depth+1,path:[...o.path,f]},r))}if(typeof n=="object"&&n!==null){let i={};for(let[c,s]of Object.entries(n))i[c]=J(t,e,s,{depth:o.depth+1,path:[...o.path,c]},r);return i}return null}function Et(t,e,n,o){if(n.endsWith("[]")){let c=n.slice(0,-2);return t.find(e,c,o).map(s=>t.text(s))}if(n.startsWith("@"))return rt(t,e,n.slice(1));let r=m(n);if(r!==-1){let c=n.slice(0,r),s=n.slice(r+1),l=c?t.find(e,c,o):[e];return l.length===0?null:rt(t,l[0],s)}if(n===".")return t.text(e);let i=t.find(e,n,o);return i.length===0?null:t.text(i[0])}function rt(t,e,n){if(q.has(n)){let r=t.prop(e,n);return r==null?null:String(r)}let o=t.attr(e,n);return o||null}var Rt=.5;function F(t,e,n,o,r,i,c,s={}){let l=t.find(e,n,s);if(r.length===0){l.forEach(p=>t.remove(p));return}let f=r.length>l.length,u=l[0]||null;if(f&&!u&&(u=xt(t,e,n,s),!u))throw new w(i.path);let a=l.map(p=>At(t,p,o,s)),h=null;if(u){h=t.clone(u),s.templateAttr&&t.removeAttr(h,s.templateAttr);let p=t.stripIds(h);p>0&&console.warn(`[hyper-html-api] stripped ${p} id attribute(s) from cloned template at "${i.path.join(".")||"(root)"}"`)}let k=Ot(r,a,o),E=l[0]||u,T=t.parent(E),Q=l.length>0?it(t,T,E):0,ht=Tt(t,l),Z=new Set,W=[],C=r.map((p,I)=>{let R=k[I];if(R>=0)return Z.add(R),W.push(!1),l[R];W.push(!0);let B=t.clone(h);return t.stripIds(B),B});if(l.forEach((p,I)=>{Z.has(I)||t.remove(p)}),ht){C.forEach((p,I)=>{let R=Q+I;t.children(T).findIndex(at=>t.sameNode(at,p))!==R&&t.insertAt(T,p,R)}),ot(t,C,o,r,i,c,s);return}It(t,C,W,T,Q),ot(t,C,o,r,i,c,s)}function ot(t,e,n,o,r,i,c){e.forEach((s,l)=>{if(n===null){let f=o[l],u=f==null?"":String(f);t.text(s)!==u&&t.text(s,u)}else{let f=i(t,s,n,o[l],{depth:r.depth+1,path:[...r.path,l]},c);f&&f!==s&&(e[l]=f)}})}function At(t,e,n,o){return n===null?t.text(e):g(t,e,n,o)}function Ot(t,e,n){let o=new Array(t.length).fill(-1),r=new Set;return t.forEach((i,c)=>{let s=-1,l=-1;e.forEach((f,u)=>{if(r.has(u))return;let a=St(i,f,n),h=a===l&&s>=0?Math.abs(u-c)<Math.abs(s-c):!1;(a>l||h)&&(l=a,s=u)}),l>=Rt&&(o[c]=s,r.add(s))}),o}function St(t,e,n){if(n===null)return t===e?1:0;let o=Object.keys(n||{});if(o.length===0)return 0;let r=0;for(let i of o)JSON.stringify(t?.[i])===JSON.stringify(e?.[i])&&r++;return r/o.length}function it(t,e,n){let o=t.children(e);for(let r=0;r<o.length;r++)if(t.sameNode(o[r],n))return r;return-1}function xt(t,e,n,o){if(!o.templateAttr)return null;let r=e;for(;r;){let i=t.find(r,n,{includeRulesTag:!1,templateAttr:null});for(let c of i)if(t.attr(c,o.templateAttr)!=null)return c;r=t.parent(r)}return null}function Tt(t,e){if(e.length<=1)return!0;let n=t.parent(e[0]);if(!n)return!1;let o=t.children(n),r=[];for(let i of e){let c=o.findIndex(s=>t.sameNode(s,i));if(c===-1)return!1;r.push(c)}return r.sort((i,c)=>i-c),r[r.length-1]-r[0]===r.length-1}function It(t,e,n,o,r){let i=null,c=r;for(let s=0;s<e.length;s++){if(!n[s]){i=e[s];continue}let l=i?t.parent(i):o;if(!l)continue;let f=i?it(t,l,i)+1:c++;t.insertAt(l,e[s],f),i=e[s]}}var st=new Set(["checked","selected","disabled","readOnly","paused"]);function S(t,e,n,o,r={}){let i=[];if(V(n,o,[],i),i.length)throw new N(i);H(t,e,n,o,{depth:0,path:[]},r)}function H(t,e,n,o,r,i={}){if(r.depth>O)throw new y(r.path);if(o===void 0)return e;if(typeof n=="string")return bt(t,e,n,o,r,i);if(Array.isArray(n)){let[c,s]=n;return F(t,e,c,s,o,r,H,i),e}if(typeof n=="object"&&n!==null){for(let[c,s]of Object.entries(n)){let l=H(t,e,s,o==null?o:o[c],{depth:r.depth+1,path:[...r.path,c]},i);l&&l!==e&&(e=l)}return e}return e}function bt(t,e,n,o,r,i){if(n.endsWith("[]")){let l=n.slice(0,-2);return F(t,e,l,null,o,r,H,i),e}if(n.startsWith("@"))return lt(t,e,n.slice(1),o);let c=m(n);if(c!==-1){let l=n.slice(0,c),f=n.slice(c+1),u=l?t.find(e,l,i):[e];return u.length===0||lt(t,u[0],f,o),e}if(n===".")return t.text(e,o==null?"":String(o)),e;let s=t.find(e,n,i);return s.length===0||t.text(s[0],o==null?"":String(o)),e}function lt(t,e,n,o){if(nt.has(n))throw new M(n);if(n==="outerHTML"){let r=o==null?"":String(o);return t.replaceWith(e,r)}return et.has(n)?(t.prop(e,n,Nt(n,o)),e):(t.attr(e,n,o==null?"":String(o)),e)}function Nt(t,e){return e==null?st.has(t)?!1:"":st.has(t)?!!e:e}function V(t,e,n,o){if(e!==void 0){if(typeof t=="string"){if(t.endsWith("[]")){Array.isArray(e)?e.forEach((r,i)=>{typeof r=="object"&&r!==null&&o.push({path:D([...n,i]),expected:"scalar",got:_(r)})}):o.push({path:D(n),expected:"array",got:_(e)});return}e!==null&&typeof e=="object"&&o.push({path:D(n),expected:"scalar",got:_(e)});return}if(Array.isArray(t)){if(!Array.isArray(e)){o.push({path:D(n),expected:"array",got:_(e)});return}let r=t[1];e.forEach((i,c)=>V(r,i,[...n,c],o));return}if(typeof t=="object"&&t!==null){if(e===null||Array.isArray(e)||typeof e!="object"){o.push({path:D(n),expected:"object",got:_(e)});return}for(let[r,i]of Object.entries(t))V(i,e[r],[...n,r],o)}}}function _(t){return t===null?"null":Array.isArray(t)?"array":typeof t}function D(t){return t.join(".")}function G(t){try{return JSON.parse(t)}catch(e){throw new A(`Invalid strict JSON: ${e.message}`,e)}}function P(t){try{return JSON.parse(t)}catch{}let e={BRACE_OPEN:"{",BRACE_CLOSE:"}",BRACKET_OPEN:"[",BRACKET_CLOSE:"]",COLON:":",COMMA:",",STRING:"STRING",SELECTOR:"SELECTOR",IDENTIFIER:"IDENTIFIER",NUMBER:"NUMBER",BOOLEAN:"BOOLEAN"};function n(r){let i=[],c=0;for(;c<r.length;){let s=r[c];if(/\s/.test(s)){c++;continue}if("{}".includes(s)){i.push({type:s,value:s}),c++;continue}if(s==="["){let a=!1,h=c+1;for(;h<r.length&&/\s/.test(r[h]);)h++;if(h<r.length&&/[a-zA-Z_]/.test(r[h])&&(a=!0),!a){i.push({type:s,value:s}),c++;continue}}if(s==="]"){i.push({type:s,value:s}),c++;continue}if(s===":"){i.push({type:e.COLON,value:s}),c++;continue}if(s===","){i.push({type:e.COMMA,value:s}),c++;continue}if(s==='"'||s==="'"){let a=s,h=c+1;for(;h<r.length&&r[h]!==a;)r[h]==="\\"&&h++,h++;i.push({type:e.STRING,value:r.substring(c+1,h),quoted:!0,sourceQuote:a}),c=h+1;continue}let l=c,f;for(;l<r.length&&!/[{},]/.test(r[l]);)if(r[l]===":"){let a=[":first",":last",":nth-child",":nth-of-type",":first-child",":last-child",":first-of-type",":last-of-type",":only-child",":only-of-type",":hover",":focus",":active",":visited",":disabled",":enabled",":checked",":empty",":root",":target",":not",":before",":after",":nth-last-child",":nth-last-of-type"],h=!1;for(let k of a){let E=k.substring(1);if(r.substring(l+1,l+1+E.length)===E){h=!0,l+=E.length;break}}if(!h)break}else if(r[l]==="["){for(l++;l<r.length&&r[l]!=="]";){if(r[l]==='"'||r[l]==="'"){let a=r[l];for(l++;l<r.length&&r[l]!==a;)r[l]==="\\"&&l++,l++}l++}l<r.length&&r[l]==="]"&&l++}else l++;f=r.substring(c,l);let u=e.IDENTIFIER;/^-?\d+(\.\d+)?$/.test(f)?u=e.NUMBER:f==="true"||f==="false"||f==="null"?u=e.BOOLEAN:/^[.#@\[]|[.#@\[]| /.test(f)&&(u=e.SELECTOR),i.push({type:u,value:f,quoted:!1}),c=l}return i}function o(r){let i="";for(let c=0;c<r.length;c++){let s=r[c];if("{}".includes(s.type)||"[]".includes(s.type)){i+=s.value;continue}if(s.type===e.COLON){i+=s.value;continue}if(s.type===e.COMMA){let l=r[c+1];if(l&&(l.type==="}"||l.type==="]"))continue;i+=s.value;continue}if(s.type===e.STRING&&s.quoted){let l=s.value;s.sourceQuote==="'"&&(l=l.replace(/\\'/g,"'"),l=l.replace(/(\\*)"/g,(f,u)=>u.length%2===0?u+'\\"':f)),i+=`"${l}"`;continue}if(s.type===e.NUMBER||s.type===e.BOOLEAN){i+=s.value;continue}if(s.type===e.SELECTOR||s.type===e.IDENTIFIER){i+=`"${s.value}"`;continue}i+=`"${s.value}"`}return i}try{let r=n(t),i=o(r);return JSON.parse(i)}catch(r){throw new A("Invalid extraction rules syntax: "+r.message,r)}}var wt="1",ct=/^[a-zA-Z0-9_-]+$/;function x(t,e,n){let o;if(n===void 0)o="script[data-rules-name]";else{if(typeof n!="string"||!ct.test(n))throw new Error(`hyper-html-api: invalid rules token ${JSON.stringify(n)} (must match ${ct})`);o=`script[data-rules-name~="${n}"]`}let r=t.find(e,o,{includeRulesTag:!0});if(r.length===0)return null;n!==void 0&&r.length>1&&console.warn(`hyper-html-api: ${r.length} rules tags match data-rules-name~="${n}"; using the first.`);let i=r[0],c=t.attr(i,"data-rules-version");if(c!==wt)throw new b(c);return{rules:P(t.text(i)),tagNode:i}}function X(t,e,n){if(n&&typeof n=="object")return{rules:n,tagNode:null};if(typeof n=="string"){let o=e&&e.ownerDocument?e.ownerDocument:e;return x(t,o,n)}return null}function ft(t,e,n,o){let r=X(t,e,n);if(!r){let s=typeof n=="string"?`data-rules-name~="${n}"`:"the provided rules object";throw new Error(`hyper-html-api: could not resolve rules for ${s}`)}let{rules:i,tagNode:c}=r;return{rules:i,tagNode:c,get:()=>g(t,e,i,o),set:s=>S(t,e,i,s,o)}}function _t(t){return t&&t.nodeType===1&&t.tagName==="SCRIPT"&&t.hasAttribute&&t.hasAttribute("data-rules-name")}function Dt(t){return t?(t.nodeType===9||t.nodeType===11,t):null}var Pt={find(t,e,n={}){let o=Dt(t);if(!o||!o.querySelectorAll)return[];let r=Array.from(o.querySelectorAll(e));n.includeRulesTag||(r=r.filter(s=>!_t(s)));let i=[];n.skip&&i.push(n.skip);let c=n.templateAttr===null?null:n.templateAttr||"cms-template";if(c&&i.push("["+c+"]"),i.length){let s=i.join(", ");r=r.filter(l=>!l.closest||!l.closest(s))}return r},parent(t){return t?t.parentElement:null},children(t){return t?Array.from(t.children):[]},text(t,e){if(e===void 0)return(t.textContent||"").trim();t.textContent=e},attr(t,e,n){if(n===void 0)return t.hasAttribute&&t.hasAttribute(e)?t.getAttribute(e):null;t.setAttribute(e,n)},removeAttr(t,e){t&&t.removeAttribute&&t.removeAttribute(e)},prop(t,e,n){if(n===void 0){let o=t?t[e]:void 0;return o!==void 0?o:null}t[e]=n},clone(t){return t.cloneNode(!0)},insertAt(t,e,n){let o=t.children[n]||null;t.insertBefore(e,o)},remove(t){t&&t.parentNode&&t.parentNode.removeChild(t)},replaceWith(t,e){if(!t||!t.parentNode)throw new Error("dom.replaceWith: node has no parent");let o=t.ownerDocument.createElement("template");o.innerHTML=e;let r=o.content.firstElementChild;if(!r)throw new Error("dom.replaceWith: html did not parse to an element");return t.parentNode.replaceChild(r,t),r},stripIds(t){let e=0;return t.id&&(t.removeAttribute("id"),e++),(t.querySelectorAll?t.querySelectorAll("[id]"):[]).forEach(o=>{o.removeAttribute("id"),e++}),e},sameNode(t,e){return t===e}},d=Pt;var L={extract:(t,e,n)=>g(d,t,e,n),apply:(t,e,n,o)=>S(d,t,e,n,o),findRulesIn:(t,e)=>x(d,t,e),findRules:(t,e)=>X(d,t,e),bind:(t,e,n)=>ft(d,t,e,n),parseStrict:G,parseRelaxed:P,ruleAttrIndex:m,splitRule:v,errors:$,DOM_PROPERTIES:j},z=t=>!!t&&typeof t.nodeType=="number";function ut(t){let e=t&&t.ownerDocument?t.ownerDocument:t,n=d.find(e,"script[data-rules-name]",{includeRulesTag:!0});if(n.length===0)throw new Error('hyper-html-api: no rules tag found. Add <script type="application/json" data-rules-name="\u2026" data-rules-version="1"> or pass rules.');if(n.length>1)throw new Error('hyper-html-api: multiple rules tags found; pass a name, e.g. extractData("api").');return x(d,e).rules}function K(t,e){let n=z(t)?t:document,o=z(t)?e:t;return o===void 0?g(d,n,ut(n)):L.bind(n,o).get()}function Y(t,e,n){if(!z(t))throw new Error("hyper-html-api: applyData(root, data, source?) needs a DOM root as the first argument.");return n===void 0?S(d,t,ut(t),e):L.bind(t,n).set(e),t}var Lt={engine:L,extractData:K,applyData:Y},Ct=Lt;return yt(jt);})();
6
6
  window.clay = window.clay || {};
7
7
  window.clay.extractData = hyperHtmlApiData.extractData;
8
8
  // clayjs-level sugar: applyData(data) writes into the whole document, matching
@@ -0,0 +1,19 @@
1
+ /* clay-internals.js — clayjs satellite: clay.internals = the supported low-level surface (snapshot, region policy, save lane). https://clayjs.com */
2
+ (function () {
3
+ var clay = window.clay = window.clay || {};
4
+ clay.loaded = clay.loaded || {};
5
+
6
+ var script = document.currentScript;
7
+ if (!script || !script.src) {
8
+ console.error("clay-internals: could not determine my own URL (load me with a classic <script src=...>, not type=\"module\")");
9
+ return;
10
+ }
11
+ var url = new URL(script.src, location.href);
12
+ var base = url.href.slice(0, url.href.lastIndexOf("/"));
13
+
14
+ // No DOM gate: nothing here touches document.body at import time, unlike clay-ui.
15
+ clay.loaded.internals = import(base + "/src/internals/index.js")
16
+ .catch(function (err) { console.error("clay-internals failed to load:", err); throw err; });
17
+ // Mark handled: a blocked satellite must not emit an unhandled rejection.
18
+ clay.loaded.internals.catch(function () {});
19
+ })();
package/clay.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* clayjs bootstrap. https://clayjs.com */
2
2
  (function () {
3
3
  // Suppress every vendored bundle's window auto-export; the loader assembles
4
- // window.clay (and a scoped compat shim) explicitly.
4
+ // window.clay explicitly.
5
5
  window.__hyperclayNoAutoExport = true;
6
6
 
7
7
  // Merge into any window.clay a satellite already created; never replace it.
@@ -11,10 +11,18 @@
11
11
  // resolves the ORIGINAL `ready` promise via the stashed resolver.
12
12
  if (clay.__booted) return;
13
13
 
14
- if (!clay.ready) {
15
- clay.ready = new Promise(function (r) { clay.__readyResolve = r; });
14
+ function mintReady() {
15
+ clay.ready = new Promise(function (res, rej) {
16
+ clay.__readyResolve = res;
17
+ clay.__readyReject = rej;
18
+ });
19
+ // Nobody may be awaiting a failed boot's promise, and an unhandled rejection
20
+ // in the console is noise on top of the error we already logged.
21
+ clay.ready.catch(function () {});
16
22
  }
17
23
 
24
+ if (!clay.ready) mintReady();
25
+
18
26
  var script = document.currentScript;
19
27
  if (!script || !script.src) {
20
28
  console.error("clayjs: could not determine my own URL (load me with a classic <script src=...>, not type=\"module\")");
@@ -29,5 +37,13 @@
29
37
  .catch(function (err) {
30
38
  clay.__booted = false;
31
39
  console.error("clayjs failed to load:", err);
40
+ // Settle the promise so `await clay.ready` fails loudly instead of hanging
41
+ // forever with nothing to catch. Then mint a fresh one: a promise settles
42
+ // once, so without this the corrected retry tag would call __readyResolve on
43
+ // an already-rejected promise and every later await would keep throwing even
44
+ // though the retry succeeded.
45
+ var reject = clay.__readyReject;
46
+ mintReady();
47
+ if (reject) reject(err);
32
48
  });
33
49
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panphora/clayjs",
3
- "version": "0.2.1",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "clayjs: malleable HTML files. Save lifecycle for self-saving HTML.",
6
6
  "license": "MIT",
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "clay.js",
14
14
  "clay-ui.js",
15
+ "clay-internals.js",
15
16
  "clay-events.js",
16
17
  "clay-options.js",
17
18
  "clay-dom.js",
@@ -31,7 +32,9 @@
31
32
  "test:conformance": "UPDATE_GOLDENS=0 web-test-runner",
32
33
  "conformance:update": "UPDATE_GOLDENS=1 web-test-runner",
33
34
  "prepublishOnly": "npm test && npm run test:conformance",
34
- "dev": "node tests/stub-server.mjs"
35
+ "dev": "node tests/stub-server.mjs",
36
+ "build": "node build.js",
37
+ "deploy": "node build.js && wrangler deploy && git rev-parse HEAD > .deploy"
35
38
  },
36
39
  "devDependencies": {
37
40
  "@esm-bundle/chai": "^4.3.4-fix.0",