Nibble

0.22.0

A public form can total a quote, open and close itself, catch spam and notify the right people, and its submissions are handled in the Control Plane like any other work.

Every release, taken from CHANGELOG.md

← Every release

Nibble 0.22.0

Security

Forms that calculate, hold and report. A public form can total a quote, open and close itself, catch spam and notify the right people, and its submissions are handled in the Control Plane like any other work.

This release is mostly forms and fields, and a plainer Control Plane. Four database migrations are added; they only add tables and columns, so they can be rolled back, but the snapshot taken before an upgrade is the surest way back.

What’s new

  • Public forms can be handled, not just stored. Each submission is New or Handled, can be marked unread, and carries internal notes, and the list takes bulk actions. A person’s submissions can be exported or erased by email address, exports and deletes are recorded in the audit log, and the submissions of a form removed from the schema are purged after the retention period.
  • Form notifications need no code. notify entries merge {field.x} and {global.set.field} into the subject and message, take recipients from a field or a global (to: {field.email}), and are routed with if:. Each email is recorded on the submission, and Send notifications again resends them.
  • A form can open, close and cap itself. limits: takes opens_at, closes_at, max_submissions and unique (a value accepted once, such as a ticket code), with a closed_message shown when it is closed. A form’s submit_label replaces the button text.
  • More ways to catch spam. spam.min_seconds treats a submission sent too soon after the form was shown as spam, and spam.max_links catches text full of links. The rate limit now counts only accepted submissions, so refused posts no longer lock a real visitor out, and the refusal says how long to wait.
  • A form field can be encrypted. encrypt: true stores the value encrypted, bound to its submission and field. Staff, exports, emails, deliveries and per-person search still read it.
  • Multi-step forms, saved forms and calculations. steps: true turns sections into checked steps. resume: true adds Save and finish later, with a link emailed to the visitor. A calculation field is worked out on the server when the form is sent, with a live figure in the theme through calculated().
  • Forms speak the visitor’s language. A multilingual site translates a form’s text, Nibble’s own form messages and the visitor’s confirmation email.
  • Hidden and prefilled form fields. A hidden field is filled from a fixed value or from the page’s query string with prefill.
  • Themes get what they need to draw a form. A form field arrives with its fieldtype settings, its resolved default and its conditions as rules to evaluate. useNibbleForm applies conditions, leaves out fields hidden by one, refills typed values after a refused post even without JavaScript, and gives ids, aria-describedby, an error list and focus targets. Crumbs’ contact form draws every public fieldtype accessibly, with steps, calculations and the closed message.
  • Rich text can link to a global. Choose a global field in the link dialog, or write [Apply](global:site.apply_url) through the API, and the link follows the global’s address when the text renders.
  • Content migrations work on globals. rename_field and set_default take a global in every locale, and the new move_field and remove_field carry values to another global or drop a field that left the schema, so nothing is stranded.
  • New fieldtypes. decimal, range, rating, time, color and button_group work in blueprints and public forms, with Control Plane controls and crumbs inputs. note shows Markdown between a form’s fields and stores nothing. Decimal, range and rating can be used in calculations.
  • Date-times can have a time zone. time_zone: Europe/London (or site) reads the time as typed in that zone, stores the moment in UTC and shows it in that zone. A time the clocks skip is refused, and a repeated one takes its first occurrence.
  • Text can be validated by what it holds. input_type: email, url or tel checks the value on save, in blueprints and on public forms. A toggle’s inline_label can hold Markdown links, for a consent checkbox.
  • A field’s required and read_only can take a condition. The required marker shows only while the condition passes, and a hidden field keeps its stored value.
  • Read-only means read-only. A value sent for a read_only field is kept as stored whoever saves, rows in grids and replicators by their id. Entry, term, global and asset writes through the API answer with an ignored list of the fields whose value was kept.
  • Apps see fuller field schemas. The JSON Schema an app writes against describes length, format, range, step, option labels, read-only state, conditions and when a field is required.
  • list_form_submissions takes since and q, searching as the Control Plane listing does.
  • site/types.d.ts types each form’s values, and a view’s form prop uses them, so values.topic is typed. useNibbleForm takes that type; an untyped call is unchanged.
  • computed:today is a built-in default, the current date in the site’s time zone.
  • Blocks are easier to work with. Undo a removed block or grid row from the notice that appears, move blocks and rows up and down from their menu, copy a block and paste it into any field with the same set, and use collapse: accordion to keep one block open at a time. Grids and blocks have a Full screen action.
  • Rich text options now do what the reference says. placeholder, character_limit (with a counter), toolbar_mode, smart_typography, input and paste rules, link_noopener, link_noreferrer, link_collections (the link dialog’s Page tab lists routed collections), always_show_set_button, previews and inline_hard_breaks.
  • Pickers have modes and can create. entries and terms fields take mode: auto | default | select | typeahead | stack; auto, the default, is a dropdown for 25 or fewer choices. People allowed to create the item see Create a new … in the picker. nibble:check refuses an unknown mode.
  • Duplicate an entry from the editor or the listing. A field’s duplicate option decides what is copied; slug and unique fields are left out by default.
  • Entry listings sort and filter by field. Field columns sort, and listed choice, toggle and date fields add filters. sortable and filterable turn them off or on per field.
  • Documentation covers more of the site. A theming guide to forms, pages on what a view receives and on styling and the page shell, a Reference section listing every fieldtype option, validation rule and condition, and the forms guide split into declaring, delivery, spam and privacy, translation and a key reference. Tests fail when the code gains an option the pages miss.
  • Guides for the people who run a site. Webhooks, the Utilities screens, every nibble:check message, every environment variable and task, the page cache and CDN, sign-in lockout and recovery, and administering from the Control Plane without a terminal.
  • bin/rails nibble:dev:setup asks which AI tools to set up — Claude Code, Codex or Cursor, those found on this computer chosen by default — and writes only into the site’s folder. --tools claude,codex answers without asking. Codex gets a project .codex/config.toml and the skills, like Claude Code.
  • The Connected apps screen shows nibble connect <site>, which sets up Claude Code, Codex and Cursor in the folder it runs in, with the site’s MCP server and skill. It needs the nibble command 0.3.0 or later.
  • Apps are told more about each field. A field’s description says when it is shown, required or read-only, and the bundled schema, theming and extending skills cover the new fieldtypes, forms and reference pages.
  • Guides for editors, and routes in. Settings, categories and tags, the trash, your account and two-factor sign-in, and form submissions, with start-here routes by audience and every guide listed on the docs home.

What’s fixed

  • Crumbs ships a fallback default view, and nibble:check warns when a collection’s entries resolve to a view the theme lacks. A set without a view is a warning, since its blocks render nothing.
  • The 500 page gets the site’s shared props, falling back to empty ones if loading them fails.
  • Saving changes to a live entry no longer warns that someone published a newer version. The warning now appears only when the entry really was published after those changes were started.
  • Publish works on a new entry, creating and publishing it in one click, and a new entry in a collection that needs review offers Submit for review.
  • Apps are no longer told a field hidden by its condition isn’t saved; it keeps what’s sent, and only its rules are skipped.
  • The Backups screen links to the restore section of the backups guide.
  • The health check asks the page renderer on the configured port.
  • nibble:generate:form writes required: true.
  • Form emails show every fieldtype’s value again. Only a fieldtype with its own display formats a submitted value.
  • A form field’s messages override the step error, and a calculation’s posted value is never kept in a saved form.

Changed

  • Plainer wording in the Control Plane. Page-builder sets are blocks, globals are Site settings, pending edits to a live page are a Draft, a taxonomy shows its own name, and link targets are filtered by Type. Statuses show their label (In review, not the stored key), error summaries name a field once by its label, and messages say what to do next in plain words.
  • History, the audit log and approvals say what happened. They show field labels, readable values, titles and verbs, and an approval says what it does and changes. Signed-in devices have names.
  • The Control Plane formats numbers and dates in the site’s locale, the default one in config/nibble.yml. When that locale names only a language, such as en, the browser’s region for it decides the style, so an Australian browser sees day/month/year and an American one month/day/year; a site that declares no locale follows the browser. Until now every Control Plane wrote Australian dates.
  • Default validation messages name the field once: “Your name is required.” rather than “The Your name field is required.”. Their keys are unchanged, so a site’s own messages: and translations still apply.
  • Date fields are compact unless full_width: true, or the date-time has a time_zone.
  • Relationship fields with 25 or fewer choices show a dropdown and offer Create a new ….
  • nibble:check reports more: records holding a switched-off required toggle, saved text failing an email, URL or phone check, a picker mode it doesn’t know, and a link_collections handle that names no collection.

Security

  • CSV exports can’t run formulas. A cell that starts like a spreadsheet formula is written as text, in entry, term, submission and per-person exports.
  • Rich text links to other sites carry rel="noopener" by default.
  • A resume link’s token stays out of the request log, and the personal data export of saved forms leaves it out.

Upgrade:

  1. A required toggle must be switched on, everywhere. required: true on a toggle now means it has to be on: entries holding one switched off can’t be published, and terms and globals can’t be saved. bin/rails nibble:check lists them; fix them, or drop required if “off” is a valid answer.
  2. CSV exports put ' before cells that look like formulas. A script that parses an export sees '=SUM(A1) where it saw =SUM(A1); strip a leading ' if you read the files.
  3. Writes to a read_only field are now ignored from the Control Plane, API tokens and MCP, and likewise for visibility: read_only. An integration that sets such a field must use an import, a content migration or a scheduled task, which are exempt, or you remove read_only from the field.
  4. Email, URL and phone text fields are checked on save. A saved record whose value fails can’t be saved until it is fixed; bin/rails nibble:check lists them. To check a phone number against your own pattern, use validate: regex:, which replaces the phone check.
  5. A rich text field with character_limit refuses longer text on save. Content already over a newly set limit can’t be saved until it is shortened.
  6. to: {global…} on a form notification lets whoever edits that global choose where submissions go. Keep such a global to people you’d trust with the inbox.
  7. A custom theme must post _nibble_t (a hidden input holding form.timestamp) when a form sets spam.min_seconds, or every submission is caught as spam. useNibbleForm and crumbs already do.
  8. limits.unique covers only submissions accepted after it’s added. Duplicates already stored are left alone.
  9. Rich text links to other sites now get rel="noopener". Set link_noopener: false on the field to keep the old output.
  10. Editors will notice: date fields are compact unless full_width: true or zoned; grids and blocks have a Full screen action; relationship fields with 25 or fewer choices show a dropdown and offer Create a new … to people allowed to; entry listings gain filters for listed choice, toggle and date fields; and the new names (blocks, Site settings, Draft, Type).
  11. Four new database migrations add form handling (handled state, notes, retention), encrypted submission fields, submission keys for limits.unique and saved forms (form_partials). bin/rails nibble:upgrade runs them. They only add tables and columns, so bin/rails db:rollback can undo them, which drops what they hold.
  12. site/types.d.ts gains form value types. bin/rails nibble:upgrade regenerates it; commit the new file.
  13. AI apps you connected with nibble 0.2.0 were set up for your whole account, so a site’s tools and guide load in every project. Update nibble to 0.3.0, run nibble disconnect <site> once, then nibble connect <site> in the folder where you work on that site. If you ran codex mcp add nibble-dev, remove it with codex mcp remove nibble-dev and run bin/rails nibble:dev:setup again to choose your tools.