diff --git a/eslint.config.mjs b/eslint.config.mjs index 35c5cdbca3..3a9f679020 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -46,6 +46,8 @@ export default [ '**/commitlint.config.js', // E2E tests 'e2e-tests/**', + // SDK scripts — ESM parsed incorrectly by typescript-eslint + 'packages/sdk/scripts/**', ], }, { diff --git a/packages/superdoc/src/SuperDoc.vue b/packages/superdoc/src/SuperDoc.vue index 48951fc900..6b1b7cf6c7 100644 --- a/packages/superdoc/src/SuperDoc.vue +++ b/packages/superdoc/src/SuperDoc.vue @@ -956,11 +956,13 @@ watch( }, ); +// Ensure hasInitializedLocations is set when comments arrive (backup for cases +// where handleDocumentReady hasn't fired yet). Never toggle false→true→false — +// the virtualized FloatingComments reacts to comment changes via computed properties. watch(getFloatingComments, () => { - hasInitializedLocations.value = false; - nextTick(() => { + if (!hasInitializedLocations.value) { hasInitializedLocations.value = true; - }); + } }); const { diff --git a/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue b/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue index 481c3626d1..a55a10cd5c 100644 --- a/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue +++ b/packages/superdoc/src/components/CommentsLayer/FloatingComments.vue @@ -1,10 +1,18 @@ + + diff --git a/packages/superdoc/src/stores/comments-store.js b/packages/superdoc/src/stores/comments-store.js index 11b31c4e42..12bf42db82 100644 --- a/packages/superdoc/src/stores/comments-store.js +++ b/packages/superdoc/src/stores/comments-store.js @@ -781,6 +781,8 @@ export const useCommentsStore = defineStore('comments', () => { ...(formatMark && { formatMark: formatMark.mark }), }; + // nodes/deletionNodes are unused here — the function resolves them from + // trackedChangesForId which already contains all document positions for this ID. const params = createOrUpdateTrackedChangeComment({ event: 'add', marks, @@ -1019,6 +1021,7 @@ export const useCommentsStore = defineStore('comments', () => { getGroupedComments, getCommentsByPosition, getFloatingComments, + getCommentPositionKey, getCommentPosition, getCommentAnchoredText, getCommentAnchorData, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d09cff61b9..6f18c06055 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -989,6 +989,22 @@ importers: typescript: specifier: 'catalog:' version: 5.9.3 + optionalDependencies: + '@superdoc-dev/sdk-darwin-arm64': + specifier: 1.0.0-alpha.6 + version: link:platforms/sdk-darwin-arm64 + '@superdoc-dev/sdk-darwin-x64': + specifier: 1.0.0-alpha.6 + version: link:platforms/sdk-darwin-x64 + '@superdoc-dev/sdk-linux-arm64': + specifier: 1.0.0-alpha.6 + version: link:platforms/sdk-linux-arm64 + '@superdoc-dev/sdk-linux-x64': + specifier: 1.0.0-alpha.6 + version: link:platforms/sdk-linux-x64 + '@superdoc-dev/sdk-windows-x64': + specifier: 1.0.0-alpha.6 + version: link:platforms/sdk-windows-x64 packages/sdk/langs/node/platforms/sdk-darwin-arm64: {} diff --git a/tests/behavior/tests/comments/basic-comment-insertion.spec.ts b/tests/behavior/tests/comments/basic-comment-insertion.spec.ts index ff3688eb2e..43738a89de 100644 --- a/tests/behavior/tests/comments/basic-comment-insertion.spec.ts +++ b/tests/behavior/tests/comments/basic-comment-insertion.spec.ts @@ -95,9 +95,9 @@ test('add a comment via the UI bubble', async ({ superdoc }) => { } // Verify the comment text appears in the floating dialog - const commentDialog = superdoc.page.locator('.floating-comment > .comments-dialog').last(); + const commentDialog = superdoc.page.locator('.comment-placeholder .comments-dialog').last(); const commentText = commentDialog.locator('.comment-body .comment'); - await expect(commentText.first()).toBeAttached({ timeout: 5_000 }); + await expect(commentText.first()).toBeAttached({ timeout: 10_000 }); await expect(commentText.first()).toContainText('UI comment on selected text'); await superdoc.snapshot('comment added via UI'); diff --git a/tests/behavior/tests/comments/edit-comment-text.spec.ts b/tests/behavior/tests/comments/edit-comment-text.spec.ts index 028d18de73..e83ae6da43 100644 --- a/tests/behavior/tests/comments/edit-comment-text.spec.ts +++ b/tests/behavior/tests/comments/edit-comment-text.spec.ts @@ -59,8 +59,10 @@ test('editing a comment updates its text', async ({ superdoc }) => { await superdoc.waitForStable(); // After update the dialog loses is-active; verify the text changed via the visible sidebar dialog - const updatedDialog = superdoc.page.locator('.floating-comment > .comments-dialog'); - await expect(updatedDialog.locator('.comment-body .comment').first()).toContainText('changed comment'); + const updatedDialog = superdoc.page.locator('.comment-placeholder .comments-dialog'); + await expect(updatedDialog.locator('.comment-body .comment').first()).toContainText('changed comment', { + timeout: 10_000, + }); // CommentInfo.text is optional in the contract — some adapters don't populate it. // Verify via the API when available; the DOM assertion above covers all adapters. const listed = await listComments(superdoc.page, { includeResolved: true }); diff --git a/tests/behavior/tests/comments/floating-comments-virtualization.spec.ts b/tests/behavior/tests/comments/floating-comments-virtualization.spec.ts new file mode 100644 index 0000000000..30b15798f2 --- /dev/null +++ b/tests/behavior/tests/comments/floating-comments-virtualization.spec.ts @@ -0,0 +1,66 @@ +import { test, expect } from '../../fixtures/superdoc.js'; +import { assertDocumentApiReady, listTrackChanges, listComments } from '../../helpers/document-api.js'; + +test.use({ config: { toolbar: 'full', comments: 'on', trackChanges: true } }); + +test('@behavior SD-1997: floating comment bubbles render after tracked changes', async ({ superdoc }) => { + await assertDocumentApiReady(superdoc.page); + + // Switch to suggesting mode so edits create tracked changes + await superdoc.setDocumentMode('suggesting'); + await superdoc.waitForStable(); + + // Create several tracked changes + for (let i = 0; i < 5; i++) { + await superdoc.type(`tracked change ${i + 1}`); + await superdoc.newLine(); + await superdoc.waitForStable(); + } + + // Verify tracked changes were created + await expect + .poll(async () => (await listTrackChanges(superdoc.page, { type: 'insert' })).total) + .toBeGreaterThanOrEqual(5); + + // Verify floating comment placeholders appear in the sidebar + const placeholders = superdoc.page.locator('.comment-placeholder'); + await expect(placeholders.first()).toBeAttached({ timeout: 10_000 }); + + const count = await placeholders.count(); + expect(count).toBeGreaterThanOrEqual(5); + + // Verify at least one CommentDialog is mounted (visible near viewport) + const dialogs = superdoc.page.locator('.comment-placeholder .comments-dialog'); + await expect(dialogs.first()).toBeAttached({ timeout: 10_000 }); +}); + +test('@behavior SD-1997: typing does not flicker floating comments', async ({ superdoc }) => { + await assertDocumentApiReady(superdoc.page); + + // Create a tracked change in suggesting mode + await superdoc.setDocumentMode('suggesting'); + await superdoc.waitForStable(); + + await superdoc.type('initial tracked change'); + await superdoc.waitForStable(); + + // Wait for the floating comment to appear + const placeholders = superdoc.page.locator('.comment-placeholder'); + await expect(placeholders.first()).toBeAttached({ timeout: 10_000 }); + + const initialCount = await placeholders.count(); + expect(initialCount).toBeGreaterThanOrEqual(1); + + // Now type more text — placeholders should remain in the DOM (no flicker) + await superdoc.newLine(); + await superdoc.type('more text here'); + + // Placeholders should still be attached without disappearing + await expect(placeholders.first()).toBeAttached(); + const afterTypingCount = await placeholders.count(); + expect(afterTypingCount).toBeGreaterThanOrEqual(initialCount); + + // Verify the comment dialog content is still visible (not unmounted/remounted empty) + const dialog = superdoc.page.locator('.comment-placeholder .comments-dialog'); + await expect(dialog.first()).toBeAttached({ timeout: 5_000 }); +}); diff --git a/tests/behavior/tests/comments/tracked-change-replacement-bubble.spec.ts b/tests/behavior/tests/comments/tracked-change-replacement-bubble.spec.ts index a381624fa8..a31576c1d2 100644 --- a/tests/behavior/tests/comments/tracked-change-replacement-bubble.spec.ts +++ b/tests/behavior/tests/comments/tracked-change-replacement-bubble.spec.ts @@ -25,10 +25,10 @@ test('SD-1739 tracked change replacement does not duplicate text in bubble', asy // The floating dialog should show the tracked change with correct text // (Bug SD-1739 would show "Added: redliningg" with duplicated trailing char) - const dialog = superdoc.page.locator('.floating-comment > .comments-dialog', { + const dialog = superdoc.page.locator('.comment-placeholder .comments-dialog', { has: superdoc.page.locator('.tracked-change-text'), }); - await expect(dialog).toBeVisible({ timeout: 5_000 }); + await expect(dialog).toBeVisible({ timeout: 10_000 }); // "Added:" label with "redlining" text — must NOT contain "redliningg" const addedText = dialog.locator('.tracked-change-text').first();