@taylordb/cms
v0.3.0
Published
Embeddable in-page TinaCMS editor for TaylorDB apps. Renders a TaylorDB-styled content editor on any page — no /admin route, no iframe.
Readme
@taylordb/cms
Embeddable, in-page TinaCMS editor for TaylorDB apps.
Instead of TinaCMS's default /admin route (a separate SPA that edits your site
through an iframe), @taylordb/cms renders a TaylorDB-styled editing sidebar
directly on the page you're viewing. It mounts a minimal local-mode TinaCMS
runtime and a vendored copy of @tinacms/app's postMessage host, pointed at the
current window — collapsing Tina's cross-iframe handshake into a single
document. Your page's useTina hooks light up as editable forms in place.
It edits your repo content files (markdown / MDX / JSON) through the local
tinacms dev GraphQL server — the standard Tina local workflow, no Tina Cloud.
Install
pnpm add @taylordb/cms tinacms
pnpm add -D @tinacms/clitinacms (and the @tinacms/cli dev server) are peer dependencies — the host
app owns them.
Usage
Render <TaylorCMSDev> (from the lightweight @taylordb/cms/dev entry) once in
your root layout, behind a static dev guard. It's SSR-safe (renders nothing on
the server), lazy-loads the editor runtime + stylesheet on the client, and
captures the page's early useTina registrations until the editor is up. You
supply only your app's Tina pieces via load:
import { TaylorCMSDev } from "@taylordb/cms/dev"
export function CmsEditor() {
if (!import.meta.env.DEV) return null // static → editor tree-shaken from prod
return (
<TaylorCMSDev
load={async () => {
const [config, schema] = await Promise.all([
import("../tina/config"),
import("../tina/__generated__/_graphql.json"),
])
return { config: config.default, schemaJson: schema.default }
}}
/>
)
}Keep the import.meta.env.DEV guard in your code — it's what lets the bundler
drop the editor (and your Tina config/schema) from production builds entirely.
Run the app with the Tina content server alongside your dev server:
// package.json
"scripts": {
"dev": "tinacms dev -c \"vite dev --port 3000\""
}See apps/cms/playground for a complete TanStack
Start example.
Optional: shrink the editor download
tinacms's bundle eagerly pulls in mermaid (~3 MB) and posthog-js, which the
editor only needs conditionally. @taylordb/cms/vite ships a plugin that rewrites
those into lazy imports at build time — cutting ~13% off the editor's eager
download — so you don't need a package patch:
// vite.config.ts
import { tinacmsLazyDeps } from "@taylordb/cms/vite"
export default defineConfig({
plugins: [tinacmsLazyDeps()],
})It's optional and fail-soft: it only touches tinacms/dist/index.js, and if a
future tinacms version changes that file it logs one warning and leaves it
untouched — you lose the size win, never the build. Works with any package
manager and covers both vite dev and vite build. See
docs/api.md.
The same entry also ships inlineJsonModule({ virtualId, file }), which serves
a JSON file as a Vite virtual module — the standard fix for dev servers (Nitro /
TanStack Start) that intercept .json requests, and the recommended way to load
_graphql.json there. See
docs/api.md.
Features
- In-page editing — a TaylorDB-styled sidebar on the page itself, no
/adminiframe. Hover a field → outline → edit → save to your content files. - Non-blocking load — the ~2 MB editor runtime never sits on the page's critical path; it streams in behind a themed loading frame in the sidebar. If the drawer was left closed, only a small "Edit" tab shows and the runtime is prefetched during idle so a click opens instantly. A failed load shows a Retry/Close state instead of a stuck spinner.
- TaylorDB theming — ships TaylorDB's Material Design 3 palette (brand
primary
#5C6AE7) by default; athemeprop reskins the shell and Tina's field widgets, using the same token keys as@taylordb/forms-ui. - Page switcher — an optional
pagesprop lets editors jump between the app's pages; wireonNavigate+currentPathfor SPA (no-remount) navigation. - Global settings — a collection with
ui: { global: true }is surfaced behind a gear button, out of the per-page document list. - Custom field widgets — the host ships none of its own, but a
fieldsprop registers consumer-suppliedui.componentwidgets (e.g.@taylordb/forms-cms's form-builder fields). Seedocs/api.md. - Host-page isolation — the drawer stacks near the maximum z-index (Tina's
own overlays are lifted above it), and scroll gestures inside the editor never
reach the host page's
windowlisteners or chain into page scroll. Seedocs/api.md. - Media library — image fields and a header button open Tina's repo-based
media manager (uploads land in
public/<mediaRoot>/). - TaylorDB commits — every save is announced as a
'taylor-cms:save'CustomEvent onwindow(detail:{ collection, relativePath }); on a TaylorDB dev preview the platform's injected page agent picks it up and commits the change. Without an agent (plain local dev) the event is simply unheard.
Props
<TaylorCMSDev> (the mount you'll normally use):
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| load | () => Promise<{ config, schemaJson, fields? }> | — | Dynamic imports of your tina/config + generated _graphql.json, plus optional custom field widgets. |
| enabled | boolean | dev builds only | Force the editor on/off (e.g. a deployed preview). |
| …rest | — | — | Everything below except config / schemaJson / fields passes through. |
<TaylorCMS> (the underlying editor, for full manual control):
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| config | Tina config | — | The default export of your tina/config.ts. |
| schemaJson | DocumentNode | — | tina/__generated__/_graphql.json. |
| apiUrl | string | http://localhost:4001/graphql | The local tinacms dev GraphQL server. |
| fields | CmsFieldPlugin[] | [] | Custom field widgets, registered by ui.component name. |
| loadingFallback | ReactNode | — | Shown while forms build; <TaylorCMSDev> sets its own. |
| theme | 'light' \| 'dark' \| CmsTheme | 'light' | Reskins the shell and Tina's widgets. |
| pages | CmsPage[] | — | Pages for the drawer's page switcher. |
| currentPath | string | window.location.pathname | The active route (pass a reactive value for SPA nav). |
| onNavigate | (path: string) => void | window.location.assign | How to move between pages; pass your router's navigate for SPA. |
Full reference: docs/api.md.
Docs
docs/setup.md— install, Tina config, collections, media, the dev script, mounting.docs/api.md— props, exports, theme tokens, the@taylordb/cms/viteplugin.llm.txt— agent-facing wiring checklist.src/vendor/README.md— the vendored@tinacms/appbridge: provenance and re-vendor steps.
Status
Early release (0.1.x). The features above — theming, nested-field back
navigation, the page switcher, and the repo-based media library — are complete
and exercised end-to-end by the playground. It's a
local-development editor: it drives the tinacms dev server and writes to
your working tree, so mount it dev-only (see Usage).
