# Changelog

> Every Lettern release and what changed in it, from new features to security fixes, generated from the code.

## Changelog

November 2, 2026

## 0.2.0

### Added

- A block can be shown only to people signed in, by binding `visible` to `account`, or only to people signed out, with `hidden`, on every page: on one built in advance, the browser shows the right one before it's painted, from a cookie that says only that someone is signed in. The data `accounts` has the sign-in, sign-up and account pages' addresses and words in the page's language, while public accounts are on.
- Pick any icon for a collection or a global set: a searchable list of all 1,500 Phosphor icons, found by name or by what they are for ("email" finds the envelope).
- Plugins can dock a column beside the page on every screen: the `shell.dock` slot with `Dock` from `@letterncms/ui`. A `topbar.end` panel sits in every screen's bar, an editor's own tools go in that bar through `toolbar` in `EntryEditorProps`, and `registry.entryActions` adds lines to the editor's **More** menu. Routes can open over the screen they came from (`overlay`) and keep what they show private (`private`).
- Skills: ready-made ways of working for agents, registered with `cms()->skills()`. Every agent gets them: the assistant's `/`, and MCP clients as prompts.
- Plugins can add to what `@` and `/` offer in a chat with `registry.composer`, and offer things from a screen with `useScreenContext({ items })`. All of it is one thing, a `ContextItem`.
- Abilities can mark a change that stays a draft (`annotations: ['draft' => true]`), for agents to treat as safe to undo unless a call publishes it. `entries/create` and `entries/update` do.
- Screens can say what they show with `useScreenContext`, for whatever works alongside them. An open entry shows changes made from outside as they happen, and `events.emit('content.changed')` asks it to look.
- Assistant: The chat works beside the page, on every screen. **Chat** in the top bar or `⌘J` opens it at the right edge, ready to type in. The page stays open and usable, and anything it changes shows there as it happens, in the builder too. Drag it wider, or take it to full size and back without losing your place.
- Assistant: In an editor the chat floats over the page at the right of the canvas, so the inspector stays where it is and the canvas doesn't move; drag it wherever suits you. Selecting a block with the chat open attaches that block. Anywhere else, **Detach** does the same.
- Assistant: The chat knows what you're looking at, including what's on the page: the entry you have open, the block selected in the builder, the text you selected. Chips in the composer show what a message carries, and each comes off with its ×. Account, Users, Licence and Audit Log stay private, and show no chip.
- Assistant: `@` in a message attaches anything else: a page or post, a file from the library, a global set, the collections, the design system. `/` asks for a skill by name, such as `/proofread`, `/build-page` or `/audit-site`. Plugins add to both.
- Assistant: Each message shows what it carried, one click away.
- Assistant: Chats name themselves after the first reply, and a new chat suggests what to work on based on what you have on view. Both run on a small model you choose under **Settings → Assistant**: Claude Haiku 5.5 unless you change it. A suggestion goes into the composer for you to read before sending.
- Blocks: A block shown or hidden on `account` works on pages built in advance: both versions are in the page, and the browser shows the one for someone signed in or out before it's painted.
- Blocks: A box can be a heading (`h1` to `h6`) or a paragraph, such as a heading that holds a link.
- Media: `@` in the assistant's chat finds files from the library.
- Site: The starter's header shows **Sign in**, or **My account** once someone is signed in, on sites with public accounts turned on, on phones too.
- Site: Adds `/adjust-design` to the assistant's chat, and a prompt for MCP clients.
- Commerce: Buyers outside the EU can be taxed by Stripe Tax. Set **Buyers Outside the EU** to **Tax From the Payment Gateway**: the checkout charges the exact sales tax, GST or VAT Stripe Tax works out for the buyer's address wherever you're registered, invoices name that tax and its rate, every paid sale is recorded in Stripe Tax and refunds are taken back there. The VAT report lists these sales per country under **Tax Abroad**, and **Go Live** checks that Stripe Tax is set up. Another gateway can offer the same through the new `TaxService` contract.

### Changed

