fix(cms): make the admin panel usable in a deployed container #142

Closed
tudor wants to merge 1 commits from fix/payload-initial-migration into main
pull from: fix/payload-initial-migration
Owner

Two bugs that each made the CMS unusable outside the throwaway database it was developed against. Both were silent in different ways.

The container had no tables

Staging failed on boot with 42P01, relation "payload.users" does not exist. No migration existed, and the adapter cannot create tables itself: db-postgres/connect.js gates push on NODE_ENV !== 'production', so it is inert in a deployed container regardless of config.

The generated migration is schema-qualified to payload throughout but does not create that schema. schemaName says where tables go, it does not create anything. It only worked against the database used to generate it because the schema had been created there by hand, so every real environment would have failed on the first statement. CREATE SCHEMA IF NOT EXISTS is hand-added at the top of up(), which makes it exactly the kind of edit a regeneration discards silently, so a test asserts it is present and ordered before the first CREATE TABLE.

payload-types.ts is now committed rather than ignored. Ignoring it meant CI typechecked against looser types than a developer with a generated copy, which is how a Record<string, unknown> cast passed CI and then failed locally the moment the file appeared.

The Content field did not render

Creating a post showed no Content editor, and saving then failed validation on a field the writer was never shown.

The admin panel does not import field components. The server hands the client a path per field and resolves it through the generated map at app/(payload)/admin/importMap.js; a richText field's is @payloadcms/richtext-lexical/rsc#RscEntryLexicalField. The committed map held a single entry, @payloadcms/next/rsc#CollectionCards, generated before the blog collections existed and never re-run. A path missing from the map is not an error the panel reports. The field simply does not render, while required is still enforced server-side on save.

next build does not regenerate the map, so the stale copy shipped in the image and the editor was equally broken on staging and production.

Regenerating adds the lexical RSC field, cell and diff components, BlocksFeatureClient for the Callout block, and the default toolbar features. Two things stop it drifting again: there was no script to run, so package.json gets generate:importmap, and a test asserts the map carries an entry for each thing the config asks for. Against the old map all five of those tests fail.

Verification

  • tsc --noEmit clean.
  • 404 tests across 48 suites pass, including the four __tests__/payload/ suites.
  • next build succeeds with DATABASE_URL unset, as CI builds it.

No e2e/ change. The journeys cover the public blog and assert /admin is noindex; the authenticated editor is not reachable from them, and no public behaviour changes here. The real proof is creating a post in the staging panel once this is deployed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DXnXQKnPpZBBP61fBQiFkq

Two bugs that each made the CMS unusable outside the throwaway database it was developed against. Both were silent in different ways. ## The container had no tables Staging failed on boot with `42P01, relation "payload.users" does not exist`. No migration existed, and the adapter cannot create tables itself: `db-postgres/connect.js` gates push on `NODE_ENV !== 'production'`, so it is inert in a deployed container regardless of config. The generated migration is schema-qualified to `payload` throughout but does not create that schema. `schemaName` says where tables go, it does not create anything. It only worked against the database used to generate it because the schema had been created there by hand, so every real environment would have failed on the first statement. `CREATE SCHEMA IF NOT EXISTS` is hand-added at the top of `up()`, which makes it exactly the kind of edit a regeneration discards silently, so a test asserts it is present and ordered before the first `CREATE TABLE`. `payload-types.ts` is now committed rather than ignored. Ignoring it meant CI typechecked against looser types than a developer with a generated copy, which is how a `Record<string, unknown>` cast passed CI and then failed locally the moment the file appeared. ## The Content field did not render Creating a post showed no Content editor, and saving then failed validation on a field the writer was never shown. The admin panel does not import field components. The server hands the client a *path* per field and resolves it through the generated map at `app/(payload)/admin/importMap.js`; a richText field's is `@payloadcms/richtext-lexical/rsc#RscEntryLexicalField`. The committed map held a single entry, `@payloadcms/next/rsc#CollectionCards`, generated before the blog collections existed and never re-run. A path missing from the map is not an error the panel reports. The field simply does not render, while `required` is still enforced server-side on save. `next build` does not regenerate the map, so the stale copy shipped in the image and the editor was equally broken on staging and production. Regenerating adds the lexical RSC field, cell and diff components, `BlocksFeatureClient` for the Callout block, and the default toolbar features. Two things stop it drifting again: there was no script to run, so `package.json` gets `generate:importmap`, and a test asserts the map carries an entry for each thing the config asks for. Against the old map all five of those tests fail. ## Verification - `tsc --noEmit` clean. - 404 tests across 48 suites pass, including the four `__tests__/payload/` suites. - `next build` succeeds with `DATABASE_URL` unset, as CI builds it. No `e2e/` change. The journeys cover the public blog and assert `/admin` is noindex; the authenticated editor is not reachable from them, and no public behaviour changes here. The real proof is creating a post in the staging panel once this is deployed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DXnXQKnPpZBBP61fBQiFkq
tudor added 1 commit 2026-09-02 19:43:37 +00:00
fix(cms): regenerate the import map so the Content field renders
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 8s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m9s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m4s
b2a3f32c62
Creating a post in the admin panel showed no Content editor, and saving
failed validation on the field the writer was never shown.

The admin panel does not import field components. The server hands the
client a path per field, and resolves it through the generated map at
app/(payload)/admin/importMap.js. A richText field's path is
@payloadcms/richtext-lexical/rsc#RscEntryLexicalField. The committed map
held one entry, @payloadcms/next/rsc#CollectionCards, generated before
the blog collections existed and never re-run. A path missing from the
map is not an error the panel reports: the field simply does not render,
while required is still enforced server-side on save.

next build does not regenerate the map, so the stale copy shipped in the
image and the editor was equally broken on staging and production.

Regenerated with payload generate:importmap, which adds the lexical RSC
field, cell and diff components, BlocksFeatureClient for the Callout
block, and the default toolbar features.

Two things stop it drifting again. There was no script to run, so
package.json gets generate:importmap. And a test asserts the map carries
an entry for each thing the config asks for, in the source-reading style
of the other payload suites; against the old map all five fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXnXQKnPpZBBP61fBQiFkq
tudor closed this pull request 2026-09-02 19:44:26 +00:00

🤖 AI Code Review (Claude Code)

This PR documents the Payload admin import-map gotcha (a missing entry silently drops a field's editor while server-side required still blocks saving), adds a regression test that greps importMap.js and Posts.ts to catch drift, adds a generate:importmap npm script, and includes the regenerated importMap.js with all the richtext-lexical RSC/client feature entries. Verified the test's file paths resolve correctly and its assertions (richText field, drafts:true, BlocksFeature usage) match the current Posts.ts content, so the test is correct and will pass. No correctness, security, or deploy issues found.

✅ No issues found.

## 🤖 AI Code Review (Claude Code) This PR documents the Payload admin import-map gotcha (a missing entry silently drops a field's editor while server-side `required` still blocks saving), adds a regression test that greps importMap.js and Posts.ts to catch drift, adds a `generate:importmap` npm script, and includes the regenerated importMap.js with all the richtext-lexical RSC/client feature entries. Verified the test's file paths resolve correctly and its assertions (richText field, drafts:true, BlocksFeature usage) match the current Posts.ts content, so the test is correct and will pass. No correctness, security, or deploy issues found. ✅ No issues found.

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tudor/school_compare#142