feat(apollo-wind): add PromptEditor component [MST-10659]#784
feat(apollo-wind): add PromptEditor component [MST-10659]#784fikewa-olatunji wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Ports a Lexical-based PromptEditor into @uipath/apollo-wind as a reusable component, including token-pill rendering, $-triggered variable autocomplete, formatting toolbar, and a sanitized markdown preview mode.
Changes:
- Added
PromptEditorpublic exports (+ supporting types) and integrated Lexical-based editor with token nodes + plugins. - Implemented toolbar actions, token copy/paste serialization,
$-autocomplete menu, validation, and rename-on-options-change behavior. - Added markdown preview rendering via
marked+dompurify, plus Storybook + unit tests; updated dependencies/lockfile.
Reviewed changes
Copilot reviewed 35 out of 36 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks new Lexical + markdown preview dependencies and transitive graph. |
| packages/apollo-wind/package.json | Adds Lexical + marked + dompurify runtime deps for the new component. |
| packages/apollo-wind/src/index.ts | Exports PromptEditor + types from the package root. |
| packages/apollo-wind/src/components/ui/prompt-editor/index.ts | Local barrel export for PromptEditor and its public types. |
| packages/apollo-wind/src/components/ui/prompt-editor/types.ts | Defines token/types API and token color/label helpers. |
| packages/apollo-wind/src/components/ui/prompt-editor/prompt-editor.tsx | Main component wiring Lexical composer, plugins, toolbar, preview, and imperative ref API. |
| packages/apollo-wind/src/components/ui/prompt-editor/prompt-editor.test.tsx | Unit tests for rendering, toolbar/preview toggling, and malformed-prop tolerance. |
| packages/apollo-wind/src/components/ui/prompt-editor/prompt-editor.stories.tsx | Storybook coverage for common modes (toolbar, autocomplete, preview, controlled, etc.). |
| packages/apollo-wind/src/components/ui/prompt-editor/utils/index.ts | Barrel export for prompt-editor utilities. |
| packages/apollo-wind/src/components/ui/prompt-editor/utils/serialization.ts | Token ↔ Lexical state conversion + clipboard string serialization/parsing + selection token extraction. |
| packages/apollo-wind/src/components/ui/prompt-editor/utils/insert-token.ts | Inserts token nodes at cursor / selection. |
| packages/apollo-wind/src/components/ui/prompt-editor/utils/comparison.ts | Token-array deep equality helper. |
| packages/apollo-wind/src/components/ui/prompt-editor/utils/autocomplete-segments.ts | $ trigger helpers (type inference, regex, dismissed-trigger sentinel). |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/ValueSyncPlugin.tsx | Controlled-value sync into Lexical state with focus-aware selection preservation. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/ValidateTokensPlugin.tsx | Marks token pills invalid if not present in autocomplete option set. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/RenameTokensPlugin.tsx | Renames token values when option-path trees indicate a rename. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/AutocompletePlugin.tsx | Drives $ trigger detection and commits selected/free-form variables as token pills. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/CopyPastePlugin.tsx | Custom copy/cut/paste integrating token clipboard format and Lexical mime payload. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/EditorRefPlugin.tsx | Exposes Lexical editor instance to parent wiring. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/MultilinePlugin.tsx | Enforces single-line vs multiline behavior (enter suppression, newline stripping). |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/NodeSelectionFixPlugin.tsx | Keyboard/cursor management for inline DecoratorNode “pill” selection. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/ToolbarActionsPlugin.tsx | Implements formatting actions by inserting markdown markers/prefixes. |
| packages/apollo-wind/src/components/ui/prompt-editor/plugins/shared/token-nodes.ts | Shared traversal helpers for finding token nodes in editor state. |
| packages/apollo-wind/src/components/ui/prompt-editor/nodes/index.ts | Barrel export for the custom Lexical token nodes. |
| packages/apollo-wind/src/components/ui/prompt-editor/nodes/InputTokenNode.tsx | Input token DecoratorNode implementation. |
| packages/apollo-wind/src/components/ui/prompt-editor/nodes/OutputTokenNode.tsx | Output token DecoratorNode implementation. |
| packages/apollo-wind/src/components/ui/prompt-editor/nodes/StateTokenNode.tsx | State token DecoratorNode implementation. |
| packages/apollo-wind/src/components/ui/prompt-editor/nodes/ResourceTokenNode.tsx | Resource token DecoratorNode implementation. |
| packages/apollo-wind/src/components/ui/prompt-editor/components/EditorToolbar.tsx | Toolbar UI (edit/preview toggle + formatting buttons). |
| packages/apollo-wind/src/components/ui/prompt-editor/components/PromptEditorAutocompleteMenu.tsx | Caret-anchored command menu for variable selection. |
| packages/apollo-wind/src/components/ui/prompt-editor/components/MarkdownPreview.tsx | Markdown preview rendering + sanitization + token-pill HTML injection. |
| packages/apollo-wind/src/components/ui/prompt-editor/components/markdown-preview.css | Styles for preview output and token pills in sanitized HTML. |
| packages/apollo-wind/src/components/ui/prompt-editor/components/TokenPill.tsx | Visual pill rendering (icons, remove button, selected outline). |
| packages/apollo-wind/src/components/ui/prompt-editor/components/TokenPillWithTooltip.tsx | Adds tooltips + NodeSelection mouse handling to token pills. |
| packages/apollo-wind/src/components/ui/prompt-editor/components/token-icon-markup.ts | Inline lucide SVG markup builder for preview-mode token icons. |
| packages/apollo-wind/src/components/ui/prompt-editor/components/token-icon-markup.test.ts | Tests for SVG markup builder output. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
b5f8a7a to
9168e44
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Dependency License Review
License distribution
Excluded packages
|
9168e44 to
7fc6691
Compare
7fc6691 to
10b1152
Compare
10b1152 to
645a2fb
Compare
645a2fb to
676ed08
Compare
676ed08 to
d64f988
Compare
d64f988 to
b30a8cb
Compare
e1dd32d to
b47504a
Compare
559bc21 to
11bb2a4
Compare
CalinaCristian
left a comment
There was a problem hiding this comment.
PR #784 review — PromptEditor extraction
Overall: correct, intentional decoupling — not a strict 1-to-1 but a well-executed port. Core Lexical engine, plugin architecture, serialization, and public API surface are faithfully preserved. Several real bugs from the flow-workbench original were fixed along the way (empty-array controlled value, minRows/maxRows clamping, Lexical state corruption in insertVariableToken, $-prefix on free-typed chip paths, NodeSelection crash guard, borderless text color). No data-loss or crash bugs found in the new code.
Severity legend: 🟡 medium = worth landing before merge · 🔵 low = follow-up is fine
✅ Bug fixes shipped in this extraction (all genuine improvements)
| Fix | Location |
|---|---|
root.append(tokenNode) → wrap in $createParagraphNode() |
insert-token.ts |
!value → value === undefined — empty array now clears a controlled editor |
ValueSyncPlugin.tsx |
isEmpty init seeds from initialValue ?? value — fixes controlled-only usage |
prompt-editor.tsx |
effectiveMinRows = Math.min(minRows, maxRows) — max-height no longer silently overridden |
prompt-editor.tsx |
borderless ? 'inherit' : 'var(--color-foreground)' — correct text color in parent-provided chrome |
prompt-editor.tsx |
$isRangeSelection guard — insertRawText can't throw on a NodeSelection |
prompt-editor.tsx |
Free-typed chip path: $vars.foo → vars.foo — matches menu-selected chips for validation |
AutocompletePlugin.tsx |
closeMenu(explicit) flag — auto-close no longer suppresses re-opening at the same $ position |
AutocompletePlugin.tsx |
target removed from DOMPurify allowlist — closes tabnabbing vector in markdown preview |
MarkdownPreview.tsx |
CSS inlined in JS — self-contained bundle, no unresolved .css import next to published JS |
MarkdownPreview.tsx |
Inline comments below cover the two 🟡 medium and one 🔵 low observations.
| export interface TokenPillProps { | ||
| value: string; | ||
| /** Token type — selects the leading icon and (via the wrapper) the tooltip label. */ | ||
| tokenType: PromptEditorTokenType; |
There was a problem hiding this comment.
🔵 L1 — size='md' variant not ported
The flow-workbench TokenPill had size?: 'sm' | 'md' for a taller standalone single-variable field (28px chip, larger text). The port hardcodes sm geometry and only accepts tokenType.
No current apollo-wind consumer needs the medium size, so this is fine to ship as-is. Flagging for when a standalone single-token field is added to the design system — the variant will need to be re-introduced, ideally as a named size prop to keep the API consistent with other apollo-wind components.
There was a problem hiding this comment.
Agreed — leaving sm-only for now since no apollo-wind consumer needs the medium size. Will reintroduce it as a named size prop when a standalone single-token field is added, to keep the API consistent.
| // ----------------------------------------------------------------------------- | ||
| // Prompt Editor | ||
| // ----------------------------------------------------------------------------- | ||
| export { PromptEditor } from './components/ui/prompt-editor'; |
There was a problem hiding this comment.
Should this also be exported from packages/apollo-wind/src/components/ui/index.ts?
The package root exports PromptEditor, but the components/ui barrel exports every other UI component and currently skips ./prompt-editor. Consumers importing from @uipath/apollo-wind/components/ui will not see it.
Suggested fix:
export * from './prompt-editor';There was a problem hiding this comment.
Done — added export * from './prompt-editor' to components/ui/index.ts. cecde50.
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| const AUTOCOMPLETE_OPTIONS: PromptEditorAutoCompleteOption[] = [ | ||
| { type: 'input', value: 'vars.firstName' }, |
There was a problem hiding this comment.
Can we document the token value convention for PromptEditorAutoCompleteOption.value and token values?
The stories/tests use values like vars.firstName, while flow-workbench currently uses $vars.firstName. That is fine if Apollo’s public API intentionally owns the no-$ convention, but it should be explicit because serialization/validation depend on exact string equality.
There was a problem hiding this comment.
Documented it on PromptEditorToken and PromptEditorAutoCompleteOption in types.ts: apollo-wind owns the no-$ convention (e.g. vars.firstName), and serialization + ValidateTokensPlugin compare by exact string equality, so option values and token values must use the same $-less form. cecde50.
There was a problem hiding this comment.
can you experiment with publishing a dev package for this (dev-packages label and try consume it in flow-workbench to replace existing one and verify there are no regressions?)
There was a problem hiding this comment.
Validated via the dev package + flow-workbench swap — drop-in clean except mapVarDropToToken (drag-drop, intentionally not ported). Details in the preview-parity thread.
| ); | ||
| expect(getByText('Nothing to preview')).toBeInTheDocument(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
The current tests cover rendering/mounting/preview smoke paths, but the extraction-risky flows are mostly untested.
Can we add focused tests for:
$autocomplete selection committing a token- free-form Enter commit after
$vars.foo - invalid-token validation against
autoCompleteOptions - rename-on-options-change behavior
- copy/paste token serialization
- toolbar formatting on multiline/backward selections
Those are the places most likely to regress during a design-system extraction.
There was a problem hiding this comment.
Added focused unit tests for the extraction-risky logic (368ac01): clipboard token serialization round-trip, free-form path type inference + VARIABLE_PATH_REGEX, the dismissed-trigger suppression sentinel, and token-array equality. The interaction-level flows ($-keypress→commit, toolbar on multiline/backward selections) stay limited under jsdom — the existing test file notes Lexical decorators don't paint reliably there — so those are covered at the logic layer rather than via simulated editing.
| md += token.value; | ||
| } else { | ||
| const iconSvg = buildTokenIconSvgMarkup(token.type); | ||
| md += `<span class="token-pill">${iconSvg}${escapeHtml(token.value)}</span>`; |
There was a problem hiding this comment.
Flow-workbench preview marks stale/unresolved variables invalid, while this preview always renders token pills as valid.
Can preview either:
- receive/use
autoCompleteOptionsto mirrorValidateTokensPlugin, or - explicitly document that preview is visual-only and does not reflect token validity?
Right now edit mode and preview mode can disagree about whether a token is valid.
There was a problem hiding this comment.
Documented preview as visual-only: it doesn't receive autoCompleteOptions, so every token renders as a pill, and edit mode's ValidateTokensPlugin remains the source of truth for validity (note added to the MarkdownPreview doc comment, cecde50). I kept it visual-only to stay consistent with the earlier removal of the data-invalid path — happy to thread the options through for full parity if you'd prefer.
There was a problem hiding this comment.
we need parity -- best test is to try to repalce this using the dev-packages label, install the dev version and try to replace the flow-workbench PromptEditor with this one and verify full parity on functionality.
There was a problem hiding this comment.
Ran the swap: dev pkg @uipath/apollo-wind@2.19.0-pr784.9c7181c dropped into flow's PromptEditorField (replacing the local core editor) — @flow/canvas typechecks clean, 0 prop/type mismatches across all 16 props + shared types. One gap: mapVarDropToToken (variable drag-drop) wasn't ported — intentional scope call, needs a decision (port VariableDropPlugin or drop drag-drop). Autocomplete UI was rebuilt on apollo-wind primitives (same autoCompleteOptions API).
Port the Lexical-based PromptEditor from flow-workbench into apollo-wind as a reusable, prop-driven component (value / onChange / autoCompleteOptions). It renders inline token pills (input/output/state/resource), a formatting toolbar, a markdown preview mode, and a $-triggered variable autocomplete rebuilt on apollo-wind Command/Popover/Tooltip — decoupled from flow-workbench's variables, schema, i18n, and theme systems. Adds lexical, @lexical/react, @lexical/utils, @lexical/clipboard, marked and dompurify. Token/option props are normalized to arrays so malformed input can't crash the editor, and token colors map to apollo-wind design tokens. Exports PromptEditor and its public types from the package root, with unit tests and Storybook stories. Refs: MST-10659
- Honor `borderless` in preview mode: drop the editor's own border/background there too, so borderless behaves consistently between edit and preview. - Remove dead `data-invalid` style rule + allowed attribute in MarkdownPreview (preview no longer emits it). - Fix stale lucide version reference (0.555.0 -> 0.577.0) in token-icon-markup.
apollo-react pins lexical 0.16.0 (its ApRichTextEditor); apollo-wind's PromptEditor requires lexical 0.42.0 (React 19 support + 0.42-only APIs). The two design-system packages intentionally track different lexical majors, so ignore the lexical/@lexical/* family in the consistency check rather than force-aligning and breaking one of the editors.
…focus, preview styles, exports) - PromptEditorAutocompleteMenu: preventDefault on container pointerdown so focus can't leave the editor before commit (review M1) - Move markdown-preview styles from an inline <style> into the shared package stylesheet (tailwind.utilities.css) consumers already import - Remove unused PromptEditorHighlightLocator/Item types (review M2) - Export prompt-editor from the components/ui barrel - Document the no-$ token value convention and that preview is visual-only re: validity
…lity + scope dep exemption - Narrow the dependency-consistency exemption from the repo-wide lexical family to just @uipath/apollo-wind, so lexical drift elsewhere is still caught (review) - Add unit tests for clipboard token serialization round-trip, free-form path type inference + VARIABLE_PATH_REGEX, dismissed-trigger suppression, and token equality (the extraction-risky logic; Lexical interaction paths stay limited under jsdom)
e1e2aea to
368ac01
Compare
Deployed apollo-design Storybook already themes the autodocs story background, so the borderless PromptEditor preview is readable without this override. Reverts preview-head.html to match main (review).
…mption Bumps apollo-react's lexical + @lexical/* from 0.16.0 to 0.42.0 to match apollo-wind, so the dependency-version-consistency check passes without any ignore workaround (review). Fixes the one resulting breakage: LexicalErrorBoundary is a named export in 0.42, not default. apollo-react editor typechecks clean and its 18 tests pass on 0.42.
📦 Dev Packages
|
| // Insert end marker after the selection's focus point | ||
| const focusNode = selection.focus.getNode(); | ||
| const focusOffset = selection.focus.offset; | ||
| if ($isTextNode(focusNode)) { | ||
| const text = focusNode.getTextContent(); | ||
| focusNode.setTextContent(text.slice(0, focusOffset) + endMarker + text.slice(focusOffset)); | ||
| } else { | ||
| // Focus is on a non-text node (e.g., decorator) — insert after it | ||
| const endText = $createTextNode(endMarker); | ||
| focusNode.insertAfter(endText); | ||
| } | ||
|
|
||
| // Insert start marker before the selection's anchor point | ||
| const anchorNode = selection.anchor.getNode(); | ||
| const anchorOffset = selection.anchor.offset; | ||
| if ($isTextNode(anchorNode)) { | ||
| const text = anchorNode.getTextContent(); | ||
| anchorNode.setTextContent(text.slice(0, anchorOffset) + startMarker + text.slice(anchorOffset)); | ||
| } else { | ||
| // Anchor is on a non-text node — insert before it | ||
| const startText = $createTextNode(startMarker); | ||
| anchorNode.insertBefore(startText); | ||
| } |
| () => { | ||
| let tokensChanged = false; | ||
| const tokenNodes = getAllPromptTokenNodes(); | ||
|
|
||
| for (const node of tokenNodes) { | ||
| const nodeType = isInputTokenNode(node) | ||
| ? 'input' | ||
| : isOutputTokenNode(node) | ||
| ? 'output' | ||
| : isStateTokenNode(node) | ||
| ? 'state' | ||
| : isResourceTokenNode(node) | ||
| ? 'resource' | ||
| : null; | ||
| if (!nodeType) continue; | ||
|
|
||
| const matchingRenames = sortedRenames.filter((r) => r.type === nodeType); | ||
| if (matchingRenames.length === 0) continue; | ||
|
|
Jira: MST-10659
Summary
Ports the Lexical-based PromptEditor from flow-workbench into
@uipath/apollo-windas a reusable, fully prop-driven component. It renders inline variable token pills (input / output / state / resource), a formatting toolbar, a markdown preview mode, and a$-triggered variable autocomplete.The port deliberately decouples the editor from flow-workbench-specific systems that don't exist in a design system:
Command/Popover/Tooltip), driven by theautoCompleteOptionsprop — no flowVariablePickeror schema services.react-i18next).Public API
PromptEditor+ types (PromptEditorProps,PromptEditorRef,PromptEditorToken,PromptEditorTokenType,PromptEditorAutoCompleteOption,PromptEditorMode) exported from the package root. Controlled (value/onChange) and uncontrolled (initialValue) usage,multiline,minRows/maxRows,placeholder,disabled,showToolbar,mode/onModeChange,borderless,fillHeight, and an imperativeeditorRef.Dependencies added (apollo-wind only)
lexical,@lexical/react,@lexical/utils,@lexical/clipboard(0.42.0),marked,dompurify. No other package uses them (version-consistency safe); they introduce no new audit findings.Robustness
Token/option props are normalized to arrays so malformed input (e.g. a Storybook "Set object"
{}) can't crash the editor or preview.maxRowscaps the visible height and scrolls (clamped so it isn't overridden whenmaxRows ≤ minRows).Testing
vitest: 19 unit tests for PromptEditor (rendering, toolbar, preview, mode toggle, autocomplete mount, malformed-input tolerance) — full apollo-wind suite passes (928 tests).biome format/lintclean,test:coveragepasses, fullpnpm build(all 8 packages) succeeds, frozen lockfile in sync.$-autocomplete, chip insertion, light/dark, height capping) verified end-to-end in a headless browser.