fix(mentions): Discord markup is not prose either #482

Merged
coilyco-ops merged 1 commit from fix/discord-markup-is-not-prose-either into main 2026-08-13 14:23:33 +00:00
Member

closes #479

Residual of #465, found while writing a duplicate fix for it. Demonstrated against merged main, not predicted:

name=trophy                nice work <:trophy:1234567890>
                       ->  nice work <:<@999>:1234567890>

name=1499488069269590262   ask in <#1499488069269590262>
                       ->  ask in <#<@999>>

A custom emoji becomes a broken mention inside a broken emoji.

Why it survived 465

The existing prefix guard excludes a match preceded by <@, which is why a user mention survives and <:, <#, <@& do not. 465 made links opaque and stopped there.

Rather than widen that guard, the span splitter now treats markup as opaque the same way it already treats links — one rule, one place it is applied.

Likelihood, stated honestly

465 needed a member called eco or main. This needs one called trophy, wave, tada, or a display name that is a snowflake. Less likely — but emoji names are exactly the kind of thing people call themselves, and the failure is identical in shape: the harness rewriting inside a structure whose components are not people.

Built on someone else's better shape

I wrote a competing fix for 465 with index arithmetic over match offsets; theirs splits the reply into spans and is cleaner. That PR is closed and this builds on theirs.

ward exec gate green: build, policy-check, vet, test, test-skips, pre-commit.

closes #479 Residual of https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/465, found while writing a duplicate fix for it. **Demonstrated against merged `main`, not predicted:** ``` name=trophy nice work <:trophy:1234567890> -> nice work <:<@999>:1234567890> name=1499488069269590262 ask in <#1499488069269590262> -> ask in <#<@999>> ``` A custom emoji becomes a broken mention inside a broken emoji. ## Why it survived 465 The existing prefix guard excludes a match preceded by `<@`, which is why a **user** mention survives and `<:`, `<#`, `<@&` do not. 465 made links opaque and stopped there. Rather than widen that guard, the span splitter now treats markup as opaque the same way it already treats links — **one rule, one place it is applied.** ## Likelihood, stated honestly 465 needed a member called `eco` or `main`. This needs one called `trophy`, `wave`, `tada`, or a display name that is a snowflake. Less likely — but emoji names are exactly the kind of thing people call themselves, and the failure is identical in shape: the harness rewriting inside a structure whose components are not people. ## Built on someone else's better shape I wrote a competing fix for 465 with index arithmetic over match offsets; theirs splits the reply into spans and is cleaner. That PR is closed and this builds on theirs. `ward exec gate` green: build, policy-check, vet, test, test-skips, pre-commit.
fix(mentions): Discord markup is not prose either
All checks were successful
ci / image-build (pull_request) Successful in 22s
ci / test (pull_request) Successful in 39s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
97c6aed718
465 made links opaque and stopped there. Markup is also not prose and also
contains name-shaped text, so a member called trophy or wave, or one whose
display name is a snowflake, corrupted it:

  nice work <🏆1234567890>   ->  nice work <:<@999>:1234567890>
  ask in <#1499488069269590262>    ->  ask in <#<@999>>

A custom emoji became a broken mention inside a broken emoji.

The existing prefix guard excludes a match preceded by <@, which is why a user
mention survived and an emoji, a channel, and a role did not. Rather than widen
that guard, the span splitter now treats markup as opaque the same way it
already treats links, so there is one rule and one place it is applied.

Found while writing a duplicate fix for 465 that someone landed first. Their
span-splitting shape is better than my offset arithmetic, so this builds on
theirs. The gap is demonstrated in the issue rather than argued.

closes #479

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Author
Member

Context for whoever reviews this: my patch is one of three for the same bug, and I have filed the layer underneath — Angie (ENG, claude seat).

#494. The match rule admits a name preceded by any non-word character, so every structure containing name-shaped text needs its own exception. Links, then markup, then dotted identifiers — three in three hours, and nothing suggests three is the number.

Measured there: narrowing the boundary to whitespace-or-bracket handles all three cases and preserves every case the three patches keep.

This PR is still worth landing. It is tested, it carries a corpus, and it closes a demonstrated hole today. But if 494 is taken, this becomes defence in depth rather than the defence, which is the right relationship.

**Context for whoever reviews this: my patch is one of three for the same bug, and I have filed the layer underneath — Angie (ENG, claude seat).** https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/494. The match rule admits a name preceded by **any** non-word character, so every structure containing name-shaped text needs its own exception. Links, then markup, then dotted identifiers — three in three hours, and nothing suggests three is the number. Measured there: narrowing the boundary to whitespace-or-bracket handles all three cases and preserves every case the three patches keep. **This PR is still worth landing.** It is tested, it carries a corpus, and it closes a demonstrated hole today. But if 494 is taken, this becomes defence in depth rather than the defence, which is the right relationship.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!482
No description provided.