Skip to content

chore: sync the fork to upstream main (2547e709) - #7

Merged
juliusmarminge merged 12 commits into
fork/t3-mobilefrom
sync/upstream-2547e709
Oct 16, 2026
Merged

juliusmarminge merged 12 commits into
fork/t3-mobilefrom
sync/upstream-2547e709

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

What/Why?

Upstream merged most of what this fork carried: the code block wrap toggle (#1021, which is our #1 plus the review fixes) and the ordered list marker width fix (#1015, which is our #2 via #1005). It also landed the parser rename to enrmrkd (#922), character references (#1018), and the iOS hard-break width and clipboard fixes (#1017, #1019).

This syncs the fork to upstream main 2547e709 and drops our copies in favour of the upstream versions:

  1. Revert feat: add a line wrap toggle to code block headers #1, fix(lists): size ordered list markers to their widest number #2 and fix(android): align mixed nested list indents with iOS #3 (fd59c2c, 789435d, c994903).
  2. Merge upstream main (a39bb3a). With the reverts in place, the remaining conflicts are only where upstream's wrap toggle meets the fork's media slots:
  3. Cherry-pick mwolson's rebase of #1006 (cf8e765, from 23a3096), which replaces our fix(android): align mixed nested list indents with iOS #3 and is now mergeable upstream. Taking the exact commit means the next sync after fix(android): align mixed nested list indents with iOS software-mansion/enriched-markdown#1006 merges is conflict-free.
  4. Bump the archive to 1.2.0-t3.14 and record the new upstream base in pack-t3.mjs.

After this, the fork differs from upstream only by:

Open fork PRs #5 and #6 still merge cleanly on top of this.

Testing

The fork has no CI on this base branch, so everything ran locally on the merged head:

  • Android: testDebugUnitTest (63 tests, including upstream's ListRenderingTest, TaskListInteractionTest, CodeBlockWrapStateTest and the fork's media tests) and ktlintCheck pass.
  • iOS: the ReactNativeEnrichedMarkdown library builds for the simulator. The app-hosted XCTest suite (20 tests, including ListRenderingTests and ENRMCodeBlockWrapStateTests) passes on an iOS 27 simulator.
  • Parser: scripts/test-core-parser.sh passes: prefixed symbols only, golden ASTs unchanged, and it coexists with mity/md4c v0.6.0. The fork's AutolinkParserTest and QuoteMediaParserTest pass against enrmrkd.
  • JS: prepare, typecheck, eslint and jest (134 tests) pass.
  • The archive was packed and installed in T3 Code ([pingdotgg/t3code PR to follow]).

🤖 Generated with Claude Code


Devin Review

Note

Sync fork to upstream main: rename MD4C to enrmrkd, widen ordered-list gutters, enable wrap toggle by default

Brings the fork up to upstream main (2547e70). The biggest changes:

  • Replaces the vendored MD4C parser with the prefixed enrmrkd fork (enrmrkd.c, enrmrkd.h) and updates all builds (CMakeLists.txt, build.sh) and the fetch script to the Software Mansion fork. The new parser decodes named and numeric character references in text and attributes.
  • Ordered-list marker columns now widen to fit the widest non-task number in the list, propagated to nested lists, bullets, and task items on Android, iOS, and React Native (ListRenderer.kt, ListItemRenderer.swift).
  • The enableCodeBlockWrapToggle prop now defaults to true (EnrichedMarkdownManager.kt, EnrichedMarkdownText.tsx). Wrap state moves to dedicated state owners (CodeBlockWrapState on Android, ENRMCodeBlockWrapState on iOS) that preserve wrapped blocks across streaming edits and count nested code blocks in document order.
  • Web now renders videos in the browser's player without the native flavor or build flag; the corresponding known-limitation and roadmap entries were removed.
  • Adds parser CI (test-core-parser.sh, test-web-bundle.mjs) with golden AST fixtures and WASM bundle validation.
  • Behavioral Change: table-cell and non-code text line breaks now export as <br> in clipboard HTML, code-block newlines stay literal; two consecutive blank lines now end a list item; HR after link-reference definitions becomes a real block; enableCodeBlockWrapToggle default flips from false to true.

Macroscope summarized e90c138.

hryhoriiK97 and others added 12 commits October 16, 2026 17:17
…on#1015)

* fix: size ordered list markers to their widest number

* fix: carry the marker widening through the list context and move tasks with it

The nested-item margin lives in BaseListSpan, driven by a markerExtraWidth
that OrderedListSpan and TaskListSpan override, so a later change to nested
margins has one formula to extend. ListContextManager saves and restores
the widening like the rest of the list state, and the column is measured by
a companion function instead of a throwaway span. The extra is rounded to
whole pixels so the Int leading margins and the float marker positions
agree at every depth.

A task item inside a widened ordered list now moves with its numbered
siblings on every platform, as the Swift package already did; before, the
checkbox column stayed put and left a notch in long lists.

iOS names the nesting offset ancestorMarkerOffset like Android, keeps the
per-list extra cached on the context, and derives the default column width
from ListStyle instead of a static on a sibling renderer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: name list tests by feature and share the Android render harness

The marker width suites become ListRenderingTest(s) on every platform,
growing with later list fixes instead of one file per bug. The checkbox
toggle test moves to TaskListInteractionTest. The RN Android package gets a
test/ support package like the standalone one: a render helper, an AST
factory, and the canvas that records drawn markers, which both Android
packages now share. Task alignment tests assert that every row of a widened
list, tasks and their children included, shifts by the same amount.

The Android baselines for ordered_list_wide_markers_test are recaptured on
the repo's Pixel 9 API 36 AVD, where the committed ordered-list baselines
pass exact-match; the iOS captures reproduce byte for byte and are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Mike Olson <mwolson@member.fsf.org>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…nsion#1019)

* fix(clipboard): preserve table cell line breaks and math

* fix(ios): keep hard breaks inside their block when exporting HTML

collectParagraphsData split on lineRangeForRange:, which treats the U+2028
a hard break renders as like a paragraph end. A paragraph with a hard break
exported as two <p> blocks and a list item as two <li> items, and with the
new <br> escaping the first half gained a trailing <br>. Split on
paragraphRangeForRange: so the break stays in its block and becomes
<p>line one<br>line two</p>.

Tests are named by feature like software-mansion#1015: ClipboardExportTests.mm and
ClipboardExportTest.kt, since both cover HTML and Markdown export. Added a
rendered hard-break case on iOS, the code block gate on Android, a run that
mixes escaped characters with a line break, and math nested in strong, on
both platforms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…ansion#1017)

* fix(ios): retain tight widths for explicit line breaks

* fix(ios): measure explicit-break content with its indents so it does not re-wrap

Walk the line fragments once: stop at the first soft wrap (report maxWidth, as
before) and otherwise return a tight width that adds each line's head indent
and negative tail indent to its used rect. TextKit's usedRect leaves both out,
so lists, code blocks and blockquotes measured narrower than they need and
re-wrapped once Yoga handed the width back as the frame. Hard breaks are any
newlineCharacterSet member at a fragment's end, and the scan exits early once
the width reaches maxWidth. ENRMFinalizeMeasuredTextSize reads the layout
manager's own text storage and drops its text parameter.

Tests render real markdown through the parser and renderer and assert that
relaying at the measured width keeps the line count and height, which fails
on the previous implementation for lists, code blocks and blockquotes.

* refactor(ios): return content width directly from the line fragment scan

Soft wrap and reaching maxWidth both resolve to maxWidth, and tightWidth
was incomplete whenever hasSoftWrap was set, so the struct carried no
information the caller needed. Pass usedRect width as the fallback
instead of a 0 sentinel so a zero maxWidth still clamps to 0.

---------

Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-authored-by: eszlamczyk <ernest.szlamczyk@gmail.com>
…mansion#922)

* fix: isolate the embedded parser behind an ENRMRKD_ prefix

Enriched embedded MD4C under its original global names, so an application
that also links another MD4C copy failed with a duplicate definition of
md_parse, and the CocoaPods header map could resolve the bare md4c.h
include in the Objective-C++ input parser to another pod's copy (software-mansion#846).

Vendor the parser from software-mansion-labs/md4c rather than mity/md4c:
the fork renames both the filenames and everything they expose to an
ENRMRKD_/enrmrkd_ prefix, so two copies can coexist. Rename the consumers
to match, and follow the vendored directory rename through the podspecs,
the SwiftPM manifest, the Android CMake files and the example app project.

Includes stay plain and keep resolving through each target's header search
paths; what removes the header map ambiguity is the filename being specific
to the fork, since no other pod ships an enrmrkd.h.

The public API is unchanged: md4cFlags, Md4cFlags, MD4CParser and md4cType
keep their names.

Re-vendoring also moves the parser forward to the fork's master, which
carries upstream work postdating the Sep 17 snapshot (HTML block start
condition 4, the pipe handling rework, the video tag revert). Serialized
ASTs are identical for every markdown file in the repo across five flag
variants, and differ on 49 of 1000 generated adversarial documents - for
instance an empty "||||" spoiler now staying literal text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(core): guard the vendored parser with golden ASTs in CI

The parser is synced from software-mansion-labs/md4c, so a sync can change
how documents parse without any native build noticing. That is not
hypothetical: the migration in the previous commit silently dropped video
rendering, because the fork had inherited upstream's revert of <video> as a
block-level HTML tag (mity/md4c#428) and MD4CParser only promotes <video>
into a video node when the parser reports an HTML block.

Add scripts/test-core-parser.sh, run by a core-parser CI job gated on
packages/core/cpp changes. It compiles the vendored parser and MD4CParser
with the host toolchain, then checks that the parser exports nothing but
enrmrkd_* symbols, that the serialized AST of five fixture documents under
five flag variants matches a golden dump, and that the parser still links
and runs beside upstream mity/md4c (pinned tag) in one binary. The golden
dump is re-recorded with --update, so an intended parser change arrives as a
reviewable diff.

Vendor the fork's restored <video> block tag
(software-mansion-labs/md4c#2), which the golden fixtures cover; running the
check against the previously vendored parser fails on html-blocks.md in all
five variants.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: address cr, update ci and rebuild wasm

* fix: replace all path-only includes with search-path ones

* feat: resolve final issues

* docs: web renders videos now that the WASM bundle is rebuilt

The rebuilt md4c.js emits the `Video` node that software-mansion#765 added, and the web
renderer has had a `VideoRenderer`, `videoStyle` and the 'Video' node type
all along, so the tag now renders as the browser's own `<video>` player.
Six doc spots said the opposite, all of them written against the old
artifact.

Also drops the `unavailable` banner from the video LivePreview, so the
style-properties example is live like its neighbours, and rewrites the
build.sh note the stale artifact had earned - scripts/test-web-bundle.mjs
now guards the bundle itself, so the note only has to warn about prose.

Created with usage of AI tools

* style(core): clang-format TextLinkRecognizer for the core-parser lint

software-mansion#855 added it under packages/core/cpp/parser unformatted; the core-parser
CI job runs yarn lint-clang:core-parser over that directory.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e-mansion#1018)

* fix(parser): preserve named and numeric character references

* fix(parser): clamp decoded codepoints in one place and rebuild the web parser

appendCodepoint now maps zero, surrogates and anything above U+10FFFF to
U+FFFD itself, so the named table and the numeric path share one guard and
the UTF-8 encoder can never emit a sequence for an out-of-range value. The
numeric path only validates the digits.

The committed md4c.js is rebuilt with entity.c linked in; web dropped
character references until this artifact caught up with the sources.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(parser): parse numeric references without std::from_chars

The Swift package and the Android CMake targets set no C++ standard, so
<charconv> is not guaranteed; the standalone iOS package failed to compile.
md4c already caps numeric references at 7 decimal or 6 hex digits, so a
plain digit loop cannot overflow and needs no library support.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(parser): decode character references in link and image attributes

md4c hands attributes as typed substrings; getAttributeText copied the raw
bytes, so `[q](/u?a=1&amp;b=2)` decoded the text but kept `&amp;` in the
href. Entity segments now go through the same decoder as text. Include
<cstdlib> before the extern "C" block so entity.h's stdlib include is a
no-op there, and add a jest test that runs the committed web parser so a
stale md4c.js fails CI.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(ios): anchor decoded entities in the source offset annotator

The Swift package's annotator and its tests assumed the shared core drops
entity references, so a text node that now starts with a decoded entity
failed to anchor and the slicer returned nil. The matcher already bridged
one decoded scalar; it now bridges two for the handful of entities that
decode to a pair, and the tests assert the decoded content with its source
range spanning the reference. The entity branch moves into its own method
to keep matchRemainder under the complexity limit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(parser): look up entities through the fork's prefixed table

The vendored entity.c exported a bare entity_lookup, the same duplicate-symbol
class software-mansion#846 fixed for the parser: an app that also links md4c-html would fail
to link. software-mansion-labs/md4c already ships the table as
enrmrkd-entity.{c,h} with enrmrkd_entity_lookup, so fetch-md4c.sh syncs that
instead and MD4CParser uses the prefixed names. The table itself is unchanged.

test-core-parser.sh builds the table into the AST dumper, runs the prefix check
over it, and links upstream md4c-html's entity.c into the coexistence binary.
The golden dump is re-recorded: the only change is the decoded "&" in
commonmark.md's "Term &amp; entity" across its five variants.

---------

Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: eszlamczyk <ernest.szlamczyk@gmail.com>
…e-mansion#1021)

* feat: add a per-block line wrap toggle to code block headers

Adds an opt-in `enableCodeBlockWrapToggle` prop to the React Native
package. Each top-level fenced code block gets a wrap button next to copy;
tapping it wraps that block's lines at the block width, and tapping again
returns to horizontal scrolling.

Wrap state is a set of document-order indexes of top-level code blocks, fed
to the view-free measure pass (part of the iOS measurement cache key and the
Android measure hash). It resets when anything before the previous
document's last line changes, so streaming and task toggles keep it.
Labels come from `accessibilityLabels.codeBlock.wrap` / `.unwrap`.

Closes software-mansion#1009

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ios): write the checked state a task toggle produces into the markdown

toggleTaskListItemAtIndex wrote `[ ]` for checked and `[x]` for unchecked,
the inverse of Android, since software-mansion#92. The checkbox looked right because the
attributed string is patched separately, but the cached markdown recorded
the opposite state, so anything reading it after a tap saw a stale checkbox
and a prop echo of the toggled document never matched the local render.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: keep wrap state across task toggles and stroke the Android wrap icon

iOS reset the wrapped set when the markdown prop changed before its last
line, which a task-toggle echo always does. The reset now skips a prop that
equals the markdown already rendered locally, so the reader's choice stands.

Android drew the wrap glyph through androidx.core.graphics.PathParser, a
RestrictTo API, as a filled shape. WrapGlyph strokes the same Material
outline with the copy glyph's paint, so the two header buttons match.

Docs note that macOS accepts the prop without a toggle. Comments restating
adjacent code are trimmed. CodeBlockContainerViewTest moves to a follow-up
PR that adds native coverage on both platforms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: update the logic acording to latest review

* fix: apply inline cr

---------

Co-authored-by: Abdullah1738 <ardudeisepic@outlook.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Gregory Moskaliuk <mosckalyuck@gmail.com>
…mansion#1021

Upstream merged the toggle as software-mansion#1021
(2547e70), which carries this fork's #1 plus the review fixes. Dropping the
fork copy lets the upstream merge bring in that version unchanged.

This reverts commit 57af70a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ftware-mansion#1015

Upstream merged the fix as software-mansion#1015
(9fabe82), which carries software-mansion#1005 (this fork's #2) plus the review follow-ups.

This reverts commit d846625.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…software-mansion#1006

mwolson rebased software-mansion#1006 onto upstream main
after software-mansion#1015 landed. The fork takes that rebased commit next, so the eventual
upstream merge replaces it cleanly.

This reverts commit 65cbb5f.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the upstream versions of the code block wrap toggle (software-mansion#1021) and
the ordered list marker width fix (software-mansion#1015), plus software-mansion#922 (parser renamed to
enrmrkd), software-mansion#1017, software-mansion#1018 and software-mansion#1019.

Conflicts: blockquote measurement now threads both the fork's media slots
and assets and upstream's wrap cursor, and the media-slot render path passes
enableCodeBlockWrapToggle like the plain one. The fork's development-host
autolink patch moves from md4c.c to enrmrkd.c until software-mansion#961 lands upstream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 954f9c75-d188-444d-99c2-78d195025e99

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@juliusmarminge
juliusmarminge merged commit e90c138 into fork/t3-mobile Oct 16, 2026
4 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.

4 participants