Skip to content

feat(workbench): add unstable_defineMediaLibrary#1423

Open
gu-stav wants to merge 11 commits into
mainfrom
sdk-1782
Open

feat(workbench): add unstable_defineMediaLibrary#1423
gu-stav wants to merge 11 commits into
mainfrom
sdk-1782

Conversation

@gu-stav

@gu-stav gu-stav commented Jul 2, 2026

Copy link
Copy Markdown
Member

unstable_defineMediaLibrary declares the Sanity Media Library as a workbench app — a singleton whose custom fields become an installation config.

Design decisions

  • The config splits in two. A field's defineField value can't serialize, so only its metadata rides the wire (and deploys); the live value loads from the app's federation module. The workbench merges the two halves.
  • Discriminated by appType. fields is the media-library payload; the shape is a union, so other config types can be added without reworking the pipeline.
  • Its own dev channel. In dev the config travels over the dev-server registry + HMR on an installationConfigs stream, separate from local applications. A dev server is a local app unless it's config-only (a config with no interfaces); one with interfaces too is both.

Workbench half: sanity-io/workbench#267.


Note

Medium Risk
Touches workbench build/dev/deploy and federation expose semantics; deploy persistence is intentionally stubbed, so behavior depends on the paired workbench change.

Overview
Introduces unstable_defineMediaLibrary as sugar over unstable_defineApp, which now accepts optional installationConfig and internal isSingleton. Media library apps are fixed singletons (media-library) whose custom fields become a discriminated media-library installation config.

Build & federation: Installation configs generate a federation module (./configs/installation_config) that aggregates field metadata and live defineField imports; build and dev exposes now include installationConfig alongside views and services.

Dev: Registry entries carry installationConfigs separately from interfaces; the workbench HMR payload splits applications vs installationConfigs (config-only servers skip the apps list). Interface-set tracking is generalized to exposesSetId so adding/renaming config fields triggers remote rebuilds while title/public edits do not.

Deploy: assertDeployable treats installation config as deployable content; deploy validates and logs the installation-config payload but does not persist it yet. @sanity/cli re-exports the new API; a fixtures/media-library example and tests cover schemas, artifacts, and routing.

Reviewed by Cursor Bugbot for commit 71d7eac. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @sanity/cli

Compared against main (38480893)

@sanity/cli

Metric Value vs main (3848089)
Internal (raw) 2.2 KB -450 B, -16.5%
Internal (gzip) 838 B -218 B, -20.6%
Bundled (raw) 11.16 MB +1.1 KB, +0.0%
Bundled (gzip) 2.10 MB +230 B, +0.0%
Import time 791ms +5ms, +0.6%

bin:sanity

Metric Value vs main (3848089)
Internal (raw) 782 B -
Internal (gzip) 423 B -
Bundled (raw) 9.87 MB -
Bundled (gzip) 1.78 MB -
Import time 2.05s -3ms, -0.1%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against main (38480893)

Metric Value vs main (3848089)
Internal (raw) 105.5 KB -
Internal (gzip) 25.8 KB -
Bundled (raw) 21.72 MB -
Bundled (gzip) 3.46 MB -
Import time 701ms +3ms, +0.4%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against main (38480893)

Metric Value vs main (3848089)
Internal (raw) 908 B -
Internal (gzip) 483 B -
Bundled (raw) 931 B -
Bundled (gzip) 491 B -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Preview this PR with pkg.pr.new

Run the Sanity CLI

npx https://pkg.pr.new/sanity-io/cli/@sanity/cli@71d7eac <command>

...Or upgrade project dependencies

📦 @sanity/cli
pnpm install https://pkg.pr.new/@sanity/cli@71d7eac
📦 @sanity/cli-build
pnpm install https://pkg.pr.new/@sanity/cli-build@71d7eac
📦 @sanity/cli-core
pnpm install https://pkg.pr.new/@sanity/cli-core@71d7eac
📦 @sanity/cli-test
pnpm install https://pkg.pr.new/@sanity/cli-test@71d7eac
📦 @sanity/eslint-config-cli
pnpm install https://pkg.pr.new/@sanity/eslint-config-cli@71d7eac
📦 @sanity/workbench-cli
pnpm install https://pkg.pr.new/@sanity/workbench-cli@71d7eac

View Commit (71d7eac)

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli/src/actions/build/buildApp.ts 77.8% (±0%)
packages/@sanity/cli/src/actions/deploy/installationConfigDeployment.ts 100.0% (new)
packages/@sanity/cli/src/actions/dev/servers/getDevServerConfig.ts 100.0% (±0%)
packages/@sanity/workbench-cli/src/actions/build/artifact.ts 16.7% (±0%)
packages/@sanity/workbench-cli/src/actions/build/configs/artifact.ts 100.0% (new)
packages/@sanity/workbench-cli/src/actions/deploy/getWorkbench.ts 87.5% (±0%)
packages/@sanity/workbench-cli/src/actions/dev/deriveInterfaces.ts 100.0% (±0%)
packages/@sanity/workbench-cli/src/actions/dev/exposesSetId.ts 100.0% (new)
packages/@sanity/workbench-cli/src/actions/dev/registry.ts 4.3% (±0%)
packages/@sanity/workbench-cli/src/actions/dev/startDevManifestWatcher.ts 89.4% (±0%)
packages/@sanity/workbench-cli/src/actions/dev/startDevServerRegistration.ts 100.0% (±0%)
packages/@sanity/workbench-cli/src/actions/dev/startWorkbenchDevServer.ts 89.8% (+ 0.7%)
packages/@sanity/workbench-cli/src/contract.ts 100.0% (±0%)
packages/@sanity/workbench-cli/src/defineApp.ts 100.0% (±0%)
packages/@sanity/workbench-cli/src/resolveWorkbenchApp.ts 100.0% (±0%)

Comparing 15 changed files against main @ 3848089387f2948ea948cb6c4d992e6aedbab09b

Overall Coverage

Metric Coverage
Statements 73.7% (+ 0.1%)
Branches 63.8% (+ 0.1%)
Functions 67.8% (+ 0.3%)
Lines 74.3% (+ 0.1%)

@gu-stav gu-stav force-pushed the sdk-1782 branch 3 times, most recently from 2f17943 to 1171cf3 Compare July 2, 2026 14:55
@gu-stav gu-stav changed the title feat(workbench): add unstable_defineMediaLibrary and the configs declaration family feat(workbench): add unstable_defineMediaLibrary Jul 2, 2026
@gu-stav gu-stav force-pushed the sdk-1782 branch 2 times, most recently from 6ae0e06 to ed8da3c Compare July 2, 2026 15:23
@gu-stav gu-stav changed the base branch from main to refactor/workbench-exposes July 2, 2026 15:26
@gu-stav gu-stav force-pushed the refactor/workbench-exposes branch from 08a446d to 63435cb Compare July 2, 2026 15:30
@gu-stav gu-stav force-pushed the refactor/workbench-exposes branch from 63435cb to ad9ff83 Compare July 2, 2026 15:38
@gu-stav gu-stav force-pushed the sdk-1782 branch 2 times, most recently from a418e8f to 6591862 Compare July 2, 2026 15:39
@gu-stav gu-stav force-pushed the refactor/workbench-exposes branch from ad9ff83 to d59fddb Compare July 3, 2026 06:50
Base automatically changed from refactor/workbench-exposes to main July 3, 2026 08:09
@gu-stav gu-stav changed the base branch from main to refactor/move-is-workbench-app July 3, 2026 09:00
Base automatically changed from refactor/move-is-workbench-app to main July 3, 2026 12:31
@gu-stav gu-stav marked this pull request as ready for review July 3, 2026 14:04
@gu-stav gu-stav requested a review from a team as a code owner July 3, 2026 14:04
@gu-stav gu-stav requested a review from joshuaellis July 3, 2026 14:04

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 19b6020. Configure here.

Comment thread packages/@sanity/workbench-cli/src/actions/dev/exposesSetId.ts Outdated

@gu-stav gu-stav Jul 3, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly renamed from interfaceSetId.ts, because an installation config isn't an interface but fits into the same model to calculate HMR changes too.

gu-stav added 10 commits July 3, 2026 16:12
…aration family

Media Library needs per-org custom fields, and in Brett's model those
are installation configs — a versioned snapshot on an org's app
installation, not interfaces registered with the application service.
An installation has one active config, so an app declares at most one
(rejected otherwise).

unstable_defineApp grows generic primitives: an isSingleton flag and a
configs list — a discriminated union of installation configs, with
media-library (an object carrying a fields array) the only member today.
unstable_defineMediaLibrary is sugar: isSingleton, fixed name/title, and
all fields collected into that single media-library config.

Configs ride the existing federation build and dev plumbing: the fields
compile into one self-accepting HMR module exposed as
./configs/media-library, and the config shows up on the dev-server
registry (interface_type: media-library) so the workbench shell sees it
over the existing websocket channel. Deploy validates and logs the
installation-config payload only — Brett's endpoint isn't wired yet.
…ations

A config-only singleton isn't an app, so a media-library dev server no longer
registers as a local application. Its installation config travels on an
installationConfigs channel next to the applications in the HMR payload.
… moduleName

Tag the config by `type` with `config` as the payload, and send the app's
`unstable_defineApp` name as `moduleName` plus its origin as `remoteURL`, so the
workbench loads the config module without reconstructing the remote name or URL
from host and port.
…config ready

The dev-server registry carries `installationConfigs` (an array — an app may
expose more than one), and a singleton is flagged with `isSingleton` rather than
a `media-library` dev-server type. The wire routes config-only singletons to the
installationConfigs channel by that flag.
moduleName is only read when loading a config's live values, so it lives on the
installationConfigs entries rather than as a top-level registry field that every
studio/app carried but never used.
… flag

A dev server is a local application unless it's config-only — an installation
config with no interfaces. A config that also exposes interfaces is both a local
app and an installation config. Drop the `isSingleton` registry field: routing
is by what a server exposes, not whether it's a singleton.
… config generic

Derive the dev-manifest-watcher extract return from ManifestPatch instead of
re-listing its fields. Keep the installation-config wire projection payload-
agnostic (spread whatever remains after the discriminator and transport), since
`fields` is the media-library payload and other config types will differ.
…pt from the catalog

Pinning `sanity: workbench` pulled a second copy of the Studio dependency tree
(the portable-text editor and its plugins) into the lockfile. Use `catalog:` for
`sanity` and `typescript`, like the rest of the workspace, so the fixture shares
the existing versions.
`exposesSetId` used a fixed token for the installation config, so editing
`fields` in `sanity.cli.ts` (add or rename) during dev kept the same expose-set
id — the watcher patched the wire metadata but skipped the rebuild, leaving the
build-baked config module stale. Key the id on each field, like interfaces, so a
field change takes the rebuild path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant