🐛(frontend) fix loading comments transaction

When we load the comments we have to notify the
subscribers of the DocsThreadStore. This generates
a Yjs transaction that is currently treated as a
user-initiated content change that will trigger
a patch request when the doc will try to save.
We now update the transaction origin when we notify
the subscribers so that we can reliably identify
and ignore those transactions in the useSaveDoc
hook.
This commit is contained in:
Anthony LC
2026-05-06 11:20:29 +02:00
parent 85128c7b11
commit 37eaf6abfd
4 changed files with 51 additions and 9 deletions

View File

@@ -12,6 +12,7 @@ and this project adheres to
### Fixed
- 🐛(frontend) fix loading comments transaction #2273
- 💬(frontend) add missing link in onboarding description #2233
- 🐛(frontend) sanitize pasted and dropped content in document title #2210
- 🐛(frontend) Emoji menu doesn't display above comment box #2229

View File

@@ -1,5 +1,6 @@
import { CommentBody, ThreadStore } from '@blocknote/core/comments';
import type { Awareness } from 'y-protocols/awareness';
import * as Y from 'yjs';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Doc } from '@/features/docs/doc-management';
@@ -17,6 +18,13 @@ import {
type ServerThreadListResponse = ServerThread[];
/**
* notifySubscribers generate a transaction, to distinguish
* the origin of the update, we use a specific origin "commentMarkUpdate"
* for the updates coming from the comment mark changes.
*/
export const COMMENT_UPDATE_ORIGIN = 'commentMarkUpdate';
export class DocsThreadStore extends ThreadStore {
protected static COMMENTS_PING = 'commentsPing';
protected threads: Map<string, ClientThreadData> = new Map();
@@ -24,6 +32,7 @@ export class DocsThreadStore extends ThreadStore {
(threads: Map<string, ClientThreadData>) => void
>();
private awareness?: Awareness;
private yDoc?: Y.Doc;
private lastPingAt = 0;
private pingTimer?: ReturnType<typeof setTimeout>;
@@ -31,11 +40,13 @@ export class DocsThreadStore extends ThreadStore {
protected docId: Doc['id'],
awareness: Awareness | undefined,
protected docAuth: DocsThreadStoreAuth,
yDoc?: Y.Doc,
) {
super(docAuth);
if (docAuth.canSee) {
this.awareness = awareness;
this.yDoc = yDoc;
this.awareness?.on('update', this.onAwarenessUpdate);
this.refreshThreads();
@@ -134,18 +145,30 @@ export class DocsThreadStore extends ThreadStore {
}
/**
* Notifies all subscribers about the current thread state
* Notifies all subscribers about the current thread state.
* We trigger the transaction with a specific origin so we will be able
* to flag that the update comes from a comment update.
* The inner ydoc.transact calls from y-prosemirror will see there's already
* an active transaction and reuse it.
*/
private notifySubscribers() {
// Always emit a new Map reference to help consumers detect changes
const threads = new Map(this.threads);
this.subscribers.forEach((cb) => {
try {
cb(threads);
} catch (e) {
console.warn('DocsThreadStore subscriber threw', e);
}
});
const notify = () => {
this.subscribers.forEach((cb) => {
try {
cb(threads);
} catch (e) {
console.warn('DocsThreadStore subscriber threw', e);
}
});
};
if (this.yDoc) {
this.yDoc.transact(notify, COMMENT_UPDATE_ORIGIN);
} else {
notify();
}
}
private upsertClientThreadData(thread: ClientThreadData) {

View File

@@ -27,8 +27,15 @@ export function useComments(
encodeURIComponent(user?.full_name || ''),
canComment,
),
provider?.document,
);
}, [docId, canComment, provider?.awareness, user?.full_name]);
}, [
docId,
canComment,
provider?.awareness,
provider?.document,
user?.full_name,
]);
useEffect(() => {
if (canComment) {

View File

@@ -2,6 +2,7 @@ import { useRouter } from 'next/router';
import { useCallback, useEffect, useRef, useState } from 'react';
import * as Y from 'yjs';
import { COMMENT_UPDATE_ORIGIN } from '@/docs/doc-editor/components/comments/DocsThreadStore';
import { useDocContentUpdate } from '@/docs/doc-management/api/useDocContentUpdate';
import { useProviderStore } from '@/docs/doc-management/stores/useProviderStore';
import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning/api/useDocVersions';
@@ -65,6 +66,16 @@ export const useSaveDoc = (docId: string, yDoc: Y.Doc) => {
const isAIChange =
!transaction.local && transactionOrigin !== PROVIDER_ORIGIN_CONSTRUCTOR;
/**
* notifySubscribers generate a transaction that can be
* interpreted as a local change.
* We intercept the update with this origin to
* avoid marking the change as local.
*/
if (transaction.origin === COMMENT_UPDATE_ORIGIN) {
return;
}
setIsLocalChange(transaction.local || isAIChange);
};