yrby 0.3.0-x86_64-linux → 0.4.0-x86_64-linux
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +25 -0
- data/lib/y/3.4/yrby.so +0 -0
- data/lib/y/4.0/yrby.so +0 -0
- data/lib/y/version.rb +1 -1
- metadata +2 -5
- data/lib/y/decoder/version.rb +0 -7
- data/lib/y/decoder.rb +0 -66
- data/lib/yrby-decoder.rb +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dbd0d24e57ba7484ef5a9853585483169aa6a9ca04df5edba0c7b5145daa20ae
|
|
4
|
+
data.tar.gz: 4e87999fa4474abc9c42b6281d92f52cdead13b9fb5e367a594c392b3eb52f93
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a538834a7854a3eed5bfa1e8d06b0ff85b03862b480e921f79caf43e8cc16b5437c9c5588b128170da8269c5f871409246b1fed8f3457420c2643e73b6a27422
|
|
7
|
+
data.tar.gz: 46f53542bfc7b2305ebdf20326c4546b875185494a9c13686b99d8858ac84b19ba27eea5e8eaf4a28680238d701e6f4c2737f66266129ad7dce9a630056c9d63
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,62 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-07-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`Y::ProseMirror` — render ProseMirror/Tiptap documents to HTML.**
|
|
14
|
+
`Y::ProseMirror.new(doc).to_html` turns a Tiptap document into HTML on the
|
|
15
|
+
server, with no Node process or headless editor. The output matches Tiptap's
|
|
16
|
+
own `getHTML()`; the tests check it byte-for-byte against a document captured
|
|
17
|
+
from a real editor. It follows `ueberdosis/tiptap-php` and reads both name
|
|
18
|
+
styles editors use — Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
|
|
19
|
+
`bullet_list`/`strong`. Covers paragraphs, headings, blockquotes,
|
|
20
|
+
bullet/ordered/task lists, code blocks, links, images, mentions, details,
|
|
21
|
+
hard breaks, horizontal rules, tables, text styles (color, font family), and
|
|
22
|
+
every text mark. A table renders as semantic `<table><tbody>` without the
|
|
23
|
+
editor's column-width styling. A root that isn't ProseMirror (a Lexical
|
|
24
|
+
document, say) returns `nil`.
|
|
25
|
+
|
|
26
|
+
## [0.3.1] - 2026-07-01
|
|
27
|
+
|
|
28
|
+
Fixes from a full source review.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **`Doc#update_ready?` is now exact.** It previously checked only the
|
|
33
|
+
per-client clock lower bound, but yrs's real integration gate also requires
|
|
34
|
+
every block referenced by an item's origin / right-origin / parent — which
|
|
35
|
+
routinely belong to *other* clients — and post-Skip blocks in a merged update
|
|
36
|
+
sit above the lower bound. An update could pass the clock check yet park as
|
|
37
|
+
pending; downstream, `update_advances?` then misread the parked update as an
|
|
38
|
+
already-applied retry (pending doesn't move a state vector) and the sync
|
|
39
|
+
channel **acked and dropped real content**. `update_ready?` now
|
|
40
|
+
trial-integrates on a throwaway probe seeded with the doc's integrated state
|
|
41
|
+
(the clock check remains as a cheap pre-filter), so a cross-client-origin gap
|
|
42
|
+
is correctly rejected for a resync. `update_advances?` also gained defense in
|
|
43
|
+
depth: an update that would park reports as advancing, never as a duplicate.
|
|
44
|
+
- **`Doc#read_text` could deadlock the process.** It opened a second read
|
|
45
|
+
transaction while still holding the first (a chained temporary); yrs's lock is
|
|
46
|
+
write-preferring, so a concurrent writer between the two acquisitions
|
|
47
|
+
deadlocked reader-vs-writer inside the GVL-released (uninterruptible) region.
|
|
48
|
+
Now uses a single transaction.
|
|
49
|
+
- **TOCTOU in gap-free encoding.** The pending check and the encode ran in
|
|
50
|
+
separate transactions, so a concurrent gappy `apply_update` between them could
|
|
51
|
+
make `handle_sync_message`/`compacted_state_update` serve pending structs
|
|
52
|
+
anyway. Both now happen under one transaction.
|
|
53
|
+
- `read_xml`: Lexical soft line breaks and tabs now come through as `\n`/`\t`
|
|
54
|
+
instead of vanishing (`"foo⏎bar"` no longer extracts as `"foobar"`).
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- `update_advances?` skips its full-document probe when the update carries
|
|
59
|
+
blocks beyond the doc's state vector (a novel update trivially advances) —
|
|
60
|
+
the common case no longer pays O(doc) per frame.
|
|
61
|
+
- The gem no longer packages the `yrby-decoder` gem's files (they ship in that
|
|
62
|
+
gem; the duplicate copy could shadow a newer standalone release), and now
|
|
63
|
+
ships `Cargo.lock` so source builds compile the exact crate graph CI tested.
|
|
64
|
+
|
|
9
65
|
## [0.3.0] - 2026-07-01
|
|
10
66
|
|
|
11
67
|
### Fixed
|
data/README.md
CHANGED
|
@@ -175,6 +175,31 @@ guarantees keep serving safe:
|
|
|
175
175
|
keeps its pending), while `encode_state_as_update` stays lossless so you can
|
|
176
176
|
still preserve the raw pending bytes for recovery.
|
|
177
177
|
|
|
178
|
+
### Rendering to HTML
|
|
179
|
+
|
|
180
|
+
`Y::ProseMirror` renders a ProseMirror or Tiptap document to HTML on the
|
|
181
|
+
server, with no Node process or headless editor:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
prosemirror = Y::ProseMirror.new(doc)
|
|
185
|
+
prosemirror.to_html # the "default" fragment (Tiptap's default root)
|
|
186
|
+
prosemirror.to_html("content") # or another XML root
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The output matches Tiptap's own `getHTML()`, checked byte-for-byte in the tests
|
|
190
|
+
against a document captured from a real editor. So you can render, search, or
|
|
191
|
+
email a collaborative document without running a browser. It follows
|
|
192
|
+
[`tiptap-php`](https://github.com/ueberdosis/tiptap-php) and reads both name
|
|
193
|
+
styles editors use — Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
|
|
194
|
+
`bullet_list`/`strong`.
|
|
195
|
+
|
|
196
|
+
It covers paragraphs, headings, blockquotes, bullet/ordered/task lists, code
|
|
197
|
+
blocks, links, images, mentions, details, hard breaks, horizontal rules,
|
|
198
|
+
tables, text styles (color, font family), and every text mark. A table renders
|
|
199
|
+
as semantic `<table><tbody>`, without the column-width styling Tiptap's editor
|
|
200
|
+
view adds. A root that isn't ProseMirror — a Lexical document, say — returns
|
|
201
|
+
`nil`.
|
|
202
|
+
|
|
178
203
|
### Protocol codec (module functions)
|
|
179
204
|
|
|
180
205
|
Classifying and unwrapping wire frames is stateless, so it's exposed as
|
data/lib/y/3.4/yrby.so
CHANGED
|
Binary file
|
data/lib/y/4.0/yrby.so
CHANGED
|
Binary file
|
data/lib/y/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yrby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- JP Camara
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -68,10 +68,7 @@ files:
|
|
|
68
68
|
- lib/y.rb
|
|
69
69
|
- lib/y/3.4/yrby.so
|
|
70
70
|
- lib/y/4.0/yrby.so
|
|
71
|
-
- lib/y/decoder.rb
|
|
72
|
-
- lib/y/decoder/version.rb
|
|
73
71
|
- lib/y/version.rb
|
|
74
|
-
- lib/yrby-decoder.rb
|
|
75
72
|
- lib/yrby.rb
|
|
76
73
|
homepage: https://github.com/jpcamara/yrby
|
|
77
74
|
licenses:
|
data/lib/y/decoder/version.rb
DELETED
data/lib/y/decoder.rb
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "y"
|
|
4
|
-
require "y/decoder/version"
|
|
5
|
-
|
|
6
|
-
module Y
|
|
7
|
-
# Plain-text reconstruction of a stored Yjs document, in pure Ruby — for search
|
|
8
|
-
# indexing and previews. The core `yrby` gem moves and stores opaque CRDT
|
|
9
|
-
# updates without reading them; this reads the text out of the shared type the
|
|
10
|
-
# editor uses (Lexical's `Y.XmlText`, plain `Y.Text`, or ProseMirror's
|
|
11
|
-
# `Y.XmlFragment`), in-process, on the native extension core already ships — no
|
|
12
|
-
# Node, no subprocess, no binary.
|
|
13
|
-
#
|
|
14
|
-
# state = doc.encode_state_as_update # opaque CRDT bytes from the store
|
|
15
|
-
# Y::Decoder.text(state) # => "hello world"
|
|
16
|
-
# Y::Decoder.preview(state, 280) # => "hello world…"
|
|
17
|
-
#
|
|
18
|
-
# Full-fidelity reconstruction (the exact Lexical EditorState / HTML, which
|
|
19
|
-
# needs @lexical/yjs) is a separate, opt-in concern — see the `yrby-decode`
|
|
20
|
-
# package's Bun binary. This gem stays pure Ruby on purpose.
|
|
21
|
-
module Decoder
|
|
22
|
-
class Error < Y::Error; end
|
|
23
|
-
|
|
24
|
-
module_function
|
|
25
|
-
|
|
26
|
-
# Plain text of the document. `field` pins the root key (Lexical: the editor
|
|
27
|
-
# id; ProseMirror: "default"); omit it to use the document's sole root.
|
|
28
|
-
def text(state, field: nil)
|
|
29
|
-
field ||= Y::Doc.new.tap { |d| d.apply_update(state) }.root_names.first
|
|
30
|
-
return "" unless field
|
|
31
|
-
|
|
32
|
-
# A plain `Y.Text` root (a simple shared-text editor) reads straight out.
|
|
33
|
-
# (A yrs root's type is fixed by its first typed access, so each reader
|
|
34
|
-
# gets a fresh doc to try a different shared type against the same state.)
|
|
35
|
-
direct = load(state).read_text(field)
|
|
36
|
-
return normalize(direct) if direct && !direct.strip.empty?
|
|
37
|
-
|
|
38
|
-
# Lexical (each block a sibling `Y.XmlText`) and ProseMirror (blocks are
|
|
39
|
-
# `Y.XmlElement`s) both come back from read_xml as block-per-line markup;
|
|
40
|
-
# strip any element tags to plain text.
|
|
41
|
-
markup = load(state).read_xml(field)
|
|
42
|
-
markup ? normalize(strip_tags(markup)) : ""
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# A compact, single-line preview for list UIs.
|
|
46
|
-
def preview(state, limit: 280, field: nil)
|
|
47
|
-
body = text(state, field: field).gsub(/\s+/, " ").strip
|
|
48
|
-
body.length > limit ? "#{body[0, limit].rstrip}…" : body
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def load(state)
|
|
52
|
-
Y::Doc.new.tap { |doc| doc.apply_update(state) }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def strip_tags(markup)
|
|
56
|
-
markup.gsub(/<[^>]*>/, " ")
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def normalize(text)
|
|
60
|
-
text.gsub(/[ \t]+/, " ") # collapse runs of spaces/tabs
|
|
61
|
-
.gsub(/ *\n */, "\n") # trim spaces left around block separators
|
|
62
|
-
.gsub(/\n{3,}/, "\n\n") # cap blank-line runs
|
|
63
|
-
.strip
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
data/lib/yrby-decoder.rb
DELETED