Nibble

0.13.0

What changed in Nibble 0.13.0.

Every release, taken from CHANGELOG.md

← Every release

Nibble 0.13.0

What’s new

  • A folder of Markdown is served without a database copy of it. Declare a collection with files: docs, give it a view, and the folder is the collection: its shape is the address, a folder’s own index.md answers for the folder, and the folders are the navigation tree. Nothing is synced, nothing is migrated, and there is no step to remember on a deploy.
    • Each page declares an id: in its frontmatter, which is what links to it hold. Moving or renaming a file changes its address and keeps its identity; editing it changes neither.
    • Resolving one of these addresses is a lookup in memory rather than a query, so it is quicker than the rows it replaces. In development a file watcher rebuilds the index as you edit.
  • An image in a page is offered at the widths a browser can choose between. Whether it was written beside the page or uploaded in the control panel, it renders with a srcset — so a phone stops fetching a screenshot meant for a desktop. sizes defaults to the full viewport, which is what a browser assumes anyway, and a theme narrows it in CSS where it knows the layout.
  • An image beside a page is published, not uploaded. Write it next to the page, reference it as ![alt](diagram.png), and it is published under a digested name at /nibble-assets/… and served as a static file — cacheable forever, changing address only when the image itself changes. There is no upload step and nothing to keep in step. Only images are published, and a published file whose original has gone is removed.
  • bin/rails nibble:build checks everything derived from files and generates what a build needs. It opens no database, so it runs in CI and in the image build — a page with no id:, unreadable frontmatter, a field no blueprint has, two pages claiming one id, or a page under a folder with no index.md all fail there rather than in a container. It checks the schema too — everything nibble:check does that needs no database.

Changed

  • How a site builds and deploys is generated at install, not shipped. Dockerfile, .dockerignore, .kamal/secrets and a CLAUDE.md stub are written once, alongside config/deploy.yml, and belong to the site from then on. They used to be Nibble’s, which meant an upgrade merged over anything a site changed.

  • NIBBLE-ARCHITECTURE.md describes the CMS to whoever reads it, an agent included: where the code lives, how a request becomes a page, what a site owns. It arrives with each release; the CLAUDE.md beside it is the site’s and says so.

  • There is no staging environment. Whether a site has one, what it is called and how it is protected are the site’s to decide, so Nibble ships neither the environment, its deploy file, nor the basic auth that guarded it.

  • asset_path covers the whole of public/, so images published from content/ bridge a deploy the way Vite’s bundles already did.

  • files: replaces source: on a collection, and a collection written as files no longer has rows: no drafts, revisions, workflow, trash, content migrations or recorded redirects, none of which mean anything for a page whose truth is a file in git. Deleting the file is the whole of deleting the page.

  • nibble:content:markdown is gone, and with it the sync on the boot path. bin/docker-entrypoint runs nibble:upgrade and nothing else, so a folder of files can no longer stop a container starting.

  • A record cannot be saved onto an address a file already holds; it is refused where the save is made.

  • A theme no longer ships content. A theme is layouts, views, components, styles and schema; content is a site’s own. The example pages and posts a new install offers are Nibble’s now, not the starter theme’s, and the question nibble:install asks is unchanged.

  • nibble:content:import takes the directory to import. It used to default to the active theme’s content/, which is the assumption above in another form. A site keeps its package wherever it likes and names it.

What’s fixed

  • An image in Markdown is served at the field’s preset. image_preset was declared on the field, shown when editing it, and never reached the resolver — so an upload was rendered at whatever size it arrived at.

  • A page written as a file serves its words again. The body of a Markdown file reached the theme as an empty string, so every file-backed page rendered its title and nothing else. It fills the field the blueprint writes as Markdown.

  • A command given a path that is not a content package says so in one line, instead of printing a Ruby backtrace. Every nibble:* command now fails the same way.

Upgrade

  • This release deletes files it used to ship and you now own. Dockerfile, .dockerignore, .kamal/secrets, .kamal/hooks/ and CLAUDE.md were ours and are now generated once at install, so the merge removes them from your checkout — without a conflict, because you had not changed them. A hook you wrote yourself goes with them, so check .kamal/hooks/ before merging and keep a copy of anything real. Afterwards, bin/rails nibble:install --only=deploy writes the deploy files back and bin/rails nibble:install writes CLAUDE.md.
  • Your config/deploy.yml is left alone, so it keeps asset_path: /rails/public/vite. Images published from content/ live under public/ too now, so widen it to /rails/public or a changed image’s old address disappears mid-deploy.
  • A collection written as files needs files: where it said source:, and every page needs an id: in its frontmatter. bin/rails nibble:build names every file that is missing one.