Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/lexical-y-older-clients-keep-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@lexical/y': patch
---

[fix] Keep the fields a newer version of a node class added when a client with the older class edits the node in `@lexical/y`

A client writes and deletes only the fields its classes declare in `$config`, with the text formats and NodeState it holds, and keeps every other stored name. A client that does not register a link class (or `LinkNode`) a document uses opens it read only, as it does for every other node type it does not register.
7 changes: 7 additions & 0 deletions .changeset/lexical-y-unreadable-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@lexical/y': patch
---

[fix] Open a document read only in `@lexical/y` when a node class throws on its stored content, instead of showing it empty

The binding warns `UNKNOWN_NODE_TYPES_WARNING` with the new reason `'unreadable'` (with `types` and `error`), keeps showing the rest of the document, and throws nothing into the provider's `Y.applyUpdate` or from `initialize()`.
4 changes: 2 additions & 2 deletions packages/lexical-website/docs/collaboration/y-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ synchronous loop over many documents holds one at a time.

They throw a `YHeadlessError` with a `code`: `'not-initialized'` (no layout
record yet: seed the document first), `'read-only'` (a newer layout, a
node type the config does not register, or a named slot the document cannot
store, which the binding refuses to write; the error's `warnings` are the
node type the config does not register, content its classes throw on, or a
named slot the document cannot store, which the binding refuses to write; the error's `warnings` are the
binding's typed warnings that say which, so an app can narrow on each
one's `code` and `data.reason` instead of reading the message),
`'foreign-roots'` (the update writes to a root that is not allowed),
Expand Down
29 changes: 23 additions & 6 deletions packages/lexical-website/docs/collaboration/y.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ provider.once('sync', () => {
- The editor stays **read only** until the document's layout record has
been read. `status` is a signal: `'pending'`, then `'active'`, or
`'readonly'` when the binding cannot write the document safely (a newer
layout, a node type this editor does not register; a
[warning](#warnings) says why).
layout, a node type this editor does not register, stored content its
classes throw on; a [warning](#warnings) says why).
Register the same node classes on every client.
- `binding.initialize()`, called after the provider's first sync, writes the
editor's content into an empty document (an `editor.update()` still
Expand Down Expand Up @@ -249,7 +249,7 @@ a short note for developers: render your own text from `code` and `data`.
| Constant | Reported when | `data` (`reason`: other fields) |
| -- | -- | -- |
| `UNSUPPORTED_LAYOUT_WARNING` | The layout is one this version cannot read. Read only. | `'newer'`: `found`; `'malformed'`: `key`, `found`; `'missing'`, `'removed'`; `'legacy-roots'`: `roots`. All: `supported` |
| `UNKNOWN_NODE_TYPES_WARNING` | The document has node types the editor cannot build. Read only. | `'unregistered'`, `'malformed-slot'`: `types` |
| `UNKNOWN_NODE_TYPES_WARNING` | The document has node types the editor cannot build: types it does not register, however they are stored (a link class it lacks too), or content its classes throw on. Read only. | `'unregistered'`, `'malformed-slot'`: `types`; `'unreadable'`: `types`, `error` |
| `STORAGE_MISMATCH_WARNING` | A node type is stored in a way its class cannot be (one warning per type). Read only. | `'declaration-changed'`: `type`, `stored`, `declared`; `'malformed'`: `type`, `key`, `found` |
| `UNSUPPORTED_SLOTS_WARNING` | Named slots where the document cannot store them. Not written; read only. | `'stored-host'`, `'root'`: `types` |
| `UNSUPPORTED_CONFIG_WARNING` | `BlockIdExtension` with `repairTransforms: true` (see [Set up an editor](#set-up-an-editor)). Nothing is written; read only. | `'repair-transforms'`: `options` |
Expand Down Expand Up @@ -874,6 +874,16 @@ not merge as a user might expect:
keeps the format: the collaborator's undo brings it back.
- **Large lists.** A structural edit in a list costs time proportional to
the list on every client.
- **Older app versions.** A client keeps the fields, text formats and
NodeState it does not know when it edits a node (see
[Changing your own node types](#changing-the-layout-and-migrating)), but a
block it moves is written anew from what it shows, so the moved block loses
them (see Moves). A link it replaces with another link of the same type over
the same text keeps the ones of the link it replaced: it cannot tell the
new link from the old one edited. A class that writes its own `exportJSON`
owns every field of its type, since which ones it writes cannot be known:
an older version of it deletes the fields a newer one added. Declare fields
in `$config` to keep them.
- **The caret when a collaborator rewrites its line.** A caret stays after
the text it was after. When a collaborator replaces the line holding it,
deleting all the line showed and putting new content at its place in one
Expand Down Expand Up @@ -936,9 +946,16 @@ hold one stay readable and editable by layout 1 clients.
field with a default, or dropping one, works as in
[Lexical JSON](/docs/serialization/#versioning--breaking-changes): a field
a room does not have reads as its default, and one your class no longer has
is ignored. A change to a class's declared node structure does not apply to
rooms that already hold that type (they keep the recorded kind); reseed
them to adopt it.
is ignored. Clients of both versions can edit a room at once (during a
rollout): a client writes only the fields its classes declare in
`$config`, so an older client's edit of a node keeps the fields a newer
version added, and a field a client knows and clears is deleted (see
"Older app versions" in the limits). A value an older class throws on (an
`updateFromJSON` that rejects a value the newer version writes) opens the
room read only for that client, as a type it does not register does. A
change to a class's declared node structure does not apply to rooms that
already hold that type (they keep the recorded kind); reseed them to adopt
it.

### Reseeding a room

Expand Down
11 changes: 11 additions & 0 deletions packages/lexical-y/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ of set-valued NodeState; layout 3 (planned, #9449) adds move records.
`\u0000` followed by the value's JSON with sorted keys. A string that
itself starts with `\u0000` is stored the same way (as the JSON of that
string), so every member reads back with its type and value.
- **Names a client does not own:** a writer changes the value a name gives
content only for the names it owns, the binding's own and the fields its
classes declare for the content's type (all of them where the write
changes that type), its text formats and NodeState, and keeps every other
name's value as stored: a field a newer version of a class added, a text
format a newer Lexical added, a key of a later layout. Format items that
only keep every name's value where it is (around inserted content, at the
end of a run) carry any name. Inserted text takes the names it does not
own from the text it continues (a field only from text of its own type),
and a new group item the group fields of the item next to it. A reader
reads only the names it owns.
- **Defaults are left out:** a field or NodeState value equal to the one a
new instance of the class has (its `$config` default) is not stored, so a
reader takes it from its own class (an `h1` heading's marker is
Expand Down
10 changes: 9 additions & 1 deletion packages/lexical-y/flow/LexicalY.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ export type YUnknownNodeTypesData = {
readonly reason: 'unregistered' | 'malformed-slot',
readonly types: ReadonlyArray<string>,
};
export type YUnreadableContentData = {
readonly reason: 'unreadable',
readonly types: ReadonlyArray<string>,
readonly error: unknown,
};
export type YUnsupportedSlotsData = {
readonly reason: 'stored-host' | 'root',
readonly types: ReadonlyArray<string>,
Expand Down Expand Up @@ -123,7 +128,10 @@ declare export class YWarningOf<out C extends YWarningCode, out D>
}
export type YWarning =
| YWarningOf<typeof UNSUPPORTED_LAYOUT_WARNING, YUnsupportedLayoutData>
| YWarningOf<typeof UNKNOWN_NODE_TYPES_WARNING, YUnknownNodeTypesData>
| YWarningOf<
typeof UNKNOWN_NODE_TYPES_WARNING,
YUnknownNodeTypesData | YUnreadableContentData,
>
| YWarningOf<typeof STORAGE_MISMATCH_WARNING, YStorageMismatchData>
| YWarningOf<typeof SELF_CHECK_WARNING, YSelfCheckData>
| YWarningOf<typeof RECOVERED_READ_WARNING, YRecoveredReadData>
Expand Down
Loading
Loading