Skip to content

fix(plugin-page-builder,builder): a class id is a key, not a property name - #1874

Merged
mobeenabdullah merged 2 commits into
mainfrom
pb6-rename-attempts-hold-any-class-id
Sep 14, 2026
Merged

mobeenabdullah merged 2 commits into
mainfrom
pb6-rename-attempts-hold-any-class-id

Conversation

@mobeenabdullah

Copy link
Copy Markdown
Collaborator

Why

Codex flagged on #1857 that the rename-attempt counters are a plain object keyed by class id. isUsableNamedClass accepts any id, and for __proto__ the read returns Object.prototype while the write goes through the prototype setter and stores nothing. endRename then never matches, the pending name never clears, and after a refused save retrying the same name is dropped as "no change".

The pending-name record beside it has the same shape one layer out. The class manager panel read pendingSlugs?.[row.id], which for __proto__ is an object, so a name typed away and back was sent as a rename.

What changed

  • useClassSurface (plugin-page-builder) keeps the counters in a Map, and removes a pending name only when the record really holds one (Object.hasOwn).
  • ClassManagerPanel (builder) reads a pending name as the record's own entry, the same Object.hasOwn pattern class-library.ts, instance-inspector.ts and style-values.ts already use. The prop type is unchanged.

Evidence

Reproduced first, as two tests that fail on the old code:

  • class-manager-panel.test.tsx: treats a name typed away and back as no rename, whatever the class id
  • BlocksField.classesPanel.test.tsx: lets a class whose id is proto retry a rename its save refused

Break-verified by writing the old implementation back (mutation confirmed applied, file restored byte-for-byte afterwards):

Wrong implementation Test that failed
the panel indexes the pending record directly (pendingSlugs?.[row.id]) the builder test above
the rename counters are a plain record again the plugin test above

An own-key check on the pending name inside beginRename was tried and dropped: it changes no outcome (a string name never equals the inherited object), so no test could hold it.

Gate Result
builder vitest, from the package 109 files, 3,217 passed (1 new)
plugin-page-builder vitest, from the package 89 files, 1,089 passed (1 new)
pnpm check-types 44 / 44
lint (builder, plugin-page-builder) pass
comment convention pass
changeset lockstep check covers all 26 packages
fallow audit (base origin/main) pass, 5 changed files, nothing introduced

… name

The class manager kept which rename of each class is live on a plain object
keyed by class id. For the id __proto__ the read returned Object.prototype and
the write stored nothing, so a refused rename never released its pending name
and a retry of the same name was dropped as no change. The panel read pending
names the same way, so a name typed away and back was sent as a rename.

The counters are now a Map, and pending names are read as the record's own
entries on both sides of the prop.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T16:31:48.212334Z e2d0e29 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: e98bc25f-283d-477b-966c-aed9ad95993a

📥 Commits

Reviewing files that changed from the base of the PR and between a4238d9 and e2d0e29.

