diff --git a/CLAY-VITE.md b/CLAY-VITE.md index 927c7c0..bb0413b 100644 --- a/CLAY-VITE.md +++ b/CLAY-VITE.md @@ -960,6 +960,177 @@ pattern is to expose a **promise** that consumers `.then()` instead of listening A resolved promise is always "replayable" — calling `.then()` on an already-resolved promise runs the callback in the next microtask, with no shim required. +--- + +### Beyond sticky events: value-shaped state for cross-cutting client facts + +#### The general shape: a signal nobody was listening for yet + +`stickyEvents` fixes one instance of a wider problem the Vite bootstrap introduced. Under +Browserify, every consuming site got an implicit **total execution order** for free: every +component's top-level code ran synchronously during HTML parse, before any deferred +third-party script and before any other component's code that mattered. The deferred +` + +``` + +Bundled component code — Browserify's or Vite's — always arrives over the network at some point +in time, no matter how fast the bundler pipeline gets. Only inline, synchronous document script +is guaranteed to run before an async vendor SDK's first callback. A component's `client.js` can +then subscribe whenever it happens to load, with no race, because it's reading a value, not +racing an edge: + +```js +// client.js — arrives later, async; never races the vendor callback +window.consentStore.subscribe((activeGroups) => { + // runs immediately with whatever the current decision already is, + // and again whenever the reader changes it later in the session +}); +``` + +No bundler configuration and no claycli feature can substitute for owning that one synchronous +boundary point. It's inherent to how a page loads, not a limitation of any particular pipeline. + +#### The tempting non-fix: making component imports eager + +It's worth ruling out an obvious-looking alternative: have claycli load some or all `client.js` +files eagerly (static imports baked into the bootstrap) instead of via lazy, per-component +dynamic `import()`, so component code runs closer to Browserify's old timing. Don't reach for +this. It only narrows the timing window — it doesn't remove the race, since a static import is +still asynchronous relative to inline document script and still resolves at whatever speed the +network happens to allow that pageview. Offering it as an easy knob would also encourage teams +to reach for a timing tweak instead of the actual fix, quietly leaving the underlying race in +place for the next vendor integration or the next slow network. + +#### claycli's role here + +This pattern lives in the consuming site's code, not in claycli. claycli's role is this section +plus build-time diagnostics: a build-time diagnostic exists to help surface component code that +assigns to a bare `window.`, which is often a sign that code may need this pattern. + +**Future direction:** once a site has migrated its cross-cutting one-shot custom events (like +`auth:init`) onto this value-shaped pattern, `stickyEvents` becomes unnecessary for those events +and could eventually be removed from `claycli.config.js`. This doesn't make `stickyEvents` +deprecated — it may still be the right tool for events that haven't been migrated yet — but it's +a bridge, not a destination, in exactly the same sense that "promises over events" above is: a +store subsumes a promise, since a promise settles once and a store can also model a value that +goes on changing. + ### Watch mode (`clay vite --watch`) The watch implementation uses **Rollup watch mode** for JS incremental rebuilds and @@ -1226,6 +1397,8 @@ Run against the feature branch URL after enabling `CLAYCLI_VITE_ENABLED=true`. - [ ] Dollar-Slice controller components mount correctly - [ ] Vue components render correctly (subscriptions, listings-search, leaderboard, account) - [ ] `auth:init` sticky event is received by late subscribers +- [ ] No component's `client.js` depends on winning a race against a third-party vendor + callback or another component's one-shot signal - [ ] Ads load on article pages #### Edit mode (Kiln)