Skip to content

ui.tiptap concurent multi editing with WYSIWYG editor#5791

Draft
phifuh wants to merge 3 commits intozauberzeug:mainfrom
phifuh:tiptap-element
Draft

ui.tiptap concurent multi editing with WYSIWYG editor#5791
phifuh wants to merge 3 commits intozauberzeug:mainfrom
phifuh:tiptap-element

Conversation

@phifuh
Copy link
Copy Markdown

@phifuh phifuh commented Feb 16, 2026

Motivation

With inspiration of #5775 and as discussed in #5774 there is a usecase for having a WYSIWYG editor with realtime collab. @falkoschindler gave the hint to maybe look at tiptap with py-y which this PR introduces as new element.

Implementation

y-py is optional and only needed for the state management, maybe it should be added as core tho?
A few extra extensions got added to extend the tiptap js starterkit most notable @tiptap/extension-underline
This can be extended in the future

Progress

  • I chose a meaningful title that completes the sentence: "If applied, this PR will..."
  • The implementation is complete.
  • If this PR addresses a security issue, it has been coordinated via the security advisory process.
  • Pytests have been added (or are not necessary).
  • Documentation has been added (or is not necessary).

Comments:

There is at least one minor outstanding issue which I am stuck on and thats the handling of threads in nicegui and garbage collection.
E.x when running uv run pytest test/test_tiptap.py i get a warning RuntimeError: y_py::y_doc::YDoc is unsendbale, but is dropped on another thread!
I even asked my popular friend claude with no luck, so I am hoping that @evnchn @falkoschindler that you can have a look at it , since you guys no a lot more about the internal wiring.

Another issue was trying to run it on a different machine, which resultet in some y-py dependency issue which I havent looked into further but that might just be a pyproject.toml setup issue

I am not sure if there are any other short comming, I tested it on a render.com server to be sure with a little demo and that worked execelent.


AI summary:

This pull request introduces a collaborative rich-text editor element (Tiptap) to NiceGUI, powered by Tiptap and Yjs for real-time editing and CRDT-based document merging. It adds both frontend and backend support for collaborative editing, state persistence, and awareness/cursor sharing. The changes also integrate the new element into NiceGUI's import system and documentation.

Collaborative editor integration:

  • Added the Tiptap element, a collaborative rich-text editor using Tiptap and Yjs, including state persistence and awareness features.
  • Implemented a server-side Yjs room manager (tiptap_room.py) to handle document state, client synchronization, and awareness messages.
  • Registered and cleaned up Yjs event handlers in the main NiceGUI server lifecycle, ensuring proper room management and client disconnection handling.

Frontend bundling and exports:

  • Added a Node.js package (package.json, rollup.config.mjs, src/index.mjs) to bundle Tiptap core, extensions, and Yjs-related modules for frontend usage.

NiceGUI integration and documentation:

  • Registered the Tiptap element in NiceGUI's UI import system and documentation sections.

This pull request introduces a collaborative rich-text editor element (`Tiptap`) to NiceGUI, powered by Tiptap and Yjs for real-time editing and CRDT-based document merging. It adds both frontend and backend support for collaborative editing, state persistence, and awareness/cursor sharing. The changes also integrate the new element into NiceGUI's import system and documentation.

**Collaborative editor integration:**

* Added the `Tiptap` element, a collaborative rich-text editor using Tiptap and Yjs, including state persistence and awareness features. (`nicegui/elements/tiptap/tiptap.py`, [nicegui/elements/tiptap/tiptap.pyR1-R91](diffhunk://#diff-ccc5568f1245d0c39a2ea013238618e2508c5ca9c744b2ecafd4bdc2d3a53296R1-R91))
* Implemented a server-side Yjs room manager (`tiptap_room.py`) to handle document state, client synchronization, and awareness messages. (`nicegui/tiptap_room.py`, [nicegui/tiptap_room.pyR1-R174](diffhunk://#diff-5097f3fe7705b5fc74c0ee1fb55edda2a1bac88336e7f3bf577a7a512a6b2ac4R1-R174))
* Registered and cleaned up Yjs event handlers in the main NiceGUI server lifecycle, ensuring proper room management and client disconnection handling. (`nicegui/nicegui.py`, [[1]](diffhunk://#diff-fbad2baebb45fa59d006de0b509cf19f3e5d69eb0360127b7407681dcc7be8c6R55-R57) [[2]](diffhunk://#diff-fbad2baebb45fa59d006de0b509cf19f3e5d69eb0360127b7407681dcc7be8c6R229-R230)

**Frontend bundling and exports:**

* Added a Node.js package (`package.json`, `rollup.config.mjs`, `src/index.mjs`) to bundle Tiptap core, extensions, and Yjs-related modules for frontend usage. (`nicegui/elements/tiptap/package.json`, [[1]](diffhunk://#diff-90ccc82794a2b7fb460bdee5091d62f14843806ed6891603b66ed9234df2e698R1-R18) [[2]](diffhunk://#diff-9879eb64daedc4fa08a54520e46c1d5a708951ddde94039c1452e754d2a961a6R1-R19)

**NiceGUI integration and documentation:**

* Registered the `Tiptap` element in NiceGUI's UI import system and documentation sections. (`nicegui/elements/tiptap/__init__.py`, [[1]](diffhunk://#diff-677449fa3e4bb8174b92c08e638a63d1c3c6c5eb5e03d462920f28f04432aaa3R132) [[2]](diffhunk://#diff-677449fa3e4bb8174b92c08e638a63d1c3c6c5eb5e03d462920f28f04432aaa3R253) [[3]](diffhunk://#diff-e15c4544fe5aec3e396bb671aaf01d3926ffad969b9815d41a4792ad79710d73R23) [[4]](diffhunk://#diff-e15c4544fe5aec3e396bb671aaf01d3926ffad969b9815d41a4792ad79710d73R56)
* Added `y-py` as an optional dependency for state persistence and included it in the development dependency group. (`pyproject.toml`, [[1]](diffhunk://#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711R52-R53) [[2]](diffhunk://#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711R66)
@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 16, 2026

For a first-time PR, this is very impressive! You don't have to aim for perfection right away (this is likely scheduled for 3.9 if not 3.10) and please do not take me as inspiration (it's 4AM where I live right now)

I'd assume AI helped you in the process. It's too late to add AI co-authorship though (prefer not to force-push when PR is open). If you have any friction points in the process, please be sure to voice out, as we are about to revamp the AGENTS.md.

@evnchn evnchn marked this pull request as draft February 16, 2026 19:54
@evnchn evnchn added feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it labels Feb 16, 2026
@phifuh
Copy link
Copy Markdown
Author

phifuh commented Feb 16, 2026

Thank you, my friend Claude did a lot of the heavy lifting. My first little “baby” script was written back in Python  2.5, and I’ve just been promoted to senior developer at work. It’s crazy to see how far agent‑style coding has come tho in the past year.

I mention this because I am very interested in hybrid intelligence. At my current job I see junior developers struggling to handle the direction of AI, mainly because they lack the necessary knowledge, company insights and experience and thats an issue because AI is in the driving seat without the needed supervision and direction in the steeringwheel.

I feel like this PR/draft is already far along—not perfect, but good enough as a draft. I wouldn’t have tackled this PR if it weren’t for Claude; it took me the whole weekend, and the amount of time I would have needed to do everything manually would have been insane.

@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 16, 2026

Just to be clear and set the record straight for other people and possibly AI Agents reading: Please try to ensure you are familiar with NiceGUI before opening a PR. IMO not that you must, but there will be a lot of back-and-forth otherwise (not sure for other maintainers, they reserve the right to close your PR)

@phifuh, with 11 issues and 6 discussions, you are well-past-the-mark on that one 💪

@evnchn
Copy link
Copy Markdown
Collaborator

evnchn commented Feb 17, 2026

Hello @phifuh. Happy Lunar New Year!

It's been a busy few days. I don't think I have the time to look at this in detail just yet, but I think I should point out 2 obvious things right away:

Mysterious error RuntimeError: y_py::y_doc::YDoc is unsendbale, but is dropped on another thread! at the end of passing pytest

I am positively sure that comes from y-py or its dependencies because NiceGUI uses asyncio which is single-threaded concurrency, while y-py calls Rust and it definitely has to be on another thread.

We run gc.collect() in the test fixture, and previously we have leaked stuff which complains when being GCed and the fixture's GC catches that. The fact that tests passes means that message has nothing to do with garbage collection. Ref:

gc.collect()
try:
yield
finally:
gc.collect()

Library selection and usage

Speaking of y-py, it is abandoned. Can we switch to maintained alternative such as https://github.com/y-crdt/pycrdt which it linked?

Nevertheless, that also invokes Rust, so if there's no prebuilt wheel then stuff gets complicated real quick. We should strive to not have y-crdt as a hard-requirement of NiceGUI as such. Ref:

from typing import Literal
_optional_features: set[str] = set()
FEATURE = Literal[
'altair',
'anywidget',
'highcharts',
'matplotlib',
'pandas',
'pillow',
'plotly',
'polars',
'pyecharts',
'redis',
'webview',
]
def register(feature: FEATURE) -> None:
"""Register an optional feature."""
_optional_features.add(feature)
def has(feature: FEATURE) -> bool:
"""Check if an optional feature is registered."""
return feature in _optional_features

@phifuh
Copy link
Copy Markdown
Author

phifuh commented Feb 17, 2026

I always thought you were from Norway or Sweden, but the other day when you mentioned its 4AM it was clear you are currently far in the east :D
happy New Year tho! May the Year of the Fire Horse be a lucky charm for the coming year.

I tracked the pytest that raises the warning and it seems to be related to screen.open('/') which makes sense because I would assume it opens a new thread inside pytest. I’ll try the garbage‑collector controller and see if that fixes it.

I don’t see any reason not to switch to y‑crdt.
From a user perspective, the most important thing is to make it clear that this adds an extra dependency and to document that accordingly.

@phifuh
Copy link
Copy Markdown
Author

phifuh commented Feb 17, 2026

@evnchn

Moving from y-py to py_crdt fixed the y_py::y_doc::YDoc is unsendbale, but is dropped on another thread! but it could be that the other libary just doesnt have this specific warning, so I am unsure how to debug/profile if the gc is doing its job.


Changlog:

  1. pyproject.toml:
    - Updateded dependencies

  2. nicegui/tiptap_room.py:
    - Renamed HAS_Y_PY → PYCRDT_AVAILABLE
    - Updated imports
    - Updated API calls:
    - Updated docstrings and comments

  3. tests/test_tiptap.py:
    - Updated imports to use from pycrdt import Doc, Map
    - Updated API calls in tests for transactions and map access
    - Renamed test functions to reflect pycrdt naming

  4. website/documentation/content/tiptap_documentation.py:
    - Updated doc about optional pycrdt installation and Toolbar()

  5. Added a new Toolbar class to be more explicit about creation of the toolbar and for future extion like preset, sticky, position, style or what ever. So calling ui.tiptap() will return a default toolbar, while toolbar=None is hiding it


I guess I could add another test about the toolbar but I am done for today

@falkoschindler falkoschindler added this to the 3.x milestone Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants