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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c679e136a706fb345dedc22c7b3e9da97919a16c827dc67fe0a5e06ab0ed414
4
- data.tar.gz: df08be53339b8a833474f3ecd8234838e1aad5dd178fd1ae419d44adaf29637c
3
+ metadata.gz: dbd0d24e57ba7484ef5a9853585483169aa6a9ca04df5edba0c7b5145daa20ae
4
+ data.tar.gz: 4e87999fa4474abc9c42b6281d92f52cdead13b9fb5e367a594c392b3eb52f93
5
5
  SHA512:
6
- metadata.gz: '0903a87fcf089ec17d8aafbe0feb39fdd05b1068ae85540167434c818b2542a8b4f252115fd79f3f3d01a1ac63ee1a0a54eabd28172600538154f195e330cb04'
7
- data.tar.gz: 4f349abd4f2f62f662d8c988489a9a5b9b79892e5f78ce5790a4e0638217e4262b3c69d39d390c2f4d34bcf1b5a9ee981520ad178cd7b81c820ff08e7771fdc6
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Y
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
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.3.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-01 00:00:00.000000000 Z
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:
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Y
4
- module Decoder
5
- VERSION = "0.1.0.alpha1"
6
- end
7
- end
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
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Entry point matching the gem name, so `Bundler.require` loads it automatically.
4
- require "y/decoder"