---
title: "v1.7.6"
description: "A security release for the Next.js 16.4 remote code execution flaws, with Payload 3.91, reliable post publishing, custom Lexical node rendering and private AI writing settings."
url: https://docs.systhema.app/changelog/v1.7.6
version: changelog
docs_index: https://docs.systhema.app/llms.txt
---

## ✨ Highlights

1.7.6 is a security release first. Next.js published fixes for two critical unauthenticated remote code execution flaws and a denial of service on 14 October 2026, with no backport to 16.3, and Systhema's exact `sharp` pin blocked the workaround Vercel recommends. 1.7.6 requires Next.js 16.4.2 and sharp 0.35.5, and moves the Payload family to 3.91.0. **Every project on 1.7.4 or 1.7.5 should upgrade now.** The rest of the release is the batch of fixes that collected since 1.7.5: posts that publish reliably, custom Lexical nodes that render on the site, private AI writing settings, safer `self-update` and `upgrade` runs, Admin labels that stay in one language, and a round of accessibility, right-to-left and styling fixes.

### Security: Next.js 16.4.2 and sharp 0.35.5

| Advisory                                                                       | Severity | Affected                   | Fixed in                           |
| ------------------------------------------------------------------------------ | -------- | -------------------------- | ---------------------------------- |
| GHSA-2464-fhr4-w9xw, unauthenticated RCE in the image optimizer with SVG       | critical | next >=10.0.0 <=16.4.0     | next 16.4.1, or sharp 0.35.5       |
| GHSA-673q-mjm6-r5xr, unauthenticated RCE in `use cache` returning typed arrays | critical | next >=16.0.0 <=16.4.0     | next 16.4.1                        |
| GHSA-p3m5-59mp-jv5r, denial of service in App Router Server Actions            | high     | next >=16.0.0 <=16.4.0     | next 16.4.1                        |
| GHSA-xqc2-xf4c-x3h3, unauthenticated RCE in React Server Components            | critical | react-server-dom-\* 19.3.0 | 19.3.1 (Next vendors its own copy) |
| GHSA-wq5f-xc86-pv6w, librsvg vulnerability                                     | high     | sharp <0.35.5              | sharp 0.35.5                       |

16.4.1 is the first fixed 16.x release, so every project on 16.3.8, the floor 1.7.4 set, is affected. The image-optimizer flaw sits in sharp's bundled librsvg on glibc Linux, and Vercel's workaround is sharp 0.35.5 or later. Systhema pinned sharp to exactly 0.35.4 in the Payload template, the root overrides and the packages, so no consumer could take that workaround. These advisories are not yet in GitHub's global advisory database, so `pnpm audit` and dependency dashboards built on it do not report them.

The `next` peer range of `@systhemaui/next` and `@systhemaui/payload` is now `^16.4.2`, and the `sharp` peer of `@systhemaui/payload` is `^0.35.5` (was `^0.34.2 || ^0.35.0`). `systhema upgrade` reads those peers, so it raises `next` to `^16.4.2`, `sharp` to `^0.35.5` in Payload projects, and the whole Payload family to `^3.91.0`. New projects also get `pnpm.overrides` floors for `compression` 1.8.2, `shell-quote` 1.11.0 and `source-map-js` 1.2.2 (#278).

### Payload 3.91.0

Payload 3.91.0 carries no security fixes of its own (3.90.2 already had October's), but it is the version the bundled patches now target. It brings nodemailer 10, activity-aware Admin sessions and a faster rich text client schema map. Both bundled patches are regenerated as `@payloadcms__ui@3.91.0.patch` and `@payloadcms__richtext-lexical@3.91.0.patch` and keyed `^3.91.0`; every hunk carries over, and `systhema upgrade` and `systhema doctor` install them. Payload 3.91 needs Node 20.18.1 or later, adds a `_parent_id` index to localized tables, and moves `@payloadcms/email-nodemailer` to nodemailer 10; the Upgrade section below says what that means for your project (#278).

### Posts publish reliably

Publishing a post could fail with "The following field is invalid: slug", and scheduled publishes failed in the job queue with the same error. Three causes, three fixes (#276):

- **A taken URL path gets the next free suffix.** On Posts, Categories and Tags the slug hook appends `-2`, `-3` and so on until the path is free, counting trashed documents too, because the unique index covers them. Before, the save failed on the index.
- **A blank URL path is filled from the title**, the same as a missing one, so a published post always has a URL.
- **The slug no longer freezes on title blur.** Leaving the title field quickly, for example after pasting a title, cancelled the debounced sync and left the slug empty or cut short. Blur now runs the pending sync first.

A post published without a date now gets the publish time as its `publishedAt`, also when per-locale publishing publishes every locale at once. Listings sort by `-publishedAt` and PostgreSQL puts empty dates first, so undated posts used to sit above every dated one. A date the editor set is kept. Existing undated posts need a one-off backfill in each project.

Post bodies can also hold a Gallery block now. The default post editor turns on the new opt-in `regionLexicalEditor({ withGallery: true })` flag, so projects with `contentEditor: 'region'` or no option get it without a code change (#263).

### Custom Lexical nodes render on the site

A project that added its own Lexical feature with a custom node saw Payload's `unknown node` placeholder on the published page, because Systhema's renderer had no extension point for nodes. The new `customNodeConverters` option of `withSysthema()` takes a converter per node `type`; under `livePreview.mode: 'client'`, pass the same entries to `registerSysthemaClientNodeConverters` from `@systhemaui/payload/next/client`. Built-in node types are kept unless an entry sets `override: true`, and block types stay on `customBlocks`. In development, client-mode preview warns when a node converter is registered on only one side (#271).

```ts
withSysthema(payloadConfig, {
  customNodeConverters: [{ nodeType: 'callout', converter: calloutConverter }],
})
```

### AI writing settings are private

Any user with `ai.generate` could read and overwrite another user's AI writing settings (`systhemaAiSettings`: tone, dials and custom instructions), admins included. The group is now visible and editable only by its owner, when they hold `ai.generate`, and by `dev` users. An admin can still save another user's name, avatar and roles; Payload strips the settings from that request without an error. The new `selfOrDevField(cap)` access helper, exported from `@systhemaui/payload`, protects a project's own per-user fields the same way (#275).

### Safer `self-update`, `upgrade` and `doctor`

Four changes for real projects (#269):

- **`systhema self-update` refuses a downgrade.** On a canary, alpha, beta or internal CLI, or inside a project on such a version, `latest` is older. The command now names the channel to stay on, asks in a terminal (default no), and exits 1 under `--yes` or without a TTY. `--target <channel>` or the new `--allow-downgrade` installs as asked.
- **`systhema upgrade` pins pnpm** on a project with no `packageManager`, using the pnpm new projects get when `pnpm-lock.yaml` allows it. Its installs pass `--config.confirm-modules-purge=false`, so a pnpm switch no longer stalls on an invisible question or aborts without a TTY.
- **The upgrade refreshes project-scope skills only.** `--user-skills` adds user scope back; `systhema skills refresh` still covers both.
- **New `stale-import-map` doctor check** (fast). It flags a Payload `importMap.js` that names a `@systhemaui/payload` entry the installed package no longer ships, misses an Admin provider (the Admin renders blank), or has no AI component while an AI provider is configured. `--fix` runs `payload generate:importmap`.

### The Admin stays in one language

Switching the Admin language left native block and field labels in the previous language, because Payload caches the client field schema without the language. The bundled UI patch now keys those caches by language and entity. Row actions, nested validation messages, Live Preview device labels and the Add block and Drag to move labels are translated in all seven Admin languages, and broken interpolation tokens in French, Arabic and Slovak are fixed (#262).

---

## ⬆️ Upgrade

```bash
pnpm add -g @systhemaui/cli@latest
cd <your-project>
systhema upgrade --dry-run
systhema upgrade --yes
```

No codemods, token migrations or database hooks run in this release. `systhema upgrade` raises `next`, `sharp` and the Payload family through the peer check, pins pnpm if the project has no `packageManager`, and installs both regenerated Payload patches. Then install, rebuild and redeploy.

**Check that `next` resolves to 16.4.2 or later** after the install (`pnpm why next`). If your project pins `sharp` or `next` in its own `pnpm.overrides`, raise those entries yourself: the peer check rewrites only Payload-family overrides.

**Node 20.18.1 or later.** Payload 3.91 raises its Node floor. New projects declare `engines.node: ">=20.18.1"`; check your build host and runtime image.

**The new `_parent_id` index on localized tables** is additive. Push-mode databases get it on boot. On a push-disabled PostgreSQL database, add it with `systhema migrate --schema` or a Payload migration. Nothing breaks without it.

**Your own nodemailer transport.** A project that passes its own nodemailer 9 transport instance to `@payloadcms/email-nodemailer` must move to nodemailer 10. Systhema's scaffolded SMTP and SendGrid modules pass `transportOptions` and need nothing.

**Custom Payload patches.** `systhema upgrade` and `systhema doctor` install `@payloadcms__ui@3.91.0.patch` and `@payloadcms__richtext-lexical@3.91.0.patch`. A patch path you point at yourself is never overwritten; combine your changes with the new patches by hand.

**New security floors reach new projects only.** Copy the `compression`, `shell-quote` and `source-map-js` entries from `scripts/security-overrides.json` into your `pnpm.overrides` and run `pnpm install`.

**Regenerate the import map** if `systhema doctor` reports `stale-import-map`: `systhema doctor --fix`, or `payload generate:importmap`.

**Backfill undated posts** if your listings show old posts on top: set `publishedAt` on published posts that have none.

---

## ⚠️ Breaking & Behavioral Changes

### 1. Next.js 16.4.2, sharp 0.35.5, Payload 3.91 and Node 20.18.1

The `next` peer of `@systhemaui/next` and `@systhemaui/payload` is `^16.4.2`, the `sharp` peer of `@systhemaui/payload` is `^0.35.5`, and every `payload` and `@payloadcms/*` peer is `^3.91.0`. `systhema upgrade` moves the dependencies; a project that pins the Payload family to one exact version is moved to exactly `3.91.0`. Payload 3.91 needs Node 20.18.1 or later. Manual: your own `next` or `sharp` overrides, and your own nodemailer 9 transport (#278).

### 2. Radio and checkbox ids are generated

`FormRadio` and `FormCheckbox` used `name` as the default `id`, so radios in one group shared an id, and `RadioField` and `CheckboxField` used each item's `value`, so two yes/no groups collided. An input without an explicit `id` now gets a generated one. Manual: CSS, scripts or `htmlFor` that target `#<name>` or `#<value>` need an explicit `id` (#267).

### 3. `aria-invalid` follows the visible error

`FormFieldShell` no longer sets `aria-invalid="true"` whenever `invalidMessage` is present; Payload's form block always passes one, so every such field was announced as invalid on page load. Controls set `aria-invalid` from the `is-invalid` class, and an explicit `aria-invalid` prop wins. The Payload form block adds `is-invalid` when its own validation rejects a value (#267).

### 4. `self-update --yes` refuses a downgrade

A script that runs `systhema self-update --yes` on a prerelease CLI, or inside a prerelease project, now exits 1 when `latest` is older. Pass `--target <channel>` or `--allow-downgrade`. `systhema upgrade` no longer refreshes user-scope skills unless you pass `--user-skills` (#269).

### 5. AI writing settings are owner-only

Admins no longer see or change another user's `systhemaAiSettings`; `dev` users still can. On create, only a `dev` user can set the group (#275).

### 6. A custom block is no longer offered inside itself

A custom block registered for a tier is left out of that tier's editors in its own rich-text fields, so `payload generate:types` stops overflowing the stack. A block that can still reach itself through built-in blocks gets a warning with the full path; it never throws. A document that already nests a block inside itself fails validation on its next save for the nested copy (#268).

### 7. Post slugs are suffixed, and published posts get a date

A post, category or tag saved onto a taken URL path gets `-2`, `-3` and so on instead of a validation error. A post published without `publishedAt` gets the publish time (#276).

### 8. Layout and styling changes you can see

- **Every `Columns` and `flex-grid` gap is 1px wider.** Gaps used to render 1px short of their token; they now use the exact value, and span widths round down instead (#273).
- **Divided `Columns` clip.** `columns-divide-x` and `columns-divide-y` no longer draw a divider after the last column of a row or under the last row. They use `overflow: clip`, so `.column-sticky` keeps working; `overflow-visible` turns the clipping off (#277).
- **Parallax modifiers do what they say.** `.parallax--big` zooms deeper to cover its longer travel, and `.parallax--noscale` travels inside a taller box without zooming (#273).
- **`blocks.rootVariables`, `blocks.variablesUtilities` and `blocks.applicationLayout` are honoured.** They were emitted even when set to `false` (#273).
- **An unprefixed `text-h1` to `text-h6` on `<Heading>` replaces the level's default size**, so it can enlarge a heading as well as shrink it (#273).
- **HTML projects configure parallax through `window.__SYSTHEMA_PARALLAX__`.** The bundle no longer imports the Node config loader; `systhema.config`'s `parallax` key was never readable in the browser (#270).

---

## 🐛 Fixes & Internal Improvements

### Bug Fixes

- **Draft-mode homepage** — `/` no longer returns 404 in draft mode when the homepage is published but has newer unpublished or autosaved changes; public visitors are unaffected (#261)
- **Client-mode live preview keeps up** — archive theme, background, padding, gaps, layout and Load-more button update while you edit, Posts blocks and forms stay after the first update, and the archive search filter no longer drops `__livePreview` from the URL (#266)
- **Inline block drawers** — Button, Chip, Icon and Avatar drawers no longer show Stack Related Settings outside a Stack, a drawer save rejected by server validation now says so, and visually hidden button and chip titles are hidden in the editor too (#266)
- **Nested Stack renders again**, and editing a component revalidates the pages that embed it (#268)
- **Accordion shorthands work from Server Components** — `Accordion.section`, `AccordionTitle.button` and `AccordionContent.div` take the base props, `expanded` is followed after mount, and `onExpandedChange` reports toggles (#267)
- **The header's mobile menu works inside a layout wrapper**; it used to make its own ancestor inert (#267)
- **Form and byline accessibility** — `is-invalid` on a radio or checkbox styles the whole control, `CheckboxField requiredMode="any"` works with uncontrolled items, the HTML-projects accordion inerts collapsed panels, and the post byline avatar is decorative (#267)
- **Media play buttons from Server Components** — a `MediaWrapper` written in a Server Component, including every Payload media converter, becomes the play button after hydration; an autoplaying video never does (#272)
- **Smaller component fixes** — `Stack wrap="wrap"` adds `flex-wrap`, custom list marker icons are sized, Carousel forwards extra `Figure` props, the next `Avatar` defaults `sizes` to its width instead of `100vw`, and Footer bottom-navigation links get their own props slot (#272)
- **`@systhemaui/next` parity** — the provider and listeners accept `resetKey`, and the helpers `@systhemaui/react` exports are re-exported (#272)
- **The HTML-projects bundle loads in the browser again** — parallax, the cookie banner and `window.SysthemaJS` start; the bundle also drops from 54.7 KB to 34.7 KB (#270)
- **Right-to-left projects** — lists, quotes, select and search icons, checkboxes and radios, file inputs, features, header icons, the share rail and column dividers use logical properties (#270)
- **`animate-disable` shows a fade-in's end state**, the skip link follows the primary button radius, and the gallery, carousel and posts pagination buttons render their shadow (#270)
- **Tailwind v4 utilities** — `flex-grid-gap-*` and `flex-grid-divide-*` emit CSS again, including arbitrary values; `autotext-*` keeps arbitrary units and drops the bogus `autotext-shadow-*` classes (#273)
- **Full-bleed figures outside an Article lose their radius and shadow**, and an ancestor `stagger-delay-*` no longer retunes the posts listing (#273)
- **Translucent colours in TOON references keep their alpha** (`#00000040` instead of `#000000`) (#273)
- **A linked parallax video is clickable** — a parallax wrapper that is itself a link or button takes pointer events (#274)
- **Gallery in development** — Strict Mode no longer crashes a page with "Cannot read properties of undefined (reading 'navigation')" (#265)
- **Admin copy** — "Justify content" and "Paste YouTube URL" are spelled right, and the `linkFields` JSDoc points at `@systhemaui/payload/utils/internalRefToHref` (#268)

### Internal / Monorepo

- **Dependency refresh** — every in-range bump, plus happy-dom 21, tsup 9 (building needs Node 22.18+), vite 8 and emojibase-data 18; `@lexical/utils` moves to `^0.50.0` (#278)
- **`happy-dom` client tests for react and next** — `*.client.test.tsx` covers behaviour that needs effects (#272)
- **New guard tests** — `'use client'` modules may not attach component statics, every `var(--x, fallback)` must be defined or listed in `CONSUMER_OVERRIDABLE`, no custom property references itself, and physical left/right styles need an allowed reason (#267, #270)
- **The HTML bundle is pinned browser-only** by `js/browserBundle.test.ts` (#270)

---

## List of all changes

### 🚀 Features

#### payload

- feat(payload): render project lexical nodes through registered converters (#271) (97d390fe)
- feat(payload): offer the gallery block in the default post body (#263) (e52c80d0)

### 🐛 Bug fixes

#### cli

- fix(cli): make self-update, upgrade and doctor safer on real projects (#269) (97d0eadf)

#### core

- fix(core): clip column dividers at row and grid edges (#277) (057dc5d7)
- fix(core): fix the html bundle, animate-disable, skip link and rtl styles (#270) (06c748ae)
- fix(core): let a link or button parallax wrapper take pointer events (#274) (f0a4d95f)

#### core, react

- fix(core,react): repair v4 utilities, parallax modifiers and toggles (#273) (212254ec)

#### core, react, next

- fix(core,react,next): media play targets from server components and fixes (#272) (45fa8225)

#### core, react, next, payload

- fix(core,react,next,payload): accordion, form, header menu and byline a11y fixes (#267) (6ed59801)

#### next, payload

- fix(next,payload): require next 16.4.2, sharp 0.35.5 and payload 3.91 (#278) (5b0b297)

#### payload

- fix(payload): keep post slugs unique and date posts on publish (#276) (d0aab317)
- fix(payload): keep AI writing settings private to their owner and developers (#275) (ae11aff5)
- fix(payload): stop custom block self-nesting and fix stack, component revalidation and copy (#268) (f70613df)
- fix(payload): keep live preview and inline block drawers in sync with edits (#266) (94de6c2f)
- fix(payload): keep native admin translations consistent (#262) (60fcfd29)
- fix(payload): render a homepage with unpublished changes in draft mode (#261) (cc3aa612)

#### react

- fix(react): skip a destroyed swiper instance in the gallery (#265) (a23ac88c)
