Skip to content

[lexical-y] Chore: Test that internal writes of node attributes go through Seen - #9550

Open
zurfyx wants to merge 1 commit into
zurfyx/y-seen-viewfrom
zurfyx/y-seen-writes-guard
Open

zurfyx wants to merge 1 commit into
zurfyx/y-seen-viewfrom
zurfyx/y-seen-writes-guard

Conversation

@zurfyx

@zurfyx zurfyx commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Description

Stacked on #9533 (base branch zurfyx/y-seen-view; one module for what a binding's editor sees of Yjs).

A binding must write node attributes through Seen's setAttr and deleteAttr: while a remote change waits to be read, they write where the binding's editor sees the document. A raw y.setAttr(...) writes where Yjs has it now and brings back what that change deleted. Misses of this kind were found by review twice before (the N2 and G-4 items of the integration review); nothing enforced the rule.

This adds a syntactic guard, a source test next to the existing process.env one. It parses each internal module except Seen.ts with the TypeScript AST and lists every use of a Y.Node method that writes attributes (setAttr, deleteAttr, clearAttrs, applyDelta; property or string-literal element access, called or only referenced as in a bind, or destructured), and every call shaped like Y.Node's delete(index, length) or format(index, length, attrs), each by the function it is in and its first argument. The only ones allowed are the five writes of the root's records (the layout and schema versions written, a type's pinned storage), listed by call site and key: they are not content the editor shows. Comments and strings do not hide or invent a match.

It is not exhaustive. Not covered: Item.delete(tr) (a binding deletes items through deleteItem) and a one-argument Y.Node#delete(index), which a syntactic scan cannot tell from a Map or Set delete; aliases of delete and format; computed method names; writes reached through other modules' helpers; and reads, which the reader of remote changes and scans of what each client wrote do raw on purpose.

Changeset

None: test only.

Test plan

Before

With a scratch module internal/ZZProbe.ts of raw writes (optional calls, bracket access, a bound alias, comment and string tricks, clearAttrs, applyDelta, delete(0, 1), format(0, 1, …); not committed; a second probe with destructured and parenthesized forms is caught the same way):

npx vitest run --project unit packages/lexical-y/src/__tests__/unit/Source.test.ts
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.setAttr((not called))",
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.deleteAttr('x')",
+   "ZZProbe.ts probe: node.setAttr('y')",
+   "ZZProbe.ts probe: node.clearAttrs()",
+   "ZZProbe.ts probe: node.applyDelta(node.change['setAttr']('x', 'new'))",
+   "ZZProbe.ts probe: node.change.setAttr('x')",
+   "ZZProbe.ts probe: node.delete(0)",
+   "ZZProbe.ts probe: node.format(0)",
      Tests  1 failed | 3 passed (4)

After

On this branch as committed:

npx vitest run --project unit packages/lexical-y/src/__tests__/unit/Source.test.ts
      Tests  4 passed (4)

pnpm run ci-check: Found 0 errors.

@vercel

vercel Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lexical Ready Ready Preview Oct 29, 2026 11:53pm UTC
lexical-playground Ready Ready Preview Oct 29, 2026 11:53pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 29, 2026
@pkg-pr-new

pkg-pr-new Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
lexical

npm i https://pkg.pr.new/lexical@9550

@lexical/a11y

npm i https://pkg.pr.new/@lexical/a11y@9550

@lexical/clipboard

npm i https://pkg.pr.new/@lexical/clipboard@9550

@lexical/code

npm i https://pkg.pr.new/@lexical/code@9550

@lexical/code-core

npm i https://pkg.pr.new/@lexical/code-core@9550

@lexical/code-prism

npm i https://pkg.pr.new/@lexical/code-prism@9550

@lexical/code-shiki

npm i https://pkg.pr.new/@lexical/code-shiki@9550

@lexical/compiler

npm i https://pkg.pr.new/@lexical/compiler@9550

@lexical/devtools-core

npm i https://pkg.pr.new/@lexical/devtools-core@9550

@lexical/dragon

npm i https://pkg.pr.new/@lexical/dragon@9550

@lexical/eslint-plugin

npm i https://pkg.pr.new/@lexical/eslint-plugin@9550

@lexical/extension

npm i https://pkg.pr.new/@lexical/extension@9550

@lexical/fast-check

npm i https://pkg.pr.new/@lexical/fast-check@9550

@lexical/file

npm i https://pkg.pr.new/@lexical/file@9550

@lexical/hashtag

npm i https://pkg.pr.new/@lexical/hashtag@9550

@lexical/headless

npm i https://pkg.pr.new/@lexical/headless@9550

@lexical/history

npm i https://pkg.pr.new/@lexical/history@9550

@lexical/html

npm i https://pkg.pr.new/@lexical/html@9550

@lexical/internal

npm i https://pkg.pr.new/@lexical/internal@9550

@lexical/link

npm i https://pkg.pr.new/@lexical/link@9550

@lexical/list

npm i https://pkg.pr.new/@lexical/list@9550

@lexical/mark

npm i https://pkg.pr.new/@lexical/mark@9550

@lexical/markdown

npm i https://pkg.pr.new/@lexical/markdown@9550

@lexical/mdast

npm i https://pkg.pr.new/@lexical/mdast@9550

@lexical/offset

npm i https://pkg.pr.new/@lexical/offset@9550

@lexical/overflow

npm i https://pkg.pr.new/@lexical/overflow@9550

@lexical/plain-text

npm i https://pkg.pr.new/@lexical/plain-text@9550

@lexical/react

npm i https://pkg.pr.new/@lexical/react@9550

@lexical/rich-text

npm i https://pkg.pr.new/@lexical/rich-text@9550

@lexical/selection

npm i https://pkg.pr.new/@lexical/selection@9550

@lexical/table

npm i https://pkg.pr.new/@lexical/table@9550

@lexical/tailwind

npm i https://pkg.pr.new/@lexical/tailwind@9550

@lexical/text

npm i https://pkg.pr.new/@lexical/text@9550

@lexical/utils

npm i https://pkg.pr.new/@lexical/utils@9550

@lexical/y

npm i https://pkg.pr.new/@lexical/y@9550

@lexical/yjs

npm i https://pkg.pr.new/@lexical/yjs@9550

commit: ad94a30

…rough Seen

## Description

Stacked on #9533 (base branch `zurfyx/y-seen-view`; one module for what a binding's editor sees of Yjs).

A binding must write node attributes through `Seen`'s `setAttr` and `deleteAttr`: while a remote change waits to be read, they write where the binding's editor sees the document. A raw `y.setAttr(...)` writes where Yjs has it now and brings back what that change deleted. Misses of this kind were found by review twice before (the N2 and G-4 items of the integration review); nothing enforced the rule.

This adds a syntactic guard, a source test next to the existing `process.env` one. It parses each internal module except `Seen.ts` with the TypeScript AST and lists every use of a `Y.Node` method that writes attributes (`setAttr`, `deleteAttr`, `clearAttrs`, `applyDelta`; property or string-literal element access, called or only referenced as in a `bind`, or destructured), and every call shaped like `Y.Node`'s `delete(index, length)` or `format(index, length, attrs)`, each by the function it is in and its first argument. The only ones allowed are the five writes of the root's records (the layout and schema versions written, a type's pinned storage), listed by call site and key: they are not content the editor shows. Comments and strings do not hide or invent a match.

It is not exhaustive. Not covered: `Item.delete(tr)` (a binding deletes items through `deleteItem`) and a one-argument `Y.Node#delete(index)`, which a syntactic scan cannot tell from a `Map` or `Set` delete; aliases of `delete` and `format`; computed method names; writes reached through other modules' helpers; and reads, which the reader of remote changes and scans of what each client wrote do raw on purpose.

## Changeset

None: test only.

## Test plan

### Before

With a scratch module `internal/ZZProbe.ts` of raw writes (optional calls, bracket access, a bound alias, comment and string tricks, `clearAttrs`, `applyDelta`, `delete(0, 1)`, `format(0, 1, …)`; not committed; a second probe with destructured and parenthesized forms is caught the same way):

```
npx vitest run --project unit packages/lexical-y/src/__tests__/unit/Source.test.ts
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.setAttr((not called))",
+   "ZZProbe.ts probe: node.setAttr('x')",
+   "ZZProbe.ts probe: node.deleteAttr('x')",
+   "ZZProbe.ts probe: node.setAttr('y')",
+   "ZZProbe.ts probe: node.clearAttrs()",
+   "ZZProbe.ts probe: node.applyDelta(node.change['setAttr']('x', 'new'))",
+   "ZZProbe.ts probe: node.change.setAttr('x')",
+   "ZZProbe.ts probe: node.delete(0)",
+   "ZZProbe.ts probe: node.format(0)",
      Tests  1 failed | 3 passed (4)
```

### After

On this branch as committed:

```
npx vitest run --project unit packages/lexical-y/src/__tests__/unit/Source.test.ts
      Tests  4 passed (4)
```

`pnpm run ci-check`: `Found 0 errors`.

This branch was successfully deployed

2 active deployments
Preview – lexical — ad94a309 Deployed Oct 29, 2026 by vercel[bot]
Preview – lexical-playground — ad94a309 Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant