Skip to content

feat(media): extract image EXIF metadata (sanity.imageExifTags) on asset upload - #1115

Merged
stipsan merged 11 commits into
mainfrom
cursor/port-media-image-exif-metadata-9d4d
Aug 3, 2026
Merged

feat(media): extract image EXIF metadata (sanity.imageExifTags) on asset upload#1115
stipsan merged 11 commits into
mainfrom
cursor/port-media-image-exif-metadata-9d4d

Conversation

@stipsan

@stipsan stipsan commented Jun 17, 2026

Copy link
Copy Markdown
Member

Ports sanity-io/sanity-plugin-media#293 by @am0wa into the monorepo.

Problem

metadata.image (sanity.imageExifTags) is never extracted because 'image' is missing from the upload extract array, and the GROQ projection also omits it — so even assets that already have the data won't return it.

Changes

  • Add 'image' to the asset upload extract list (src/utils/uploadSanityAsset.ts).
  • Add image to the asset GROQ projection so the data is returned for existing assets (src/modules/assets/index.ts).
  • Auto-populate image.ImageDescription into the DialogAssetEdit Description field as a fallback, for image assets only (src/components/DialogAssetEdit/index.tsx).

Review follow-ups

Addressed Copilot review comments:

  • Validate ImageDescription with a runtime typeof === 'string' check (no unsafe cast).
  • Preserve intentionally empty localized description strings (locale.id in field) so EXIF does not refill cleared values.
  • Let react-hook-form own the Description field default (removed asset-derived defaultValue override).
  • Added DialogAssetEdit tests for EXIF prefill, non-override of existing description, and empty localized preservation.

Notes

  • Commits are authored by the original contributor (@am0wa) and the changeset credits them via an author: directive.
  • The change is non-breaking, so the changeset is a minor bump.
Open in Web Open in Cursor 

@changeset-bot

changeset-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5c66deb

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

This PR includes changesets to release 1 package
Name Type
sanity-plugin-media 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

@vercel

vercel Bot commented Jun 17, 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 Jul 30, 2026 8:19pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plugins-e2e-test-studio Ignored Ignored Jul 30, 2026 8:19pm

Request Review

@stipsan
stipsan marked this pull request as ready for review June 17, 2026 15:46
@stipsan
stipsan requested a review from a team as a code owner June 17, 2026 15:46
@stipsan
stipsan requested review from bjoerge and Copilot and removed request for a team June 17, 2026 15:46

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

This PR ports the Media plugin enhancement to extract and surface sanity.imageExifTags (metadata.image) on image uploads, and uses EXIF ImageDescription as a fallback to prefill the asset Description field in the asset edit dialog.

Changes:

  • Requests image metadata extraction during asset uploads.
  • Extends the assets GROQ projection to include metadata.image.
  • Adds an EXIF-based fallback for initializing the Description field in DialogAssetEdit.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
plugins/sanity-plugin-media/src/utils/uploadSanityAsset.ts Adds image to the upload extract list so EXIF tag metadata is generated on upload.
plugins/sanity-plugin-media/src/modules/assets/index.ts Updates GROQ projection to return metadata.image for existing assets.
plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx Prefills Description from metadata.image.ImageDescription when no description exists.
.changeset/media-image-exif-metadata.md Adds a minor changeset documenting the new extraction + UI behavior.

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

Comment thread plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx Outdated
bjoerge
bjoerge previously approved these changes Jun 19, 2026
@am0wa

am0wa commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@stipsan thanks for port 🙌
Unfortunately I have no write permission to rebase onto latest main and fixup auto-review comments.

So guys lets decide: whether you fix it up (its non braking change) or I'd need to create a fresh fork and PR...

cursoragent and others added 2 commits July 30, 2026 13:24
…ge-exif-metadata-9d4d

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Validate ImageDescription with typeof before use, preserve empty localized
strings instead of falling through to EXIF, let RHF own the description
field default, and cover the behavior with DialogAssetEdit tests.
@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-kzkwru5q2.sanity.dev

Datasets: pr-1115-chromium-30578570363, pr-1115-firefox-30578570363

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 31.4% 5009 / 15948
🔵 Statements 31.11% 5271 / 16941
🔵 Functions 26.8% 1281 / 4779
🔵 Branches 22.56% 2526 / 11192
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
plugins/sanity-plugin-media/src/components/DialogAssetEdit/Details.tsx 69.23% 75% 83.33% 81.81% 14, 16-17
plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx 68.84% 57.93% 64.7% 71.66% 32, 91, 116-117, 156, 168-171, 178-183, 190, 200, 209-237, 244, 266-270, 283, 299-301, 308-313, 369, 415
plugins/sanity-plugin-media/src/modules/assets/index.ts 81.48% 42.62% 78.62% 81.22% 117-119, 135-137, 150, 169, 176, 321, 334-335, 357-366, 389, 410, 455-481, 490, 499-504, 509, 516-520, 525, 531-534, 552-555, 600, 630-639, 671-680, 734-742, 770
plugins/sanity-plugin-media/src/utils/uploadSanityAsset.ts 26.66% 22.22% 6.66% 26.66% 12-15, 19-30, 36-37, 48-49, 60-90
Generated in workflow #8393 for commit 5c66deb by the Vitest Coverage Report Action

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 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx:112

  • When locales are disabled but the stored field value is a localized object (e.g. {en: '', fr: ''}), flattenField() now returns the EXIF fallback because it searches for the first truthy value. That re-fills intentionally cleared localized values in the locales-disabled path, contradicting the goal of preserving empty strings.

Consider only applying the EXIF fallback when the localized object has no keys (i.e. truly missing), and preserve '' when keys exist but are empty.

      const flattenField = (field: unknown, fallback = ''): string => {
        if (typeof field === 'string') return field
        if (typeof field === 'object' && field !== null) {
          const values = Object.values(field as Record<string, string>)
          return values.find((v) => v) || fallback

sanitizeFormData maps empty strings to null, which made a cleared
Description look missing on reopen and re-apply ImageDescription.
Preserve '' on save and cover the non-localized clear path with tests.

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 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx:88

  • locale.id in field will also match inherited/prototype keys (e.g. toString, __proto__), which can cause non-string values to be assigned into the localized form object. Using an own-property check plus a typeof === 'string' guard keeps the “preserve empty string” behavior without risking prototype leakage.
            // Prefer key presence over truthiness so an intentional empty string
            // (e.g. `{en: ''}`) is preserved and does not fall through to EXIF.
            if (typeof field === 'object' && field && locale.id in field) {
              obj[locale.id] = field[locale.id] ?? ''
            } else if (typeof field === 'string') {

plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx:114

  • In the flattenField locale-normalization path, applying the EXIF fallback when field is an object can unintentionally refill a description that was explicitly localized-but-empty (e.g. {en: '', fr: ''}) when locales are later disabled. Treat a non-empty object as “explicit value present” and only use the fallback when the object has no values at all.
        if (typeof field === 'object' && field !== null) {
          const values = Object.values(field as Record<string, string>)
          return values.find((v) => v) || fallback
        }
        return fallback

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

When a localized description object already exists, leave absent locale
keys empty instead of applying ImageDescription to the first locale.

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 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx:117

  • flattenField will apply the EXIF fallback when asset.description is a localized object whose keys exist but all values are empty/null (e.g. after locales were previously enabled and descriptions were cleared). In that case, the presence of the object should still be treated as an intentional “no description”, otherwise EXIF can refill cleared values when locales are later disabled.
      const flattenField = (field: unknown, fallback = ''): string => {
        if (typeof field === 'string') return field
        if (typeof field === 'object' && field !== null) {
          const values = Object.values(field as Record<string, string>)
          return values.find((v) => v) || fallback

@stipsan
stipsan enabled auto-merge (squash) July 30, 2026 20:17

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 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx:75

  • imageDescription is typed as string | undefined but is passed as the fallback argument to helpers that expect a string. When imageDescription is undefined (most common case), this can lead to undefined being written into the generated default values object (and will also fail type-checking in strict mode).
      let imageDescription: string | undefined
      if (asset && isImageAsset(asset)) {
        const raw = asset.metadata?.image?.['ImageDescription']
        if (typeof raw === 'string') {
          imageDescription = raw

plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx:117

  • When locales are not configured, flattenField() will apply the EXIF fallback if a localized object exists but all its values are empty/null. This can cause an intentionally-cleared localized description (e.g. {en: ''} or {en: null} from sanitizeFormData) to be refilled from EXIF when locales are later disabled.
      const flattenField = (field: unknown, fallback = ''): string => {
        if (typeof field === 'string') return field
        if (typeof field === 'object' && field !== null) {
          const values = Object.values(field as Record<string, string>)
          return values.find((v) => v) || fallback

@stipsan
stipsan disabled auto-merge August 3, 2026 12:02
@stipsan
stipsan merged commit e7ec6e6 into main Aug 3, 2026
26 checks passed
@stipsan
stipsan deleted the cursor/port-media-image-exif-metadata-9d4d branch August 3, 2026 12:02
@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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants