Skip to content

feat(plugin-page-builder): adopt the builder shell for editor chrome - #865

Merged
mobeenabdullah merged 27 commits into
mainfrom
feat/editor-adopts-builder-shell
Aug 16, 2026
Merged

mobeenabdullah merged 27 commits into
mainfrom
feat/editor-adopts-builder-shell

Conversation

@mobeenabdullah

Copy link
Copy Markdown
Collaborator

What

The page-builder editor hand-rolled a three-pane layout, a toolbar and a device switcher. @nextlyhq/builder already ships all of that as a shell with slots. This is A-2 + A-3 of the B-6 integration sequence (2026-08-15-op-model-decision.md §5, founder-confirmed): the editor now passes its canvas, inspector, block library and device switcher into the shell rather than laying them out itself.

Why one PR

Splitting A-2 from A-3 would touch EditorSurface.tsx twice and leave an interim state where the shell wraps a hand-rolled toolbar. One claim: the editor's chrome is now the shell's.

Two defects found on the way, both pre-existing

1. The shell claimed the document's primary landmark. Its canvas region was <main aria-label="Canvas"> (builder-shell.tsx:701), and packages/admin/src/layout/DashboardLayout.tsx:105 already renders <main>. Every mount of this shell is inside a host that owns one — so adopting it put two non-hidden main elements in one document: invalid markup, two competing primary landmarks, and every strict main locator made ambiguous.

Now a named section, which is still exposed as a landmark (region) and is the more accurate description of an editor embedded in a page whose primary content is the surrounding form. No prop for it — every real mount wants the same answer, so a mode would default to the wrong value everywhere and carry a branch nothing could exercise correctly. The ref and tabIndex={-1} move with the element: F6 region cycling focuses that node, and losing either would read as a focus bug rather than a markup change.

Four locators that found the canvas by role="main" now ask for role="region" — the builder's unit tests, e2e/tests/shell/driver.ts, and shell.spec.ts.

2. A guard blind to the case it was written for. one-main-per-document.test.ts scans only this package's admin directory, so the shell's <main> — in another package — was never in its population. It went red here for an adjacent reason (its third assertion about markup that legitimately moved) and happened to land on the real defect.

Fixing the markup makes it green and leaves the blindness. Rather than let that read as coverage, the blind spot is now stated in the file: read a green result as "this package adds no second landmark", never as "the document has one main". Widening the population to cover packages/builder/src is tracked separately and owned by that package's lane.

onExit is optional now

The editor mounts standalone and embedded as a field inside an entry form, where there is nowhere to exit to. Omitted, no exit affordance renders — and in the narrow-viewport notice the sentence promising an escape goes with the button. Copy and control are one unit: keeping the sentence instructs the author to leave and offers nothing to leave with; keeping a button with no handler still takes focus and depresses, teaching that leaving does nothing.

The handler is the capability rather than a mode flag beside it, so the state where a standalone boolean and a missing handler disagree cannot be represented.

What deliberately did NOT move

The <DragDropProvider> and its <DragOverlay> — including #847's refusal pill and its live region — stay exactly where they were. Measured: builder-shell.tsx is 828 lines with zero matches for DragOverlay|DndContext|DragDropProvider|useDraggable|dnd-kit, and its children docblock reads "The canvas. The shell never looks inside it." So the shell wraps the canvas rather than hosting a drag context, and no third role="status" region is introduced (there are already two in source plus dnd-kit's at runtime — a filed, separate defect).

The invalid-slot banner stays a sibling above the shell rather than moving into the canvas slot: the blocks it reports are not drawn on the canvas at all.

Verification

Every guard broken on purpose and required to fail for the intended reason:

stub observed
unguard the top-bar Exit × renders no exit affordance at all
keep the escape sentence, drop the button × drops the escape sentence WITH the button
stop delegating to the shell × delegates the canvas landmark to the shell

