Skip to content

feat(message-parser): horizontal rule (thematic break) - #41113

Merged
ggazzo merged 5 commits into
developfrom
feat/message-parser-thematic-break
Jul 1, 2026
Merged

feat(message-parser): horizontal rule (thematic break)#41113
ggazzo merged 5 commits into
developfrom
feat/message-parser-thematic-break

Conversation

@ggazzo

@ggazzo ggazzo commented Jun 30, 2026

Copy link
Copy Markdown
Member

Proposed changes (including videos or screenshots)

Adds horizontal rules (thematic breaks) to the message parser and renderer.

A line consisting of 3 or more contiguous -, *, or _ markers — with nothing else on the line (leading/trailing spaces allowed) — is parsed into a new HORIZONTAL_RULE block node:

above
---
below
  • ---, ***, ___, ---------- all produce a rule.
  • Rendered with Fuselage's Divider in gazzodown.
  • New HORIZONTAL_RULE AST node: { type: 'HORIZONTAL_RULE', value: undefined }.

Why contiguous-only

Spaced variants like * * * and _ _ _ are intentionally not matched. Rocket.Chat already treats ** ** / __ __ as whitespace-emphasis (plain text); matching spaced markers as a rule would regress that behavior. Contiguous-only keeps the common --- / *** / ___ forms working without touching emphasis.

Issue(s)

Part of the message-parser markdown gap work (thematic break / P1).

Steps to test or reproduce

  1. cd packages/message-parser && yarn jest tests/horizontalRule.test.ts
  2. Visual: yarn workspace @rocket.chat/gazzodown storybookMarkup stories → HorizontalRule / Example.

Or paste in a message:

section one
---
section two

Further comments

Full parser suite green (645 tests). Spaced thematic breaks (* * *) can be revisited later if the emphasis grammar is reworked.

Review in cubic

Task: ARCH-2198

Summary by CodeRabbit

  • New Features
    • Added horizontal-rule support in message parsing and rendering, displaying a divider between sections.
    • Recognizes lines containing 3+ contiguous - characters (allowing optional surrounding spaces).
    • Added a Storybook story and updated examples to demonstrate horizontal-rule rendering.
  • Bug Fixes
    • Ensures */_-style marker-only lines are not interpreted as horizontal rules, preserving existing emphasis and masking behavior.
    • Includes an optional source fallback for renderers that can’t handle horizontal rules directly.
  • Tests
    • Added comprehensive horizontal-rule parser test coverage, including correct source offsets and non-matching edge cases.

@dionisio-bot

dionisio-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 20ad732

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@rocket.chat/message-parser Minor
@rocket.chat/gazzodown Major
@rocket.chat/meteor Patch
@rocket.chat/core-services Patch
@rocket.chat/core-typings Patch
@rocket.chat/livechat Patch
@rocket.chat/rest-typings Patch
rocketchat-services Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7abbd0cd-1403-4584-b46c-bbbb8e5ae9bf

📥 Commits

Reviewing files that changed from the base of the PR and between 404ecd0 and 20ad732.

📒 Files selected for processing (3)
  • .changeset/parser-horizontal-rule.md
  • packages/message-parser/src/grammar.pegjs
  • packages/message-parser/tests/horizontalRule.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/parser-horizontal-rule.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/message-parser/tests/horizontalRule.test.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
⚠️ CI failures not shown inline (2)

GitHub Check: Dionisio QA: Some checks did not pass

Conclusion: failure

View job details

**Conclusion:** failure
### Steps
- ✅ **No merge conflicts**
- ❌ **QA assured** — This PR is missing the 'stat: QA assured' label
- ✅ **Mergeable**
- ✅ **Has milestone or project**
- ✅ **Valid PR title**
- ✅ **Correct target version**

GitHub Check: Dionisio QA: Some checks did not pass

Conclusion: failure

View job details

**Conclusion:** failure
### Steps
- ✅ **No merge conflicts**
- ❌ **QA assured** — This PR is missing the 'stat: QA assured' label
- ✅ **Mergeable**
- ✅ **Has milestone or project**
- ✅ **Valid PR title**
- ✅ **Correct target version**
🔇 Additional comments (2)
packages/message-parser/src/grammar.pegjs (2)

17-17: LGTM!

Also applies to: 61-61


244-260: LGTM!

Dash-only restriction, contiguous-marker requirement, and strict end-of-line/end-of-input termination correctly avoid collisions with UnorderedListHyphenItem, spaced emphasis variants (- - -), and mid-paragraph text. Marker source-range capture via range() matches the fallback offsets asserted in horizontalRule.test.ts.


Walkthrough

Adds horizontal rule parsing to the message parser and renders the new block in gazzodown. The change introduces a HORIZONTAL_RULE AST node, grammar support for dash-only thematic breaks, node construction with optional source-range fallback, renderer support, stories, tests, and a changeset.

Changes

Horizontal Rule Parsing and Rendering

Layer / File(s) Summary
AST type and block union
packages/message-parser/src/definitions.ts
Adds HorizontalRule with HORIZONTAL_RULE discriminator and optional fallback, and registers it in Types, ASTNode, and Blocks.
PEG grammar rules and util constructor
packages/message-parser/src/grammar.pegjs, packages/message-parser/src/utils.ts
Adds dash-only thematic-break parsing for 3+ contiguous - characters and the horizontalRule() constructor used to build the new node.
gazzodown Divider rendering
packages/gazzodown/src/Markup.tsx, packages/gazzodown/src/Markup.stories.tsx
Imports Divider, renders HORIZONTAL_RULE blocks as a Divider, and adds Storybook coverage for the new output.
Tests and changeset
packages/message-parser/tests/helpers.ts, packages/message-parser/tests/horizontalRule.test.ts, .changeset/parser-horizontal-rule.md
Adds the test helper, parser coverage for valid and invalid marker patterns, and the release note entry for the package bumps.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested labels

Suggested labels: type: feature

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding horizontal rule (thematic break) support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • ARCH-2198: Request failed with status code 401

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 69.09%. Comparing base (70c0ff0) to head (20ad732).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #41113      +/-   ##
===========================================
- Coverage    69.10%   69.09%   -0.01%     
===========================================
  Files         3433     3433              
  Lines       132340   132444     +104     
  Branches     23104    23109       +5     
===========================================
+ Hits         91452    91512      +60     
- Misses       37520    37570      +50     
+ Partials      3368     3362       -6     
Flag Coverage Δ
e2e 59.31% <ø> (-0.05%) ⬇️
e2e-api 40.39% <ø> (-0.01%) ⬇️
unit 69.95% <96.77%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ggazzo ggazzo added this to the 8.7.0 milestone Jun 30, 2026
@ggazzo

ggazzo commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

/jira ARCH-2167

@ggazzo
ggazzo marked this pull request as ready for review June 30, 2026 17:53
@coderabbitai coderabbitai Bot added the type: feature Pull requests that introduces new feature label Jun 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/message-parser/src/definitions.ts (1)

129-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the inline fallback comment from the type body.

Line 129 adds a code comment inside a TypeScript implementation file. If this note needs to stay discoverable, move it to docs/tests instead of the type definition. As per coding guidelines, **/*.{ts,tsx,js} should "Avoid code comments in the implementation".

🤖 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 `@packages/message-parser/src/definitions.ts` around lines 129 - 130, Remove
the inline implementation comment from the type body in definitions.ts so the
Plain fallback field is declared without a code comment; if the note needs to
remain visible, move it out of the type definition and into docs or tests
instead. Update the Definitions/Plain fallback area only, keeping the actual
fallback?: Plain symbol intact and avoiding any comment in the implementation
file.

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.

Nitpick comments:
In `@packages/message-parser/src/definitions.ts`:
- Around line 129-130: Remove the inline implementation comment from the type
body in definitions.ts so the Plain fallback field is declared without a code
comment; if the note needs to remain visible, move it out of the type definition
and into docs or tests instead. Update the Definitions/Plain fallback area only,
keeping the actual fallback?: Plain symbol intact and avoiding any comment in
the implementation file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2ac712e-e75d-473e-a778-21bc60496463

📥 Commits

Reviewing files that changed from the base of the PR and between a158ae7 and 44e8042.

📒 Files selected for processing (8)
  • .changeset/parser-horizontal-rule.md
  • packages/gazzodown/src/Markup.stories.tsx
  • packages/gazzodown/src/Markup.tsx
  • packages/message-parser/src/definitions.ts
  • packages/message-parser/src/grammar.pegjs
  • packages/message-parser/src/utils.ts
  • packages/message-parser/tests/helpers.ts
  • packages/message-parser/tests/horizontalRule.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (15)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Hacktron Security Check
  • GitHub Check: 🔨 Test UI (EE) / MongoDB 8.0 coverage (4/5)
  • GitHub Check: 🔨 Test UI (EE) / MongoDB 8.0 coverage (5/5)
  • GitHub Check: 🔨 Test UI (EE) / MongoDB 8.0 coverage (1/5)
  • GitHub Check: 🔨 Test UI (EE) / MongoDB 8.0 coverage (3/5)
  • GitHub Check: 🔨 Test UI (EE) / MongoDB 8.0 coverage (2/5)
  • GitHub Check: 🔨 Test API Livechat (CE) / MongoDB 8.0 (1/1)
  • GitHub Check: 🔨 Test UI (CE) / MongoDB 8.0 (2/4)
  • GitHub Check: 🔨 Test UI (CE) / MongoDB 8.0 (1/4)
  • GitHub Check: 🔨 Test API Livechat (EE) / MongoDB 8.0 coverage (1/1)
  • GitHub Check: 🔨 Test UI (CE) / MongoDB 8.0 (3/4)
  • GitHub Check: 🔨 Test UI (CE) / MongoDB 8.0 (4/4)
  • GitHub Check: 🔨 Test API (EE) / MongoDB 8.0 coverage (1/1)
  • GitHub Check: 🔨 Test Federation Matrix
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • packages/message-parser/src/utils.ts
  • packages/message-parser/tests/horizontalRule.test.ts
  • packages/gazzodown/src/Markup.stories.tsx
  • packages/message-parser/tests/helpers.ts
  • packages/gazzodown/src/Markup.tsx
  • packages/message-parser/src/definitions.ts
🧠 Learnings (5)
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • packages/message-parser/src/utils.ts
  • packages/message-parser/tests/horizontalRule.test.ts
  • packages/message-parser/tests/helpers.ts
  • packages/message-parser/src/definitions.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • packages/message-parser/src/utils.ts
  • packages/message-parser/tests/horizontalRule.test.ts
  • packages/message-parser/tests/helpers.ts
  • packages/message-parser/src/definitions.ts
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.

Applied to files:

  • packages/message-parser/src/utils.ts
  • packages/message-parser/tests/horizontalRule.test.ts
  • packages/gazzodown/src/Markup.stories.tsx
  • packages/message-parser/tests/helpers.ts
  • packages/gazzodown/src/Markup.tsx
  • packages/message-parser/src/definitions.ts
📚 Learning: 2026-03-16T21:50:37.589Z
Learnt from: amitb0ra
Repo: RocketChat/Rocket.Chat PR: 39676
File: .changeset/migrate-users-register-openapi.md:3-3
Timestamp: 2026-03-16T21:50:37.589Z
Learning: For changes related to OpenAPI migrations in Rocket.Chat/OpenAPI, when removing endpoint types and validators from rocket.chat/rest-typings (e.g., UserRegisterParamsPOST, /v1/users.register) document this as a minor changeset (not breaking) per RocketChat/Rocket.Chat-Open-API#150 Rule 7. Note that the endpoint type is re-exposed via a module augmentation .d.ts in the consuming package (e.g., packages/web-ui-registration/src/users-register.d.ts). In reviews, ensure the changeset clearly states: this is a non-breaking change, the major version should not be bumped, and the changeset reflects a minor version bump. Do not treat this as a breaking change during OpenAPI migrations.

Applied to files:

  • .changeset/parser-horizontal-rule.md
📚 Learning: 2026-03-27T14:52:56.865Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39892
File: apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx:150-155
Timestamp: 2026-03-27T14:52:56.865Z
Learning: In Rocket.Chat, there are two different `ModalBackdrop` components with different prop APIs. During review, confirm the import source: (1) `rocket.chat/fuselage` `ModalBackdrop` uses `ModalBackdropProps` based on `BoxProps` (so it supports `onClick` and other Box/DOM props) and does not have an `onDismiss` prop; (2) `rocket.chat/ui-client` `ModalBackdrop` uses a narrower props interface like `{ children?: ReactNode; onDismiss?: () => void }` and handles Escape keypress and outside mouse-up, and it does not forward arbitrary DOM props such as `onClick`. Flag mismatched props (e.g., `onDismiss` passed to the fuselage component or `onClick` passed to the ui-client component) and ensure the usage matches the correct component being imported.

Applied to files:

  • packages/gazzodown/src/Markup.stories.tsx
  • packages/gazzodown/src/Markup.tsx
🔇 Additional comments (6)
packages/gazzodown/src/Markup.tsx (1)

1-1: LGTM!

Also applies to: 67-69

packages/gazzodown/src/Markup.stories.tsx (1)

261-266: LGTM!

Also applies to: 296-297

packages/message-parser/tests/helpers.ts (1)

94-98: LGTM!

packages/message-parser/tests/horizontalRule.test.ts (1)

5-25: LGTM!

.changeset/parser-horizontal-rule.md (1)

1-6: LGTM!

packages/message-parser/src/utils.ts (1)

273-276: 🎯 Functional Correctness

No change needed for the conditional spread The same ...(cond && { ... }) pattern is already used in packages/message-parser/src/utils.ts and elsewhere in the repo, so this doesn’t appear to be a TypeScript type-checking problem.

			> Likely an incorrect or invalid review comment.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 8 files

Re-trigger cubic

Comment thread packages/message-parser/src/definitions.ts Outdated
ggazzo added 4 commits July 1, 2026 11:25
A line of 3+ contiguous -, * or _ markers (nothing else on the line) parses
into a new HORIZONTAL_RULE block node, rendered with Fuselage's Divider.
Spaced variants like '* * *' are intentionally not matched to preserve the
existing whitespace-emphasis behavior of '** **' / '__ __'.
The HORIZONTAL_RULE node now carries an optional `fallback` plain-text node
with its matched source, so consumers without a horizontal-rule renderer
degrade to the original markup (e.g. `---`) instead of dropping the node.
Replace the duplicated plain-text fallback on HORIZONTAL_RULE with a
[start, end] offset span (SourceRange) covering the marker run, so consumers
slice the source instead of carrying a copy in the AST.
@ggazzo
ggazzo force-pushed the feat/message-parser-thematic-break branch from cc08c34 to 404ecd0 Compare July 1, 2026 14:36
juliajforesti
juliajforesti previously approved these changes Jul 1, 2026
A line of 3+ '*' or '_' collided with emphasis and with censored words
(bad-words masks a term as a run of '*'), so a fully-censored message like
'*******' rendered as a divider instead of the masked text. Accept only '-'
for the thematic break; bare '***' / '_______' lines stay text/emphasis.
CommonMark allows all three markers, but '-' is the common divider syntax
and avoids the collisions.
@ggazzo
ggazzo merged commit 4117a1d into develop Jul 1, 2026
47 of 48 checks passed
@ggazzo
ggazzo deleted the feat/message-parser-thematic-break branch July 1, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature Pull requests that introduces new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants