yrby 0.6.1 → 0.7.1

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: ba28bef094ec97908a5ce4b01a61aedbbbcd49585c88a47acfbcfe0dab02c5be
4
- data.tar.gz: dae39a894e92836681c0aa73dc54201aecf1d3fa68eaac38496e420579d760b3
3
+ metadata.gz: 80931cbbabc08011f5f8ac78dfcf92e648feaf39ef8338ba7abb77bd575bb231
4
+ data.tar.gz: 340e157cea8aeee6ab5013ca43571e74a8f01f514ee8172aad81e14c10cf55b6
5
5
  SHA512:
6
- metadata.gz: de4dedd7b4eb4e14e343b83b20007587cd129cbc0f42ff03f21118043ebced3853b9a2e2c51e77688ba1b8665e058422e24d1cc18d7acfd85fd6c99e435b43b9
7
- data.tar.gz: 93291be948f34edc50c98266e554bd41a2beb7f8e74b1aeab87b19dc41efed425603f0634cefd117f24077b7d4fecf967b3ff4e40614b2276f2ccb40e85b5dc6
6
+ metadata.gz: fe5ba9524856f360def8b53a942bc8142017cb87254344aaeab9b90d4006471d3f480db5c527e4563f031ebc25d9c504c3070d306308676e10f11127ac6695c7
7
+ data.tar.gz: ef2cf7df98eced8c39ad5059ff12d3dfbdd8fc6dbd22c949163cc074d1710add09b9c4adb9d4c4ca24fa957f1b5983578c9f38623a80b0a4172027da4505a3a9
data/CHANGELOG.md CHANGED
@@ -4,6 +4,53 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project aims
5
5
  to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.7.1] - 2026-08-19
8
+
9
+ ### Fixed
10
+
11
+ - `Y::ProseMirror` and `Y::Tiptap` keep marks shared by adjacent text
12
+ runs open across them, the way ProseMirror's own serializer does:
13
+ bold then bold-italic renders as `<strong>a<em>b</em></strong>`, not
14
+ as two sibling `<strong>` wraps. A mark whose attributes differ
15
+ between runs never merges. Found by the new cross-renderer
16
+ verification, which checks the renderers against a live Tiptap
17
+ editor, `@tiptap/html`'s static `generateHTML`, `tiptap-php`,
18
+ Lexical's `$generateHtmlFromNodes`, and Payload CMS's
19
+ `convertLexicalToHTML` on a shared corpus.
20
+
21
+ ## [0.7.0] - 2026-08-11
22
+
23
+ ### Added
24
+
25
+ - `Y::Decoder` ships in the core gem and loads with `require "y"`. It was
26
+ scaffolded as a separate `yrby-decoder` gem, but it is 66 lines of pure
27
+ Ruby over `Doc#read_text` / `read_xml`, requires the native core either
28
+ way, and the separate gem was never published, so the split left the
29
+ module in no gem at all. The `yrby-decoder` name is retired unused.
30
+
31
+ ### Changed
32
+
33
+ - `Doc#handle_sync_message` answers a SyncStep1 with the doc's full state,
34
+ pending included, matching Y.js's `encodeStateAsUpdate`. It previously
35
+ served integrated-only state. A peer parks a served pending struct the
36
+ same way the doc did and heals it when the missing dependency arrives
37
+ from its sender's ack-driven retransmit. `compacted_state_update` still
38
+ excludes pending, so compaction cannot freeze a gap into a snapshot.
39
+
40
+ ### Fixed
41
+
42
+ - **`Doc#update_advances?` no longer misreads a gappy merged update carrying
43
+ novel content as a no-op.** A crafted frame can hide an internal gap behind
44
+ a Skip block while its post-gap blocks still integrate (yrs plants a Skip
45
+ hole in the store). That moves neither the doc's public state vector nor
46
+ pending, so the probe comparison reported genuinely novel content as
47
+ "doesn't advance"; `update_ready?` accepted the frame and the doc applied
48
+ it, but it was never recorded to the durable log or broadcast. Any
49
+ insertion past the update's own (Skip-capped) `state_vector()` now
50
+ conservatively reports as advancing. Not reachable through standard Yjs
51
+ providers (a client's own updates and diffs are gap-free), so this closes
52
+ a hostile-input hole, not a real-world regression.
53
+
7
54
  ## [0.6.1] - 2026-08-04
8
55
 
9
56
  ### Fixed
@@ -21,16 +68,16 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
21
68
  ### Added
22
69
 
23
70
  - **Custom render rules for `Y::Lexical` and `Y::ProseMirror`.** Both
24
- renderers now take a block registering rules per node type
71
+ renderers now take a block registering rules per node type:
25
72
  `rules.node "callout", tag: "aside"` for markup-as-data, a Ruby block for
26
- logic plus `nodes:`/`marks:` keywords as the equivalent data form, to
73
+ logic, plus `nodes:`/`marks:` keywords as the equivalent data form, to
27
74
  render node types the pinned schemas don't know or to override how a
28
75
  built-in renders.
29
76
  Declarative rules (`tag`/`attrs`/`text`/`contains`, with templates mixing
30
77
  literals and attribute references) render natively at full speed. Callback
31
78
  rules run a Ruby block per node, receiving its type, attributes,
32
79
  already-rendered children, and `child_types` (its element/block children by
33
- type the structural facts behind gallery counts and nested-list classes).
80
+ type, the structural facts behind gallery counts and nested-list classes).
34
81
  The block runs after the document read has finished, never while the doc is
35
82
  locked, so it can safely read or write the same doc. Blocks are proven
36
83
  sufficient for whole schemas: the gem's own editor schemas (`Y::Lexxy`,
@@ -38,13 +85,13 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
38
85
  output byte-identical to a live editor's. With no callback rules the
39
86
  render path is unchanged, byte for byte. See "Custom nodes and marks" in
40
87
  the README.
41
- - **`Y::Lexical#node_types` / `Y::ProseMirror#node_types` schema
88
+ - **`Y::Lexical#node_types` / `Y::ProseMirror#node_types`: schema
42
89
  discovery.** Ask a real document which node types it holds and what they
43
90
  look like: counts, attribute names as stored, child types, whether text
44
91
  runs appear, and whether a builtin or one of your rules already handles
45
92
  each ("handled" nil marks what still needs a rule). Editors store names
46
93
  you'd never guess; this is how you find them.
47
- - `Y::RenderRules.escape_text` / `escape_attr` the exact escaping the
94
+ - `Y::RenderRules.escape_text` / `escape_attr`: the exact escaping the
48
95
  native renderers use, for blocks that build markup from stored values
49
96
  (ERB's `html_escape` also rewrites apostrophes, which breaks byte parity
50
97
  with editor output).
@@ -54,15 +101,15 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
54
101
  - **Lexical rendering is now two classes: `Y::Lexical` (core Lexical) and
55
102
  `Y::Lexxy` (core plus the Lexxy schema as render rules).** Stock Lexical
56
103
  has no canonical serializer, so the editor-specific class carries the
57
- editor's name `Y::Lexxy.new(doc).to_html` is the byte-parity call for
104
+ editor's name; `Y::Lexxy.new(doc).to_html` is the byte-parity call for
58
105
  Lexxy/Rails apps, and `Y::Lexical` is the base any other Lexical editor
59
106
  extends with its own rules. The
60
- native side renders core structure paragraphs, headings, quotes, code,
107
+ native side renders core structure (paragraphs, headings, quotes, code,
61
108
  lists, tables, links, the full text-format model. Lexxy's own node types
62
109
  (attachments, galleries, `early_escape_code`, `horizontal_divider`) and its
63
110
  decorations of core nodes (the table figure wrapper, header-cell styling,
64
111
  the nested-list-item class) are rules applied beneath the app's, on the
65
- same extension API the gem's Lexxy support is the API's first consumer.
112
+ same extension API; the gem's Lexxy support is the API's first consumer.
66
113
  Output is unchanged: the fixture tests still hold `to_html` byte-identical
67
114
  to a live editor's serialized value, now through the extension path. An
68
115
  unknown Lexical container also degrades better: its block children render
@@ -71,8 +118,8 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
71
118
  ProseMirror) and `Y::Tiptap` (core plus Tiptap's extension nodes as render
72
119
  rules).** `Y::Tiptap.new(doc).to_html` is the byte-parity call for Tiptap
73
120
  apps. The native side renders prosemirror-schema-basic plus the
74
- prosemirror-tables family; Tiptap's extension nodes task lists, mentions,
75
- the details family are `Y::Tiptap::NODES` rules. Marks stay native in the
121
+ prosemirror-tables family; Tiptap's extension nodes (task lists, mentions,
122
+ the details family) are `Y::Tiptap::NODES` rules. Marks stay native in the
76
123
  base class: mark rendering (nesting order, `textStyle` CSS, `code`
77
124
  exclusivity) runs through text-run machinery node rules don't reach, so
78
125
  `Y::ProseMirror` still renders Tiptap's full mark set and `rules.mark`
@@ -83,7 +130,7 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
83
130
 
84
131
  ### Added
85
132
 
86
- - **`Y::Lexical` render Lexical/Lexxy documents to HTML.**
133
+ - **`Y::Lexical`: render Lexical/Lexxy documents to HTML.**
87
134
  `Y::Lexical.new(doc).to_html` turns a Lexxy document into HTML on the server,
88
135
  with no Node process or headless editor. The output is identical to the HTML
89
136
  a `lexxy-editor` submits to Rails; the tests check it byte-for-byte against a
@@ -101,12 +148,12 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
101
148
 
102
149
  ### Added
103
150
 
104
- - **`Y::ProseMirror` render ProseMirror/Tiptap documents to HTML.**
151
+ - **`Y::ProseMirror`: render ProseMirror/Tiptap documents to HTML.**
105
152
  `Y::ProseMirror.new(doc).to_html` turns a Tiptap document into HTML on the
106
153
  server, with no Node process or headless editor. The output matches Tiptap's
107
154
  own `getHTML()`; the tests check it byte-for-byte against a document captured
108
155
  from a real editor. It follows `ueberdosis/tiptap-php` and reads both name
109
- styles editors use Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
156
+ styles editors use: Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
110
157
  `bullet_list`/`strong`. Covers paragraphs, headings, blockquotes,
111
158
  bullet/ordered/task lists, code blocks, links, images, mentions, details,
112
159
  hard breaks, horizontal rules, tables, text styles (color, font family), and
@@ -122,8 +169,8 @@ Fixes from a full source review.
122
169
 
123
170
  - **`Doc#update_ready?` is now exact.** It previously checked only the
124
171
  per-client clock lower bound, but yrs's real integration gate also requires
125
- every block referenced by an item's origin / right-origin / parent which
126
- routinely belong to *other* clients and post-Skip blocks in a merged update
172
+ every block referenced by an item's origin / right-origin / parent (which
173
+ routinely belong to *other* clients) and post-Skip blocks in a merged update
127
174
  sit above the lower bound. An update could pass the clock check yet park as
128
175
  pending; downstream, `update_advances?` then misread the parked update as an
129
176
  already-applied retry (pending doesn't move a state vector) and the sync
@@ -147,7 +194,7 @@ Fixes from a full source review.
147
194
  ### Changed
148
195
 
149
196
  - `update_advances?` skips its full-document probe when the update carries
150
- blocks beyond the doc's state vector (a novel update trivially advances)
197
+ blocks beyond the doc's state vector (a novel update trivially advances);
151
198
  the common case no longer pays O(doc) per frame.
152
199
  - The gem no longer packages the `yrby-decoder` gem's files (they ship in that
153
200
  gem; the duplicate copy could shadow a newer standalone release), and now
@@ -158,7 +205,7 @@ Fixes from a full source review.
158
205
  ### Fixed
159
206
 
160
207
  - **Sync no longer serves un-integrable pending structs.** When a doc holds a
161
- *pending* struct (a gappy update whose causally-prior update is missing e.g.
208
+ *pending* struct (a gappy update whose causally-prior update is missing, e.g.
162
209
  legacy data recorded before the `update_ready?` gate existed), its integrated
163
210
  state vector is empty but `encode_state_as_update` merges the pending bytes back
164
211
  in. Answering a peer's `SyncStep1` with that state handed the peer content it
@@ -166,18 +213,18 @@ Fixes from a full source review.
166
213
  non-empty-content mismatch drove endless resync traffic (observed as a browser
167
214
  re-sending frames several times a second). `handle_sync_message` now answers
168
215
  `SyncStep1` with **integrated-only** state, so a server never serves a struct it
169
- can't integrate itself. Neutralizes existing poisoned server state on deploy
216
+ can't integrate itself. Neutralizes existing poisoned server state on deploy;
170
217
  no migration needed. The server's own pending is untouched and still heals if
171
218
  the missing dependency later arrives (only then does the content become
172
219
  visible in sync). Live delta relay (`Update` frames) is unchanged.
173
220
 
174
221
  ### Added
175
222
 
176
- - `Doc#pending?` true if the doc holds un-integrable pending structs or a
223
+ - `Doc#pending?`: true if the doc holds un-integrable pending structs or a
177
224
  pending delete set (content waiting on a missing causally-prior update).
178
- - `Doc#compacted_state_update` like `encode_state_as_update` (full state) but
225
+ - `Doc#compacted_state_update`: like `encode_state_as_update` (full state) but
179
226
  **gap-free**: excludes pending structs/delete set. Use it when persisting or
180
- serving state other peers will apply. Non-destructive the doc keeps its
227
+ serving state other peers will apply. Non-destructive; the doc keeps its
181
228
  pending (so it can still heal), and `encode_state_as_update` stays lossless for
182
229
  raw-update recovery.
183
230
 
@@ -195,13 +242,13 @@ Fixes from a full source review.
195
242
  before vs. after a trial apply on an isolated probe: a genuinely new deletion
196
243
  changes it (`true`); an already-applied retry re-encodes identically (`false`).
197
244
  Insert/format-only updates keep the cheaper state-vector path, so only
198
- delete-bearing frames a minority pay for the exact comparison. The exactly-
245
+ delete-bearing frames (a minority) pay for the exact comparison. The exactly-
199
246
  once guarantee is unchanged in the safe direction: a real deletion is never
200
247
  dropped.
201
248
 
202
249
  This lets `yrby-actioncable` (and any caller gating `on_change` on
203
- `update_advances?`) settle a duplicate pure-delete frame as `:applied` acked,
204
- but not stored or relayed so apps no longer need an app-level
250
+ `update_advances?`) settle a duplicate pure-delete frame as `:applied` (acked,
251
+ but not stored or relayed), so apps no longer need an app-level
205
252
  encode-and-compare guard around their durable writes.
206
253
 
207
254
  ## [0.2.2] - 2026-06-30
@@ -212,7 +259,7 @@ Fixes from a full source review.
212
259
  embeds child blocks (list items, table cells, nested lists) as `Y.XmlText`
213
260
  embeds that `get_string` silently drops, so lists and tables previously came
214
261
  back empty. `read_xml` now walks the embeds: text runs build a line, inline
215
- children (links) join it, and nested block children flush and recurse so a
262
+ children (links) join it, and nested block children flush and recurse, so a
216
263
  document with headings, formatted text, links, bullet/numbered/check/nested
217
264
  lists, blockquotes, code blocks and tables extracts every piece of text.
218
265
  Lexical decorator elements (horizontal rule, image) are skipped instead of
@@ -223,17 +270,17 @@ Fixes from a full source review.
223
270
 
224
271
  ### Changed
225
272
  - **Internal:** renamed the native extension crate `y_ruby` → `yrby` (now loads
226
- from `lib/y/yrby.bundle`). No public API change `require "y"` and `Y::Doc`
273
+ from `lib/y/yrby.bundle`). No public API change: `require "y"` and `Y::Doc`
227
274
  are unchanged.
228
275
 
229
276
  ## [0.2.0] - 2026-06-28
230
277
 
231
278
  First release. The public Ruby interface is the top-level module **`Y`**
232
- (`Y::Doc`, `Y::Error`, `Y::VERSION`), loaded with `require "y"` mirroring the
279
+ (`Y::Doc`, `Y::Error`, `Y::VERSION`), loaded with `require "y"`, mirroring the
233
280
  `y-rb` gem's `Y::Doc` interface.
234
281
 
235
282
  ### Added
236
- - Native `Doc#read_text` and `Doc#read_map` readers reconstruct plain text and
283
+ - Native `Doc#read_text` and `Doc#read_map` readers: reconstruct plain text and
237
284
  a JSON map from the stored CRDT state in-process, server-side, with no Node or
238
285
  subprocess.
239
286
 
data/Cargo.lock CHANGED
@@ -227,6 +227,15 @@ version = "1.3.0"
227
227
  source = "registry+https://github.com/rust-lang/crates.io-index"
228
228
  checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
229
229
 
230
+ [[package]]
231
+ name = "lexical-yjs-html"
232
+ version = "0.1.1"
233
+ dependencies = [
234
+ "serde_json",
235
+ "yjs-html-core",
236
+ "yrs",
237
+ ]
238
+
230
239
  [[package]]
231
240
  name = "libc"
232
241
  version = "0.2.180"
@@ -337,6 +346,15 @@ dependencies = [
337
346
  "unicode-ident",
338
347
  ]
339
348
 
349
+ [[package]]
350
+ name = "prosemirror-yjs-html"
351
+ version = "0.1.2"
352
+ dependencies = [
353
+ "serde_json",
354
+ "yjs-html-core",
355
+ "yrs",
356
+ ]
357
+
340
358
  [[package]]
341
359
  name = "quote"
342
360
  version = "1.0.43"
@@ -608,18 +626,26 @@ version = "0.2.1"
608
626
  source = "registry+https://github.com/rust-lang/crates.io-index"
609
627
  checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
610
628
 
629
+ [[package]]
630
+ name = "yjs-html-core"
631
+ version = "0.1.1"
632
+ dependencies = [
633
+ "serde_json",
634
+ "yrs",
635
+ ]
636
+
611
637
  [[package]]
612
638
  name = "yrby"
613
639
  version = "0.1.0"
614
640
  dependencies = [
641
+ "lexical-yjs-html",
615
642
  "magnus",
643
+ "prosemirror-yjs-html",
616
644
  "rb-sys",
617
645
  "rb-sys-env 0.1.2",
618
646
  "serde_json",
647
+ "yjs-html-core",
619
648
  "yrs",
620
- "yrs-html-core",
621
- "yrs-lexical-html",
622
- "yrs-prosemirror-html",
623
649
  ]
624
650
 
625
651
  [[package]]
@@ -640,32 +666,6 @@ dependencies = [
640
666
  "thiserror",
641
667
  ]
642
668
 
643
- [[package]]
644
- name = "yrs-html-core"
645
- version = "0.1.0"
646
- dependencies = [
647
- "serde_json",
648
- "yrs",
649
- ]
650
-
651
- [[package]]
652
- name = "yrs-lexical-html"
653
- version = "0.1.0"
654
- dependencies = [
655
- "serde_json",
656
- "yrs",
657
- "yrs-html-core",
658
- ]
659
-
660
- [[package]]
661
- name = "yrs-prosemirror-html"
662
- version = "0.1.0"
663
- dependencies = [
664
- "serde_json",
665
- "yrs",
666
- "yrs-html-core",
667
- ]
668
-
669
669
  [[package]]
670
670
  name = "zmij"
671
671
  version = "1.0.14"