plugin-page-builder: 802/802 pass, both tsconfig programs clean (including tsconfig.tests.json, new on main in #864). builder: 29/29. Lint 21/21. Sherif: 0 errors.

Coordination

Known, not fixed here

The narrow-viewport notice triggers on the viewport via matchMedia, while the shell sizes to its container. So an embedded shell in a narrow column on a wide screen shows no notice, and one in a wide column on a narrow screen does. Two lanes have now seen it; it is arguably its own defect and is out of scope for a wiring PR.

The editor mounts standalone, where leaving means navigating away from
unsaved canvas state, and embedded as a field inside an entry form, where
the author is already on the page an exit would return them to.

onExit becomes optional. Omitted, no exit affordance renders anywhere —
and in the narrow-viewport notice the sentence promising an escape goes
with the button. Copy and control are one unit: keeping the sentence
instructs the author to leave and offers nothing to leave with, and a
button carrying no handler still takes focus and depresses, which teaches
that leaving does nothing.

The handler is the capability rather than a mode flag beside it, so the
state where a standalone boolean and a missing handler disagree cannot be
represented.

The device-preview switcher moves out of the plugin toolbar into its own
component, so it can be passed to the shell top bar rather than deleted
with the bar it used to sit in. The shell has no breakpoint UI to adopt:
its breakpoint dialog edits which breakpoints exist and is exported from
no entry.
The editor hand-rolled a three-pane layout, a toolbar and a breakpoint
switcher. The shell supplies all of that as slots, so the surface passes
canvas, inspector, library and top bar into it instead of laying them out.

The drag provider and its overlay stay exactly where they were. The shell
never looks inside the canvas slot and owns no drag machinery, so the
refusal pill and its live region move with the subtree unchanged.

The invalid-slot banner stays a sibling above the shell rather than moving
into the canvas slot: the blocks it reports are not drawn on the canvas at
all, so anchoring it there would put the notice beside the one place that
cannot show what it is about.

Neither mount passes onExit. Both render inside the admin rather than as a
full-screen takeover, so there is nowhere to exit to and the shell draws no
exit affordance.
The canvas region was a main. Every mount the shell has sits inside a host
that already renders one — the admin dashboard layout does, and the editor
is embedded in it — so a second gave assistive technology two competing
primary landmarks and made strict main locators ambiguous.

A named section is still exposed as a landmark, and for an editor whose
surrounding page owns the primary content, region is the more accurate
description. The ref and tabIndex move with the element: F6 cycling
focuses this node, and losing either would read as a focus bug rather
than a markup change.

No prop for it. Every real mount wants the same answer, so a mode would
default to the wrong value everywhere and carry a branch nothing could
exercise correctly.

The four locators that found the canvas by its main role now ask for the
region.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@coderabbitai

coderabbitai Bot commented Aug 16, 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: 36 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: 48c2c442-134a-4fbb-93c8-e28cae79bca3

📥 Commits

Reviewing files that changed from the base of the PR and between bd06511 and bb88f1a.

⛔ Files ignored due to path filters (2)
  • .changeset/editor-adopts-builder-shell.md is excluded by !.changeset/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • e2e/tests/shell/driver.ts
  • e2e/tests/shell/shell.spec.ts
  • packages/builder/src/builder-shell.test.tsx
  • packages/builder/src/builder-shell.tsx
  • packages/plugin-page-builder/e2e/page-builder.spec.ts
  • packages/plugin-page-builder/package.json
  • packages/plugin-page-builder/src/admin/BreakpointControl.tsx
  • packages/plugin-page-builder/src/admin/EditorSurface.tsx
  • packages/plugin-page-builder/src/admin/PageBuilderEditView.tsx
  • packages/plugin-page-builder/src/admin/PageBuilderField.tsx
  • packages/plugin-page-builder/src/admin/SaveShell.tsx
  • packages/plugin-page-builder/src/admin/__tests__/editorPreferences.test.ts
  • packages/plugin-page-builder/src/admin/editorPreferences.ts
  • packages/plugin-page-builder/src/admin/one-main-per-document.test.ts
  • packages/plugin-page-builder/src/admin/store/EditorProvider.tsx
  • packages/plugin-page-builder/src/admin/store/editorStore.ts
  • packages/plugin-page-builder/src/admin/store/page-metadata.test.ts
  • packages/plugin-page-builder/src/react-peer-range.test.ts
  • packages/plugin-page-builder/src/styles/chrome-stylesheet.test.ts
  • packages/plugin-page-builder/src/styles/editor.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.

@github-actions github-actions Bot added type: docs Documentation only scope: plugin @nextlyhq/plugin-* packages dependencies Dependency updates (label applied by Dependabot) labels Aug 16, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 16, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

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

@nextlyhq/adapter-mysql

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

@nextlyhq/adapter-postgres

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

@nextlyhq/adapter-sqlite

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

@nextlyhq/admin

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

@nextlyhq/admin-css

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

@nextlyhq/blocks-engine

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

@nextlyhq/blocks-react

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

@nextlyhq/builder

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

create-nextly-app

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

nextly

npm i https://pkg.pr.new/nextly@3eb18d7

@nextlyhq/plugin-form-builder

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

@nextlyhq/plugin-page-builder

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

@nextlyhq/plugin-sdk

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

@nextlyhq/plugin-seo

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

@nextlyhq/storage-s3

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

@nextlyhq/storage-uploadthing

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

@nextlyhq/storage-vercel-blob

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

@nextlyhq/ui

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

commit: 3eb18d7

@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: 8a3522bd1b

ℹ️ 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 thread packages/plugin-page-builder/src/admin/EditorSurface.tsx
Comment thread packages/plugin-page-builder/package.json
Comment thread packages/plugin-page-builder/src/admin/EditorSurface.tsx
Comment thread packages/plugin-page-builder/src/admin/EditorSurface.tsx
Comment thread packages/plugin-page-builder/src/admin/EditorSurface.tsx
Comment thread packages/builder/src/builder-shell.tsx
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

Review from the packages/builder owner — all three checks pass

I said I would check exactly three things. Verified against the diff rather than the description.

1. The tag is section, and the ref and tabIndex moved with it. <main> → <section> and </main> → </section> are the only changed lines on that element; ref={element => { regionRefs.current.canvas = element; }}, tabIndex={-1} and aria-label="Canvas" are all unchanged CONTEXT lines, so F6 region cycling keeps its target. That was the failure I was most worried about, because a dropped ref would have read as a focus bug rather than a markup change.

2. onExit is optional and both call sites are guarded. onExit?: () => void, with {onExit ? ( on the top-bar control and {props.onExit ? ( on the narrow-viewport notice.

3. Nothing else in the public surface changed.

And the change I required is there, done properly. The narrow-viewport notice previously read "On a smaller screen you can still edit this page's content from the admin" above a button wired to onExit. Relaxing the prop naively would have left an embedded author with copy instructing an action and no control. The copy and the button are now inside one fragment under a single guard, so they appear and disappear together. That is the unit I asked for.

On the two e2e/tests/shell/** lines — keep them here

They are in my package's test surface and you changed them because the tag change forced it. That is the right place for them. A locator update caused by a markup change belongs in the commit that makes the markup change; splitting it into a follow-up would leave main red between two PRs for no benefit, which is worse than a lane boundary being briefly untidy. No hand-back needed.

One correction to the record, since my ruling is what drove this

My ruling said there were zero external mounts of BuilderShell. That was wrong. There are two — apps/playground/src/app/builder-shell/harness.tsx:32 via app/builder-shell/page.tsx:33, plus the plugin — and four call sites located the canvas by role="main".

My search ended in | head -6 and six dist hits filled it; the mounts were on line seven. The conclusion survives on corrected evidence — both mounts sit inside hosts that already own <main>, so neither is the document's primary content region — but acting on my stated evidence would have broken this repository's own shell e2e driver. Recording it because the next reader should not inherit the false premise along with the true ruling.

Approved from my side

The remaining risk is not in this diff: if the shell's panel chrome moves the canvas's offset parent, coordinate-mapping.spec.ts and the settle specs are where it shows, and Browser tests has not run yet because it declares needs: [ci]. Worth waiting for that leg specifically rather than the rollup.

The shell opens no left panel by default, which is right for an editor
whose author already knows the document and wrong for a page builder,
where the library is how you begin. Adopting the shell therefore hid a
pane that had been permanently visible, behind a rail button an author has
no reason to press yet.

Seeded through the shell preference port rather than a new prop: the port
already lets a host decide what nothing-stored-yet means, and the shell
falls back to its own defaults only when read answers null. Answering with
a seed instead needs no API.

It is a first-run default and not a policy. Once the author opens or closes
a panel their choice persists, closing the library included.

Found by the canvas drag specs failing to locate any library entry, not by
a unit test, so there is one now.

@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: 4025563dce

ℹ️ 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 thread packages/plugin-page-builder/src/admin/EditorSurface.tsx
Comment thread packages/plugin-page-builder/src/admin/EditorSurface.tsx Outdated
Comment thread packages/plugin-page-builder/src/admin/editorPreferences.ts Outdated
…allow

The manifest offered react 18 while depending on blocks-react, which
requires 19. That combination gives nobody react 18 support: it hands a
react 18 app an unsatisfiable peer graph and an error naming a transitive
package they never installed. The promise predates the builder dependency
and was never keepable.

Narrowed to 19, which is what the repo installs, what the renderer serving
published pages already requires, and what nothing here has ever tested
against anything else.

Guarded by deriving both sides from the manifests rather than restating a
literal: a hardcoded expectation keeps passing after a dependency widens,
which is the case worth catching. The check asserts it read some
dependencies before judging them, and reports an unreadable manifest
rather than skipping it.
…ditor

The editor renders the builder shell, whose layout rules and --nx-builder-*
tokens live in that package sheet. This plugin documents and exports exactly
one stylesheet, so a host following the documentation precisely still got a
shell with no layout and no colours.

An @import rather than a component-level import: this package copies its
styles directory into dist instead of running CSS through the bundler, so a
side-effect import in a component would never be processed.

Placed first, because CSS drops an @import that follows a style rule. The
test pins the ORDER as well as the presence, since an import at the bottom
reads as configured and is silently ignored by every browser.

@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: ec30b5d5e9

ℹ️ 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 thread packages/plugin-page-builder/src/admin/one-main-per-document.test.ts Outdated

@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: b0a6e74683

ℹ️ 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 thread packages/plugin-page-builder/src/react-peer-range.test.ts Outdated
Comment thread packages/plugin-page-builder/src/admin/EditorSurface.tsx
Comment thread packages/plugin-page-builder/package.json
A form may embed several page-builder fields. Every store read and wrote one
key, so opening one surface applied the panel selection and widths the other
had last written — which reads as the editor forgetting the choice at random.

Keyed by the same identifier the provider takes as its draft key, so the two
cannot disagree. The standalone edit view passes none and keeps the bare key,
which is right for the only surface of its kind on a page.

The full edit view also now supplies its cancel handler as the exit. It was
the one mount with somewhere to go, and without it no exit affordance rendered
anywhere at all — the optional prop was doing nothing but hide the control.

@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: 17e306c299

ℹ️ 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 thread packages/plugin-page-builder/src/admin/PageBuilderEditView.tsx
The rail drew all seven panels while a host might fill one, so six controls
opened a reserved panel and shrank the canvas to show nothing — a broken
control rather than an absent feature.

They are shown and disabled rather than hidden: the set is the editor shape,
and hiding the unbuilt ones would make the chrome change under an author as
features land. Each says what it is and that it is coming.

The host passes the same set renderPanel returns content for, so the rail
cannot disagree with the panel body. Omitting it treats every panel as
available, which keeps a host that fills all of them from enumerating them.

@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: d70acd86ab

ℹ️ 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 thread packages/builder/src/builder-shell.tsx Outdated
…dits

Wiring the host cancel handler into Exit exposed a path that navigates away
and consults nothing. The canvas holds unsaved edits in memory and its local
draft is restored nowhere, so a click discarded the whole edit — fastest
right after a change, while the draft write was still inside its debounce.

The confirmation belongs to the editor rather than the host: the host cannot
see dirty state, and every host would otherwise have to remember this. A
clean document still exits on one click.

The shell also normalises a restored panel selection against what the host
can fill. Disabling the rail button did not cover it — nobody clicked, the
selection came out of storage — so the layout still reserved a panel whose
content renders nothing. One predicate now answers both, so the button and
the reserved panel cannot disagree.

@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: bfe5c13c41

ℹ️ 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 thread packages/plugin-page-builder/src/admin/EditorSurface.tsx
The guard asked whether a range mentioned 18, which answers a narrower
question than the contract it states. It was silent on this package
advertising ^19.0.0 against a dependency that has moved to ^20.0.0, silent
on the package widening to ^19.0.0 || ^20.0.0, and would have reported
19.x against ^19.0.0 despite the two denoting the same set.

Compatibility is decided by whether every React version satisfying our
range also satisfies the dependency's, so ask that directly. An
unparseable range on either side is reported rather than skipped, since
treating it as compatible would let a typo silence the check for that
dependency.

The predicate is exercised on known answers as well, because the
manifest-driven assertion reports nothing while the workspace is
consistent, and that silence is the same output whether the comparison
works or cannot see anything. The population assertion moves from a count
to naming blocks-react: a count is satisfied by a selector that drops the
renderer and picks up something else.
Title and slug lived in SaveShell's own useState while every consumer asked
EditorProvider's dirty flag for whether unsaved work existed. A component
local copy cannot move that flag, so retyping a page's title and nothing
else left the editor reporting itself clean — and the exit confirmation,
which reads that flag, navigated away and discarded the edit without
asking.

Two owners of one question is what produced it, so the repair removes one
rather than teaching the guard about the second: the editor state now holds
the page fields, SET_PAGE_METADATA marks the page unsaved like any other
edit, and MARK_SAVED clears them together. A field added later inherits
this by being stored here, rather than by whoever adds it remembering that
a second place exists.

The draft write carries the metadata for the same reason, and REPLACE
preserves it — rebuilding without it would blank the inputs mid-edit while
also clearing dirty, so the loss would be silent.
The binding is registered on the document, so every mounted shell sees every
press, and eligibility came from viewport state alone. A shell embedded as a
field in an entry form therefore answered F6 from a sibling input and moved
focus into an editor the author was not in; with several such fields, the
most recently registered one won.

Eligibility now asks where focus IS. Containment is asked of the regions
rather than of a separate root element, since they are what cycling already
moves between and a second ref for the same question could disagree with
the first.

Focus resting on the body still enters, which is what keeps the key working
on a page whose editor owns it and has not been clicked yet — but only while
one shell is mounted, because with more than one the press names none of
them.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

The count deciding whether one shell is mounted is what allows F6 to enter
from a page where focus rests on the body. A count that only rose would
decline that entry permanently, which is the defect the focus rule fixes
arriving from the other side — and it would appear first in dev, where a
hot reload remounts.

Asserting the count after a single mount cannot separate the two: it passes
on a leaking counter the first time. Mounting, tearing down and mounting
again is what distinguishes them.

@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: 3eb18d7421

ℹ️ 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 thread packages/builder/src/builder-shell.tsx Outdated
Comment thread packages/plugin-page-builder/src/styles/chrome-stylesheet.test.ts Outdated
Asking the region list who owns the key rejected the shell whenever focus
sat in the chrome header. The header carries the exit button and the host's
top bar, and it is a sibling of the region container rather than inside one,
so F6 did nothing from the very chrome it belongs to.

Root and regions answer different questions that looked like one: the root
says whether the author is inside this editor, the region list says where
cycling can land. Ownership moves to the existing chrome ref, which already
wraps both the header and the regions; the region list still decides the
cycle. Declining presses from outside the shell is unchanged.
… sheet

The ordering assertion took `.nx-pb-editor {` as the first style rule, so a
rule inserted above the import but below that selector invalidated the
import while the comparison still read correctly. Every browser drops an
`@import` that follows a style rule, and the file would have shipped with
no chrome styles at all.

Both operands have now defeated a text search in turn: the bare word
`@import` matched the comment above the statement, and one selector stands
in for every rule. Neither is a property of the file. Walking the parsed
stylesheet's top-level children asks what CSS asks — does any style rule
precede the import.

`generate` produces the prelude rather than the node's shape being guessed
at; css-tree gives an `AtrulePrelude` here, and a wrong guess matches
nothing, which reads as a missing import rather than a bad probe.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: bb88f1a76f

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependency updates (label applied by Dependabot) scope: plugin @nextlyhq/plugin-* packages type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant