From 8cbed247e265b8f539b8ba7047af46b3640b9718 Mon Sep 17 00:00:00 2001
From: "release-bot-allow-prs-and-push[bot]"
<173871997+release-bot-allow-prs-and-push[bot]@users.noreply.github.com>
Date: Tue, 12 May 2026 10:35:28 +1000
Subject: [PATCH] Version Packages (#6791)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
## @tinacms/astro@0.2.0
### Minor Changes
- [#6771](https://github.com/tinacms/tinacms/pull/6771)
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb)
Thanks [@wicksipedia](https://github.com/wicksipedia)! - ✨ **New
package: `@tinacms/astro`** — the one-stop integration for using TinaCMS
with Astro.
```bash
pnpm add @tinacms/astro
```
Bundles the rich-text renderer and re-exports the framework-agnostic
bridge under one install. `@tinacms/bridge` stays publishable on its own
for non-Astro frontends (coming soon); Astro projects only need
`@tinacms/astro`.
**What's exported**
| Subpath | What it gives you |
| ----------------------------------- |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| `@tinacms/astro` | `requestWithMetadata`, `tinaField`, `QueryResult`,
and the rich-text types |
| `@tinacms/astro/TinaMarkdown.astro` | `` — the rich-text renderer (import via subpath so Astro's
check sees a real `.astro` component) |
| `@tinacms/astro/integration` | `tina()` integration — auto-wires the
middleware and bridge route so `requestWithMetadata()` works without
threading `Astro.request` or writing wiring components |
| `@tinacms/astro/TinaIsland.astro` | `` — marker wrapper for an editable region |
| `@tinacms/astro/types` | `TinaRichTextContent`, `CustomComponentsMap`,
`TinaRichTextNode`, `MdxElement`, `TextElement`, etc. |
| `@tinacms/astro/sanitize` | `sanitizeHref` / `sanitizeImageSrc` for
CMS-supplied URLs |
| `@tinacms/astro/bridge` | `init`, `refreshForms`, and the rest of
`@tinacms/bridge` |
| `@tinacms/astro/tina-field` | `tinaField()` helper for
`data-tina-field` markers |
| `@tinacms/astro/is-edit-mode` | `isEditMode(request)` — server-side
admin-iframe detection |
| `@tinacms/astro/experimental` | `experimental_createIslandRoute()` —
opt-in helper for the dynamic `/tina-island/[name]` endpoint |
**Usage**
```astro
---
import TinaMarkdown from '@tinacms/astro/TinaMarkdown.astro';
import { requestWithMetadata, tinaField } from '@tinacms/astro';
import client from '../tina/__generated__/client';
import { customComponents } from '../components/markdown';
const post = await requestWithMetadata(
client.queries.post({ relativePath: 'hello.md' }),
);
---
```
Add `tina()` from `@tinacms/astro/integration` to your
`astro.config.mjs` and the middleware auto-injects the bridge script +
per-form payloads on edit-mode requests. Production HTML is
byte-identical to a Tina-free Astro app.
The renderer mirrors the React `TinaMarkdown` from
`tinacms/dist/rich-text` — same `content` prop, same `components` map
shape — but emits pure HTML with no React in the page tree. Custom MDX
components register by name (`mdxJsxFlowElement` / `mdxJsxTextElement`);
default tags (`p`, `h1`, `a`, etc.) can be overridden by registering
them on the same map.
**Peer deps**
- `astro >=5.0.0` — uses Astro's container API for islands and ships
`.astro` source files for the consumer's Astro pipeline to compile.
### Patch Changes
- Updated dependencies
\[[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb)]:
- @tinacms/bridge@0.2.0
## @tinacms/bridge@0.2.0
### Minor Changes
- [#6771](https://github.com/tinacms/tinacms/pull/6771)
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb)
Thanks [@wicksipedia](https://github.com/wicksipedia)! - ✨ **Visual
editing for Astro — without React.**
TinaCMS visual editing previously required `useTina()`, a React hook
that subscribes to admin postMessages and re-renders the page tree. That
made it a hard sell for Astro: the framework is built around shipping
zero JS by default, and the existing `examples/astro/kitchen-sink`
worked around the React requirement by hydrating React inside the editor
iframe — exactly the pattern Astro authors avoid.
This release ships a vanilla-JS bridge that brings the same
click-to-focus, live-update, and form-syncing UX to Astro components,
Hugo templates, plain HTML — anything that can emit a `data-tina-form`
payload per query.
**New package: `@tinacms/bridge`**
A ~2 KB gzipped, zero-dependency ESM bundle that speaks the existing
TinaCMS admin postMessage protocol. No React in the page tree, no client
islands, no hydration cost outside the editor iframe.
Astro projects install `@tinacms/astro` instead and the bundled
integration's middleware auto-injects everything on edit-mode responses.
Direct `@tinacms/bridge` consumption is for non-Astro frontends:
```html
```
The bridge submodules:
- **`init()`** — top-level entry. Detects iframe embedding, registers
all `[data-tina-form]` payloads with the admin (with retry, since the
bridge boots faster than the admin's listener), wires data updates and
click-to-focus.
- **`refreshForms()`** — re-scans the DOM after soft navigations (Astro
view transitions, Turbo, htmx). Posts `close` for forms that left and
`open` for forms that appeared.
- **`tinaField()`** — framework-free field-id helper, identical API to
`tinacms/dist/react`'s export. Use on any element to make it
click-to-edit.
- **`@tinacms/bridge/preview`** — server-side helper for non-React
frameworks. `readOverlay(request, queryId)` returns the unsaved form
data the admin is editing, so per-route refresh endpoints can re-render
with overlay data on every keystroke.
**How edits flow without re-rendering React**
The bridge takes a soft-refresh approach instead of in-place
reconciliation. Mark editable regions with
`data-tina-island=""`; on every form change the bridge
POSTs the current overlay to that endpoint, the server renders the
matching component to an HTML fragment, and the bridge swaps it into the
live DOM. Per-island scoped — editing the hero refetches only the hero,
not the whole page. The transport is JSON-over-POST so UTF-8 (em-dashes,
smart quotes, emoji) and large rich-text bodies round-trip without size
or charset limits.
**The protocol stays stateless** — admin pushes already-resolved data to
the bridge, bridge forwards it to the island endpoint, endpoint reads it
via `readOverlay()` instead of hitting the canonical content store.
Works identically against self-hosted Tina, TinaCloud, or any GraphQL
endpoint. No backend changes shipped.
**`tinacms`: framework-free `tinaField` subpath**
`tinaField()` was already pure — just reads `_content_source` metadata.
It's now exported from `tinacms/tina-field` as a standalone module so
non-React frontends can import it without pulling React (and Plate, and
dnd-kit, and ~50 other React deps) into their bundle. The existing
`tinacms/dist/react` re-export keeps the public API stable.
**Reference example: `examples/astro/visual-editing`**
A new Astro 5 example that mirrors `examples/astro/kitchen-sink`
field-for-field — same six collections (Tag, Author, Global, Post, Blog,
Page), same shared content via `localContentPath`, same eight routes —
but rendered with pure Astro components instead of React islands.
Includes:
- The **`@tinacms/astro` package's `TinaMarkdown`** — a vanilla Astro
rich-text renderer that walks the Plate AST Tina returns, dispatches
custom MDX components (NewsletterSignup, BlockQuote, DateTime, code
blocks) by name to authored Astro components — the same `components` map
shape as `TinaMarkdown` from `tinacms/dist/rich-text`, but emitting
Astro markup
- An island-refresh pattern: one dynamic endpoint at
`src/pages/tina-island/[name].ts` backed by a registry in
`src/lib/islands.ts`. The endpoint uses Astro's
`experimental_AstroContainer` to render the matching component as a
fragment-only response. Adding a new editable region is one entry in the
registry
- Multi-form pages: layout fetches global, route fetches its primary
collection, both register independently — admin shows the right form
based on which marked element you click
- A **`requestWithMetadata()`** helper wrapping every data load so the
same code path runs in production (no overlay → real fetch) and inside
the editor (overlay → use the bridge payload). Production builds ship
zero bridge JS to non-admin visitors
**Why this matters for the Astro community**
Astro is the second-most-starred meta-framework on GitHub and grew
specifically because authors care about runtime cost. Every previous
attempt to integrate a React-based CMS into Astro carried the same
caveat: "but you'll need to ship React for editing." That caveat is now
gone. The bridge is the smallest piece of JS that can deliver Tina's
full editing experience — click to focus, live preview as you type,
click-to-edit overlays — to a framework whose audience explicitly didn't
sign up for React.
**Known content-shape note**
For nested MDX components in rich-text bodies (e.g. ``
inside a post's `_body`) to render via the Astro renderer instead of as
raw HTML, the content needs to be authored through the Tina editor —
which inserts them as MDX templates that Tina parses into
`mdxJsxFlowElement` nodes. Hand-authored `` syntax in the
markdown source is currently parsed as `html` by Tina's MDX layer; same
behaviour as the React renderer. Worth flagging up-front for anyone
migrating existing markdown content.
**Soft-navigation support: `refreshForms()`**
`init()` scans `[data-tina-form]` elements once on first load and
captures the resulting set in closure. Sites using Astro's
`` (or any view-transitions setup that swaps the DOM
without a full reload) would post the first page's forms to the admin
and never refresh them — navigating between docs inside the editor
iframe left the sidebar showing the previous page's form.
`refreshForms()` re-scans the live DOM, diffs against the
previously-mounted set, and posts `close` for forms that disappeared and
`open` (with the same retry-until-acked behaviour as `init`) for forms
that appeared. The one-time global listeners — `click` capture, the
`updateData` ack handler, the `beforeunload` close — stay bound across
refreshes, so calling it on every navigation is cheap and idempotent.
The Astro integration wires it to `astro:page-load` automatically.
**Sticky edit-mode**
A `__tina_edit` session cookie (SameSite=Strict, gated on
`Sec-Fetch-Dest: iframe`) keeps the iframe in edit mode across in-iframe
link clicks — without it, clicking a link inside the preview drops the
`/admin/` Referer and the next request falls out of edit mode. Top-level
visitors never get edit mode because the dest check fails before the
cookie is consulted, so production HTML is unaffected.
**Out of scope (follow-ups)**
- Hugo / Eleventy adapters using the same bridge — the contract is
framework-free, just needs an integration guide
- TinaCloud overlay channel — not needed; the stateless POST protocol
works against any backend
## @tinacms/cli@2.3.0
### Minor Changes
- [#6738](https://github.com/tinacms/tinacms/pull/6738)
[`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d)
Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Stop
writing generated files (`_schema.json`, `_graphql.json`,
`_lookup.json`, `tina-lock.json`) to the content repo when
`localContentPath` is set. Generated files now live only in the
generator repo's `tina/__generated__/`. The content repo is no longer
required to contain a `tina/` folder. `FilesystemBridge.get` / `put` /
`delete` now route `tina/__generated__/` and `.tina/__generated__/`
paths to `rootPath` (the generator) instead of `outputPath` (the content
root). Closes
[tinacms/tinacloud#3295](https://github.com/tinacms/tinacloud/issues/3295).
### ⚠️ Rollout gate
**This release must not be promoted to the `@latest` dist-tag until
TinaCloud prod has deployed
[tinacms/tinacloud#3403](https://github.com/tinacms/tinacloud/issues/3403).**
Pre-#3403 TinaCloud reads `tina-lock.json` from the content repo on
generator pushes; shipping this change before the server-side fix breaks
every existing multi-repo user's indexing.
### Migration notes for existing multi-repo projects
After upgrading (and once TinaCloud prod is on #3403):
- **Stale `tina/` folder in your content repo.** Pre-upgrade builds
committed `tina/__generated__/*` and `tina/tina-lock.json` to the
content repo. Nothing updates or reads those files any more. They are
safe — and recommended — to delete from the content repo in a single
cleanup commit.
- **`ConfigManager.generatedFolderPathContentRepo` is removed.** If any
custom CLI code, plugins, or scripts referenced this field, they will
fail at type-check or runtime. Use `generatedFolderPath` — it has always
been the generator-relative path.
- **`ConfigManager.getTinaFolderPath` no longer accepts an
`isContentRoot` option.** The content root never needs a `tina/` folder
now, so the option was removed. If any custom code called
`getTinaFolderPath(path, { isContentRoot: true })`, drop the second
argument.
- **`FilesystemBridge` behavior change for `tina/__generated__/`
paths.** In multi-repo setups, bridge reads/writes of paths under
`tina/__generated__/` or `.tina/__generated__/` now resolve against the
generator (`rootPath`) rather than the content repo (`outputPath`). If
you have custom bridge subclasses or code that relied on these paths
resolving to the content repo, update it.
- **Generated `client.ts` / `database-client.ts` now import `./types`
extensionless** (was `./types.ts`) for TypeScript projects. Avoids
requiring `allowImportingTsExtensions: true` in consumer tsconfigs,
which broke the build under Next.js 15.5+ defaults. JS projects still
import `./types.js` (Node ESM requires the extension).
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e),
[`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
- @tinacms/graphql@2.4.0
- @tinacms/metrics@2.1.0
- @tinacms/app@2.4.7
- @tinacms/search@1.2.14
## @tinacms/graphql@2.4.0
### Minor Changes
- [#6738](https://github.com/tinacms/tinacms/pull/6738)
[`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d)
Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Stop
writing generated files (`_schema.json`, `_graphql.json`,
`_lookup.json`, `tina-lock.json`) to the content repo when
`localContentPath` is set. Generated files now live only in the
generator repo's `tina/__generated__/`. The content repo is no longer
required to contain a `tina/` folder. `FilesystemBridge.get` / `put` /
`delete` now route `tina/__generated__/` and `.tina/__generated__/`
paths to `rootPath` (the generator) instead of `outputPath` (the content
root). Closes
[tinacms/tinacloud#3295](https://github.com/tinacms/tinacloud/issues/3295).
### ⚠️ Rollout gate
**This release must not be promoted to the `@latest` dist-tag until
TinaCloud prod has deployed
[tinacms/tinacloud#3403](https://github.com/tinacms/tinacloud/issues/3403).**
Pre-#3403 TinaCloud reads `tina-lock.json` from the content repo on
generator pushes; shipping this change before the server-side fix breaks
every existing multi-repo user's indexing.
### Migration notes for existing multi-repo projects
After upgrading (and once TinaCloud prod is on #3403):
- **Stale `tina/` folder in your content repo.** Pre-upgrade builds
committed `tina/__generated__/*` and `tina/tina-lock.json` to the
content repo. Nothing updates or reads those files any more. They are
safe — and recommended — to delete from the content repo in a single
cleanup commit.
- **`ConfigManager.generatedFolderPathContentRepo` is removed.** If any
custom CLI code, plugins, or scripts referenced this field, they will
fail at type-check or runtime. Use `generatedFolderPath` — it has always
been the generator-relative path.
- **`ConfigManager.getTinaFolderPath` no longer accepts an
`isContentRoot` option.** The content root never needs a `tina/` folder
now, so the option was removed. If any custom code called
`getTinaFolderPath(path, { isContentRoot: true })`, drop the second
argument.
- **`FilesystemBridge` behavior change for `tina/__generated__/`
paths.** In multi-repo setups, bridge reads/writes of paths under
`tina/__generated__/` or `.tina/__generated__/` now resolve against the
generator (`rootPath`) rather than the content repo (`outputPath`). If
you have custom bridge subclasses or code that relied on these paths
resolving to the content repo, update it.
- **Generated `client.ts` / `database-client.ts` now import `./types`
extensionless** (was `./types.ts`) for TypeScript projects. Avoids
requiring `allowImportingTsExtensions: true` in consumer tsconfigs,
which broke the build under Next.js 15.5+ defaults. JS projects still
import `./types.js` (Node ESM requires the extension).
- [#6765](https://github.com/tinacms/tinacms/pull/6765)
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)
Thanks [@kulesy](https://github.com/kulesy)! - Forward the editor's
current branch to the TinaCloud assets-api on every cloud media call,
and fix staging URL handling for multi-segment branches
`TinaMediaStore` now appends `?branch=` to its
`upload_url`, `list`, and `delete` requests so that — once the
assets-api opts an app into branch-aware media — uploads, listings, and
deletions are scoped to the branch the editor is on, instead of always
hitting the production branch. The branch is read from `Client.branch`
(already URL-encoded) and decoded then re-encoded at the use site to
avoid double-encoding.
The query parameter is ignored by assets-api versions that do not parse
it, so this change is safe to deploy ahead of the server-side rollout.
Local mode is unaffected.
`@tinacms/graphql`'s media URL resolver now formats staging URLs as
`/__staging//__file/` instead of
`/__staging//`. The previous form broke for
branches containing `/` (e.g. `feat/my-branch`) because CloudFront
decodes paths before downstream components see them, so the S3 write key
(with a literal `%2F`) wouldn't match the decoded read path. The
`__file` delimiter lets the branch contribute its natural `/` segments
while still marking where the file path begins.
Note: staging URLs produced by `@tinacms/graphql@2.3.0`–`2.3.1` use the
old format and will not round-trip through this version's
`resolveMediaCloudToRelative`. Branch-aware media is gated server-side
and has not been enabled for any tenant yet, so no persisted data is
expected to be affected — but if you turned it on for testing,
regenerate the affected field values from the editor after upgrading.
After a successful cloud upload `TinaMediaStore.persist()` now resolves
its return value from the assets-api `list` endpoint instead of
constructing each `Media.src` locally — the server is the source of
truth for the canonical URL (including the staging-branch path and
per-stage CDN host). The `MediaStore.persist()` contract is preserved,
so the returned items still flow through the media manager and the
image-field drop handler.
Also reserves an optional `rename?(from, to)` hook on the `MediaStore`
interface as a future extension point — no implementation yet.
### Patch Changes
- [#6828](https://github.com/tinacms/tinacms/pull/6828)
[`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e)
Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Fix
`resolveMediaCloudToRelative` so it strips any TinaCloud cloud URL on
save, not only ones whose host matches `config.assetsHost`. The match
condition is now host-agnostic: the `/…` path prefix is the
durable invariant; the host segment can vary across stages.
This unblocks multi-host setups (PR / stage / personal-dev TinaCloud
stages) where the dashboard's default `MediaStore` inserts upload URLs
with one host while content-api returns a different one as `assetsHost`.
Previously the round-trip silently failed and absolute URLs got
committed to the content repo. After this fix, content saves as a
relative path regardless of which host the dashboard inserted, matching
pre-existing content's format.
Also covers cross-stage content migration: an absolute URL written
against one stage strips correctly when re-saved against another.
Closes [#6827](https://github.com/tinacms/tinacms/issues/6827).
## @tinacms/metrics@2.1.0
### Minor Changes
- [#6738](https://github.com/tinacms/tinacms/pull/6738)
[`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d)
Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Stop
writing generated files (`_schema.json`, `_graphql.json`,
`_lookup.json`, `tina-lock.json`) to the content repo when
`localContentPath` is set. Generated files now live only in the
generator repo's `tina/__generated__/`. The content repo is no longer
required to contain a `tina/` folder. `FilesystemBridge.get` / `put` /
`delete` now route `tina/__generated__/` and `.tina/__generated__/`
paths to `rootPath` (the generator) instead of `outputPath` (the content
root). Closes
[tinacms/tinacloud#3295](https://github.com/tinacms/tinacloud/issues/3295).
### ⚠️ Rollout gate
**This release must not be promoted to the `@latest` dist-tag until
TinaCloud prod has deployed
[tinacms/tinacloud#3403](https://github.com/tinacms/tinacloud/issues/3403).**
Pre-#3403 TinaCloud reads `tina-lock.json` from the content repo on
generator pushes; shipping this change before the server-side fix breaks
every existing multi-repo user's indexing.
### Migration notes for existing multi-repo projects
After upgrading (and once TinaCloud prod is on #3403):
- **Stale `tina/` folder in your content repo.** Pre-upgrade builds
committed `tina/__generated__/*` and `tina/tina-lock.json` to the
content repo. Nothing updates or reads those files any more. They are
safe — and recommended — to delete from the content repo in a single
cleanup commit.
- **`ConfigManager.generatedFolderPathContentRepo` is removed.** If any
custom CLI code, plugins, or scripts referenced this field, they will
fail at type-check or runtime. Use `generatedFolderPath` — it has always
been the generator-relative path.
- **`ConfigManager.getTinaFolderPath` no longer accepts an
`isContentRoot` option.** The content root never needs a `tina/` folder
now, so the option was removed. If any custom code called
`getTinaFolderPath(path, { isContentRoot: true })`, drop the second
argument.
- **`FilesystemBridge` behavior change for `tina/__generated__/`
paths.** In multi-repo setups, bridge reads/writes of paths under
`tina/__generated__/` or `.tina/__generated__/` now resolve against the
generator (`rootPath`) rather than the content repo (`outputPath`). If
you have custom bridge subclasses or code that relied on these paths
resolving to the content repo, update it.
- **Generated `client.ts` / `database-client.ts` now import `./types`
extensionless** (was `./types.ts`) for TypeScript projects. Avoids
requiring `allowImportingTsExtensions: true` in consumer tsconfigs,
which broke the build under Next.js 15.5+ defaults. JS projects still
import `./types.js` (Node ESM requires the extension).
## tinacms@3.8.0
### Minor Changes
- [#6771](https://github.com/tinacms/tinacms/pull/6771)
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb)
Thanks [@wicksipedia](https://github.com/wicksipedia)! - ✨ **Visual
editing for Astro — without React.**
TinaCMS visual editing previously required `useTina()`, a React hook
that subscribes to admin postMessages and re-renders the page tree. That
made it a hard sell for Astro: the framework is built around shipping
zero JS by default, and the existing `examples/astro/kitchen-sink`
worked around the React requirement by hydrating React inside the editor
iframe — exactly the pattern Astro authors avoid.
This release ships a vanilla-JS bridge that brings the same
click-to-focus, live-update, and form-syncing UX to Astro components,
Hugo templates, plain HTML — anything that can emit a `data-tina-form`
payload per query.
**New package: `@tinacms/bridge`**
A ~2 KB gzipped, zero-dependency ESM bundle that speaks the existing
TinaCMS admin postMessage protocol. No React in the page tree, no client
islands, no hydration cost outside the editor iframe.
Astro projects install `@tinacms/astro` instead and the bundled
integration's middleware auto-injects everything on edit-mode responses.
Direct `@tinacms/bridge` consumption is for non-Astro frontends:
```html
```
The bridge submodules:
- **`init()`** — top-level entry. Detects iframe embedding, registers
all `[data-tina-form]` payloads with the admin (with retry, since the
bridge boots faster than the admin's listener), wires data updates and
click-to-focus.
- **`refreshForms()`** — re-scans the DOM after soft navigations (Astro
view transitions, Turbo, htmx). Posts `close` for forms that left and
`open` for forms that appeared.
- **`tinaField()`** — framework-free field-id helper, identical API to
`tinacms/dist/react`'s export. Use on any element to make it
click-to-edit.
- **`@tinacms/bridge/preview`** — server-side helper for non-React
frameworks. `readOverlay(request, queryId)` returns the unsaved form
data the admin is editing, so per-route refresh endpoints can re-render
with overlay data on every keystroke.
**How edits flow without re-rendering React**
The bridge takes a soft-refresh approach instead of in-place
reconciliation. Mark editable regions with
`data-tina-island=""`; on every form change the bridge
POSTs the current overlay to that endpoint, the server renders the
matching component to an HTML fragment, and the bridge swaps it into the
live DOM. Per-island scoped — editing the hero refetches only the hero,
not the whole page. The transport is JSON-over-POST so UTF-8 (em-dashes,
smart quotes, emoji) and large rich-text bodies round-trip without size
or charset limits.
**The protocol stays stateless** — admin pushes already-resolved data to
the bridge, bridge forwards it to the island endpoint, endpoint reads it
via `readOverlay()` instead of hitting the canonical content store.
Works identically against self-hosted Tina, TinaCloud, or any GraphQL
endpoint. No backend changes shipped.
**`tinacms`: framework-free `tinaField` subpath**
`tinaField()` was already pure — just reads `_content_source` metadata.
It's now exported from `tinacms/tina-field` as a standalone module so
non-React frontends can import it without pulling React (and Plate, and
dnd-kit, and ~50 other React deps) into their bundle. The existing
`tinacms/dist/react` re-export keeps the public API stable.
**Reference example: `examples/astro/visual-editing`**
A new Astro 5 example that mirrors `examples/astro/kitchen-sink`
field-for-field — same six collections (Tag, Author, Global, Post, Blog,
Page), same shared content via `localContentPath`, same eight routes —
but rendered with pure Astro components instead of React islands.
Includes:
- The **`@tinacms/astro` package's `TinaMarkdown`** — a vanilla Astro
rich-text renderer that walks the Plate AST Tina returns, dispatches
custom MDX components (NewsletterSignup, BlockQuote, DateTime, code
blocks) by name to authored Astro components — the same `components` map
shape as `TinaMarkdown` from `tinacms/dist/rich-text`, but emitting
Astro markup
- An island-refresh pattern: one dynamic endpoint at
`src/pages/tina-island/[name].ts` backed by a registry in
`src/lib/islands.ts`. The endpoint uses Astro's
`experimental_AstroContainer` to render the matching component as a
fragment-only response. Adding a new editable region is one entry in the
registry
- Multi-form pages: layout fetches global, route fetches its primary
collection, both register independently — admin shows the right form
based on which marked element you click
- A **`requestWithMetadata()`** helper wrapping every data load so the
same code path runs in production (no overlay → real fetch) and inside
the editor (overlay → use the bridge payload). Production builds ship
zero bridge JS to non-admin visitors
**Why this matters for the Astro community**
Astro is the second-most-starred meta-framework on GitHub and grew
specifically because authors care about runtime cost. Every previous
attempt to integrate a React-based CMS into Astro carried the same
caveat: "but you'll need to ship React for editing." That caveat is now
gone. The bridge is the smallest piece of JS that can deliver Tina's
full editing experience — click to focus, live preview as you type,
click-to-edit overlays — to a framework whose audience explicitly didn't
sign up for React.
**Known content-shape note**
For nested MDX components in rich-text bodies (e.g. ``
inside a post's `_body`) to render via the Astro renderer instead of as
raw HTML, the content needs to be authored through the Tina editor —
which inserts them as MDX templates that Tina parses into
`mdxJsxFlowElement` nodes. Hand-authored `` syntax in the
markdown source is currently parsed as `html` by Tina's MDX layer; same
behaviour as the React renderer. Worth flagging up-front for anyone
migrating existing markdown content.
**Soft-navigation support: `refreshForms()`**
`init()` scans `[data-tina-form]` elements once on first load and
captures the resulting set in closure. Sites using Astro's
`` (or any view-transitions setup that swaps the DOM
without a full reload) would post the first page's forms to the admin
and never refresh them — navigating between docs inside the editor
iframe left the sidebar showing the previous page's form.
`refreshForms()` re-scans the live DOM, diffs against the
previously-mounted set, and posts `close` for forms that disappeared and
`open` (with the same retry-until-acked behaviour as `init`) for forms
that appeared. The one-time global listeners — `click` capture, the
`updateData` ack handler, the `beforeunload` close — stay bound across
refreshes, so calling it on every navigation is cheap and idempotent.
The Astro integration wires it to `astro:page-load` automatically.
**Sticky edit-mode**
A `__tina_edit` session cookie (SameSite=Strict, gated on
`Sec-Fetch-Dest: iframe`) keeps the iframe in edit mode across in-iframe
link clicks — without it, clicking a link inside the preview drops the
`/admin/` Referer and the next request falls out of edit mode. Top-level
visitors never get edit mode because the dest check fails before the
cookie is consulted, so production HTML is unaffected.
**Out of scope (follow-ups)**
- Hugo / Eleventy adapters using the same bridge — the contract is
framework-free, just needs an integration guide
- TinaCloud overlay channel — not needed; the stateless POST protocol
works against any backend
- [#6765](https://github.com/tinacms/tinacms/pull/6765)
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)
Thanks [@kulesy](https://github.com/kulesy)! - Forward the editor's
current branch to the TinaCloud assets-api on every cloud media call,
and fix staging URL handling for multi-segment branches
`TinaMediaStore` now appends `?branch=` to its
`upload_url`, `list`, and `delete` requests so that — once the
assets-api opts an app into branch-aware media — uploads, listings, and
deletions are scoped to the branch the editor is on, instead of always
hitting the production branch. The branch is read from `Client.branch`
(already URL-encoded) and decoded then re-encoded at the use site to
avoid double-encoding.
The query parameter is ignored by assets-api versions that do not parse
it, so this change is safe to deploy ahead of the server-side rollout.
Local mode is unaffected.
`@tinacms/graphql`'s media URL resolver now formats staging URLs as
`/__staging//__file/` instead of
`/__staging//`. The previous form broke for
branches containing `/` (e.g. `feat/my-branch`) because CloudFront
decodes paths before downstream components see them, so the S3 write key
(with a literal `%2F`) wouldn't match the decoded read path. The
`__file` delimiter lets the branch contribute its natural `/` segments
while still marking where the file path begins.
Note: staging URLs produced by `@tinacms/graphql@2.3.0`–`2.3.1` use the
old format and will not round-trip through this version's
`resolveMediaCloudToRelative`. Branch-aware media is gated server-side
and has not been enabled for any tenant yet, so no persisted data is
expected to be affected — but if you turned it on for testing,
regenerate the affected field values from the editor after upgrading.
After a successful cloud upload `TinaMediaStore.persist()` now resolves
its return value from the assets-api `list` endpoint instead of
constructing each `Media.src` locally — the server is the source of
truth for the canonical URL (including the staging-branch path and
per-stage CDN host). The `MediaStore.persist()` contract is preserved,
so the returned items still flow through the media manager and the
image-field drop handler.
Also reserves an optional `rename?(from, to)` hook on the `MediaStore`
interface as a future extension point — no implementation yet.
### Patch Changes
- [#6694](https://github.com/tinacms/tinacms/pull/6694)
[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0)
Thanks [@alhafoudh](https://github.com/alhafoudh)! - Fix crash in
`getFieldGroup` when editing deeply nested rich-text fields (3+ levels)
with templates. The method used `findIndex` which always searched from
the start of the path array, causing it to resolve the wrong
"children"/"props" segments on recursive calls. Replaced with `indexOf`
searching from the current position, and added a null guard for graceful
fallback on malformed content.
- Updated dependencies
\[[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb)]:
- @tinacms/bridge@0.2.0
- @tinacms/search@1.2.14
## @tinacms/app@2.4.7
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## @tinacms/datalayer@2.0.20
### Patch Changes
- Updated dependencies
\[[`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e),
[`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- @tinacms/graphql@2.4.0
## @tinacms/search@1.2.14
### Patch Changes
- Updated dependencies
\[[`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e),
[`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- @tinacms/graphql@2.4.0
## @tinacms/vercel-previews@0.2.7
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## next-tinacms-azure@13.0.0
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## next-tinacms-cloudinary@25.0.0
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## next-tinacms-dos@22.0.0
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## next-tinacms-s3@22.0.0
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## tinacms-authjs@22.0.0
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## tinacms-clerk@22.0.0
### Patch Changes
- Updated dependencies
\[[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0),
[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb),
[`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
- tinacms@3.8.0
## tinacms-gitprovider-github@4.1.7
### Patch Changes
- Updated dependencies \[]:
- @tinacms/datalayer@2.0.20
Co-authored-by: Tina Release Bot
---
.changeset/astro-initial.md | 52 ---------
.../fix-nested-rich-text-getfieldgroup.md | 5 -
.changeset/orange-comics-destroy.md | 75 -------------
.changeset/strip-cloud-url-cross-host.md | 11 --
...ld-localcontentpath-no-duplicate-writes.md | 21 ----
.../tinacms-pass-branch-on-media-calls.md | 18 ----
packages/@tinacms/app/CHANGELOG.md | 7 ++
packages/@tinacms/app/package.json | 2 +-
packages/@tinacms/astro/CHANGELOG.md | 59 +++++++++++
packages/@tinacms/astro/package.json | 62 ++++++++---
packages/@tinacms/bridge/CHANGELOG.md | 79 ++++++++++++++
packages/@tinacms/bridge/package.json | 2 +-
packages/@tinacms/cli/CHANGELOG.md | 29 +++++
packages/@tinacms/cli/package.json | 2 +-
packages/@tinacms/datalayer/CHANGELOG.md | 7 ++
packages/@tinacms/datalayer/package.json | 2 +-
packages/@tinacms/graphql/CHANGELOG.md | 44 ++++++++
packages/@tinacms/graphql/package.json | 2 +-
packages/@tinacms/metrics/CHANGELOG.md | 20 ++++
packages/@tinacms/metrics/package.json | 2 +-
packages/@tinacms/search/CHANGELOG.md | 7 ++
packages/@tinacms/search/package.json | 2 +-
.../@tinacms/vercel-previews/CHANGELOG.md | 7 ++
.../@tinacms/vercel-previews/package.json | 2 +-
packages/next-tinacms-azure/CHANGELOG.md | 7 ++
packages/next-tinacms-azure/package.json | 2 +-
packages/next-tinacms-cloudinary/CHANGELOG.md | 7 ++
packages/next-tinacms-cloudinary/package.json | 2 +-
packages/next-tinacms-dos/CHANGELOG.md | 7 ++
packages/next-tinacms-dos/package.json | 2 +-
packages/next-tinacms-s3/CHANGELOG.md | 7 ++
packages/next-tinacms-s3/package.json | 2 +-
packages/tinacms-authjs/CHANGELOG.md | 7 ++
packages/tinacms-authjs/package.json | 2 +-
packages/tinacms-clerk/CHANGELOG.md | 7 ++
packages/tinacms-clerk/package.json | 2 +-
.../tinacms-gitprovider-github/CHANGELOG.md | 7 ++
.../tinacms-gitprovider-github/package.json | 2 +-
packages/tinacms/CHANGELOG.md | 100 ++++++++++++++++++
packages/tinacms/package.json | 2 +-
40 files changed, 473 insertions(+), 211 deletions(-)
delete mode 100644 .changeset/astro-initial.md
delete mode 100644 .changeset/fix-nested-rich-text-getfieldgroup.md
delete mode 100644 .changeset/orange-comics-destroy.md
delete mode 100644 .changeset/strip-cloud-url-cross-host.md
delete mode 100644 .changeset/tinacms-dev-build-localcontentpath-no-duplicate-writes.md
delete mode 100644 .changeset/tinacms-pass-branch-on-media-calls.md
create mode 100644 packages/@tinacms/astro/CHANGELOG.md
create mode 100644 packages/@tinacms/bridge/CHANGELOG.md
diff --git a/.changeset/astro-initial.md b/.changeset/astro-initial.md
deleted file mode 100644
index 0e2ab6b64e..0000000000
--- a/.changeset/astro-initial.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-"@tinacms/astro": minor
----
-
-✨ **New package: `@tinacms/astro`** — the one-stop integration for using TinaCMS with Astro.
-
-```bash
-pnpm add @tinacms/astro
-```
-
-Bundles the rich-text renderer and re-exports the framework-agnostic bridge under one install. `@tinacms/bridge` stays publishable on its own for non-Astro frontends (coming soon); Astro projects only need `@tinacms/astro`.
-
-**What's exported**
-
-| Subpath | What it gives you |
-|---------|-------------------|
-| `@tinacms/astro` | `requestWithMetadata`, `tinaField`, `QueryResult`, and the rich-text types |
-| `@tinacms/astro/TinaMarkdown.astro` | `` — the rich-text renderer (import via subpath so Astro's check sees a real `.astro` component) |
-| `@tinacms/astro/integration` | `tina()` integration — auto-wires the middleware and bridge route so `requestWithMetadata()` works without threading `Astro.request` or writing wiring components |
-| `@tinacms/astro/TinaIsland.astro` | `` — marker wrapper for an editable region |
-| `@tinacms/astro/types` | `TinaRichTextContent`, `CustomComponentsMap`, `TinaRichTextNode`, `MdxElement`, `TextElement`, etc. |
-| `@tinacms/astro/sanitize` | `sanitizeHref` / `sanitizeImageSrc` for CMS-supplied URLs |
-| `@tinacms/astro/bridge` | `init`, `refreshForms`, and the rest of `@tinacms/bridge` |
-| `@tinacms/astro/tina-field` | `tinaField()` helper for `data-tina-field` markers |
-| `@tinacms/astro/is-edit-mode` | `isEditMode(request)` — server-side admin-iframe detection |
-| `@tinacms/astro/experimental` | `experimental_createIslandRoute()` — opt-in helper for the dynamic `/tina-island/[name]` endpoint |
-
-**Usage**
-
-```astro
----
-import TinaMarkdown from '@tinacms/astro/TinaMarkdown.astro';
-import { requestWithMetadata, tinaField } from '@tinacms/astro';
-import client from '../tina/__generated__/client';
-import { customComponents } from '../components/markdown';
-
-const post = await requestWithMetadata(
- client.queries.post({ relativePath: 'hello.md' }),
-);
----
-
-
-
-```
-
-Add `tina()` from `@tinacms/astro/integration` to your `astro.config.mjs` and the middleware auto-injects the bridge script + per-form payloads on edit-mode requests. Production HTML is byte-identical to a Tina-free Astro app.
-
-The renderer mirrors the React `TinaMarkdown` from `tinacms/dist/rich-text` — same `content` prop, same `components` map shape — but emits pure HTML with no React in the page tree. Custom MDX components register by name (`mdxJsxFlowElement` / `mdxJsxTextElement`); default tags (`p`, `h1`, `a`, etc.) can be overridden by registering them on the same map.
-
-**Peer deps**
-
-- `astro >=5.0.0` — uses Astro's container API for islands and ships `.astro` source files for the consumer's Astro pipeline to compile.
diff --git a/.changeset/fix-nested-rich-text-getfieldgroup.md b/.changeset/fix-nested-rich-text-getfieldgroup.md
deleted file mode 100644
index 53363e2779..0000000000
--- a/.changeset/fix-nested-rich-text-getfieldgroup.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"tinacms": patch
----
-
-Fix crash in `getFieldGroup` when editing deeply nested rich-text fields (3+ levels) with templates. The method used `findIndex` which always searched from the start of the path array, causing it to resolve the wrong "children"/"props" segments on recursive calls. Replaced with `indexOf` searching from the current position, and added a null guard for graceful fallback on malformed content.
diff --git a/.changeset/orange-comics-destroy.md b/.changeset/orange-comics-destroy.md
deleted file mode 100644
index 586d13807b..0000000000
--- a/.changeset/orange-comics-destroy.md
+++ /dev/null
@@ -1,75 +0,0 @@
----
-"@tinacms/bridge": minor
-"tinacms": minor
----
-
-✨ **Visual editing for Astro — without React.**
-
-TinaCMS visual editing previously required `useTina()`, a React hook that subscribes to admin postMessages and re-renders the page tree. That made it a hard sell for Astro: the framework is built around shipping zero JS by default, and the existing `examples/astro/kitchen-sink` worked around the React requirement by hydrating React inside the editor iframe — exactly the pattern Astro authors avoid.
-
-This release ships a vanilla-JS bridge that brings the same click-to-focus, live-update, and form-syncing UX to Astro components, Hugo templates, plain HTML — anything that can emit a `data-tina-form` payload per query.
-
-**New package: `@tinacms/bridge`**
-
-A ~2 KB gzipped, zero-dependency ESM bundle that speaks the existing TinaCMS admin postMessage protocol. No React in the page tree, no client islands, no hydration cost outside the editor iframe.
-
-Astro projects install `@tinacms/astro` instead and the bundled integration's middleware auto-injects everything on edit-mode responses. Direct `@tinacms/bridge` consumption is for non-Astro frontends:
-
-```html
-
-
-
-
-```
-
-The bridge submodules:
-
-- **`init()`** — top-level entry. Detects iframe embedding, registers all `[data-tina-form]` payloads with the admin (with retry, since the bridge boots faster than the admin's listener), wires data updates and click-to-focus.
-- **`refreshForms()`** — re-scans the DOM after soft navigations (Astro view transitions, Turbo, htmx). Posts `close` for forms that left and `open` for forms that appeared.
-- **`tinaField()`** — framework-free field-id helper, identical API to `tinacms/dist/react`'s export. Use on any element to make it click-to-edit.
-- **`@tinacms/bridge/preview`** — server-side helper for non-React frameworks. `readOverlay(request, queryId)` returns the unsaved form data the admin is editing, so per-route refresh endpoints can re-render with overlay data on every keystroke.
-
-**How edits flow without re-rendering React**
-
-The bridge takes a soft-refresh approach instead of in-place reconciliation. Mark editable regions with `data-tina-island=""`; on every form change the bridge POSTs the current overlay to that endpoint, the server renders the matching component to an HTML fragment, and the bridge swaps it into the live DOM. Per-island scoped — editing the hero refetches only the hero, not the whole page. The transport is JSON-over-POST so UTF-8 (em-dashes, smart quotes, emoji) and large rich-text bodies round-trip without size or charset limits.
-
-**The protocol stays stateless** — admin pushes already-resolved data to the bridge, bridge forwards it to the island endpoint, endpoint reads it via `readOverlay()` instead of hitting the canonical content store. Works identically against self-hosted Tina, TinaCloud, or any GraphQL endpoint. No backend changes shipped.
-
-**`tinacms`: framework-free `tinaField` subpath**
-
-`tinaField()` was already pure — just reads `_content_source` metadata. It's now exported from `tinacms/tina-field` as a standalone module so non-React frontends can import it without pulling React (and Plate, and dnd-kit, and ~50 other React deps) into their bundle. The existing `tinacms/dist/react` re-export keeps the public API stable.
-
-**Reference example: `examples/astro/visual-editing`**
-
-A new Astro 5 example that mirrors `examples/astro/kitchen-sink` field-for-field — same six collections (Tag, Author, Global, Post, Blog, Page), same shared content via `localContentPath`, same eight routes — but rendered with pure Astro components instead of React islands. Includes:
-
-- The **`@tinacms/astro` package's `TinaMarkdown`** — a vanilla Astro rich-text renderer that walks the Plate AST Tina returns, dispatches custom MDX components (NewsletterSignup, BlockQuote, DateTime, code blocks) by name to authored Astro components — the same `components` map shape as `TinaMarkdown` from `tinacms/dist/rich-text`, but emitting Astro markup
-- An island-refresh pattern: one dynamic endpoint at `src/pages/tina-island/[name].ts` backed by a registry in `src/lib/islands.ts`. The endpoint uses Astro's `experimental_AstroContainer` to render the matching component as a fragment-only response. Adding a new editable region is one entry in the registry
-- Multi-form pages: layout fetches global, route fetches its primary collection, both register independently — admin shows the right form based on which marked element you click
-- A **`requestWithMetadata()`** helper wrapping every data load so the same code path runs in production (no overlay → real fetch) and inside the editor (overlay → use the bridge payload). Production builds ship zero bridge JS to non-admin visitors
-
-**Why this matters for the Astro community**
-
-Astro is the second-most-starred meta-framework on GitHub and grew specifically because authors care about runtime cost. Every previous attempt to integrate a React-based CMS into Astro carried the same caveat: "but you'll need to ship React for editing." That caveat is now gone. The bridge is the smallest piece of JS that can deliver Tina's full editing experience — click to focus, live preview as you type, click-to-edit overlays — to a framework whose audience explicitly didn't sign up for React.
-
-**Known content-shape note**
-
-For nested MDX components in rich-text bodies (e.g. `` inside a post's `_body`) to render via the Astro renderer instead of as raw HTML, the content needs to be authored through the Tina editor — which inserts them as MDX templates that Tina parses into `mdxJsxFlowElement` nodes. Hand-authored `` syntax in the markdown source is currently parsed as `html` by Tina's MDX layer; same behaviour as the React renderer. Worth flagging up-front for anyone migrating existing markdown content.
-
-**Soft-navigation support: `refreshForms()`**
-
-`init()` scans `[data-tina-form]` elements once on first load and captures the resulting set in closure. Sites using Astro's `` (or any view-transitions setup that swaps the DOM without a full reload) would post the first page's forms to the admin and never refresh them — navigating between docs inside the editor iframe left the sidebar showing the previous page's form.
-
-`refreshForms()` re-scans the live DOM, diffs against the previously-mounted set, and posts `close` for forms that disappeared and `open` (with the same retry-until-acked behaviour as `init`) for forms that appeared. The one-time global listeners — `click` capture, the `updateData` ack handler, the `beforeunload` close — stay bound across refreshes, so calling it on every navigation is cheap and idempotent. The Astro integration wires it to `astro:page-load` automatically.
-
-**Sticky edit-mode**
-
-A `__tina_edit` session cookie (SameSite=Strict, gated on `Sec-Fetch-Dest: iframe`) keeps the iframe in edit mode across in-iframe link clicks — without it, clicking a link inside the preview drops the `/admin/` Referer and the next request falls out of edit mode. Top-level visitors never get edit mode because the dest check fails before the cookie is consulted, so production HTML is unaffected.
-
-**Out of scope (follow-ups)**
-
-- Hugo / Eleventy adapters using the same bridge — the contract is framework-free, just needs an integration guide
-- TinaCloud overlay channel — not needed; the stateless POST protocol works against any backend
diff --git a/.changeset/strip-cloud-url-cross-host.md b/.changeset/strip-cloud-url-cross-host.md
deleted file mode 100644
index 988a8f9de1..0000000000
--- a/.changeset/strip-cloud-url-cross-host.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-"@tinacms/graphql": patch
----
-
-Fix `resolveMediaCloudToRelative` so it strips any TinaCloud cloud URL on save, not only ones whose host matches `config.assetsHost`. The match condition is now host-agnostic: the `/…` path prefix is the durable invariant; the host segment can vary across stages.
-
-This unblocks multi-host setups (PR / stage / personal-dev TinaCloud stages) where the dashboard's default `MediaStore` inserts upload URLs with one host while content-api returns a different one as `assetsHost`. Previously the round-trip silently failed and absolute URLs got committed to the content repo. After this fix, content saves as a relative path regardless of which host the dashboard inserted, matching pre-existing content's format.
-
-Also covers cross-stage content migration: an absolute URL written against one stage strips correctly when re-saved against another.
-
-Closes [#6827](https://github.com/tinacms/tinacms/issues/6827).
diff --git a/.changeset/tinacms-dev-build-localcontentpath-no-duplicate-writes.md b/.changeset/tinacms-dev-build-localcontentpath-no-duplicate-writes.md
deleted file mode 100644
index eb1446c830..0000000000
--- a/.changeset/tinacms-dev-build-localcontentpath-no-duplicate-writes.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-"@tinacms/cli": minor
-"@tinacms/graphql": minor
-"@tinacms/metrics": minor
----
-
-Stop writing generated files (`_schema.json`, `_graphql.json`, `_lookup.json`, `tina-lock.json`) to the content repo when `localContentPath` is set. Generated files now live only in the generator repo's `tina/__generated__/`. The content repo is no longer required to contain a `tina/` folder. `FilesystemBridge.get` / `put` / `delete` now route `tina/__generated__/` and `.tina/__generated__/` paths to `rootPath` (the generator) instead of `outputPath` (the content root). Closes [tinacms/tinacloud#3295](https://github.com/tinacms/tinacloud/issues/3295).
-
-### ⚠️ Rollout gate
-
-**This release must not be promoted to the `@latest` dist-tag until TinaCloud prod has deployed [tinacms/tinacloud#3403](https://github.com/tinacms/tinacloud/issues/3403).** Pre-#3403 TinaCloud reads `tina-lock.json` from the content repo on generator pushes; shipping this change before the server-side fix breaks every existing multi-repo user's indexing.
-
-### Migration notes for existing multi-repo projects
-
-After upgrading (and once TinaCloud prod is on #3403):
-
-- **Stale `tina/` folder in your content repo.** Pre-upgrade builds committed `tina/__generated__/*` and `tina/tina-lock.json` to the content repo. Nothing updates or reads those files any more. They are safe — and recommended — to delete from the content repo in a single cleanup commit.
-- **`ConfigManager.generatedFolderPathContentRepo` is removed.** If any custom CLI code, plugins, or scripts referenced this field, they will fail at type-check or runtime. Use `generatedFolderPath` — it has always been the generator-relative path.
-- **`ConfigManager.getTinaFolderPath` no longer accepts an `isContentRoot` option.** The content root never needs a `tina/` folder now, so the option was removed. If any custom code called `getTinaFolderPath(path, { isContentRoot: true })`, drop the second argument.
-- **`FilesystemBridge` behavior change for `tina/__generated__/` paths.** In multi-repo setups, bridge reads/writes of paths under `tina/__generated__/` or `.tina/__generated__/` now resolve against the generator (`rootPath`) rather than the content repo (`outputPath`). If you have custom bridge subclasses or code that relied on these paths resolving to the content repo, update it.
-- **Generated `client.ts` / `database-client.ts` now import `./types` extensionless** (was `./types.ts`) for TypeScript projects. Avoids requiring `allowImportingTsExtensions: true` in consumer tsconfigs, which broke the build under Next.js 15.5+ defaults. JS projects still import `./types.js` (Node ESM requires the extension).
diff --git a/.changeset/tinacms-pass-branch-on-media-calls.md b/.changeset/tinacms-pass-branch-on-media-calls.md
deleted file mode 100644
index b5e0447c47..0000000000
--- a/.changeset/tinacms-pass-branch-on-media-calls.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-"tinacms": minor
-"@tinacms/graphql": minor
----
-
-Forward the editor's current branch to the TinaCloud assets-api on every cloud media call, and fix staging URL handling for multi-segment branches
-
-`TinaMediaStore` now appends `?branch=` to its `upload_url`, `list`, and `delete` requests so that — once the assets-api opts an app into branch-aware media — uploads, listings, and deletions are scoped to the branch the editor is on, instead of always hitting the production branch. The branch is read from `Client.branch` (already URL-encoded) and decoded then re-encoded at the use site to avoid double-encoding.
-
-The query parameter is ignored by assets-api versions that do not parse it, so this change is safe to deploy ahead of the server-side rollout. Local mode is unaffected.
-
-`@tinacms/graphql`'s media URL resolver now formats staging URLs as `/__staging//__file/` instead of `/__staging//`. The previous form broke for branches containing `/` (e.g. `feat/my-branch`) because CloudFront decodes paths before downstream components see them, so the S3 write key (with a literal `%2F`) wouldn't match the decoded read path. The `__file` delimiter lets the branch contribute its natural `/` segments while still marking where the file path begins.
-
-Note: staging URLs produced by `@tinacms/graphql@2.3.0`–`2.3.1` use the old format and will not round-trip through this version's `resolveMediaCloudToRelative`. Branch-aware media is gated server-side and has not been enabled for any tenant yet, so no persisted data is expected to be affected — but if you turned it on for testing, regenerate the affected field values from the editor after upgrading.
-
-After a successful cloud upload `TinaMediaStore.persist()` now resolves its return value from the assets-api `list` endpoint instead of constructing each `Media.src` locally — the server is the source of truth for the canonical URL (including the staging-branch path and per-stage CDN host). The `MediaStore.persist()` contract is preserved, so the returned items still flow through the media manager and the image-field drop handler.
-
-Also reserves an optional `rename?(from, to)` hook on the `MediaStore` interface as a future extension point — no implementation yet.
diff --git a/packages/@tinacms/app/CHANGELOG.md b/packages/@tinacms/app/CHANGELOG.md
index 14c8f50342..0aa9fd0d5d 100644
--- a/packages/@tinacms/app/CHANGELOG.md
+++ b/packages/@tinacms/app/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tinacms/app
+## 2.4.7
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 2.4.6
### Patch Changes
diff --git a/packages/@tinacms/app/package.json b/packages/@tinacms/app/package.json
index 26db9f9363..e62984cc52 100644
--- a/packages/@tinacms/app/package.json
+++ b/packages/@tinacms/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@tinacms/app",
- "version": "2.4.6",
+ "version": "2.4.7",
"main": "src/main.tsx",
"license": "Apache-2.0",
"devDependencies": {
diff --git a/packages/@tinacms/astro/CHANGELOG.md b/packages/@tinacms/astro/CHANGELOG.md
new file mode 100644
index 0000000000..4ea372d1f9
--- /dev/null
+++ b/packages/@tinacms/astro/CHANGELOG.md
@@ -0,0 +1,59 @@
+# @tinacms/astro
+
+## 0.2.0
+
+### Minor Changes
+
+- [#6771](https://github.com/tinacms/tinacms/pull/6771) [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb) Thanks [@wicksipedia](https://github.com/wicksipedia)! - ✨ **New package: `@tinacms/astro`** — the one-stop integration for using TinaCMS with Astro.
+
+ ```bash
+ pnpm add @tinacms/astro
+ ```
+
+ Bundles the rich-text renderer and re-exports the framework-agnostic bridge under one install. `@tinacms/bridge` stays publishable on its own for non-Astro frontends (coming soon); Astro projects only need `@tinacms/astro`.
+
+ **What's exported**
+
+ | Subpath | What it gives you |
+ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+ | `@tinacms/astro` | `requestWithMetadata`, `tinaField`, `QueryResult`, and the rich-text types |
+ | `@tinacms/astro/TinaMarkdown.astro` | `` — the rich-text renderer (import via subpath so Astro's check sees a real `.astro` component) |
+ | `@tinacms/astro/integration` | `tina()` integration — auto-wires the middleware and bridge route so `requestWithMetadata()` works without threading `Astro.request` or writing wiring components |
+ | `@tinacms/astro/TinaIsland.astro` | `` — marker wrapper for an editable region |
+ | `@tinacms/astro/types` | `TinaRichTextContent`, `CustomComponentsMap`, `TinaRichTextNode`, `MdxElement`, `TextElement`, etc. |
+ | `@tinacms/astro/sanitize` | `sanitizeHref` / `sanitizeImageSrc` for CMS-supplied URLs |
+ | `@tinacms/astro/bridge` | `init`, `refreshForms`, and the rest of `@tinacms/bridge` |
+ | `@tinacms/astro/tina-field` | `tinaField()` helper for `data-tina-field` markers |
+ | `@tinacms/astro/is-edit-mode` | `isEditMode(request)` — server-side admin-iframe detection |
+ | `@tinacms/astro/experimental` | `experimental_createIslandRoute()` — opt-in helper for the dynamic `/tina-island/[name]` endpoint |
+
+ **Usage**
+
+ ```astro
+ ---
+ import TinaMarkdown from '@tinacms/astro/TinaMarkdown.astro';
+ import { requestWithMetadata, tinaField } from '@tinacms/astro';
+ import client from '../tina/__generated__/client';
+ import { customComponents } from '../components/markdown';
+
+ const post = await requestWithMetadata(
+ client.queries.post({ relativePath: 'hello.md' }),
+ );
+ ---
+
+
+
+ ```
+
+ Add `tina()` from `@tinacms/astro/integration` to your `astro.config.mjs` and the middleware auto-injects the bridge script + per-form payloads on edit-mode requests. Production HTML is byte-identical to a Tina-free Astro app.
+
+ The renderer mirrors the React `TinaMarkdown` from `tinacms/dist/rich-text` — same `content` prop, same `components` map shape — but emits pure HTML with no React in the page tree. Custom MDX components register by name (`mdxJsxFlowElement` / `mdxJsxTextElement`); default tags (`p`, `h1`, `a`, etc.) can be overridden by registering them on the same map.
+
+ **Peer deps**
+
+ - `astro >=5.0.0` — uses Astro's container API for islands and ships `.astro` source files for the consumer's Astro pipeline to compile.
+
+### Patch Changes
+
+- Updated dependencies [[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb)]:
+ - @tinacms/bridge@0.2.0
diff --git a/packages/@tinacms/astro/package.json b/packages/@tinacms/astro/package.json
index dcfdf2687a..84058f79ab 100644
--- a/packages/@tinacms/astro/package.json
+++ b/packages/@tinacms/astro/package.json
@@ -1,6 +1,6 @@
{
"name": "@tinacms/astro",
- "version": "0.1.0",
+ "version": "0.2.0",
"type": "module",
"main": "src/TinaMarkdown.astro",
"types": "dist/index.d.ts",
@@ -71,18 +71,54 @@
"license": "Apache-2.0",
"buildConfig": {
"entryPoints": [
- { "name": "src/index.ts", "target": "node" },
- { "name": "src/types.ts", "target": "node" },
- { "name": "src/sanitize.ts", "target": "node" },
- { "name": "src/bridge.ts", "target": "node" },
- { "name": "src/tina-field.ts", "target": "node" },
- { "name": "src/data.ts", "target": "node" },
- { "name": "src/is-edit-mode.ts", "target": "node" },
- { "name": "src/middleware.ts", "target": "node" },
- { "name": "src/integration.ts", "target": "node" },
- { "name": "src/bridge-route.ts", "target": "node" },
- { "name": "src/experimental.ts", "target": "node" },
- { "name": "src/island-route.ts", "target": "node" }
+ {
+ "name": "src/index.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/types.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/sanitize.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/bridge.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/tina-field.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/data.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/is-edit-mode.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/middleware.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/integration.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/bridge-route.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/experimental.ts",
+ "target": "node"
+ },
+ {
+ "name": "src/island-route.ts",
+ "target": "node"
+ }
]
},
"scripts": {
diff --git a/packages/@tinacms/bridge/CHANGELOG.md b/packages/@tinacms/bridge/CHANGELOG.md
new file mode 100644
index 0000000000..bd0fa189b4
--- /dev/null
+++ b/packages/@tinacms/bridge/CHANGELOG.md
@@ -0,0 +1,79 @@
+# @tinacms/bridge
+
+## 0.2.0
+
+### Minor Changes
+
+- [#6771](https://github.com/tinacms/tinacms/pull/6771) [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb) Thanks [@wicksipedia](https://github.com/wicksipedia)! - ✨ **Visual editing for Astro — without React.**
+
+ TinaCMS visual editing previously required `useTina()`, a React hook that subscribes to admin postMessages and re-renders the page tree. That made it a hard sell for Astro: the framework is built around shipping zero JS by default, and the existing `examples/astro/kitchen-sink` worked around the React requirement by hydrating React inside the editor iframe — exactly the pattern Astro authors avoid.
+
+ This release ships a vanilla-JS bridge that brings the same click-to-focus, live-update, and form-syncing UX to Astro components, Hugo templates, plain HTML — anything that can emit a `data-tina-form` payload per query.
+
+ **New package: `@tinacms/bridge`**
+
+ A ~2 KB gzipped, zero-dependency ESM bundle that speaks the existing TinaCMS admin postMessage protocol. No React in the page tree, no client islands, no hydration cost outside the editor iframe.
+
+ Astro projects install `@tinacms/astro` instead and the bundled integration's middleware auto-injects everything on edit-mode responses. Direct `@tinacms/bridge` consumption is for non-Astro frontends:
+
+ ```html
+
+
+
+
+ ```
+
+ The bridge submodules:
+
+ - **`init()`** — top-level entry. Detects iframe embedding, registers all `[data-tina-form]` payloads with the admin (with retry, since the bridge boots faster than the admin's listener), wires data updates and click-to-focus.
+ - **`refreshForms()`** — re-scans the DOM after soft navigations (Astro view transitions, Turbo, htmx). Posts `close` for forms that left and `open` for forms that appeared.
+ - **`tinaField()`** — framework-free field-id helper, identical API to `tinacms/dist/react`'s export. Use on any element to make it click-to-edit.
+ - **`@tinacms/bridge/preview`** — server-side helper for non-React frameworks. `readOverlay(request, queryId)` returns the unsaved form data the admin is editing, so per-route refresh endpoints can re-render with overlay data on every keystroke.
+
+ **How edits flow without re-rendering React**
+
+ The bridge takes a soft-refresh approach instead of in-place reconciliation. Mark editable regions with `data-tina-island=""`; on every form change the bridge POSTs the current overlay to that endpoint, the server renders the matching component to an HTML fragment, and the bridge swaps it into the live DOM. Per-island scoped — editing the hero refetches only the hero, not the whole page. The transport is JSON-over-POST so UTF-8 (em-dashes, smart quotes, emoji) and large rich-text bodies round-trip without size or charset limits.
+
+ **The protocol stays stateless** — admin pushes already-resolved data to the bridge, bridge forwards it to the island endpoint, endpoint reads it via `readOverlay()` instead of hitting the canonical content store. Works identically against self-hosted Tina, TinaCloud, or any GraphQL endpoint. No backend changes shipped.
+
+ **`tinacms`: framework-free `tinaField` subpath**
+
+ `tinaField()` was already pure — just reads `_content_source` metadata. It's now exported from `tinacms/tina-field` as a standalone module so non-React frontends can import it without pulling React (and Plate, and dnd-kit, and ~50 other React deps) into their bundle. The existing `tinacms/dist/react` re-export keeps the public API stable.
+
+ **Reference example: `examples/astro/visual-editing`**
+
+ A new Astro 5 example that mirrors `examples/astro/kitchen-sink` field-for-field — same six collections (Tag, Author, Global, Post, Blog, Page), same shared content via `localContentPath`, same eight routes — but rendered with pure Astro components instead of React islands. Includes:
+
+ - The **`@tinacms/astro` package's `TinaMarkdown`** — a vanilla Astro rich-text renderer that walks the Plate AST Tina returns, dispatches custom MDX components (NewsletterSignup, BlockQuote, DateTime, code blocks) by name to authored Astro components — the same `components` map shape as `TinaMarkdown` from `tinacms/dist/rich-text`, but emitting Astro markup
+ - An island-refresh pattern: one dynamic endpoint at `src/pages/tina-island/[name].ts` backed by a registry in `src/lib/islands.ts`. The endpoint uses Astro's `experimental_AstroContainer` to render the matching component as a fragment-only response. Adding a new editable region is one entry in the registry
+ - Multi-form pages: layout fetches global, route fetches its primary collection, both register independently — admin shows the right form based on which marked element you click
+ - A **`requestWithMetadata()`** helper wrapping every data load so the same code path runs in production (no overlay → real fetch) and inside the editor (overlay → use the bridge payload). Production builds ship zero bridge JS to non-admin visitors
+
+ **Why this matters for the Astro community**
+
+ Astro is the second-most-starred meta-framework on GitHub and grew specifically because authors care about runtime cost. Every previous attempt to integrate a React-based CMS into Astro carried the same caveat: "but you'll need to ship React for editing." That caveat is now gone. The bridge is the smallest piece of JS that can deliver Tina's full editing experience — click to focus, live preview as you type, click-to-edit overlays — to a framework whose audience explicitly didn't sign up for React.
+
+ **Known content-shape note**
+
+ For nested MDX components in rich-text bodies (e.g. `` inside a post's `_body`) to render via the Astro renderer instead of as raw HTML, the content needs to be authored through the Tina editor — which inserts them as MDX templates that Tina parses into `mdxJsxFlowElement` nodes. Hand-authored `` syntax in the markdown source is currently parsed as `html` by Tina's MDX layer; same behaviour as the React renderer. Worth flagging up-front for anyone migrating existing markdown content.
+
+ **Soft-navigation support: `refreshForms()`**
+
+ `init()` scans `[data-tina-form]` elements once on first load and captures the resulting set in closure. Sites using Astro's `` (or any view-transitions setup that swaps the DOM without a full reload) would post the first page's forms to the admin and never refresh them — navigating between docs inside the editor iframe left the sidebar showing the previous page's form.
+
+ `refreshForms()` re-scans the live DOM, diffs against the previously-mounted set, and posts `close` for forms that disappeared and `open` (with the same retry-until-acked behaviour as `init`) for forms that appeared. The one-time global listeners — `click` capture, the `updateData` ack handler, the `beforeunload` close — stay bound across refreshes, so calling it on every navigation is cheap and idempotent. The Astro integration wires it to `astro:page-load` automatically.
+
+ **Sticky edit-mode**
+
+ A `__tina_edit` session cookie (SameSite=Strict, gated on `Sec-Fetch-Dest: iframe`) keeps the iframe in edit mode across in-iframe link clicks — without it, clicking a link inside the preview drops the `/admin/` Referer and the next request falls out of edit mode. Top-level visitors never get edit mode because the dest check fails before the cookie is consulted, so production HTML is unaffected.
+
+ **Out of scope (follow-ups)**
+
+ - Hugo / Eleventy adapters using the same bridge — the contract is framework-free, just needs an integration guide
+ - TinaCloud overlay channel — not needed; the stateless POST protocol works against any backend
diff --git a/packages/@tinacms/bridge/package.json b/packages/@tinacms/bridge/package.json
index b1ba065e04..8ef27d4236 100644
--- a/packages/@tinacms/bridge/package.json
+++ b/packages/@tinacms/bridge/package.json
@@ -1,6 +1,6 @@
{
"name": "@tinacms/bridge",
- "version": "0.1.0",
+ "version": "0.2.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/packages/@tinacms/cli/CHANGELOG.md b/packages/@tinacms/cli/CHANGELOG.md
index 8749b756c3..40bac760cc 100644
--- a/packages/@tinacms/cli/CHANGELOG.md
+++ b/packages/@tinacms/cli/CHANGELOG.md
@@ -1,5 +1,34 @@
# tinacms-cli
+## 2.3.0
+
+### Minor Changes
+
+- [#6738](https://github.com/tinacms/tinacms/pull/6738) [`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d) Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Stop writing generated files (`_schema.json`, `_graphql.json`, `_lookup.json`, `tina-lock.json`) to the content repo when `localContentPath` is set. Generated files now live only in the generator repo's `tina/__generated__/`. The content repo is no longer required to contain a `tina/` folder. `FilesystemBridge.get` / `put` / `delete` now route `tina/__generated__/` and `.tina/__generated__/` paths to `rootPath` (the generator) instead of `outputPath` (the content root). Closes [tinacms/tinacloud#3295](https://github.com/tinacms/tinacloud/issues/3295).
+
+ ### ⚠️ Rollout gate
+
+ **This release must not be promoted to the `@latest` dist-tag until TinaCloud prod has deployed [tinacms/tinacloud#3403](https://github.com/tinacms/tinacloud/issues/3403).** Pre-#3403 TinaCloud reads `tina-lock.json` from the content repo on generator pushes; shipping this change before the server-side fix breaks every existing multi-repo user's indexing.
+
+ ### Migration notes for existing multi-repo projects
+
+ After upgrading (and once TinaCloud prod is on #3403):
+
+ - **Stale `tina/` folder in your content repo.** Pre-upgrade builds committed `tina/__generated__/*` and `tina/tina-lock.json` to the content repo. Nothing updates or reads those files any more. They are safe — and recommended — to delete from the content repo in a single cleanup commit.
+ - **`ConfigManager.generatedFolderPathContentRepo` is removed.** If any custom CLI code, plugins, or scripts referenced this field, they will fail at type-check or runtime. Use `generatedFolderPath` — it has always been the generator-relative path.
+ - **`ConfigManager.getTinaFolderPath` no longer accepts an `isContentRoot` option.** The content root never needs a `tina/` folder now, so the option was removed. If any custom code called `getTinaFolderPath(path, { isContentRoot: true })`, drop the second argument.
+ - **`FilesystemBridge` behavior change for `tina/__generated__/` paths.** In multi-repo setups, bridge reads/writes of paths under `tina/__generated__/` or `.tina/__generated__/` now resolve against the generator (`rootPath`) rather than the content repo (`outputPath`). If you have custom bridge subclasses or code that relied on these paths resolving to the content repo, update it.
+ - **Generated `client.ts` / `database-client.ts` now import `./types` extensionless** (was `./types.ts`) for TypeScript projects. Avoids requiring `allowImportingTsExtensions: true` in consumer tsconfigs, which broke the build under Next.js 15.5+ defaults. JS projects still import `./types.js` (Node ESM requires the extension).
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e), [`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+ - @tinacms/graphql@2.4.0
+ - @tinacms/metrics@2.1.0
+ - @tinacms/app@2.4.7
+ - @tinacms/search@1.2.14
+
## 2.2.6
### Patch Changes
diff --git a/packages/@tinacms/cli/package.json b/packages/@tinacms/cli/package.json
index eae3316b71..2f9592b97f 100644
--- a/packages/@tinacms/cli/package.json
+++ b/packages/@tinacms/cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@tinacms/cli",
"type": "module",
- "version": "2.2.6",
+ "version": "2.3.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"files": [
diff --git a/packages/@tinacms/datalayer/CHANGELOG.md b/packages/@tinacms/datalayer/CHANGELOG.md
index 6f1cdc3803..dc5140d5ce 100644
--- a/packages/@tinacms/datalayer/CHANGELOG.md
+++ b/packages/@tinacms/datalayer/CHANGELOG.md
@@ -1,5 +1,12 @@
# tina-graphql
+## 2.0.20
+
+### Patch Changes
+
+- Updated dependencies [[`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e), [`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - @tinacms/graphql@2.4.0
+
## 2.0.19
### Patch Changes
diff --git a/packages/@tinacms/datalayer/package.json b/packages/@tinacms/datalayer/package.json
index cfe73dd527..f4e1f527f0 100644
--- a/packages/@tinacms/datalayer/package.json
+++ b/packages/@tinacms/datalayer/package.json
@@ -1,7 +1,7 @@
{
"name": "@tinacms/datalayer",
"type": "module",
- "version": "2.0.19",
+ "version": "2.0.20",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
diff --git a/packages/@tinacms/graphql/CHANGELOG.md b/packages/@tinacms/graphql/CHANGELOG.md
index 052288100a..f0d8e22095 100644
--- a/packages/@tinacms/graphql/CHANGELOG.md
+++ b/packages/@tinacms/graphql/CHANGELOG.md
@@ -1,5 +1,49 @@
# tina-graphql
+## 2.4.0
+
+### Minor Changes
+
+- [#6738](https://github.com/tinacms/tinacms/pull/6738) [`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d) Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Stop writing generated files (`_schema.json`, `_graphql.json`, `_lookup.json`, `tina-lock.json`) to the content repo when `localContentPath` is set. Generated files now live only in the generator repo's `tina/__generated__/`. The content repo is no longer required to contain a `tina/` folder. `FilesystemBridge.get` / `put` / `delete` now route `tina/__generated__/` and `.tina/__generated__/` paths to `rootPath` (the generator) instead of `outputPath` (the content root). Closes [tinacms/tinacloud#3295](https://github.com/tinacms/tinacloud/issues/3295).
+
+ ### ⚠️ Rollout gate
+
+ **This release must not be promoted to the `@latest` dist-tag until TinaCloud prod has deployed [tinacms/tinacloud#3403](https://github.com/tinacms/tinacloud/issues/3403).** Pre-#3403 TinaCloud reads `tina-lock.json` from the content repo on generator pushes; shipping this change before the server-side fix breaks every existing multi-repo user's indexing.
+
+ ### Migration notes for existing multi-repo projects
+
+ After upgrading (and once TinaCloud prod is on #3403):
+
+ - **Stale `tina/` folder in your content repo.** Pre-upgrade builds committed `tina/__generated__/*` and `tina/tina-lock.json` to the content repo. Nothing updates or reads those files any more. They are safe — and recommended — to delete from the content repo in a single cleanup commit.
+ - **`ConfigManager.generatedFolderPathContentRepo` is removed.** If any custom CLI code, plugins, or scripts referenced this field, they will fail at type-check or runtime. Use `generatedFolderPath` — it has always been the generator-relative path.
+ - **`ConfigManager.getTinaFolderPath` no longer accepts an `isContentRoot` option.** The content root never needs a `tina/` folder now, so the option was removed. If any custom code called `getTinaFolderPath(path, { isContentRoot: true })`, drop the second argument.
+ - **`FilesystemBridge` behavior change for `tina/__generated__/` paths.** In multi-repo setups, bridge reads/writes of paths under `tina/__generated__/` or `.tina/__generated__/` now resolve against the generator (`rootPath`) rather than the content repo (`outputPath`). If you have custom bridge subclasses or code that relied on these paths resolving to the content repo, update it.
+ - **Generated `client.ts` / `database-client.ts` now import `./types` extensionless** (was `./types.ts`) for TypeScript projects. Avoids requiring `allowImportingTsExtensions: true` in consumer tsconfigs, which broke the build under Next.js 15.5+ defaults. JS projects still import `./types.js` (Node ESM requires the extension).
+
+- [#6765](https://github.com/tinacms/tinacms/pull/6765) [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8) Thanks [@kulesy](https://github.com/kulesy)! - Forward the editor's current branch to the TinaCloud assets-api on every cloud media call, and fix staging URL handling for multi-segment branches
+
+ `TinaMediaStore` now appends `?branch=` to its `upload_url`, `list`, and `delete` requests so that — once the assets-api opts an app into branch-aware media — uploads, listings, and deletions are scoped to the branch the editor is on, instead of always hitting the production branch. The branch is read from `Client.branch` (already URL-encoded) and decoded then re-encoded at the use site to avoid double-encoding.
+
+ The query parameter is ignored by assets-api versions that do not parse it, so this change is safe to deploy ahead of the server-side rollout. Local mode is unaffected.
+
+ `@tinacms/graphql`'s media URL resolver now formats staging URLs as `/__staging//__file/` instead of `/__staging//`. The previous form broke for branches containing `/` (e.g. `feat/my-branch`) because CloudFront decodes paths before downstream components see them, so the S3 write key (with a literal `%2F`) wouldn't match the decoded read path. The `__file` delimiter lets the branch contribute its natural `/` segments while still marking where the file path begins.
+
+ Note: staging URLs produced by `@tinacms/graphql@2.3.0`–`2.3.1` use the old format and will not round-trip through this version's `resolveMediaCloudToRelative`. Branch-aware media is gated server-side and has not been enabled for any tenant yet, so no persisted data is expected to be affected — but if you turned it on for testing, regenerate the affected field values from the editor after upgrading.
+
+ After a successful cloud upload `TinaMediaStore.persist()` now resolves its return value from the assets-api `list` endpoint instead of constructing each `Media.src` locally — the server is the source of truth for the canonical URL (including the staging-branch path and per-stage CDN host). The `MediaStore.persist()` contract is preserved, so the returned items still flow through the media manager and the image-field drop handler.
+
+ Also reserves an optional `rename?(from, to)` hook on the `MediaStore` interface as a future extension point — no implementation yet.
+
+### Patch Changes
+
+- [#6828](https://github.com/tinacms/tinacms/pull/6828) [`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e) Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Fix `resolveMediaCloudToRelative` so it strips any TinaCloud cloud URL on save, not only ones whose host matches `config.assetsHost`. The match condition is now host-agnostic: the `/…` path prefix is the durable invariant; the host segment can vary across stages.
+
+ This unblocks multi-host setups (PR / stage / personal-dev TinaCloud stages) where the dashboard's default `MediaStore` inserts upload URLs with one host while content-api returns a different one as `assetsHost`. Previously the round-trip silently failed and absolute URLs got committed to the content repo. After this fix, content saves as a relative path regardless of which host the dashboard inserted, matching pre-existing content's format.
+
+ Also covers cross-stage content migration: an absolute URL written against one stage strips correctly when re-saved against another.
+
+ Closes [#6827](https://github.com/tinacms/tinacms/issues/6827).
+
## 2.3.1
### Patch Changes
diff --git a/packages/@tinacms/graphql/package.json b/packages/@tinacms/graphql/package.json
index 167a859745..401f316ac8 100644
--- a/packages/@tinacms/graphql/package.json
+++ b/packages/@tinacms/graphql/package.json
@@ -1,7 +1,7 @@
{
"name": "@tinacms/graphql",
"type": "module",
- "version": "2.3.1",
+ "version": "2.4.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"files": [
diff --git a/packages/@tinacms/metrics/CHANGELOG.md b/packages/@tinacms/metrics/CHANGELOG.md
index 146ab687e6..acb7afe824 100644
--- a/packages/@tinacms/metrics/CHANGELOG.md
+++ b/packages/@tinacms/metrics/CHANGELOG.md
@@ -1,5 +1,25 @@
# @tinacms/metrics
+## 2.1.0
+
+### Minor Changes
+
+- [#6738](https://github.com/tinacms/tinacms/pull/6738) [`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d) Thanks [@joshbermanssw](https://github.com/joshbermanssw)! - Stop writing generated files (`_schema.json`, `_graphql.json`, `_lookup.json`, `tina-lock.json`) to the content repo when `localContentPath` is set. Generated files now live only in the generator repo's `tina/__generated__/`. The content repo is no longer required to contain a `tina/` folder. `FilesystemBridge.get` / `put` / `delete` now route `tina/__generated__/` and `.tina/__generated__/` paths to `rootPath` (the generator) instead of `outputPath` (the content root). Closes [tinacms/tinacloud#3295](https://github.com/tinacms/tinacloud/issues/3295).
+
+ ### ⚠️ Rollout gate
+
+ **This release must not be promoted to the `@latest` dist-tag until TinaCloud prod has deployed [tinacms/tinacloud#3403](https://github.com/tinacms/tinacloud/issues/3403).** Pre-#3403 TinaCloud reads `tina-lock.json` from the content repo on generator pushes; shipping this change before the server-side fix breaks every existing multi-repo user's indexing.
+
+ ### Migration notes for existing multi-repo projects
+
+ After upgrading (and once TinaCloud prod is on #3403):
+
+ - **Stale `tina/` folder in your content repo.** Pre-upgrade builds committed `tina/__generated__/*` and `tina/tina-lock.json` to the content repo. Nothing updates or reads those files any more. They are safe — and recommended — to delete from the content repo in a single cleanup commit.
+ - **`ConfigManager.generatedFolderPathContentRepo` is removed.** If any custom CLI code, plugins, or scripts referenced this field, they will fail at type-check or runtime. Use `generatedFolderPath` — it has always been the generator-relative path.
+ - **`ConfigManager.getTinaFolderPath` no longer accepts an `isContentRoot` option.** The content root never needs a `tina/` folder now, so the option was removed. If any custom code called `getTinaFolderPath(path, { isContentRoot: true })`, drop the second argument.
+ - **`FilesystemBridge` behavior change for `tina/__generated__/` paths.** In multi-repo setups, bridge reads/writes of paths under `tina/__generated__/` or `.tina/__generated__/` now resolve against the generator (`rootPath`) rather than the content repo (`outputPath`). If you have custom bridge subclasses or code that relied on these paths resolving to the content repo, update it.
+ - **Generated `client.ts` / `database-client.ts` now import `./types` extensionless** (was `./types.ts`) for TypeScript projects. Avoids requiring `allowImportingTsExtensions: true` in consumer tsconfigs, which broke the build under Next.js 15.5+ defaults. JS projects still import `./types.js` (Node ESM requires the extension).
+
## 2.0.1
### Patch Changes
diff --git a/packages/@tinacms/metrics/package.json b/packages/@tinacms/metrics/package.json
index 4461de90ff..2d364bdaa0 100644
--- a/packages/@tinacms/metrics/package.json
+++ b/packages/@tinacms/metrics/package.json
@@ -1,6 +1,6 @@
{
"name": "@tinacms/metrics",
- "version": "2.0.1",
+ "version": "2.1.0",
"main": "dist/index.js",
"type": "module",
"typings": "dist/index.d.ts",
diff --git a/packages/@tinacms/search/CHANGELOG.md b/packages/@tinacms/search/CHANGELOG.md
index 8e1dd0ea83..22cc098142 100644
--- a/packages/@tinacms/search/CHANGELOG.md
+++ b/packages/@tinacms/search/CHANGELOG.md
@@ -1,5 +1,12 @@
# @tinacms/search
+## 1.2.14
+
+### Patch Changes
+
+- Updated dependencies [[`eafb1ff`](https://github.com/tinacms/tinacms/commit/eafb1ffbd78267838f6939b3e993efc37c05cb2e), [`4d0c37a`](https://github.com/tinacms/tinacms/commit/4d0c37a8a50b211b7c5070c370faa369ee5d260d), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - @tinacms/graphql@2.4.0
+
## 1.2.13
### Patch Changes
diff --git a/packages/@tinacms/search/package.json b/packages/@tinacms/search/package.json
index 3c60306455..71cfa4c7b1 100644
--- a/packages/@tinacms/search/package.json
+++ b/packages/@tinacms/search/package.json
@@ -1,7 +1,7 @@
{
"name": "@tinacms/search",
"type": "module",
- "version": "1.2.13",
+ "version": "1.2.14",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
diff --git a/packages/@tinacms/vercel-previews/CHANGELOG.md b/packages/@tinacms/vercel-previews/CHANGELOG.md
index 432b55609b..c822dcbae5 100644
--- a/packages/@tinacms/vercel-previews/CHANGELOG.md
+++ b/packages/@tinacms/vercel-previews/CHANGELOG.md
@@ -1,5 +1,12 @@
# Change Log
+## 0.2.7
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 0.2.6
### Patch Changes
diff --git a/packages/@tinacms/vercel-previews/package.json b/packages/@tinacms/vercel-previews/package.json
index 3bfe13c5e8..978186a73f 100644
--- a/packages/@tinacms/vercel-previews/package.json
+++ b/packages/@tinacms/vercel-previews/package.json
@@ -1,7 +1,7 @@
{
"name": "@tinacms/vercel-previews",
"type": "module",
- "version": "0.2.6",
+ "version": "0.2.7",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"keywords": [
diff --git a/packages/next-tinacms-azure/CHANGELOG.md b/packages/next-tinacms-azure/CHANGELOG.md
index aa5c07fd3c..abf2726669 100644
--- a/packages/next-tinacms-azure/CHANGELOG.md
+++ b/packages/next-tinacms-azure/CHANGELOG.md
@@ -1,5 +1,12 @@
# next-tinacms-azure
+## 13.0.0
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 12.0.6
### Patch Changes
diff --git a/packages/next-tinacms-azure/package.json b/packages/next-tinacms-azure/package.json
index 3cf14a3f6a..12bd69c0a1 100644
--- a/packages/next-tinacms-azure/package.json
+++ b/packages/next-tinacms-azure/package.json
@@ -1,7 +1,7 @@
{
"name": "next-tinacms-azure",
"type": "module",
- "version": "12.0.6",
+ "version": "13.0.0",
"description": "",
"main": "dist/index.js",
"module": "./dist/index.js",
diff --git a/packages/next-tinacms-cloudinary/CHANGELOG.md b/packages/next-tinacms-cloudinary/CHANGELOG.md
index 7752cfd19b..8f252b710a 100644
--- a/packages/next-tinacms-cloudinary/CHANGELOG.md
+++ b/packages/next-tinacms-cloudinary/CHANGELOG.md
@@ -1,5 +1,12 @@
# next-tinacms-cloudinary
+## 25.0.0
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 24.0.6
### Patch Changes
diff --git a/packages/next-tinacms-cloudinary/package.json b/packages/next-tinacms-cloudinary/package.json
index 713482edc9..765946b298 100644
--- a/packages/next-tinacms-cloudinary/package.json
+++ b/packages/next-tinacms-cloudinary/package.json
@@ -1,7 +1,7 @@
{
"name": "next-tinacms-cloudinary",
"type": "module",
- "version": "24.0.6",
+ "version": "25.0.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"files": [
diff --git a/packages/next-tinacms-dos/CHANGELOG.md b/packages/next-tinacms-dos/CHANGELOG.md
index 46a9373dba..2194eb3a11 100644
--- a/packages/next-tinacms-dos/CHANGELOG.md
+++ b/packages/next-tinacms-dos/CHANGELOG.md
@@ -1,5 +1,12 @@
# next-tinacms-cloudinary
+## 22.0.0
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 21.0.6
### Patch Changes
diff --git a/packages/next-tinacms-dos/package.json b/packages/next-tinacms-dos/package.json
index 6fde8ffc8d..89da6bc799 100644
--- a/packages/next-tinacms-dos/package.json
+++ b/packages/next-tinacms-dos/package.json
@@ -1,7 +1,7 @@
{
"name": "next-tinacms-dos",
"type": "module",
- "version": "21.0.6",
+ "version": "22.0.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"files": [
diff --git a/packages/next-tinacms-s3/CHANGELOG.md b/packages/next-tinacms-s3/CHANGELOG.md
index f8995c7182..4572d76e7d 100644
--- a/packages/next-tinacms-s3/CHANGELOG.md
+++ b/packages/next-tinacms-s3/CHANGELOG.md
@@ -1,5 +1,12 @@
# next-tinacms-s3
+## 22.0.0
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 21.0.6
### Patch Changes
diff --git a/packages/next-tinacms-s3/package.json b/packages/next-tinacms-s3/package.json
index e5dc276498..60e9c17eaa 100644
--- a/packages/next-tinacms-s3/package.json
+++ b/packages/next-tinacms-s3/package.json
@@ -1,7 +1,7 @@
{
"name": "next-tinacms-s3",
"type": "module",
- "version": "21.0.6",
+ "version": "22.0.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"files": [
diff --git a/packages/tinacms-authjs/CHANGELOG.md b/packages/tinacms-authjs/CHANGELOG.md
index 3399cf01da..a4353ca639 100644
--- a/packages/tinacms-authjs/CHANGELOG.md
+++ b/packages/tinacms-authjs/CHANGELOG.md
@@ -1,5 +1,12 @@
# tinacms-authjs
+## 22.0.0
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 21.0.6
### Patch Changes
diff --git a/packages/tinacms-authjs/package.json b/packages/tinacms-authjs/package.json
index 67e2da2f1c..03694ac96b 100644
--- a/packages/tinacms-authjs/package.json
+++ b/packages/tinacms-authjs/package.json
@@ -1,7 +1,7 @@
{
"name": "tinacms-authjs",
"type": "module",
- "version": "21.0.6",
+ "version": "22.0.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"files": [
diff --git a/packages/tinacms-clerk/CHANGELOG.md b/packages/tinacms-clerk/CHANGELOG.md
index e987ee4e45..8457a284f3 100644
--- a/packages/tinacms-clerk/CHANGELOG.md
+++ b/packages/tinacms-clerk/CHANGELOG.md
@@ -1,5 +1,12 @@
# tinacms-clerk
+## 22.0.0
+
+### Patch Changes
+
+- Updated dependencies [[`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0), [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb), [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8)]:
+ - tinacms@3.8.0
+
## 21.0.6
### Patch Changes
diff --git a/packages/tinacms-clerk/package.json b/packages/tinacms-clerk/package.json
index 9f27847faf..45397d8fa6 100644
--- a/packages/tinacms-clerk/package.json
+++ b/packages/tinacms-clerk/package.json
@@ -1,7 +1,7 @@
{
"name": "tinacms-clerk",
"type": "module",
- "version": "21.0.6",
+ "version": "22.0.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"files": [
diff --git a/packages/tinacms-gitprovider-github/CHANGELOG.md b/packages/tinacms-gitprovider-github/CHANGELOG.md
index b3fa50f2fd..0e6dbcd59d 100644
--- a/packages/tinacms-gitprovider-github/CHANGELOG.md
+++ b/packages/tinacms-gitprovider-github/CHANGELOG.md
@@ -1,5 +1,12 @@
# tinacms-gitprovider-github
+## 4.1.7
+
+### Patch Changes
+
+- Updated dependencies []:
+ - @tinacms/datalayer@2.0.20
+
## 4.1.6
### Patch Changes
diff --git a/packages/tinacms-gitprovider-github/package.json b/packages/tinacms-gitprovider-github/package.json
index 71c6fa5700..6adc1e40a6 100644
--- a/packages/tinacms-gitprovider-github/package.json
+++ b/packages/tinacms-gitprovider-github/package.json
@@ -1,7 +1,7 @@
{
"name": "tinacms-gitprovider-github",
"type": "module",
- "version": "4.1.6",
+ "version": "4.1.7",
"main": "dist/index.js",
"module": "./dist/index.js",
"files": [
diff --git a/packages/tinacms/CHANGELOG.md b/packages/tinacms/CHANGELOG.md
index 31a86b1765..8f852bd86a 100644
--- a/packages/tinacms/CHANGELOG.md
+++ b/packages/tinacms/CHANGELOG.md
@@ -1,5 +1,105 @@
# tinacms
+## 3.8.0
+
+### Minor Changes
+
+- [#6771](https://github.com/tinacms/tinacms/pull/6771) [`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb) Thanks [@wicksipedia](https://github.com/wicksipedia)! - ✨ **Visual editing for Astro — without React.**
+
+ TinaCMS visual editing previously required `useTina()`, a React hook that subscribes to admin postMessages and re-renders the page tree. That made it a hard sell for Astro: the framework is built around shipping zero JS by default, and the existing `examples/astro/kitchen-sink` worked around the React requirement by hydrating React inside the editor iframe — exactly the pattern Astro authors avoid.
+
+ This release ships a vanilla-JS bridge that brings the same click-to-focus, live-update, and form-syncing UX to Astro components, Hugo templates, plain HTML — anything that can emit a `data-tina-form` payload per query.
+
+ **New package: `@tinacms/bridge`**
+
+ A ~2 KB gzipped, zero-dependency ESM bundle that speaks the existing TinaCMS admin postMessage protocol. No React in the page tree, no client islands, no hydration cost outside the editor iframe.
+
+ Astro projects install `@tinacms/astro` instead and the bundled integration's middleware auto-injects everything on edit-mode responses. Direct `@tinacms/bridge` consumption is for non-Astro frontends:
+
+ ```html
+
+
+
+
+ ```
+
+ The bridge submodules:
+
+ - **`init()`** — top-level entry. Detects iframe embedding, registers all `[data-tina-form]` payloads with the admin (with retry, since the bridge boots faster than the admin's listener), wires data updates and click-to-focus.
+ - **`refreshForms()`** — re-scans the DOM after soft navigations (Astro view transitions, Turbo, htmx). Posts `close` for forms that left and `open` for forms that appeared.
+ - **`tinaField()`** — framework-free field-id helper, identical API to `tinacms/dist/react`'s export. Use on any element to make it click-to-edit.
+ - **`@tinacms/bridge/preview`** — server-side helper for non-React frameworks. `readOverlay(request, queryId)` returns the unsaved form data the admin is editing, so per-route refresh endpoints can re-render with overlay data on every keystroke.
+
+ **How edits flow without re-rendering React**
+
+ The bridge takes a soft-refresh approach instead of in-place reconciliation. Mark editable regions with `data-tina-island=""`; on every form change the bridge POSTs the current overlay to that endpoint, the server renders the matching component to an HTML fragment, and the bridge swaps it into the live DOM. Per-island scoped — editing the hero refetches only the hero, not the whole page. The transport is JSON-over-POST so UTF-8 (em-dashes, smart quotes, emoji) and large rich-text bodies round-trip without size or charset limits.
+
+ **The protocol stays stateless** — admin pushes already-resolved data to the bridge, bridge forwards it to the island endpoint, endpoint reads it via `readOverlay()` instead of hitting the canonical content store. Works identically against self-hosted Tina, TinaCloud, or any GraphQL endpoint. No backend changes shipped.
+
+ **`tinacms`: framework-free `tinaField` subpath**
+
+ `tinaField()` was already pure — just reads `_content_source` metadata. It's now exported from `tinacms/tina-field` as a standalone module so non-React frontends can import it without pulling React (and Plate, and dnd-kit, and ~50 other React deps) into their bundle. The existing `tinacms/dist/react` re-export keeps the public API stable.
+
+ **Reference example: `examples/astro/visual-editing`**
+
+ A new Astro 5 example that mirrors `examples/astro/kitchen-sink` field-for-field — same six collections (Tag, Author, Global, Post, Blog, Page), same shared content via `localContentPath`, same eight routes — but rendered with pure Astro components instead of React islands. Includes:
+
+ - The **`@tinacms/astro` package's `TinaMarkdown`** — a vanilla Astro rich-text renderer that walks the Plate AST Tina returns, dispatches custom MDX components (NewsletterSignup, BlockQuote, DateTime, code blocks) by name to authored Astro components — the same `components` map shape as `TinaMarkdown` from `tinacms/dist/rich-text`, but emitting Astro markup
+ - An island-refresh pattern: one dynamic endpoint at `src/pages/tina-island/[name].ts` backed by a registry in `src/lib/islands.ts`. The endpoint uses Astro's `experimental_AstroContainer` to render the matching component as a fragment-only response. Adding a new editable region is one entry in the registry
+ - Multi-form pages: layout fetches global, route fetches its primary collection, both register independently — admin shows the right form based on which marked element you click
+ - A **`requestWithMetadata()`** helper wrapping every data load so the same code path runs in production (no overlay → real fetch) and inside the editor (overlay → use the bridge payload). Production builds ship zero bridge JS to non-admin visitors
+
+ **Why this matters for the Astro community**
+
+ Astro is the second-most-starred meta-framework on GitHub and grew specifically because authors care about runtime cost. Every previous attempt to integrate a React-based CMS into Astro carried the same caveat: "but you'll need to ship React for editing." That caveat is now gone. The bridge is the smallest piece of JS that can deliver Tina's full editing experience — click to focus, live preview as you type, click-to-edit overlays — to a framework whose audience explicitly didn't sign up for React.
+
+ **Known content-shape note**
+
+ For nested MDX components in rich-text bodies (e.g. `` inside a post's `_body`) to render via the Astro renderer instead of as raw HTML, the content needs to be authored through the Tina editor — which inserts them as MDX templates that Tina parses into `mdxJsxFlowElement` nodes. Hand-authored `` syntax in the markdown source is currently parsed as `html` by Tina's MDX layer; same behaviour as the React renderer. Worth flagging up-front for anyone migrating existing markdown content.
+
+ **Soft-navigation support: `refreshForms()`**
+
+ `init()` scans `[data-tina-form]` elements once on first load and captures the resulting set in closure. Sites using Astro's `` (or any view-transitions setup that swaps the DOM without a full reload) would post the first page's forms to the admin and never refresh them — navigating between docs inside the editor iframe left the sidebar showing the previous page's form.
+
+ `refreshForms()` re-scans the live DOM, diffs against the previously-mounted set, and posts `close` for forms that disappeared and `open` (with the same retry-until-acked behaviour as `init`) for forms that appeared. The one-time global listeners — `click` capture, the `updateData` ack handler, the `beforeunload` close — stay bound across refreshes, so calling it on every navigation is cheap and idempotent. The Astro integration wires it to `astro:page-load` automatically.
+
+ **Sticky edit-mode**
+
+ A `__tina_edit` session cookie (SameSite=Strict, gated on `Sec-Fetch-Dest: iframe`) keeps the iframe in edit mode across in-iframe link clicks — without it, clicking a link inside the preview drops the `/admin/` Referer and the next request falls out of edit mode. Top-level visitors never get edit mode because the dest check fails before the cookie is consulted, so production HTML is unaffected.
+
+ **Out of scope (follow-ups)**
+
+ - Hugo / Eleventy adapters using the same bridge — the contract is framework-free, just needs an integration guide
+ - TinaCloud overlay channel — not needed; the stateless POST protocol works against any backend
+
+- [#6765](https://github.com/tinacms/tinacms/pull/6765) [`9e7eba9`](https://github.com/tinacms/tinacms/commit/9e7eba9f290c935cd56569421de88b5adfac65d8) Thanks [@kulesy](https://github.com/kulesy)! - Forward the editor's current branch to the TinaCloud assets-api on every cloud media call, and fix staging URL handling for multi-segment branches
+
+ `TinaMediaStore` now appends `?branch=` to its `upload_url`, `list`, and `delete` requests so that — once the assets-api opts an app into branch-aware media — uploads, listings, and deletions are scoped to the branch the editor is on, instead of always hitting the production branch. The branch is read from `Client.branch` (already URL-encoded) and decoded then re-encoded at the use site to avoid double-encoding.
+
+ The query parameter is ignored by assets-api versions that do not parse it, so this change is safe to deploy ahead of the server-side rollout. Local mode is unaffected.
+
+ `@tinacms/graphql`'s media URL resolver now formats staging URLs as `/__staging//__file/` instead of `/__staging//`. The previous form broke for branches containing `/` (e.g. `feat/my-branch`) because CloudFront decodes paths before downstream components see them, so the S3 write key (with a literal `%2F`) wouldn't match the decoded read path. The `__file` delimiter lets the branch contribute its natural `/` segments while still marking where the file path begins.
+
+ Note: staging URLs produced by `@tinacms/graphql@2.3.0`–`2.3.1` use the old format and will not round-trip through this version's `resolveMediaCloudToRelative`. Branch-aware media is gated server-side and has not been enabled for any tenant yet, so no persisted data is expected to be affected — but if you turned it on for testing, regenerate the affected field values from the editor after upgrading.
+
+ After a successful cloud upload `TinaMediaStore.persist()` now resolves its return value from the assets-api `list` endpoint instead of constructing each `Media.src` locally — the server is the source of truth for the canonical URL (including the staging-branch path and per-stage CDN host). The `MediaStore.persist()` contract is preserved, so the returned items still flow through the media manager and the image-field drop handler.
+
+ Also reserves an optional `rename?(from, to)` hook on the `MediaStore` interface as a future extension point — no implementation yet.
+
+### Patch Changes
+
+- [#6694](https://github.com/tinacms/tinacms/pull/6694) [`723632b`](https://github.com/tinacms/tinacms/commit/723632b050b1e9502c46215fd6e8e548cc108ac0) Thanks [@alhafoudh](https://github.com/alhafoudh)! - Fix crash in `getFieldGroup` when editing deeply nested rich-text fields (3+ levels) with templates. The method used `findIndex` which always searched from the start of the path array, causing it to resolve the wrong "children"/"props" segments on recursive calls. Replaced with `indexOf` searching from the current position, and added a null guard for graceful fallback on malformed content.
+
+- Updated dependencies [[`95758a0`](https://github.com/tinacms/tinacms/commit/95758a0ad31ec96aa652f247211a769e82a37cbb)]:
+ - @tinacms/bridge@0.2.0
+ - @tinacms/search@1.2.14
+
## 3.7.6
### Patch Changes
diff --git a/packages/tinacms/package.json b/packages/tinacms/package.json
index fa83f9b00d..d7e72bdd0a 100644
--- a/packages/tinacms/package.json
+++ b/packages/tinacms/package.json
@@ -2,7 +2,7 @@
"name": "tinacms",
"type": "module",
"typings": "dist/index.d.ts",
- "version": "3.7.6",
+ "version": "3.8.0",
"main": "dist/index.js",
"module": "./dist/index.js",
"exports": {