Releases: samreshan/cimisy
Release list
cimisy@2.2.0
Minor Changes
-
8211e9d:
cimisy scan --fullnow also detects static, non-repeating content — headings, rich-text paragraphs, images, and standalone links — grouped into sections at semantic HTML5 boundary tags (<section>,<header>,<footer>, etc.), with a fallback region per component when no boundary tag is present.cimisy importpresents these alongside collection candidates in one combined picker; selecting one writes a real.yaml/.mdxentry viawriteSingleton, splices asingleton({...})orpage({...}){ sections }intocimisy.config.ts, and rewrites the source JSX to read fromreader.singletons.<key>/reader.pages.<pageKey>.<sectionKey>— the same git-branch-review trust model as collection imports (clean working tree or--allow-dirty, a dedicatedcimisy/import-<timestamp>branch, no runtime RBAC involved).Detection is conservative by design, mirroring the existing array scanner: content mixed with a non-literal expression (
Welcome to {siteName},{t("key")}), conditionally rendered content ({cond && <X/>}, ternaries), and ESM-imported images (import hero from "./hero.png") are reported as detected-but-not-eligible rather than guessed at. Only a narrow tag/prop allowlist is ever read as content (headings,p/blockquoterich text,figcaption,span,img/Image,a/Link) —className,data-*,aria-*, event handlers, and every other prop are never touched.A component rendered from a single route becomes a page-scoped
section(); the same component rendered from multiple routes (e.g. a sharedFooter) becomes a top-levelsingleton()instead.The admin content tree now separates a page's static content from its collections into two labeled groups instead of one flat list.
No breaking changes —
cimisy scan/cimisy importwithout--fullbehave exactly as before.
cimisy@2.1.2
Minor Changes
-
Admin UI retheme + dark mode. The admin editor now uses Cimisy Blue and the same-hue "ink" neutral ramp (
brand/design-system) in place of the original bone/charcoal/purple palette — the rethemebrand/BRAND.mdhad deliberately deferred — and every surface has a matching dark theme, auto-detected from the OS and toggleable from a new button in the top nav (persisted tolocalStorage, applied before first paint so there's no flash).Alongside the retheme, the entry editor picked up a few layout changes to read as a calmer, less chrome-heavy writing surface:
- A
cimisy / collection / entrybreadcrumb trail replaces the old "← back" link + heading. - The first plain-text field in a collection's schema now renders as a large, borderless hero title instead of a boxed input — the field editors reach for as "the title" in practice, not
slugField(which names whatever the URL slug is derived from, often a separate auto-generated field). - The block editor shows its "type to insert a block" hint inline, in whichever block is currently empty (via the new
@tiptap/extension-placeholderdependency, admin-UI-only like the rest of the Tiptap family), instead of as static text below the editor. The slash-command menu also gained a small icon per block kind. - The preview pane's header is now a "Draft mode preview" label with a status pill, and a sticky action bar at the bottom of the form surfaces the draft's branch and PR link next to Save — replacing the inline banners that used to carry that after a save.
Also fixes a bug found while verifying the above:
EntryForm/SingletonFormcould get stuck on "Loading…" forever if the initial fetch failed validation (e.g. a required field left empty) instead of surfacing the error.No public API changed —
AdminApp's props, the manifest shape, and every route/handler are untouched. If you've overridden.cimisy-*class styles from the outside, re-check them against the new token names inadmin-theme.ts(the CSS custom properties were renamed, e.g.--cimisy-charcoal→--cimisy-text,--cimisy-purple→--cimisy-accent). - A
cimisy@2.0.2
Patch Changes
- adb2d28: Admin content tree: top-level collections and singletons with a
previewPathare now grouped by route, the same way an explicitpage({ route })block already is — apreviewPath: "/blog/:slug"collection or apreviewPath: "/about"singleton declared outside anypage()now renders under a labeled route group instead of a bare, context-free card. If the derived route matches an existingpage({ route })'s route, the item merges into that page's group instead of creating a duplicate. Entities with nopreviewPathare unaffected. Purely a grouping change inbuildAdminManifest— no config shape changes, nothing to migrate.
cimisy@2.0.1
Patch Changes
-
0698df8: Security hardening pass: fixes a TOCTOU race in the local storage adapter, closes a media-upload gap, adds login rate limiting, and enforces a minimum session-secret length.
Breaking for existing deployments — action required before upgrading:
githubSource({ sessionSecret })now throwsWEAK_SESSION_SECRETat construction time ifsessionSecretis shorter than 32 characters (or missing). This is the key that signs the admin session cookie — a short one is brute-forceable. Generate a proper one withopenssl rand -base64 32and update your deployment'sCIMISY_SESSION_SECRET(or equivalent) before upgrading, or the app will fail to start.Other fixes:
- TOCTOU race in
LocalStorageAdapter.list()(CodeQL CWE-367): replaced thestat()-then-readFile()pattern, where a file could change or disappear between the two calls, with a directreadFile()per entry that skipsENOENT/EISDIR— the same check-free idiom already used byread(). - Unvalidated
targetKeyon media upload:POST /api/cimisy/medianow 404s iftargetKeydoesn't match a declared collection or singleton, closing a gap where a writer could mint draft branches/PRs for content keys that don't exist in config. /auth/loginnow rate-limited, the same IP-keyed limiter already applied to/auth/callback.- Dev/example dependency bumps clearing ~60 Dependabot alerts (none were in the package's published runtime dependencies):
nextto^15.5.16,vitestto^3.2.6, pluspostcss/esbuild/vitepinned viapnpm.overrideswhere transitive resolution still lagged. Thenextpeer range is unchanged (>=14.0.0). The two example apps' route handlers and internal library types were updated for Next 15's Promise-based routeparams, and a couple of internal nav links were switched tonext/linkto satisfyeslint-config-next'sno-html-link-for-pagesrule.
- TOCTOU race in
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
cimisy@1.1.0
Minor Changes
-
d007582: Add admin-managed user roles on top of GitHub auth. GitHub sign-in now creates a pending user record instead of deriving role from live collaborator permission on every request. The first sign-in bootstraps as admin (repo owner in the common case); every person after that starts pending until an existing admin assigns a role from the new Team screen.
Adds a
publisher(direct-publish) role alongsideeditor, amanageUsersaction, and a zero-admin lockout guard on role changes.Upgrade note for existing GitHub-source deployments: the user roster (
.cimisy/users.yaml) starts empty. The next person to sign in bootstraps as admin if their live GitHub collaborator permission maps toadmin; everyone else lands pending until an admin assigns them a role from the Team screen. If the next sign-in isn't a repo admin, sign in as one first to bootstrap access before anyone else logs in.Also refines the admin UI: a persistent top nav (replacing the old corner auth bar), a "waiting for access" screen for pending users, and the new Team screen for managing roles.