Compare commits

..
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 f30a65eb74 fix(status): banner Details strips Markdown syntax, not every #, _, >, * (#308)
CI / Build & Quality Checks (pull_request) Successful in 3m20s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 13s
CI / Playwright smoke (e2e) (pull_request) Successful in 17m3s
plain() deleted those characters everywhere, so "join #lotus-general"
lost its #, snake_case and @some_user lost underscores, "->" became
"-", "5 * 3" became "5  3", and "<b>" became "<b". Strip only syntax:
heading / quote / list markers at line start, paired **x** *x* _x_ `x`,
and HTML tags.

Fixes #308

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-17 20:22:12 -04:00
10 changed files with 51 additions and 25 deletions
+1 -1
View File
@@ -297,7 +297,7 @@ export function AccountDataEditor({
</Chip>
</Box>
<Box shrink="No">
<IconButton type="button" onClick={requestClose} variant="Surface" aria-label="Close">
<IconButton onClick={requestClose} variant="Surface" aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Box>
@@ -315,7 +315,7 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
Setup Device Verification
</Text>
</Box>
<IconButton type="button" size="300" radii="300" onClick={onCancel} aria-label="Cancel">
<IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@@ -361,7 +361,7 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
Reset Device Verification
</Text>
</Box>
<IconButton type="button" size="300" radii="300" onClick={onCancel} aria-label="Cancel">
<IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@@ -275,7 +275,7 @@ export function StateEventEditor({ type, stateKey, requestClose }: StateEventEdi
</Chip>
</Box>
<Box shrink="No">
<IconButton type="button" onClick={requestClose} variant="Surface" aria-label="Close">
<IconButton onClick={requestClose} variant="Surface" aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Box>
+1 -1
View File
@@ -143,7 +143,7 @@ export function PollCreator({ roomId, threadRootId, onClose }: PollCreatorProps)
Create Poll
</Text>
</Box>
<IconButton type="button" size="300" radii="300" onClick={onClose} aria-label="Close">
<IconButton size="300" radii="300" onClick={onClose} aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
+1 -1
View File
@@ -118,7 +118,7 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
Report Room
</Text>
</Box>
<IconButton type="button" size="300" onClick={onClose} radii="300" aria-label="Close">
<IconButton size="300" onClick={onClose} radii="300" aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
+1 -1
View File
@@ -124,7 +124,7 @@ export function ReportUserModal({ userId, onClose }: ReportUserModalProps) {
Report User
</Text>
</Box>
<IconButton type="button" size="300" onClick={onClose} radii="300" aria-label="Close">
<IconButton size="300" onClick={onClose} radii="300" aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@@ -202,7 +202,7 @@ export function ScheduleMessageModal({
{title}
</Text>
</Box>
<IconButton type="button" size="300" radii="300" onClick={onClose} aria-label="Close">
<IconButton size="300" radii="300" onClick={onClose} aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
+2 -14
View File
@@ -725,13 +725,7 @@ export const MessageDeleteItem = as<
Delete Message
</Text>
</Box>
<IconButton
type="button"
size="300"
onClick={handleClose}
radii="300"
aria-label="Close"
>
<IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@@ -871,13 +865,7 @@ export const MessageReportItem = as<
Report Message
</Text>
</Box>
<IconButton
type="button"
size="300"
onClick={handleClose}
radii="300"
aria-label="Close"
>
<IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
+25
View File
@@ -1,6 +1,7 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
plain,
DEFAULT_GROUPS,
kumaBeatTime,
kumaUrls,
@@ -260,3 +261,27 @@ test('a dismissed calls-down banner lets the next one through, never hides serve
'server-down',
);
});
test('plain: strips Markdown syntax only, keeps ordinary #, _, > and * (#308)', () => {
assert.equal(
plain('join #lotus-general or #announcements:lotusguild.org'),
'join #lotus-general or #announcements:lotusguild.org',
);
assert.equal(
plain('ping @some_user about snake_case and C#'),
'ping @some_user about snake_case and C#',
);
assert.equal(
plain('calls -> fixed, > 5 people, 5 * 3 = 15'),
'calls -> fixed, > 5 people, 5 * 3 = 15',
);
assert.equal(
plain('# Heading\n* item one\n- item two\n> quoted'),
'Heading\nitem one\nitem two\nquoted',
);
assert.equal(plain('**bold**, *em*, _em_, `/sfu/get`'), 'bold, em, em, /sfu/get');
assert.equal(
plain('Read the <b>guide</b> <img src=x onerror=alert(1)> [docs](https://x.y)'),
'Read the guide docs',
);
});
+16 -3
View File
@@ -240,12 +240,25 @@ const incidentTone = (style: string): BannerTone => {
};
/** Markdown-ish incident text as plain text for the Details line. */
const plain = (s: string): string =>
/**
* [Gitea #308] Markdown → plain text for the Details line. Strips only the
* syntax: removing every `*_\`#>` also broke "#lotus-general", "snake_case",
* "->" and "5 * 3". HTML tags are dropped (React escapes the text anyway).
*/
export const plain = (s: string): string =>
s
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
.replace(/[*_`#>]/g, '')
.replace(/\s+\n/g, '\n')
.replace(/<\/?[a-z][^>]*>/gi, '')
.replace(/^[ \t]{0,3}#{1,6}[ \t]+/gm, '')
.replace(/^[ \t]*>[ \t]?/gm, '')
.replace(/^[ \t]*[*+-][ \t]+/gm, '')
.replace(/`([^`\n]+)`/g, '$1')
.replace(/\*\*(?=\S)([^*\n]*?\S)\*\*/g, '$1')
.replace(/__(?=\S)([^_\n]*?\S)__/g, '$1')
.replace(/(^|[^\w*])\*(?=\S)([^*\n]*?\S)\*(?![\w*])/g, '$1$2')
.replace(/(^|[^\w])_(?=\S)([^_\n]*?\S)_(?!\w)/g, '$1$2')
.replace(/[ \t]+\n/g, '\n')
.trim();
/**