@pilotiq/tiptap 3.10.5 → 3.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +745 -0
- package/boost/guidelines.md +268 -0
- package/boost/skills/pilotiq-tiptap-blocks/SKILL.md +48 -0
- package/boost/skills/pilotiq-tiptap-blocks/rules/custom-blocks.md +90 -0
- package/boost/skills/pilotiq-tiptap-blocks/rules/slash-menu-and-mentions.md +101 -0
- package/boost/skills/pilotiq-tiptap-blocks/rules/toolbar-and-extensibility.md +161 -0
- package/package.json +4 -3
- package/src/Block.ts +0 -75
- package/src/MentionProvider.ts +0 -153
- package/src/PlainTextEditor.dom.test.ts +0 -111
- package/src/PlainTextEditor.test.ts +0 -158
- package/src/PlainTextEditor.ts +0 -229
- package/src/RichTextField.test.ts +0 -447
- package/src/RichTextField.ts +0 -508
- package/src/extensions/AiInlineDiffExtension.ts +0 -286
- package/src/extensions/AiSuggestionExtension.test.ts +0 -141
- package/src/extensions/AiSuggestionExtension.ts +0 -522
- package/src/extensions/BlockNodeExtension.ts +0 -134
- package/src/extensions/DragHandleExtension.ts +0 -184
- package/src/extensions/GridExtension.test.ts +0 -31
- package/src/extensions/GridExtension.ts +0 -138
- package/src/extensions/MentionExtension.ts +0 -248
- package/src/extensions/MergeTagExtension.ts +0 -75
- package/src/extensions/SlashCommandExtension.test.ts +0 -147
- package/src/extensions/SlashCommandExtension.ts +0 -332
- package/src/extensions/TextSizeMarks.ts +0 -73
- package/src/index.ts +0 -62
- package/src/markdownExtension.ts +0 -19
- package/src/markdownStorage.ts +0 -49
- package/src/plugin.test.ts +0 -19
- package/src/plugin.ts +0 -26
- package/src/react/AiSuggestionBanner.tsx +0 -185
- package/src/react/BlockNodeView.tsx +0 -99
- package/src/react/BlockSidePanel.dom.test.tsx +0 -38
- package/src/react/BlockSidePanel.test.ts +0 -412
- package/src/react/BlockSidePanel.tsx +0 -451
- package/src/react/CollabTextRenderer.tsx +0 -228
- package/src/react/FloatingToolbar.tsx +0 -304
- package/src/react/MarkdownEditor.tsx +0 -603
- package/src/react/MentionMenu.tsx +0 -120
- package/src/react/Palette.tsx +0 -86
- package/src/react/SlashMenu.tsx +0 -129
- package/src/react/TableFloatingToolbar.tsx +0 -154
- package/src/react/TiptapEditor.dom.test.tsx +0 -112
- package/src/react/TiptapEditor.tsx +0 -777
- package/src/react/Toolbar.tsx +0 -438
- package/src/react/toolbarButtons.tsx +0 -579
- package/src/react/useAiInlineDiff.ts +0 -342
- package/src/react/useAiSuggestionBridge.ts +0 -223
- package/src/register.test.ts +0 -14
- package/src/register.ts +0 -42
- package/src/render.test.ts +0 -745
- package/src/render.ts +0 -480
- package/src/surgicalOps.ts +0 -205
- package/src/test/setup.ts +0 -64
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
# @pilotiq/tiptap
|
|
2
|
+
|
|
3
|
+
## 3.10.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1c6a067: feat(adapters): ship `boost/guidelines.md` for `@rudderjs/boost` discovery
|
|
8
|
+
|
|
9
|
+
Phase C of the boost-producer rollout. Each adapter now ships its own `boost/guidelines.md` so consumer Rudder apps with `@rudderjs/boost` installed pick them up automatically via `rudder boost:install`. Per-agent config files (`CLAUDE.md` / `.cursorrules` / `AGENTS.md` / etc.) include all installed adapter guidelines in the concatenated body.
|
|
10
|
+
|
|
11
|
+
- **`@pilotiq/tiptap`** — RichTextField + Block (custom-block side panel), toolbar customization, mentions (static + async) + merge tags, file attachments, JSON vs HTML storage, server-side rendering via `renderRichTextToHtml`.
|
|
12
|
+
- **`@pilotiq/codemirror`** — CodeEditorField + Code alias, language registry (`registerCodeLanguage` / `codeEditor({ languages })`), theming (auto / light / dark), reactive integration, validation, common language packs.
|
|
13
|
+
- **`@pilotiq/recharts`** — Chart class + fluent form, chart types (line / bar / pie / doughnut), Chart.js-shaped data normalized to Recharts internally, per-chart filter dropdown, polling, resource header/footer placement, escape hatch via `static options`.
|
|
14
|
+
|
|
15
|
+
Each guideline closes with a "Common Pitfalls" section distilled from project memory + a "Key Imports" reference. No skills shipped in this phase — adapter usage is single-surface enough that the always-loaded `guidelines.md` covers it; skill modules can follow if a consumer asks.
|
|
16
|
+
|
|
17
|
+
- 6d2ac13: chore: slim published tarballs to `dist` + `boost` + `CHANGELOG.md`
|
|
18
|
+
|
|
19
|
+
All four packages now declare `"files": ["dist", "boost", "CHANGELOG.md"]` so npm pack only ships the compiled output, the `@rudderjs/boost` guidelines + skills tree, and the changelog. Previously `@pilotiq/pilotiq` shipped its full `src/`, `CLAUDE.md`, `.turbo/`, and test files; the three adapters shipped `src/` deliberately but no longer need to.
|
|
20
|
+
|
|
21
|
+
- **`@pilotiq/pilotiq`** — 2.1 MB → 1.3 MB (~38% smaller). Drops `src/**`, `CLAUDE.md`, `.turbo/` from the tarball.
|
|
22
|
+
- **`@pilotiq/tiptap` / `@pilotiq/codemirror` / `@pilotiq/recharts`** — drop `src/**` from the tarball.
|
|
23
|
+
|
|
24
|
+
No API impact. Consumer Tailwind `@source` rules that previously scanned `node_modules/@pilotiq/*/src` should re-point at `node_modules/@pilotiq/*/dist` (Tailwind scans `.js` just fine). Source maps in `dist/` still reference `../src/*.ts` paths that are no longer in the tarball — sourcemap navigation inside `node_modules` won't resolve to TS, but stack traces still line up.
|
|
25
|
+
|
|
26
|
+
- 6d2ac13: feat(tiptap): ship `pilotiq-tiptap-blocks` boost skill
|
|
27
|
+
|
|
28
|
+
First on-demand skill for `@pilotiq/tiptap`. `SKILL.md` declares `appliesTo: ['@pilotiq/tiptap']` so `@rudderjs/boost`'s `boost:install` only writes it to `.ai/skills/` when the consumer has `@pilotiq/tiptap` installed. Trigger heuristics scope the deep rules to specific authoring contexts — defining `Block.make(...)` types, wiring mentions / merge tags, customizing the toolbar, debugging slash-menu or drag-handle behavior.
|
|
29
|
+
|
|
30
|
+
Three rule files under `boost/skills/pilotiq-tiptap-blocks/rules/`:
|
|
31
|
+
|
|
32
|
+
- **`custom-blocks.md`** — `Block.make().schema([…])`, side panel V2 UX, field-type coverage inside a block (primitives, JSON-encoded, repeater / builder), `Mod-E` / `Esc` / focus trap / width memory, common authoring mistakes (including the `'block'` name collision).
|
|
33
|
+
- **`slash-menu-and-mentions.md`** — slash menu groups, capture-phase keys, `MentionProvider` (static + async via `itemsUsing`), merge tags, mentions inside Repeater / Builder rows.
|
|
34
|
+
- **`toolbar-and-extensibility.md`** — three customization styles (`toolbarButtons` / `enable+disableToolbarButtons` / hide chrome), the recognized button-id union, opt-in primitives (`lead` / `small` / `details` / `grid`), file attachments, drag-handle's three-step drop dance, Tiptap module identity (`resolve.dedupe`), toolbar-driven slash entries.
|
|
35
|
+
|
|
36
|
+
Mirrors the shape established by `pilotiq-resource` / `pilotiq-fields` / `pilotiq-relations`.
|
|
37
|
+
|
|
38
|
+
## 3.10.5
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- 0002c59: refactor(tiptap): narrow `collabExtensions` + `initialContent` at the typed boundary
|
|
43
|
+
|
|
44
|
+
`TiptapEditor` and `MarkdownEditor` previously cast the collab-extension array as `any[]` at the spread site (`...(collabExtensions as any[])`) even though the produced array is `AnyExtension[]`-shaped. Adding `import type { AnyExtension, Content } from '@tiptap/core'` lets us type the `useMemo` directly and narrow `editor.commands.setContent(initialContent as Content)` instead of bypassing the type system entirely.
|
|
45
|
+
|
|
46
|
+
`initialContent` is already gated by `isTiptapShapedContent(...)` upstream — the explicit `as Content` cast documents what we expect at the call site rather than papering over with `as any`. No behavior change; tests 193/193.
|
|
47
|
+
|
|
48
|
+
The `room as unknown as FrameworkCollabRoom` casts on `CollabTextRenderer` / `CollabCodeMirrorEditor` are deliberate framework-room boundary casts and stay as-is.
|
|
49
|
+
|
|
50
|
+
## 3.10.4
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- f28e4f9: fix(ai): move `AiSuggestionBanner` from above the toolbar to below the editor content
|
|
55
|
+
|
|
56
|
+
The Accept / Reject strip for whole-field AI suggestions on `RichTextField` and `MarkdownField` previously mounted at the top of the editor wrapper, above the toolbar. That position pushed the toolbar and content down on every suggestion arrival, shifting the writing surface mid-edit and competing with the toolbar for the user's attention.
|
|
57
|
+
|
|
58
|
+
The banner now mounts below `<EditorContent>` (after all tab content in `MarkdownEditor`, so the position is uniform across editor/source/preview tabs). The CSS margin flipped from `margin-bottom` to `margin-top` so the banner has breathing room from the content above it instead of the chrome below.
|
|
59
|
+
|
|
60
|
+
Behavior is unchanged — same Accept / Reject handlers, same diff-active vs whole-field branching, same per-suggestion stacking semantics.
|
|
61
|
+
|
|
62
|
+
## 3.10.3
|
|
63
|
+
|
|
64
|
+
### Patch Changes
|
|
65
|
+
|
|
66
|
+
- 0b2a8bd: fix(collab): `CollabTextRenderer`'s post-sync seed uses a stable initial-defaultValue ref
|
|
67
|
+
|
|
68
|
+
The `useCollabSeed` callback closed over the live `defaultValue` prop. In the cold-mount-against-a-populated-room case, the editor's first `onUpdate` could fire with empty text (a transient sync artifact from y-prosemirror's `ySyncPlugin` running `_forceRerender` before the React owner had stable state), which propagated empty through the host's `onChange` → `setText('')` → `setValue('')` → `FormStateContext.values[name] = ''`, which then cascaded back through `FormBody`'s `renderFieldWithValue` to re-render `CollabTextField` (and `CollabTextRenderer`) with `defaultValue=''`. By the time `room.synced` resolved and `useCollabSeed` fired the seed callback, the closure saw `defaultValue=''` — so the `fragment.length === 0 && defaultValue && editor` seed condition was false (the second clause), `setContent` was skipped, and `onChange(plainTextOf(editor))` propagated the editor's still-empty content. The hidden FormData input was then `value=""` at submit time → server-side `required` validation failed.
|
|
69
|
+
|
|
70
|
+
Fix: capture the first non-empty `defaultValue` in a `useRef` at mount time and use it as the seed source inside the seedFn. The ref preserves the original SSR-loaded value across the entire `room.synced` lifecycle, so the seed always recovers the right content even if the host prop has been clobbered to `''` by an intermediate sync-triggered round-trip. Once the user types into the editor the fragment is no longer empty, so the ref is read-only from then on — there's no legitimate "user explicitly cleared the field" case where this masks intent.
|
|
71
|
+
|
|
72
|
+
Surfaced by `pilotiq-pro/e2e/tests/collab/relationship-pk-switch.spec.ts` consistently failing against `@pilotiq/pilotiq@0.23.0+` with `422 errors.title = "This field is required"`. After the fix, 3/3 local runs of that spec pass + the full 16/16 `pilotiq-pro/e2e/tests/collab/*` suite passes + 193/193 `@pilotiq/tiptap` unit tests pass + full monorepo typecheck clean.
|
|
73
|
+
|
|
74
|
+
## 3.10.2
|
|
75
|
+
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
- 00b0c48: fix(collab): mirror editor text into the FormData hidden input after first sync
|
|
79
|
+
|
|
80
|
+
`CollabTextRenderer` (the plain-text Tiptap editor mounted for `TextField` / `TextareaField` when collab is on) relied on three paths to keep the hidden FormData mirror in sync with the y-prosemirror-backed editor doc:
|
|
81
|
+
|
|
82
|
+
1. `onUpdate` — fires on every y-prosemirror transaction.
|
|
83
|
+
2. The mount-time safety-net `useEffect(() => onChange(plainTextOf(editor)), [editor])` — fires once when the editor instance materializes.
|
|
84
|
+
3. The `useCollabSeed` callback — seeded empty fragments with the SSR-rendered `defaultValue`, but never propagated the seed (or the post-sync fragment content) back to the host's `onChange`.
|
|
85
|
+
|
|
86
|
+
In the cold-mount case (a fresh peer joining a populated doc), all three paths could miss: the safety net reads `plainTextOf(editor)` before y-prosemirror's `ySyncPlugin` view-hook dispatch has populated the prose-mirror doc, and the subsequent `_forceRerender` / `_typeChanged` transactions occasionally landed in a window where the `update` listener hadn't been installed by the React owner yet. The result: hidden input stayed empty, server-submitted values dropped row text on `disconnect-and-reload`.
|
|
87
|
+
|
|
88
|
+
The fix extends the existing `useCollabSeed` callback to also call `onChange(plainTextOf(editor))` after `room.synced` resolves — regardless of whether the seed branch ran. Idempotent (`setText(sameValue)` is a no-op when `onUpdate` already propagated the value); same shape as the catch-up replay in `@pilotiq-pro/collab`'s `rowArrayBinding.subscribeRows`.
|
|
89
|
+
|
|
90
|
+
Closes the remaining ~20% flake on `pilotiq-pro/e2e/tests/collab/reorder-persistence.spec.ts` (peer C's `metadata.0.heading` hidden input occasionally never appeared within 20s).
|
|
91
|
+
|
|
92
|
+
- b87b2a5: fix(ai): scope inline-diff + chip suggestion appliers by surrounding form id
|
|
93
|
+
|
|
94
|
+
Multi-form pages would route AI suggestions to whichever editor mounted last because both `useAiInlineDiff` and `useAiSuggestionBridge` hard-coded `formId: undefined` when registering their applier — so two editors sharing a field name across forms (e.g. a "summary" `RichTextField` in the main edit form + the same field in a Replicate modal) would race on `registerPendingSuggestionApplier(undefined, fieldName, …)` and the last `useEffect` would win.
|
|
95
|
+
|
|
96
|
+
**`@pilotiq/pilotiq` (minor — new public API, additive)**
|
|
97
|
+
|
|
98
|
+
- New `useFormId(): string | undefined` hook re-exported from `@pilotiq/pilotiq/react`. Reads the surrounding `FormRenderer`'s id from `FormIdContext` and normalizes the sentinel empty string to `undefined`. Adapter packages (Tiptap + future editor adapters) consume this to scope per-field registries by form.
|
|
99
|
+
- `getPendingSuggestionApplier(undefined, fieldName)` now falls back to ANY matching scoped entry when no wildcard entry is registered. Closes the regression that would have followed from adapter scoping: editors now register under their form's id, so the wildcard slot is almost always empty — without the fallback, global producers (suggestions pushed without a `formId`) would silently fail to resolve. Scoped lookups + explicit wildcard registrations preserve their original precedence.
|
|
100
|
+
|
|
101
|
+
**`@pilotiq/tiptap` (patch — internal hook wiring)**
|
|
102
|
+
|
|
103
|
+
`useAiInlineDiff` and `useAiSuggestionBridge` now thread `useFormId()` into `registerPendingSuggestionApplier(formId, fieldName, applier)` and the effect's dep array. No public-surface change; the multi-form routing simply works now.
|
|
104
|
+
|
|
105
|
+
Coverage: 9 new unit tests on `PendingSuggestionApplierRegistry` cover scoped lookup, scoped multi-form disambiguation, the global-producer fallback, precedence (wildcard wins over scoped for undefined lookups when both exist; scoped wins for explicit lookups), unregister cleanup, and re-register identity guard.
|
|
106
|
+
|
|
107
|
+
- 20513fc: fix(collab): mirror markdown editor into the FormData hidden input after first sync
|
|
108
|
+
|
|
109
|
+
Third symmetric application of the subscribe-after-sync mirror — `MarkdownEditor` had the same gap as `TiptapEditor` and `CollabTextRenderer`. The wrapping host (`MarkdownEditorHost` in pilotiq core) drives a hidden FormData input from React state populated ONLY through the editor's `onChange` callback. In the cold-mount case (a fresh peer joining a populated doc) y-prosemirror's `ySyncPlugin` view-hook `_forceRerender` could land before the React owner installed the `update` listener — leaving the hidden input at its SSR-rendered `defaultValue` through to submit.
|
|
110
|
+
|
|
111
|
+
The `useCollabSeed` callback now serializes the editor's markdown via `editor.storage.markdown.getMarkdown()` and fires `onChange(md)` after `room.synced` resolves, alongside the existing empty-fragment seed branch. Idempotent — when `onUpdate` already propagated the value, this is a no-op `setText(sameValue)`.
|
|
112
|
+
|
|
113
|
+
Sister audit on `@pilotiq/codemirror`'s `CollabCodeMirrorEditor` came back clean: that path already reads `yText.toString()` synchronously in its mount effect and propagates via `setText`, so the catch-up is built into the codemirror branch.
|
|
114
|
+
|
|
115
|
+
- a81af81: fix(collab): mirror rich-text editor into the FormData hidden input after first sync
|
|
116
|
+
|
|
117
|
+
Symmetric follow-on to the `CollabTextRenderer` fix from the same session. `TiptapEditor` (the rich-text surface mounted for `RichTextField`) had the same subscribe-after-sync gap: `onUpdate` debounces to `setSerialized(ed.getHTML() | JSON.stringify(ed.getJSON()))` on every keystroke, but in the cold-mount case (a fresh peer joining a populated doc) y-prosemirror's `ySyncPlugin` view-hook `_forceRerender` could land before the React owner installed the `update` listener — leaving the hidden FormData input at its SSR-rendered initial value through to submit.
|
|
118
|
+
|
|
119
|
+
The `useCollabSeed` callback now also calls `setSerialized` (using the same `storage`-mode-aware serialization as the debounced `onUpdate` body) after `room.synced` resolves. Idempotent — when `onUpdate` already propagated the value, this is a no-op `setSerialized(sameValue)`.
|
|
120
|
+
|
|
121
|
+
Not tickled by `pilotiq-pro/e2e/tests/collab/reorder-persistence.spec.ts` (which exercises plain-text fields), but closes the same latent flake for any `RichTextField` mounted under a populated collab room.
|
|
122
|
+
|
|
123
|
+
## 3.10.1
|
|
124
|
+
|
|
125
|
+
### Patch Changes
|
|
126
|
+
|
|
127
|
+
- 143e4a3: fix(adapters): adapter polish — TiptapEditor.setEditable sync, MarkdownEditor upload errors, CodeMirror useMemo + yCollab cast cleanup
|
|
128
|
+
|
|
129
|
+
Bundle of three small adapter-side correctness / hygiene fixes from the 2026-05-21 code-quality sweep (Phase 6 a/b/c). Phase 6d (consume `@rudderjs/sync/react` collab hooks) is deferred to its own focused session since it needs playground + dual-browser smoke; Phase 6e (React-mount test coverage) is its own pass — neither ships here.
|
|
130
|
+
|
|
131
|
+
- **6a — `TiptapEditor.setEditable` runtime sync.** `useEditor({ editable: !disabled, … })` only fires at construction. A parent flipping `disabled` after mount (validation failure mid-edit, form submitting state) would silently no-op. Sibling adapters `MarkdownEditor.tsx:256-259` and `CollabTextRenderer.tsx:127-130` already wire the matching effect; this aligns `TiptapEditor.tsx` with them.
|
|
132
|
+
- **6b — `MarkdownEditor.uploadAndInsert` surfaces server errors.** `if (!res.ok || !data.ok || !data.url) return` silently stopped the spinner with no toast and no console — users see the upload button revert with no signal that anything went wrong. Now wired through `useToast()` from `@pilotiq/pilotiq/react` (same surface `<Toolbar>`'s media-dialog already uses): network-fail and server-fail both emit an `error`-type toast with the server's `data.error` (or `"Upload failed (status N)."` fallback). Falls back to a no-op when no `ToasterProvider` is mounted — `useToast` returns a default context.
|
|
133
|
+
- **6c — CodeMirror `useMemo` + `as never` cleanup.** Two adjacent fixes in the codemirror adapter:
|
|
134
|
+
- `CodeMirrorEditor.tsx:131` — the `const initial = useMemo(() => stringValue(defaultValue), [])` indirection was dressing — `useState<string>(initial)`'s initializer only runs once on mount regardless. Inlined as `useState<string>(() => stringValue(defaultValue))` with a comment naming the uncontrolled-fallback semantic + how `key`-based remount is the documented pattern for resetting a starting value across record swaps (the controlled path via `Form.stateUrl` doesn't need it).
|
|
135
|
+
- `CollabCodeMirrorEditor.tsx:125` — `yCollab(yText, awareness, { undoManager: false } as never)` dropped the `as never` cast. Verified against `y-codemirror.next@^0.5`'s `index.d.ts`: the option key is `undoManager` (typed `Y.UndoManager | false`), not the suspected `yUndoManager` — the cast was bypassing typecheck for nothing.
|
|
136
|
+
|
|
137
|
+
Tests: 183 / 183 green in `@pilotiq/tiptap`; 22 / 22 green in `@pilotiq/codemirror`; monorepo `pnpm typecheck` clean (9 / 9 packages).
|
|
138
|
+
|
|
139
|
+
- 89a9101: feat(collab): consume `@rudderjs/sync/react`'s collab-room lifecycle via `useCollabSeed` (Phase 6d of the code-quality sweep)
|
|
140
|
+
|
|
141
|
+
The same `provider.once('synced', …)` + empty-fragment seed dance was duplicated across four pilotiq adapters (`TiptapEditor`, `MarkdownEditor`, `CollabTextRenderer`, `CollabCodeMirrorEditor`) and `@pilotiq-pro/collab`'s `useRecordCollabRoom`. `@rudderjs/sync@1.2.0` shipped `@rudderjs/sync/react` with `CollabRoomManager` (cancellation-safe, idempotent stop, optional `y-indexeddb`); this PR threads its synced Promise through pilotiq's open-core `CollabRoom` so adapters can consume the consolidated seed-gate via `useCollabSeed`.
|
|
142
|
+
|
|
143
|
+
**`@pilotiq/pilotiq` (minor — new public API + widened `CollabRoom` shape, both additive)**
|
|
144
|
+
|
|
145
|
+
- `CollabRoom` interface widened with two optional fields:
|
|
146
|
+
- `synced?: Promise<void>` — resolves on the provider's first sync. Stamped by `@pilotiq-pro/collab@>=0.2`'s `<RecordCollabRoom>`; absent for legacy / hand-rolled providers.
|
|
147
|
+
- `persistence?: unknown` — `y-indexeddb` handle, opaque to pilotiq core. Present when the room owner wired offline persistence; absent otherwise.
|
|
148
|
+
- New `useCollabSeed(room, fragmentKey, seedFn)` hook (re-exported from `@pilotiq/pilotiq/react`). Mirrors `@rudderjs/sync/react`'s shape — reimplemented locally so pilotiq core stays free of any hard runtime dep on Yjs. Waits for `room.synced` to resolve, wraps `seedFn` in `ydoc.transact(..., 'pilotiq-collab-seed')`. Consumers manage their own share-type lookup (`doc.getXmlFragment(key)` for Tiptap; `doc.getText(key)` for CodeMirror) and emptiness check, since the share type varies per adapter and pilotiq's `CollabRoom.ydoc` stays `unknown`.
|
|
149
|
+
- `onProviderSynced` is unchanged and still exported for back-compat — legacy rooms without `.synced` short-circuit through `useCollabSeed` immediately (seeded=true with no callback fired), so any adapter still calling `onProviderSynced` keeps working unchanged.
|
|
150
|
+
|
|
151
|
+
**`@pilotiq/tiptap` (patch — internal migration, no public-surface change)**
|
|
152
|
+
|
|
153
|
+
`TiptapEditor`, `MarkdownEditor`, and `CollabTextRenderer` each dropped their inline `useEffect(() => onProviderSynced(provider, trySeed), [editor, collabActive, room])` block in favour of one `useCollabSeed(editor && collabActive ? room : null, collabName, seedFn)`. The shape of the seed (Y.XmlFragment empty-check + `editor.commands.setContent(initialContent)` via the y-prosemirror binding) is unchanged. Roughly −40 LOC per file; the `hasSeeded` `useState` slots are gone (the hook owns dedup).
|
|
154
|
+
|
|
155
|
+
**`@pilotiq/codemirror` (patch — internal migration, additive prop)**
|
|
156
|
+
|
|
157
|
+
- New optional `synced?: Promise<void> | null` prop on `CollabCodeMirrorEditor`. Threaded from the wrapper in `CodeMirrorEditor.tsx`'s `<CollabBranch>` so the renderer can gate the brand-new-record Y.Text seed on the same Promise.
|
|
158
|
+
- Seed logic moved out of the EditorView mount effect to a top-level `useCollabSeed` call. The mount-time pre-seed (`EditorState.create({ doc: yText.toString(), ... })`) is unchanged — that path handles re-mount onto a yText that already has content (e.g. `renameRow` clones); the post-sync seed handles brand-new records where the share is empty after first sync.
|
|
159
|
+
- `onProviderSynced` + `SyncedProviderLike` no longer imported. The `synced` prop is optional with `null` default — passing nothing falls back to seeding immediately, matching the legacy `onProviderSynced(null, …)` no-op posture.
|
|
160
|
+
|
|
161
|
+
No wire-protocol changes. The race window (two peers mounting against a brand-new record may both see "empty" + seed) is unchanged from the prior `onProviderSynced` path; the fix is server-side seed handoff, deferred.
|
|
162
|
+
|
|
163
|
+
Coverage: existing tests pass unchanged (tiptap 183/183, codemirror 22/22, pilotiq monorepo typecheck 9/9). Dual-browser smoke via the existing `pilotiq-pro/e2e/collab` Playwright suite gates the actual sync behaviour.
|
|
164
|
+
|
|
165
|
+
- 63b5dc1: fix(tiptap): inline-diff effect re-runs when editor doc shape changes
|
|
166
|
+
|
|
167
|
+
`useAiInlineDiff`'s diff-start effect previously depended only on `[editor, list]`, so a surgical suggestion arriving during the seed window of a collab-enabled markdown / richtext editor (editor mounts with an empty placeholder paragraph, then the Yjs provider seeds the real content asynchronously) would call `planReplaceBlock(editor, blockIndex, …)` against the empty doc, get `null` for any blockIndex >= 1, and silently bail. The suggestion stayed in the queue, the banner appeared, but no decorations rendered. On Accept, the applier's auto-mode fallback re-planned the modifier against the now-seeded doc and dispatched it — so the change landed but the user never saw a preview.
|
|
168
|
+
|
|
169
|
+
Fix: subscribe to the editor's `doc.childCount` via `useEditorState` and include it in the diff-start effect's deps. The effect now re-runs when the doc transitions from empty (during seed) → loaded (after sync), picking up any suggestions whose modifier returned null on first attempt.
|
|
170
|
+
|
|
171
|
+
No behaviour change outside the seed-race window. Established tests + e2e suite (5 cases, ~15s) green.
|
|
172
|
+
|
|
173
|
+
## 3.10.0
|
|
174
|
+
|
|
175
|
+
### Minor Changes
|
|
176
|
+
|
|
177
|
+
- 349c1f3: fix(collab-text): split `name` (FormData/AI routing) from `fragmentKey` (collab Y fragment) on the plain-text collab renderer
|
|
178
|
+
|
|
179
|
+
Audit catch from the same family as the `MarkdownEditor` fix in `@pilotiq/pilotiq@0.20.0` / `@pilotiq/tiptap@3.9.0`. The `CollabTextRenderer` (Tiptap-backed plain-text editor used by collab-enabled `TextField` / `TextareaField` / `MarkdownField`'s collab fallback) had the same single-prop / two-concerns shape:
|
|
180
|
+
|
|
181
|
+
- `TextLikeInput.tsx → CollabTextField` and `MarkdownInput.tsx → MarkdownCollabInput` both overrode the renderer's `name` with the composite row-id fragment key — needed for `Y.XmlFragment` stability under reorders — but that override ALSO re-keyed AI suggestion routing (`useAiSuggestionBridge`), so the chip-widget surface on a plain `TextField` nested in a Repeater row would never receive AI suggestions addressed by the positional FormData name (`metadata.0.title`).
|
|
182
|
+
|
|
183
|
+
Fix: `CollabTextRendererProps` now carries an optional `fragmentKey`. `CollabTextRenderer` uses `fragmentKey ?? name` for the collab factory `fieldName` + first-load `ydoc.getXmlFragment(...)` seed only; AI suggestion bridge + form integration stay on `name`. Both host wrappers pass `name={hiddenInputName}` (positional FormData path) and `fragmentKey={composite}` (row-id-anchored) when the two differ; top-level fields omit `fragmentKey` and keep today's behavior.
|
|
184
|
+
|
|
185
|
+
Latent bug, fixed preemptively: AI tool calls on plain `TextField` nested in a Repeater / Builder row would silently fail to render their inline-diff chip — same root cause as the `MarkdownField` bug in `@pilotiq/tiptap@3.8.0` and below, just for the chip-widget surface instead of the inline-diff overlay.
|
|
186
|
+
|
|
187
|
+
All 16 collab e2e tests + 4 AI surgical e2e tests pass against the change.
|
|
188
|
+
|
|
189
|
+
- 29ccaff: fix(tiptap): RichTextField collab Y fragment now uses a row-id-anchored composite key inside Repeater / Builder rows
|
|
190
|
+
|
|
191
|
+
Mirrors the `MarkdownField` fix shipped in `@pilotiq/tiptap@3.9.0`. When `TiptapEditor` mounts inside a Repeater / Builder row (i.e. its `name` is a dotted positional path like `metadata.0.body` AND a `RowCoordsContext` is present), the editor now computes a stable composite key — `metadata.<rowId>.body` — and uses it for:
|
|
192
|
+
|
|
193
|
+
- `ydoc.getXmlFragment(...)` first-load seeding
|
|
194
|
+
- The collab extension factory's `fieldName` (Yjs collab scope per field)
|
|
195
|
+
|
|
196
|
+
`name` remains the positional FormData path everywhere else — AI suggestion routing (`useAiInlineDiff`, `useAiSuggestionBridge`), the inline-diff banner, mentions, and the hidden form input.
|
|
197
|
+
|
|
198
|
+
Different mechanics from the `MarkdownField` fix: `MarkdownField` has a textarea fallback path that needs the same composite, so the logic lived in `@pilotiq/pilotiq`'s `MarkdownInput` host. `RichTextField` has no fallback — pilotiq core dispatches the registered renderer directly — so the composite logic lives here, inside the only editor that needs it. `useRowCoords` + `parseRowFieldPath` are already exported from `@pilotiq/pilotiq/react`.
|
|
199
|
+
|
|
200
|
+
Latent bug, fixed preemptively: no consumer currently nests `RichTextField` inside a Repeater / Builder row, but if one did, row reorders would silently rebind the Y.XmlFragment to the wrong row's editor (the fragment key was the positional `metadata.<index>.body`, which shifts on reorder). AI suggestion routing was unaffected — positional names matched on both sides.
|
|
201
|
+
|
|
202
|
+
## 3.9.0
|
|
203
|
+
|
|
204
|
+
### Minor Changes
|
|
205
|
+
|
|
206
|
+
- cead688: fix(markdown): split `name` (FormData/AI routing) from `fragmentKey` (collab Y fragment) on the markdown editor
|
|
207
|
+
|
|
208
|
+
`MarkdownEditorProps` previously had a single `name` prop that drove both the FormData hidden input + AI suggestion routing AND the `Y.XmlFragment` key. Inside a Repeater / Builder row, `MarkdownInput` overrode `name` with a row-id-anchored composite (`metadata.<rowId>.body`) so the Y fragment survived row reorders — but this also re-keyed the AI applier registry and `<AiSuggestionBanner>`, so tool calls that referenced the field by its dotted FormData name (`metadata.0.body`) never reached the row editor.
|
|
209
|
+
|
|
210
|
+
Result: AI surgical / whole-field suggestions on a `MarkdownField` nested inside a Repeater row silently failed — the tool reported "queued for review" but no diff overlay appeared in the row.
|
|
211
|
+
|
|
212
|
+
Fix: `MarkdownEditorProps` now carries a separate optional `fragmentKey` prop. The editor uses it for the collab Y fragment key (`ydoc.getXmlFragment(...)` + the collab factory's `fieldName`) but keeps `name` for everything else — AI suggestion routing, applier registry, hidden FormData input, inline-diff banner. Top-level fields omit `fragmentKey`; row leaves pass the composite as `fragmentKey` while leaving `name` as the dotted FormData path.
|
|
213
|
+
|
|
214
|
+
`@pilotiq/tiptap`'s `MarkdownEditor` accepts the new prop and routes it correctly. `@pilotiq/pilotiq`'s `MarkdownInput` passes both props to the registered editor.
|
|
215
|
+
|
|
216
|
+
Caveat: `RichTextField`'s `TiptapEditor` has the analogous single-`name` shape and would surface the same gap if nested in a Repeater. Not in scope for this change — no consumer currently nests `RichTextField` in a row. File a follow-up when it becomes a real path.
|
|
217
|
+
|
|
218
|
+
## 3.8.0
|
|
219
|
+
|
|
220
|
+
### Minor Changes
|
|
221
|
+
|
|
222
|
+
- 7cbf610: feat(tiptap): auto-mode applier for surgical AI inline-diff ops
|
|
223
|
+
|
|
224
|
+
`useAiInlineDiff`'s registry applier now handles two paths:
|
|
225
|
+
|
|
226
|
+
1. **Review accept (existing).** Suggestion was already started via `startAiInlineDiff` / `applySurgicalAiInlineDiff`; approve runs `acceptAiInlineDiff()`.
|
|
227
|
+
2. **Auto-mode direct apply (new).** Suggestion arrives at the applier with `meta.surgical` but was never started (the producer bypassed the queue). The hook plans the same modifier the diff path uses and dispatches it as a plain transaction — no diff overlay, no Accept / Reject step.
|
|
228
|
+
|
|
229
|
+
Mirrors the existing `set_value` auto-mode behaviour, where the AI tool binding calls the applier directly with a synthesized suggestion to skip the review queue. Surgical ops in `Pilotiq.aiSuggestionsMode('auto')` now write through immediately instead of always waiting on the user.
|
|
230
|
+
|
|
231
|
+
Review-mode behaviour unchanged.
|
|
232
|
+
|
|
233
|
+
- 374168b: feat(tiptap): cross-tool-call stacking for surgical AI inline-diff ops
|
|
234
|
+
|
|
235
|
+
When a surgical AI suggestion arrives while an inline-diff review is already active for the same field, `useAiInlineDiff` now folds the new op into the active diff instead of stalling the suggestion in the queue.
|
|
236
|
+
|
|
237
|
+
Previously: the second suggestion sat in the queue until the user approved or rejected the first, then started its own diff afterwards. Worse, if the user clicked Accept while two were pending, the banner's "approve all" path dismissed both queue entries even though only the first had been applied — the second was silently dropped.
|
|
238
|
+
|
|
239
|
+
Now: the new modifier dispatches as a plain transaction; the extension's plugin folds the resulting steps into the running changeset, so:
|
|
240
|
+
|
|
241
|
+
- The banner shows the combined count (`"N changes suggested"`).
|
|
242
|
+
- Decorations update to cover both ops' ranges.
|
|
243
|
+
- Accept commits the union, Reject reverts to the original baseline captured when the first suggestion started the diff — semantically "reject all pending suggested changes", matching the banner copy.
|
|
244
|
+
|
|
245
|
+
Whole-field (non-surgical) suggestions still bail when a diff is active — replacing the entire doc on top of an active review would be too disruptive. That gap (whole-field stacking + silent-drop) remains a known issue, deferred until a consumer hits it.
|
|
246
|
+
|
|
247
|
+
- cabbcf3: feat(tiptap): surgical AI inline-diff ops now support markdown fields
|
|
248
|
+
|
|
249
|
+
`planReplaceBlock` and `planInsertBlockBefore` now auto-detect markdown editors by sniffing for the `tiptap-markdown` extension's `storage.markdown.parser`:
|
|
250
|
+
|
|
251
|
+
- **Richtext (`RichTextField` / `TiptapEditor`)** — unchanged. `content` is HTML and parses through `DOMParser.fromSchema(...).parseSlice(...)` directly.
|
|
252
|
+
- **Markdown (`MarkdownField` / `MarkdownEditor`)** — new. `content` is markdown source; the planner runs it through the markdown-it parser bundled with `tiptap-markdown` to produce HTML first, then parses that as a Slice.
|
|
253
|
+
|
|
254
|
+
Mirrors the same auto-detect strategy `MarkdownEditor.tsx` already uses for whole-field `parseSuggestion` callbacks, so surgical ops on markdown fields now share the same content-handling path as the existing whole-field replacement path.
|
|
255
|
+
|
|
256
|
+
Closes follow-up #4 of the surgical block ops shipped in `@pilotiq/tiptap@3.7.0`.
|
|
257
|
+
|
|
258
|
+
## 3.7.0
|
|
259
|
+
|
|
260
|
+
### Minor Changes
|
|
261
|
+
|
|
262
|
+
- b5462b7: feat(tiptap): surgical block-level inline-diff ops for AI agents
|
|
263
|
+
|
|
264
|
+
Adds 4 precise block-edit primitives the AI agent can call instead of always rewriting the whole field via `set_value`. Each lands an inline-diff overlay scoped to just the changed range — far cheaper in tokens, far cleaner UX for the reviewer.
|
|
265
|
+
|
|
266
|
+
**New extension command** on `AiInlineDiffExtension`:
|
|
267
|
+
|
|
268
|
+
- `applySurgicalAiInlineDiff(id, applyFn)` — snapshots the current doc as the baseline, runs `applyFn(tr)` to mutate the transaction with a precise change, then folds the resulting steps into the changeset. The existing decoration spec walks per-change ranges, so surgical edits get the same green-insert / red-strikethrough overlay as whole-field replacements, but only on the touched blocks.
|
|
269
|
+
|
|
270
|
+
**4 planner helpers** in a new `surgicalOps.ts` module (re-exported from the package root):
|
|
271
|
+
|
|
272
|
+
- `planReplaceBlock(editor, blockIndex, html)` — swap one top-level block.
|
|
273
|
+
- `planInsertBlockBefore(editor, blockIndex, html)` — insert before a given index (or append at `doc.childCount`).
|
|
274
|
+
- `planDeleteBlock(editor, blockIndex)` — delete one top-level block. Refuses the last remaining block.
|
|
275
|
+
- `planUpdateBlockMark(editor, blockIndex, mark, range, apply, attrs?)` — apply/remove inline marks on a character range _within_ a block. Offsets are 0-based within the block's text.
|
|
276
|
+
- `summarizeBlockStructure(doc, maxChars?)` — render the doc's top-level structure as a numbered list (`[0] heading: Welcome`, …) for sending to the AI alongside the field value.
|
|
277
|
+
|
|
278
|
+
Each planner returns a `TransactionModifier | null` — `null` means "abort, this can't be planned" (out-of-range index, unparseable HTML, unknown mark).
|
|
279
|
+
|
|
280
|
+
**`useAiInlineDiff` hook** now reads `meta.surgical` on pending suggestions in two shapes:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
// Single op (one surgical change)
|
|
284
|
+
meta: { surgical: { op: 'replace_block', blockIndex: 2, content: '<h2>...</h2>' } }
|
|
285
|
+
|
|
286
|
+
// Batched ops (multiple surgical changes from one AI tool call)
|
|
287
|
+
meta: { surgical: { ops: [
|
|
288
|
+
{ op: 'replace_block', blockIndex: 0, content: '<h1>Title</h1>' },
|
|
289
|
+
{ op: 'insert_block_before', blockIndex: 2, content: '<p>New para</p>' },
|
|
290
|
+
] } }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Batches are applied as one combined diff: modifiers are computed against the original (pre-transaction) doc, then dispatched in DESC `blockIndex` order so earlier modifiers' edits at higher positions don't shift the absolute positions later modifiers were planned with. The user sees a single inline-diff overlay with one Accept / Reject covering every op in the batch — rather than N pending suggestions that have to be reviewed serially.
|
|
294
|
+
|
|
295
|
+
Whole-field suggestions (no surgical meta) continue through the existing `startAiInlineDiff` path.
|
|
296
|
+
|
|
297
|
+
Also re-exports `AiInlineDiffExtension` / `aiInlineDiffPluginKey` / `getAiInlineDiffState` from the package root for consumers that want to read diff state directly.
|
|
298
|
+
|
|
299
|
+
## 3.6.0
|
|
300
|
+
|
|
301
|
+
### Minor Changes
|
|
302
|
+
|
|
303
|
+
- 8a32c8e: feat(tiptap): inline-diff visualization + banner UX for whole-field AI suggestions on RichTextField and MarkdownField
|
|
304
|
+
|
|
305
|
+
The chip widget path (`AiSuggestionExtension`) keeps its role for _surgical_ range-anchored suggestions (`format_text`, `set_link`, `insert_paragraph`, …) — those have a precise location worth visualizing inline. For whole-field replacements from chat-driven `update_form_state` / `set_value` calls, the chip's `textContent` render surfaced raw markup as literal text inside the green pill — visually unparseable on multi-paragraph rewrites.
|
|
306
|
+
|
|
307
|
+
Two new pieces in `@pilotiq/tiptap`:
|
|
308
|
+
|
|
309
|
+
1. **`AiInlineDiffExtension` + `useAiInlineDiff` hook** — Tiptap-Pro-class inline-diff visualization driven by [`prosemirror-changeset`](https://github.com/ProseMirror/prosemirror-changeset). The hook watches `<PendingSuggestionsContext>` for whole-field suggestions, runs the renderer-supplied parser (`tiptap-markdown.parser.parse(value)` → HTML → `ProseMirrorDOMParser.parseSlice` for markdown / direct DOMParser for richtext), and calls `editor.commands.startAiInlineDiff(id, slice)`. The extension snapshots the current doc as the baseline, replaces the doc body with the proposed slice, and initializes a changeset tracking the diff. Decorations render:
|
|
310
|
+
|
|
311
|
+
- Green-background `<span>` over inserted ranges (current doc).
|
|
312
|
+
- Strikethrough widget at the insert anchor showing the _deleted_ text in red — the deleted content isn't in the current doc, so a widget is the only way to surface it.
|
|
313
|
+
- `acceptAiInlineDiff()` clears the diff state (current doc IS the accepted state). `rejectAiInlineDiff()` reverts the doc to the captured baseline. Both commands are public and the host's banner drives them.
|
|
314
|
+
|
|
315
|
+
2. **`<AiSuggestionBanner>` host component** — a top-of-editor strip that mounts above the editor when whole-field suggestions are pending. Replaces the chip path for richtext / markdown surfaces (which always had ugly raw-markup chips). Two modes:
|
|
316
|
+
- Default (no diff): Accept routes through the renderer-supplied `onApplyWholeField(value)`, mirroring the previous chip-Approve semantics for plain text fallback.
|
|
317
|
+
- Diff-active: `onAcceptViaEditor` / `onRejectViaEditor` props route through the extension's commands so the doc commits / reverts cleanly.
|
|
318
|
+
|
|
319
|
+
Default CSS for both the banner chrome and the diff decorations auto-injects on first mount (idempotent via sentinels), so consumers see the visualization out of the box. Class names (`pilotiq-ai-banner-*` + `pilotiq-ai-diff-*`) stay the documented surface for theme customization.
|
|
320
|
+
|
|
321
|
+
`MarkdownEditor` and `TiptapEditor` mount the new extension + banner; `CollabTextRenderer` keeps the chip path (plain-text replacement renders cleanly in the chip).
|
|
322
|
+
|
|
323
|
+
Wire shape unchanged on the host side — `@pilotiq-pro/ai`'s `update_form_state` → `set_value` tool keeps emitting a single `suggestedValue` string. The renderer-supplied parser decides what to do with it.
|
|
324
|
+
|
|
325
|
+
## 3.5.0
|
|
326
|
+
|
|
327
|
+
### Minor Changes
|
|
328
|
+
|
|
329
|
+
- 644939b: fix(pilotiq, tiptap): route AI suggestions through the Tiptap bridge for collab-on / markdown / richtext fields — fixes chat-driven `update_form_state` no-op
|
|
330
|
+
|
|
331
|
+
Two cooperating bugs left chat-sidebar Approve doing nothing on Tiptap-backed fields:
|
|
332
|
+
|
|
333
|
+
1. **`FieldShell` overlay shadowed the bridge.** The gate `isRichText = fieldType === 'richtext'` ran the legacy overlay UI on `markdown` / `text` / `textarea`, _and_ registered a generic DOM-write applier that overwrote the Tiptap bridge's applier in the registry (parent effect runs after children). Approve set the hidden `<input>`'s `.value`, which the Tiptap editor never observes, so the visible content never changed.
|
|
334
|
+
|
|
335
|
+
2. **Bridge skipped whole-field suggestions.** `useAiSuggestionBridge` only pushed entries with `meta.editorRange = { from, to }` into the editor. Chat-agent producers like `@pilotiq-pro/ai`'s `update_form_state` tool target the whole field — no range — so suggestions sat in the queue with no chip widget and no applier path.
|
|
336
|
+
|
|
337
|
+
Fix:
|
|
338
|
+
|
|
339
|
+
- **`@pilotiq/pilotiq`** — `FieldShell` widens `isRichText` to `isTiptapMounted`: `richtext` always, `markdown` when a `MarkdownEditor` is registered, `text` / `textarea` when both a `CollabTextRenderer` is registered and `useCollabRoom()` resolves a room. Hides the legacy overlay and skips DOM-write applier registration so the bridge's editor-driven applier owns the surface.
|
|
340
|
+
|
|
341
|
+
- **`@pilotiq/tiptap`** — `useAiSuggestionBridge` accepts a new `onApplyWholeField(value)` option. When Approve fires for a non-bridge-pushed id, the bridge calls this callback instead of no-op'ing. Each renderer passes its own implementation:
|
|
342
|
+
- `CollabTextRenderer` → `editor.commands.setContent(plainTextToDoc(value, multiline))` — y-prosemirror syncs the resulting transaction to peers when collab is on.
|
|
343
|
+
- `MarkdownEditor` → `editor.commands.setContent(value)` — the Markdown extension parses the raw source.
|
|
344
|
+
- `TiptapEditor` (RichTextField) → `editor.commands.setContent(value)` — HTML / JSON.
|
|
345
|
+
|
|
346
|
+
After the fix every chat-driven `update_form_state` set-value lands on the visible editor surface across all three Tiptap mounts. Range-anchored suggestions (existing chip-widget path) keep their original behavior unchanged.
|
|
347
|
+
|
|
348
|
+
**Plus inline-diff visualization for whole-field suggestions.** Two follow-on improvements in `@pilotiq/tiptap`:
|
|
349
|
+
|
|
350
|
+
- `useAiSuggestionBridge` accepts `synthesizeWholeFieldRange(editor, suggestion) => { from, to } | undefined`. When opted in, whole-field suggestions get a synthesized range and the inline-diff chip widget renders BEFORE the user approves (red strikethrough on the current value + green chip with the suggested text + ✓/✕ buttons). `CollabTextRenderer` opts in with `{ from: 0, to: editor.state.doc.content.size }` — its plain-text schema accepts the extension's text-node replacement on Approve cleanly. `MarkdownEditor` and `TiptapEditor` abstain (they'd lose formatting on the chip-driven approve) and continue to use the silent `onApplyWholeField` fallback.
|
|
351
|
+
|
|
352
|
+
- `AiSuggestionExtension` injects minimal default styles into `<head>` on first mount (idempotent via a `data-pilotiq-ai-suggestion-styles` sentinel). Consumers no longer need to wire CSS for the chip — they see the visualization out of the box. User stylesheets still override since they cascade after the injected `<style>` block, and the class names (`pilotiq-ai-suggestion-original` / `-chip` / `-replacement` / `-accept` / `-reject`) stay the documented surface for customization.
|
|
353
|
+
|
|
354
|
+
### Patch Changes
|
|
355
|
+
|
|
356
|
+
- adc0ce0: feat(pilotiq, tiptap): auto-upgrade `TextField` / `TextareaField` to the Tiptap-backed editor when AI agents are attached (no collab required)
|
|
357
|
+
|
|
358
|
+
Previously, the Tiptap-backed renderer (`CollabTextRenderer` in `@pilotiq/tiptap`) only mounted when a `<RecordCollabRoom>` was active — so AI suggestions on plain (non-collab) `TextField` / `TextareaField` fell back to the legacy DOM-write overlay, with no inline-diff chip widget.
|
|
359
|
+
|
|
360
|
+
The rule is now: a text-like field gets the Tiptap surface if **any one of**:
|
|
361
|
+
|
|
362
|
+
1. A collab room is active (existing behavior — cursor preservation under concurrent edits).
|
|
363
|
+
2. AI agents are attached via `field.ai([…])` (new — the inline-diff chip needs a ProseMirror surface to render).
|
|
364
|
+
3. The field is a `MarkdownField` (existing — always Tiptap).
|
|
365
|
+
|
|
366
|
+
`TextLikeInput` widens its routing gate from `room && collabRenderer …` to `(room || hasAi) && collabRenderer …`. `FieldShell` mirrors the widening so its legacy overlay + DOM-write applier stay out of the way when the Tiptap bridge owns the surface. `CollabTextRenderer` already handles `useCollabRoom() === null` — it just mounts the editor without the Yjs Collaboration extension, so this widening doesn't force a collab room.
|
|
367
|
+
|
|
368
|
+
No new public API. Users get the auto-upgrade for free by attaching agents — exactly what they already do to opt into AI features on a field.
|
|
369
|
+
|
|
370
|
+
**`@pilotiq/tiptap` follow-on:**
|
|
371
|
+
|
|
372
|
+
- `CollabTextRenderer` now sets `immediatelyRender: false` on the editor config. Pre-rule-#2 the host's `TextLikeInput` gated on a live collab room (client-only state), so SSR fell through to the native input and the editor never constructed server-side. With AI-attached fields now SSR-rendering Tiptap, `useEditor` would throw `"Tiptap Error: SSR has been detected, please set immediatelyRender explicitly to false"` on the first direct-navigation request. The flag defers construction to the first React effect — empty shell on SSR, live editor on hydration.
|
|
373
|
+
- Build script no longer ships `dist/markdownExtension.js.map`. The bundled file is 371 KB of inlined `tiptap-markdown` + `markdown-it` chain; the sourcemap from `tsc` only described the original ~20-line wrapper, leaving Vite to log a `Sourcemap … points to missing source files` warning on every consumer dev boot.
|
|
374
|
+
|
|
375
|
+
**Inline-diff chip visualization extended to MarkdownEditor + TiptapEditor.** Both now opt into `synthesizeWholeFieldRange` so chat-driven whole-field suggestions (`update_form_state`'s `set_value`) render the chip widget over the whole doc. The bridge tracks synthesized ids in a separate set: on Approve, _producer-supplied_ range hits the editor's `approveAiSuggestion` (text-node replace, surgical), while _synthesized_ whole-doc range delegates to the renderer's `onApplyWholeField` (`setContent(...)`) and clears the chip with a no-op reject. Without this split, approving a synthesized chip on richtext / markdown would do a plain-text replace and clobber all formatting; without the synthesis, the user saw no visualization at all on richtext / markdown.
|
|
376
|
+
|
|
377
|
+
## 3.4.0
|
|
378
|
+
|
|
379
|
+
### Minor Changes
|
|
380
|
+
|
|
381
|
+
- 071ca3a: fix(tiptap): mount `AiSuggestionExtension` + `useAiSuggestionBridge` in `CollabTextRenderer` and `MarkdownEditor`
|
|
382
|
+
|
|
383
|
+
The cross-package AI suggestion plumbing (extension + host bridge to `<PendingSuggestionsContext>`) was wired into `TiptapEditor` (RichTextField) but missing from the other two Tiptap-backed editors:
|
|
384
|
+
|
|
385
|
+
- `CollabTextRenderer` — the Tiptap-backed plain-text path used by `TextField` and `TextareaField` when collab is on.
|
|
386
|
+
- `MarkdownEditor` — `MarkdownField`'s editor surface.
|
|
387
|
+
|
|
388
|
+
`editor.commands.addAiSuggestion(...)` was a no-op on those fields. Now every Tiptap mount across the adapter participates in suggestion mode uniformly — same wire-shape ids, same Approve / Reject chip widgets, same dismissal lifecycle as the rich-text path.
|
|
389
|
+
|
|
390
|
+
No host changes required — the bridge reads the field name from the props the renderers already accept.
|
|
391
|
+
|
|
392
|
+
## 3.3.3
|
|
393
|
+
|
|
394
|
+
### Patch Changes
|
|
395
|
+
|
|
396
|
+
- 1b8c1bc: feat(pilotiq): extract `onProviderSynced(provider, fn)` helper for the seed-on-synced collab lifecycle pattern
|
|
397
|
+
|
|
398
|
+
Adapter packages that bind to a collab room (Tiptap-backed editors, the CodeMirror collab adapter) all need the same choreography on mount: if the provider's already streamed in the initial room state, run the seed callback now; otherwise register `provider.once('synced', fn)` and clean up via `provider.off?.('synced', fn)`. That gate was implemented separately in 4 renderers (`CollabTextRenderer`, `MarkdownEditor`, `TiptapEditor` in `@pilotiq/tiptap`; `CollabCodeMirrorEditor` in `@pilotiq/codemirror`).
|
|
399
|
+
|
|
400
|
+
This change extracts the pattern into a single helper in `@pilotiq/pilotiq/react` so future bug fixes in the gate logic (StrictMode double-fire, missing-off-method providers, etc.) fix in one place and so adapters from outside this monorepo can adopt the same pattern with one import.
|
|
401
|
+
|
|
402
|
+
**New public surface on `@pilotiq/pilotiq/react`:**
|
|
403
|
+
|
|
404
|
+
- `onProviderSynced(provider, fn): () => void` — runs `fn` synchronously if `provider.synced`, otherwise registers `provider.once('synced', fn)`. Returns a cleanup that safely unregisters via `try { provider.off?.('synced', fn) } catch {}`. Null/undefined provider returns a no-op cleanup.
|
|
405
|
+
- `SyncedProviderLike` — structural type with `synced?: boolean`, `once?(event: 'synced', fn): void`, `off?(event: 'synced', fn): void`. No yjs / y-websocket peer dep — callers cast their concrete provider via `provider as SyncedProviderLike`.
|
|
406
|
+
|
|
407
|
+
**Adapter package changes (patch-grade):**
|
|
408
|
+
|
|
409
|
+
- `@pilotiq/tiptap`: `CollabTextRenderer`, `MarkdownEditor`, and `TiptapEditor` each replace their ~10-line gate block with `return onProviderSynced(provider, trySeed)` (still inside the existing `useEffect`).
|
|
410
|
+
- `@pilotiq/codemirror`: `CollabCodeMirrorEditor` stores the cleanup and invokes it alongside `view.destroy()` inside the mount effect's combined cleanup.
|
|
411
|
+
|
|
412
|
+
Behavior is unchanged — no double-fire risk, no missed-cleanup risk, no API changes for callers of any of the affected renderers.
|
|
413
|
+
|
|
414
|
+
Test coverage: 6 new unit tests in `packages/pilotiq/src/react/onProviderSynced.test.ts` cover synced-now, defer-until-synced, cleanup-before-synced, null provider, off-throws, and provider-missing-once/off.
|
|
415
|
+
|
|
416
|
+
## 3.3.2
|
|
417
|
+
|
|
418
|
+
### Patch Changes
|
|
419
|
+
|
|
420
|
+
- 5907520: fix(tiptap): bundle the markdown extension chain into dist
|
|
421
|
+
|
|
422
|
+
`tiptap-markdown@^0.9`'s transitive `markdown-it-task-lists@2.1.1` is pure CJS (`module.exports = function...`) with no `default` export, which Vite's dev runtime can't synthesize — the `import x from 'markdown-it-task-lists'` inside tiptap-markdown's task-list node threw `does not provide an export named 'default'` at module init and silently killed the entire admin client bundle (no editors mounted, no console-visible error beyond a single `pageerror`). The previous workaround was for downstream consumers to wire `tiptap-markdown` + `markdown-it` + `markdown-it-task-lists` into their `optimizeDeps.include`.
|
|
423
|
+
|
|
424
|
+
Now the chain is pre-bundled into `dist/markdownExtension.js` at `@pilotiq/tiptap` build time via esbuild (`scripts.bundle:markdown`). `MarkdownEditor.tsx` imports `{ Markdown }` from `../markdownExtension.js` instead of `'tiptap-markdown'` directly, so the CJS↔ESM interop lives inside our dist and consumers can drop the `optimizeDeps.include` workaround.
|
|
425
|
+
|
|
426
|
+
`tiptap-markdown` moves from `peerDependencies` to `devDependencies` (consumers no longer need to install it; only used at build time).
|
|
427
|
+
|
|
428
|
+
## 3.3.1
|
|
429
|
+
|
|
430
|
+
### Patch Changes
|
|
431
|
+
|
|
432
|
+
- 894e82a: Fix Tiptap v3 SSR crash in `MarkdownEditor` under Vike. Sets `immediatelyRender: false` so the editor defers DOM construction until the first React effect; SSR renders an empty shell and hydration mounts the live editor.
|
|
433
|
+
|
|
434
|
+
## 3.3.0
|
|
435
|
+
|
|
436
|
+
### Minor Changes
|
|
437
|
+
|
|
438
|
+
- 850638f: `MarkdownField` swaps its textarea + manual-toolbar UI for a real WYSIWYG editor when `@pilotiq/tiptap` is installed. The editor parses markdown into a Tiptap document, exposes a rich-text toolbar (bold / italic / strike / link / heading / lists / blockquote / code / attach files), and serializes back to markdown on every change via `tiptap-markdown`. Editor / Source / Preview tabs let users switch between WYSIWYG, raw markdown, and a rendered preview.
|
|
439
|
+
|
|
440
|
+
Collab is automatic — when a `<RecordCollabRoom>` is up-tree the editor binds to the shared `Y.XmlFragment` the same way `RichTextField` does. All peers see live edits; only the local serialize-to-markdown runs per peer.
|
|
441
|
+
|
|
442
|
+
Wire format unchanged — a plain markdown string under the field name. Panels that don't install `@pilotiq/tiptap` keep the textarea fallback.
|
|
443
|
+
|
|
444
|
+
New public API in pilotiq core:
|
|
445
|
+
|
|
446
|
+
- `registerMarkdownEditor(C) / getMarkdownEditor()` + `MarkdownEditor / MarkdownEditorProps` types — re-exported from `@pilotiq/pilotiq/react`.
|
|
447
|
+
|
|
448
|
+
New in `@pilotiq/tiptap`:
|
|
449
|
+
|
|
450
|
+
- `MarkdownEditor` component, auto-registered by `registerTiptap()` / `tiptap()` plugin.
|
|
451
|
+
- `tiptap-markdown@^0.9` peer dep.
|
|
452
|
+
|
|
453
|
+
## 3.2.1
|
|
454
|
+
|
|
455
|
+
### Patch Changes
|
|
456
|
+
|
|
457
|
+
- Phase D — drop the `_pt:` field-name prefix from `CollabTextRenderer`. The `Y.XmlFragment` now lives under the natural field name. The prefix was a temporary workaround during the Tiptap-backed text-collab swap to dodge a `Y.Text` / `Y.XmlFragment` constructor collision against the legacy form-binding allocation in `@pilotiq-pro/collab`.
|
|
458
|
+
|
|
459
|
+
**Coordination requirement when using `@pilotiq-pro/collab`:** ship the matching `@pilotiq-pro/collab` Phase D update (drops the per-field `Y.Text` allocation) at the same time. Without it, the natural-key `Y.XmlFragment` collides with the legacy `Y.Text(name)` slot and the binding throws on mount. Standalone `@pilotiq/tiptap` consumers (no collab) are unaffected — there's no `Y.Text` allocation in play.
|
|
460
|
+
|
|
461
|
+
Migration note: records that were edited under the pre-Phase-D code carry a stale `Y.Text(name)` in their IndexedDB / server-persisted ydoc state. The new code ignores it (no consumer touches that slot anymore); the persisted record value is unaffected, only per-keystroke CRDT history from active sessions during the migration is silently dropped.
|
|
462
|
+
|
|
463
|
+
## 3.2.0
|
|
464
|
+
|
|
465
|
+
### Minor Changes
|
|
466
|
+
|
|
467
|
+
- 353a228: feat(tiptap): collab-aware editor via pilotiq's collab registries
|
|
468
|
+
|
|
469
|
+
`TiptapEditor` now plugs into `@pilotiq/pilotiq`'s `CollabRoomContext`
|
|
470
|
+
and `CollabExtensionFactory` registry — when a `<RecordCollabRoom>` is
|
|
471
|
+
mounted up-tree AND a plugin (e.g. `@pilotiq-pro/collab`) has registered
|
|
472
|
+
extensions, the editor attaches to the room and uses the field's name as
|
|
473
|
+
the `Y.XmlFragment` selector. Multiple `RichTextField`s on one record
|
|
474
|
+
share ONE Y.Doc + ONE WebSocket connection — mirrors Tiptap's
|
|
475
|
+
"Collaborative Fields" experiment.
|
|
476
|
+
|
|
477
|
+
### Behavior
|
|
478
|
+
|
|
479
|
+
- **Remount on collab toggle.** A `CollabAwareTiptap` shell reads the
|
|
480
|
+
room + factory and keys `ClientEditor` on `collabActive ? 'collab' :
|
|
481
|
+
'local'`. Tiptap can't swap `Collaboration` at runtime, so the keyed
|
|
482
|
+
remount handles the room-attaches-after-mount case cleanly.
|
|
483
|
+
- **History disabled when collab is active.** Yjs ships its own undo
|
|
484
|
+
manager via `Collaboration`; StarterKit's `undoRedo` extension is
|
|
485
|
+
disabled in the collab branch to avoid two stacks fighting.
|
|
486
|
+
- **First-load seed.** After `provider.synced` fires, if the field's
|
|
487
|
+
Y.XmlFragment is empty AND `defaultValue` looks like a Tiptap doc
|
|
488
|
+
(`isTiptapShapedContent` guard), seed once via
|
|
489
|
+
`editor.commands.setContent`. Subsequent joiners find the fragment
|
|
490
|
+
populated and skip.
|
|
491
|
+
- **Lexical-shape guard.** Existing rows holding old Lexical-format JSON
|
|
492
|
+
(`{ root: {...} }`) no longer crash the editor — the same guard skips
|
|
493
|
+
the parse so the editor mounts empty instead.
|
|
494
|
+
- **Per-field opt-out.** `RichTextField.make('private').collab(false)`
|
|
495
|
+
stamps `meta.collab === false`; the renderer skips the collab
|
|
496
|
+
extensions even with a room mounted. (`.collab()` itself lives on the
|
|
497
|
+
`Field` base in `@pilotiq/pilotiq`; this PR only wires the renderer.)
|
|
498
|
+
|
|
499
|
+
### Cosmetics
|
|
500
|
+
|
|
501
|
+
- Field container `<div>` lost `gap-1` — collab cursors render flush
|
|
502
|
+
against the editor frame now.
|
|
503
|
+
|
|
504
|
+
### Required peers (when collab is in use)
|
|
505
|
+
|
|
506
|
+
`@pilotiq/tiptap` itself takes no new peer deps — the collab factory is
|
|
507
|
+
opaque `unknown[]`. The pro consumer (`@pilotiq-pro/collab`) declares
|
|
508
|
+
`@tiptap/extension-collaboration` + `@tiptap/extension-collaboration-caret`
|
|
509
|
+
peers + ships the Yjs runtime.
|
|
510
|
+
|
|
511
|
+
## 3.1.1
|
|
512
|
+
|
|
513
|
+
### Patch Changes
|
|
514
|
+
|
|
515
|
+
- b14119e: Widen the `@pilotiq/pilotiq` peer dependency from `workspace:^` (publishes as `^<version>`) to the literal range `>=0.6.0 <1.0.0`.
|
|
516
|
+
|
|
517
|
+
Under pre-1.0 caret semver, `^0.6.0` does not satisfy `0.7.0`, so every pilotiq minor bump was breaking the adapters' published peer range — which in turn made changesets propose a MAJOR bump on the adapters on every release, even when nothing in them changed. The literal range covers the whole `0.x` track, so the trap no longer fires.
|
|
518
|
+
|
|
519
|
+
## 3.1.0
|
|
520
|
+
|
|
521
|
+
### Minor Changes
|
|
522
|
+
|
|
523
|
+
- e1a79f6: feat(core+tiptap): cross-tree applier registry — Approve from anywhere
|
|
524
|
+
|
|
525
|
+
Phase 8.5 of the AI UX polish plan. Adds an open-core registry that
|
|
526
|
+
lets aggregate consumers — chat-sidebar pending-pills, bulk-action
|
|
527
|
+
menus, future "AI inbox" surfaces — apply a `PendingSuggestion` to its
|
|
528
|
+
target field without sharing the form's React tree.
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
import { registerPendingSuggestionApplier } from "@pilotiq/pilotiq/react";
|
|
532
|
+
|
|
533
|
+
// Renderer-side (auto-wired by FieldShell + Tiptap bridge):
|
|
534
|
+
useEffect(
|
|
535
|
+
() =>
|
|
536
|
+
registerPendingSuggestionApplier(formId, fieldName, (suggestion) => {
|
|
537
|
+
/* apply to this field's underlying input or editor */
|
|
538
|
+
}),
|
|
539
|
+
[formId, fieldName]
|
|
540
|
+
);
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**Core (`@pilotiq/pilotiq`)**:
|
|
544
|
+
|
|
545
|
+
- New module `react/PendingSuggestionApplierRegistry.ts` — module-level
|
|
546
|
+
Map keyed by `(formId, fieldName)` (`formId` defaults to `'*'` for
|
|
547
|
+
global form scope; form-scoped registrations always win over the
|
|
548
|
+
wildcard for the same field). Exposes `registerPendingSuggestionApplier`
|
|
549
|
+
(returns unregister fn for `useEffect` cleanup) and
|
|
550
|
+
`getPendingSuggestionApplier`.
|
|
551
|
+
- `PendingSuggestionsApi` extended with `approve(id)` and
|
|
552
|
+
`approveAll(filter?)` — resolves the suggestion's `(formId,
|
|
553
|
+
fieldName)` against the registry, runs the applier, then dismisses.
|
|
554
|
+
Falls through to plain `dismiss` when no applier is registered or
|
|
555
|
+
the applier throws (so a busted applier doesn't strand entries).
|
|
556
|
+
Default no-op context implements both as plain dismiss.
|
|
557
|
+
- `<FieldShell>` auto-registers a generic applier on mount for every
|
|
558
|
+
non-richtext, non-dotted-path field. Applier uses
|
|
559
|
+
`useFieldState.setValue` for controlled (live) forms and a DOM
|
|
560
|
+
fallback (React's internal value setter via
|
|
561
|
+
`Object.getOwnPropertyDescriptor(proto, 'value').set`) for
|
|
562
|
+
uncontrolled forms. Cleanup on unmount.
|
|
563
|
+
|
|
564
|
+
**Tiptap (`@pilotiq/tiptap`)**:
|
|
565
|
+
|
|
566
|
+
- `useAiSuggestionBridge` registers a richtext-aware applier that
|
|
567
|
+
calls `editor.chain().focus().approveAiSuggestion(id).run()` —
|
|
568
|
+
same path the inline chip click takes. The transaction listener
|
|
569
|
+
already mirrors the editor-side dismissal back to context, so a
|
|
570
|
+
pill-driven Approve flows: pill → applier → editor command →
|
|
571
|
+
editor `onTransaction` → context `dismiss`.
|
|
572
|
+
|
|
573
|
+
The registry is generic — not AI-specific. Future field-mutation
|
|
574
|
+
extensions (form-recovery, undo stacks, bulk imports) can register
|
|
575
|
+
through the same seam.
|
|
576
|
+
|
|
577
|
+
Default no-op context still ships, so trees without a real provider
|
|
578
|
+
mounted (e.g. headless tests, marketing-site previews) see no behavior
|
|
579
|
+
change.
|
|
580
|
+
|
|
581
|
+
- 56a6f62: feat(core+tiptap): PendingSuggestionsContext seam + RichTextField AI bridge
|
|
582
|
+
|
|
583
|
+
Adds a cross-package, plugin-fillable queue of suggested field-value
|
|
584
|
+
changes that any field renderer can subscribe to. Open-core seam — core
|
|
585
|
+
defines the shape + provider, plugins like `@pilotiq-pro/ai` ship the
|
|
586
|
+
real implementation.
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
import { usePendingSuggestionsForField } from "@pilotiq/pilotiq/react";
|
|
590
|
+
|
|
591
|
+
const { list, dismiss } = usePendingSuggestionsForField("body");
|
|
592
|
+
// ↑ filtered to suggestions targeting this field+formId
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**`@pilotiq/pilotiq` exports** (`@pilotiq/pilotiq/react`):
|
|
596
|
+
|
|
597
|
+
- `PendingSuggestion` — `{ id, fieldName, formId?, currentValue,
|
|
598
|
+
suggestedValue, source?, createdAt, meta? }`. The `meta` bag carries
|
|
599
|
+
field-type-specific extras (e.g. `editorRange: { from, to }` for
|
|
600
|
+
`richtext`).
|
|
601
|
+
- `PendingSuggestionsApi` — `{ list, push, dismiss, dismissAll }`. Core
|
|
602
|
+
ships a no-op default context so trees without a real provider never
|
|
603
|
+
throw.
|
|
604
|
+
- `PendingSuggestionsContext`, `usePendingSuggestions()`,
|
|
605
|
+
`usePendingSuggestionsForField(name, formId?)` — the subscription
|
|
606
|
+
surface.
|
|
607
|
+
- `registerPendingSuggestionOverlay(C)` — mirrors
|
|
608
|
+
`registerFieldLabelSlot()`. A plugin registers a single component
|
|
609
|
+
(`{ suggestion, onApprove, onReject }` props) that `<FieldShell>`
|
|
610
|
+
mounts below the input whenever a matching pending suggestion exists.
|
|
611
|
+
Skipped on `richtext` fields (those render the diff inline via the
|
|
612
|
+
Tiptap extension).
|
|
613
|
+
|
|
614
|
+
**`@pilotiq/tiptap` `RichTextField` bridge**:
|
|
615
|
+
|
|
616
|
+
The Tiptap renderer now subscribes to the queue and mirrors entries
|
|
617
|
+
into its `AiSuggestionExtension`. Producers push a `PendingSuggestion`
|
|
618
|
+
with `meta.editorRange = { from, to }` and a string `suggestedValue`;
|
|
619
|
+
the bridge calls `editor.commands.addAiSuggestion(...)` so the inline
|
|
620
|
+
diff + Approve / Reject chips appear. When the user clicks a chip,
|
|
621
|
+
the editor command runs (mutating the doc on Approve, leaving it on
|
|
622
|
+
Reject) and the bridge mirrors the removal back to the queue via
|
|
623
|
+
`dismiss(id)` so other surfaces (chat-sidebar pill, FieldShell
|
|
624
|
+
overlay registered by another plugin) clear in lock-step.
|
|
625
|
+
|
|
626
|
+
The bridge is no-op when no provider is mounted — pilotiq core ships
|
|
627
|
+
the default no-op context, so consumers without `@pilotiq-pro/ai` see
|
|
628
|
+
no behavior change.
|
|
629
|
+
|
|
630
|
+
Pure helpers + types are public; the bridge hook
|
|
631
|
+
`useAiSuggestionBridge` is exported from `@pilotiq/tiptap` for advanced
|
|
632
|
+
producers that want to drive their own editor instances.
|
|
633
|
+
|
|
634
|
+
- 4f8e03b: feat(tiptap): AiSuggestion extension — inline diff + Approve/Reject chips
|
|
635
|
+
|
|
636
|
+
Always-on Tiptap extension that tracks AI-suggested edits as inline
|
|
637
|
+
strikethrough decorations on the original range plus a chip widget at
|
|
638
|
+
the range end carrying a preview of the replacement and per-hunk
|
|
639
|
+
Approve / Reject buttons. Idle until the host calls
|
|
640
|
+
`editor.commands.addAiSuggestion(...)`.
|
|
641
|
+
|
|
642
|
+
```ts
|
|
643
|
+
editor.commands.addAiSuggestion({
|
|
644
|
+
id: "seo-1",
|
|
645
|
+
from: 12,
|
|
646
|
+
to: 18,
|
|
647
|
+
replacement: "better",
|
|
648
|
+
source: { agentLabel: "SEO" },
|
|
649
|
+
});
|
|
650
|
+
// User clicks ✓ on the chip, or:
|
|
651
|
+
editor.commands.approveAiSuggestion("seo-1");
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Command surface: `addAiSuggestion`, `addAiSuggestions`,
|
|
655
|
+
`approveAiSuggestion(id)`, `rejectAiSuggestion(id)`,
|
|
656
|
+
`approveAllAiSuggestions()`, `rejectAllAiSuggestions()`,
|
|
657
|
+
`clearAiSuggestions()`. `approveAll` runs in highest-`from`-first order
|
|
658
|
+
so earlier-in-doc replacements don't shift the positions of later
|
|
659
|
+
suggestions.
|
|
660
|
+
|
|
661
|
+
Suggestion ranges remap through every doc transaction; ranges that
|
|
662
|
+
collapse past each other under user edits drop automatically. Plain-text
|
|
663
|
+
replacement only in v1 (marks/structure are not carried).
|
|
664
|
+
|
|
665
|
+
The package stays CSS-free — consumers wire styles against the
|
|
666
|
+
documented class names: `pilotiq-ai-suggestion-original` (strikethrough
|
|
667
|
+
on the original range), `pilotiq-ai-suggestion-chip` (widget root),
|
|
668
|
+
`pilotiq-ai-suggestion-replacement` (suggested-text preview),
|
|
669
|
+
`pilotiq-ai-suggestion-accept` / `pilotiq-ai-suggestion-reject`
|
|
670
|
+
(buttons). Class prefix is configurable via the extension's
|
|
671
|
+
`classPrefix` option.
|
|
672
|
+
|
|
673
|
+
`onChange(suggestions)` callback fires whenever the suggestion list
|
|
674
|
+
changes (after any add / approve / reject / clear, plus when a doc edit
|
|
675
|
+
collapses a range). Lets consumers mirror state into a React context
|
|
676
|
+
without polling editor state.
|
|
677
|
+
|
|
678
|
+
### Patch Changes
|
|
679
|
+
|
|
680
|
+
- Updated dependencies [b6dffde]
|
|
681
|
+
- Updated dependencies [8845b90]
|
|
682
|
+
- Updated dependencies [2c441b7]
|
|
683
|
+
- Updated dependencies [ae1450e]
|
|
684
|
+
- Updated dependencies [e1a79f6]
|
|
685
|
+
- Updated dependencies [df85886]
|
|
686
|
+
- Updated dependencies [56a6f62]
|
|
687
|
+
- Updated dependencies [e791f65]
|
|
688
|
+
- Updated dependencies [cce4f52]
|
|
689
|
+
- Updated dependencies [bd8229e]
|
|
690
|
+
- Updated dependencies [2f42dcd]
|
|
691
|
+
- Updated dependencies [425cf50]
|
|
692
|
+
- Updated dependencies [d7dbc80]
|
|
693
|
+
- Updated dependencies [8d92594]
|
|
694
|
+
- @pilotiq/pilotiq@0.7.0
|
|
695
|
+
|
|
696
|
+
## 3.0.0
|
|
697
|
+
|
|
698
|
+
### Patch Changes
|
|
699
|
+
|
|
700
|
+
- Updated dependencies [3b9d69c]
|
|
701
|
+
- Updated dependencies [e7f46a3]
|
|
702
|
+
- Updated dependencies [546b7bb]
|
|
703
|
+
- Updated dependencies [badb132]
|
|
704
|
+
- Updated dependencies [4440ec4]
|
|
705
|
+
- @pilotiq/pilotiq@0.6.0
|
|
706
|
+
|
|
707
|
+
## 2.0.1
|
|
708
|
+
|
|
709
|
+
### Patch Changes
|
|
710
|
+
|
|
711
|
+
- 863505c: Use caret peer dep for `@pilotiq/pilotiq` so adapter packages stay compatible across minor bumps.
|
|
712
|
+
|
|
713
|
+
## 2.0.0
|
|
714
|
+
|
|
715
|
+
### Patch Changes
|
|
716
|
+
|
|
717
|
+
- Updated dependencies [a1c3e40]
|
|
718
|
+
- @pilotiq/pilotiq@0.4.0
|
|
719
|
+
|
|
720
|
+
## 1.0.0
|
|
721
|
+
|
|
722
|
+
### Patch Changes
|
|
723
|
+
|
|
724
|
+
- Updated dependencies [58232be]
|
|
725
|
+
- Updated dependencies [58232be]
|
|
726
|
+
- Updated dependencies [43428d6]
|
|
727
|
+
- @pilotiq/pilotiq@0.3.0
|
|
728
|
+
|
|
729
|
+
## 0.2.0
|
|
730
|
+
|
|
731
|
+
### Patch Changes
|
|
732
|
+
|
|
733
|
+
- Updated dependencies [2dedc56]
|
|
734
|
+
- @pilotiq/pilotiq@0.2.0
|
|
735
|
+
|
|
736
|
+
## 0.1.0
|
|
737
|
+
|
|
738
|
+
### Patch Changes
|
|
739
|
+
|
|
740
|
+
- Updated dependencies [8cea72c]
|
|
741
|
+
- Updated dependencies [786da6b]
|
|
742
|
+
- Updated dependencies [2f4c948]
|
|
743
|
+
- Updated dependencies [4bdae5d]
|
|
744
|
+
- Updated dependencies [e5cd3f1]
|
|
745
|
+
- @pilotiq/pilotiq@0.1.0
|