@sparkletree/core 0.1.0 → 0.1.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 (2) hide show
  1. package/README.md +45 -43
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # @sparkletree/core
2
2
 
3
- The framework-agnostic half of the SparkleTree SDK. Wire protocol, copy
4
- choreography, theme, trust chrome, analytics, variant pinning, audio.
3
+ Framework-agnostic client for [SparkleTree](https://sparkletree.io): stream
4
+ adaptive campaign text into your site, with copy that renders immediately,
5
+ theme tokens, and built-in impression analytics.
5
6
 
6
- **This is the only thing that speaks the wire.** If you are building a React
7
- app, install [`@sparkletree/react`](https://www.npmjs.com/package/@sparkletree/react)
8
- instead — it depends on this. Reach for `core` directly when you are wiring up
9
- Vue, Svelte, Solid, or plain DOM.
7
+ **Building a React app?** Install
8
+ [`@sparkletree/react`](https://www.npmjs.com/package/@sparkletree/react)
9
+ instead — it wraps this package. Use `core` directly with Vue, Svelte, Solid,
10
+ or plain DOM.
10
11
 
11
12
  ```bash
12
13
  npm install @sparkletree/core
@@ -25,11 +26,11 @@ const handle = openStream({
25
26
  island: "hero",
26
27
  },
27
28
  // Required. This is what the visitor reads if the network is slow, the
28
- // campaign is paused, or generation fails. Write it as the only version.
29
+ // campaign is paused, or generation fails so write it as copy you would
30
+ // ship on its own.
29
31
  initial: { headline: "Software that meets the moment" },
30
- // True when that copy is ALREADY on screen (SSR, or carried across a
31
- // navigation). It changes the choreography: copy a reader can see is never
32
- // typed over.
32
+ // Set true when that copy is already visible (server-rendered, or carried
33
+ // across a navigation). Text a reader can see is never typed over.
33
34
  initialIsOnScreen: false,
34
35
  onState: (state) => {
35
36
  const { marked } = decideChrome(state.contentSource, false);
@@ -41,59 +42,61 @@ const final = await handle.finished;
41
42
  handle.close();
42
43
  ```
43
44
 
44
- `openStream` returns `{ choreographer, finished, close }`. `choreographer.getState()`
45
- is the current state; `finished` resolves with the terminal state; `close()`
46
- aborts the request and releases timers.
45
+ `openStream` returns `{ choreographer, finished, close }`.
46
+ `choreographer.getState()` is the current state; `finished` resolves with the
47
+ terminal state; `close()` aborts the request and releases timers.
47
48
 
48
- ## What you get
49
+ ## What's in the box
49
50
 
50
51
  | Export | |
51
52
  |---|---|
52
- | `openStream` | The client. Opens the SSE stream and drives the choreographer. |
53
- | `fetchContent` | Fetches `/content`. **Nothing calls it** — `openStream` does not, and the React path does not. Exported for consumers who want a first-paint fetch of their own. |
54
- | `CopyChoreographer`, `COPY_FIELDS`, `COPY_HOLD_MS` | The state machine, if you want to drive it yourself. |
55
- | `SseParser`, `readSseStream` | SSE parsing, usable standalone. |
56
- | `decideChrome`, `viewerPrefersStandard`, `setViewerPrefersStandard`, `STANDARD_MODE_KEY` | Trust chrome decisions. |
57
- | `applyTheme`, `themeStyle`, `tokenName`, `tokenSource`, `TOKEN_PREFIX` | Theme tokens. |
58
- | `track`, `ImpressionRecorder`, `observeVisibility`, `sessionId` | Analytics. |
59
- | `pageVariants`, `VariantRegistry`, `RESERVATION_TIMEOUT_MS` | Variant pinning — one page view, one variant. |
60
- | `mountAudio`, `activePlayer`, `destroyActiveAudio` | Audio engine (no chrome). |
61
- | `buildStreamUrl`, `buildContentUrl`, `negotiatedProtocol`, `PROTOCOL_*`, `SDK_PARAM`, `VARIANT_PARAM` | Protocol. |
53
+ | `openStream` | Opens the stream and manages copy state for you. |
54
+ | `fetchContent` | One-shot fetch of campaign content, for a first-paint fetch of your own. Not used by `openStream`. |
55
+ | `CopyChoreographer`, `COPY_FIELDS`, `COPY_HOLD_MS` | The copy state machine, if you want to drive it yourself. |
56
+ | `SseParser`, `readSseStream` | Server-sent-events parsing, usable standalone. |
57
+ | `decideChrome`, `viewerPrefersStandard`, `setViewerPrefersStandard`, `STANDARD_MODE_KEY` | Whether to show the AI-adaptation mark, and the viewer's opt-out. |
58
+ | `applyTheme`, `themeStyle`, `tokenName`, `tokenSource`, `TOKEN_PREFIX` | Theme tokens delivered with the campaign. |
59
+ | `track`, `ImpressionRecorder`, `observeVisibility`, `sessionId` | Impression analytics. |
60
+ | `pageVariants`, `VariantRegistry`, `RESERVATION_TIMEOUT_MS` | Variant pinning — one page view sees one variant. |
61
+ | `mountAudio`, `activePlayer`, `destroyActiveAudio` | Audio playback (no UI). |
62
+ | `buildStreamUrl`, `buildContentUrl`, `negotiatedProtocol`, `PROTOCOL_*`, `SDK_PARAM`, `VARIANT_PARAM` | Protocol plumbing. |
62
63
 
63
64
  ## Copy state
64
65
 
65
66
  `state.fields.{greeting,headline,body,cta}` each carry
66
- `{ text, source, mode, typing, settled }`, already choreographed — you render
67
- `text` and honour `typing`/`settled` for presentation only. Alongside them:
67
+ `{ text, source, mode, typing, settled }`, already sequenced for display — you
68
+ render `text` and use `typing`/`settled` for presentation only. Alongside them:
68
69
  `phase`, `error`, `rewrites`, `contentSource`, `degraded`, `variantId`,
69
70
  `theme`, `layout`, `backgroundImage`, `videoUrl`, `sequence`, `ctaStyle`,
70
71
  `ctaAction`, `meta`.
71
72
 
72
73
  `phase` moves `idle → holding? → live → settled | failed`. Nothing throws and
73
- nothing blanks: on `failed`, whatever was painted stays painted and undelivered
74
- fields fall back to your `initial` text.
74
+ nothing goes blank: on `failed`, whatever was painted stays painted, and
75
+ undelivered fields fall back to your `initial` text.
75
76
 
76
- ## The rules this enforces for you
77
+ ## What the client guarantees
77
78
 
78
- 1. **First paint is real copy.** No skeleton, and no option for one.
79
+ 1. **First paint is real copy.** Your `initial` text renders immediately
80
+ there is no skeleton state.
79
81
  2. **Copy changes at most once.** Text a reader may be mid-sentence on is never
80
- wiped and retyped. Deltas aimed at copy already on screen buffer off-canvas
81
- and land once, composed.
82
- 3. **The server declares delivery mode**; the client never guesses from timing.
83
- 4. **Adapted copy is marked.** `decideChrome` returns `marked` for `generated`
84
- and `cached`, never for `static`. Marking authored copy as adapted is a
85
- worse failure than not marking adapted copy.
82
+ wiped and retyped; updates aimed at visible text are composed off-screen and
83
+ land in one step.
84
+ 3. **The server declares how content was produced** (static, cached, or
85
+ generated); the client never guesses from timing.
86
+ 4. **AI-adapted copy is always marked.** `decideChrome` returns `marked` for
87
+ generated and cached content, never for static — so readers can tell
88
+ adapted text from authored text.
86
89
 
87
- Full documentation, including the React quickstart and the reasoning behind
88
- each rule, is in the [SDK README](https://gitlab.com/sparkletree/sparkletree-sdk/-/blob/main/README.md).
90
+ Full documentation:
91
+ [docs.sparkletree.io](https://docs.sparkletree.io/developers/sdk/core/)
89
92
 
90
93
  ## Requirements
91
94
 
92
95
  ES2022, **ESM only**, Node >= 18. `fetch`, `ReadableStream` and
93
- `AbortController` must exist — inject `fetchImpl` if you need to substitute
94
- them. No dependencies.
96
+ `AbortController` must exist — inject `fetchImpl` to substitute them. No
97
+ dependencies.
95
98
 
96
- There is no `require` export condition, which is worth knowing in two places:
99
+ There is no `require` export condition, which matters in two places:
97
100
 
98
101
  - **From CommonJS**, `require("@sparkletree/core")` fails on Node 18 with
99
102
  `ERR_REQUIRE_ESM`. Use `await import("@sparkletree/core")`. (Node 26 loads it
@@ -101,5 +104,4 @@ There is no `require` export condition, which is worth knowing in two places:
101
104
  - **In TypeScript**, `moduleResolution: "bundler"` resolves cleanly. So does
102
105
  `"node16"`/`"nodenext"` *if your own package.json has `"type": "module"`*.
103
106
  Without it you get `TS1479` telling you to use a dynamic import — accurate,
104
- not a false positive, and the most likely way a Node backend meets this
105
- package.
107
+ not a false positive.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sparkletree/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Framework-agnostic core for the SparkleTree SDK: wire protocol, copy choreography, theme, analytics, audio.",
5
5
  "license": "MIT",
6
6
  "type": "module",