From 37eaf6abfdffab6559c52e1f8a5e6d8d85758b2c Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 6 May 2026 11:20:29 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20fix=20loading=20commen?= =?UTF-8?q?ts=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- CHANGELOG.md | 1 + .../components/comments/DocsThreadStore.tsx | 39 +++++++++++++++---- .../components/comments/useComments.ts | 9 ++++- .../docs/doc-editor/hook/useSaveDoc.tsx | 11 ++++++ 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3f130b65..db10c1608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx index 43374b78b..9a86bd86a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/DocsThreadStore.tsx @@ -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 = new Map(); @@ -24,6 +32,7 @@ export class DocsThreadStore extends ThreadStore { (threads: Map) => void >(); private awareness?: Awareness; + private yDoc?: Y.Doc; private lastPingAt = 0; private pingTimer?: ReturnType; @@ -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) { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts index 6dbd3cbfb..35863ff1e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/comments/useComments.ts @@ -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) { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx index 85ac5bc1b..f434ba3df 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx @@ -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); };