Nibble

Every release of Nibble, newest first.

Taken from the changelog in the repository, so the notes here and the notes in the code cannot drift apart. Each release links to its tag and has a page of its own.

Every release, taken from CHANGELOG.md

One way to bring content in, wherever a page needs it. The queries a view’s sidecar runs now work for layouts, for the page’s own linked records, and for a form’s choices.

One database migration adds a column for the labels a submission was made with; it only adds, so it can be rolled back, but the snapshot taken before an upgrade is the surest way back.

What’s new

  • A layout can query content. layouts/<name>.yml is a sidecar like a view’s, and its props reach every page using the layout, the 404, search and preview pages included, typed as LayoutProps. A layout that shares another’s header takes its queries with extends: default. nibble:check refuses a layout query named like a view’s or like a prop every page has.
  • A page brings its linked records along. page: { include: [loan] } in a view’s or layout’s sidecar gives the page’s relationship fields as whole records, so a view or layout reads the fields of what the page links to.
  • Child entries in a query. include: [children] on a structured collection gives each entry its published children in tree order, enough for a menu of sections and their pages.
  • A form’s choices can come from content. A select, radio, checkboxes or button_group field on a form takes options: { from: entries:<collection> } (or terms:), with value, label, where and sort. Only published entries in the visitor’s language are offered and accepted. parent: narrows one field’s choices by the answer to another, through the tree or a relationship field, and the @nibble form helper hides choices that don’t fit and clears one that stops fitting.
  • Submissions keep their choices readable. Each choice’s label is stored with the submission, so the Control Plane, emails and exports still show it after the option is renamed or removed; an encrypted field’s labels are encrypted too.
  • Generated types follow include. An included field is typed as the record it holds, and children as the collection’s records.

What’s fixed

  • A crop shows wherever the image is used. An image whose field has no preset was served as uploaded, with its uploaded size, so cropping, rotating or flipping it in the media library changed only its preset uses. Every use now shows the edited image at its edited size, and Reset brings the original back everywhere. The crop editor and Download in the Control Plane still work from the original. An animated image can’t be edited, since its uses would show a single frame; one edited before keeps being served as uploaded.

Changed

  • crumbs lists topics in its footer from a layout query, and a post shows its author’s bio.
  • An image that already has edits gets a new address, so a cache holding its original under the old one can’t keep serving it; the old address redirects.

Change what a site already holds, safely. A content migration can replace a shipped value where editors haven’t changed it, and this release fixes the deploy of a site whose form emails a site setting.

It adds no database migrations.

What’s new

  • A content migration can change a value editors may have kept. set_value replaces a stored value on every record and locale where it still equals from, or the keys of grid and replicator rows that match where. A value an editor has changed since, in the record or its draft, is left alone, a second run changes nothing, and to is checked against the field’s own validation before anything runs.

What’s fixed

  • A site whose form emails a global’s address builds again. bin/rails nibble:build checked a notify recipient such as {global.site.contact_email} by asking the database for the global’s columns, and an image build has no database, so bin/kamal deploy stopped with “Could not find table ‘global_sets’”. The check reads the schema alone now, and checks the global against the schema being checked rather than the running site’s.
  • Events keep being delivered after a change is rolled back. A change that published an event and was then rolled back, such as a preview, left its server thread believing a delivery was already on the way, so that thread queued none for its later changes; their webhooks, search updates and cache purges waited for another thread’s delivery or a restart. A rollback clears that mark now.
  • A favicon can be an .ico file. Site settings’ Favicon field offers .ico, but the asset library refused to upload one, so a package setting an .ico favicon imported without it. .ico files upload now, and are served as they are.

Menus that do more, and blocks you add instead of copy. Menus gain headings, fields and kinds of their own, lists that fill themselves and show dates; a theme adds blocks to Nibble’s standard ones; and crumbs, the theme every site starts from, uses all of it.

This release is mostly menus, blocks and the default theme. It adds no database migrations; its content migrations for menus and global sets run only when a site writes them.

What’s new

  • bin/rails nibble:schema:types --without-site writes a theme’s types from Nibble’s schema and the theme’s alone, leaving out site/schema, so a theme you publish can be type-checked as it ships, whatever the site you develop it in changes.
  • Themes get the site’s time zone as site.time_zone, an IANA name such as Australia/Sydney, in every view’s props, so a theme can date a post on the day it happened where the site is rather than in UTC.
  • crumbs uses what themes can do now. It shows dates in the page’s locale and the site’s time zone, asks listing queries for only the fields it draws, sets sizes on its card and hero images, and counts each topic’s articles. Its Main menu draws nested items, as a dropdown that works without JavaScript and an indented list on phones; its footer draws the Footer menu, falling back to Main while that is empty; its header shows the Site logo when one is set. Its 404 page offers the menu, a search box and the latest articles; search results say whether a hit is an article or a page and where a page sits; the embed block plays YouTube and Vimeo videos in privacy-friendly players and links anything else; form fields sit side by side by their width hint; and the fallback taxonomy views show a term’s description.
  • Menus can hold more than links. A navigation can allow headings (headings: true), links to a global’s address field (globals:) and to files (assets: true), and fields of its own on every item (item_fields:), such as a description or an icon; entry_fields: hands a theme chosen fields of the page an item links to. Any item can open in a new tab, be hidden, or show only between two dates, and cached pages change at each date. A menu that declares none of this behaves, stores and types exactly as before.
  • Mega menus, when a navigation asks for them. kinds: declares item kinds of its own, such as a featured card with an image and a button, each saying whether it links and holds children. queries: lets an item list a collection’s published pages — a page’s children, or a term’s entries, sorted, up to 50 — so a new page appears without editing the menu; any change to that collection clears the pages that show the menu, so keep it for small collections.
  • Each menu item tells a theme whether it is the current page (current) or leads to it (in_trail), so themes stop working it out themselves, and useNavigation('main') is typed from the menu’s schema: @site/types gains a <Menu>NavigationItem per menu that narrows on kind. Apps and agents are told each menu’s item shape.
  • The menu editor adds headings, site-setting and file links, Open in a new tab, Hidden, the menu’s own fields and kinds, lists of pages, and show dates; items can be duplicated, collapsed and moved by keyboard as well as dragged between levels, and a refused save marks the item each problem is on.
  • Content packages carry every kind of menu item, naming the pages, terms and files it links to by key. An existing package imports as before.
  • A theme or site adds blocks to Nibble’s standard ones with add_sets under a reference’s config:, and leaves some out with remove_sets, instead of copying the whole list. nibble:check refuses an added block whose handle is taken or a removal of one that isn’t there, and warns when a removed block still holds saved content.
  • Content migrations rename and delete menus and global sets: rename_navigation, delete_navigation, rename_global and delete_global, in every locale and with their permissions. nibble:check warns about a menu stored for a navigation the schema no longer has, and names the migration that clears it.
  • crumbs uses all of it: its header and footer mark the current page and section from the menu, open new-tab items in a new tab and say so to screen readers, and draw headings as dropdown titles and footer columns (crumbs’ Footer menu allows headings and two levels); its pages offer a Latest posts block, added with add_sets.

What’s fixed

  • nibble:check accepts messages.unique on a field the form’s limits.unique names. It said the field had no rule for that message, though a repeated value shows it.
  • crumbs’ contact form takes posts without JavaScript when it is paced. It now sends the _nibble_t hidden input, so a form with spam.min_seconds no longer counts every no-JavaScript post as spam.
  • crumbs opens off-site menu items. It drew them with Inertia’s Link, which can’t leave the site, so a menu item such as GitHub did nothing; it uses a plain link now, as the CTA block does for an off-site link.
  • crumbs’ light or dark choice arrives with the page. It is kept in the nibble_theme cookie, so a returning visitor doesn’t see the other scheme first; a choice an earlier crumbs saved carries over.
  • crumbs loads its fonts from the page head, not an @import that held up the stylesheet, and its footer no longer prints the year, which a cached page could show stale.
  • Renaming a collection keeps who may edit it. rename_collection moves roles’, connected apps’ and Agent access permissions to the new handle, and delete_collection removes them; they stayed on the old handle, so the collection quietly disappeared for every editor who wasn’t a superuser. nibble:check no longer stops an upgrade over a role naming a handle that a pending migration renames.
  • The menu editor offers only the levels a menu allows. It offered one more than max_depth, which the save then refused, and a menu without max_depth is no longer held to three levels.
  • Re-importing a package doesn’t count a menu as changed because a save stores an item’s field differently from how the package spells it.

Changed

  • crumbs’ starter contact form is paced instead of using a CAPTCHA. spam.min_seconds: 3 and spam.max_links: 2 replace captcha: true, so a fresh install takes messages without keys. A page that shows the form is no longer kept in the page cache, since each visit needs a fresh timestamp.

Upgrade:

  1. A site that relied on crumbs’ CAPTCHA keeps it with a form of its own. Copy vendor/nibble/themes/crumbs/schema/forms/contact.yml to site/schema/forms/contact.yml and set spam: { captcha: true }.
  2. A theme that draws a paced form with its own markup adds the timestamp. Add <input v-if="form.timestamp" type="hidden" name="_nibble_t" :value="form.timestamp" />, or posts without JavaScript are counted as spam.
  3. A site that renamed a collection before this release re-ticks that collection’s permissions under Roles, checks its row under Agent access, and reconnects apps that were granted it.

What’s fixed

  • A site builds its assets again. The rich text editor’s character counter and placeholder, and the Control Plane’s calendar, use packages that 0.22.0 didn’t declare, so bin/vite build (and a deploy) could fail with failed to resolve import "@tiptap/extensions". Both are declared now, and a test fails when Nibble’s frontend imports a package it doesn’t declare.

What’s new

  • bin/rails nibble:dev:setup --remove undoes the setup. It takes out the developer tools server, the vendor/nibble/ rule and the skill links, leaves anything else in those files, and the setup chooser now asks again on an answer it can’t read.

Changed

  • A site’s skill doesn’t repeat “nibble” in its name. A site called Nibble gets the skill nibble, one called Nibble Docs gets nibble-docs, and any other site keeps nibble-<site name>. In the folder where you use it, run nibble disconnect <site> and then nibble connect <site> (the nibble command 0.3.4 or later), which replaces the skill under its old name.

What’s fixed

  • The rich text guide names the link dialog’s Site settings tab, where it said Global.

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.

What’s new

  • A field can say what it expects. messages: { regex: "Enter an Australian mobile number" } on a blueprint or form field replaces the default text for that rule, as a string or per locale, on public forms, in the Control Plane and in API errors. nibble:check refuses a message for a rule the field doesn’t have.
  • Number fields on forms reach the theme with min, max and step, and crumbs draws them as number inputs. A public form refuses a value off the step, as the browser does; blueprint fields still treat step as a hint.
  • A submission caught as spam can be rescued. Its values are kept (never its files) for spam.retention_days, 30 by default. The submissions list shows All but spam and a Spam tab with its count, and Not spam stores the submission and sends its emails, deliveries and handler once, as if it had just arrived.

Changed

  • Spam is deleted after spam.retention_days (30 by default, or retention_days if shorter), including spam stored before this release, which holds no values.
  • nibble:check warns when a form’s honeypot has a name autofill may fill in, such as website or email, because a visitor’s browser would then mark a real enquiry as spam.

What’s new

  • A stopped upgrade can be finished. When an upgrade fails after the new release is swapped in, fix what it stopped on and run bin/rails nibble:upgrade --resume: it carries on from installing gems, with the snapshot and the old release it already kept.

What’s fixed

  • An upgrade no longer stops when the new release generates different types. It writes site/types.d.ts again right after the swap, so nibble:check no longer refuses it as out of date. Commit the new file with the upgrade.

    Upgrade: a site whose upgrade to 0.21.2 stopped at “generated types are out of date” finishes it from the site’s root with bin/rails nibble:schema:types, bin/rails nibble:prepare, then bin/rails nibble:upgrade:finish tmp/nibble-<the version you came from>, before taking this release.

  • Pasted Markdown keeps its headings. A # or ##### heading pasted into a rich-text field became a paragraph with Tiptap 3.32; it now becomes the nearest heading the editor allows, h2 or h4.

Changed

  • bin/rails nibble:upgrade refuses while an upgrade is unfinished, and says how to finish it with --resume or go back, instead of starting over and replacing the old release kept as the way back.

Security

  • Fewer packages with known advisories. Vite, Vitest and Tiptap need releases with their fixes, and shadcn-vue, which only scaffolds components and pulled in most of the advisories, is no longer installed. A site’s npm audit goes from 26 findings to 2 moderate ones in @tailwindcss/typography, which only reads Nibble’s own stylesheets at build time.

What’s new

  • A block’s count can have a default. limit: { from: $set.count, default: 3 } (and the same for paginate.per_page) uses the default while the editor’s field is blank, as it is on a block saved before the field existed.

What’s fixed

  • CAPTCHAs are verified on a site that limits outbound.allowed_hosts. The check went through the allowlist, so every submission was refused as a robot unless the provider’s host was listed. The provider’s verification address is fixed in Nibble, so it is called without being listed; a check that can’t be made is now logged.
  • A term query with counts is typed with entries_count in site/types.d.ts, so a view can show the count without a cast.

Changed

  • A blank limit: $set.<field> shows at most 100, the cap an editor’s own number is held to, instead of the whole collection.

    Upgrade: a block whose count field is blank and whose collection has more than 100 entries now lists 100. Add a default to its sidecar’s limit to choose another number.

What’s new

  • Forms can be split into sections. sections: [{ display, instructions, fields }] takes the place of fields, and the theme gets the groups as sections beside the flat fields; crumbs draws each as a numbered fieldset. Submissions, emails and API deliveries are unchanged.
  • Form fields carry prepend and append to the theme, so an amount can show $ before its input.
  • offset works with paginate, so a featured post can sit above a paginated grid without repeating on page 2.
  • Term queries can count entries. counts: posts on a terms: query gives each term entries_count, in one query.
  • nibble:check names records not at their route, per collection and taxonomy, and nibble:prepare, which every deploy runs, moves them, including the terms of a taxonomy that has just been given a route.

What’s fixed

  • The page title, description, canonical link and robots tag appear once with SSR, instead of twice until the page hydrated.
  • The 404 view gets its sidecar’s data, like any other view, instead of only site and seo.

Changed

  • A form that asks for a CAPTCHA the site hasn’t set up gives the theme captcha: { provider: null, site_key: null } instead of null, so it can be told apart from a form without one. Its submissions are still refused.

    Upgrade: a theme that draws its own CAPTCHA widget whenever form.captcha is set should check form.captcha.provider instead. useNibbleForm already does.