Skip to content

🐛 Fixed formats spreading onto the next text when one ends inside another - #32355

Merged
9larsons merged 2 commits into
TryGhost:mainfrom
NgoQuocViet2001:fix-format-tag-closing
Oct 30, 2026
Merged

9larsons merged 2 commits into
TryGhost:mainfrom
NgoQuocViet2001:fix-format-tag-closing

Conversation

@NgoQuocViet2001

Copy link
Copy Markdown
Contributor

no ref

When an outer format ends while an inner one continues, the Lexical HTML renderer closes the inner tag and leaves the outer one open, so the following text picks up a format the author never applied. Typing "Hello world foo", making "Hello world" bold and "world foo" italic renders <strong>Hello <em>world</em><em> foo</em></strong>, so " foo" is bold in published posts and newsletters while the editor shows it as italic only.

For each format the next node lacked, the close step popped the innermost open format instead of the missing one. It now closes the outermost format the next node lacks together with everything nested inside it, and the next node re-opens the formats it still has. The example above now renders <strong>Hello <em>world</em></strong><em> foo</em>.

  • Ran: vitest run test/text-formats.test.ts in koenig/kg-lexical-html-renderer: the 3 new cases fail on main and pass with the fix

  • Ran: full package suite with coverage: 70/70 passed, lines 91.12% (gate 90%)

  • Checked: tsc --noEmit, eslint, oxlint --deny-warnings, oxfmt --check and pnpm change check

  • Checked: no Ghost core snapshot contains the old nesting

  • I've read and followed the Contributor Guide

  • I've explained my change

  • I've written an automated test to prove my change works

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between fa85bf8 and 311be41.

Files selected for processing: 2
  • koenig/kg-lexical-html-renderer/src/utils/TextContent.ts
  • koenig/kg-lexical-html-renderer/test/text-formats.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

Recent review details
Context from checks skipped due to timeout: 10
  • GitHub Check: Ghost-CLI tests (latest-release, Node 22.23.4)
  • GitHub Check: Ghost-CLI tests (clean-install, Node 24.20.0)
  • GitHub Check: Ghost-CLI tests (clean-install, Node 22.23.4)
  • GitHub Check: CodeRabbit
  • GitHub Check: Legacy tests (Node 24.20.0, mysql8)
  • GitHub Check: Build Docker Images
  • GitHub Check: Acceptance tests (Node 24.20.0, mysql8)
  • GitHub Check: Legacy tests (Node 22.23.4, mysql8)
  • GitHub Check: Acceptance tests (Node 22.23.4, mysql8)
  • GitHub Check: Detect Tinybird changes
Additional context used
Code guidelines: 3
tools/README.md — configured
docs/contributing/testing.md — configured
docs/codebase/monorepo-structure.md — configured
Path-based instructions: 6
Review whether tests prove changed behaviour, meaningful error/edge paths, and externally observable contracts without coupling to implementation details.

CodeRabbit configuration file

Files:

  • koenig/kg-lexical-html-renderer/test/text-formats.test.ts
Review lens: "where does this data become trusted?" Boundary data (HTTP input, external API/SDK responses, env/config, DB/filesystem reads, queue/webhook/event payloads) is `unknown` until validated — Zod by default.

CodeRabbit configuration file

Files:

  • koenig/kg-lexical-html-renderer/test/text-formats.test.ts
  • koenig/kg-lexical-html-renderer/src/utils/TextContent.ts
Prioritise concrete correctness, security, data-integrity, compatibility, and regression risks.

CodeRabbit configuration file

Files:

  • koenig/kg-lexical-html-renderer/test/text-formats.test.ts
  • koenig/kg-lexical-html-renderer/src/utils/TextContent.ts
Applicability reference: AGENTS.md: # AGENTS.md **Repository tooling:** read the [tooling ownership guide](tools/README.md) Source excerpt: Use `tools/` for shared configuration, repository automation and contributor comma...

CodeRabbit inference engine (tools/README.md)

Files:

  • koenig/kg-lexical-html-renderer/test/text-formats.test.ts
  • koenig/kg-lexical-html-renderer/src/utils/TextContent.ts
Applicability reference: AGENTS.md: # AGENTS.md Start with: [Testing](docs/contributing/testing.md) Source excerpt: Test observable behaviour rather than private implementation details.

CodeRabbit inference engine (docs/contributing/testing.md)

Files:

  • koenig/kg-lexical-html-renderer/test/text-formats.test.ts
Applicability reference: AGENTS.md: # AGENTS.md Start with: [Monorepo structure](docs/codebase/monorepo-structure.md) AGENTS.md: # AGENTS.md **Ghost Core:** use the [server map](docs/codebase/monorepo-structure.md#ghost-core) Source excerpt...

CodeRabbit inference engine (docs/codebase/monorepo-structure.md)

Files:

  • koenig/kg-lexical-html-renderer/test/text-formats.test.ts
  • koenig/kg-lexical-html-renderer/src/utils/TextContent.ts
Additional comments: 2
koenig/kg-lexical-html-renderer/src/utils/TextContent.ts: 1

126-126: LGTM!

Also applies to: 129-130, 132-136

koenig/kg-lexical-html-renderer/test/text-formats.test.ts: 1

90-103: LGTM!

Also applies to: 105-108, 110-113, 115-118

Walkthrough

TextContent.render now closes the first format that the next text or link node does not retain, along with all nested formats. It moves the current DOM node up once for each removed format. Six tests cover format transitions involving bold, italic, underline, highlight, line breaks, and links. A changeset declares a patch release for @tryghost/kg-lexical-html-renderer.

Priority: Low

Change: Bug fix

Merge Risk: Minimal · up to 311be

No merge-blocking risk was identified in the format-boundary change.

Pre-merge checks: Passed 7
Check name Status Explanation
Title check Passed The title clearly describes the main formatting bug fixed by the changeset. It is specific and concise, although it includes an emoji.
Description check Passed The description explains the formatting bug, the renderer change, the expected output, and the test results. It is directly related to the changeset.
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.
Type-Safe Boundaries Passed The PR changes internal TextContent format-stack logic only. TextContent.addNode receives LexicalNode values from the renderer's internal getChildren() traversal, not HTTP, SDK, environment, d…
New Files Are Typescript Passed The pull request adds only .changeset/modern-rice-boil.md. It modifies existing .ts files and adds no new .js, .jsx, .cjs, or .mjs source file.
Tooling Ownership Passed The PR changes only a package-local renderer source file, its package-local test, and a package changeset. No scripts, tool configuration, shared automation, or shipped command paths changed. The pack…
Full details: Type-Safe Boundaries

Explanation

The PR changes internal TextContent format-stack logic only. TextContent.addNode receives LexicalNode values from the renderer's internal getChildren() traversal, not HTTP, SDK, environment, database, filesystem, queue, webhook, or event boundary data. The production diff adds no any, @ts-nocheck, or @ts-ignore, and the as HTMLElement assertion is pre-existing code moved into the new loop. No duplicated Zod-described type was added.

Full details: Tooling Ownership

Explanation

The PR changes only a package-local renderer source file, its package-local test, and a package changeset. No scripts, tool configuration, shared automation, or shipped command paths changed. The package README and package.json place the renderer and tests under koenig/kg-lexical-html-renderer, and tools/README.md confirms application or package behavior belongs with its owning package.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

NgoQuocViet2001 and others added 2 commits October 30, 2026 11:20
…ther

no ref

When an outer format such as bold ended while an inner one such as italic carried on, the HTML renderer closed the inner tag and left the outer one open. The following text then picked up the outer format in published posts and newsletters, even though the editor showed it correctly. Closing a format now also closes the formats nested inside it, and the next text node re-opens the ones it still has.
no ref

Covers highlight, a linebreak between the nodes and a following link, so
the closing logic is exercised where the next text node is not adjacent or
a link forces every format closed. Also trimmed the closing comment to two
lines and dropped the history comment from the new test.
@9larsons
9larsons force-pushed the fix-format-tag-closing branch from fa85bf8 to 311be41 Compare October 30, 2026 18:24
@nx-cloud

nx-cloud Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 311be41

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 2m 27s View ↗
nx run ghost:test:integration ✅ Succeeded 1m 57s View ↗
nx run ghost:test:ci:e2e ✅ Succeeded 5m 10s View ↗
nx run ghost:test:e2e ✅ Succeeded 3m 56s View ↗
nx run ghost:test:legacy ✅ Succeeded 2m 5s View ↗
nx run ghost:test:ci:unit ✅ Succeeded 1m 7s View ↗
nx run-many -t test:unit -p @tryghost/kg-lexica... ✅ Succeeded 46s View ↗
nx run ghost-monorepo:lint:boundaries ✅ Succeeded 24s View ↗
Additional runs (6) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-30 18:34:16 UTC

@codecov

codecov Bot commented Oct 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.60%. Comparing base (7b3f872) to head (311be41).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #32355      +/-   ##
==========================================
+ Coverage   85.48%   85.60%   +0.11%     
==========================================
  Files        1413     1413              
  Lines       52924    52924              
  Branches     9091     9091              
==========================================
+ Hits        45244    45304      +60     
+ Misses       6565     6493      -72     
- Partials     1115     1127      +12     
Flag Coverage Δ
e2e-tests 72.76% <ø> (-0.01%) ⬇️
unit-tests 68.66% <ø> (+0.24%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

@9larsons
9larsons merged commit bfcf982 into TryGhost:main Oct 30, 2026
56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants