Skip to content

cimisy@2.0.0

Choose a tag to compare

@github-actions github-actions released this 13 Jul 18:10
78fd1f2

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/seo export adds per-entry SEO fields, a one-call generateMetadata helper, 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 flat text: string. Old { text } payloads from in-flight 1.x clients are transparently upgraded on write; on-disk .mdx files need no migration — content is rebuilt from the file on every read. If you render blocks yourself via cimisy/render, Paragraph/Callout component props changed from { text } to { content: InlineNode[] } — pass your own component in the components map if you need the old shape, or use the updated default.

    Media upload. New fields.image({ directory }) uploads go through POST /api/cimisy/media, land in the configured directory, and commit through the same draft-vs-direct-publish path as an entry save. Custom StorageAdapter implementations gain two new optional capabilities — readRaw (raw bytes, for serving uploaded images back through the admin UI) and listChangeRequests (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.writes items gained an optional encoding: "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 the publish role 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 previewPath in 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() and section() config primitives: a page({ label, path?, route?, sections }) groups the content that renders on one route — section() for static one-file content, collection() (whose path is 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 as reader.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 slug singleton, e.g. cimisy/alice/settings/singleton). All-frontmatter singletons store as plain YAML (no --- fences); schemas with a fields.blocks() body store as MDX — derived automatically, overridable via format, fail-closed on mismatch. singleton() now requires a label and accepts previewPath.

    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 — canonical is schema-refined to https:// or site-relative (no javascript: by construction), ogImage gets the same no-.. refine as fields.image. createMetadata({ seo, fallback, defaults, path }) turns it into a Next.js Metadata object with entry-value > entry-fallback > site-default precedence (title template, canonical resolution against siteUrl, Open Graph, Twitter, robots from 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 an overrides hook for CMS-editable extras. The export is separate from cimisy/next so <JsonLd> works in client components and none of it pulls server-only.

    Upgrade notes (breaking)

    • Flat configs (no pages) keep working unchanged: same keys, same on-disk paths, same RBAC rules, same reader.collections.* — the hierarchy is additive. Existing singleton() declarations need a label added (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 be team, drafts, pages, or new — they become admin URLs and git-ref components. A camelCase collection key must be renamed.
    • AdminManifest reshaped: collections: CollectionManifest[]tree: ManifestTreeNode[] + byKey: Record<string, EntityManifest>; entity manifests carry kind: "collection" | "singleton" and key (replacing name). It also gained draftsSupported: boolean, and FieldManifest gained an optional directory (image/seo fields) and richTextProp/updated blockTypes[].richTextProp.
    • Media upload body field collectionNametargetKey; the drafts API rows renamed collectionNamecontentKey and gained kind: "collection" | "singleton".
    • createCimisyHandler/createReader/buildAdminManifest now type their parameter as ResolvedCimisyConfig (what config() returns) — pass your config through config(), which you already do if you followed the docs.
    • Internal stores (collection-store, new singleton-store) consume the normalized NormalizedCollection/NormalizedSingleton shapes from config() — 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-root styles, 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 from react/admin/editor/*), never loaded by the cimisy/render or server-side exports.