Skip to content

feat(builder): name and lock a block from the inspector - #1018

Merged
mobeenabdullah merged 1 commit into
mainfrom
feat/block-identity-inspector
Aug 18, 2026
Merged

mobeenabdullah merged 1 commit into
mainfrom
feat/block-identity-inspector

Conversation

@mobeenabdullah

Copy link
Copy Markdown
Collaborator

The layers panel displays a block's name and its lock. Nothing in the editor could set either — so the panel showed information the editor had no way to produce. This is the writer for both, and it completes the loop #1013 and #1016 started.

Where they live, and why not the layers row

Plan 04's B-13 says "explicit-gesture rename; hover eye+lock", which reads as controls in the row. I put them in the inspector instead:

  • A control inside a role="treeitem" complicates the roving-tabindex model TreeView owns, and that component's accessibility is the half most likely to rot if fought.
  • The inspector already answers "change the selected block", and these are block-level properties.
  • The panel reflects the result immediately anyway, because it reads the same document. Gutenberg puts block name and lock in the inspector for the same reason.

Clearing UNSETS rather than storing a falsy value

Both fields are optional, so absent is already what "no name" and "not locked" mean everywhere else. Storing "" or false would create two spellings of one state, and layerLabel would have to know about both to avoid rendering a blank row.

Releasing a lock by writing false has a second cost: locked is absent on every node in every document written so far, so an unlock would become a write to every block an author ever touches, adding a field that means what its absence already meant.

A name is trimmed — a name of spaces passes a non-empty check, renders as nothing, and cannot be reached by the panel's typeahead.

A drift I reintroduced this morning and have now removed

The announcements were still calling a block by its type while the panel and breadcrumb used the name its author gave it:

layers:       "Hero title"
breadcrumb:   "Hero title"
announcement: "Heading is locked. Unlock it to move it."     ← the only one out of step

That is exactly the drift blockLabel was extracted to remove, reappearing one layer up — and the announcement is the one surface a screen-reader user hears. deletionAnnouncement now takes a resolved name rather than a type, so the rule stays in layerLabel instead of gaining a second resolution inside the phrasing.

Verified end to end

rename in the inspector  →  layers ["Hero title","Text"]   breadcrumb ["Hero title"]
lock in the inspector    →  layers ["Hero titleLocked","Text"]
alt+Down  → refused | "Hero title is locked. Unlock it to move it."
Delete    → refused | "Hero title is locked. Unlock it to delete it."
unlock    → moves again | "Block moved"
pageerrors: []

Tests

636 in @nextlyhq/builder (was 620), 61 in plugin-page-builder, 30/30 tasks. Four stub-verifications on the unset and trim rules, each failing exactly its intended tests.

inspector-panel.tsx had no test file at all. This adds one, scoped to the identity fields and saying so in its header — the prop controls below remain uncovered, and a file that exists otherwise reads as a file that covers the module.

One thing worth flagging from writing it: my first version of the "shows the stored lock" case contained expect(...).toBeChecked; — the property form, which asserts nothing. It threw because this package does not register jest-dom, which is the only reason I noticed. A no-op assertion that happened to be valid syntax would have shipped as coverage.

The layers panel displays a block's name and its lock, and nothing in the
editor could set either — the panel showed information the editor had no way
to produce. This is the writer for both.

They live in the inspector rather than in the layers row. A control inside a
`role="treeitem"` complicates the roving-tabindex model `TreeView` owns, and
the inspector already answers "change the selected block"; the panel reflects
the result because it reads the same document.

Clearing a name and releasing a lock UNSET the field rather than storing `""`
or `false`. Both fields are optional, so absent is already what "no name" and
"not locked" mean everywhere else — storing the falsy value would make two
spellings of one state, and `layerLabel` would have to know about both to avoid
rendering a blank row. Releasing a lock by writing `false` would also add a
field to every block an author ever touched, meaning what its absence meant.

A name is trimmed, because a name of spaces satisfies a non-empty check,
renders as nothing, and cannot be reached by the layers panel's typeahead.

The announcements now use that name too. They were the one surface still
calling a block by its type while the panel and the breadcrumb used the name
the author gave it — the same drift `blockLabel` was extracted to remove,
reappearing one layer up. `deletionAnnouncement` takes a resolved name rather
than a type so the rule stays in one place instead of gaining a second
resolution inside the phrasing.

`inspector-panel.tsx` had no test file at all; this adds one, scoped to the
identity fields and saying so, because a file that exists reads as a file that
covers the module.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@mobeenabdullah, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

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

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 24e98c0c-491b-44e2-a54e-448d1884afc9

📥 Commits

Reviewing files that changed from the base of the PR and between 15cad99 and 44fad04.

⛔ Files ignored due to path filters (1)
  • .changeset/name-and-lock-a-block.md is excluded by !.changeset/**
📒 Files selected for processing (7)
  • packages/builder/src/inspector-panel.test.tsx
  • packages/builder/src/inspector-panel.tsx
  • packages/builder/src/inspector.test.ts
  • packages/builder/src/inspector.ts
  • packages/builder/src/keyboard-actions.test.tsx
  • packages/builder/src/keyboard-actions.tsx
  • packages/builder/src/styles/builder-chrome.css

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@mobeenabdullah
mobeenabdullah merged commit 06f8e7a into main Aug 18, 2026
13 checks passed
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds inspector controls for naming and locking selected blocks, with trimmed/unset operation semantics and synchronized accessible labels.

  • Adds name and lock identity controls backed by undoable editor operations.
  • Uses author-defined block names in deletion, lock, and movement announcements.
  • Adds focused inspector and keyboard-action coverage plus inspector styling.
  • Includes the required lockstep patch changeset.

Confidence Score: 5/5

The PR appears safe to merge with no actionable correctness or security issues identified.

The new identity controls produce normalized editor operations, remain synchronized with document state, and the announcement changes resolve reachable nodes through the shared display-label logic.

Important Files Changed

Filename Overview
packages/builder/src/inspector-panel.tsx Adds controlled name and lock fields with selection-scoped state and editor operation wiring; no actionable defect identified.
packages/builder/src/inspector.ts Exposes block identity during inspection and constructs trimmed rename and lock/unlock operations using unset semantics.
packages/builder/src/keyboard-actions.tsx Resolves instance-aware labels for deletion and lock announcements while preserving existing keyboard behavior.
packages/builder/src/inspector-panel.test.tsx Covers stored identity rendering, commit timing, no-op avoidance, unset behavior, and no-selection rendering.
packages/builder/src/styles/builder-chrome.css Visually separates identity controls from editable block properties.

Reviews (1): Last reviewed commit: "feat(builder): name and lock a block fro..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Fallow audit report

Found 40 findings.

Details
Severity Rule Location Description
major fallow/unused-dependency apps/playground/package.json:24 Package '@nextlyhq/adapter-drizzle' is in dependencies but never imported; imported in other workspaces: packages/adapter-mysql, packages/adapter-postgres, packages/adapter-sqlite, packages/nextly
major fallow/unused-dependency apps/playground/package.json:25 Package '@nextlyhq/adapter-mysql' is in dependencies but never imported; imported in other workspaces: packages/nextly
major fallow/unused-dependency apps/playground/package.json:26 Package '@nextlyhq/adapter-postgres' is in dependencies but never imported; imported in other workspaces: packages/nextly
major fallow/unused-dependency apps/playground/package.json:27 Package '@nextlyhq/adapter-sqlite' is in dependencies but never imported; imported in other workspaces: packages/nextly
major fallow/unused-dependency apps/playground/package.json:42 Package 'sharp' is in dependencies but never imported; imported in other workspaces: packages/nextly
minor fallow/unused-dev-dependency apps/playground/package.json:45 Package '@eslint/eslintrc' is in devDependencies but never imported
minor fallow/unused-dev-dependency apps/playground/package.json:54 Package 'better-sqlite3' is in devDependencies but never imported; imported in other workspaces: packages/adapter-sqlite, packages/nextly
minor fallow/unused-dev-dependency apps/playground/package.json:55 Package 'culori' is in devDependencies but never imported; imported in other workspaces: packages/ui
minor fallow/unused-dev-dependency e2e/package.json:17 Package '@nextlyhq/adapter-drizzle' is in devDependencies but never imported; imported in other workspaces: packages/adapter-mysql, packages/adapter-postgres, packages/adapter-sqlite, packages/nextly
minor fallow/unused-dev-dependency e2e/package.json:18 Package '@nextlyhq/adapter-sqlite' is in devDependencies but never imported; imported in other workspaces: packages/nextly
minor fallow/unused-dev-dependency e2e/package.json:22 Package '@nextlyhq/plugin-form-builder' is in devDependencies but never imported; imported in other workspaces: apps/playground
minor fallow/unused-dev-dependency e2e/package.json:23 Package '@nextlyhq/plugin-page-builder' is in devDependencies but never imported; imported in other workspaces: apps/playground
minor fallow/dev-dependency-in-production package.json:92 devDependency '@manypkg/get-packages' is imported by production code at runtime (consider moving to dependencies)
minor fallow/dev-dependency-in-production package.json:104 devDependency 'micromatch' is imported by production code at runtime (consider moving to dependencies)
minor fallow/unused-dev-dependency package.json:106 Package 'publint' is in devDependencies but never imported
major fallow/unused-dependency packages/admin/package.json:110 Package '@lexical/history' is in dependencies but never imported
major fallow/unused-dependency packages/admin/package.json:119 Package '@radix-ui/react-accordion' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:120 Package '@radix-ui/react-alert-dialog' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:121 Package '@radix-ui/react-avatar' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:122 Package '@radix-ui/react-checkbox' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:123 Package '@radix-ui/react-collapsible' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:124 Package '@radix-ui/react-dialog' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:125 Package '@radix-ui/react-dropdown-menu' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:127 Package '@radix-ui/react-popover' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:128 Package '@radix-ui/react-radio-group' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:129 Package '@radix-ui/react-select' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:130 Package '@radix-ui/react-separator' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:132 Package '@radix-ui/react-switch' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:133 Package '@radix-ui/react-tabs' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:134 Package '@radix-ui/react-tooltip' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:140 Package 'cmdk' is in dependencies but never imported; imported in other workspaces: packages/ui
major fallow/unused-dependency packages/admin/package.json:149 Package 'tailwind-variants' is in dependencies but never imported
major fallow/unused-dependency packages/admin/package.json:150 Package 'tailwindcss-animate' is in dependencies but never imported
minor fallow/unused-dev-dependency packages/builder/package.json:68 Package '@nextlyhq/plugin-sdk' is in devDependencies but never imported; imported in other workspaces: apps/playground, packages/plugin-form-builder, packages/plugin-page-builder, packages/plugin-sdk, packages/plugin-seo
major fallow/unused-dependency packages/plugin-page-builder/package.json:51 Package '@dnd-kit/dom' is in dependencies but never imported
major fallow/unused-dependency packages/plugin-page-builder/package.json:52 Package '@dnd-kit/react' is in dependencies but never imported
major fallow/unused-dependency packages/plugin-page-builder/package.json:56 Package 'isomorphic-dompurify' is in dependencies but never imported; imported in other workspaces: packages/nextly
minor fallow/unused-dev-dependency packages/plugin-page-builder/package.json:63 Package '@tanstack/react-query' is in devDependencies but never imported; imported in other workspaces: packages/admin, packages/plugin-form-builder
minor fallow/unused-dev-dependency packages/plugin-page-builder/package.json:72 Package '@dnd-kit/abstract' is in devDependencies but never imported
major fallow/unused-dependency packages/telemetry/package.json:32 Package 'env-paths' is in dependencies but never imported

Generated by fallow.

@github-actions github-actions Bot added the type: docs Documentation only label Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant