Skip to content

feat(personalization): add field-level personalization - #1226

Merged
stipsan merged 7 commits into
mainfrom
cursor/port-field-level-personalization-620c
Aug 3, 2026
Merged

feat(personalization): add field-level personalization#1226
stipsan merged 7 commits into
mainfrom
cursor/port-field-level-personalization-620c

Conversation

@stipsan

@stipsan stipsan commented Jun 18, 2026

Copy link
Copy Markdown
Member

Ports sanity-io/sanity-plugin-personalization#38 into the monorepo.

Original author: @jjburbridge (credited via the author: directive in the changeset and as the git commit author).

Walkthrough

Field-level personalization on the Tagline field: activate via the people icon, add a segment, and "Copy default" to seed the value.

field_level_personalization_demo.mp4

Segments section with Add Enterprise / Small Business / Individual buttons
Copy default populates the segment value

What this adds

  • fieldLevelPersonalization plugin export — segment-based field personalization (the personalization counterpart to fieldLevelExperiments). Segments can be provided as a static array, fetched from an external API, or stored as Sanity documents (async resolver receives the client).
  • Component reorganization — experiment components moved into src/components/experiment/ and the new personalization components live in src/components/personalization/, each with a barrel index.ts. The shared array item component is renamed ExperimentItemArrayItem.
  • Field groups — experiment/personalization object types now declare default / experiments (or personalization) / all-fields groups, and a clearChildrenGroups helper clears nested group state so fields render cleanly inside arrays and modals.
  • Type renameFieldPluginConfig is renamed to ExperimentFieldPluginConfig, with a deprecated FieldPluginConfig type alias kept for compatibility. New PersonalizationFieldPluginConfig, PersonalizationContextProps, and PersonalizationArrayInputProps types are added.
  • Test-studio example — the existing personalization example now also wires fieldLevelPersonalization with sample segments (Enterprise / Small Business / Individual).

Not ported: the upstream CHANGELOG.md / package.json version bumps from semantic-release (the monorepo uses changesets — a minor changeset is included instead).

Review feedback addressed

  • Guard nested children before clearing field groups
  • Fix preview title operator precedence
  • Use configured apiVersion in previews
  • Avoid "undefined" segment titles; null-safe preview path traversal

Testing

  • pnpm format
  • pnpm lint (type-aware oxlint) — passes
  • pnpm --filter @sanity/personalization-plugin build — passes
  • Plugin vitest — passes
  • Manual verification in the test studio (see Walkthrough)

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Port sanity-io/sanity-plugin-personalization#38

Add a `fieldLevelPersonalization` plugin export for segment-based field
personalization, with segments sourced from a static array, an external API,
or Sanity documents. Reorganize experiment components into `experiment/` and
`personalization/` folders, rename the shared array item component to
`ArrayItem`, and introduce document field groups so experiment/personalization
fields render cleanly inside arrays and modals. The exported `FieldPluginConfig`
type is renamed to `ExperimentFieldPluginConfig`.

Also wires a field-level personalization example into the test studio.
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
plugins-studio Ready Ready Preview Aug 3, 2026 11:24am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plugins-e2e-test-studio Ignored Ignored Aug 3, 2026 11:24am

Request Review

@changeset-bot

changeset-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2fc0185

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
sanity-plugin-cloudinary Minor
@sanity/personalization-plugin Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@stipsan stipsan added the 🤖 bot label Jun 18, 2026 — with Cursor
@stipsan
stipsan marked this pull request as ready for review June 18, 2026 14:16
@stipsan
stipsan requested review from a team as code owners June 18, 2026 14:16
@stipsan
stipsan requested review from annez and Copilot and removed request for a team June 18, 2026 14:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds field-level personalization to @sanity/personalization-plugin (ported from sanity-io/sanity-plugin-personalization#38), enabling segment-based per-field overrides alongside the existing field-level experiments functionality.

Changes:

  • Introduces new fieldLevelPersonalization plugin export with segment types, inputs, and previews.
  • Reorganizes experiment and personalization UI components into src/components/experiment/* and src/components/personalization/*, with shared ArrayItem.
  • Updates exported/public types (notably renaming FieldPluginConfigExperimentFieldPluginConfig) and wires the new feature into the test-studio example + package exports snapshot.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
plugins/@sanity/personalization-plugin/src/utils/clearChildGroups.ts Adds helper to clear nested field group state for cleaner rendering in arrays/modals.
plugins/@sanity/personalization-plugin/src/types.ts Renames experiment config type and adds new personalization config/context/input prop types.
plugins/@sanity/personalization-plugin/src/index.ts Exports fieldLevelPersonalization from the package entrypoint.
plugins/@sanity/personalization-plugin/src/index.test.ts Updates package-exports snapshot to include fieldLevelPersonalization.
plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx Implements the new field-level personalization plugin (schema + form provider wiring).
plugins/@sanity/personalization-plugin/src/fieldExperiments.tsx Refactors to use reorganized experiment components and adds field groups support.
plugins/@sanity/personalization-plugin/src/components/Select.tsx Updates SelectOption import to the new experiment component location.
plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx Adds segment item preview rendering (including reference/image handling).
plugins/@sanity/personalization-plugin/src/components/personalization/SegmentInput.tsx Adds “Copy default” functionality for segment values.
plugins/@sanity/personalization-plugin/src/components/personalization/index.ts Adds personalization components barrel exports.
plugins/@sanity/personalization-plugin/src/components/personalization/Field.tsx Adds field action to toggle/remove personalization and clears nested groups.
plugins/@sanity/personalization-plugin/src/components/personalization/Context.tsx Adds personalization provider/context that resolves segments (sync/async).
plugins/@sanity/personalization-plugin/src/components/personalization/Array.tsx Adds segment “Add …” buttons to append segment entries into the array.
plugins/@sanity/personalization-plugin/src/components/experiment/VariantPreview.tsx Updates imports after experiment component reorganization.
plugins/@sanity/personalization-plugin/src/components/experiment/VariantInput.tsx Adds “Copy default” functionality for variant values.
plugins/@sanity/personalization-plugin/src/components/experiment/Input.tsx Renames experiment input component and updates imports after reorg.
plugins/@sanity/personalization-plugin/src/components/experiment/index.ts Adds experiment components barrel exports.
plugins/@sanity/personalization-plugin/src/components/experiment/Field.tsx Renames/updates field component and clears nested groups before rendering.
plugins/@sanity/personalization-plugin/src/components/experiment/Context.tsx Updates imports/types for the renamed experiment config type.
plugins/@sanity/personalization-plugin/src/components/experiment/Array.tsx Updates imports after experiment component reorganization.
plugins/@sanity/personalization-plugin/src/components/ArrayItem.tsx Renames shared array item component (ExperimentItemArrayItem).
dev/test-studio/src/personalization/index.tsx Updates test studio example to include fieldLevelPersonalization with sample segments.
.changeset/field-level-personalization.md Adds a minor changeset with author credit and summary of the new export + type rename.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx
@stipsan
stipsan requested review from a team and pedrobonamin and removed request for a team and annez July 30, 2026 10:31
cursoragent and others added 2 commits July 30, 2026 10:32
…el-personalization-620c

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Align the new personalization Field with the @sanity/icons v4
convention already applied on main for the experiment Field.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

✅ E2E Tests

🟢 24 passedview full reportview run

Studio: https://plugins-e2e-test-studio-9btrfm1o2.sanity.dev

Datasets: pr-1226-chromium-30809351975, pr-1226-firefox-30809351975

- Guard nested children before clearing field groups
- Fix preview title operator precedence
- Use configured apiVersion in SegmentPreview/VariantPreview
- Avoid "undefined" segment titles; null-safe preview path traversal
- Keep a deprecated FieldPluginConfig type alias for compatibility

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (7)

plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx:19

  • useClient is hard-coded to apiVersion 2025-01-01, which ignores the plugin's configurable apiVersion (available in PersonalizationContext). This makes the preview behavior diverge from the rest of the plugin and from user expectations when they set apiVersion.
  const {segments, apiVersion} = usePersonalizationContext()
  const client = useClient({apiVersion})

plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx:46

  • The reduce((acc, index) => acc[index], doc) will throw if any intermediate path segment is missing (because acc becomes undefined and the next iteration reads acc[index]). Use optional chaining in the reducer so missing paths resolve to undefined instead of crashing the preview.
        Object.keys(previewFields).forEach((key) => {
          const valueKey = referenceType?.preview?.select?.[key]
          selectFields[key] =
            valueKey && doc
              ? valueKey.split('.').reduce((acc: any, index) => {

plugins/@sanity/personalization-plugin/src/utils/clearChildGroups.ts:24

  • children.props.children is spread without being validated. If children.props.children is undefined (or not an element-like object), ...children.props.children will throw at runtime. Add a guard for the nested child before spreading and reuse a nestedChild variable when building the updated props tree.
    !('props' in innerChild)
  ) {
    return props
  }

plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx:29

  • setTitle(${selectedSegment?.label}) will set the literal string "undefined" when no segment is selected. Assign the label directly so the state remains undefined in that case.
      setTitle(selectedSegment?.label)

plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx:246

  • PersonalizationProvider is called like a regular function. Since it uses React hooks, this violates the Rules of Hooks and can break if the hasPersonalization condition ever changes between renders. Render it as a component instead.
              personalizationFieldPluginConfig: {

plugins/@sanity/personalization-plugin/src/types.ts:24

  • The exported FieldPluginConfig type was renamed/removed. Removing an exported type is a breaking change for TypeScript consumers; adding a deprecated alias keeps existing users compiling while still introducing the clearer ExperimentFieldPluginConfig name (and better matches the minor bump in the changeset).
export type ExperimentFieldPluginConfig = {
  fields: (string | FieldDefinition)[]
  experiments: ExperimentType[] | ((client: SanityClient) => Promise<ExperimentType[]>)
  apiVersion?: string
  experimentNameOverride?: string

plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx:214

  • Plugin name contains a misspelling (personalistaion). This can make plugin identification/debugging inconsistent (and differs from the package name).

This issue also appears on line 246 of the same file.

      personalizationNameOverride,

Copilot AI review requested due to automatic review settings July 30, 2026 10:42
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 30.46% 5001 / 16413
🔵 Statements 30.18% 5258 / 17418
🔵 Functions 26.11% 1274 / 4879
🔵 Branches 21.58% 2485 / 11510
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
plugins/@sanity/personalization-plugin/src/fieldExperiments.tsx 9.3% 0% 0% 9.3% 38-160, 177-219, 230-242, 248-297
plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx 10.25% 0% 0% 10.25% 35-131, 146-180, 190-199, 206-252
plugins/@sanity/personalization-plugin/src/index.ts 0% 0% 0% 0%
plugins/@sanity/personalization-plugin/src/types.ts 0% 0% 0% 0%
plugins/@sanity/personalization-plugin/src/components/Select.tsx 20% 0% 0% 20% 22-37
plugins/@sanity/personalization-plugin/src/components/experiment/index.ts 0% 0% 0% 0%
plugins/@sanity/personalization-plugin/src/components/personalization/Array.tsx 7.69% 0% 0% 8.33% 9-51
plugins/@sanity/personalization-plugin/src/components/personalization/Context.tsx 15.38% 0% 0% 15.38% 26-61
plugins/@sanity/personalization-plugin/src/components/personalization/Field.tsx 12.9% 0% 0% 12.9% 23-35, 46-59, 73-96, 105-133
plugins/@sanity/personalization-plugin/src/components/personalization/SegmentInput.tsx 14.28% 100% 0% 14.28% 5-15
plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx 2.7% 0% 0% 2.85% 15-69
plugins/@sanity/personalization-plugin/src/components/personalization/index.ts 0% 0% 0% 0%
plugins/@sanity/personalization-plugin/src/utils/clearChildGroups.ts 12.5% 0% 0% 12.5% 10-41
Generated in workflow #8452 for commit 2fc0185 by the Vitest Coverage Report Action

@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 e4ba4de. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

plugins/@sanity/personalization-plugin/src/components/experiment/Input.tsx:14

  • Importing ExperimentType from ../.. couples this component to the package barrel (src/index.ts) and risks circular type dependencies as exports evolve. Import the type directly from src/types instead.
    plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx:220
  • Plugin name string contains a typo ("personalistaion"), which makes the plugin identifier harder to search for and inconsistent with the package name.
      name: 'sanity-personalistaion-plugin-field-level-personalization',

plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx:210

  • segmentId / segmentArrayName are always derived from segmentNameOverride, so any explicit segmentId/segmentArrayName passed in the plugin config is ignored. If these fields are part of the public config type, they should be respected when provided (and only derived as a default).
    const segmentArrayName = `${segmentNameOverride}s`
    const segmentId = `${segmentNameOverride}Id`

Comment thread plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx Outdated
…el-personalization-620c

Resolve conflicts with #1208 review fixes: keep ArrayItem rename while
moving activation into useEffect, keep explicit preview title typing,
and use optional-chaining path traversal. Also fix the personalization
plugin name spelling to match main.
Disambiguate suspend-react cache keys so experiments and personalization
async resolvers do not collide when both plugins are active. Render
ExperimentProvider and PersonalizationProvider as components so hooks
run under a proper React boundary.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (5)

plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx:252

  • PersonalizationProvider uses hooks, but it’s being invoked as a plain function (PersonalizationProvider(providerProps)). This makes its hooks run inside the parent input component and only when hasPersonalization is true, which can violate the Rules of Hooks and break if the condition ever changes. Render it as a component instead.
            return <PersonalizationProvider {...providerProps} />

plugins/@sanity/personalization-plugin/src/components/experiment/VariantPreview.tsx:40

  • VariantPreview doesn’t clear media/subtitle when value changes between types (e.g. reference → string/image). This can leave stale preview UI from the previous value. Clear state up front in the effect before branching on value.
    plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx:32
  • SegmentPreview doesn’t clear media/subtitle when value changes between types (e.g. reference → string/image). This can leave stale preview UI from the previous value. Clear state up front in the effect before branching on value.
      setTitle(selectedSegment?.label)
      if (typeof value === 'string') {
        return setSubtitle(value)
      }

plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx:60

  • The effect dependencies don’t include segment. If the selected segment changes but the label stays the same (or selectedSegment resolves to another item with the same label), the effect won’t rerun and the preview can become stale.
  }, [value, client, selectedSegment?.label, props.schemaType])

plugins/@sanity/personalization-plugin/src/components/personalization/SegmentInput.tsx:9

  • useFormValue can return undefined when the default field is empty/unset. Passing undefined into set(...) produces an invalid JSON patch (and can crash when patches are serialized). Guard this case and unset(['value']) (or no-op) instead.
  const handleClick = () => {
    props.onChange(set(defaultValue, ['value']))
  }

Copilot AI review requested due to automatic review settings August 3, 2026 11:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (6)

plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx:38

  • This reference preview path can throw when the value field isn’t actually a reference type (or the schema shape differs): referenceField can be undefined, but referenceField.to.find(...) is called unconditionally.
        const type = props.schemaType as ObjectSchemaType
        const valueField = type.fields.find((field) => field.name === 'value') as ObjectSchemaType
        const referenceField = valueField?.type as ReferenceSchemaType
        const referenceType = referenceField.to.find((field) => field.type?.name === doc?._type)

plugins/@sanity/personalization-plugin/src/components/experiment/VariantPreview.tsx:37

  • VariantPreview stores media/subtitle in state, but doesn’t clear them when value changes between types (e.g. previously an image, now a string). This can leave stale media or subtitle from a previous value showing in the preview.

This issue also appears on line 53 of the same file.
plugins/@sanity/personalization-plugin/src/components/experiment/VariantPreview.tsx:55

  • This branch can still throw before the null-safe reduce: referenceField can be undefined depending on the schema shape, but referenceField.to.find(...) is called unconditionally. This would crash the document form preview for non-reference value fields.
    plugins/@sanity/personalization-plugin/src/fieldPersonalization.tsx:210
  • fieldLevelPersonalization computes segmentArrayName/segmentId from segmentNameOverride, but PersonalizationFieldPluginConfig also exposes segmentArrayName and segmentId overrides. As written, user-provided values are silently ignored, which is surprising for API consumers and makes those config fields ineffective.
    const segmentArrayName = `${segmentNameOverride}s`
    const segmentId = `${segmentNameOverride}Id`

plugins/@sanity/personalization-plugin/src/fieldExperiments.tsx:250

  • ExperimentFieldPluginConfig exposes experimentId / variantId / variantArrayName overrides, but fieldLevelExperiments always derives these from the *NameOverride values. This makes the override fields ineffective and prevents consumers from aligning with existing schema field names.
  const pluginConfig = {...CONFIG_DEFAULT, ...config}
  const {fields, experimentNameOverride, variantNameOverride} = pluginConfig

plugins/@sanity/personalization-plugin/src/components/personalization/SegmentPreview.tsx:32

  • SegmentPreview keeps media/subtitle in state, but doesn’t clear them when value changes between types (e.g. image → string, reference → image). This can leave stale media or subtitle from a previous value showing in the preview.

This issue also appears on line 35 of the same file.

    const getSubtitle = async () => {
      setTitle(selectedSegment?.label)
      if (typeof value === 'string') {
        return setSubtitle(value)
      }

@stipsan
stipsan merged commit 1143bd6 into main Aug 3, 2026
26 checks passed
@stipsan
stipsan deleted the cursor/port-field-level-personalization-620c branch August 3, 2026 11:51
@squiggler-app squiggler-app Bot mentioned this pull request Aug 3, 2026
stipsan pushed a commit that referenced this pull request Aug 3, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @sanity/sanity-plugin-async-list@3.0.0

### Major Changes

- [#1087](#1087)
[`f4e7fcc`](f4e7fcc)
Thanks [@stipsan](https://github.com/stipsan)! - Refactor the input into
a real React component and fix the `async-list-undefined` namespace/id

- **Breaking:** `AsyncList` is now a regular React component that takes
a single `props` argument (the standard Sanity input props plus an
`options` field), instead of being called as `AsyncList(props,
options)`. For the `components.input` slot, use the new
`createAsyncListInput(options)` factory: `input:
createAsyncListInput({loader})`. This makes the input safe under the
Rules of Hooks and lets the React Compiler optimize it.
- **Breaking:** the secrets namespace and DOM `id` are no longer derived
as `async-list-${schemaType}` (which became the literal
`async-list-undefined` for component usage). The DOM `id` now uses
Sanity's stable per-field id, and the secrets namespace falls back to
`async-list` (instead of `async-list-undefined`) when no
`schemaType`/`secrets.namespace` is available. When using the component
with `secrets`, set an explicit `secrets.namespace`; a dev warning is
logged if it is missing.
- Fix: the debounced search handler is stable and is cancelled on
unmount, so it no longer drops queued calls or updates state on an
unmounted tree.
- Fix: the value-change handler no longer depends on the whole `props`
object, avoiding unnecessary `Autocomplete` re-renders.
- Fix: loader results are validated to ensure each option's `value` is a
string.

### Patch Changes

- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## @sanity/personalization-plugin@3.1.0

### Minor Changes

- [#1226](#1226)
[`1143bd6`](1143bd6)
Thanks [@jjburbridge](https://github.com/jjburbridge)! - Add
`fieldLevelPersonalization` export for segment-based field
personalization. Segments can be defined statically, fetched from an
external API, or stored in your Sanity dataset. `FieldPluginConfig` is
renamed to `ExperimentFieldPluginConfig` (a deprecated type alias is
kept for compatibility).

### Patch Changes

- [#1208](#1208)
[`51317b2`](51317b2)
Thanks [@stipsan](https://github.com/stipsan)! - Address review feedback
from the migration into the monorepo:

- Fix LaunchDarkly experiment pagination so `offset` advances between
pages, preventing duplicate results and a possible infinite loop
- Correct misspelled plugin names (`personalistaion` →
`personalization`) and stop the LaunchDarkly entry point from
identifying itself as the GrowthBook plugin
- Fix an operator-precedence bug in the experiment field preview that
could set the title to the entire field object
- Walk reference preview paths with optional chaining to avoid throwing
on missing intermediate fields
- Move the experiment item activation patch out of render and into an
effect
  - Remove unused `Select` props
- Fix the GrowthBook (`project` config key) and LaunchDarkly
(`fieldLevelExperiments` usage) docs and drop the stale standalone-repo
Studio version / tooling references

- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## sanity-plugin-cloudinary@2.1.0

### Minor Changes

- [#1233](#1233)
[`36f3fe2`](36f3fe2)
Thanks [@ChrisLaRocque](https://github.com/ChrisLaRocque)! - Add
performant preview images

Asset previews and diffs now build an optimized, scaled-down preview URL
with `@cloudinary/url-gen` (a 400px-wide transformation) when a cloud
name is configured, instead of loading the full-size original. This
keeps the Studio fast when previewing large Cloudinary assets, and falls
back to the stored asset URL when no cloud name is available.

- [#1227](#1227)
[`3b02635`](3b02635)
Thanks [@pgurley](https://github.com/pgurley),
[@atlvis](https://github.com/atlvis)! - Add `cloudinaryReferencePlugin`
for storing Cloudinary assets as reusable document references

- New `cloudinaryReferencePlugin` registers the schema types needed to
reference Cloudinary assets as documents
- New `cloudinaryAssetDocument` type stores a Cloudinary asset as a
standalone document
- New `cloudinaryAssetReference` type references those asset documents,
with a custom input for selecting and managing assets through the
Cloudinary Media Library
- `openMediaSelector` now supports a `showHandler` callback and a
`folder` option, so the select button can show a loading state and scope
the library to a folder
- Fixed the internal name of `cloudinaryAssetSourcePlugin`
(`cloudinart-asset-source` → `cloudinary-asset-source`)

### Patch Changes

- [#1236](#1236)
[`7ec89b6`](7ec89b6)
Thanks [@stipsan](https://github.com/stipsan)! - - Fix the
`cloudinaryAssetSourcePlugin` name, which was mistakenly registered as
`cloudinart-asset-source`
- Wait for the Cloudinary Media Library script to finish loading before
opening it, avoiding runtime errors when several inputs mount at once
- Fix a user-facing typo in the asset source loading message ("Media
Libary" → "Media Library")
- Remove an invalid `src`-less `<track>` element from the video preview
- Correct the README usage examples (`defineConfg` → `defineConfig`) and
drop stale standalone-repo "Develop & test" / "Release new version"
instructions
- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## sanity-plugin-media@6.1.0

### Minor Changes

- [#1119](#1119)
[`ab39674`](ab39674)
Thanks [@lud-hu](https://github.com/lud-hu)! - Add an "Edit Media" asset
source so media details (alt text, title, tags, etc.) can be edited
directly from an image or file field on a document, without opening the
full Media tool.

- [#1117](#1117)
[`6f4316d`](6f4316d)
Thanks [@nickeforsberg](https://github.com/nickeforsberg)! - Add an
`excludeTags` option that hides assets referencing the listed
`media.tag` slugs (`name.current` values). Excluded assets are omitted
from the Media browser grid and asset-picker queries, and the matching
tags are hidden from the tag sidebar and tag search facet. The asset
edit dialog still lists all tags so you can assign or remove them on an
open asset.

- [#1121](#1121)
[`30fa7e2`](30fa7e2)
Thanks [@bobbygeo](https://github.com/bobbygeo),
[@pedrobonamin](https://github.com/pedrobonamin)! - Add folder
management to the media browser

- Organise assets into nested folders, backed by a new `media.folder`
document type (a `name` plus a weak `parent` reference) and a single
weak `opt.media.folder` reference on each asset (mirroring how tags
work). Renaming a folder is a one-field document write regardless of how
many assets it contains.
- Browse folders in a dedicated sidebar tree, with breadcrumb
navigation, create / rename / delete flows, a bulk "Move to folder"
dialog, and per-asset folder controls in the asset details dialog. The
default "All assets" view lists every asset; opening a folder filters
the list to that folder.
- Deleting a folder removes only the folder document — its assets stay
in the library with their folder assignment cleared, and nested folders
move up one level.
- Support multi-select insert when the media plugin is used as an asset
source for array (multiple) fields.

- [#1115](#1115)
[`e7ec6e6`](e7ec6e6)
Thanks [@am0wa](https://github.com/am0wa)! - Extract `image`
(`sanity.imageExifTags`) metadata on asset upload and include it in
asset queries. The asset edit dialog now pre-fills the Description field
from the image's `ImageDescription` EXIF tag as a fallback for image
assets.

- [#1118](#1118)
[`186e29c`](186e29c)
Thanks [@Polleke007](https://github.com/Polleke007),
[@joepvandepol](https://github.com/joepvandepol)! - Add the ability to
replace a selected asset with another one and update all of its
references across documents

When a single asset is selected, a new **Replace** action opens an
overview where you can pick a replacement asset. Every document that
references the original asset (including deeply nested image fields) is
re-pointed to the chosen asset. Ported from
sanity-io/sanity-plugin-media#236.

### Patch Changes

- [#1108](#1108)
[`1dc3a7b`](1dc3a7b)
Thanks [@oxygensmith](https://github.com/oxygensmith)! - Add
`crossOrigin="anonymous"` to the image thumbnail component so thumbnails
load in Firefox. Firefox's Opaque Response Blocking would otherwise
block Sanity CDN asset responses (which send `Vary: Origin`) when
requested without an `Origin` header.
## @sanity/plugin-kit@10.0.3

### Patch Changes

- [#1698](#1698)
[`d3dedd3`](d3dedd3)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency oxlint to ^1.76.0

- [#1779](#1779)
[`5555795`](5555795)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency oxfmt to ^0.61.0

- [#1799](#1799)
[`93858fc`](93858fc)
Thanks [@stipsan](https://github.com/stipsan)! - Prefer function
components via `react/prefer-function-component` (with
`allowErrorBoundary`) instead of banning `Component`/`PureComponent`
imports
## @sanity/cross-dataset-duplicator@2.0.13

### Patch Changes

- [#1228](#1228)
[`71d77d5`](71d77d5)
Thanks [@rohanvachheta](https://github.com/rohanvachheta)! - Handle
reference errors during duplication by fetching missing referenced
documents (including transitive refs, respecting `filter`), re-uploading
any recovered assets, and retrying the transaction, with a one-by-one
commit fallback

- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## @sanity/embeddings-index-ui@4.0.13

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## @sanity/form-toolkit@3.0.13

### Patch Changes

- [#1234](#1234)
[`1dd36c8`](1dd36c8)
Thanks [@stipsan](https://github.com/stipsan)! - Address post-migration
review feedback:

- `FormRenderer` now renders interactive (uncontrolled) inputs when no
`getFieldState` is provided, so the documented native HTML form usage
works out of the box
- Uncontrolled text and textarea fields honor `options.defaultValue`
(controlled fields keep form-library state only, so UI and submit values
stay in sync)
- `FormRenderer` falls back to `field.name` for the React key when a
field has no `_key`
- The shared HubSpot/Mailchimp request handler defaults to the
Next.js-compatible handler instead of throwing when no framework
environment variable is detected, and short-circuits CORS preflight
(`OPTIONS`) requests
- Corrected the `formSchema`, `formiumInput`, and `mailchimpInput` usage
examples (import paths and required options), README typos, and
example/dev-workflow references

- Updated dependencies
[[`f4e7fcc`](f4e7fcc)]:
  - @sanity/sanity-plugin-async-list@3.0.0
## @sanity/language-filter@5.0.14

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## @sanity/studio-secrets@4.0.15

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## sanity-plugin-hotspot-array@5.0.12

### Patch Changes

- [#1789](#1789)
[`9628df4`](9628df4)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency motion to ^12.43.0
## sanity-plugin-iframe-pane@5.0.30

### Patch Changes

- [#1789](#1789)
[`9628df4`](9628df4)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency motion to ^12.43.0
## sanity-plugin-internationalized-array@5.1.25

### Patch Changes

- [#1765](#1765)
[`97937c4`](97937c4)
Thanks [@pedrobonamin](https://github.com/pedrobonamin)! - Add a stable
`data-testid` on the document-level add-translations panel for e2e
coverage
## sanity-plugin-mux-input@5.0.8

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## sanity-plugin-studio-smartling@5.0.14

### Patch Changes

- [#1216](#1216)
[`d4dd45b`](d4dd45b)
Thanks [@stipsan](https://github.com/stipsan)! - Harden the Smartling
adapter and fix docs:

- Send the secret credentials JSON verbatim during authentication
instead of double-encoding it, which could break auth with proxies that
forward the request body as-is
- Throw a clear error (surfacing Smartling's message) when
authentication does not return an access token, instead of a cryptic
`TypeError`
- Avoid throwing when no existing job is found or when a
translation/progress response is missing its expected payload
- Guard the progress calculation against a zero total word count so
empty documents no longer report `NaN`/`Infinity`
- Fix the `additionalDeserializers` option name in the advanced
configuration docs (was `additonalDeserializers`) and update the README
development/release instructions for the monorepo
## sanity-plugin-workflow@3.0.39

### Patch Changes

- [#1789](#1789)
[`9628df4`](9628df4)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency motion to ^12.43.0
## @sanity/document-internationalization@6.2.28


## @sanity/sfcc@1.0.25

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a
summary for commit 9246b02. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants