Skip to content

fix(builder,plugin-page-builder): a rename outlives the panel it started in - #1857

Merged
mobeenabdullah merged 1 commit into
mainfrom
pb6-rename-outlives-the-panel
Sep 13, 2026
Merged

mobeenabdullah merged 1 commit into
mainfrom
pb6-rename-outlives-the-panel

Conversation

@mobeenabdullah

Copy link
Copy Markdown
Collaborator

Three Codex findings on merged #1367. Two fixed, one already fixed on main
verified rather than re-fixed.

What an author sees today

The class manager lives in a rail that unmounts it on every panel switch, and
that switch is exactly what makes two renames of one class overlap: rename, move
away, come back, rename again, and the first write is still on the network.

A revert is silently dropped. The panel judges an edit against the name a
class is heading for. Cleanup released that name unconditionally, so an earlier
rename finishing under a later one released it for both — and typing the original
back then reads as "nothing changed" against the name still on screen. Nothing is
written, and the later rename goes on to persist a different name.

A stale refusal is raised for an edit that no longer exists. Whether an answer
still describes the rename being awaited was decided by a counter kept inside the
field. The unmount destroys it, so on the way back it starts again at zero and the
first answer to arrive matches the newer attempt's number.

The fix

One identity, owned by the host, serving both — they are the same question asked
twice: which of these attempts is the one still being awaited. The host takes it
inside onRename, and the panel reads it straight after that call.

currentRenameAttempt is a required prop rather than an optional one, so no
consumer can silently keep the version that does not survive a remount. That is a
boundary rather than a note, which is what AGENTS.md asks for where the correct
path and the easy path differ.

The third finding

Already fixed on main: the synchronous-refusal branch reports it, with a docblock
explaining why that path needs no supersession check. Re-verified against today's
code rather than taken from the filing, which predates it.

Evidence

Two mutants, each killing exactly its own test:

  • releasing the pending name unconditionally → kills keeps the pending name when an EARLIER rename settles under a later one;
  • moving the identity back into the field → kills does not raise a refusal the author has already renamed past.

The first test needed a two-stage release to build the window at all: a single
release lets both renames finish, and then no pending name is owed to anyone and
the no-op is correct. The first version passed for that reason.

One test was written and then removed. A remount case asserted from the
panel's own DOM passes with the defect reinstated — an unmounted field's
setRefused reaches nothing either way, so the defect and the fix look identical
there. The refusal goes to the notice sink, so the case lives in
builder-notices.test.tsx where one is rendered, and the panel suite says so
where the next reader will look.

The shared test harness also gained a real counting identity. A constant stub
makes every answer look current and quietly removes the supersession these tests
are about — it broke the existing out-of-order test the moment it was introduced,
which is how it was caught.

Gates: check-types 0, lint 0, builder 3216 passed, page-builder 1086
passed, fallow pass with all four introduced counts at 0.

…ted in

Two Codex findings on merged #1367, both reachable through the panel switch and
both decided from state that switch destroys.

The PENDING NAME is released only by the attempt that is still the live one.
Cleanup ran unconditionally, so an earlier rename finishing under a later one
released the name for both — and the panel judges the next edit against the name
a class is heading for, so with it gone an author typing the original back is
read as "nothing changed" and dropped, while the later rename persists a
different name.

The SUPERSESSION IDENTITY moves to the host. Kept in a ref inside the field, it
was destroyed by the rail unmounting the manager and started again at zero, so
the first answer to arrive after a switch matched the newer attempt's number: a
refusal for a rename already replaced was raised from the shell, naming an edit
that no longer existed.

One identity serves both, because they are the same question asked twice — which
of these attempts is the one still being awaited. The host takes it inside
`onRename`, and the panel reads it straight after that call; a REQUIRED prop
rather than an optional one, so no consumer can silently keep the version that
does not survive.

The third finding on that PR — a synchronous refusal being dropped before it
could be reported — is already fixed on main, with a docblock explaining why the
synchronous path needs no supersession check. Verified rather than re-fixed.

One test was written and then removed rather than kept: a remount case asserted
from the panel's own DOM passes with the defect reinstated, because an unmounted
field's `setRefused` reaches nothing either way. The refusal goes to the notice
sink, so the case lives in `builder-notices.test.tsx` where one is rendered, and
this file says so.
@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-13T12:45:59.367173Z c018706 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 25 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: 056ff0db-6b7b-45c0-abe8-16af6169c23c

📥 Commits

Reviewing files that changed from the base of the PR and between 445f47c and c018706.

⛔ Files ignored due to path filters (1)
  • .changeset/a-rename-survives-the-panel.md is excluded by !.changeset/**
📒 Files selected for processing (5)
  • packages/builder/src/builder-notices.test.tsx
  • 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: c01870628d

ℹ️ 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".

Comment on lines +1178 to +1179
const mine = (renameAttempts.current[classId] ?? 0) + 1;
renameAttempts.current[classId] = mine;

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 Use prototype-safe storage for rename attempts

When a valid named class has the ID __proto__ (isUsableNamedClass accepts any string ID up to 128 characters), this reads the inherited prototype accessor instead of 0; mine consequently becomes a string, and the assignment invokes the prototype setter rather than storing a counter. endRename can then never match the attempt, leaving the pending slug permanently set, so after a refused save retrying the same slug is discarded as a no-op. Use a Map or null-prototype record for these counters.

AGENTS.md reference: AGENTS.md:L303-L306

Useful? React with 👍 / 👎.

@mobeenabdullah
mobeenabdullah merged commit 9bac152 into main Sep 13, 2026
20 of 21 checks passed
@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 · ⚠️ 1035 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 (1035 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:995 generateAlterTableMigration critical 103 ! 220 ! 2394.5 ! 764

5122 files, 79568 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.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

The post-merge Codex finding on this PR (rename-attempt counters on a plain record, so a class id of __proto__ never releases its pending name) is fixed in #1874. That PR also reads the pending-name record as the record's own entries in the class manager panel, which had the same shape.

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