@roughapp/feature 0.2.0 → 0.3.0
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.
- package/CHANGELOG.md +220 -0
- package/README.md +100 -24
- package/index.d.ts +177 -29
- package/index.js +15 -15
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@roughapp/feature`.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
This package is pre-1.0, so a minor version bump can contain breaking changes.
|
|
7
|
+
|
|
8
|
+
## 0.3.0 - 2026-08-10
|
|
9
|
+
|
|
10
|
+
**This release has breaking API changes.** Rough now uses an explicit client for
|
|
11
|
+
each project instead of global initialization. Apps can safely show features
|
|
12
|
+
from multiple Rough projects at the same time, and each client has a clear,
|
|
13
|
+
awaitable lifecycle.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`createRoughClient({ projectId, baseUrl?, fetchUserToken })`** creates and
|
|
18
|
+
starts a client for one project. Keep the client for as long as your app needs
|
|
19
|
+
that project, then call `await client.destroy()`.
|
|
20
|
+
- **`whenRoughClientReady({ client })`** lets you wait for startup or handle a
|
|
21
|
+
startup error.
|
|
22
|
+
- **`openRoughCreate({ target })`** lets you attach the create modal inside the
|
|
23
|
+
element that scopes your Rough theme. It defaults to `document.body`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **All stateful functions and components now take a client.** Pass `client` and
|
|
28
|
+
`surface` to `getRoughFeatures()`, `openRoughCreate()`, `<rough-surface>`,
|
|
29
|
+
`<rough-feature>`, `<rough-edit-button>` and the modal elements.
|
|
30
|
+
- **Cleanup methods are asynchronous and safe to call more than once.** Await
|
|
31
|
+
`client.destroy()`, `subscription.unsubscribe()` and `modal.close()` when you
|
|
32
|
+
need to know that cleanup has finished.
|
|
33
|
+
- **Create one client and share it for each signed-in person and project.** A
|
|
34
|
+
second client for the same `baseUrl`, `projectId` and person fails with
|
|
35
|
+
`RoughReplicacheIdentityConflictError`. Clients for different projects or
|
|
36
|
+
different signed-in people can run together.
|
|
37
|
+
- **`openRoughCreate()` no longer requires a mounted Rough component.** You can
|
|
38
|
+
open it directly with a client and surface.
|
|
39
|
+
|
|
40
|
+
### Removed
|
|
41
|
+
|
|
42
|
+
- **`initRough()` has been removed.** There is no default or global client.
|
|
43
|
+
- **`defineSurface()` has been replaced by `defineRoughSurface()`.** Rename
|
|
44
|
+
`toolList` to `tools` when updating your surface definitions.
|
|
45
|
+
- **`registerSurfaceEntry` has been removed.** You no longer need to register a
|
|
46
|
+
surface before using it.
|
|
47
|
+
|
|
48
|
+
### Migrating
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// Before
|
|
52
|
+
initRough({ projectId, fetchUserToken })
|
|
53
|
+
const surface = defineSurface({ key, name, description, toolList })
|
|
54
|
+
const unsubscribe = getRoughFeatures(surface, onFeatures)
|
|
55
|
+
openRoughCreate(surface, { projectId })
|
|
56
|
+
|
|
57
|
+
// After
|
|
58
|
+
const client = createRoughClient({ projectId, fetchUserToken })
|
|
59
|
+
const surface = defineRoughSurface({ key, name, description, tools })
|
|
60
|
+
const subscription = getRoughFeatures({ client, surface, onFeatures })
|
|
61
|
+
const modal = await openRoughCreate({ client, surface })
|
|
62
|
+
|
|
63
|
+
await modal.close()
|
|
64
|
+
await subscription.unsubscribe()
|
|
65
|
+
await client.destroy()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 0.2.1 - 2026-08-05
|
|
69
|
+
|
|
70
|
+
Add `CHANGELOG.md` to the published package.
|
|
71
|
+
|
|
72
|
+
## 0.2.0 - 2026-08-05
|
|
73
|
+
|
|
74
|
+
**Upgrade from 0.1.0 as soon as you can.** Every read and write 0.1.0 performs
|
|
75
|
+
goes through gateway REST routes that are being deleted. When that lands, a
|
|
76
|
+
0.1.0 install can no longer list, create, build or publish features. 0.2.0 does
|
|
77
|
+
not call any of those routes.
|
|
78
|
+
|
|
79
|
+
Upgrading needs no configuration change. `initRough` takes the same
|
|
80
|
+
`projectId`, `fetchUserToken` and optional `baseUrl` it always did, and no
|
|
81
|
+
export was added or removed. The work is in the type and theme changes below.
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- **Breaking: sprite, surface and ping traffic now syncs through Replicache
|
|
86
|
+
instead of the REST API.** 0.1.0 called ten routes under
|
|
87
|
+
`/api/v1/project/{projectId}/` to list sprites, fetch a sprite, list and
|
|
88
|
+
create builds, read a build and its logs, publish, register a surface and
|
|
89
|
+
ping. 0.2.0 calls none of them. It syncs over
|
|
90
|
+
`/api/v1/internal/replicache/pull` and `/api/v1/internal/replicache/push`,
|
|
91
|
+
and the session token exchange at `/api/v1/project/{projectId}/session-token`
|
|
92
|
+
is the only other request it makes. The ten legacy routes are removed in
|
|
93
|
+
[#2181](https://github.com/build-great-products/rough.app/pull/2181), which
|
|
94
|
+
is what makes this upgrade time sensitive.
|
|
95
|
+
|
|
96
|
+
One consequence worth planning for: the SDK now keeps a sync loop open and
|
|
97
|
+
polls for changes every 5 seconds for as long as the page is mounted, where
|
|
98
|
+
0.1.0 issued one request per call and then went quiet.
|
|
99
|
+
|
|
100
|
+
- **Breaking: `Sprite` and `SpriteBuild` reference related records by ID
|
|
101
|
+
instead of nesting them.**
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// 0.1.0
|
|
105
|
+
type Sprite = {
|
|
106
|
+
id: string
|
|
107
|
+
name: string
|
|
108
|
+
ownedByPersonId: string
|
|
109
|
+
publishedSpriteBuild: SpriteBuild | null // full nested record
|
|
110
|
+
}
|
|
111
|
+
type SpriteBuild = {
|
|
112
|
+
createdByPerson: { id: string; name: string }
|
|
113
|
+
// ...
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 0.2.0
|
|
117
|
+
type Sprite = {
|
|
118
|
+
id: SpriteId
|
|
119
|
+
name: string
|
|
120
|
+
ownedByPersonId: PersonId
|
|
121
|
+
publishedSpriteBuildId: SpriteBuildId | null // ID only
|
|
122
|
+
}
|
|
123
|
+
type SpriteBuild = {
|
|
124
|
+
createdByPersonId: PersonId
|
|
125
|
+
// ...
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Anything reading `sprite.publishedSpriteBuild.artifactUrl`,
|
|
130
|
+
`.status` or `.prompt` off a `getRoughFeatures` result must be reworked, and
|
|
131
|
+
`SpriteBuild` no longer carries the build author's display name. There is no
|
|
132
|
+
replacement for that name in the public API.
|
|
133
|
+
|
|
134
|
+
- **Breaking: ID fields are branded string types.** `SpriteId`,
|
|
135
|
+
`SpriteBuildId`, `SurfaceId` and `PersonId` are now `string & { __brand: … }`
|
|
136
|
+
rather than `string`, on `RoughFeature`'s `featureId` and `buildId`,
|
|
137
|
+
`RoughCreateModal`'s and `SpriteBuildMenu`'s `surfaceId`, `SpriteFrame`'s
|
|
138
|
+
`spriteBuildId`, and `registerSurfaceEntry`'s `surfaceId`.
|
|
139
|
+
|
|
140
|
+
Reading is unaffected, because a branded string is still assignable to
|
|
141
|
+
`string`. Passing one in is not: an ID you hold as a plain `string`, from
|
|
142
|
+
your own database or a URL parameter, is rejected. The brand types are not
|
|
143
|
+
themselves exported, so reach them through the types that are:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
type SpriteId = Sprite['id']
|
|
147
|
+
type SpriteBuildId = NonNullable<Sprite['publishedSpriteBuildId']>
|
|
148
|
+
type SurfaceId = RoughCreateModalElement['surfaceId']
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
IDs that flow straight out of `getRoughFeatures` into a component still
|
|
152
|
+
typecheck with no cast.
|
|
153
|
+
|
|
154
|
+
- **Breaking: the stylesheet ships a different set of design tokens.** The
|
|
155
|
+
Solarized palette was replaced with a ShadCN-style token set, so any host
|
|
156
|
+
theming Rough through CSS custom properties needs to remap. These eight are
|
|
157
|
+
gone, with their nearest replacement:
|
|
158
|
+
|
|
159
|
+
| Removed in 0.2.0 | Closest 0.2.0 token |
|
|
160
|
+
| ------------------------- | ---------------------------------------------- |
|
|
161
|
+
| `--rough-text` | `--rough-foreground` |
|
|
162
|
+
| `--rough-text-secondary` | `--rough-muted-foreground` |
|
|
163
|
+
| `--rough-surface` | `--rough-card` |
|
|
164
|
+
| `--rough-surface-border` | `--rough-border` |
|
|
165
|
+
| `--rough-background-muted` | `--rough-muted` |
|
|
166
|
+
| `--rough-danger` | `--rough-destructive` |
|
|
167
|
+
| `--rough-shadow` | `--rough-shadow-xs`, `-md`, `-lg` |
|
|
168
|
+
| `--rough-accent-secondary` | no equivalent; `--rough-brand` is the saturated brand colour |
|
|
169
|
+
|
|
170
|
+
Watch `--rough-accent` in particular. The name survived but the meaning did
|
|
171
|
+
not: it was the primary action colour (Solarized blue) and is now the subtle
|
|
172
|
+
hover background that ShadCN uses the name for. A host that set it to their
|
|
173
|
+
brand colour to tint buttons will find it tinting hover states instead.
|
|
174
|
+
`--rough-primary` is the token that now drives primary actions.
|
|
175
|
+
`--rough-background` and `--rough-border` also kept their names with new
|
|
176
|
+
values. Twenty-eight tokens were added, including a `--rough-radius` scale
|
|
177
|
+
and `--rough-success` variants.
|
|
178
|
+
|
|
179
|
+
- **`getRoughFeatures` is a live subscription rather than a one-shot fetch.**
|
|
180
|
+
In 0.1.0 the callback fired once and only fired again when something called
|
|
181
|
+
`triggerRefresh`. It now re-fires on every relevant change that syncs in, so
|
|
182
|
+
a feature published in another tab or by another user appears without the
|
|
183
|
+
host doing anything.
|
|
184
|
+
|
|
185
|
+
- **A feature becomes visible on a published build rather than on a built
|
|
186
|
+
artifact.** 0.1.0 listed a sprite only if its published build had an
|
|
187
|
+
`artifactUrl`; 0.2.0 lists it as soon as a build is marked published. A
|
|
188
|
+
published build with no artifact, which should not normally occur, now
|
|
189
|
+
reaches the host and renders an empty frame instead of being filtered out.
|
|
190
|
+
|
|
191
|
+
- The bundle is roughly three times larger, because the Replicache client is
|
|
192
|
+
compiled into it: `index.js` goes from 105 kB to 320 kB raw, 27 kB to 77 kB
|
|
193
|
+
gzipped. The stylesheet grows from 324 B to 5.2 kB with the larger token set.
|
|
194
|
+
|
|
195
|
+
- The declared `svelte` dependency range moves from `^5.56.4` to `^5.56.8`.
|
|
196
|
+
Every other runtime dependency is unchanged, `capnweb` included, so the
|
|
197
|
+
version compatibility required between the two ends of the iframe bridge has
|
|
198
|
+
not moved.
|
|
199
|
+
|
|
200
|
+
### Removed
|
|
201
|
+
|
|
202
|
+
- **`onpublish` is gone from `RoughCreateModal`, `RoughEditButton` and
|
|
203
|
+
`RoughEditModal`.** Because `getRoughFeatures` is now a live subscription, a
|
|
204
|
+
publish already reaches the host through that callback, which is what the
|
|
205
|
+
prop existed to signal. Move any publish handling into the
|
|
206
|
+
`getRoughFeatures` callback. Note that the prop was optional, so depending on
|
|
207
|
+
your setup this may fail quietly rather than at compile time.
|
|
208
|
+
|
|
209
|
+
- **`triggerRefresh` is gone from the `SurfaceEntry` passed to
|
|
210
|
+
`registerSurfaceEntry`.** It was the manual refresh hook for the old
|
|
211
|
+
fetch-once model and has nothing to do now. Delete it from the object you
|
|
212
|
+
pass in; leaving it there is an excess property and will be rejected.
|
|
213
|
+
|
|
214
|
+
### Added
|
|
215
|
+
|
|
216
|
+
- A light and dark mode toggle in the Feature Builder. The stylesheet now
|
|
217
|
+
carries a `.rough-dark` class holding the dark values for every token.
|
|
218
|
+
- The Feature Builder's build log now reports token usage and estimated cost
|
|
219
|
+
per agent step, formatted in cents below one dollar
|
|
220
|
+
([#2187](https://github.com/build-great-products/rough.app/pull/2187)).
|
package/README.md
CHANGED
|
@@ -10,16 +10,14 @@ of custom elements (including `<rough-surface>`).
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
pnpm add @roughapp/feature
|
|
13
|
+
pnpm add @roughapp/feature zod
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
This package targets **npm + a modern bundler** (Vite, Webpack, Rspack, …). It
|
|
17
17
|
is ESM-only and does not support direct `<script>`/CDN usage in this release.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
dependencies — you do not need to add them yourself, though you can import `zod`
|
|
22
|
-
directly (see below).
|
|
19
|
+
Install `zod` directly because your tool definitions import it. The package
|
|
20
|
+
installs its other runtime dependencies automatically.
|
|
23
21
|
|
|
24
22
|
## Import the stylesheet
|
|
25
23
|
|
|
@@ -40,14 +38,19 @@ A surface describes a place in your product where Rough features appear, plus
|
|
|
40
38
|
the tools the feature can call. Import `z` from `zod` directly for tool schemas:
|
|
41
39
|
|
|
42
40
|
```ts
|
|
43
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
defineRoughSurface,
|
|
43
|
+
Query,
|
|
44
|
+
Mutation,
|
|
45
|
+
Subscription,
|
|
46
|
+
} from '@roughapp/feature'
|
|
44
47
|
import { z } from 'zod'
|
|
45
48
|
|
|
46
|
-
export const inboxSurface =
|
|
49
|
+
export const inboxSurface = defineRoughSurface({
|
|
47
50
|
key: 'inbox',
|
|
48
51
|
name: 'Inbox',
|
|
49
52
|
description: 'The main message inbox.',
|
|
50
|
-
|
|
53
|
+
tools: [
|
|
51
54
|
new Query({
|
|
52
55
|
id: 'listMessages',
|
|
53
56
|
name: 'List Messages',
|
|
@@ -60,7 +63,8 @@ export const inboxSurface = defineSurface({
|
|
|
60
63
|
return []
|
|
61
64
|
},
|
|
62
65
|
}),
|
|
63
|
-
// Mutation
|
|
66
|
+
// Mutation uses the same fields. A Subscription implementation receives
|
|
67
|
+
// a callback before the input.
|
|
64
68
|
],
|
|
65
69
|
})
|
|
66
70
|
```
|
|
@@ -68,65 +72,137 @@ export const inboxSurface = defineSurface({
|
|
|
68
72
|
`Query`, `Mutation`, and `Subscription` are re-exported from
|
|
69
73
|
`@roughapp/bridge` for convenience.
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
`defineRoughSurface()` validates and freezes the surface and its tools array.
|
|
76
|
+
The definition holds no client, project id or connection state, so you can hand
|
|
77
|
+
the same value to clients for two different projects at once.
|
|
78
|
+
|
|
79
|
+
### Create a client
|
|
80
|
+
|
|
81
|
+
`createRoughClient()` returns synchronously and immediately starts that
|
|
82
|
+
project's authentication, local database and first sync. You own the client
|
|
83
|
+
until you destroy it.
|
|
72
84
|
|
|
73
85
|
```ts
|
|
74
|
-
import {
|
|
86
|
+
import { createRoughClient } from '@roughapp/feature'
|
|
75
87
|
|
|
76
|
-
|
|
88
|
+
const client = createRoughClient({
|
|
77
89
|
projectId: 'proj_123',
|
|
78
90
|
fetchUserToken: async () => myAppSession.getRoughToken(),
|
|
79
91
|
// baseUrl is optional; defaults to the Rough production API.
|
|
80
92
|
})
|
|
81
93
|
```
|
|
82
94
|
|
|
95
|
+
Create the client at whatever owns the project in your app: a route, a provider
|
|
96
|
+
component, a page controller. Share that one client with everything below it
|
|
97
|
+
rather than creating a second. Within one JavaScript realm, create only one live
|
|
98
|
+
client for each `<baseUrl, projectId, personId>` identity. If you create another,
|
|
99
|
+
its startup fails with `RoughReplicacheIdentityConflictError`.
|
|
100
|
+
|
|
101
|
+
### Destroy it when you are done
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
await client.destroy()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`destroy()` is the only lifecycle method, and it works everywhere. It is
|
|
108
|
+
idempotent, returns the same promise every time, closes every subscription and
|
|
109
|
+
modal the client still owns, and does not resolve until all of that has
|
|
110
|
+
finished. A framework unmount hook may use `void client.destroy()`; tests and
|
|
111
|
+
controlled route transitions should await it.
|
|
112
|
+
|
|
113
|
+
The client implements no disposal protocol. Teardown here is genuinely
|
|
114
|
+
asynchronous, and there is one way to ask for it.
|
|
115
|
+
|
|
116
|
+
Startup runs in the background. If you want to observe it:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { whenRoughClientReady } from '@roughapp/feature'
|
|
120
|
+
|
|
121
|
+
await whenRoughClientReady({ client })
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A client that fails startup keeps that error and replays it from every later
|
|
125
|
+
operation. It is not restarted; destroy it and create a new one.
|
|
126
|
+
|
|
83
127
|
### Render a surface
|
|
84
128
|
|
|
85
129
|
Use the `<rough-surface>` custom element, or the `RoughSurface` component
|
|
86
|
-
export. Set the
|
|
130
|
+
export. Set both the client and the surface:
|
|
87
131
|
|
|
88
132
|
```ts
|
|
89
133
|
import '@roughapp/feature'
|
|
90
134
|
|
|
91
135
|
const el = document.createElement('rough-surface')
|
|
136
|
+
el.client = client
|
|
92
137
|
el.surface = inboxSurface
|
|
93
138
|
document.querySelector('#rough-slot')?.append(el)
|
|
94
139
|
```
|
|
95
140
|
|
|
96
141
|
### List features and open the create flow
|
|
97
142
|
|
|
143
|
+
Operations are direct exports and take the client in their options. Neither
|
|
144
|
+
requires anything to have mounted first.
|
|
145
|
+
|
|
98
146
|
```ts
|
|
99
147
|
import { getRoughFeatures, openRoughCreate } from '@roughapp/feature'
|
|
100
148
|
|
|
101
149
|
// Subscribe to the published features for a surface.
|
|
102
|
-
const
|
|
103
|
-
|
|
150
|
+
const subscription = getRoughFeatures({
|
|
151
|
+
client,
|
|
152
|
+
surface: inboxSurface,
|
|
153
|
+
onFeatures: (features) => {
|
|
154
|
+
console.log(features)
|
|
155
|
+
},
|
|
156
|
+
onError: (error) => {
|
|
157
|
+
console.error(error)
|
|
158
|
+
},
|
|
104
159
|
})
|
|
105
160
|
|
|
106
|
-
//
|
|
107
|
-
|
|
161
|
+
// Optional: wait for the first feature batch.
|
|
162
|
+
await subscription.ready
|
|
163
|
+
|
|
164
|
+
// Open the "create a feature" modal.
|
|
165
|
+
const modal = await openRoughCreate({
|
|
166
|
+
client,
|
|
167
|
+
surface: inboxSurface,
|
|
168
|
+
// Where the modal attaches. Defaults to document.body. Pass the element that
|
|
169
|
+
// scopes your Rough theme if you scope it to a subtree.
|
|
170
|
+
target: document.querySelector('#rough-root') ?? undefined,
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
await modal.close()
|
|
174
|
+
await subscription.unsubscribe()
|
|
108
175
|
```
|
|
109
176
|
|
|
177
|
+
Every cleanup handle returns a promise, is safe to call twice, and returns the
|
|
178
|
+
same promise on the second call.
|
|
179
|
+
|
|
110
180
|
## Exports
|
|
111
181
|
|
|
112
182
|
Recommended, stable-ish customer API:
|
|
113
183
|
|
|
114
|
-
- `
|
|
115
|
-
|
|
184
|
+
- `createRoughClient`, `whenRoughClientReady` (+ `RoughClient`,
|
|
185
|
+
`RoughClientOptions` types)
|
|
186
|
+
- `defineRoughSurface` (+ `RoughSurfaceDefinition` type)
|
|
187
|
+
- `getRoughFeatures` (+ `GetRoughFeaturesOptions`,
|
|
188
|
+
`RoughFeatureSubscription` types)
|
|
189
|
+
- `openRoughCreate` (+ `OpenRoughCreateOptions`, `RoughModalHandle` types)
|
|
116
190
|
- `RoughSurface` / `<rough-surface>`
|
|
117
191
|
- `Query`, `Mutation`, `Subscription` (re-exported from `@roughapp/bridge`)
|
|
192
|
+
- Errors worth branching on: `RoughClientDestroyedError`,
|
|
193
|
+
`RoughReplicacheIdentityConflictError`, `RoughSurfaceContractConflictError`,
|
|
194
|
+
`RoughClientDestroyError`, `RoughInvalidClientError`
|
|
118
195
|
|
|
119
|
-
Advanced / incidental exports
|
|
120
|
-
|
|
196
|
+
Advanced / incidental exports are **not** intended as stable customer
|
|
197
|
+
dependencies during the pre-1.0 series:
|
|
121
198
|
|
|
122
199
|
- Custom-element components: `RoughCreateModal` (`<rough-create-modal>`),
|
|
123
200
|
`RoughEditButton` (`<rough-edit-button>`), `RoughFeature` (`<rough-feature>`)
|
|
124
201
|
(the `<rough-edit-modal>` element registers transitively).
|
|
125
202
|
- UI building blocks: `PrimaryButton`, `SecondaryButton`, `ResizeHandle`,
|
|
126
203
|
`SpriteBuildMenu`, `SpriteFrame`.
|
|
127
|
-
- `
|
|
128
|
-
|
|
129
|
-
`SpriteFrameDatastoreContext`.
|
|
204
|
+
- Types: `Sprite`, `SpriteBuild`, `JsonValue`, `RoughCleanup`,
|
|
205
|
+
`FetchUserTokenFn`, `SpriteFrameDatastore`, `SpriteFrameDatastoreContext`.
|
|
130
206
|
|
|
131
207
|
## License
|
|
132
208
|
|