Repository navigation
Releases
Nibble 0.25.0
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>.ymlis a sidecar like a view's, and its props reach every page
using the layout, the 404, search and preview pages included, typed asLayoutProps. A layout that shares another's
header takes its queries withextends: default.nibble:checkrefuses 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,checkboxesorbutton_groupfield on a form takes
options: { from: entries:<collection> }(orterms:), withvalue,label,whereandsort. 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@nibbleform 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, andchildrenas the
collection's records.
What's fixed
- A crop shows wherever the image is used. An image whose field has no
presetwas 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.
Nibble 0.24.0
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_valuereplaces a stored value on every
record and locale where it still equalsfrom, or the keys of grid and replicator rows that matchwhere. A value
an editor has changed since, in the record or its draft, is left alone, a second run changes nothing, andtois
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:buildchecked anotifyrecipient
such as{global.site.contact_email}by asking the database for the global's columns, and an image build has no
database, sobin/kamal deploystopped 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
.icofile. Site settings' Favicon field offers.ico, but the asset library refused to
upload one, so a package setting an.icofavicon imported without it..icofiles upload now, and are served as
they are.
Nibble 0.23.0
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-sitewrites a theme's types from Nibble's schema and the theme's
alone, leaving outsite/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 asAustralia/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, setssizeson 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 theirwidthhint; 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, anduseNavigation('main')is typed from the menu's schema:@site/typesgains a
<Menu>NavigationItemper menu that narrows onkind. 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_setsunder a reference'sconfig:, and leaves
some out withremove_sets, instead of copying the whole list.nibble:checkrefuses 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_globalanddelete_global, in every locale and with their permissions.nibble:checkwarns 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 withadd_sets.
What's fixed
nibble:checkacceptsmessages.uniqueon a field the form'slimits.uniquenames. 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_thidden
input, so a form withspam.min_secondsno 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_themecookie, 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
@importthat 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_collectionmoves roles', connected apps' and Agent access
permissions to the new handle, anddelete_collectionremoves them; they stayed on the old handle, so the collection
quietly disappeared for every editor who wasn't a superuser.nibble:checkno 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 withoutmax_depthis 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: 3andspam.max_links: 2
replacecaptcha: 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:
- A site that relied on crumbs' CAPTCHA keeps it with a form of its own. Copy
vendor/nibble/themes/crumbs/schema/forms/contact.ymltosite/schema/forms/contact.ymland set
spam: { captcha: true }. - 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. - 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.
Nibble 0.22.2
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, sobin/vite build(and a deploy) could fail withfailed to resolve import "@tiptap/extensions". Both are declared now, and a test fails when Nibble's frontend imports a
package it doesn't declare.
Nibble 0.22.1
What's new
bin/rails nibble:dev:setup --removeundoes the setup. It takes out the developer tools server, thevendor/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 getsnibble-docs, and any other site keepsnibble-<site name>. In the folder where you use it, run
nibble disconnect <site>and thennibble connect <site>(thenibblecommand 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.
Nibble 0.22.0
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.
notifyentries 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 withif:. Each email
is recorded on the submission, and Send notifications again resends them. - A form can open, close and cap itself.
limits:takesopens_at,closes_at,max_submissionsandunique
(a value accepted once, such as a ticket code), with aclosed_messageshown when it is closed. A form's
submit_labelreplaces the button text. - More ways to catch spam.
spam.min_secondstreats a submission sent too soon after the form was shown as spam,
andspam.max_linkscatches 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: truestores 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: trueturns sections into checked steps.resume: true
adds Save and finish later, with a link emailed to the visitor. Acalculationfield is worked out on the
server when the form is sent, with a live figure in the theme throughcalculated(). - 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
hiddenfield is filled from a fixedvalueor from the page's query
string withprefill. - 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.useNibbleFormapplies 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_fieldandset_defaulttake aglobalin every locale, and the new
move_fieldandremove_fieldcarry values to another global or drop a field that left the schema, so nothing is
stranded. - New fieldtypes.
decimal,range,rating,time,colorandbutton_groupwork in blueprints and public
forms, with Control Plane controls and crumbs inputs.noteshows 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(orsite) 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,urlortelchecks the value on save, in
blueprints and on public forms. A toggle'sinline_labelcan hold Markdown links, for a consent checkbox. - A field's
requiredandread_onlycan 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_onlyfield is kept as stored whoever saves, rows in grids
and replicators by theirid. Entry, term, global and asset writes through the API answer with anignoredlist 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_submissionstakessinceandq, searching as the Control Plane listing does.site/types.d.tstypes each form's values, and a view's form prop uses them, sovalues.topicis typed.
useNibbleFormtakes that type; an untyped call is unchanged.computed:todayis 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: accordionto 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,previewsand
inline_hard_breaks. - Pickers have modes and can create.
entriesandtermsfields takemode: 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:checkrefuses an unknown mode. - Duplicate an entry from the editor or the listing. A field's
duplicateoption decides what is copied; slug and
uniquefields are left out by default. - Entry listings sort and filter by field. Field columns sort, and listed choice, toggle and date fields add
filters.sortableandfilterableturn 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:checkmessage, 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:setupasks 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,codexanswers without asking.
Codex gets a project.codex/config.tomland 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 thenibblecommand 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
defaultview, andnibble:checkwarns 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:formwritesrequired: true.- Form emails show every fieldtype's value again. Only a fieldtype with its own display formats a submitted value.
- A form field's
messagesoverride 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,...
Nibble 0.21.4
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:checkrefuses a message for a rule the field doesn't have. - Number fields on forms reach the theme with
min,maxandstep, and crumbs draws them as number inputs. A
public form refuses a value off the step, as the browser does; blueprint fields still treatstepas 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, orretention_daysif shorter), including spam
stored before this release, which holds no values. nibble:checkwarns when a form's honeypot has a name autofill may fill in, such aswebsiteoremail,
because a visitor's browser would then mark a real enquiry as spam.
Nibble 0.21.3
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 runbin/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.tsagain
right after the swap, sonibble:checkno 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 withbin/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:upgraderefuses while an upgrade is unfinished, and says how to finish it with--resumeor 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'snpm audit
goes from 26 findings to 2 moderate ones in@tailwindcss/typography, which only reads Nibble's own stylesheets at
build time.
Nibble 0.21.2
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
countsis typed withentries_countinsite/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
defaultto its sidecar'slimitto choose another number.
Nibble 0.21.1
What's new
- Forms can be split into sections.
sections: [{ display, instructions, fields }]takes the place offields,
and the theme gets the groups assectionsbeside the flatfields; crumbs draws each as a numbered fieldset.
Submissions, emails and API deliveries are unchanged. - Form fields carry
prependandappendto the theme, so an amount can show$before its input. offsetworks withpaginate, so a featured post can sit above a paginated grid without repeating on page 2.- Term queries can count entries.
counts: postson aterms:query gives each termentries_count, in one
query. nibble:checknames records not at their route, per collection and taxonomy, andnibble:prepare, which every
deploy runs, moves them, including the terms of a taxonomy that has just been given aroute.
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
siteandseo.
Changed
-
A form that asks for a CAPTCHA the site hasn't set up gives the theme
captcha: { provider: null, site_key: null }
instead ofnull, 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.captchais set should check
form.captcha.providerinstead.useNibbleFormalready does.