⛔ Files ignored due to path filters (1)
  • .changeset/a-class-id-is-a-key-not-a-property.md is excluded by !.changeset/**
📒 Files selected for processing (4)
  • packages/builder/src/class-manager-panel.test.tsx
  • packages/builder/src/class-manager-panel.tsx
  • packages/plugin-page-builder/src/admin/BlocksField.classesPanel.test.tsx
  • packages/plugin-page-builder/src/admin/BlocksField.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e482605869

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

pendingSlugs: Readonly<Record<string, string>> | undefined,
classId: string
): string | undefined {
if (pendingSlugs === undefined || !Object.hasOwn(pendingSlugs, classId)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep null pending-slug inputs nonfatal

When a JavaScript or loosely typed host passes pendingSlugs={null}, the previous optional-chain lookup treated it like an omitted map, but this condition proceeds to Object.hasOwn(null, classId) and throws as soon as a class row renders, taking down the manager. Include null in the absence guard (or normalize the prop before this call) so the new own-property check preserves the former runtime behavior.

AGENTS.md reference: AGENTS.md:L303-L307

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced and fixed in e2d0e29. Passing pendingSlugs={null} made Object.hasOwn(null, classId) throw while the rows were drawn, where the optional index it replaced answered nothing. pendingSlugFor now reads null as no record, as an omitted one is; the prop's type is unchanged. New test: draws the list when a host passes null for the pending names. With the null guard removed it fails (1 failed | 75 passed), and with it restored the file passes 76/76. Builder suite 3,218 passed, plugin-page-builder 1,089, check-types 44/44, lint, comment convention and fallow all pass.

The pending-name lookup now asks for an own entry, and Object.hasOwn throws on
null where the optional index it replaced answered nothing. The prop's type
excludes null, but a host written in JavaScript can pass it, and the throw took
the whole class manager down while its rows were drawn. null is now read as no
record, as an omitted one is.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: e2d0e299a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

Copy link
Copy Markdown
Contributor

Whole-Repository Code Hygiene Summary

Full dead-code, duplication, and complexity report for the PR branch as it stands now. Playground is excluded. Quality gate enforcement on introduced issues is performed by the Changed files job.

🌿 Fallow

Warning

Review needed

⚠️ 73 code issues · ⚠️ 677 clone groups · ⚠️ 1036 health findings

See inline review comments for per-finding details.

Code issues (73)
Category Count
Unused files 2
Unused exports 5
Unused dependencies 19
Unused devDependencies 6
Unresolved imports 2
Unlisted dependencies 1
Circular dependencies 38
Duplication (677 groups · 28396 lines · 3.9%)
Locations Lines Tokens
schemas/_dialect-bundles/mysql.relations.ts:40-134
schemas/_dialect-bundles/postgres.relations.ts:40-134
schemas/_dialect-bundles/sqlite.relations.ts:40-134
95 593
cli/commands/db-sync-demote.ts:70-75
cli/commands/db-sync-promote.ts:38-43
cli/commands/dev-build.ts:100-105
cli/commands/dev-build.ts:179-184
cli/commands/dev-build.ts:299-304
cli/commands/dev-build.ts:411-416
cli/commands/dev-build.ts:552-557
cli/commands/dev-server.ts:575-580
cli/commands/dev-server.ts:840-845
cli/commands/dev-server.ts:1143-1148
cli/commands/migrate-field-groups.ts:110-115
6 70
entries/EntryList/EntryTableSkeleton.tsx:74-98
collection/components/CollectionTableSkeleton.tsx:94-118
field-group/components/FieldGroupTableSkeleton.tsx:90-114
plugins/components/PluginsTableSkeleton.tsx:86-110
singles/components/SinglesTableSkeleton.tsx:77-101
src/components/table-skeleton.tsx:100-124
25 89
collections/config/validate-config.ts:380-433
field-groups/config/validate-field-group.ts:185-238
singles/config/validate-single.ts:190-243
54 152
dispatcher/handlers/collection-dispatcher.ts:925-967
field-groups/services/field-group-table-provisioning.ts:186-236
singles/services/reconcile-single-companion.ts:110-160
51 149

… and 672 more groups.

Across 425 files.

Complexity (1036 functions above threshold)
File Function Severity Cyclomatic Cognitive CRAP Lines
singles/services/single-mutation-service.ts:966 <arrow> critical 246 ! 308 ! 13317.5 ! 1650
collections/services/collection-mutation-service.ts:6355 <arrow> critical 168 ! 155 ! 6264.4 ! 1296
src/init/reload-config.ts:1417 applyReload critical 143 ! 211 ! 4560 ! 1470
shared/lib/entry-validation.ts:245 validateFieldValue critical 109 ! 157 ! 2675.3 ! 432
dynamic-collections/services/dynamic-collection-schema-service.ts:1050 generateAlterTableMigration critical 104 ! 221 ! 2440.3 ! 782

5131 files, 79870 functions analyzed (thresholds: cyclomatic > 20, cognitive > 15, CRAP >= 30)

Codebase health

Metric Value
Maintainability 91.7 / 100
Avg complexity 1.8

Tip

Run fallow fix --dry-run to preview auto-fixes.
Add /** @public */ above exports to preserve them.

@pkg-pr-new

pkg-pr-new Bot commented Sep 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

npm i https://pkg.pr.new/@nextlyhq/adapter-drizzle@e2d0e29

@nextlyhq/adapter-mysql

npm i https://pkg.pr.new/@nextlyhq/adapter-mysql@e2d0e29

@nextlyhq/adapter-postgres

npm i https://pkg.pr.new/@nextlyhq/adapter-postgres@e2d0e29

@nextlyhq/adapter-sqlite

npm i https://pkg.pr.new/@nextlyhq/adapter-sqlite@e2d0e29

@nextlyhq/admin

npm i https://pkg.pr.new/@nextlyhq/admin@e2d0e29

@nextlyhq/admin-css

npm i https://pkg.pr.new/@nextlyhq/admin-css@e2d0e29

@nextlyhq/blocks-engine

npm i https://pkg.pr.new/@nextlyhq/blocks-engine@e2d0e29

@nextlyhq/blocks-react

npm i https://pkg.pr.new/@nextlyhq/blocks-react@e2d0e29

@nextlyhq/builder

npm i https://pkg.pr.new/@nextlyhq/builder@e2d0e29

create-nextly-app

npm i https://pkg.pr.new/create-nextly-app@e2d0e29

@nextlyhq/eslint-plugin

npm i https://pkg.pr.new/@nextlyhq/eslint-plugin@e2d0e29

nextly

npm i https://pkg.pr.new/nextly@e2d0e29

@nextlyhq/plugin-form-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-form-builder@e2d0e29

@nextlyhq/plugin-mcp

npm i https://pkg.pr.new/@nextlyhq/plugin-mcp@e2d0e29

@nextlyhq/plugin-page-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-page-builder@e2d0e29

@nextlyhq/plugin-sdk

npm i https://pkg.pr.new/@nextlyhq/plugin-sdk@e2d0e29

@nextlyhq/plugin-seo

npm i https://pkg.pr.new/@nextlyhq/plugin-seo@e2d0e29

@nextlyhq/storage-s3

npm i https://pkg.pr.new/@nextlyhq/storage-s3@e2d0e29

@nextlyhq/storage-uploadthing

npm i https://pkg.pr.new/@nextlyhq/storage-uploadthing@e2d0e29

@nextlyhq/storage-vercel-blob

npm i https://pkg.pr.new/@nextlyhq/storage-vercel-blob@e2d0e29

@nextlyhq/ui

npm i https://pkg.pr.new/@nextlyhq/ui@e2d0e29

commit: e2d0e29

@github-actions github-actions Bot added type: docs Documentation only scope: plugin @nextlyhq/plugin-* packages labels Sep 13, 2026
@mobeenabdullah
mobeenabdullah merged commit ce962c7 into main Sep 14, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: plugin @nextlyhq/plugin-* packages type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant