Reference
Release Notes
Release notes and upgrade guidance for the Rough Feature SDK.
The SDK is pre-1.0, so minor releases can contain breaking changes.
v0.7.2
We have a small release today, with no changes to the public API.
Added
- Features can now be unpublished from the management console! Unpublished features will be excluded from
subscribeToRoughFeatures. You can still explicitly display a Rough Feature usingmountRoughFeature, and its specificbuildId.
Fixed
- We have fixed an annoying bug where the Feature Builder preview unexpectedly reloads and flashes while idle.
Changed
- Rough now records which version of the SDK is being used on which projects. We need this so we can see which versions of the SDK are currently being used in production, helping us make decisions around how long we need to support past versions of the SDK.
v0.7.1
This is a patch release that fixes our TypeScript type definitions.
We have also hidden internal properties from the RoughClient type, so that we
can change these in future versions without having to support backwards
compatibility.
Breaking type change
- The
RoughClienttype has now had most of its internal properties hidden away. The only public property it has now is a singledestroy()method.
You should never need to access the internal properties of theRoughClient, instead use our public SDK functions (see the SDK Reference for details). By hiding them, we can also simplify the package type definitions.
Fixed
- As part of the simplification of our type definitions, we have also fixed
some errors with how the
index.d.tsfile was being created.
This update fixes TypeScript errors caused by the published declarations in v0.7.0.
v0.7.0
This is a big one! We have made some great quality-of-life improvements to our SDK. It is simpler to work with and more reliable. However, that also comes with a large number of breaking changes.
Breaking changes
- The stylesheet has been removed. You should no longer import
@roughapp/feature/style.css. We now automatically handle loading the stylesheet for you. - The
getRoughFeaturesfunction has been renamed tosubscribeToRoughFeatures,a more accurate description of what it does. The callback method has also been renamed fromonFeaturestoonChange.
// before
getRoughFeatures({ client, surface, onFeatures })
// after
subscribeToRoughFeatures({ client, surface, onChange })
- The way to create a new feature with the Feature Builder has also changed. The function
openRoughCreatehas been replaced with two separate function calls:createRoughFeature: create a new Rough FeatureopenRoughBuilder: open the Feature Builder for a specific feature
- This is more modular, meaning you can use the same
openRoughBuilderfunction to edit existing features or newly created ones.
// before
openRoughCreate({ client, surface })
// after
const feature = await createRoughFeature({ client, surface })
openRoughBuilder({ client, surface, featureId: feature.id })
- The function
createRoughClienthas been renamed tostartRoughClient. We changed it tostartto minimise confusion with the verbcreatealso being used withcreateRoughFeature. - Starting a new Rough Client now requires two new properties:
externalUserId: the ID of the currently logged-in user. Must match the JWTsubfield.externalAccountId: the ID of the account that the user is logged into. Must match the JWTrough.account.idfield.
- Note: both of these are your app's identifiers, and must match the values you specify in the JWT returned by
fetchUserToken. - There is also a new
onErrorcallback you can subscribe to be notified of any authentication errors.
// before
const client = createRoughClient({
projectId: 'my-rough-project-123',
fetchUserToken: () => { ... }
})
// after
const client = startRoughClient({
projectId: 'my-rough-project-123',
externalUserId: localSession.userId,
externalAccountId: localSession.accountId,
fetchUserToken: () => { ... },
onError: (error) => {
console.error(error)
}
})
- The
whenRoughClientReadyfunction has been removed. This is no longer necessary, as the Rough Client is now synchronous and can be used immediately. - We have removed the
<rough-feature>Web Component and<RoughFeature>Svelte components from the SDK. These were difficult to work with (especially if you are not using Svelte as your framework) and have been replaced with a vanilla JS function,mountRoughFeature. ThebuildIdproperty is now optional if you want to display the currently published version of the feature.
// before
const element = document.createElement('rough-feature')
element.client = client
element.surface = surface
element.featureId = feature.id
element.buildId = feature.publishedSpriteBuildId
container.append(element)
// after
const element = mountRoughFeature(container, {
client,
surface,
featureId: feature.id
})
- We have also removed all other Web Components that were previously being registered:
<rough-create-modal>: callcreateRoughFeatureandopenRoughBuilderinstead<rough-edit-modal>: callopenRoughBuilderinstead<rough-edit-button>: callopenRoughBuilderinstead
- We have also removed all other Svelte components, including:
<RoughCreateModal>: callcreateRoughFeatureandopenRoughBuilderinstead<RoughEditButton>: callopenRoughBuilderinstead
- When calling
defineRoughSurface, the deprecatedtoolsproperty has been removed. You must usetoolList(see the v0.6.0 release notes for more details).
Added
- The Feature Builder now lets you choose whether to use a dark or light theme! When calling
openRoughBuilder,you can now passtheme: "dark"to use the dark theme (it defaults to the light theme). - We have finally got a good way to display Rough Features on your app:
mountRoughFeature. This is a vanilla JS function that is very easy to work with. Just pass it aclient,surface,afeatureId,and an HTML Element to mount it inside of. It has anupdatemethod allowing you to update the props and anunmountmethod to remove it from the page. You no longer need to pass abuildId- the currently published version will be displayed by default - but if you do want to display a particular version, you can specify thebuildId.
Changed
- Now that
startRoughClienthas access to theexternalUserIdandexternalAccountId, it can immediately load previously cached data, substantially reducing startup times. - We have fixed an issue with the Feature Builder UI being affected by global CSS rules. We now mount the Feature Builder inside a Shadow Root, so that we can guarantee it looks good on any website. We also now bundle a webfont, so it looks good on any computer.
- The
subscribeToRoughFeaturesfunction will only callonChangewhen the list of features actually changes. Previously, we were seeing issues with this callback happening more frequently than necessary, sometimes causing re-rendering issues.
v0.6.0
We have been tidying up our codebase this week and renaming some internal
components to be more consistent. We have tried to minimise the number of
breaking changes, but there are a few minor adjustments you will need to make
in your apps. Surfaces now use toolList (previously tools) and we have
removed the custom error classes to keep things simple. On the new features
side, you can now specify a custom prompt for your Rough Project, which can
guide how Rough builds Features, helping them feel even more at home in your
product!
Breaking changes
- The Surface
toolsproperty has been renamed totoolList. New code should passtoolListtodefineRoughSurface(), and readsurface.toolListinstead. To make this transition easier, thedefineRoughSurface()function will continue to accepttools, but we highly recommend that you refactor your code to usetoolList, as we will be removing this option in a future version.Migration steps…
- Replace the
toolsoption in each call todefineRoughSurface()withtoolList. - Replace any reads of
surface.toolswithsurface.toolList. - Run your type checks and tests to catch Surface definitions or helpers that still use the old property.
// Before const surface = defineRoughSurface({ key: 'dashboard', name: 'Dashboard', description: 'The main dashboard view', tools: [getCurrentUser], }) console.log(surface.tools) // After const surface = defineRoughSurface({ key: 'dashboard', name: 'Dashboard', description: 'The main dashboard view', toolList: [getCurrentUser], }) console.log(surface.toolList) - Replace the
- Our custom error classes have been removed. The SDK now throws plain old
Errorvalues. This keeps error handling simpler, but if you were importing one of our customRough*Errorclasses, you will need to change your code.Migration steps…
- Remove imports of
RoughClientDestroyError,RoughClientDestroyedError,RoughInvalidClientError,RoughReplicacheIdentityConflictError, andRoughSurfaceContractConflictError. - Replace
instanceof Rough*Errorbranches with your app's normal error reporting or recovery flow. - Stop reading the removed
errors,replicacheName, andsurfaceKeyfields. Display the regular error message where useful, but do not depend on matching its text for application logic.
// Before import { RoughClientDestroyedError, RoughSurfaceContractConflictError, } from '@roughapp/feature' try { await openRoughCreate({ client, surface }) } catch (error) { if (error instanceof RoughClientDestroyedError) { // ... } } // After try { await openRoughCreate({ client, surface }) } catch (error) { const message = error instanceof Error ? error.message : String(error) reportError(message) } - Remove imports of
Added
- Rough Projects can now have custom prompts! Rough admins can specify a custom system prompt for their Rough Features. This lets you guide how the Features should fit into your product by specifying conventions or particular styles for generated Rough Features.
v0.5.0
Users can now upload images to Feature Builder chats, and when embedding a
Rough Feature, you can now hide Rough's editing controls. This release also
removes the <rough-surface> component and cleans up the styling of the
<rough-feature> frame.
Breaking changes
<rough-surface>has been removed. Instead of using Rough to render a list of surfaces, we encourage you to control how features are rendered. You can use thegetRoughFeatures()function to access the feature list, and then render each using<rough-feature>. This gives you full control over how and where features are rendered for a Surface.Migration Guide…
// Before: <rough-surface> rendered every published feature const surfaceElement = document.createElement('rough-surface') surfaceElement.client = client surfaceElement.surface = surface slot.append(surfaceElement) // After: subscribe and render each feature in host-owned layout const subscription = getRoughFeatures({ client, surface, onFeatures: (features) => { slot.replaceChildren( ...features.flatMap((feature) => { if (!feature.publishedSpriteBuildId) return [] const featureElement = document.createElement('rough-feature') featureElement.client = client featureElement.surface = surface featureElement.featureId = feature.id featureElement.buildId = feature.publishedSpriteBuildId return [featureElement] }), ) }, }) // When this part of the UI unmounts await subscription.unsubscribe()<rough-feature>no longer has visible border styles. This makes it easier to integrate Rough Features into your app aesthetically, allowing you to control how the feature frame looks.
Added
- Users can upload images. Each message can now have up to three GIF, JPEG, PNG, or WebP images attached. These images can be used to influence the feature design or even be integrated and displayed in the feature directly.
- The new
isEditableprop hides the Rough Edit button. WhenisEditableis set tofalse, users will no longer see the Rough Icon button and will not be able to customise that feature.
v0.4.2
The Feature Builder's chat assistant can now use build logs when helping users refine a feature, and open clients receive more reliable real-time updates.
Added
- The chat assistant can inspect the logs for a specific build. It can use build steps, failures and agent output as context when responding to follow-up messages.
Changed
- Real-time updates now cover changes made across Rough’s services. Publishing from the management app and progress from background build workers notify connected Feature SDK clients over their project WebSocket, rather than waiting for the next poll.
- Fallback polling now runs once per minute instead of every five seconds. This reduces background requests while the WebSocket is connected.
Fixed
- The Build Logs panel now follows the latest build in the conversation. Selecting an earlier ready build for preview no longer replaces the logs for the build being discussed.
v0.4.1
Documentation-only release. The README now has expanded setup, security, lifecycle, theming and API guidance, and the changelog includes the release notes that were missing from 0.4.0. There are no runtime, type or API changes.
v0.4.0
The Feature Builder is now conversational: Rough can clarify what to build, inspect the tools available on the surface and start a build when it has enough context. The recommended client and surface APIs are unchanged, so host apps do not need an integration migration.
Added
- Feature Builder chat. Conversations are saved per feature and remain available when the builder is reopened. Follow-up messages can refine the feature before Rough starts another build.
- Markdown responses. Assistant messages render headings, lists, links, tables, blockquotes and code blocks.
- Build cancellation. Pending and in-progress builds have a Stop control. Canceled builds remain in the conversation and build log with the time they ran before stopping.
Changed
- Build and publish updates reach open clients sooner. The SDK uses an authenticated WebSocket when available, so updates no longer wait for the five-second polling interval. Polling remains available as a fallback.
- Abandoned create flows no longer leave empty features behind. Rough creates the feature only after the user sends the first message.
- Advanced consumers of the incidental
SpriteBuildexport must handle the newCANCELEDstatus. Build records also includecancelRequestedAt: number | null; update exhaustive status switches and test fixtures accordingly.
v0.3.0
This release has breaking API changes. Rough now uses an explicit client for each project instead of global initialization. Apps can safely show features from multiple Rough projects at the same time, and each client has a clear, awaitable lifecycle.
Breaking changes
The recommended migration is shown below. In summary:
- All stateful functions and components now take a client. Pass
clientandsurfacetogetRoughFeatures(),openRoughCreate(),<rough-surface>,<rough-feature>,<rough-edit-button>and the modal elements. - Cleanup methods are asynchronous and safe to call more than once. Await
client.destroy(),subscription.unsubscribe()andmodal.close()when you need to know that cleanup has finished. - Create one client and share it for each signed-in person and project. A
second client for the same
baseUrl,projectIdand person fails with an error. Clients for different projects or different signed-in people can run together. openRoughCreate()no longer requires a mounted Rough component. You can open it directly with a client and surface.
Added
createRoughClient({ projectId, baseUrl?, fetchUserToken })creates and starts a client for one project. Keep the client for as long as your app needs that project, then callawait client.destroy().whenRoughClientReady({ client })lets you wait for startup or handle a startup error.openRoughCreate({ target })lets you attach the create modal inside the element that scopes your Rough theme. It defaults todocument.body.
Removed
initRough()has been removed. There is no default or global client.defineSurface()has been replaced bydefineRoughSurface(). RenametoolListtotoolswhen updating your surface definitions.registerSurfaceEntryhas been removed. You no longer need to register a surface before using it.
Migration
// Before
initRough({ projectId, fetchUserToken })
const surface = defineSurface({ key, name, description, toolList })
const unsubscribe = getRoughFeatures(surface, onFeatures)
openRoughCreate(surface, { projectId })
// After
const client = createRoughClient({ projectId, fetchUserToken })
const surface = defineRoughSurface({ key, name, description, tools })
const subscription = getRoughFeatures({ client, surface, onFeatures })
const modal = await openRoughCreate({ client, surface })
await modal.close()
await subscription.unsubscribe()
await client.destroy()
v0.2.1
The published package now includes this changelog, so release and migration notes are available alongside the installed SDK. There are no runtime, type or API changes.
v0.2.0
Upgrade from 0.1.0 as soon as you can. Version 0.1.0 relies on retired service endpoints and can no longer list, create, build or publish features. Version 0.2.0 uses the supported synchronization service.
Upgrading needs no configuration change. initRough takes the same
projectId, fetchUserToken and optional baseUrl, and no export was added
or removed. Consumers of the exported data types or theme variables need the
migrations below.
Breaking changes
Hosts using exported data types or custom theme variables need the migrations below.
SpriteandSpriteBuildreference related records by ID instead of nesting them.// 0.1.0 type Sprite = { id: string name: string ownedByPersonId: string publishedSpriteBuild: SpriteBuild | null // full nested record } type SpriteBuild = { createdByPerson: { id: string; name: string } // ... } // 0.2.0 type Sprite = { id: SpriteId name: string ownedByPersonId: PersonId publishedSpriteBuildId: SpriteBuildId | null // ID only } type SpriteBuild = { createdByPersonId: PersonId // ... }Anything reading
sprite.publishedSpriteBuild.artifactUrl,.statusor.promptoff agetRoughFeaturesresult must be reworked, andSpriteBuildno longer carries the build author's display name. There is no replacement for that name in the public API.- ID fields are branded string types.
SpriteId,SpriteBuildId,SurfaceIdandPersonIdare nowstring & { __brand: … }rather thanstring, onRoughFeature'sfeatureIdandbuildId,RoughCreateModal's andSpriteBuildMenu'ssurfaceId,SpriteFrame'sspriteBuildId, andregisterSurfaceEntry'ssurfaceId.Reading is unaffected, because a branded string is still assignable to
string. Passing one in is not: an ID you hold as a plainstring, from your own database or a URL parameter, is rejected. The brand types are not themselves exported, so reach them through the types that are:type SpriteId = Sprite['id'] type SpriteBuildId = NonNullable<Sprite['publishedSpriteBuildId']> type SurfaceId = RoughCreateModalElement['surfaceId']IDs that flow straight out of
getRoughFeaturesinto a component still typecheck with no cast. - The stylesheet ships an expanded set of semantic design tokens. Hosts
that theme Rough through CSS custom properties need to remap these eight
removed variables to their nearest replacement:
Removed in 0.2.0 Closest 0.2.0 token --rough-text--rough-foreground--rough-text-secondary--rough-muted-foreground--rough-surface--rough-card--rough-surface-border--rough-border--rough-background-muted--rough-muted--rough-danger--rough-destructive--rough-shadow--rough-shadow-xs,-md,-lg--rough-accent-secondaryNo equivalent; --rough-brandis the saturated brand colourWatch
--rough-accentin particular. The name survived but now controls a subtle hover background rather than the primary action colour. A host that set it to a brand colour to tint buttons will find it tinting hover states instead.--rough-primarynow controls primary actions.--rough-backgroundand--rough-borderalso kept their names with new values. The expanded set adds variables for radius and success states.
Changed
- Feature data now stays synchronized while the SDK is mounted. Published features and build changes can appear without another host action. Unlike 0.1.0's one-off requests, 0.2.0 keeps a sync loop open and polls every five seconds, which may affect network monitoring or request allowlists.
getRoughFeaturesis a live subscription rather than a one-shot fetch. In 0.1.0 the callback fired once and only fired again when something calledtriggerRefresh. It now re-fires on every relevant change that syncs in, so a feature published in another tab or by another user appears without the host doing anything.- The JavaScript bundle is larger because it now includes continuous sync.
index.jsgrows from 105 kB to 320 kB raw (27 kB to 77 kB gzipped). The stylesheet grows from 324 B to 5.2 kB with the expanded theme token set.
Removed
onpublishis gone fromRoughCreateModal,RoughEditButtonandRoughEditModal. BecausegetRoughFeaturesis now a live subscription, a publish already reaches the host through that callback, which is what the prop existed to signal. Move any publish handling into thegetRoughFeaturescallback. Note that the prop was optional, so depending on your setup this may fail quietly rather than at compile time.triggerRefreshis gone from theSurfaceEntrypassed toregisterSurfaceEntry. It was the manual refresh hook for the old fetch-once model and has nothing to do now. Delete it from the object you pass in; leaving it there is an excess property and will be rejected.
Added
- A light and dark mode toggle in the Feature Builder. The stylesheet now
carries a
.rough-darkclass holding the dark values for every token. - The Feature Builder's build log now reports token usage and estimated cost per agent step, formatted in cents below one dollar.
v0.1.0
Initial public release of the Rough Feature SDK. This version is no longer supported because the service endpoints it uses have been retired; upgrade to a current release before integrating it.
Added
- An embeddable browser SDK for Rough Features. The npm package ships compiled ESM, TypeScript declarations, Web Components and a separate stylesheet for apps using a modern bundler.
- Project and user authentication.
initRough()configures a project with a host-providedfetchUserTokencallback and an optional API base URL. - Typed surface definitions.
defineSurface()describes where features can appear and theQuery,MutationandSubscriptiontools they can call. - Published feature rendering and discovery. Apps can use
RoughSurfaceor<rough-surface>to render features andgetRoughFeatures()to receive the published feature list for a surface. - Embedded feature creation and editing.
openRoughCreate()opens the Feature Builder, where users can create, build and publish a feature without leaving the host product.