Skip to content

feat: add selection command menu#191

Merged
ocavue merged 3 commits into
prosekit:masterfrom
maccman:feat/selection-menu-pending-replacement
Jul 2, 2026
Merged

feat: add selection command menu#191
ocavue merged 3 commits into
prosekit:masterfrom
maccman:feat/selection-menu-pending-replacement

Conversation

@maccman

@maccman maccman commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Two host-agnostic editor primitives that let a host run text transformations (e.g. AI prompts) over a selection with a preview-before-apply flow.

Selection command menu

  • onSelectionMenuSearch(query, context) on MeowdownEditor enables a popover anchored to the current selection, with a filter input and host-supplied rows — the same host-callback shape as the wikilink/tag menus. context carries { selectedText, from, to }.
  • Opened via EditorHandle.openSelectionMenu() (so the host binds its own shortcut) or a small floating affordance shown on a non-empty text selection (selectionMenuAffordance, on by default).
  • EditorHandle.getSelectedText() and a core getSelectedText(state) util expose the selection text (block boundaries as blank lines; inline Markdown stays literal).

Pending replacement

  • Core extension staging Markdown over a source range without touching the document: startPendingReplacement({from, to, mode}) (restart = retry), appendPendingReplacementText(text) for streaming, acceptPendingReplacement() applies the parsed result as one transaction — inline when a single-paragraph result lands inside one textblock, as blocks otherwise (mode: 'append' inserts after the source block) — and discardPendingReplacement() is a document no-op.
  • Other edits remap the staged range through the transaction mapping; deleting the source content discards a replace stage. Mod-Enter accepts, Escape discards.
  • React preview: a popover anchored to the source range showing the accumulated text with Accept/Discard, a host pendingReplacementActions slot for extra controls, and onPendingReplacementResolve(outcome, pending) so the host can stop an in-flight stream.
  • The source range is highlighted via an inline decoration (.md-pending-replacement).

Tests

  • 12 core browser tests (staging, accept inline/blocks/append, discard byte-identical, remap-through-edits, retry, handler events, Escape).
  • 13 React browser tests across both components (menu open/filter/keyboard/affordance, preview stream/accept/discard/actions slot/retry).
  • tsc --build, eslint/oxfmt/knip clean; full core (945) and react (135) suites pass.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

@maccman is attempting to deploy a commit to the ocavue's projects Team on Vercel.

A member of the Team first needs to authorize it.

@pkg-pr-new

pkg-pr-new Bot commented Jul 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/prosekit/meowdown/@meowdown/core@191
npm i https://pkg.pr.new/prosekit/meowdown/@meowdown/react@191

commit: ca6a7d4

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
meowdown Ignored Ignored Preview Jul 2, 2026 2:24am

@maccman maccman force-pushed the feat/selection-menu-pending-replacement branch from de92b1f to 3819bce Compare July 2, 2026 06:01
maccman added a commit to team-reflect/reflect-open that referenced this pull request Jul 2, 2026
prosekit/meowdown#191 was rebased onto upstream master, which unified the
paste seam (onImagePaste/onImageSaveError → onFilePaste/onFileSaveError);
saveImage already declines non-images by returning null, so only images
are persisted through it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
maccman and others added 2 commits July 2, 2026 10:54
Two host-agnostic editor primitives:

- A selection command menu: a popover anchored to the current selection
  with a filter input and host-supplied rows (onSelectionMenuSearch),
  opened via EditorHandle.openSelectionMenu() or a small floating
  affordance shown on a non-empty text selection.
- A pending replacement: Markdown staged over a source range (e.g.
  streamed from an AI provider) previewed without touching the document.
  Accept applies the result as a single transaction (inline for a
  single-paragraph result inside one textblock, as blocks otherwise);
  discard is a no-op. Other edits remap the staged range; deleting the
  source discards it. Mod-Enter accepts, Escape discards, and the
  preview popover offers Accept/Discard plus a host actions slot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Floating-UI re-measures asynchronously; a measurement landing after the
editor unmounts hit view.docView.domFromPos on a destroyed view
(unhandled rejection on WebKit). Return the last known rect instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maccman maccman force-pushed the feat/selection-menu-pending-replacement branch from 3819bce to 5073906 Compare July 2, 2026 10:25
maccman added a commit to team-reflect/reflect-open that referenced this pull request Jul 2, 2026
prosekit/meowdown#191 was rebased again; upstream's follow-link feature
widened LinkClickPayload.event to MouseEvent | KeyboardEvent (links now
also follow on Mod-Enter), so the link-click handler's type widens with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Old Reflect's prediction menu let the user pick Replace vs Insert at
accept time and serialized the selection as Markdown; port both:

- acceptPendingReplacement({ mode }) overrides the staged placement for
  one accept, so a host can offer the alternate action next to Accept.
- getSelectedText serializes the selection slice through docToMarkdown,
  keeping list markers, headings, and blockquotes intact instead of
  flattening blocks to bare lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ocavue ocavue changed the title feat: selection command menu and pending-replacement preview feat: add selection command menu Jul 2, 2026
@ocavue ocavue merged commit 286628f into prosekit:master Jul 2, 2026
10 checks passed
@ocavuebot ocavuebot mentioned this pull request Jul 2, 2026
maccman added a commit to team-reflect/reflect-open that referenced this pull request Jul 2, 2026
prosekit/meowdown#191 (selection command menu + pending replacement)
merged and released, so the temporary snapshot overrides give way to
the plain npm dependency; the override block in pnpm-workspace.yaml
returns to its commented example form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maccman maccman deleted the feat/selection-menu-pending-replacement branch July 2, 2026 12:32
maccman added a commit to team-reflect/reflect-open that referenced this pull request Jul 2, 2026
* feat: AI menu on the editor selection (prompts + streamed preview)

Port of old Reflect's AI prompt picker (docs/porting/ai-menu-and-prompts.md):
select text, press ⌘⇧J (or the selection affordance), pick a prompt, and the
transformation streams into a preview — nothing touches the file until the
user accepts; a discarded run leaves the note byte-identical.

- @reflect/core: aiPrompts settings key (per-entry resilience), six curated
  built-in prompts + {{selectedText}} substitution (v1 syntax), a
  cloudSafeSelection privacy gate, and transformSelection — a one-shot BYOK
  streaming call on the existing languageModel factory.
- Editor: meowdown's new selection menu + pending-replacement primitives
  (prosekit/meowdown#191, pinned via pkg.pr.new overrides until released),
  wired through NoteEditor/NotePane with built-ins + saved prompts, fuzzy
  label filtering, and Retry with a one-shot model switch.
- Privacy: a private: true note gets no menu, no affordance, and the
  selection cannot typecheck its way to a provider; with no configured
  provider the menu points to Settings.
- Settings → AI prompts: saved-prompt list with add/edit/remove and a
  replace/insert-below mode per prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump meowdown snapshot to the rebased PR head

prosekit/meowdown#191 was rebased onto upstream master, which unified the
paste seam (onImagePaste/onImageSaveError → onFilePaste/onFileSaveError);
saveImage already declines non-images by returning null, so only images
are persisted through it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: bump meowdown snapshot after upstream rebase (0.31.0 head)

prosekit/meowdown#191 was rebased again; upstream's follow-link feature
widened LinkClickPayload.event to MouseEvent | KeyboardEvent (links now
also follow on Mod-Enter), so the link-click handler's type widens with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: v1-parity refinements for the AI menu

From reading old Reflect's prediction-menu source:

- Saved prompts list before the built-ins (v1 ordered custom templates
  first — the user's own workflow beats the stock set).
- Built-in bodies adopt v1's battle-tested prompt engineering (fenced
  selection, do-not-return-anything-else, preserve wikilinks/tags, don't
  translate); 'Continue writing' becomes v1's 'Write the next paragraph'
  and v1's 'Decorate my writing with backlinks' joins the set.
- The typed picker query runs as a one-off prompt (v1's 'Ask anything to
  AI'), with the selection appended as fenced context — the same compile
  rule v1 used for placeholder-less templates.
- The preview offers the alternate placement at accept time (v1 had both
  Replace and Insert buttons): 'Insert below' on a replace stage,
  'Replace selection' on an append one, via meowdown's new
  acceptPendingReplacement({ mode }) override.
- meowdown's getSelectedText now serializes the selection as Markdown
  (list markers and headings intact), matching v1's markdown slice
  serialization — snapshot pin bumped to pick both changes up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: address review feedback on the AI menu

- Fail closed on privacy: an unresolved note row now counts as private,
  so neither the menu nor the CloudSafe mint treats a not-yet-loaded
  note as sendable (cursor/coderabbit).
- ⌘⇧J falls through on an empty selection instead of being consumed
  (cursor/coderabbit).
- A stream error now ends the run: fail() aborts the controller, clears
  the run state, and the loop returns after the error event
  (cursor/coderabbit).
- renderSelectionPrompt uses a replacer function so $-sequences in the
  selection land verbatim, with a regression test (coderabbit).
- The prompt dialog surfaces field errors via aria-invalid and keeps
  the submit button enabled (coderabbit; zodResolver skipped — no
  @hookform/resolvers dep, and the house dialog pattern uses register
  rules).
- pnpm-workspace override comment no longer says 'uncomment'; the
  porting doc's open questions are resolved in place (coderabbit nits).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: abort an in-flight AI transform when the note changes

A run belongs to one note: a path change (or pane unmount) mid-stream
now aborts the provider call and drops the run state, so a stale stream
can't keep consuming tokens or leave Retry pointing at ranges from a
different document.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: port four more v1 built-in prompts

Act as a copy editor, Format paragraphs (replace), List key takeaways
(append), and Points to document (replace) join the curated set —
eleven built-ins total, bodies adapted from v1's templates. The rest of
v1's 23 (tweets, blog posts, emails, outlines…) stay deliberately
unported; any of them can be recreated verbatim as a saved prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: update meowdown to 0.33.0, drop the pkg.pr.new pin

prosekit/meowdown#191 (selection command menu + pending replacement)
merged and released, so the temporary snapshot overrides give way to
the plain npm dependency; the override block in pnpm-workspace.yaml
returns to its commented example form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: key the AI-run teardown on the editor session, not the path

A title-driven rename retargets the live session under a new filename
(Plan 17) without remounting the editor, so a path-keyed cleanup was
aborting the run under its still-visible preview and leaving Retry a
silent no-op. sessionEpoch bumps only when a session is created — note
switches and unmounts still tear the run down; renames keep it alive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: carry resolved privacy across a rename's row refetch

A title rename retargets the same note under a new path, and the new
path's index-row query starts empty — with fail-closed privacy that
beat misreported a public note as private (a Retry mid-rename surfaced
the private-note error). The last flag resolved in this session stays
authoritative while the new row loads, adjusted during render (the
note-pane seed pattern) and keyed to the session epoch so a genuinely
new session still fails closed until its row resolves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: list the AI menu shortcut in the keyboard cheat sheet

⌘⇧J was in the collision registry but not in
EDITOR_BINDING_DESCRIPTIONS, which is what the ⌘/ cheat sheet and the
Keyboard settings section render — so it fired without ever being
documented. Reflect's own editor bindings now merge into that record
next to meowdown's, and the keymap component imports the binding from
the registry instead of declaring its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants