Skip to content

feat: add a line wrap toggle to code block headers - #1

Merged
juliusmarminge merged 8 commits into
pingdotgg:fork/t3-mobilefrom
Abdullah1738:feat/code-block-wrap
Oct 15, 2026
Merged

juliusmarminge merged 8 commits into
pingdotgg:fork/t3-mobilefrom
Abdullah1738:feat/code-block-wrap

Conversation

@Abdullah1738

@Abdullah1738 Abdullah1738 commented Oct 13, 2026 •

Copy link
Copy Markdown

What/Why?

Long lines in GitHub-flavor code blocks only scroll sideways. That's fine on desktop, but on a phone you end up swiping back and forth inside a small box to read a long command or a drafted comment.

This adds an opt-in enableCodeBlockWrapToggle prop (default false):

  • Button: each top-level fenced code block gets a wrap button to the left of the copy button. Tapping it wraps that block's lines at the block width, and tapping again goes back to horizontal scrolling. Every block starts unwrapped and toggles on its own.
  • iOS: text.alignleft SF Symbol. Wrapping bounds the cached TextKit container to the pane width. VoiceOver gets a custom action next to copy.
  • Android: Material wrap_text glyph. Wrapping caps the code TextView's width at the pane width inside the existing HorizontalScrollView. TalkBack reads the same labels.
  • Height: both platforms measure without the view, so the root view keeps the wrapped blocks as document-order indexes of top-level code blocks, and the measure pass reads them. On iOS they are part of the MeasurementCache key; on Android they go into the measure hash.
  • Not covered: code blocks inside blockquotes and admonitions have no toggle and keep scrolling.

Takeover changes (by @juliusmarminge, on top of @Abdullah1738's feature):

  • Wrap state reset: wrap state belongs to a block's position, so it now resets when anything before the previous document's last line changes, such as a recycled cell, an edited message, or a block inserted above. Before, a mounted view kept stale indexes when it got unrelated markdown. Streaming appends, rewrites of an unfinished last line (an HTML tag closing, a link relabeled) and task checkbox toggles keep the reader's choice.
  • Localized labels: the labels come from accessibilityLabels.codeBlock.wrap / .unwrap, defaulting to "Wrap lines" / "Disable line wrap". Before, they were hardcoded English next to a localized copy button.
  • iOS cache: iOS keys the measurement cache by the wrapped set instead of skipping the cache while any block is wrapped.
  • Cleanup and docs: removed an unused usedHeight. Updated docs-md/API_REFERENCE.md, the React Native API reference and the accessibility page.
  • Tests: Android CodeBlockContainerViewTest. It checks that the measured height equals the laid-out view's, wrapped and unwrapped, at three widths, and it checks the reset rule. Both were checked to fail when the code they cover is broken.
  • No version bump here: the archive bump is a separate commit.

Upstream: software-mansion#1009 proposes this API. Upstream's guidelines ask for an issue before API changes, and their standalone packages already have a global codeBlockLayout, so the shape may change there.

Testing

  • typecheck, Jest (20 suites, 128 tests), eslint on the touched TS files, and clang-format on the touched ObjC files all pass.
  • Android testDebugUnitTest and ktlintCheck pass. The iOS library pod builds for the simulator with xcodebuild.
  • Two review rounds, by separate Opus and GPT reviewers. The second round found no blockers. Its two non-blocking findings, a streamed tail rewrite and an Android task toggle resetting the state, are fixed in the last commit.
  • @Abdullah1738 ran the original feature in T3 Code mobile: an iOS release simulator build, iPhone and iPad device builds, and an Android release build on an emulator. The takeover changes are verified again in T3 Code's PR, which turns the prop on.

Screenshots

iOS Android
Off
On

Originally built by @Abdullah1738 in this PR.

Note

Add per-code-block line-wrap toggle to EnrichedMarkdown code block headers

  • Adds an enableCodeBlockWrapToggle prop (default false) for GitHub-flavor fenced code blocks on iOS and Android. Each top-level block starts unwrapped and can be toggled independently via a button beside the copy button, with VoiceOver/accessibility custom actions
  • Wrapped blocks lay out text within the block width using TextKit (iOS) and StaticLayout (Android) measurement; unwrapped blocks keep horizontal scrolling. Measurement caches include a wrapped-code-block fingerprint so wrapped and unwrapped states get distinct entries
  • Wrap state persists across streaming appends (new markdown starts with the old markdown up to its last line) and task-list checkbox updates; it clears when markdown is replaced or a block is inserted above
  • Adds codeBlock.wrap and codeBlock.unwrap accessibility labels with English defaults "Wrap lines" and "Disable line wrap", documented in API_REFERENCE.md and enriched-markdown-text.md
  • Behavioral Change: nested code blocks (inside blockquotes or admonitions) get no toggle; the language label in the header gets less width when the wrap button is visible

Macroscope summarized 52251d0.

@coderabbitai

coderabbitai Bot commented Oct 13, 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: Advanced
  • Run ID: 999b9816-2908-464d-94a9-d15eb775d95b

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.

@Abdullah1738

Copy link
Copy Markdown
Author

The T3 Code side, which bumps the vendored archive and turns the prop on, is pingdotgg/t3code#19041.

@juliusmarminge

Copy link
Copy Markdown
Member

Taking over this one to land it in the fork and ship it in T3 Code. I'll keep you credited and post what changed here.

juliusmarminge and others added 7 commits October 15, 2026 16:24
The wrap toggle announced hardcoded English on both platforms while the
copy button next to it already read its label from props. Add a
`codeBlock.wrap` / `codeBlock.unwrap` group to `accessibilityLabels`
(English defaults "Wrap lines" / "Disable line wrap") and route it to
the code block view the same way the other label groups reach their
segment views.

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

Wrap state is keyed by a code block's position among the top-level code
blocks, so it only stays meaningful while the document grows in place.
A mounted view that received unrelated markdown (a recycled list cell,
an edited message, a block inserted above) kept the old indexes and
wrapped the wrong block. Clear the set, and the measure pass's copy of
it, when the new markdown does not start with the previous one, and
force a re-measure the way the toggle itself does. Android keeps the
rule in `keepsCodeBlockWrapState` so it can be unit tested without the
JNI parser.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…f skipping it

Any wrapped block disabled the props-keyed measurement cache for the
whole document, so every Yoga pass re-measured from scratch. Fold an
order-independent fingerprint of the wrapped index set into the cache
key, as Android folds the sorted list into its props hash, and drop the
bypass flag. The set stays an immutable NSSet read through the atomic
property, so the measure thread never sees a partial update.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
State belongs to the block's position and resets when the markdown is
replaced rather than appended to; blocks inside blockquotes and
admonitions have no toggle; the spoken labels come from
`accessibilityLabels.codeBlock`. Add the prop to the website API
reference, which the PR had skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Checks that measureCodeBlockNodeHeight matches the laid-out
CodeBlockContainerView height for wrapped and unwrapped blocks at several
widths, and that keepsCodeBlockWrapState keeps wrap state only for in-place
growth (streaming appends, identical markdown).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…writes and task toggles

Apps that preprocess streaming markdown rewrite its unfinished last line (an
HTML tag closing, a link relabeled), which is not a pure append. Only a change
before the previous document's last line now resets wrap state. Android task
toggles also kept resetting it while iOS applies them in place; they now keep it.

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

Copy link
Copy Markdown
Member

Upstreamed as software-mansion#1010 (issue software-mansion#1009), with @Abdullah1738 as the commit author.

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