Skip to content

feat: let canvas extensions select a section - #1282

Draft
keepthebyte wants to merge 2 commits into
adobe:mainfrom
keepthebyte:selectsection-extension-action
Draft

feat: let canvas extensions select a section#1282
keepthebyte wants to merge 2 commits into
adobe:mainfrom
keepthebyte:selectsection-extension-action

Conversation

@keepthebyte

Copy link
Copy Markdown

Closes #1281 — please read that first for the motivation. Opening this as a draft: it is a concrete suggestion to argue about, not a merge request. Happy for the team to take the idea and implement it their own way.

What

Adds one action to the canvas extension channel, selectSection, backed by a selectSection(view, sectionIndex) helper in editor-utils/blocks.js.

Why

A BYO panel extension can already read the document (the handshake hands it { org, repo, ref, path } and the IMS token, so it can fetch page source from admin.da.live) and insert into it (sendHTMLinsertHTML). What it cannot do is move the selection, and that blocks two things:

  1. Jumping the canvas to a section. A plugin listing the page's sections cannot scroll the editor to one when clicked, the way ew-page-outline does.
  2. Editing an existing section. insertHTML does replaceRange(from, to, …), so the edit loop already works — it just needs something to put the selection on the right node first. Without that the only remaining route is writing source via admin.da.live underneath a live collab session, which we are deliberately not doing.

Selecting the whole section rather than only scrolling to it is the load-bearing choice: it delivers the jump and leaves a replaceable range under the existing sendHTML, so editing needs no second API.

Implementation notes

  • getSectionRanges reuses the same "split top-level nodes on horizontal_rule" walk that deleteSection and moveSection already use, rather than introducing a second notion of what a section is.
  • Ends in TextSelection + scrollIntoView() + view.focus(), mirroring what the outline path does.
  • Replies on the port with { action: 'selectSectionResult', details: { ok } } so a plugin can feature-detect and degrade rather than fail silently — our prototype shows an explicit "this host does not support it yet" note on timeout.
  • Nothing here lets a plugin mutate the document outside the editor. Edits still go through ProseMirror and collab.

Open questions for the team

  • Address shape. sectionIndex is what the existing block/section helpers already speak, but a prose-index or node address would be more general and would survive section reordering better. Happy to change.
  • Naming / placement. selectSection sits alongside the other verbs in iframe-protocol.js; if extension capabilities are meant to be gated or declared per plugin, this should probably go through that instead.
  • Should a read-only variant exist (scrollToSection) for plugins that want to navigate without disturbing the user's selection? We did not add one because the replaceable range is the point, but the two uses are separable.

Testing

Verified against a real customer page in a local canvas: clicking a section in a plugin panel scrolls the editor to it, and a subsequent getSelection → edit → sendHTML round-trips the section's prose HTML — preserving block content and any non-audience Style tokens, and updating only what the plugin intended.

This is driven by an authoring plugin we built for section-scoped personalization; happy to share it if that helps evaluate the shape.

Cedric Huesler and others added 2 commits August 28, 2026 12:10
A panel extension can read the document and insert into it, but has no way to
move the selection to a known place. That blocks two things a structural
authoring plugin needs: jumping the canvas to a section the way the outline
rail does, and editing a section at all -- insertHTML already replaces the
current selection, so the edit loop works as soon as something can position
that selection.

Without it the only route left is writing source via admin.da.live underneath
a live collab session, which risks clobbering or being clobbered by the y.js
document.

selectSection reuses the same "split top-level nodes on horizontal_rule" walk
that deleteSection and moveSection already use, and ends in TextSelection plus
scrollIntoView. Selecting the whole section rather than only scrolling to it
is deliberate: it gives the jump and leaves a replaceable range under
sendHTML, so editing needs no second API.

Refs adobe#1281

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmqV8wmTVrzTsEYCd5y5rT
Jumping and range-selecting are different asks, and only the first has
precedent. The outline addresses blocks and prose items, never sections --
its section row is a drag handle with add/delete, not a click target -- and
the jump it performs is canvasBus.editorSelectState -> _scrollDocToBlock.

Driving the view directly, as the first version did, works but goes around
all of that: the outline highlight stays stale and _broadcastSelectedNode
never runs, so the wysiwyg side never learns about the selection. scrollToBlock
now emits on the bus with the same blockIndex address instead.

selectSection stays, for a different reason than navigation: getBlockPositions
excludes NON_BLOCK_TABLE_NAMES, so a section's metadata table has no blockIndex
and cannot be addressed at all. The section range is what makes it reachable,
and leaves something for sendHTML to replace.

Refs adobe#1281

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmqV8wmTVrzTsEYCd5y5rT
@keepthebyte

Copy link
Copy Markdown
Author

Revised after a good challenge from @keepthebyte: the outline can't jump to a section either — it only addresses blocks and prose items. Its section row is a drag handle with add/delete, not a click target. That's worth taking seriously, because it means the first version of this PR invented a section-level navigation concept the host doesn't have.

Two things fall out, and they pull in opposite directions:

Navigation should reuse what exists. The outline's jump is canvasBus.editorSelectState.emit({ blockIndex, source: 'outline' })ew-editor-doc._scrollDocToBlock. Driving the view directly, as I did first, works but goes around all of it — the outline highlight stays stale and _broadcastSelectedNode never runs, so the wysiwyg side never learns about the selection. So navigation is now scrollToBlock({ blockIndex }), emitting on the same bus with the same address. A plugin listing sections just jumps to a section's first block, which is what the user wanted anyway.

A section range is still needed, for a different reason. getBlockPositions skips NON_BLOCK_TABLE_NAMES, so a section's metadata table has no blockIndex and cannot be addressed at all. That's the case our tooling actually needs — editing which audiences a section targets means reaching its section metadata. selectSection stays for that, and it's what leaves a replaceable range under sendHTML.

So: scrollToBlock has precedent and should be uncontroversial; selectSection is the genuinely new concept, and it's for reaching metadata rather than for moving the viewport. Reviewing them separately is probably right — the first may be worth taking on its own even if the second needs more thought.

One open question I didn't want to decide unilaterally: _scrollDocToBlock runs for any source, but _broadcastSelectedNode(true) only fires for source === 'outline'. I emit source: 'plugin', so a plugin-driven jump scrolls without broadcasting. Using 'outline' would get the broadcast but would be a lie about provenance. Whether plugin jumps should broadcast is your call.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Canvas extensions cannot move the selection, which blocks structural authoring plugins

1 participant