@roughapp/feature 0.4.0 → 0.4.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.
- package/CHANGELOG.md +40 -5
- package/README.md +206 -116
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,43 @@ All notable changes to `@roughapp/feature`.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
This package is pre-1.0, so a minor version bump can contain breaking changes.
|
|
7
7
|
|
|
8
|
+
## 0.4.1 - 2026-08-26
|
|
9
|
+
|
|
10
|
+
Documentation-only release. The README now has expanded setup, security,
|
|
11
|
+
lifecycle, theming and API guidance, and the changelog includes the release
|
|
12
|
+
notes that were missing from 0.4.0. There are no runtime, type or API changes.
|
|
13
|
+
|
|
14
|
+
## 0.4.0 - 2026-08-26
|
|
15
|
+
|
|
16
|
+
The Feature Builder is now conversational: Rough can clarify what to build,
|
|
17
|
+
inspect the tools available on the surface and start a build when it has enough
|
|
18
|
+
context. The recommended client and surface APIs are unchanged, so host apps do
|
|
19
|
+
not need an integration migration.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Feature Builder chat.** Conversations are saved per feature and remain
|
|
24
|
+
available when the builder is reopened. Follow-up messages can refine the
|
|
25
|
+
feature before Rough starts another build.
|
|
26
|
+
- **Markdown responses.** Assistant messages render headings, lists, links,
|
|
27
|
+
tables, blockquotes and code blocks.
|
|
28
|
+
- **Build cancellation.** Pending and in-progress builds have a Stop control.
|
|
29
|
+
Canceled builds remain in the conversation and build log with the time they
|
|
30
|
+
ran before stopping.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Build and publish updates arrive over an authenticated WebSocket when
|
|
35
|
+
available**, with Replicache polling retained as a fallback. Updates produced
|
|
36
|
+
by workers or another server process now reach an open Feature client without
|
|
37
|
+
waiting for the previous five-second polling interval.
|
|
38
|
+
- Opening the create flow no longer creates an empty feature immediately. Rough
|
|
39
|
+
creates it only after the first message is sent.
|
|
40
|
+
- Advanced consumers of the incidental `SpriteBuild` export must handle the new
|
|
41
|
+
`CANCELED` status. Build records also include
|
|
42
|
+
`cancelRequestedAt: number | null`; update exhaustive status switches and
|
|
43
|
+
test fixtures accordingly.
|
|
44
|
+
|
|
8
45
|
## 0.3.0 - 2026-08-10
|
|
9
46
|
|
|
10
47
|
**This release has breaking API changes.** Rough now uses an explicit client for
|
|
@@ -89,9 +126,8 @@ export was added or removed. The work is in the type and theme changes below.
|
|
|
89
126
|
ping. 0.2.0 calls none of them. It syncs over
|
|
90
127
|
`/api/v1/internal/replicache/pull` and `/api/v1/internal/replicache/push`,
|
|
91
128
|
and the session token exchange at `/api/v1/project/{projectId}/session-token`
|
|
92
|
-
is the only other request it makes. The ten legacy routes
|
|
93
|
-
|
|
94
|
-
is what makes this upgrade time sensitive.
|
|
129
|
+
is the only other request it makes. The ten legacy routes have now been
|
|
130
|
+
removed, which is what makes this upgrade time sensitive.
|
|
95
131
|
|
|
96
132
|
One consequence worth planning for: the SDK now keeps a sync loop open and
|
|
97
133
|
polls for changes every 5 seconds for as long as the page is mounted, where
|
|
@@ -216,5 +252,4 @@ export was added or removed. The work is in the type and theme changes below.
|
|
|
216
252
|
- A light and dark mode toggle in the Feature Builder. The stylesheet now
|
|
217
253
|
carries a `.rough-dark` class holding the dark values for every token.
|
|
218
254
|
- 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)).
|
|
255
|
+
per agent step, formatted in cents below one dollar.
|
package/README.md
CHANGED
|
@@ -1,210 +1,300 @@
|
|
|
1
1
|
# @roughapp/feature
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
product.
|
|
5
|
-
|
|
3
|
+
`@roughapp/feature` is the browser SDK for adding Rough Features to your
|
|
4
|
+
product. Your app defines where features can appear (a **surface**) and the
|
|
5
|
+
typed tools available there. The SDK renders published features and opens the
|
|
6
|
+
Feature Builder where users create them.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
The package ships as compiled ESM and Web Components, with TypeScript
|
|
9
|
+
declarations and a separate stylesheet.
|
|
10
|
+
|
|
11
|
+
> **Pre-1.0:** minor releases may contain breaking changes. Pin
|
|
12
|
+
> `@roughapp/feature` to an exact version if you want to control when you
|
|
13
|
+
> upgrade. See the [changelog](./CHANGELOG.md) before updating.
|
|
14
|
+
|
|
15
|
+
## Before you start
|
|
16
|
+
|
|
17
|
+
You will need:
|
|
18
|
+
|
|
19
|
+
- a Rough project ID;
|
|
20
|
+
- a Rough signing key, with its private key kept on your server; and
|
|
21
|
+
- a backend endpoint that returns a short-lived Rough identity token for the
|
|
22
|
+
signed-in user.
|
|
23
|
+
|
|
24
|
+
Never put the private key in browser code. `fetchUserToken` should call your
|
|
25
|
+
backend, which authenticates the current user and mints the identity token.
|
|
9
26
|
|
|
10
27
|
## Install
|
|
11
28
|
|
|
12
29
|
```bash
|
|
13
|
-
|
|
30
|
+
npm install @roughapp/feature zod
|
|
14
31
|
```
|
|
15
32
|
|
|
16
|
-
|
|
17
|
-
|
|
33
|
+
The package is ESM-only and expects a modern browser and bundler such as Vite,
|
|
34
|
+
Webpack or Rspack. Direct `<script>` and CDN usage is not supported.
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
`@roughapp/feature` is browser-only: its root module registers custom elements
|
|
37
|
+
when it is evaluated. In an SSR app, import it and create the client from a
|
|
38
|
+
client-only boundary rather than from server-rendered code.
|
|
21
39
|
|
|
22
|
-
|
|
40
|
+
Install `zod` directly because your surface definitions import it. Other
|
|
41
|
+
runtime dependencies are installed with the package.
|
|
23
42
|
|
|
24
|
-
|
|
25
|
-
your app entry, to get the Rough theme variables:
|
|
43
|
+
Import the stylesheet once, near your app entry:
|
|
26
44
|
|
|
27
45
|
```ts
|
|
28
46
|
import '@roughapp/feature/style.css'
|
|
29
47
|
```
|
|
30
48
|
|
|
31
|
-
|
|
49
|
+
It provides Rough's theme variables but does not apply a global CSS reset.
|
|
50
|
+
Component styles are registered by the custom elements when the JavaScript
|
|
51
|
+
package loads.
|
|
32
52
|
|
|
33
|
-
##
|
|
53
|
+
## Quick start
|
|
34
54
|
|
|
35
|
-
### Define a surface
|
|
55
|
+
### 1. Define a surface
|
|
36
56
|
|
|
37
|
-
A surface
|
|
38
|
-
|
|
57
|
+
A surface represents one place in your product where Rough Features can appear.
|
|
58
|
+
Give it a stable key and describe the tools a feature may call there.
|
|
39
59
|
|
|
40
60
|
```ts
|
|
41
|
-
import {
|
|
42
|
-
defineRoughSurface,
|
|
43
|
-
Query,
|
|
44
|
-
Mutation,
|
|
45
|
-
Subscription,
|
|
46
|
-
} from '@roughapp/feature'
|
|
61
|
+
import { defineRoughSurface, Query } from '@roughapp/feature'
|
|
47
62
|
import { z } from 'zod'
|
|
48
63
|
|
|
64
|
+
const Message = z.object({
|
|
65
|
+
id: z.string(),
|
|
66
|
+
subject: z.string(),
|
|
67
|
+
})
|
|
68
|
+
|
|
49
69
|
export const inboxSurface = defineRoughSurface({
|
|
50
70
|
key: 'inbox',
|
|
51
71
|
name: 'Inbox',
|
|
52
|
-
description: 'The
|
|
72
|
+
description: 'The signed-in user’s message inbox.',
|
|
53
73
|
tools: [
|
|
54
74
|
new Query({
|
|
55
75
|
id: 'listMessages',
|
|
56
|
-
name: 'List
|
|
57
|
-
description: '
|
|
58
|
-
inputSchema: z.object({
|
|
59
|
-
|
|
76
|
+
name: 'List messages',
|
|
77
|
+
description: 'Return the newest messages in the inbox.',
|
|
78
|
+
inputSchema: z.object({
|
|
79
|
+
limit: z.number().int().min(1).max(100).default(20),
|
|
80
|
+
}),
|
|
81
|
+
outputSchema: z.array(Message),
|
|
60
82
|
outputSample: [{ id: 'msg_1', subject: 'Welcome' }],
|
|
61
83
|
implementation: async ({ limit }) => {
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
const response = await fetch(`/api/messages?limit=${limit}`)
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error('Unable to load messages')
|
|
87
|
+
}
|
|
88
|
+
return response.json()
|
|
64
89
|
},
|
|
65
90
|
}),
|
|
66
|
-
// Mutation uses the same fields. A Subscription implementation receives
|
|
67
|
-
// a callback before the input.
|
|
68
91
|
],
|
|
69
92
|
})
|
|
70
93
|
```
|
|
71
94
|
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
Tool inputs and outputs are validated against their Zod schemas at runtime.
|
|
96
|
+
`outputSample` should be representative, but must not contain real customer
|
|
97
|
+
data or secrets.
|
|
98
|
+
|
|
99
|
+
Use `Query` for reads and `Mutation` for writes; both implementations receive
|
|
100
|
+
the parsed input and return a promise. A `Subscription` implementation receives
|
|
101
|
+
an output callback first and the parsed input second, then returns a promise for
|
|
102
|
+
an unsubscribe function. All three classes are exported from
|
|
103
|
+
`@roughapp/feature`.
|
|
74
104
|
|
|
75
|
-
`defineRoughSurface()` validates and freezes the
|
|
76
|
-
|
|
77
|
-
|
|
105
|
+
`defineRoughSurface()` validates and freezes the definition. The definition has
|
|
106
|
+
no project or connection state, so the same value can be used with clients for
|
|
107
|
+
different projects. Within one client, a surface key identifies one contract;
|
|
108
|
+
reuse the same definition anywhere that surface is active.
|
|
78
109
|
|
|
79
|
-
### Create a client
|
|
110
|
+
### 2. Create a client
|
|
80
111
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
until you destroy it.
|
|
112
|
+
Create one client at the part of your app that owns the Rough project: a route,
|
|
113
|
+
provider, page controller or similar boundary.
|
|
84
114
|
|
|
85
115
|
```ts
|
|
86
116
|
import { createRoughClient } from '@roughapp/feature'
|
|
87
117
|
|
|
88
|
-
const
|
|
118
|
+
export const roughClient = createRoughClient({
|
|
89
119
|
projectId: 'proj_123',
|
|
90
|
-
fetchUserToken: async () =>
|
|
91
|
-
|
|
120
|
+
fetchUserToken: async () => {
|
|
121
|
+
const response = await fetch('/api/rough/user-token', {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
credentials: 'include',
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error('Unable to authenticate with Rough')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { token } = (await response.json()) as { token: string }
|
|
131
|
+
return token
|
|
132
|
+
},
|
|
133
|
+
// baseUrl is optional. Omit it when using the Rough production API.
|
|
92
134
|
})
|
|
93
135
|
```
|
|
94
136
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
137
|
+
`createRoughClient()` returns immediately, then starts authentication, the local
|
|
138
|
+
browser database and the first sync in the background. Operations wait for
|
|
139
|
+
startup themselves. If your UI needs an explicit ready state, use:
|
|
102
140
|
|
|
103
141
|
```ts
|
|
104
|
-
|
|
142
|
+
import { whenRoughClientReady } from '@roughapp/feature'
|
|
143
|
+
|
|
144
|
+
await whenRoughClientReady({ client: roughClient })
|
|
105
145
|
```
|
|
106
146
|
|
|
107
|
-
|
|
108
|
-
|
|
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.
|
|
147
|
+
A failed client is not restarted. Destroy it and create a new one after the
|
|
148
|
+
underlying problem has been handled.
|
|
112
149
|
|
|
113
|
-
|
|
114
|
-
asynchronous, and there is one way to ask for it.
|
|
150
|
+
### 3. Render the surface
|
|
115
151
|
|
|
116
|
-
|
|
152
|
+
Importing `@roughapp/feature` registers its custom elements. Set the `client`
|
|
153
|
+
and `surface` properties on `<rough-surface>`; these are JavaScript values, not
|
|
154
|
+
HTML attributes.
|
|
117
155
|
|
|
118
156
|
```ts
|
|
119
|
-
import
|
|
157
|
+
import '@roughapp/feature'
|
|
158
|
+
|
|
159
|
+
const element = document.createElement('rough-surface')
|
|
160
|
+
element.client = roughClient
|
|
161
|
+
element.surface = inboxSurface
|
|
120
162
|
|
|
121
|
-
|
|
163
|
+
document.querySelector('#rough-slot')?.append(element)
|
|
122
164
|
```
|
|
123
165
|
|
|
124
|
-
|
|
125
|
-
|
|
166
|
+
The package also exports `RoughSurface` for Svelte apps. As with the custom
|
|
167
|
+
element, import and render it only on the client when the app uses SSR.
|
|
126
168
|
|
|
127
|
-
|
|
169
|
+
## Open the Feature Builder
|
|
128
170
|
|
|
129
|
-
|
|
130
|
-
|
|
171
|
+
`openRoughCreate()` opens the create flow without requiring a mounted surface.
|
|
172
|
+
It returns a handle you can close during route teardown.
|
|
131
173
|
|
|
132
174
|
```ts
|
|
133
|
-
import '@roughapp/feature'
|
|
175
|
+
import { openRoughCreate } from '@roughapp/feature'
|
|
134
176
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
document.
|
|
177
|
+
const modal = await openRoughCreate({
|
|
178
|
+
client: roughClient,
|
|
179
|
+
surface: inboxSurface,
|
|
180
|
+
// Defaults to document.body. Use a target when your Rough theme is scoped
|
|
181
|
+
// to a subtree.
|
|
182
|
+
target: document.querySelector('#rough-root') ?? undefined,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// If the host needs to close it programmatically:
|
|
186
|
+
await modal.close()
|
|
139
187
|
```
|
|
140
188
|
|
|
141
|
-
|
|
189
|
+
The user can also close the modal from the UI. Calling `close()` again is safe.
|
|
190
|
+
|
|
191
|
+
## Subscribe to published features
|
|
142
192
|
|
|
143
|
-
|
|
144
|
-
|
|
193
|
+
Use `getRoughFeatures()` when you need the feature list rather than the rendered
|
|
194
|
+
`<rough-surface>`. The callback runs with the initial list, even when it is
|
|
195
|
+
empty, and runs again as published features change.
|
|
145
196
|
|
|
146
197
|
```ts
|
|
147
|
-
import { getRoughFeatures
|
|
198
|
+
import { getRoughFeatures } from '@roughapp/feature'
|
|
148
199
|
|
|
149
|
-
// Subscribe to the published features for a surface.
|
|
150
200
|
const subscription = getRoughFeatures({
|
|
151
|
-
client,
|
|
201
|
+
client: roughClient,
|
|
152
202
|
surface: inboxSurface,
|
|
153
203
|
onFeatures: (features) => {
|
|
154
204
|
console.log(features)
|
|
155
205
|
},
|
|
156
206
|
onError: (error) => {
|
|
157
|
-
console.error(error)
|
|
207
|
+
console.error('Unable to load Rough Features', error)
|
|
158
208
|
},
|
|
159
209
|
})
|
|
160
210
|
|
|
161
|
-
// Optional: wait
|
|
211
|
+
// Optional: wait until the first feature list has been delivered.
|
|
162
212
|
await subscription.ready
|
|
163
213
|
|
|
164
|
-
//
|
|
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()
|
|
214
|
+
// Later:
|
|
174
215
|
await subscription.unsubscribe()
|
|
175
216
|
```
|
|
176
217
|
|
|
177
|
-
|
|
178
|
-
|
|
218
|
+
Both `getRoughFeatures()` and `openRoughCreate()` also accept an `AbortSignal`.
|
|
219
|
+
Their cleanup methods are asynchronous, idempotent and return the same promise
|
|
220
|
+
when called more than once.
|
|
179
221
|
|
|
180
|
-
##
|
|
222
|
+
## Client lifetime
|
|
181
223
|
|
|
182
|
-
|
|
224
|
+
Share one client with everything that uses the same Rough project and signed-in
|
|
225
|
+
person. In one JavaScript realm, only one live client may own a given
|
|
226
|
+
`<baseUrl, projectId, personId>` identity. Starting a duplicate client fails
|
|
227
|
+
with `RoughReplicacheIdentityConflictError`.
|
|
183
228
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
- `defineRoughSurface` (+ `RoughSurfaceDefinition` type)
|
|
187
|
-
- `getRoughFeatures` (+ `GetRoughFeaturesOptions`,
|
|
188
|
-
`RoughFeatureSubscription` types)
|
|
189
|
-
- `openRoughCreate` (+ `OpenRoughCreateOptions`, `RoughModalHandle` types)
|
|
190
|
-
- `RoughSurface` / `<rough-surface>`
|
|
191
|
-
- `Query`, `Mutation`, `Subscription` (re-exported from `@roughapp/bridge`)
|
|
192
|
-
- Errors worth branching on: `RoughClientDestroyedError`,
|
|
193
|
-
`RoughReplicacheIdentityConflictError`, `RoughSurfaceContractConflictError`,
|
|
194
|
-
`RoughClientDestroyError`, `RoughInvalidClientError`
|
|
229
|
+
Destroy the client when its owning route or provider goes away, or before
|
|
230
|
+
replacing it after the user or project changes:
|
|
195
231
|
|
|
196
|
-
|
|
197
|
-
|
|
232
|
+
```ts
|
|
233
|
+
await roughClient.destroy()
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`destroy()` closes any subscriptions and modals still owned by the client and
|
|
237
|
+
waits for cleanup to finish. It is safe to call more than once. Framework
|
|
238
|
+
unmount hooks that cannot await cleanup may use `void roughClient.destroy()`;
|
|
239
|
+
tests and controlled transitions should await it.
|
|
240
|
+
|
|
241
|
+
## Theming
|
|
242
|
+
|
|
243
|
+
The stylesheet defines a light theme on `:root`. Add `rough-dark` to an
|
|
244
|
+
ancestor to use the bundled dark values:
|
|
245
|
+
|
|
246
|
+
```html
|
|
247
|
+
<div id="rough-root" class="rough-dark"></div>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Override variables on `:root` or on the subtree containing your Rough elements:
|
|
251
|
+
|
|
252
|
+
```css
|
|
253
|
+
#rough-root {
|
|
254
|
+
--rough-primary: oklch(0.52 0.2 264);
|
|
255
|
+
--rough-primary-foreground: white;
|
|
256
|
+
--rough-brand: #635bff;
|
|
257
|
+
--rough-radius: 0.75rem;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
198
260
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
261
|
+
Common variables include `--rough-background`, `--rough-foreground`,
|
|
262
|
+
`--rough-card`, `--rough-primary`, `--rough-secondary`, `--rough-muted`,
|
|
263
|
+
`--rough-border`, `--rough-ring`, `--rough-brand` and `--rough-radius`.
|
|
264
|
+
|
|
265
|
+
Create and edit modals attach to `document.body` by default. If your overrides
|
|
266
|
+
live below `body`, pass that themed element as the modal's `target` so the modal
|
|
267
|
+
inherits them.
|
|
268
|
+
|
|
269
|
+
## Public API
|
|
270
|
+
|
|
271
|
+
The recommended integration surface is:
|
|
272
|
+
|
|
273
|
+
- `createRoughClient`, `whenRoughClientReady` and the `RoughClient`,
|
|
274
|
+
`RoughClientOptions` types;
|
|
275
|
+
- `defineRoughSurface` and the `RoughSurfaceDefinition` type;
|
|
276
|
+
- `getRoughFeatures` and the `GetRoughFeaturesOptions`,
|
|
277
|
+
`RoughFeatureSubscription` types;
|
|
278
|
+
- `openRoughCreate` and the `OpenRoughCreateOptions`, `RoughModalHandle` types;
|
|
279
|
+
- `RoughSurface` / `<rough-surface>`;
|
|
280
|
+
- `Query`, `Mutation`, `Subscription`; and
|
|
281
|
+
- `RoughClientDestroyedError`, `RoughReplicacheIdentityConflictError`,
|
|
282
|
+
`RoughSurfaceContractConflictError`, `RoughClientDestroyError` and
|
|
283
|
+
`RoughInvalidClientError`.
|
|
284
|
+
|
|
285
|
+
The package also exports lower-level components and data types used to build its
|
|
286
|
+
own UI. During the pre-1.0 series, do not treat these as a stable integration
|
|
287
|
+
surface:
|
|
288
|
+
|
|
289
|
+
- `RoughCreateModal` (`<rough-create-modal>`), `RoughEditButton`
|
|
290
|
+
(`<rough-edit-button>`) and `RoughFeature` (`<rough-feature>`). The
|
|
291
|
+
`<rough-edit-modal>` element is registered transitively.
|
|
292
|
+
- `PrimaryButton`, `SecondaryButton`, `ResizeHandle`, `SpriteBuildMenu` and
|
|
293
|
+
`SpriteFrame`.
|
|
294
|
+
- `Sprite`, `SpriteBuild`, `JsonValue`, `RoughCleanup`, `FetchUserTokenFn`,
|
|
295
|
+
`SpriteFrameDatastore` and `SpriteFrameDatastoreContext`.
|
|
206
296
|
|
|
207
297
|
## License
|
|
208
298
|
|
|
209
|
-
`UNLICENSED`. This package is publicly installable but is not open source
|
|
299
|
+
`UNLICENSED`. This package is publicly installable but is not open source. Use
|
|
210
300
|
is governed by your Rough customer terms.
|