v1.7.3
The bundled Lexical admin patch follows the Payload minor, so a Payload patch release no longer needs a Systhema release, and the upgrade tooling handles exact pins, failed installs and blocked schema plans.
On this page
✨ HighlightsLink to this section
1.7.3 is the release that came out of seven consumer projects upgrading to 1.7.2 and reporting back, over five canaries. The headline change is structural: a Payload patch release no longer needs a Systhema release. The bundled Lexical admin patch is keyed to the Payload minor (@payloadcms/richtext-lexical@^3.90.0) instead of one exact version, so the family may float on caret ranges again, and the scaffold goes back to carets. The rest is the upgrade and migration tooling getting honest about the states real projects are in: exact pins that must stay exact, a failed install that must be resumable, a schema plan that must show what it would do even while something else blocks it, and a data helper that must match nothing rather than crash on a label the enum no longer has.
The Lexical patch follows the Payload minorLink to this section
Payload 3.90.1 shipped a few hours after 1.7.2, and every fresh install broke: the upgrade wrote ^3.90.0, the patch was keyed to 3.90.0, and pnpm aborted with ERR_PNPM_UNUSED_PATCH after package.json was already rewritten. The key is now the range. pnpm applies the diff to whatever version resolves, the doctor and the upgrade pre-flight test-apply the diff to the installed package instead of comparing version numbers, and a daily job in the Systhema repo packs the newest release the key allows and test-applies the patch, so a Payload release that rewrites one of the two patched files is caught before it reaches a project. That is the only case left where a Payload patch release costs a Systhema release (#222 (opens in new tab), #221 (opens in new tab)).
Pinned projects stay pinned, and a failed upgrade resumesLink to this section
A project that pins the Payload family exactly keeps an exact pin: a pin below the new floor moves to the exact floor, never a caret, and the upgrade writes pnpm.overrides for the whole family, including the @payloadcms/* packages @systhemaui/payload itself depends on through caret ranges, which otherwise resolved a patch ahead of the pin and tripped Payload's boot check. A re-run after an aborted install no longer answers "Already on 1.7.3. Nothing to do": if the installed @systhemaui/core does not satisfy package.json, the install and every step after it run again (#220 (opens in new tab)).
pnpm patches survive the upgradeLink to this section
The pre-flight test-applies every patched dependency the upgrade bumps. A patch that applies is re-keyed onto the resolved version; one that does not is renamed to <name>.stale.patch and its entry removed, behind a confirm (--drop-stale-patches under --yes), so a diff that stopped applying because the surrounding code moved can be rebased by hand rather than being lost. The check is whitespace-lenient, matching pnpm's own applier, so a working patch is never reported stale (#220 (opens in new tab), #223 (opens in new tab), #225 (opens in new tab)).
systhema migrate --schema shows its workLink to this section
A blocked plan prints the additive statements it would have applied as SQL comments beneath the blockers, so the column a release needs cannot hide behind unrelated drift. A removed enum label that no row uses is a warning, not a blocker, and the operator-owned recreate DDL rides along commented out in both variants, because its cast fails on exactly the rows that cause a block and a plan is pipeable SQL (#220 (opens in new tab), #223 (opens in new tab), #224 (opens in new tab)).
⬆️ UpgradeLink to this section
pnpm add -g @systhemaui/cli@latest
cd <your-project>
systhema upgrade --dry-run
systhema upgrade --yes --allow-databaseThe CLI needs Node 22. The bundled execa calls Set.prototype.union at module load, so the 1.7.0 to 1.7.2 binaries crashed on Node 20 with TEXT_ENCODINGS.union is not a function. The requirement is now declared, and an older Node gets one line, Systhema CLI needs Node 22 or newer, instead of a stack trace. Project runtimes are unchanged at Node 20.9+.
The upgrade moves the Payload family to 3.90.1. A project on caret ranges resolves it and the Lexical patch applies under the ^3.90.0 key; a project pinned exactly is moved to exactly 3.90.1 with whole-family overrides. You may now drop an exact pin if you only added it to survive 1.7.2.
One codemod runs: commit-managed-files-ledger narrows a wholesale .systhema/ ignore rule to .systhema/* and re-includes .systhema/managed-files.json, so the ledger that protects your edited managed files is committed. Commit it after the run.
Then, on PostgreSQL, re-run the capability rename once, whether or not you ran it on 1.7.1 or 1.7.2:
systhema migrate migrate-capability-stringsThe 1.7.1 and 1.7.2 versions of that script could not work on PostgreSQL: it joined on _parent_id (the array convention) where a hasMany select table has parent_id, then compared the enum column to legacy strings the 1.7 enum recreate had already removed, and Payload's bin swallowed the rejection so the runner printed completion over a failed transaction. It now uses parent_id, compares as text, skips a rename whose target label the project's enum does not carry (a General Settings tab that is off drops its capabilities from the enum), and exits non-zero on error. On a push-off production database that is the usual reviewed-SQL route: a DELETE of legacy rows whose renamed twin exists and an UPDATE ... SET value per rename, mapping in CAPABILITY_STRING_RENAMES.
⚠️ Breaking & Behavioral ChangesLink to this section
1. The systhema CLI requires Node 22Link to this section
@systhemaui/cli declares engines.node >=22.0.0. It never worked on Node 20 since 1.7.0; the change is the declaration and the clear message. Projects themselves still run on Node 20.9+.
2. Form notification emails follow the email adapter, not the templates moduleLink to this section
The form-builder emails array was removed from the schema whenever the emails plugin option (the email templates module) was off, so a project running an email adapter with emails: false silently lost form notifications. The array now stays whenever Payload has an email adapter configured; the emails option controls only the templates module. beforeEmail falls back to the adapter's default sender when the module is off. A project in that state gets forms_emails back in its schema; an existing retained table already matches, so there is no drift (#223 (opens in new tab)).
3. Image optimization skips client uploadsLink to this section
uploads.imageOptimization re-encoded a client-uploaded file and renamed an opaque PNG to .jpg, but with Payload clientUploads the file is already in storage under its original name and the cloud-storage plugin skips re-uploading it, so the document pointed at an object that was never written. The optimizer now leaves any file carrying clientUploadContext alone; server-side uploads are optimized as before (#225 (opens in new tab)).
4. .systhema/managed-files.json is meant to be committedLink to this section
The Next scaffold's .gitignore narrows .systhema/ to .systhema/* plus a negation for the ledger, and the codemod above does the same in existing projects. Git never descends into an excluded directory, so a bare negation under a .systhema/ rule was a silent no-op (#223 (opens in new tab)).
🐛 Fixes & Internal ImprovementsLink to this section
Bug FixesLink to this section
- Per-locale publishing migration keeps published pages published — the live-document step derived each locale's status from the newest version row, so a pending draft version turned every locale of a published page into a draft; it now copies the main row's shared
_statusinto each locale row (#219 (opens in new tab)) - Doctor honours an exported
DATABASE_URI— thepending-db-renamesandlocalize-status-schemachecks read.envfirst, whilesysthema migratelet the process environment win (#225 (opens in new tab)) - No
<path>.newwhen the template is unchanged — an edited managed file whose template hash still equals the ledger's record has nothing to fold (#225 (opens in new tab)) - The upgrade plan names the right files — edited managed files go to
<path>.new, not<path>.bak, and the recovery hint printssysthema payload create-app-files --override, which runs without the moved-command notice (#223 (opens in new tab)) repair-fa-icon-picker-importstops flagging forever — a project whose only barrel use is an intentional genericiconPickerFieldis no longer applicable (#220 (opens in new tab))systhema-core refresh-cacheis silent without.systhema/— the dependency stage of a multi-stage Dockerfile has no project directory yet; the sync hint still prints when.systhema/exists without artifacts (#220 (opens in new tab))systhema skills refresh— re-copies the bundled version of every installed skill into the scope and agent locations where it already is;--dry-runlists the targets (#220 (opens in new tab))
Internal / MonorepoLink to this section
- Daily Lexical patch watch —
lexical-patch-watch.ymlpacks the newest@payloadcms/richtext-lexicalthe patch key allows and runsgit apply --checkagainst it (#221 (opens in new tab)) - 1.7.0 and 1.7.2 notes amended — the import-export plugin's two job task labels in the
payload_jobsenums, and Payload 3.90's client-upload signing and{prefix}/{_objectKey}/{filename}key for hand-written upload handlers (#223 (opens in new tab), #225 (opens in new tab))
List of all changesLink to this section
🚀 FeaturesLink to this section
cliLink to this section
- feat(cli): key the Lexical patch to a range so Payload patch releases need no Systhema release (#222) (1af2747d (opens in new tab))
🐛 Bug fixesLink to this section
cli, core, payloadLink to this section
- fix(cli,core,payload): third consumer batch for 1.7.3 (#225) (f158ddfe (opens in new tab))
cli, payloadLink to this section
- fix(cli,payload): second consumer batch for 1.7.3 (#223) (db2b6092 (opens in new tab))
- fix(cli,payload): keep pinned projects pinned and make a failed upgrade resumable (#220) (2fccf828 (opens in new tab))
payloadLink to this section
- fix(payload): skip capability renames the enum cannot hold and comment out blocked recreate DDL (#224) (ba32ad72 (opens in new tab))
- fix(payload): keep a published page published when the localize-status migration runs (#219) (e7db4ad6 (opens in new tab))
🧹 ChoresLink to this section
ciLink to this section
- chore(ci): watch the newest Payload release the Lexical patch key allows (#221) (b55119f6 (opens in new tab))