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.
0.13.0: live copy in server-rendered HTML
Section titled “0.13.0: live copy in server-rendered HTML”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.
0.8.0: the cleanup release (2026-08-28)
Section titled “0.8.0: the cleanup release (2026-08-28)”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.
0.6.1: the review release (2026-08-27)
Section titled “0.6.1: the review release (2026-08-27)”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).
0.5.0: the entrance doctrine (2026-08-24)
Section titled “0.5.0: the entrance doctrine (2026-08-24)”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 passsmooth={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.
Earlier releases
Section titled “Earlier releases”- 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 upgradeports 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.