Repository navigation
Take y-tiptap 3.0.10 so a remote change stops rebuilding the paragraphs below it #520
Description
Part of #512. Read the tracker first. Work on main.
Summary
- Today.
@tiptap/y-tiptap3.0.9 clears its whole Yjs-to-ProseMirror node map on every remote change and every y-undo step. Every block then comes back as a fresh ProseMirror node object. - Effect. After a remote change that shifts blocks, prosemirror-view rebuilds the paragraphs below it. Each rebuilt paragraph rebuilds its link
<a>. After Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513, that is a new mark view, a new resolver call and a new icon element. - How far it runs. The rebuild runs down to the next block with different markup, such as a heading, a list or a media embed. On a pad of plain paragraphs, it runs to the end.
- Media node views are not rebuilt. The probe below found 0 media constructor calls and 0
srcchanges in every case. The Chrome check in §Verification must confirm this outside jsdom. - Upstream already fixed it.
@tiptap/y-tiptap3.0.10 (npm, 2026-10-13) keeps unchanged nodes and replaces only the changed blocks. Open PR Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 takes 3.0.10. - Goal. Land 3.0.10 on
main, measure it there, and fix the docs that still describe the full replace.
Maintainer rulings that bind this issue
#512 quotes every ruling with its number and date. Both below are from 2026-10-17.
- Ruling 17, file the side issues. This issue is the y-tiptap re-render cascade, adjusted to the mark-view plan.
- Ruling 9, mark view, not a decoration. So the cascade now rebuilds link mark views, not widget decorations.
Depends on / Blocks
- Depends on Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 ("Bump TipTap packages to 3.32.0 for the remote-cursor fix"). It moves the catalog to
@tiptap/y-tiptap^3.0.10and TipTap to 3.32.0. It is open and mergeable today. See Decision 1. - Overlap with Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513 Decision 2. Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 also moves the root
prosemirror-viewoverride from1.42.3to1.42.6, so Decision 1 here also answers Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513 Decision 2 ([Tracking] Inline link icons through a hyperlink mark view #512 §prosemirror-view floor). This issue does not block on it. - Does not need Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513. A plain link is also a mark view inside prosemirror-view (a
MarkViewDescbuilt fromtoDOM). So<a>element identity shows the cascade with or without icons. - Blocks nothing. Once 3.0.10 is on
main, Show a link icon before every pad link in the webapp #516 Step 9 gates the browser floor (1 build per inserted link block), not the jsdom floor that Step 2 posts.
Current behaviour
Installed file: node_modules/.bun/@tiptap+y-tiptap@3.0.9+b4bff67c2908c8af/node_modules/@tiptap/y-tiptap/dist/y-tiptap.js. The root catalog pins ^3.0.9 (package.json#L187). Only the webapp depends on it (apps/webapp/package.json#L67). The @tiptap+y-tiptap@3.0.2 folder in node_modules/.bun is install residue with no bun.lock entry. Ignore it.
-
_typeChangedclears the map (y-tiptap.js:780-784):transaction.changed.forEach(delType); transaction.changedParentTypes.forEach(delType); // Rebuild the full Y↔PM mapping so relative cursor positions resolve against // current node sizes after structural changes (e.g. drag-and-drop block moves). this.mapping.clear();
It then builds every top-level node again through
createNodeIfNotExists(defined at:854). It applies onereplaceover the whole document (:785-797), then callsrestoreRelativeSelection(:799, defined at:317). -
Why the clear exists. Upstream commit ueberdosis/y-tiptap@0f3f68a added it, and 3.0.6 shipped it (npm, 2026-07-03). It fixes the local caret position after a block move (drag and drop). In this issue, "caret position after a block move" means that case.
-
y-prosemirror 1.3.7 does not clear. It deletes only the changed and deleted types (
node_modules/.bun/y-prosemirror@1.3.7+b4bff67c2908c8af/node_modules/y-prosemirror/src/plugins/sync-plugin.js:603-620). -
How prosemirror-view turns fresh nodes into a cascade (prosemirror-view 1.42.3,
node_modules/.bun/prosemirror-view@1.42.3/node_modules/prosemirror-view/src/viewdesc.ts). Fresh node objects defeatpreMatch, so each paragraph after the change is overwritten and its<a>rebuilt. This runs until the next block with different markup. -
Who pays.
- Every link in the rebuilt paragraphs gets a new
<a>. After Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513, each one calls thelinkIconresolver and builds a new icon element. A cached image shows at once, and a known miss makes no request (Add linkIconMap and linkIconFetcher helpers with light and dark pairs and a tile #514). The resolver call and the DOM build still happen. - Any decoration plugin that maps through the remote transaction loses every decoration, because the step covers the whole document. TipTap/CLAUDE.md#L60 records this for HeadingScale (about 8.7 ms per remote transaction at 1501 headings).
- Every link in the rebuilt paragraphs gets a new
-
Upstream fix. ueberdosis/y-tiptap#54 ("Every remote edit rebuilds the whole ProseMirror document since 3.0.6") is closed. ueberdosis/tiptap#8475, merged 2026-10-13, fixed it and also closed y-tiptap#55. The y-tiptap source now lives in
ueberdosis/tiptap, folderpackages/y-tiptap. In the 3.0.10 npm tarball:_typeChangedno longer clears the map (package/dist/index.js:1563-1589). A clear remains only in the snapshot, forced re-render and projection-error paths (:1461,:1472,:1485,:1527).replaceChangedChildren(:1352-1374) compares old and new top-level children by identity from both ends. It replaces only the span between them. It falls back to the full replace when the result does not equal the new content.- Every name the webapp imports (
ySyncPluginKey,yUndoPluginKey) is still exported (:2209).@tiptap/extension-collaborationand-caret3.31.3 ask for^3.0.7, which 3.0.10 meets. The build layout moved fromdist/y-tiptap.jstodist/index.js.
Measured
Probe: the <details> block in Step 0, run outside the repo. Label: laptop, jsdom view, no layout (jsdom 26.1.0, bun 1.4.3, prosemirror-view 1.42.3, yjs 13.6.32). Client B makes the change, and client A is measured. Each cell is "mark views built / mark views destroyed" on client A. Every block has its own href.
| Build | Blocks, shape | Remote keystroke | Remote 1-block insert | B undoes that insert | Remote Enter | Remote 10-block paste |
|---|---|---|---|---|---|---|
| 3.0.9 | 62, flat | 0 / 0 | 63 / 61 | 0 / 2 | 122 / 122 | 81 / 61 |
| 3.0.9 | 500, flat | 0 / 0 | 501 / 499 | 0 / 2 | 998 / 998 | 519 / 499 |
| 3.0.9 | 2000, flat | 0 / 0 | 2001 / 1999 | 0 / 2 | 3998 / 3998 | 2019 / 1999 |
| 3.0.9 | 62, mixed | 0 / 0 | 6 / 4 | 0 / 2 | 8 / 8 | 28 / 8 |
| 3.0.9 | 62, 500, 2000, embed runs | 0 / 0 | 8 / 6 | 0 / 2 | 12 / 12 | 32 / 12 |
| 3.0.9 with line 784 removed | every row above | 0 / 0 | 2 / 0 | 0 / 2 | 2 / 2 | 20 / 0 |
| 3.0.10 | every row above | 0 / 0 | 2 / 0 | 0 / 2 | 2 / 2 | 20 / 0 |
- "flat" is paragraphs only. "mixed" puts a heading with its own
toc-idat every 10th block and an embed at every 10th block. "embed runs" is 7 paragraphs, then 3 embeds, repeated. - Cause proven by sabotage. Removing only line 784 from the 3.0.9 file gives the 3.0.10 numbers in every row. Restoring it brings the cascade back.
- The floor. Every build builds 2 mark views per inserted link block and destroys 0, in jsdom. That is the floor, not the cascade. Its cause was not traced. In real Chromium 151 and WebKit 26.5, the floor is 1 per inserted link block (laptop, headless, layout on, 2026-10-18). The browser floor is the one Show a link icon before every pad link in the webapp #516 Step 9 gates.
- Media. In every case and every build, media node views had 0 constructor calls, 0
updatecalls that changedsrcand 0 destroys. An embed inserted into a run of embeds built only its own view (2 constructor calls, same floor). - Not measured by this probe: time per transaction and prosemirror-view 1.42.6 (the version Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 ships). [Tracking] Inline link icons through a hyperlink mark view #512 §Tests and benchmarks has real-browser remote-edit times on y-tiptap 3.0.9 and 3.0.10, on prosemirror-view 1.42.3 (laptop, Chromium headless, layout on).
Target behaviour
- On
mainwith 3.0.10, a remote insert, Enter, paste or y-undo builds and destroys only the floor counts in the table above. That holds at 62, 500 and 2000 flat blocks. - In Chrome, every unchanged
<a>and every media iframe below a remote change keeps its DOM element. - The caret position after a block move still lands on the same text, on the client that moved the block.
Implementation steps
Step 0. Read first, then reproduce the probe
Read the files in §Read first. Then run the probe once on 3.0.9 at 62 flat blocks, outside the repo. That run proves that the probe sees the cascade. Do not run bun add or bun install in the repo. Use a scratch folder ($P below). The probe resolves every shared module from one real path, so instanceof Y.Item checks hold. Two yjs copies exist in node_modules/.bun (13.6.31 and 13.6.32); use 13.6.32, the one y-tiptap 3.0.9 links to.
Save the JS block as $P/probe.mjs.
Setup commands and probe
export REPO=$(git rev-parse --show-toplevel)
R=$REPO/node_modules/.bun
P=<your scratch folder>; mkdir -p "$P/node_modules/@tiptap" && cd "$P"
ln -sfn $R/yjs@13.6.32/node_modules/yjs node_modules/yjs
ln -sfn $R/lib0@0.2.117/node_modules/lib0 node_modules/lib0
Y9=$R/@tiptap+y-tiptap@3.0.9+b4bff67c2908c8af/node_modules
ln -sfn "$(cd $Y9/y-protocols && pwd -P)" node_modules/y-protocols
for p in prosemirror-view prosemirror-state prosemirror-model prosemirror-transform; do
ln -sfn "$(cd $R/@tiptap+pm@3.31.3/node_modules/$p && pwd -P)" node_modules/$p; done
bun probe.mjs 309 62 flat// probe.mjs. Real y-tiptap, two clients, client A measured.
// Label: laptop, jsdom view, no layout. Usage: bun probe.mjs <309|3010> <blocks> <flat|mixed|embedrun>
const { JSDOM } = await import(`${process.env.REPO}/node_modules/.bun/jsdom@26.1.0/node_modules/jsdom/lib/api.js`)
const dom = new JSDOM('<!doctype html><body><div id=a></div><div id=b></div></body>')
for (const k of ['window', 'document', 'Node', 'Element', 'HTMLElement', 'MutationObserver', 'getComputedStyle', 'DOMParser', 'Text']) globalThis[k] = k === 'window' ? dom.window : dom.window[k]
globalThis.requestAnimationFrame = (f) => setTimeout(f, 0)
const VER = process.argv[2] || '309', N = Number(process.argv[3] || 62), MODE = process.argv[4] || 'mixed'
const Y = await import('yjs')
const yt = VER === '3010' ? await import('@tiptap/y-tiptap')
: await import(`${process.env.REPO}/node_modules/.bun/@tiptap+y-tiptap@3.0.9+b4bff67c2908c8af/node_modules/@tiptap/y-tiptap/dist/y-tiptap.js`)
const { Schema, Fragment } = await import('prosemirror-model')
const { EditorView } = await import('prosemirror-view')
const { EditorState } = await import('prosemirror-state')
const schema = new Schema({
nodes: {
doc: { content: 'block+' },
paragraph: { group: 'block', content: 'inline*', toDOM: () => ['p', 0] },
heading: { group: 'block', content: 'inline*', attrs: { level: { default: 2 }, id: { default: null } }, toDOM: (n) => ['h' + n.attrs.level, 0] },
embed: { group: 'block', atom: true, attrs: { src: { default: '' } }, toDOM: (n) => ['div', { 'data-src': n.attrs.src }] },
text: { group: 'inline' },
},
marks: { hyperlink: { attrs: { href: {} }, inclusive: false, toDOM: (m) => ['a', { href: m.attrs.href }, 0] } },
})
const c = { mvNew: 0, mvDestroy: 0, nvNew: 0, nvUpdate: 0, nvUpdateSrc: 0, nvDestroy: 0 }
// One mark-view build = one resolver call in the #513 design (read from code, not measured).
const markViews = { hyperlink: (mark) => {
c.mvNew++
const dom = document.createElement('a'); dom.href = mark.attrs.href
const icon = document.createElement('img'); icon.contentEditable = 'false'
const contentDOM = document.createElement('span'); dom.append(icon, contentDOM)
return { dom, contentDOM, destroy() { c.mvDestroy++ }, ignoreMutation: (m) => m.type !== 'selection' && icon.contains(m.target) }
} }
// Like a media node view: update() accepts the same type, so a shift could change src in place.
const nodeViews = { embed: (node) => {
c.nvNew++
const d = document.createElement('div'); d.dataset.src = node.attrs.src; let cur = node
return { dom: d, update(n) { if (n.type !== cur.type) return false; c.nvUpdate++; if (n.attrs.src !== cur.attrs.src) { c.nvUpdateSrc++; d.dataset.src = n.attrs.src } cur = n; return true }, destroy() { c.nvDestroy++ } }
} }
const para = (i) => schema.nodes.paragraph.create(null, [schema.text(`Block ${i} text `), schema.text(`link ${i}`, [schema.marks.hyperlink.create({ href: `https://site${i}.example.com/p/${i}` })]), schema.text(' tail.')])
const blocks = []
for (let i = 0; i < N; i++) {
if (MODE === 'flat') blocks.push(para(i))
else if (MODE === 'embedrun') blocks.push(i % 10 >= 7 ? schema.nodes.embed.create({ src: `https://video.example.com/${i}` }) : para(i))
else if (i % 10 === 5) blocks.push(schema.nodes.embed.create({ src: `https://video.example.com/${i}` }))
else if (i % 10 === 0) blocks.push(schema.nodes.heading.create({ id: `h-${i}` }, [schema.text(`Heading ${i} `), schema.text(`hl ${i}`, [schema.marks.hyperlink.create({ href: `https://head${i}.example.com/` })])]))
else blocks.push(para(i))
}
const yA = new Y.Doc(), yB = new Y.Doc()
yA.on('update', (u, o) => { if (o !== 'net') Y.applyUpdate(yB, u, 'net') })
yB.on('update', (u, o) => { if (o !== 'net') Y.applyUpdate(yA, u, 'net') })
yt.prosemirrorToYXmlFragment(schema.nodes.doc.create(null, blocks), yA.getXmlFragment('prosemirror'))
const mk = (el, ydoc, undo) => new EditorView(document.getElementById(el), {
markViews, nodeViews,
state: EditorState.create({ schema, plugins: [yt.ySyncPlugin(ydoc.getXmlFragment('prosemirror')), ...(undo ? [yt.yUndoPlugin()] : [])] }),
})
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const A = mk('a', yA, false), B = mk('b', yB, true)
await sleep(30)
const out = { ver: VER, mode: MODE, blocks: A.state.doc.childCount, links: A.dom.querySelectorAll('a').length }
const run = async (name, fn) => {
for (const k in c) c[k] = 0
const before = new Set(A.dom.querySelectorAll('a, [data-src]'))
fn(); await sleep(10)
out[name] = { ...c, newEls: [...A.dom.querySelectorAll('a, [data-src]')].filter((e) => !before.has(e)).length }
}
const um = () => yt.yUndoPluginKey.getState(B.state).undoManager
await run('remoteKeystrokeMid', () => { const p = 1 + Math.floor(B.state.doc.content.size / 2); let q = p; B.state.doc.nodesBetween(p, p + 200, (n, pos) => { if (n.isTextblock && q === p) { q = pos + 2; return false } }); B.dispatch(B.state.tr.insertText('x', q)) })
um().stopCapturing()
const p1 = () => B.state.doc.child(0).nodeSize // start of block 1, a paragraph
await run('remoteInsert', () => B.dispatch(B.state.tr.insert(p1(), para(9999))))
um().stopCapturing()
await run('bUndoesItsInsert', () => yt.undo(B.state)) // yUndoPlugin tracks local origins only, so B undoes its own insert
um().stopCapturing()
await run('remoteEnter', () => B.dispatch(B.state.tr.split(p1() + 3)))
um().stopCapturing()
await run('remotePaste10', () => B.dispatch(B.state.tr.insert(p1(), Fragment.from(Array.from({ length: 10 }, (_, i) => para(8000 + i))))))
if (MODE === 'embedrun') { um().stopCapturing(); let at = 0; for (let i = 0; i < 7; i++) at += B.state.doc.child(i).nodeSize
await run('remoteEmbedIntoRun', () => B.dispatch(B.state.tr.insert(at, schema.nodes.embed.create({ src: 'https://video.example.com/new' })))) }
console.log(JSON.stringify({ ...out, sameDoc: A.state.doc.eq(B.state.doc) }))
A.destroy(); B.destroy(); process.exit(0)- The changes start at block 1, a paragraph. A change at block 0 of the "mixed" shape hits a heading first, and the cascade stops at once.
- Expect the 3.0.9, 62, flat row of the table above.
sameDocmust printtrue. If your numbers differ from that row, post them here and stop. - If that run shows no cascade, post the numbers as a comment here and stop.
Step 1. Wait for #473 on main
Stop until a Maintainer ruling comment exists for Decision 1. Do not check out the #473 branch.
- Recommended route. Wait until Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 is merged on
main. Edit nopackage.jsonorbun.lockhere. - Alternative A. Change
"@tiptap/y-tiptap": "^3.0.9"to"^3.0.10"in the root catalog (line 187). Runbun install. Thebun.lockdiff must touch only the@tiptap/y-tiptapentries. Prove it with the cold install in AGENTS.md line 202. - Alternative B. Stop here. The maintainer closes this issue and moves §Docs to update into Bump TipTap packages to 3.32.0 for the remote-cursor fix #473.
Step 2. Re-measure on main
- Confirm the versions:
ls node_modules/.bun | grep -E '^@tiptap\+y-tiptap@|^prosemirror-view@'. Expect3.0.10and theprosemirror-viewversion that Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 ships (1.42.6in its diff). - Re-run the probe against the installed 3.0.10 and that
prosemirror-view. Work in$P, withRset as in Step 0.rm -rf node_modules/@tiptap/y-tiptap && cp -R "$(cd $R/@tiptap+y-tiptap@3.0.10*/node_modules/@tiptap/y-tiptap && pwd -P)" node_modules/@tiptap/y-tiptap. The probe imports@tiptap/y-tiptapby its bare name, so it needs this folder.- Re-link the four
prosemirror-*folders from the@tiptap+pm@3.32.0folder. - Link the
yjsfolder that y-tiptap 3.0.10 links to. - Then run
bun probe.mjs 3010 <blocks> flatat 62, 500 and 2000 blocks.
- Post the numbers as a comment here, labelled "laptop, jsdom view, no layout". Expect the floor counts from the table. If the numbers on
maindiffer from the floor, post them here and stop.
Step 3. Browser checks
Run the checks in §Verification. Post the results as a comment.
Step 4. Update the docs
Edit the files in §Docs to update. Then run bun run check:agent-docs.
Review checkpoint
Show the touched files and the probe numbers, then stop. Do not run git add, git commit, git push or git stash. Do not create a branch.
Decisions
Owner: the maintainer. The recommended answer comes first.
- Mark draft PR Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 ready, then merge it before Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513 and this issue? Recommended: yes. This also answers Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513 Decision 2. If Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 closes unmerged, rule Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513 Decision 2 alone.
- After the merge, this issue runs Steps 2 to 4 on
main. - Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 already carries
@tiptap/y-tiptap^3.0.10, and its CI checks pass. A second bump here would duplicate it. - Alternative A: bump only
@tiptap/y-tiptapto^3.0.10in this issue (catalog line 187 andbun.lock). The collaboration packages accept it (^3.0.7). Then prove the lockfile with a cold install, as in AGENTS.md#L202. - Alternative B: close this issue as fixed by Bump TipTap packages to 3.32.0 for the remote-cursor fix #473, and move the doc edits into Bump TipTap packages to 3.32.0 for the remote-cursor fix #473.
- Needed by Step 1.
- After the merge, this issue runs Steps 2 to 4 on
Acceptance criteria
- A comment here shows the Step 0 numbers for the 3.0.9 run at 62 flat blocks.
- Decision 1 has a Maintainer ruling comment, and
@tiptap/y-tiptap3.0.10 is onmain. - A comment shows the Step 2 numbers on
main. They equal the floor counts at 62, 500 and 2000 flat blocks. - In Chrome, a remote Enter in the second paragraph of a 500-link pad leaves every
<a>below the changed block connected, and no media iframe reloads. - After a block move on one client, that client's caret stays on the same text.
- A remote y-undo and y-redo leave both clients with the same document, and the moving client's caret stays on the same text.
-
collab-heading-sync.cy.jspasses, and a full-document paste (⌘Athen⌘V) keeps the document. - The files in §Docs to update are edited, and
bun run check:agent-docspasses.
Verification
- The Step 0 probe on 3.0.9 at 62 flat blocks, and the Step 2 probe on
main. Run withbun, outside the repo. - Two-client Chrome check. Run
make dev-local. Open one pad in two Chrome profiles, both signed in.- Fill the pad: write a scratch HTML file outside the repo with 500 lines like
<p>Block 7 <a href="https://site7.example.com/">link 7</a> tail.</p>. Open it in Chrome, select all, copy, and paste into the pad. - Add three media embeds below the first 20 blocks with the media toolbar. Any provider works, for example a YouTube URL.
- On client A, in DevTools:
window.__a = [...document.querySelectorAll('.ProseMirror a')]; window.__f = [...document.querySelectorAll('.ProseMirror iframe')]. - On client B, press Enter inside the second paragraph.
- On client A:
__a.filter((x) => !x.isConnected).lengthmust be at most 1, the link in the split block.__f.every((x) => x.isConnected)must betrue. - Repeat with a 10-block paste on client B. Expect no disconnected
<a>outside the pasted span.
- Fill the pad: write a scratch HTML file outside the repo with 500 lines like
- Caret position after a block move. On client A, drag a heading in the TOC to move its section to a new place. Then type one character. It must land at the caret, on the same text as before the move.
- y-undo and y-redo. On client B, insert a block, then press
⌘Zand⌘⇧Z. Both clients must show the same document. - Collab Cypress spec: run
cypress/e2e/editor/collaboration/collab-heading-sync.cy.jsin the Cypress form from [Tracking] Inline link icons through a hyperlink mark view #512 §Local stack. Follow its port note. - Full-document paste (AGENTS.md#L112): in a pad, press
⌘A,⌘C, then⌘V. The document must stay the same on both clients. bun run --filter @docs.plus/webapp typecheck.bun run check:agent-docs.
Docs to update
apps/webapp/src/components/TipTap/CLAUDE.mdline 60: "A Yjs change ... replaces the whole document". With 3.0.10, a remote change replaces only the changed blocks, and the full replace is a fallback. Re-measure HeadingScale per remote transaction at 1501 headings, or mark the 8.7 ms figure as measured on 3.0.9.apps/webapp/src/components/TipTap/CLAUDE.md§Editor Performance: Show a link icon before every pad link in the webapp #516 writes the clause "a remote block insert rebuilds the link icons below it", with a link to this issue. It writes that clause only while@tiptap/y-tiptaponmainis older than 3.0.10. If that clause is onmainwhen this issue runs, remove it and keep the rest of the line.caret-find-plugin.test.tsline 40: the comment says "y-tiptap replaces the whole document on a remote change". Change it to say the full replace is y-tiptap's fallback path. Keep the test; it still covers that path.- No
AGENTS.mdedit. No local patch lands, so no §Dependencies note is needed.
Out of scope
- Link icon behaviour itself. Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513 owns it.
- The metadata write that reaches every peer. Stop link preview metadata writes from reaching every peer #519 owns it.
- The
prosemirror-view1.42.6 bump. Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 and Add a linkIcon option and a hyperlink mark view that draws an icon before each link #513 Decision 2 own it. hooks/guardProviderAwareness.ts. Its y-tiptap half answers y-tiptap#55, which 3.0.10 closes. Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 keeps the whole guard on purpose, because@hocuspocus/provider3.4.4 still echoes awareness. Removing the y-tiptap half is a separate change (apps/webapp/CLAUDE.md#L198).- The local selection jump to the document end after the first remote sync. y-tiptap#54 reports it as a side note.
Rejected options
- A local
patchedDependenciespatch of 3.0.9. Upstream already ships the fix. A patch would also needpatches/copied in apps/webapp/docker/Dockerfile.bun#L37 and apps/hocuspocus.server/docker/Dockerfile.bun#L18. The cold-installrsynclist in AGENTS.md line 202 would need it too. - A new upstream report. y-tiptap#54 already reported it, and Integrate y-tiptap and fix structural relative positions ueberdosis/tiptap#8475 fixed it.
Read first
AGENTS.md, mainly §Package Manager, §Dependencies, §Test Policy and §Simplified English Mandate.apps/webapp/CLAUDE.md, the collaboration section around line 198.apps/webapp/src/components/TipTap/CLAUDE.md§HeadingScale (lines 42-63) and §Editor Performance.
Activity
HMarzban commentedon Oct 18, 2026
Maintainer ruling, 2026-10-18
The maintainer answered these Decisions through a multiple-choice prompt in the agent session. Each answer below is the option the maintainer picked; the quoted label is the option text.
main, runs the browser checks and fixes the docs. If Bump TipTap packages to 3.32.0 for the remote-cursor fix #473 closes unmerged, take Alternative A.Recorded on the tracker #512.