cimisy@2.0.0
Major Changes
-
85daeda: The editor experience gets real, and content gets a shape. A rich Tiptap-based block editor, media upload, an in-CMS drafts/review screen, and a live preview pane close the biggest gaps in day-to-day editing; a new page/section/collection hierarchy plus finally-wired-up singletons let content be organized the way real sites actually are; and a new
cimisy/seoexport adds per-entry SEO fields, a one-callgenerateMetadatahelper, and hardened JSON-LD builders.The editor experience
Rich inline text. Paragraph and callout blocks now carry
content: InlineNode[](bold/italic/inline-code/links) instead of a flattext: string. Old{ text }payloads from in-flight 1.x clients are transparently upgraded on write; on-disk.mdxfiles need no migration — content is rebuilt from the file on every read. If you render blocks yourself viacimisy/render,Paragraph/Calloutcomponent props changed from{ text }to{ content: InlineNode[] }— pass your own component in thecomponentsmap if you need the old shape, or use the updated default.Media upload. New
fields.image({ directory })uploads go throughPOST /api/cimisy/media, land in the configured directory, and commit through the same draft-vs-direct-publish path as an entry save. CustomStorageAdapterimplementations gain two new optional capabilities —readRaw(raw bytes, for serving uploaded images back through the admin UI) andlistChangeRequests(for the Drafts screen below); implement both to get full media/drafts support, or omit them to degrade gracefully (uploads disabled, Drafts screen empty).ChangeRequest.writesitems gained an optionalencoding: "utf-8" | "base64"(defaults to"utf-8", matching all prior behavior) — a custom adapter must handle"base64"to support uploads.Drafts screen. A new "Drafts" nav item (shown when the adapter reports PR support) lists open drafts — your own, plus anyone's you can
publish. Reviewers get a live preview of the draft (previewing someone else's in-progress branch, not just your own) and an "Approve & merge" button. This is the first release where thepublishrole action (declared since 1.1.0's RBAC work but never enforced) actually gates something: merging.Live preview pane. The entry editor has a "Show preview" toggle rendering the collection's
previewPathin a side-by-side iframe, refreshed on save. It shows the last saved state, not unsaved edits in progress — save to refresh it.Content hierarchy and singletons
Page/section/collection hierarchy. New
page()andsection()config primitives: apage({ label, path?, route?, sections })groups the content that renders on one route —section()for static one-file content,collection()(whosepathis now optional inside a page — derived as<pagePath>/<key>/*.mdx) for repeating entries. Content lives in nested directories mirroring the hierarchy (e.g.content/pages/home/hero.yaml,content/pages/home/testimonials/*.mdx), the admin home screen renders the tree, and the reader exposes it asreader.pages.<page>.<section>. Every content target gets a flat dot-joined key (posts,home.hero) used in admin URLs, API routes, and draft-branch names — draft branches from a flat, pre-hierarchy config still parse and merge. RBAC is unchanged: rules still match real repo paths, so{ path: "content/pages/home/**", actions: [...] }scopes a role to one page's subtree.Singletons, wired end-to-end.
singleton()existed as a config type since 1.0 but nothing consumed it. It now works everywhere:GET/PUT /api/cimisy/singletons/<key>(with baseVersion optimistic concurrency and per-path RBAC),reader.singletons.<key>.get()(null until first saved — declaring one in config is all it takes to make it editable), an admin form with the same publish/draft/conflict/history/preview affordances as entries, and draft/PR parity (singleton drafts use the reserved branch slugsingleton, e.g.cimisy/alice/settings/singleton). All-frontmatter singletons store as plain YAML (no---fences); schemas with afields.blocks()body store as MDX — derived automatically, overridable viaformat, fail-closed on mismatch.singleton()now requires alabeland acceptspreviewPath.SEO (
cimisy/seo, new export)fields.seo({ imageDirectory? })adds a collapsed per-entry SEO panel (title/description with character-count hints, canonical, og:image via the standard media pipeline, noindex) stored as one nested frontmatter mapping —canonicalis schema-refined tohttps://or site-relative (nojavascript:by construction),ogImagegets the same no-..refine asfields.image.createMetadata({ seo, fallback, defaults, path })turns it into a Next.jsMetadataobject with entry-value > entry-fallback > site-default precedence (title template, canonical resolution againstsiteUrl, Open Graph, Twitter,robotsfrom noindex).seoSettingsFields()+seoDefaultsFromSettings()establish the conventional site-settings singleton those defaults come from.articleJsonLd/breadcrumbListJsonLd/organizationJsonLd/webSiteJsonLd+<JsonLd>render schema.org structured data with</script>-breakout-safe serialization (CMS-edited strings flow into it) and anoverrideshook for CMS-editable extras. The export is separate fromcimisy/nextso<JsonLd>works in client components and none of it pullsserver-only.Upgrade notes (breaking)
- Flat configs (no
pages) keep working unchanged: same keys, same on-disk paths, same RBAC rules, samereader.collections.*— the hierarchy is additive. Existingsingleton()declarations need alabeladded (and now actually do something). - Content keys (collection/singleton/page names in config) must now be lowercase kebab-case (
[a-z0-9-], dot-joined segments internally) and may not beteam,drafts,pages, ornew— they become admin URLs and git-ref components. A camelCase collection key must be renamed. AdminManifestreshaped:collections: CollectionManifest[]→tree: ManifestTreeNode[]+byKey: Record<string, EntityManifest>; entity manifests carrykind: "collection" | "singleton"andkey(replacingname). It also gaineddraftsSupported: boolean, andFieldManifestgained an optionaldirectory(image/seo fields) andrichTextProp/updatedblockTypes[].richTextProp.- Media upload body field
collectionName→targetKey; the drafts API rows renamedcollectionName→contentKeyand gainedkind: "collection" | "singleton". createCimisyHandler/createReader/buildAdminManifestnow type their parameter asResolvedCimisyConfig(whatconfig()returns) — pass your config throughconfig(), which you already do if you followed the docs.- Internal stores (
collection-store, newsingleton-store) consume the normalizedNormalizedCollection/NormalizedSingletonshapes fromconfig()— only relevant if you imported those internals directly. - The admin UI's root container widened from 760px to 960px to fit the live preview pane's side-by-side layout — if you've overridden
.cimisy-rootstyles, re-check the width assumption. - New dependencies:
@tiptap/core,@tiptap/react,@tiptap/pm,@tiptap/starter-kit,@tiptap/extension-link,@tiptap/suggestion— all admin-UI-only (imported exclusively fromreact/admin/editor/*), never loaded by thecimisy/renderor server-side exports.
- Flat configs (no