fix(web): render custom HTML and Markdown content consistently#5760
Conversation
- support soft line breaks for announcement markdown without changing the default parser behavior. - add explicit markdown element styles so lists, tables, code blocks, and quotes render correctly when typography styles are unavailable. - apply the announcement markdown mode in both the popover and detail dialog for consistent display.
- remove duplicate typography utility classes now covered by explicit markdown element fallbacks. - keep the markdown renderer behavior unchanged while reducing class noise. - modernize small helper expressions to satisfy targeted lint checks.
- add shared rich content rendering so custom HTML and Markdown use the same path across public pages and announcements. - reuse common URL and HTML detection instead of duplicating content format checks per page. - keep custom home content inside the standard public layout while preserving full-page iframe rendering for external URLs.
WalkthroughShared HTML and markdown content renderers were added, and about, home, legal, announcement, and notification surfaces now use them. Markdown parsing now accepts a ChangesShared content rendering and markdown support
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
web/default/src/components/rich-content.tsx (1)
19-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRename this shared component file to PascalCase.
Since this is a reusable component under
src/components, the filename should beRichContent.tsxfor consistency with the rest of the component layer.As per coding guidelines, "Place reusable shared components in src/components/ and keep their file names in PascalCase."
🤖 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 `@web/default/src/components/rich-content.tsx` around lines 19 - 39, The shared component exported as RichContent should follow the component naming convention for reusable files. Rename the file containing RichContent to PascalCase so it matches the rest of the component layer, and make sure any imports still resolve after the rename.Source: Coding guidelines
web/default/src/components/html-content.tsx (1)
19-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRename this shared component file to PascalCase.
This new reusable component lives under
src/components, so the filename should beHtmlContent.tsxto match the repository convention.As per coding guidelines, "Place reusable shared components in src/components/ and keep their file names in PascalCase."
🤖 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 `@web/default/src/components/html-content.tsx` around lines 19 - 33, Rename the shared reusable component file for HtmlContent to PascalCase so it matches the repository convention. Keep the component implementation in HtmlContent and update any imports/usages that reference html-content if needed, ensuring the component remains under src/components and the filename is HtmlContent.tsx.Source: Coding guidelines
🤖 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 `@web/default/src/components/html-content.tsx`:
- Around line 26-31: The HtmlContent component currently injects props.content
directly via dangerouslySetInnerHTML, which can expose public pages to stored
XSS. Update HtmlContent to sanitize or safely transform the incoming HTML before
rendering, keeping the change localized to HtmlContent and its props.content
flow so untrusted backend content is not inserted verbatim into the DOM.
In `@web/default/src/components/notification-popover.tsx`:
- Line 207: The notification popover is rendering notice/announcement content
through RichContent without sanitization, which can pass unsafe HTML into
dangerouslySetInnerHTML. Update the notification-popover rendering path that
uses RichContent for notice and announcement fields to sanitize or escape the
content before rendering, or switch back to the safer Markdown rendering path if
appropriate. Make the fix in the shared notice/announcement rendering logic so
both usages are covered, and keep the change localized around the RichContent
usage in the notification-popover component.
- Around line 109-117: The relative-time logic in notification-popover should
not fall through to the month branch for 28–29 day values, because the current
condition around the week/month split lets diffMonths be 0 and renders “0 months
ago.” Update the branching in the timestamp formatting helper in
notification-popover.tsx so the week-based path stays active until 30 days (for
example by using a day threshold instead of only diffWeeks), and ensure the
month branch only handles 30+ day values.
In
`@web/default/src/features/dashboard/components/overview/announcement-detail-dialog.tsx`:
- Around line 62-74: Announcement content rendering was switched to RichContent,
which introduces unsanitized HTML injection risk; keep these fields
Markdown-only until sanitization is in place. In announcement-detail-dialog.tsx,
replace the RichContent usage for announcement.content and announcement.extra
with the Markdown renderer used elsewhere, preserving soft breaks but avoiding
dangerouslySetInnerHTML. Use the existing announcement-detail-dialog component
and its announcement.content/announcement.extra fields as the targets for the
fix.
In `@web/default/src/features/home/index.tsx`:
- Around line 57-60: The public home route is rendering API-driven HTML through
RichContent/HtmlContent without sanitization, which allows unsafe content to
reach dangerouslySetInnerHTML. Update the home page flow in index.tsx so the
content is sanitized before it is passed into RichContent, or restrict this
surface to Markdown-only content; also review HtmlContent to ensure it does not
inject raw HTML unless it has been cleaned first. Keep the fix centered around
RichContent and HtmlContent so the unsafe rendering path is removed for
unauthenticated visitors.
In `@web/default/src/lib/content-format.ts`:
- Around line 28-31: The HTML detection in isLikelyHtml is too broad and
incorrectly treats mixed markdown plus a few tags as full HTML. Narrow the regex
in isLikelyHtml so it only returns true for obvious full-document HTML patterns
(for example doctype/root/page structure tags) and does not match simple
fragments like inline formatting or embedded tags; this will let mixed content
continue through the Markdown rendering path.
---
Nitpick comments:
In `@web/default/src/components/html-content.tsx`:
- Around line 19-33: Rename the shared reusable component file for HtmlContent
to PascalCase so it matches the repository convention. Keep the component
implementation in HtmlContent and update any imports/usages that reference
html-content if needed, ensuring the component remains under src/components and
the filename is HtmlContent.tsx.
In `@web/default/src/components/rich-content.tsx`:
- Around line 19-39: The shared component exported as RichContent should follow
the component naming convention for reusable files. Rename the file containing
RichContent to PascalCase so it matches the rest of the component layer, and
make sure any imports still resolve after the rename.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5359d3dc-3398-4b16-adec-f9fe8e33c235
📒 Files selected for processing (10)
web/default/src/components/html-content.tsxweb/default/src/components/notification-popover.tsxweb/default/src/components/rich-content.tsxweb/default/src/components/ui/markdown.tsxweb/default/src/features/about/index.tsxweb/default/src/features/dashboard/components/overview/announcement-detail-dialog.tsxweb/default/src/features/home/hooks/use-home-page-content.tsweb/default/src/features/home/index.tsxweb/default/src/features/legal/legal-document.tsxweb/default/src/lib/content-format.ts
| export function HtmlContent(props: HtmlContentProps) { | ||
| return ( | ||
| <div | ||
| className={cn('prose prose-neutral dark:prose-invert max-w-none', props.className)} | ||
| dangerouslySetInnerHTML={{ __html: props.content }} | ||
| /> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Sanitize props.content before injecting it into the DOM.
This branch now renders backend-provided page content verbatim on public surfaces. A saved <script> tag or inline event handler here becomes stored XSS for every visitor who hits the HTML path.
Suggested change
+import DOMPurify from 'dompurify'
import { cn } from '`@/lib/utils`'
@@
export function HtmlContent(props: HtmlContentProps) {
+ const sanitizedContent = DOMPurify.sanitize(props.content)
+
return (
<div
className={cn('prose prose-neutral dark:prose-invert max-w-none', props.className)}
- dangerouslySetInnerHTML={{ __html: props.content }}
+ dangerouslySetInnerHTML={{ __html: sanitizedContent }}
/>
)
}As per coding guidelines, "Do not rely on client-side checks alone for auth, permissions, validation, or security-sensitive data; avoid hardcoded secrets and minimize dangerouslySetInnerHTML usage."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function HtmlContent(props: HtmlContentProps) { | |
| return ( | |
| <div | |
| className={cn('prose prose-neutral dark:prose-invert max-w-none', props.className)} | |
| dangerouslySetInnerHTML={{ __html: props.content }} | |
| /> | |
| import DOMPurify from 'dompurify' | |
| import { cn } from '`@/lib/utils`' | |
| export function HtmlContent(props: HtmlContentProps) { | |
| const sanitizedContent = DOMPurify.sanitize(props.content) | |
| return ( | |
| <div | |
| className={cn('prose prose-neutral dark:prose-invert max-w-none', props.className)} | |
| dangerouslySetInnerHTML={{ __html: sanitizedContent }} | |
| /> | |
| ) | |
| } |
🧰 Tools
🪛 ast-grep (0.44.0)
[warning] 29-29: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation
(react-unsafe-html-injection)
🤖 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 `@web/default/src/components/html-content.tsx` around lines 26 - 31, The
HtmlContent component currently injects props.content directly via
dangerouslySetInnerHTML, which can expose public pages to stored XSS. Update
HtmlContent to sanitize or safely transform the incoming HTML before rendering,
keeping the change localized to HtmlContent and its props.content flow so
untrusted backend content is not inserted verbatim into the DOM.
Sources: Coding guidelines, Linters/SAST tools
| if (diffWeeks < 4) { | ||
| return diffWeeks === 1 | ||
| ? t('1 week ago') | ||
| : t('{{count}} weeks ago', { count: diffWeeks }) | ||
| if (diffMonths < 12) | ||
| } | ||
| if (diffMonths < 12) { | ||
| return diffMonths === 1 | ||
| ? t('1 month ago') | ||
| : t('{{count}} months ago', { count: diffMonths }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
28–29 day timestamps now render as 0 months ago.
When diffDays is 28 or 29, diffWeeks < 4 is false, but diffMonths is still 0, so the month branch produces a zero-count string. Keep the week branch active until 30 days.
🐛 Proposed fix
- if (diffWeeks < 4) {
+ if (diffDays < 30) {
return diffWeeks === 1
? t('1 week ago')
: t('{{count}} weeks ago', { count: diffWeeks })
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (diffWeeks < 4) { | |
| return diffWeeks === 1 | |
| ? t('1 week ago') | |
| : t('{{count}} weeks ago', { count: diffWeeks }) | |
| if (diffMonths < 12) | |
| } | |
| if (diffMonths < 12) { | |
| return diffMonths === 1 | |
| ? t('1 month ago') | |
| : t('{{count}} months ago', { count: diffMonths }) | |
| if (diffDays < 30) { | |
| return diffWeeks === 1 | |
| ? t('1 week ago') | |
| : t('{{count}} weeks ago', { count: diffWeeks }) | |
| } | |
| if (diffMonths < 12) { | |
| return diffMonths === 1 | |
| ? t('1 month ago') | |
| : t('{{count}} months ago', { count: diffMonths }) |
🤖 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 `@web/default/src/components/notification-popover.tsx` around lines 109 - 117,
The relative-time logic in notification-popover should not fall through to the
month branch for 28–29 day values, because the current condition around the
week/month split lets diffMonths be 0 and renders “0 months ago.” Update the
branching in the timestamp formatting helper in notification-popover.tsx so the
week-based path stays active until 30 days (for example by using a day threshold
instead of only diffWeeks), and ensure the month branch only handles 30+ day
values.
| return ( | ||
| <ScrollArea className='h-[min(52vh,28rem)] pr-3'> | ||
| <Markdown>{notice}</Markdown> | ||
| <RichContent breaks content={notice} /> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Don't render notice/announcement HTML through RichContent without sanitizing it first.
These fields used to stay on the Markdown renderer. With RichContent, any tag-like payload now flows into unsanitized dangerouslySetInnerHTML, which turns notice or announcement content into a stored XSS vector inside the notification UI.
As per coding guidelines, "Do not rely on client-side checks alone for auth, permissions, validation, or security-sensitive data; avoid hardcoded secrets and minimize dangerouslySetInnerHTML usage."
Also applies to: 262-267
🤖 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 `@web/default/src/components/notification-popover.tsx` at line 207, The
notification popover is rendering notice/announcement content through
RichContent without sanitization, which can pass unsafe HTML into
dangerouslySetInnerHTML. Update the notification-popover rendering path that
uses RichContent for notice and announcement fields to sanitize or escape the
content before rendering, or switch back to the safer Markdown rendering path if
appropriate. Make the fix in the shared notice/announcement rendering logic so
both usages are covered, and keep the change localized around the RichContent
usage in the notification-popover component.
Source: Coding guidelines
| <RichContent breaks content={announcement.content} /> | ||
| </div> | ||
| )} | ||
| {announcement?.extra && ( | ||
| <div> | ||
| <h4 className='mb-2 font-medium'> | ||
| {t('Additional Information')} | ||
| </h4> | ||
| <Markdown className='text-muted-foreground'> | ||
| {announcement.extra} | ||
| </Markdown> | ||
| <RichContent | ||
| breaks | ||
| content={announcement.extra} | ||
| className='text-muted-foreground' | ||
| /> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Keep announcement fields Markdown-only until HTML is sanitized.
Switching these fields from Markdown to RichContent means any announcement string containing tags now goes through unsanitized dangerouslySetInnerHTML. The stated goal here is Markdown rendering with soft breaks, so this adds stored XSS exposure without being necessary for the feature.
As per coding guidelines, "Do not rely on client-side checks alone for auth, permissions, validation, or security-sensitive data; avoid hardcoded secrets and minimize dangerouslySetInnerHTML usage."
🤖 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
`@web/default/src/features/dashboard/components/overview/announcement-detail-dialog.tsx`
around lines 62 - 74, Announcement content rendering was switched to
RichContent, which introduces unsanitized HTML injection risk; keep these fields
Markdown-only until sanitization is in place. In announcement-detail-dialog.tsx,
replace the RichContent usage for announcement.content and announcement.extra
with the Markdown renderer used elsewhere, preserving soft breaks but avoiding
dangerouslySetInnerHTML. Use the existing announcement-detail-dialog component
and its announcement.content/announcement.extra fields as the targets for the
fix.
Source: Coding guidelines
| return ( | ||
| <PublicLayout showMainContainer={false}> | ||
| <main className='overflow-x-hidden'> | ||
| {isUrl ? ( | ||
| <iframe | ||
| src={content} | ||
| className='h-screen w-full border-none' | ||
| title={t('Custom Home Page')} | ||
| /> | ||
| ) : ( | ||
| <div className='container mx-auto py-8'> | ||
| <Markdown className='custom-home-content'>{content}</Markdown> | ||
| </div> | ||
| )} | ||
| </main> | ||
| <PublicLayout> | ||
| <div className='mx-auto max-w-6xl px-4 py-8'> | ||
| <RichContent content={content} className='custom-home-content' /> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Sanitize custom home HTML before rendering it on the public route.
RichContent sends HTML-looking strings to HtmlContent, and that component injects them with dangerouslySetInnerHTML without sanitization. Because this value comes from the home-page content API and is rendered for unauthenticated visitors, a stored <script>/event-handler payload will execute in the app origin here. Either sanitize before render or keep this surface Markdown-only.
As per coding guidelines, "Do not rely on client-side checks alone for auth, permissions, validation, or security-sensitive data; avoid hardcoded secrets and minimize dangerouslySetInnerHTML usage."
🤖 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 `@web/default/src/features/home/index.tsx` around lines 57 - 60, The public
home route is rendering API-driven HTML through RichContent/HtmlContent without
sanitization, which allows unsafe content to reach dangerouslySetInnerHTML.
Update the home page flow in index.tsx so the content is sanitized before it is
passed into RichContent, or restrict this surface to Markdown-only content; also
review HtmlContent to ensure it does not inject raw HTML unless it has been
cleaned first. Keep the fix centered around RichContent and HtmlContent so the
unsafe rendering path is removed for unauthenticated visitors.
Source: Coding guidelines
| export function isLikelyHtml(value: string): boolean { | ||
| return /<!doctype html|<html[\s>]|<head[\s>]|<body[\s>]|<style[\s>]|<script[\s>]|<\/?[a-z][\s\S]*>/i.test( | ||
| value | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Narrow the HTML detector so mixed markdown still goes through Markdown.
Lines 28-31 currently match any content that contains a single tag, so input like Intro <strong>text</strong>\n- item will bypass the markdown renderer and show the list syntax literally. This regresses the shared rendering path the PR is trying to standardize. Restrict this helper to obvious full-document HTML and let fragments fall through to the markdown path.
Suggested change
export function isLikelyHtml(value: string): boolean {
- return /<!doctype html|<html[\s>]|<head[\s>]|<body[\s>]|<style[\s>]|<script[\s>]|<\/?[a-z][\s\S]*>/i.test(
- value
- )
+ const trimmed = value.trim()
+ return /^(<!doctype html|<html[\s>]|<head[\s>]|<body[\s>])/i.test(trimmed)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function isLikelyHtml(value: string): boolean { | |
| return /<!doctype html|<html[\s>]|<head[\s>]|<body[\s>]|<style[\s>]|<script[\s>]|<\/?[a-z][\s\S]*>/i.test( | |
| value | |
| ) | |
| export function isLikelyHtml(value: string): boolean { | |
| const trimmed = value.trim() | |
| return /^(<!doctype html|<html[\s>]|<head[\s>]|<body[\s>])/i.test(trimmed) | |
| } |
🤖 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 `@web/default/src/lib/content-format.ts` around lines 28 - 31, The HTML
detection in isLikelyHtml is too broad and incorrectly treats mixed markdown
plus a few tags as full HTML. Narrow the regex in isLikelyHtml so it only
returns true for obvious full-document HTML patterns (for example
doctype/root/page structure tags) and does not match simple fragments like
inline formatting or embedded tags; this will let mixed content continue through
the Markdown rendering path.
* chore: avoid duplicate shadcn skill exposure * fix: support SMTP STARTTLS mode and NTLM auth (QuantumNous#5426) * fix: support SMTP STARTTLS mode and NTLM auth Add explicit SMTP STARTTLS configuration for 587-style connections and keep SSL/TLS as the implicit TLS mode. Prefer PLAIN when advertised, keep LOGIN compatibility, and add NTLM as a fallback for Exchange SMTP servers that require it after STARTTLS. * fix: respect explicit SMTP encryption mode * fix: preserve SMTP TLS compatibility * fix: preserve SMTP PLAIN auth TLS guard * chore(deps): bump github.com/ClickHouse/ch-go from 0.58.2 to 0.65.0 (QuantumNous#5664) Bumps [github.com/ClickHouse/ch-go](https://github.com/ClickHouse/ch-go) from 0.58.2 to 0.65.0. - [Release notes](https://github.com/ClickHouse/ch-go/releases) - [Commits](ClickHouse/ch-go@v0.58.2...v0.65.0) --- updated-dependencies: - dependency-name: github.com/ClickHouse/ch-go dependency-version: 0.65.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: update agent skills and project config - add vercel-react-best-practices skill (SKILL.md + full-guide.md) - slim CLAUDE.md to import shared AGENTS.md conventions - promote go-ntlmssp to a direct dependency in go.mod * fix: date-fns-tz classic theme build error (QuantumNous#5676) * chore(deps): update clickhouse-go and orb dependencies * feat: add system task runner (QuantumNous#5680) * feat: add system instance info panel (QuantumNous#5716) * feat: add system instance reporting * feat: show system instance resources * fix: update translations for heartbeat messages in Russian and Vietnamese * fix(web): replace default markdown renderer and expand syntax support (QuantumNous#5689) * fix(markdown): render default markdown with marked - switch default frontend markdown rendering from react-markdown/remark-gfm to marked to avoid old WebKit parse failures from lookbehind regex literals - sanitize marked HTML output with DOMPurify and preserve external link target and rel behavior - remove default direct dependencies on react-markdown, remark-gfm, and rehype-raw while leaving classic unchanged * fix(markdown): expand default markdown rendering support - render default markdown with marked extensions for KaTeX formulas, page breaks, and common emoji shortcodes. - sanitize KaTeX output with an explicit DOMPurify allowlist while preserving external link behavior. - avoid overriding marked text rendering so lists and inline parsing keep their internal parser context. * fix(markdown): render diagram code blocks in default UI - add sanitized SVG rendering for flow and sequence diagram code blocks. - size flow nodes from their labels and route edges from node anchors to prevent clipping. - style diagram nodes, arrows, labels, and notes with theme-aware classes. * fix(web): sync channel card selection state (QuantumNous#5700) * fix(web): hide wallet entry in profile dropdown when wallet module disabled (QuantumNous#5708) The profile dropdown rendered the wallet item unconditionally, so it still showed after an admin disabled the personal/topup (wallet) sidebar module. Reuse the sidebar module visibility check so the dropdown honours the same toggle as the sidebar. Fixes QuantumNous#5696 * feat(system-settings): add user token limit configuration section (QuantumNous#5678) * feat: add channel async polling delay toggle Fixes QuantumNous#5717 Fixes QuantumNous#4244 * fix: add token limit save label translations * feat: enhance i18n-translate skill * feat: add date-fns and date-fns-tz dependencies * feat: add date-fns and date-fns-tz paths to build configuration * chore(deps): bump dompurify from 3.4.5 to 3.4.11 in /web/default (QuantumNous#5718) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.5 to 3.4.11. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.4.5...3.4.11) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.11 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(ci): install classic workspace dependencies for releases (QuantumNous#5719) * fix: use neutral drawing task labels * perf(web): streamline table actions and destructive dialogs (QuantumNous#5645) * perf(data-table): autosize action columns - exclude actions columns from shared table width calculations so action cells size to their content. - remove fixed size and w-* width overrides from feature action columns to preserve content-based layout. * perf(data-table): streamline row action controls - expose common edit and status actions directly while moving secondary actions into overflow menus. - add shared row action menu helpers so static and table rows use consistent action controls. - let action columns size to their content instead of relying on fixed widths. * fix(web): localize destructive dialog copy - route delete, reset, and batch update confirmation text through i18n. - add locale entries for affected channel, model, system settings, and user dialogs. * perf(web): unify destructive dialog actions - align delete and cleanup confirmation buttons with the shared destructive variant. - replace custom destructive color overrides with semantic button variants. - clean up lint errors in touched dialog files before committing. * fix(web): add user action success translations - add localized success messages for user delete, status, and role changes. - keep user management toast copy available across all frontend locales. * fix(data-table): prevent mobile badge clipping - expose badge cell slots so mobile card styles can target nested badge wrappers. - reset badge margins in card rows to keep provider icons fully visible on small screens. * fix: add Waffo goods info and webhook SDK update (QuantumNous#5704) * fix: add Waffo goods info and webhook SDK update * chore: remove Waffo test code from PR * fix(model-pricing): refresh tiered expression editor when switching models (QuantumNous#5752) Switching models in the pricing editor kept the previous model's tiers and prices in the expression panel: TieredPricingEditor seeds its internal visual/raw state only on mount, and the initRef guard never re-ran on prop changes, so only the model name updated. Bump a reload token in the same effect that seeds billingExpr and use it as the editor's key, so a freshly loaded model remounts the editor and re-parses its expression. The token changes in lockstep with billingExpr, and user edits (which only touch state) do not trigger it. Closes QuantumNous#5750 * chore(deps): sync bun.lock for dompurify 3.4.11 (QuantumNous#5738) * fix(theme): 切换前端主题后重置到首页,避免路由 404 (QuantumNous#5612) * fix(theme): 切换前端主题后重置到首页,避免路由 404 经典前端与新版前端的路由路径不同,切换主题后停留在原路径会导致 404: - 经典前端切换到新版前端时跳转首页,不再原地刷新当前路径 - 新版前端保存时若前端主题发生变化,保存成功后跳转首页 Fixes QuantumNous#4947 * fix: 更新前端切换提示信息,修正页面跳转逻辑 * fix(task): attribute async task usage log to the initiating node (QuantumNous#5684) Async task usage logs (LogQuotaData node dimension) were recorded under whichever node happened to poll the task to completion, not the node that submitted it. For token/adaptor-billed video tasks the pre-deduction is often 0, so the entire quota landed on the last polling node. Snapshot common.NodeName into TaskPrivateData at submit time and use it when writing the settlement consume log; fall back to the current node when empty so existing tasks stay compatible. * chore: update i18n skill * feat: better admin permissions (QuantumNous#5755) * feat: add casbin admin permissions * feat: improve audit logging to associate logs with actual operators and target users * feat: enhance admin permissions and UI interactions for sensitive actions * Refactor authz RBAC and tighten channel permissions * Split channel authz field policy * Address channel authz review findings * fix: adapt ClickHouse log LIKE filters * feat(playground): improve Playground chat experience and Markdown rendering (QuantumNous#5217) * refactor(playground): streamline chat request state - extract conversation actions from the page component to keep message flow logic reusable. - unify streaming and non-streaming generation state, including abort support for non-stream requests. - simplify message rendering and payload construction while localizing Playground prompts. * fix(playground): validate persisted chat state - wrap saved Playground state with a storage version while still reading legacy values. - validate config, parameter toggles, and messages before restoring them from localStorage. - cap stored chat history to the latest messages to avoid oversized or stale state. * refactor(playground): centralize message content access - route chat rendering, copy actions, and error display through shared message helpers. - reuse the current-version update helper for non-streaming assistant responses. - keep message version details behind utility functions to reduce future model churn. * refactor(playground): split storage schemas - move Playground storage validation schemas into a dedicated module. - keep storage read and write logic focused on migration, trimming, and persistence. - preserve the existing storage envelope and validation behavior. * refactor(playground): extract options loading hook - move model and group queries into a dedicated hook so the page component stays focused on layout wiring. - preserve existing fallback selection and error toast behavior while reusing the hook through the playground barrel export. * refactor(playground): extract prompt suggestions - move static prompt suggestion rendering into a focused component so the input stays centered on compose controls. - preserve translated suggestion submission behavior while isolating icon metadata from the input form. * refactor(playground): extract input tools - move attachment and search controls into a dedicated component so the prompt input stays focused on compose state. - keep existing development toast behavior and disabled handling while centralizing tool metadata. * refactor(playground): extract input controls - move model, group, send, and stop controls into a focused component so the input only manages compose state. - preserve existing disabled states and generation button behavior while isolating control rendering. * refactor(playground): extract message content display - move sources, reasoning, loading, error, and response rendering into a dedicated message content component. - keep the chat list focused on message iteration, edit state, and action wiring without changing display behavior. * refactor(playground): extract message editor - move inline message editing controls into a dedicated editor component so the chat list stays focused on rendering flow. - preserve save, save-and-submit, cancel, and disabled-state behavior for edited messages. * refactor(playground): extract stream error parsing - move SSE error payload parsing into a reusable stream utility so the request hook stays focused on lifecycle handling. - preserve existing error message, error code, and fallback behavior for raw or empty stream errors. * refactor(playground): extract request error parsing - move non-stream request error extraction into a shared utility so the chat handler stays focused on request flow. - preserve the existing response message, error code, and fallback priority for failed chat completions. * refactor(playground): extract streaming chunk updates - move reasoning and content chunk application into a message utility so the chat handler only wires stream events. - preserve error-state skipping, reasoning accumulation, and content streaming behavior for assistant messages. * refactor(playground): extract message reasoning parser - move think tag parsing into a dedicated playground message utility. - export the parser through the shared playground lib barrel for consistent imports. * refactor(playground): extract message streaming utilities - move stream chunk application and message finalization into a dedicated utility. - keep stored message sanitization with the streaming lifecycle helpers. * refactor(playground): extract message update utilities - move assistant message update helpers into a focused playground utility. - keep error-state message updates separate from core message construction helpers. * refactor(playground): extract completion choice handling - move non-streaming choice application into the message streaming utilities. - keep the chat handler focused on request orchestration and message updates. * refactor(playground): centralize assistant completion state - add a helper for finalizing assistant messages with complete status. - reuse the helper in stream completion and stop-generation paths. * refactor(playground): extract stream message parsing - move SSE delta parsing into a shared stream utility. - keep the stream request hook focused on lifecycle handling and update dispatch. * refactor(playground): extract stream ready state checks - move SSE ready-state status handling into stream utilities. - keep weak source status typing outside the stream request hook. * refactor(playground): extract conversation message helpers - move send, regenerate, and edit message list construction into focused utilities. - keep the conversation hook focused on edit state and update dispatch. * refactor(playground): extract state initialization helpers - move playground initial state loading into focused utility helpers. - centralize message state updater resolution outside the React state hook. * refactor(playground): extract option fallback helpers - move model and group fallback selection into focused playground utilities. - keep the options hook focused on query results, toasts, and config updates. * refactor(playground): extract message action helpers - move message action state derivation into focused utilities. - keep the action component focused on guarded handlers and rendering. * refactor(playground): extract input control state - move submit, stop, and selector state derivation into a pure helper. - keep input controls focused on rendering model selectors and action buttons. * refactor(playground): extract message content state - move source, reasoning, loader, and body visibility checks into a pure helper. - use a discriminated state shape so rendered reasoning content stays type-safe. * refactor(playground): extract message editor state - move save eligibility and submit visibility checks into a pure helper. - keep the editor component focused on textarea and button rendering. * refactor(playground): extract message error state - move error kind, fallback content, and admin visibility checks into a pure helper. - centralize the model pricing settings path used by the error action. * refactor(playground): extract chat render state - move editing content lookup and per-message render flags into conversation helpers. - keep the chat component focused on mapping messages to editor and content views. * refactor(playground): extract suggestion display state - move suggestion class selection into a pure helper. - keep the suggestions component focused on translation and rendering. * refactor(playground): extract assistant message state checks - move final and pending assistant status checks into streaming utilities. - keep the chat handler focused on request lifecycle updates. * refactor(playground): extract input tool state - move attachment action metadata and development notices into input tool utilities. - keep the input tools component focused on menu and button rendering. * refactor(playground): extract stream protocol checks - move SSE done-message and closed-ready-state checks into stream utilities. - keep the stream request hook focused on event handling flow. * refactor(playground): extract message removal helper - move delete-message filtering into conversation message utilities. - keep the conversation hook focused on action orchestration. * refactor(playground): extract option error messages - move option load error message selection into playground option utilities - keep the options hook focused on query effects and fallback updates * refactor(playground): extract input submit text helper - move prompt submit text validation into input control utilities - let the input component submit only when a concrete text value is available * refactor(playground): centralize error message checks - add a shared helper for identifying error messages - remove direct status string checks from message content rendering * refactor(playground): extract message content display checks - move loader and content visibility decisions into local helper functions - keep message content state assembly focused on composing render state * refactor(playground): replace raw message role checks - use shared message role constants in conversation edit handling - avoid raw assistant role literals when validating API messages * refactor(playground): extract non-stream response handling - move chat completion response choice handling into message streaming utilities - keep the chat handler focused on request lifecycle and error routing * refactor(playground): centralize stream cleanup - reuse one stream cleanup path for completion, errors, startup failures, and manual stops - preserve the current-source guard when closing SSE streams * refactor(playground): extract pending assistant check - centralize pending assistant message detection in streaming utilities - reuse the helper when sanitizing stored playground messages * perf(playground): improve mobile input controls - split mobile input controls into selector and action rows - keep the desktop input footer compact while reducing mobile control crowding * perf(playground): add starter empty state - show starter prompts in the empty playground chat area - wire empty-state prompt selection into the existing send flow - add localized copy for the new empty state * perf(playground): improve mobile message actions - collapse mobile message actions into a touch-friendly dropdown menu - keep the desktop hover action strip unchanged for pointer workflows - share one action list between desktop buttons and the mobile menu * perf(playground): add error recovery actions - show retry, edit, and delete actions inside error message alerts - route edit recovery to the previous user prompt when available - keep recovery controls touch-friendly on mobile layouts * perf(playground): refine message editing experience - present message edits in a focused bordered editor panel - add unsaved-change state, reset, and cancel confirmation flows - improve mobile touch targets and keyboard shortcuts for editing * perf(playground): improve markdown code blocks - render fenced markdown code with syntax highlighting, line numbers, and fallback plain text - add copy, download, and collapse controls for playground AI responses - tighten code block layout and theme token styles for responsive markdown rendering * fix(playground): constrain markdown code block height - collapse long playground code blocks after a short preview instead of waiting for very large snippets - cap expanded code blocks so long responses scroll inside the code block - keep generic code block usage unconstrained unless a caller opts in * feat(playground): add chat history clearing - add a toolbar action that is enabled only when saved playground messages exist. - confirm destructive clears before removing browser-stored conversation state. - add localized strings for the action, dialog, and completion toast. * perf(playground): improve chat markdown rendering - refine assistant and user message surfaces so chat content matches the app UI. - normalize markdown typography, tables, images, lists, blockquotes, and details rendering. - add indentation cues for collapsible reasoning and source sections. * style: format code block component * style: format playground frontend files * feat(playground): render markdown with stream parser - replace Streamdown with stream-markdown-parser for project-owned markdown rendering and styling. - split response rendering into focused block, inline, table, alert, details, and footnote modules. - pass message final state into response parsing so streaming content can be parsed incrementally. * fix(playground): localize reasoning and chat feedback - translate reasoning status, message actions, playground errors, and response renderer fallbacks across supported locales. - keep reasoning duration numeric and tighten the collapsible layout to prevent trigger jitter. - register dynamic keys so i18n sync keeps runtime labels covered. * refactor(playground): group files by functional area - move chat, input, and message components into focused subdirectories to make the UI structure easier to scan. - split playground helpers into input, message, streaming, storage, options, state, and suggestions modules. - update barrel exports and imports so existing feature entry points continue to work. * fix(playground): prevent history replay from freezing page - defer saved conversation loading so route entry no longer blocks on localStorage parsing and markdown rendering. - limit initial history rendering and skip expensive markdown parsing for oversized responses. - normalize corrupted streaming snapshots and cumulative chunks to keep saved playground history bounded. - add message timing metadata and layout alignment groundwork without introducing live timers. * feat(playground): allow regenerating from user messages - show regenerate actions on user messages with saved content. - truncate following conversation state before starting a fresh assistant response. * feat(playground): add raw response source view - add a per-message source toggle for assistant responses. - render raw response content with the existing code block viewer. - localize the new source and preview action labels. * feat(playground): render code with unified editor - replace Shiki HTML rendering with a read-only CodeMirror view for code blocks and raw responses. - reuse the same CodeMirror frame for message editing so source and edit modes stay visually aligned. - add lightweight CodeMirror dependencies while keeping language support scoped to Markdown. * perf(playground): streamline chat input controls - combine model and group selection into one compact picker for faster context switching. - switch playground action buttons to icon-first controls with tooltips to reduce toolbar width. - refresh input footer styling and submit states so active and destructive actions are clearer. - bump dompurify lockfile entry to keep the frontend dependency current. * fix(playground): filter models by selected group - query user models by the selected playground group instead of reusing the cross-group model union. - clear unavailable model selections and block sending when the active group has no models. - align model selector and error action controls with the existing playground interaction style. * perf(playground): remove input suggestion chips - remove the prompt suggestion row below the playground input to reduce visual noise. - delete the now-unused suggestion component and display helper. * perf(playground): stabilize reasoning trigger layout - use fixed icon slots around the reasoning label so the left content stays still when toggling. - limit the open state animation to the chevron rotation for a smoother collapse interaction. * perf(playground): smooth reasoning expansion - use the collapsible panel height animation for vertical reasoning reveals. - sync inner content opacity and position with the panel state. * fix(auth): align password validation copy (QuantumNous#5759) * fix(i18n): add missing frontend translations - add missing locale entries for API key loading, channel model empty states, auth, playground, and model configuration copy. - correct inaccurate Russian and Vietnamese model empty-state translations to avoid fallback or misleading copy. * fix(auth): align password validation copy - remove the login password length gate so existing shorter passwords are not blocked before reaching the server. - reuse distinct minimum-length and 8-20 character messages based on the actual validation rule. - drop unused duplicate password locale keys and align the user creation placeholder with the 8-20 character constraint. * fix(i18n): add auth validation message translations - cover schema-driven auth form errors that are translated through FormMessage. - keep password, username, confirmation, and OTP validation messages available in every locale. * fix(web): render custom HTML and Markdown content consistently (QuantumNous#5760) * fix(markdown): render announcement markdown consistently - support soft line breaks for announcement markdown without changing the default parser behavior. - add explicit markdown element styles so lists, tables, code blocks, and quotes render correctly when typography styles are unavailable. - apply the announcement markdown mode in both the popover and detail dialog for consistent display. * refactor(markdown): simplify fallback markdown styles - remove duplicate typography utility classes now covered by explicit markdown element fallbacks. - keep the markdown renderer behavior unchanged while reducing class noise. - modernize small helper expressions to satisfy targeted lint checks. * fix(content): render custom HTML consistently - add shared rich content rendering so custom HTML and Markdown use the same path across public pages and announcements. - reuse common URL and HTML detection instead of duplicating content format checks per page. - keep custom home content inside the standard public layout while preserving full-page iframe rendering for external URLs. * fix(security): pin patched frontend transitive dependencies * fix(web): secure rich content rendering * fix(web): harden iframe sandboxing --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: CaIon <i@caion.me> Co-authored-by: Benson Yan <fuxin04@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Seefs <40468931+seefs001@users.noreply.github.com> Co-authored-by: QuentinHsu <xuquentinyang@gmail.com> Co-authored-by: yyhhyyyyyy <yyhhyyyyyy8@gmail.com> Co-authored-by: feitianbubu <feitianbubu@qq.com> Co-authored-by: RedwindA <128586631+RedwindA@users.noreply.github.com> Co-authored-by: zhongyuanzhao-alt <zhongyuan.zhao@waffo.com> Co-authored-by: peakchao <zhangzhichaolove@vip.qq.com>
The custom home page URL iframe was given a sandbox attribute in QuantumNous#5760 with an incomplete token set, causing two regressions for the embedded page (both absent before rc.16): 1. Missing allow-same-origin forced the embedded document into an opaque (null) origin, so its own same-origin @font-face requests were treated as cross-origin and blocked by CORS, falling back to system fonts. 2. Missing allow-top-navigation-by-user-activation blocked the embedded page's target="_top" links from navigating the top-level window on user click (nav/menu links did nothing on desktop). Since the home page URL is admin-configured (trusted), add both tokens to restore same-origin resource loading and user-initiated top navigation. Closes QuantumNous#5907
Important
📝 变更描述 / Description
(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
问题描述
修复方式
测试说明
bun run typecheck。🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
(请在此粘贴截图、关键日志或测试报告,以证明变更生效)


Summary by CodeRabbit
New Features
Bug Fixes