Skip to content

Changelog

@sparkletree/core, @sparkletree/react and @sparkletree/cli are versioned and released together. This page carries the releases in reader terms; the full changelog in the repository is the complete record.

A page rendered on your server can now read the live copy already cached for the visitor’s context, at request time, and render it as final text: getIslandCopy from @sparkletree/react/server. A hit is passed to the island as initialCopy and is visible from the first byte; a miss renders the reservation and holds in the browser as before. Pass the read’s context to the provider’s new serverContext prop so the browser keys its streams the same way and keeps the variant the server read. IslandReservation is an island’s reservation as static markup, for a Suspense fallback; it and the island are one slot with one clock, so copy that arrives after the reservation’s 5 second deadline is not shown. Pass the islands’ fallbacks as fallbacks so the read asks for the same length budgets as the stream. The read takes a live key in production or a test key in local development. Static pages, ISR and static export should not call it. See Live copy in server-rendered pages.

0.13.0: text a visitor can see is never replaced

Section titled “0.13.0: text a visitor can see is never replaced”

Your fallback now waits invisibly everywhere, including in the server HTML, and once it is shown it stays. A text slot holds (blank, its space reserved by your invisible fallback) while live copy is coming, starting when the block is in view. Live copy lands in that space; if the hold runs out, your fallback lands once and is final for the page view, so adapted copy that arrives later is not shown. If the page’s JavaScript is late, a CSS-only failsafe reveals your fallback after 5 seconds.

Your fallback sets the size: each field’s slot is exactly your fallback’s box for the whole page view, its length is sent as the field’s length budget, copy that would not fit is not shown, typed copy is clipped rather than growing the slot, and a field your fallback does not have is never shown. Visible copy changes only when the visitor causes it (markVisitorChange() in the handler, then one cross-fade) or with the next page view; any other change waits until the block is off screen. A held call-to-action is invisible and inert until its label lands. A block that mounts again shows what it showed and records no second impression. A stream that fails while writing a line keeps what it wrote unless it is clearly an error. Fragments never replace a landed fallback: "before-paint" and "always" are deprecated and now behave like "never". serverRendered still means finished creative from the platform: visible from the first frame, and final. See What your visitors see.

0.12.0: live copy on a site without a backend (2026-10-06)

Section titled “0.12.0: live copy on a site without a backend (2026-10-06)”

Fragments now send your publishable key when they ask the platform to write new copy. createClient and useFragments pass the provider’s key through, and fetchFragments takes a new publishableKey option. The key goes on that write request only, as Authorization: Bearer <key>, and never on the cached read. A live key sent from an origin you registered for your organization is enough for the platform to write the copy, so a site with no backend, such as a brand page, gets live fragments without a mint token. If your organization has Signed mints only turned on, the key is refused on your other sites, which still need a mint token; your SparkleTree brand page keeps its live copy. mintToken keeps working; when both are given the platform uses the token. Without a key nothing changes.

Impressions, clicks and conversions now reach your analytics with their variant and surface: track also puts variantId and surfaceId inside eventData, where the platform reads them, and impression detail goes under eventData.context. A surfaceId that is not a surface id is no longer sent there, because the platform discarded the whole event for it. For a conversion, a numeric value is copied into amount. Events recorded before this release keep their missing variant and surface.

One type change: ContentSnapshot.content.image is an object (url, alt, heroFrame.url) or null, as the platform always sent it, not a string. Read image.url for an <img> src. Code that used image as a string now fails to compile; at runtime nothing changes.

0.11.2: a token for each declaration (2026-09-18)

Section titled “0.11.2: a token for each declaration (2026-09-18)”

The mintToken callback now receives the write it is signing for: the same context and schema the write request carries, plus both hashes. A signed token is bound to exactly one context and schema, and the callback used to be called with nothing, so a page with more than one declaration could never hand back a matching token and every write on it was refused. Forward context and schema to your mint-token endpoint unchanged and each declaration gets its own. A callback that ignores its argument keeps working.

0.11.1: a key-only page mints (2026-09-18)

Section titled “0.11.1: a key-only page mints (2026-09-18)”

A page that passes nothing but publishableKey now writes its cold contexts. The key carries the platform’s public edge origin, p.qrtree.ai, and the browser-side rule that keeps your signed mint token from following an unproven host recognised only sparkletree.io, so such pages read fine but never minted and stayed on your fallbacks. qrtree.ai is now on the first-party list. Nothing else changes: the list is still a fixed set of hosts SparkleTree operates, and a key pointing anywhere else still gets content and no token.

0.11.0: written on the visitor’s request (2026-09-18)

Section titled “0.11.0: written on the visitor’s request (2026-09-18)”

A cold context is now written on the visitor’s own request. The read is unchanged, one cacheable request that always answers, but on a cold answer the SDK’s single mint request no longer fires and forgets: the platform generates the copy right then and streams it back on that connection, and the served fields come through the same result, generated and marked. The hold restarts once when the server accepts, the same rule an island stream gets when it proves it is alive, so a quick model lands inside the budget and the first visitor reads generated copy. A slow one loses its slot to your fallback, which then stays; nothing your visitors see is ever replaced. The “first visitor to a new context always sees your fallback” rule is retired from the cookbook and these pages. A platform still answering the old 202 acknowledgement is tolerated: fallbacks stand, exactly as before. New FetchFragmentsOptions.onMintStarted for renderers outside React.

0.10.0: the principle release (2026-09-18)

Section titled “0.10.0: the principle release (2026-09-18)”

One user-experience principle now governs every comment and every page here, in both repositories: no bait and switch. A visitor is never shown something and then has it replaced, taken away, or pushed around, whether copy, image, video or layout. Your fallback is the failure path; it never shows first and then gets replaced. Nothing your visitors see changes in this release. What changes is that the docs stop describing “one permitted rewrite” and “hydrated copy is exempt from the hold” as design, and name them as known bugs being fixed: a late stream still lands once over a shown fallback, a server-rendered "use client" block ships the fallback text instead of the reservation, and the fragment swap defaults still admit a late replacement. Two real fixes ride along: declaredToDims now keeps literal x.* context keys exactly as the server does, so signed fragment mints stop being refused for pages that declare them, and the vendored wire contract picks up the live-ready hero event it had been missing since August. The publishable key is now a required, signed credential: mint a new key in the dashboard.

0.9.0: useIsland keeps the space for waiting copy (2026-08-29)

Section titled “0.9.0: useIsland keeps the space for waiting copy (2026-08-29)”

useIsland now reserves the space for every field that is still waiting for its copy, sized by the fallback text you pass. The packaged Hero and Cta, and the copies sparkletree add writes into your project, no longer contain that code themselves. @sparkletree/react exports heldReserve, which returns the text a waiting field is keeping space for: keep the element mounted while it returns a value, and use that value as the element’s accessible name. withReserve is still exported for copies made before 0.9.0, and sparkletree upgrade removes the old reservation code from existing copies. Nothing your visitors see changes.

Every mechanism that had quietly grown two implementations now has one: config resolution (the provider now refuses a plain-http apiBase loudly at setup, as the vanilla client always did), the FieldState constructor, hydration detection. Render costs came down with it — the SSE parser no longer rescans its whole buffer per network chunk, useFragments serializes its declaration once per render instead of three times, and StreamText stops constructing a media query per animation frame. Two small API removals: hashDeclaredContext no longer accepts an environment bag (env signals never enter the fragment identity), and the CLI’s unused targetPathFor export is gone.

0.7.0: the upgrade command grows generations (2026-08-27)

Section titled “0.7.0: the upgrade command grows generations (2026-08-27)”

sparkletree upgrade now ports a copy through every template generation it is missing — decided by the copy’s content, never by guessing which release wrote it. Copies made at 0.4.0 or 0.5.0, which the old check reported “up to date” while their inline URL-vetting helper stayed frozen, now get 0.6.1’s shared safeActionUrl import; 0.3.0 copies get the reservation wiring and then that, landing on the current template. The safety rules are unchanged: exact matches, all-or-nothing per file, your Tailwind edits survive, and nothing is written when your installed packages are older than the rewritten copy needs — the command now names the version to update to.

Ten verified findings from a full-SDK review, fixed together. What your visitors gain: a stream that dies during the hold no longer throws away a headline it had fully delivered; full-bleed heroes taller than the viewport count their impressions again; a late serverRendered={false} stream can no longer retype copy on screen; and nothing that arrives after a stream’s terminal event can restyle or re-action a settled creative. What your code can rely on: impression and click beacons now ride the same fetchImpl you gave the provider, fetchContent returns null for any malformed body instead of a snapshot that throws on first touch, aborting via options.signal tears the stream down completely, and safeActionUrl is one implementation in @sparkletree/core — imported by the packaged components and the copied templates alike, so a future fix reaches all of them at once (sparkletree upgrade ports 0.3.0 copies).

0.6.0: the mark becomes opt-in (2026-08-27)

Section titled “0.6.0: the mark becomes opt-in (2026-08-27)”

No component renders a visible disclosure glyph by default any more. Provenance is unchanged — every non-static field still stamps data-content-source, and decideChrome / $group.marked still report the honest per-viewer decision — but showing a visible label is now yours to opt into: render the new InlineMark where the decision says so. The markLabel and renderMark props are gone, and FragmentGroup no longer imposes position: relative on its container (give it one yourself if you positioned absolute descendants against the group).

The hold spans the whole wait, every entrance types in, fallback and fragments included, and copy a reader can see is never typed over.

The hold belongs to the component, and spans the whole wait. It arms the moment an island mounts, before the browser ever paints, not when the stream request opens. A pinned sibling waiting on the page’s variant draw now holds reserved from its first frame instead of showing the fallback it is about to lose, and one budget covers the whole path from mount through open to delivery. This is the root fix for “fallback shown before stream arrival”, which reproduced on pinned siblings even with fast cached streams.

Every entrance types in. Text landing on a blank held canvas is always a paced, typed entrance, whichever event staged it: a live generation, a cache replay’s composed flush, a fragment’s served copy, or your fallback when the budget runs out. No entrance is a hard paint. FragmentText shares StreamText’s pacer and gains a smooth prop (default true); its settled markup is unchanged. A disabled fragment is still byte-identical to a plain string.

On-screen copy is never typed over, by anything. Deltas aimed at copy a reader can see buffer off-canvas and the field changes at most once, composed, when it settles; if the stream dies first, your fallback simply stands. This now also holds on a mid-view declaration change: an island whose campaignId or context changes after copy has been read never re-arms the hold over it.

Upgrading. No API breaks. Two things you may notice:

  • Tests judge entrances at rest: text entering a blank canvas paces, so await the final text (waitFor, or pass smooth={false} in fixtures), and strip [aria-hidden="true"] nodes before asserting visible text. The entrance’s hidden tail rides inside the element mid-drain, as the reservation already did in 0.4.0.
  • Cache replays and fragment copy no longer hard-cut onto the canvas: they type in. Hydrated/SSR pages are unchanged: copy present at first paint renders whole and is never retyped.
  • 0.4.0 (2026-08-18): the layout doctrine. Adapted copy never moves the page. The hold reserves space instead of blanking, hydrated pages never hold, the layout gate verifies late swaps, and sparkletree upgrade ports 0.3.0 copies to the reservation wiring, proven by a real-browser zero-CLS suite in CI.
  • 0.3.0 (2026-08-17): the server-side cap lift 0.2.0 staged for, section-scale fragments against the fragments-0.3 API.
  • 0.2.0 (2026-08-17): fragments grow from microcopy to whole constructions, with no wire change. Array fallbacks, the length budget, and group-level disclosure.
  • 0.1.x (2026-08-11 to 08-12): the first public releases. The copy choreography (hold, single rewrite, honest provenance), paced live streaming on by default, and the whole-fallback restore when a stream dies mid-sentence.