yrby 0.3.1 → 0.5.0

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: addc2f9c08a109299b6c3db7cbf977b9b39194b7d784424b55876e8357897b29
4
- data.tar.gz: 586d689d080396b5e3c9cf528f49dfbccd5dca831e6e64bbaf6de35216f74143
3
+ metadata.gz: 3c53c83185ba046ddbcb6b232953af9aa2f52f883120aff8bb2fed8867f247df
4
+ data.tar.gz: 6a09a5541cca754d6ff838feb82838ce881d8fea144fed692031f7c957c9850b
5
5
  SHA512:
6
- metadata.gz: 3412853b540745a3a1db1134104c7cda42330d952b9d6eb08d8c20512069eae93f762aa77204cb6ecc3961da0c117df6bb66ef4b2aad5b0b81b05beedf3253b7
7
- data.tar.gz: 6d65ed5a61f737cd904800747c40e74d7b57fb0f64f77b12128533a0cfbf4c86058e13d2b2a33d9d005a7bfd015c31683fb25054236dbb3f28639f867d9a3a69
6
+ metadata.gz: d44fb5bb2a723b6a0dcf7ac6fd3bc248bd58f1401fe2e62009a214148f4a7b1ef1586d0d25585b39d291667eff04d6c66e911c7cdb5dec4968a0f3192b70a85c
7
+ data.tar.gz: 74da78ebc395e44cbdc9f32dcc51a239b7ea312de24f5ac64ec47db41bc784b47a53425afc83b948d36c9bf1b11922a3f9f3aa6615dcbac72a77759abe046ccc
data/CHANGELOG.md CHANGED
@@ -6,6 +6,41 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2026-07-08
10
+
11
+ ### Added
12
+
13
+ - **`Y::Lexical` — render Lexical/Lexxy documents to HTML.**
14
+ `Y::Lexical.new(doc).to_html` turns a Lexxy document into HTML on the server,
15
+ with no Node process or headless editor. The output is identical to the HTML
16
+ a `lexxy-editor` submits to Rails; the tests check it byte-for-byte against a
17
+ document captured from a real editor. It covers the whole Lexxy 0.9.x node
18
+ set: headings, every text format, links, bullet/numbered/check/nested lists,
19
+ quotes, code blocks, horizontal rules, tables with header cells, image galleries, and
20
+ ActionText attachments. Unknown nodes fall back to a plain paragraph, and a
21
+ root that isn't Lexical (a ProseMirror document, say) returns `nil`. This is
22
+ what `tiptap-php` does for ProseMirror JSON, applied to the Yjs structure.
23
+ - `read_xml` now pulls text out of attachments too: a mention's text goes
24
+ inline, and an upload adds its caption, alt text, or filename. Before, both
25
+ were dropped.
26
+
27
+ ## [0.4.0] - 2026-07-07
28
+
29
+ ### Added
30
+
31
+ - **`Y::ProseMirror` — render ProseMirror/Tiptap documents to HTML.**
32
+ `Y::ProseMirror.new(doc).to_html` turns a Tiptap document into HTML on the
33
+ server, with no Node process or headless editor. The output matches Tiptap's
34
+ own `getHTML()`; the tests check it byte-for-byte against a document captured
35
+ from a real editor. It follows `ueberdosis/tiptap-php` and reads both name
36
+ styles editors use — Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
37
+ `bullet_list`/`strong`. Covers paragraphs, headings, blockquotes,
38
+ bullet/ordered/task lists, code blocks, links, images, mentions, details,
39
+ hard breaks, horizontal rules, tables, text styles (color, font family), and
40
+ every text mark. A table renders as semantic `<table><tbody>` without the
41
+ editor's column-width styling. A root that isn't ProseMirror (a Lexical
42
+ document, say) returns `nil`.
43
+
9
44
  ## [0.3.1] - 2026-07-01
10
45
 
11
46
  Fixes from a full source review.
data/README.md CHANGED
@@ -157,6 +157,17 @@ doc.handle_sync_message(data) # => [msg_type, sync_type, response]; answers
157
157
  # SyncStep2 (never serves pending structs)
158
158
  ```
159
159
 
160
+ ### Reading document contents
161
+
162
+ Reconstruct a document server-side — search, exports, emails, SSR — with no
163
+ Node process:
164
+
165
+ ```ruby
166
+ doc.read_text("prosemirror") # => plain text of a Y.Text root, or nil
167
+ doc.read_xml("root") # => text of an XML root, one block per line
168
+ doc.read_map("state") # => a Y.Map root as a JSON string; JSON.parse it
169
+ ```
170
+
160
171
  ### Pending structs and gap-free state
161
172
 
162
173
  If a doc applies an update whose causally-prior update is missing (a "gappy"
@@ -175,6 +186,56 @@ guarantees keep serving safe:
175
186
  keeps its pending), while `encode_state_as_update` stays lossless so you can
176
187
  still preserve the raw pending bytes for recovery.
177
188
 
189
+ ### Rendering to HTML
190
+
191
+ Two schema-pinned renderers turn a collaborative document into HTML on the
192
+ server, with no Node process or headless editor: `Y::ProseMirror` for
193
+ ProseMirror/Tiptap documents and `Y::Lexical` for
194
+ [Lexxy](https://github.com/basecamp/lexxy) (Lexical) documents. Each returns
195
+ `nil` for a root that belongs to the other schema.
196
+
197
+ #### `Y::ProseMirror`
198
+
199
+ ```ruby
200
+ prosemirror = Y::ProseMirror.new(doc)
201
+ prosemirror.to_html # the "default" fragment (Tiptap's default root)
202
+ prosemirror.to_html("content") # or another XML root
203
+ ```
204
+
205
+ The output matches Tiptap's own `getHTML()`, checked byte-for-byte in the tests
206
+ against a document captured from a real editor. It follows
207
+ [`tiptap-php`](https://github.com/ueberdosis/tiptap-php) and reads both name
208
+ styles editors use — Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
209
+ `bullet_list`/`strong`.
210
+
211
+ It covers paragraphs, headings, blockquotes, bullet/ordered/task lists, code
212
+ blocks, links, images, mentions, details, hard breaks, horizontal rules,
213
+ tables, text styles (color, font family), and every text mark. A table renders
214
+ as semantic `<table><tbody>`, without the column-width styling Tiptap's editor
215
+ view adds.
216
+
217
+ #### `Y::Lexical`
218
+
219
+ ```ruby
220
+ lexical = Y::Lexical.new(doc)
221
+ lexical.to_html # the "root" fragment (Lexical's default root name)
222
+ lexical.to_html("notepad") # or another XML root
223
+ ```
224
+
225
+ The HTML is identical to what a `lexxy-editor` submits to Rails (its `value`).
226
+ The tests check this byte-for-byte against a document captured from a real
227
+ editor.
228
+
229
+ It handles the whole Lexxy 0.9.x node set: paragraphs, headings, every text
230
+ format and their combinations, links, the four list types and nesting,
231
+ blockquotes, code blocks, tabs and soft breaks, horizontal rules, tables with
232
+ header cells, image galleries, and ActionText attachments (uploads and
233
+ mentions both emit `<action-text-attachment>` elements that ActionText can
234
+ re-render).
235
+
236
+ In both renderers an unknown node keeps its content — text falls back to a
237
+ plain paragraph rather than disappearing.
238
+
178
239
  ### Protocol codec (module functions)
179
240
 
180
241
  Classifying and unwrapping wire frames is stateless, so it's exposed as
@@ -242,21 +303,6 @@ servers:
242
303
  causal dependencies are already in the store (checked against `on_load`); a
243
304
  causally-incomplete update triggers a resync instead, so the log always
244
305
  rebuilds cleanly.
245
- - **An unhealable gap is dropped, not resynced forever.** A resync heals a gap
246
- whose missing dependency is still in flight. But a *permanently*-orphaned update
247
- (its dependency is gone for good) stays gappy through every resync, and a client
248
- retransmitting it would loop endlessly (server resyncs → client resends →
249
- repeat). After `gap_strike_limit` rejections of the same update on one
250
- connection (default 3, minimum 2), the channel settles it with
251
- `{ "ack" => id, "dropped" => true }` and drops it instead of resyncing again —
252
- breaking the loop while never dropping a *healable* gap (those heal within a
253
- resync or two, and healing frees the strike). The `dropped` flag lets the
254
- client surface the loss (`yrby-client` reports it via `onError`) instead of
255
- silently showing synced. Set `gap_strike_limit nil` to disable. Works on both
256
- transports: plain ActionCable keeps strikes on the channel instance; under
257
- AnyCable (fresh instance per RPC command) they persist through
258
- anycable-rails' `state_attr_accessor` (istate), declared automatically when
259
- anycable-rails is loaded.
260
306
  - **`on_change` is at-least-once, and the durable guarantee is that replaying the
261
307
  log reconstructs the document.** Every update triggers `on_change` before it's acked or
262
308
  broadcast (record-before-distribute). If exactly-once updates matter for you, **you