Reference

Feature SDK changelog

What changed in @roughapp/feature, why it matters, and what to do when upgrading.

The SDK is pre-1.0, so minor releases can contain breaking changes.

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 tools property has been renamed to toolList. New code should pass toolList to defineRoughSurface(), and read surface.toolList instead. To make this transition easier, the defineRoughSurface() function will continue to accept tools, but we highly recommend that you refactor your code to use toolList, as we will be removing this option in a future version.
    Migration steps…
    1. Replace the tools option in each call to defineRoughSurface() with toolList.
    2. Replace any reads of surface.tools with surface.toolList.
    3. 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)
  • Our custom error classes have been removed. The SDK now throws plain old Error values. This keeps error handling simpler, but if you were importing one of our custom Rough*Error classes, you will need to change your code.
    Migration steps…
    1. Remove imports of RoughClientDestroyError, RoughClientDestroyedError, RoughInvalidClientError, RoughReplicacheIdentityConflictError, and RoughSurfaceContractConflictError.
    2. Replace instanceof Rough*Error branches with your app's normal error reporting or recovery flow.
    3. Stop reading the removed errors, replicacheName, and surfaceKey fields. 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)
    }

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 the getRoughFeatures() 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 isEditable prop hides the Rough Edit button. When isEditable is set to false, 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 SpriteBuild export must handle the new CANCELED status. Build records also include cancelRequestedAt: 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 client and surface to getRoughFeatures(), 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() and modal.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, projectId and 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 call await 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 to document.body.

Removed

  • initRough() has been removed. There is no default or global client.
  • defineSurface() has been replaced by defineRoughSurface(). Rename toolList to tools when updating your surface definitions.
  • registerSurfaceEntry has 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

Unsupported release

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

Unsupported release

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.

  • Sprite and SpriteBuild reference 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, .status or .prompt off a getRoughFeatures result must be reworked, and SpriteBuild no 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, SurfaceId and PersonId are now string & { __brand: … } rather than string, on RoughFeature's featureId and buildId, RoughCreateModal's and SpriteBuildMenu's surfaceId, SpriteFrame's spriteBuildId, and registerSurfaceEntry's surfaceId.

    Reading is unaffected, because a branded string is still assignable to string. Passing one in is not: an ID you hold as a plain string, 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 getRoughFeatures into 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.0Closest 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-brand is the saturated brand colour

    Watch --rough-accent in 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-primary now controls primary actions. --rough-background and --rough-border also 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.
  • getRoughFeatures is a live subscription rather than a one-shot fetch. In 0.1.0 the callback fired once and only fired again when something called triggerRefresh. 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.js grows 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

  • onpublish is gone from RoughCreateModal, RoughEditButton and RoughEditModal. Because getRoughFeatures is 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 the getRoughFeatures callback. Note that the prop was optional, so depending on your setup this may fail quietly rather than at compile time.
  • triggerRefresh is gone from the SurfaceEntry passed to registerSurfaceEntry. 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-dark class 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

Unsupported release

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-provided fetchUserToken callback and an optional API base URL.
  • Typed surface definitions. defineSurface() describes where features can appear and the Query, Mutation and Subscription tools they can call.
  • Published feature rendering and discovery. Apps can use RoughSurface or <rough-surface> to render features and getRoughFeatures() 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.