From e3a3f209f971010bc3ebd36e1fd1929cc89c4dfa Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 7 May 2026 18:53:26 +0200 Subject: [PATCH] feat(chronik): render commentPreview in ChronikRow; add aria-label for screen readers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the „…" placeholder with {item.commentPreview ?? '„…"'}. Plain-text binding — no {@html} — as specified in the security note from issue #285. Adds aria-label to the wrapper for COMMENT_ADDED rows that carry a preview, giving screen reader users the full context in one announcement. Generated api.ts updated manually to include commentPreview?:string; will be regenerated by npm run generate:api once the backend is running. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/activity/ChronikRow.svelte | 14 ++---- .../lib/activity/ChronikRow.svelte.spec.ts | 43 +++++++++++++++++++ frontend/src/lib/generated/api.ts | 2 + 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/frontend/src/lib/activity/ChronikRow.svelte b/frontend/src/lib/activity/ChronikRow.svelte index a324066e..2b2cb904 100644 --- a/frontend/src/lib/activity/ChronikRow.svelte +++ b/frontend/src/lib/activity/ChronikRow.svelte @@ -108,6 +108,9 @@ const rowHref: string = $derived( @@ -159,20 +162,11 @@ const rowHref: string = $derived(

{#if variant === 'comment'} -

- „…“ + {item.commentPreview ?? '„…"'}

{/if} diff --git a/frontend/src/lib/activity/ChronikRow.svelte.spec.ts b/frontend/src/lib/activity/ChronikRow.svelte.spec.ts index c95e761b..827ce16c 100644 --- a/frontend/src/lib/activity/ChronikRow.svelte.spec.ts +++ b/frontend/src/lib/activity/ChronikRow.svelte.spec.ts @@ -186,6 +186,49 @@ describe('ChronikRow', () => { expect(link).not.toBeNull(); }); + // --- commentPreview content --- + it('renders commentPreview text when variant is comment and commentPreview is present', async () => { + const item: ActivityFeedItemDTO = { + ...baseItem, + kind: 'COMMENT_ADDED', + commentPreview: 'Hello family, great letter!' + }; + render(ChronikRow, { item }); + const preview = document.querySelector('[data-testid="chronik-comment-preview"]'); + expect(preview).not.toBeNull(); + expect(preview?.textContent).toContain('Hello family, great letter!'); + }); + + it('renders placeholder ellipsis when variant is comment and commentPreview is null', async () => { + const item: ActivityFeedItemDTO = { + ...baseItem, + kind: 'COMMENT_ADDED', + commentPreview: undefined + }; + render(ChronikRow, { item }); + const preview = document.querySelector('[data-testid="chronik-comment-preview"]'); + expect(preview).not.toBeNull(); + expect(preview?.textContent?.trim()).toBe('„…"'); + }); + + it('does not render preview paragraph for non-comment variants', async () => { + const item: ActivityFeedItemDTO = { ...baseItem, kind: 'TEXT_SAVED' }; + render(ChronikRow, { item }); + expect(document.querySelector('[data-testid="chronik-comment-preview"]')).toBeNull(); + }); + + it('link has aria-label containing preview text for comment variant with preview', async () => { + const item: ActivityFeedItemDTO = { + ...baseItem, + kind: 'COMMENT_ADDED', + commentPreview: 'A wonderful letter from grandma' + }; + render(ChronikRow, { item }); + const link = document.querySelector('a[aria-label]'); + expect(link).not.toBeNull(); + expect(link?.getAttribute('aria-label')).toContain('A wonderful letter from grandma'); + }); + // --- robustness: title rendering for edge cases --- it('still renders the row link when documentTitle is an empty string', async () => { // Felix: verbText.indexOf(docTitle) returned 0 for empty titles — the span diff --git a/frontend/src/lib/generated/api.ts b/frontend/src/lib/generated/api.ts index 32c1c2e6..a90502e4 100644 --- a/frontend/src/lib/generated/api.ts +++ b/frontend/src/lib/generated/api.ts @@ -2402,6 +2402,8 @@ export interface components { * @description Annotation associated with the comment; populated only for COMMENT_ADDED and MENTION_CREATED kinds. */ annotationId?: string; + /** @description Plain-text preview of the comment body (HTML stripped server-side, truncated to 120 chars); null for non-comment feed items or deleted comments. */ + commentPreview?: string; }; InvitePrefillDTO: { firstName: string;