- The team has the site's account pages too, with what they bought there like anyone: signing in at `/admin` and going to `/account` no longer leads back to the admin. Their profile, password and data stay in the admin, and the account menu links there. A signed-in team member who opens `/sign-in` goes to their account.
- The store, the licence service and the website are at lettern.org, and the docs at docs.lettern.org. Sites find the store there from this version on.
- A calmer top bar in the editor. A way back and tools on the left, the page's title in the middle, actions on the right. Publishing is one control that also says where things stand: the button when there's something to publish, a quiet "Published" when there isn't. Scheduling moved into **More**, the last thing in the bar. Plugins can use it with `back` and `title` on `Topbar`.
- The way back in the settings and outline sidebars says where it goes, and the sidebar's name sits below it, so it can't be mistaken for closing the sidebar.
- There are never more than two side panels: one on the left and one on the right. A screen that uses the right edge says so with `useScreenContext({ rightEdge: true })`, and a `Dock` then gets `floating`, a card over the page.
- The sidebar is there or it isn't, with one button at the top left and `⌘\`. That button and the way back beside it never move, the top bar slides along with the sidebar, and its title stays in the middle. Left and right panels come and go with the same quick animation. It stays that way until you change it. It no longer folds to a row of icons. A screen can bring a state of its own to it with `ScreenSidebar` from `@letterncms/ui`, like the settings one, with a way back, and folded away to start with.
- On a phone the sidebar opens from that button instead of keeping a row of icons down the side, so screens get the full width.
- Assistant: Fewer approvals: a chat that has read content others may have written saves drafts without asking, since nothing is live until you publish and the history keeps what was there. Anything that goes live or can't be undone still asks, and **Ask Before: Every change** still asks for everything.
- Assistant: Your chats are in one place: in the chat, all of them, by age, with a column of their own at full size. They're no longer listed in the sidebar or the Ask widget.
- Assistant: **New Chat** is no longer a sidebar row, since the sidebar isn't always there: **Chat** in the top bar is on every screen. If you don't use AI, **Hide Chat** on the setup screen takes it out of the bar.
- Blocks: The builder gets the whole window: the admin's sidebar folds away when it opens and comes back when you leave. Bring it out with the same button and `⌘\`, and it shows the outline, with a way back to the list, the way it shows the settings on a settings page.
- Blocks: One bar: insert, undo and redo moved from their own row into the top bar.
- Blocks: The selected block is known to whatever works alongside the editor, such as the assistant's chat.
- Blocks: The inspector no longer shows a block's parents above its name. The outline and the canvas lead to them.
- Forms: Forms no longer check the core's `CMS_LOCKDOWN`, which is gone. This version works with every 0.x of the core; the previous one stops saving on a core without lockdown.
- Mcp: Prompts are now the CMS's skills, so plugins that register a skill with `cms()->skills()` add a prompt too.
- Navigation: Menus no longer check the core's `CMS_LOCKDOWN`, which is gone. This version works with every 0.x of the core; the previous one stops saving on a core without lockdown.
- Seo: Redirects no longer check the core's `CMS_LOCKDOWN`, which is gone. This version works with every 0.x of the core; the previous one stops saving on a core without lockdown.
- Site: The design system and custom code no longer check the core's `CMS_LOCKDOWN`, which is gone. This version works with every 0.x of the core; the previous one stops saving on a core without lockdown.
- Site: **Preview** moved from the editor's top bar into its **More** menu.
- Commerce: **Breaking.** The **Private Buyers Outside the EU** setting is now **Buyers Outside the EU** (`outside_eu`), with the choices Businesses Only, Without Tax; Everyone, Without Tax; and Tax From the Payment Gateway.
- Commerce: A business outside the EU can give a tax number that isn't an EU VAT number, instead of being told it doesn't look like one.

### Removed

- **Breaking.** `CMS_LOCKDOWN` is gone. Collections, settings, roles, the design system and plugins can always be changed in the admin, by the people whose role allows it. A site that sets it can drop the line. `CMS_ALLOW_FILE_MODS=false` still stops plugin files changing from the web. For plugins: `Cms::lockedDown()`, `Cms::guardConfigWrites()` and `cms.lockedDown` in the admin's manifest are gone with it.
- **Breaking.** The `sidebar.history` slot, and `shell.compact`. Use `ScreenSidebar`.
- Commerce: **Breaking.** Authors in a store publish free listings only: the store's operator is the only seller. Payouts, store commission, Stripe Connect onboarding and the `PayoutGateway` contract are gone, with the **Store Commission** and **Days Before an Author Is Paid** settings, and authors no longer give a country or VAT number when they join.

### Fixed

- A URL that names a folder in the site's root no longer redirects through `http://` when a proxy in front of the site handles HTTPS. New sites get this in `.htaccess`. On an existing site, add `DirectorySlash Off` below `DirectoryIndex index.php`, and `RewriteOptions AllowNoSlash` below `RewriteEngine On`.
- A line break in rich text no longer turns into two each time it's saved.
- An entry only saves when something in it changed. A field reporting the value it already had could rewrite the page's file, and changing something back no longer counts as unsaved.
- A collection whose icon is named after a Phosphor icon, like `handshake` or `book`, now shows it in the sidebar and lists instead of a plain circle.
- Blocks: Saving a page keeps every block as it was. A text block with a tag such as `span`, an id that interactions point at, or no text yet no longer turns into plain Markdown and loses them, two lists in a row stay two, and a screen size with no styles left is dropped instead of saved as an empty list. A page saved without changes keeps its file as it was.
- Blocks: A save keeps `calc()` lengths in animations, and a `style` attribute that only sets CSS variables, such as a section's own colours.
- Blocks: Clicking into text on the canvas and leaving it no longer saves the page. Text the browser writes differently, such as a non-breaking space, counted as an edit and was changed without anyone typing.
- Blocks: The admin's shortcuts, such as `⌘J`, `⌘K` and `⌘\`, work with the cursor on the canvas.
- Blocks: A page whose blocks have short ids, or an id used twice, no longer opens with an empty canvas in the editor.
- Forms: A form embedded in a page shows its fields on the builder's canvas, not only its submit button, from the moment the page opens.

November 1, 2026

## 0.1.0

### Added

- Build a plugin's admin module from npm: `@letterncms/admin`, `@letterncms/sdk` and `@letterncms/ui`, with no workspace needed.
- Install with Composer (`composer create-project letterncms/lettern`), or upload a small zip that fetches the same release. Either way the site is a Composer project.
- Choose your plugins during setup. Nothing is preinstalled, and each plugin updates on its own.
- Public accounts, off until a site turns them on: visitors can sign up, sign in, reset a password, confirm an email address, edit a profile, download their data and delete their account, on pages designed in the builder. Limit pages to an account, a role or an access name, with a teaser for everyone else. Plugins use it all through `cms()->accounts()`.
- Update from the admin with one click: releases are signed, go live by switching a pointer and go back by themselves if they fail, and security updates install on their own. The Store and Updates tabs in Modules browse and install plugins from a store, on any site, and a paid plugin's key goes in from its listing. A paid plugin's licence never takes a public site down.
- Assistant: First release.
- Blocks: An accordion can look like cards, with each question on a soft panel of its own.
- Blocks: A row can step through its items with a Carousel behaviour and two buttons, and Tabs can stand in a column beside their panels.
- Blocks: A List Filter can sort, with a new Filter Sort block and Sort By on the Repeat, and a Filter Choice can look like a list. A carousel can have thumbnails, through `data-carousel-to`.
- Blocks: An Appear animation can now loop as one timeline: set Repeat to forever and a rest at the end, and its steps play together while the element is on screen, so a short product demo needs no video or script of its own.
- Blocks: A Menu behavior for navigation: one panel moves between items, changing shape as it goes, with a pill that follows the pointer and keyboard focus.
- Blocks: The Tabs block can look like a segmented control.
- Blocks: A Table can mark one column, head its rows with its first column and use a soft look without rules, which stacks neatly on a phone.
- Blocks: New Tabs and List Filter blocks that work without scripts, `where` on the entries source, and an RSS feed for each collection of dated entries.
- Collab: First release.
- Forms: First release.
- I18n: A site reads fully in each language. Links follow the visitor's language, collections can have a URL pattern per language, menu items and forms have their wording per language, a missing page and the text from the code answer in the page's language, and each language gets its own sitemap. `hreflang` leaves out pages that aren't indexed, and a translation no longer inherits a canonical or noindex.
- Mcp: First release.
- Media: Media records are kept in `content/media`, so a fresh install or deploy from git has every image. `php cms media:check` and `php cms build --strict` report what points at nothing, and deleting a file still in use is refused.
- Navigation: First release.
- Seo: Set a security contact in SEO settings and the site publishes `/.well-known/security.txt`, renewed on every build. A home page's social title now matches its page title, and a site with no favicon no longer gets a 404 for `/favicon.ico`.
- Site: Navigation from a tree: `entry.root`, `item.current` and `item.active` on entries, child pages of the top of a branch, and **Group by** on Repeat. `cms links:check` fails on links, images and anchors in the built site that lead nowhere.
- Commerce: The store is a Composer repository: `composer config repositories.store composer <store>/composer`, with paid packages behind the same licence check as any download.
- Commerce: Commerce now issues per-site licences and can run a store: listings of anything, signed versions, paid downloads gated by licence, third-party authors by invitation, commission, and payouts through Stripe Connect or any gateway that can pay others.
- Commerce: Commerce, a paid plugin from the store: sell downloads, courses, memberships or anything else with a hosted checkout, your own numbered invoices and credit notes, EU VAT with VIES and OSS reports, refunds, disputes, subscriptions and Peppol. Stripe is the first gateway and any plugin can register another. It runs in test mode until a site with a licence goes live, and after that no licence check ever stops it selling.
- Commerce: A store listing can have a short address, what it needs to run, a link to its documentation and a description for each screenshot, and a site can build its own store and item pages from the Store Items source and `entry.listing`. A built page that lists the store is written again when the store changes, and listings are in the sitemap with their structured data.

### Changed

- New installs lock to commits that stay available, after a tidy-up of this repository's history.
- The `@letterncms/*` npm packages are now published by a GitHub workflow with no stored token, so each version carries npm's provenance record of where it was built.
- The public account pages say what's wrong at the field it's about and put the focus there, show a password on request, and make way for the message once a link is sent. Account Navigation can be a sidebar, and plugins choose their place in it.
- The main sidebar is now about content: Design and Tools moved to Settings.
- The publish button in the admin is the brand's yellow instead of orange.
- Lettern is free and open source under the MIT licence, for any use on any number of sites, with no key, no limit and no banner. Paid plugins carry a licence of their own.
- Analytics: **Breaking.** Analytics counts page views by default, with no script and nothing read from the browser or kept about visitors. Visitors, sources and devices are now a setting, and that mode waits for a consent signal.
- Blocks: **Breaking.** Animations no longer use GSAP. Page load, scroll and loop animations are plain CSS, and the rest use the Web Animations API in a 2 KB script. Each word animates from markup in the page, letters and lines read as words, and scroll's Smooth, Lazy and Hold in place are gone. Animations can play per screen size, and a new Motion Toggle block pauses what runs forever.
- Brevo: Brevo is now a free plugin from the store, and no longer comes in the download. Install it under Modules, then Store.
- Mail: Mail wears the site's colours, and mail to visitors and buyers has one clear button with its link written out.
- Commerce: The checkout, order page and licences page are laid out for buyers: errors at their field, a filled-in form when going back, an order summary beside the form, clear outcomes, and new invoices and downloads pages in the account. Test mode says so on every page, invoice and mail, and says when the seller's address is missing. In test mode the EU's test VAT numbers try a business sale without anyone's real number.

### Removed

- Site themes, admin themes and their settings. The starter kit is part of the site plugin and the admin is a module.
- Sharing one installation between sites.
- Content no longer has `{brand.name}` and its sibling tokens, and `brand.*` is gone as a binding path. Write the product's name and links as plain text. `brand/brand.json` still names the product in the admin, emails, the command line and the installer.

### Fixed

- Blocks: Long code blocks and carousels of pictures can be scrolled from the keyboard, and the Search block's name no longer differs from its visible label.
- Site: A static build removes generated files nothing produces any more, such as the sitemap of a language the site no longer has.
- Site: A visitor who hits a rate limit, such as more than 20 checkout posts in a minute, now sees the site's own page with a plain message and a way back, instead of a bare 429. A checkout form left open past its session goes back to the form.
- Commerce: An invoice opens in the browser rather than downloading, its lines follow the addresses without a large gap, and the VAT report says Consumers and Businesses in words.
- Commerce: Per-site licences work out a domain with the whole Public Suffix List, wildcard and exception rules included, instead of a short list kept by hand.
- Commerce: A VAT check answered by the EU's test service in test mode is no longer reused for a sale once the site has gone live.

### Security

- The update list can be kept fresh by a key that can only do that: it can't sign a release, so it never has to sit beside the keys that can.

## New releases by email.

---
Source: https://lettern.org/changelog · Updated 2026-11-02
