[CSM Portal] unify rich-text comment/description rendering with customer portal - #1210
Conversation
…mer portal Port dark-mode inline-style neutralization, link hardening, bare-URL auto-linking, image click-to-zoom/a11y, raw-comment preprocessing, empty-bubble suppression, and a dual sanitize policy (permissive for comments, stripped for descriptions) from customer-portal into CSM. Adds a Description card to the case detail page.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
🚧 Files skipped from review as they are similar to previous changes (9)
📝 WalkthroughWalkthroughChangesCase comment rendering now processes legacy wrappers, chatbot markdown, links, images, tables, and theme-specific styles with sanitization and accessibility handling. Case descriptions use dedicated sanitized HTML. Inline comment images open in a fullscreen dialog, with utility and component tests covering the new behavior. CSM case rendering
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CsmCaseCommentBubble
participant CaseActivitiesFeed
participant ImageFullscreenModal
CsmCaseCommentBubble->>CaseActivitiesFeed: Report clicked image source
CaseActivitiesFeed->>ImageFullscreenModal: Open with image source
ImageFullscreenModal->>CaseActivitiesFeed: Invoke close callback
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/csm-portal/webapp/src/features/csm-cases/components/CaseActivitiesFeed.tsx (1)
262-269: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
onImageClickwiring only carriessrc— see consolidated comment.
setFullscreenImageSrcis wired directly asonImageClick, so only the image URL reaches the fullscreen modal state; there's no channel for the original image'salttext. Addressed in the consolidated comment covering this file,CsmCaseCommentBubble.tsx, andImageFullscreenModal.tsx.🤖 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 `@apps/csm-portal/webapp/src/features/csm-cases/components/CaseActivitiesFeed.tsx` around lines 262 - 269, Update the comment activity rendering in CaseActivitiesFeed and the related CsmCaseCommentBubble/ImageFullscreenModal flow so onImageClick propagates both the image src and its original alt text, rather than passing setFullscreenImageSrc directly with only the URL. Update the fullscreen modal state and props consistently while preserving existing image-click behavior.apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.tsx (2)
331-342: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRaw-table scroll styling also matches tables already wrapped by
.md-table-wrap.
"& table"is a universal descendant selector, so it also applies to<table>elements nested inside.md-table-wrap(markdown-rendered tables), in addition to the wrapper's ownoverflowX/maxWidth. This produces a redundant nested scroll container for markdown tables, contradicting the comment's stated intent to only target "raw (non-markdown)" tables.♻️ Proposed fix
- "& table": { + "& table:not(.md-table-wrap table)": { display: "block", overflowX: "auto", maxWidth: "100%", width: "max-content", minWidth: "100%", borderCollapse: "collapse", },🤖 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 `@apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.tsx` around lines 331 - 342, Restrict the raw-table styles in the markdown content styling block to tables that are not descendants of .md-table-wrap, so markdown-rendered tables retain only the wrapper’s scrolling behavior. Update the "& table" selector near the existing "& .md-table-wrap" rule while preserving the raw table sizing and overflow properties.
1-1: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFullscreen image preview loses the original
alttext end-to-end. The image's accessible name is never captured at the click site, so the fullscreen dialog always falls back to a generic "Full size" label — undermining the PR's stated accessibility goal for image click-to-zoom.
apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.tsx#L134-146: capturetarget.alt || target.getAttribute("alt")inhandleClick/handleKeyDownand widenonImageClickto(src: string, alt?: string) => void.apps/csm-portal/webapp/src/features/csm-cases/components/CaseActivitiesFeed.tsx#L262-269: replace the singlefullscreenImageSrcstring state with a{ src, alt } | nullshape (or a secondfullscreenImageAltstate) and forward both fromonImageClick.apps/csm-portal/webapp/src/features/csm-cases/components/ImageFullscreenModal.tsx#L79-93: add analt?: stringprop and render it (falling back to a sensible default like"Image preview"only when absent) instead of the hardcoded"Full size".🤖 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 `@apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.tsx` at line 1, The fullscreen image preview drops the source image’s accessible alt text across the click, state, and modal flow. Update CsmCaseCommentBubble’s handleClick/handleKeyDown and onImageClick signature to capture and pass target.alt or target.getAttribute("alt"), store both src and alt in CaseActivitiesFeed’s fullscreen state, and add an optional alt prop to ImageFullscreenModal that uses it with “Image preview” only as the fallback instead of hardcoded “Full size”.
🧹 Nitpick comments (1)
apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.test.tsx (1)
78-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a keyboard-activation test for the image preview.
Coverage exists for mouse click on the image-preview button, but not for
Enter/Spacekeyboard activation, which is the new a11y behaviorhandleKeyDownimplements.it("invokes onImageClick on Enter keypress", () => { const onImageClick = vi.fn(); render( <CsmCaseCommentBubble comment={makeComment({ bodyHtml: '<img src="/abc123.iix" alt="a" />' })} onImageClick={onImageClick} />, ); const img = screen.getByRole("button", { name: "Open image preview" }); fireEvent.keyDown(img, { key: "Enter" }); expect(onImageClick).toHaveBeenCalledWith(expect.stringContaining("abc123.iix")); });🤖 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 `@apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.test.tsx` around lines 78 - 97, Add keyboard-activation coverage alongside the existing mouse-click test for the image-preview button in CsmCaseCommentBubble. Render the unresolved attachment image, dispatch keydown events for Enter and Space against the accessible button, and assert onImageClick receives the attachment URL for each activation.
🤖 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
`@apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.tsx`:
- Around line 134-146: The handleClick callback in CsmCaseCommentBubble must
preserve the clicked image’s alt text when invoking onImageClick. Update the
callback and its handler contract to forward both the image source and its alt
attribute, while retaining the existing preventDefault and image validation
behavior.
- Around line 117-123: Update setImageA11yAttributes and its callers so image
accessibility attributes are applied only when onImageClick is provided.
Preserve the existing interactive image behavior when the callback exists, while
leaving images non-focusable and without button semantics when it is absent.
In
`@apps/csm-portal/webapp/src/features/csm-cases/components/ImageFullscreenModal.tsx`:
- Around line 79-93: Update ImageFullscreenModal and its callers, including
CsmCaseCommentBubble and CaseActivitiesFeed, to accept and forward the original
image alt text into the fullscreen preview. Replace the hardcoded “Full size”
value on the image element with the propagated alt prop, preserving a suitable
fallback only when no alt text exists.
In `@apps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsx`:
- Around line 1520-1529: Update the description rendering in CsmCaseDetailPage
to apply the same useDarkMode and stripLightModeInlineStyles cleanup used by the
comment pipeline after sanitizeDescriptionHtml and before
dangerouslySetInnerHTML injection. Preserve existing sanitization and add a
regression test covering light inline styles in dark mode.
In `@apps/csm-portal/webapp/src/features/csm-cases/utils/commentContent.ts`:
- Around line 122-129: Update hasDisplayableContent to follow the same
code-block preprocessing branch as CsmCaseCommentBubble’s preprocessed memo,
using stripAllCodeBlocks and convertCodeTagsToHtml for multi-[code] content
while preserving the single-wrapper path. Perform label stripping and
displayability checks on the resulting rendered-equivalent content so empty or
label-only multiple code blocks return false.
- Around line 135-140: Update linkifyBareUrls so URL text inside existing anchor
elements is excluded from replacement, preventing nested <a> elements while
preserving linkification of standalone bare URLs. Adjust the matching logic or
processing flow around linkifyBareUrls and update the related test to assert
that existing anchor labels remain unchanged.
In `@apps/csm-portal/webapp/src/utils/sanitizeHtml.ts`:
- Around line 105-108: Update isDarkColor so its named dark-color regex no
longer classifies every `#1xxxxx` or `#2xxxxx` value as dark; retain only the
explicit black forms and let the existing per-channel hex parsing determine
whether other hex colors are dark.
- Around line 69-92: Update stripLightModeInlineStyles to match both
double-quoted and single-quoted style attributes, preserving the matched quote
when rebuilding the cleaned attribute. Add a regression test covering a
single-quoted light-mode style such as background:`#fff` and verify it is removed.
---
Outside diff comments:
In
`@apps/csm-portal/webapp/src/features/csm-cases/components/CaseActivitiesFeed.tsx`:
- Around line 262-269: Update the comment activity rendering in
CaseActivitiesFeed and the related CsmCaseCommentBubble/ImageFullscreenModal
flow so onImageClick propagates both the image src and its original alt text,
rather than passing setFullscreenImageSrc directly with only the URL. Update the
fullscreen modal state and props consistently while preserving existing
image-click behavior.
In
`@apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.tsx`:
- Around line 331-342: Restrict the raw-table styles in the markdown content
styling block to tables that are not descendants of .md-table-wrap, so
markdown-rendered tables retain only the wrapper’s scrolling behavior. Update
the "& table" selector near the existing "& .md-table-wrap" rule while
preserving the raw table sizing and overflow properties.
- Line 1: The fullscreen image preview drops the source image’s accessible alt
text across the click, state, and modal flow. Update CsmCaseCommentBubble’s
handleClick/handleKeyDown and onImageClick signature to capture and pass
target.alt or target.getAttribute("alt"), store both src and alt in
CaseActivitiesFeed’s fullscreen state, and add an optional alt prop to
ImageFullscreenModal that uses it with “Image preview” only as the fallback
instead of hardcoded “Full size”.
---
Nitpick comments:
In
`@apps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.test.tsx`:
- Around line 78-97: Add keyboard-activation coverage alongside the existing
mouse-click test for the image-preview button in CsmCaseCommentBubble. Render
the unresolved attachment image, dispatch keydown events for Enter and Space
against the accessible button, and assert onImageClick receives the attachment
URL for each activation.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b9542777-608c-4374-9e41-0818f497a10f
📒 Files selected for processing (10)
apps/csm-portal/webapp/src/features/csm-cases/components/CaseActivitiesFeed.tsxapps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.test.tsxapps/csm-portal/webapp/src/features/csm-cases/components/CsmCaseCommentBubble.tsxapps/csm-portal/webapp/src/features/csm-cases/components/ImageFullscreenModal.tsxapps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsxapps/csm-portal/webapp/src/features/csm-cases/utils/commentContent.test.tsapps/csm-portal/webapp/src/features/csm-cases/utils/commentContent.tsapps/csm-portal/webapp/src/utils/sanitizeHtml.test.tsapps/csm-portal/webapp/src/utils/sanitizeHtml.tsapps/csm-portal/webapp/src/utils/useDarkMode.ts
- gate image a11y attrs (tabindex/role/aria-label) on onImageClick presence so a non-interactive comment doesn't announce fake buttons - forward the original <img alt> through the click-to-zoom chain into ImageFullscreenModal instead of a hardcoded "Full size" - apply useDarkMode/stripLightModeInlineStyles to the case description card, matching the comment pipeline's dark-mode cleanup - hasDisplayableContent now mirrors the multi-[code]-block branching used for rendering, so label-only multi-block comments are suppressed too - fix linkifyBareUrls to avoid nested <a> tags around a URL that's already an anchor's visible text (using an atomic-group-emulated lookahead — CodeRabbit's own suggested trailing lookahead truncates the URL under backtracking, verified against a plain node repro) - stripLightModeInlineStyles now matches single- and double-quoted style attributes - isDarkColor no longer treats every #1xxxxx/#2xxxxx hex color as dark; relies on the existing per-channel luminance parsing instead
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
Purpose
CSM portal's rich-text rendering of case comments and descriptions (ServiceNow-authored HTML) lagged behind the customer portal's, most visibly: inline
color/background-colorstyles authored for a light page (e.g.color:#000000, light-yellow highlights) rendered illegibly in CSM's dark theme, since nothing neutralized them. A broader comparison against the customer portal's comment-rendering pipeline surfaced several more gaps in the same area.Goals
Bring CSM portal's comment/description rendering to parity with the customer portal's existing, working implementation:
rel="noopener noreferrer"ontarget="_blank") and auto-link bare URLs typed into comments.[code]wrapper tags, stray leading<br>, a redundant "Customer comment added" label).Approach
Ported the relevant utilities/hooks from the customer portal's existing implementation (
useDarkMode,stripLightModeInlineStyles, code-wrapper/label-stripping helpers, bare-URL linkifier, empty-content check) into CSM-portal-local equivalents, adapted to CSM'sCsmCaseCommentshape and dark-mode signal (CSM already uses the samedata-color-schemeattribute the customer portal observes). Wired the full pipeline intoCsmCaseCommentBubble.tsxin the same order the customer portal already uses (code-wrapper handling → trim → label-strip → dark-mode strip → sanitize → resolve inline images → linkify), added a smallImageFullscreenModalfor the zoom feature, and added a new "Description" card to the case detail page using a newsanitizeDescriptionHtmlpolicy.CSM's internal-only "work note" concept (
comment.internal, tinted background + "Internal note" chip) — which the customer portal has no equivalent of — is untouched; none of the new pipeline steps are gated on it.Verified against the real ServiceNow DEV tenant via the full local stack (webapp + BFF + Go entity-service + local Ballerina), confirming dark-mode comment rendering, image zoom, and the new description card render correctly against live case data.
User stories
As a CS engineer working a case in dark mode, I can read comment text and highlights regardless of what color the original ServiceNow rich-text editor applied, click an inline image to view it full-size, and see the case description as formatted rich text instead of only in a dialog default.
Release note
Fixed unreadable comment text/highlights in CSM portal dark mode; added image zoom, bare-URL auto-linking, and a rich-text case description card; cleaned up minor comment-formatting artifacts.
Documentation
N/A — internal CSM-portal-only UI behavior, no user-facing docs to update.
Training
N/A — no training content affected.
Certification
N/A — no certification exam impact.
Marketing
N/A — internal tooling change, not customer/marketing-facing.
Automation tests
Security checks
pnpm lint/eslintclean insteadSamples
N/A — no new sample apps/snippets.
Related PRs
None.
Migrations (if applicable)
N/A — no schema/data migrations.
Test environment
Node/pnpm per
apps/csm-portal/webapp/package.jsonengines; tested in a Chromium-based browser (local dev) against the full local CSM stack (webapp, BFF, Go entity-service, local Ballerina) pointed at the real ServiceNow DEV tenant.Learning
Compared the customer portal's existing
CommentBubble.tsx/ChatMessageCard.tsx/support.tsrich-text pipeline against CSM portal'sCsmCaseCommentBubble.tsxto find the working pattern to port, rather than designing a new one from scratch.Summary by CodeRabbit
target="_blank"withrel="noopener noreferrer".