Skip to content

🐛(y-provider) return empty output when converting empty Yjs document#2328

Merged
AntoLC merged 1 commit into
mainfrom
fix-empty-yjs-convert
May 29, 2026
Merged

🐛(y-provider) return empty output when converting empty Yjs document#2328
AntoLC merged 1 commit into
mainfrom
fix-empty-yjs-convert

Conversation

@sylvinus
Copy link
Copy Markdown
Member

The convert handler returned 500 "No valid blocks were generated" whenever the reader produced an empty blocks array, which is the normal state of a freshly-created Yjs document. Treat empty input as a valid case and return 200 with an empty body for every supported output format.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Review Change Stack

Walkthrough

This PR updates the y-provider convert handler to gracefully handle empty Yjs documents by returning HTTP 200 with empty output, instead of returning HTTP 500 when blocks are missing or empty. The core change modifies convertHandler to always call writer.write(blocks ?? []) on successful conversion. A new integration test validates that empty Yjs documents produce empty string responses for both HTML and Markdown formats. A changelog entry documents the fix.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main fix: handling empty Yjs documents to return empty output instead of an error.
Description check ✅ Passed The description is directly related to the changeset, explaining the bug fix context and the expected behavior change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-empty-yjs-convert

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@zeylos
Copy link
Copy Markdown
Contributor

zeylos commented May 27, 2026

tested successfuly in production on our instance
image

@AntoLC
Copy link
Copy Markdown
Collaborator

AntoLC commented May 27, 2026

Why calling this webservice with an empty body ?

@AntoLC AntoLC added bug Something isn't working y.js labels May 27, 2026
@sylvinus
Copy link
Copy Markdown
Member Author

@AntoLC because our CMS fetches all pages without knowing in advance if some are empty or not :) (the parent pages are often empty)

@AntoLC AntoLC self-requested a review May 27, 2026 09:56
@AntoLC AntoLC force-pushed the fix-empty-yjs-convert branch from 44ca7a1 to 036feee Compare May 29, 2026 15:18
The convert handler returned 500 "No valid blocks were generated" whenever
the reader produced an empty blocks array, which is the normal state of a
freshly-created Yjs document. Treat empty input as a valid case and return
200 with an empty body for every supported output format.

Signed-off-by: Sylvain Zimmer <sylvain@sylvainzimmer.com>
@AntoLC AntoLC force-pushed the fix-empty-yjs-convert branch from 036feee to 5bd2d1b Compare May 29, 2026 15:23
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/frontend/servers/y-provider/src/handlers/convertHandler.ts (1)

319-344: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider testing additional output formats for empty documents.

The test validates the core fix by checking HTML and Markdown outputs. For more comprehensive coverage, consider also testing:

  • Empty Yjs → JSON output
  • Empty Yjs → YJS output
  • Empty BlockNote input ([]) → various outputs

This would ensure all writers handle empty blocks consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/servers/y-provider/src/handlers/convertHandler.ts` around lines
319 - 344, Add unit tests that exercise additional serialization paths for empty
documents beyond HTML and Markdown to ensure all writers handle empty blocks
consistently: specifically add tests that feed an empty Yjs document into the
conversion flow used by convertHandler and assert JSON output (Yjs → JSON) and
YJS serialized output (Yjs → YJS) are correct/empty, and add tests that convert
an empty BlockNote input ([]) through the same convertHandler/writers and assert
HTML, Markdown, JSON and YJS outputs. Reference the convertHandler conversion
entrypoint and the writer functions used there (e.g., the JSON writer, YJS
writer and BlockNote input handling) so the new tests invoke the exact code
paths you fixed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/frontend/servers/y-provider/__tests__/convert.test.ts`:
- Around line 319-344: Add a third sub-test in the 'POST /api/convert empty Yjs
document returns 200 with empty content' test: after creating yjsUpdate via
Y.encodeStateAsUpdate(new Y.Doc()) and the existing HTML/Markdown requests, send
a POST to '/api/convert' using initApp() with headers 'content-type:
application/vnd.yjs.doc' and 'accept: application/json', sending
Buffer.from(yjsUpdate); then assert the response status is 200 and the parsed
JSON body equals an empty object ({}), mirroring the non-empty JSON conversion
test pattern.
- Around line 319-344: The test "POST /api/convert empty Yjs document returns
200 with empty content" is missing the established spy on
Y.Doc.prototype.destroy; add a jest.spyOn(Y.Doc.prototype, 'destroy') before
making the two requests, run the two requests as-is, then assert the spy was
called twice (to verify both decodings cleaned up), and finally restore the spy
with mockRestore to avoid polluting other tests.

---

Outside diff comments:
In `@src/frontend/servers/y-provider/src/handlers/convertHandler.ts`:
- Around line 319-344: Add unit tests that exercise additional serialization
paths for empty documents beyond HTML and Markdown to ensure all writers handle
empty blocks consistently: specifically add tests that feed an empty Yjs
document into the conversion flow used by convertHandler and assert JSON output
(Yjs → JSON) and YJS serialized output (Yjs → YJS) are correct/empty, and add
tests that convert an empty BlockNote input ([]) through the same
convertHandler/writers and assert HTML, Markdown, JSON and YJS outputs.
Reference the convertHandler conversion entrypoint and the writer functions used
there (e.g., the JSON writer, YJS writer and BlockNote input handling) so the
new tests invoke the exact code paths you fixed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 95738a87-3706-438d-905a-449333f8b79c

📥 Commits

Reviewing files that changed from the base of the PR and between 44ca7a1 and 5bd2d1b.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/frontend/servers/y-provider/__tests__/convert.test.ts
  • src/frontend/servers/y-provider/src/handlers/convertHandler.ts

Comment on lines +319 to +344
test('POST /api/convert empty Yjs document returns 200 with empty content', async () => {
const app = initApp();
const yjsUpdate = Y.encodeStateAsUpdate(new Y.Doc());

const htmlResponse = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', `Bearer ${apiKey}`)
.set('content-type', 'application/vnd.yjs.doc')
.set('accept', 'text/html')
.send(Buffer.from(yjsUpdate));

expect(htmlResponse.status).toBe(200);
expect(htmlResponse.text).toBe('');

const markdownResponse = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', `Bearer ${apiKey}`)
.set('content-type', 'application/vnd.yjs.doc')
.set('accept', 'text/markdown')
.send(Buffer.from(yjsUpdate));

expect(markdownResponse.status).toBe(200);
expect(markdownResponse.text).toBe('\n');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding JSON output format coverage.

For completeness, consider also testing conversion of an empty Yjs document to JSON format, similar to how the test at line 261 validates non-empty Yjs to JSON conversion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/servers/y-provider/__tests__/convert.test.ts` around lines 319 -
344, Add a third sub-test in the 'POST /api/convert empty Yjs document returns
200 with empty content' test: after creating yjsUpdate via
Y.encodeStateAsUpdate(new Y.Doc()) and the existing HTML/Markdown requests, send
a POST to '/api/convert' using initApp() with headers 'content-type:
application/vnd.yjs.doc' and 'accept: application/json', sending
Buffer.from(yjsUpdate); then assert the response status is 200 and the parsed
JSON body equals an empty object ({}), mirroring the non-empty JSON conversion
test pattern.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add Y.Doc.destroy spy to follow established pattern.

Every test in this file that converts FROM Yjs format verifies that Y.Doc.prototype.destroy is called (lines 262, 304). Since this test makes two requests that decode Yjs input, you should add the spy and expect it to be called twice to ensure resource cleanup is tested.

♻️ Proposed addition to verify cleanup
  test('POST /api/convert empty Yjs document returns 200 with empty content', async () => {
+   const destroySpy = vi.spyOn(Y.Doc.prototype, 'destroy');
    const app = initApp();
    const yjsUpdate = Y.encodeStateAsUpdate(new Y.Doc());

    const htmlResponse = await request(app)
      .post('/api/convert')
      .set('origin', origin)
      .set('authorization', `Bearer ${apiKey}`)
      .set('content-type', 'application/vnd.yjs.doc')
      .set('accept', 'text/html')
      .send(Buffer.from(yjsUpdate));

    expect(htmlResponse.status).toBe(200);
    expect(htmlResponse.text).toBe('');

    const markdownResponse = await request(app)
      .post('/api/convert')
      .set('origin', origin)
      .set('authorization', `Bearer ${apiKey}`)
      .set('content-type', 'application/vnd.yjs.doc')
      .set('accept', 'text/markdown')
      .send(Buffer.from(yjsUpdate));

    expect(markdownResponse.status).toBe(200);
    expect(markdownResponse.text).toBe('\n');
+   expect(destroySpy).toHaveBeenCalledTimes(2);
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('POST /api/convert empty Yjs document returns 200 with empty content', async () => {
const app = initApp();
const yjsUpdate = Y.encodeStateAsUpdate(new Y.Doc());
const htmlResponse = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', `Bearer ${apiKey}`)
.set('content-type', 'application/vnd.yjs.doc')
.set('accept', 'text/html')
.send(Buffer.from(yjsUpdate));
expect(htmlResponse.status).toBe(200);
expect(htmlResponse.text).toBe('');
const markdownResponse = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', `Bearer ${apiKey}`)
.set('content-type', 'application/vnd.yjs.doc')
.set('accept', 'text/markdown')
.send(Buffer.from(yjsUpdate));
expect(markdownResponse.status).toBe(200);
expect(markdownResponse.text).toBe('\n');
});
test('POST /api/convert empty Yjs document returns 200 with empty content', async () => {
const destroySpy = vi.spyOn(Y.Doc.prototype, 'destroy');
const app = initApp();
const yjsUpdate = Y.encodeStateAsUpdate(new Y.Doc());
const htmlResponse = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', `Bearer ${apiKey}`)
.set('content-type', 'application/vnd.yjs.doc')
.set('accept', 'text/html')
.send(Buffer.from(yjsUpdate));
expect(htmlResponse.status).toBe(200);
expect(htmlResponse.text).toBe('');
const markdownResponse = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', `Bearer ${apiKey}`)
.set('content-type', 'application/vnd.yjs.doc')
.set('accept', 'text/markdown')
.send(Buffer.from(yjsUpdate));
expect(markdownResponse.status).toBe(200);
expect(markdownResponse.text).toBe('\n');
expect(destroySpy).toHaveBeenCalledTimes(2);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/servers/y-provider/__tests__/convert.test.ts` around lines 319 -
344, The test "POST /api/convert empty Yjs document returns 200 with empty
content" is missing the established spy on Y.Doc.prototype.destroy; add a
jest.spyOn(Y.Doc.prototype, 'destroy') before making the two requests, run the
two requests as-is, then assert the spy was called twice (to verify both
decodings cleaned up), and finally restore the spy with mockRestore to avoid
polluting other tests.

@AntoLC AntoLC merged commit 5bd2d1b into main May 29, 2026
45 checks passed
@AntoLC AntoLC deleted the fix-empty-yjs-convert branch May 29, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working y.js

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants