Skip to content

feat(app): add code block wrap setting - #61083

Open
wheresvic wants to merge 3 commits into
anomalyco:devfrom
wheresvic:code-block-wrap
Open

wheresvic wants to merge 3 commits into
anomalyco:devfrom
wheresvic:code-block-wrap

Conversation

@wheresvic

@wheresvic wheresvic commented Nov 1, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #60143

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds a "Wrap long lines in code blocks" toggle to Settings > Appearance, off by default, so nothing changes unless you turn it on. Code blocks hide their horizontal scrollbar, so long lines get cut off; with the toggle on they wrap instead.

Same pattern as Reduce motion (#57103): an appearance setting sets data-code-wrap on the root, and one rule in markdown.css keys off it:

:root[data-code-wrap="on"] {
  [data-component="markdown"] pre {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }
}

Stored settings without the field load as false (withInitial fills in the default; the existing "defaults invalid preferences locally" test covers it). The scrollbar and focus behaviour of pre are unchanged.

I skipped the timeline benchmark from packages/app/AGENTS.md: the setting is off by default and only adds a CSS rule behind a root attribute.

How did you verify your code works?

  • bun typecheck and bun run test in packages/app: 948 unit and 303 browser tests pass; the pre-push typecheck passes for all packages.
  • In the dev web UI (Firefox): with the toggle on, a 300-character echo line and a 1900-character string with no spaces wrap inside the column without widening it; with it off, they're cut off as before.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none">
  <defs><clipPath id="b"><rect x="24" y="8" width="192" height="56" rx="6"/></clipPath></defs>
  <rect x="24" y="8" width="192" height="56" rx="6" fill="currentColor" fill-opacity=".05" stroke="currentColor" stroke-opacity=".25"/>
  <g clip-path="url(#b)" fill="currentColor">
    <rect x="36" y="18" width="64" height="6" rx="3" opacity=".45"/>
    <rect x="36" y="32" width="168" height="6" rx="3" opacity=".8">
      <animate attributeName="width" values="260;260;168;168;260" keyTimes="0;.35;.5;.85;1" calcMode="spline" keySplines=".4 0 .2 1;.4 0 .2 1;.4 0 .2 1;.4 0 .2 1" dur="4s" repeatCount="indefinite"/>
    </rect>
    <rect x="36" y="46" width="92" height="6" rx="3" opacity=".8">
      <animate attributeName="opacity" values="0;0;.8;.8;0" keyTimes="0;.35;.5;.85;1" calcMode="spline" keySplines=".4 0 .2 1;.4 0 .2 1;.4 0 .2 1;.4 0 .2 1" dur="4s" repeatCount="indefinite"/>
    </rect>
  </g>
</svg>

Screenshots / recordings

Before (toggle off):

Screenshot From 2026-11-01 18-26-08

After (toggle on):

Screenshot From 2026-11-01 18-25-55

The setting:

Screenshot From 2026-11-01 18-29-01

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

The Visual: a code block where the long line first runs past the edge and is cut off, then wraps onto a second line, on a loop. Without animation it shows the wrapped state. It's drawn in currentColor and well under 8 KB.


Added by the opencode bot (AI-generated).

Related: #57432

@wheresvic
wheresvic marked this pull request as ready for review November 1, 2026 17:43

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The change looks sound; I have no blocking concerns. With the toggle off, the new CSS rule never applies, so nothing changes for existing users.

I couldn't check it in the UI: there's no browser in this environment, so I relied on your screenshots. bun test src/settings/model.test.ts passes (stored settings without codeWrap load as false), and tsc -b in packages/app passes. The persisted setting follows the existing reduceMotion pattern, and the timeline re-measures blocks that change height when the setting is toggled.

Public API: unchanged. The only new field, appearance.codeWrap, is a local app preference with a default.

Related: #57432

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The new push is a rebase onto the latest dev; the feature commit is unchanged. On the new head, src/settings/model.test.ts and tsc -b in packages/app pass. The wrap rule doesn't interact with the code-block plugin content slots that are now on dev. I still couldn't render the toggle here because there's no browser, so I'm relying on your screenshots. No new concerns.

Public API: unchanged (appearance.codeWrap is a local app preference with a default).

@colthreepv

Copy link
Copy Markdown

@wheresvic I ran into this exact problem while testing long code blocks in the desktop chat: lines don't wrap, and the hidden horizontal scrollbar makes the remaining content hard to discover.

I would recommend against global (OFF) appearance setting, this should be ON by default (who wants to not be able to read the produced text entirely?)

In #62543 I have added a wrap toggle to the file viewer, using the VS Code Codicons word-wrap icon with a tooltip and a visible pressed state.
It could be reused here, but my opinion is this is a BUG, not a choice

@wheresvic

Copy link
Copy Markdown
Author

@colthreepv Thanks for the feedback. #62543 covers the file viewer and this PR covers code blocks in chat, so the two don't overlap.

I kept this off by default on purpose: turning it on changes how code blocks look for everyone, so I'd leave that call to the maintainers. If they'd rather have it on by default, that's a small change here. I went with one setting instead of a toggle per block because streaming replaces the block elements, which makes a per-block toggle a lot more work.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This push only merges the latest dev; your changes are identical to the previous review. On the new head, src/settings/model.test.ts and tsc -b in packages/app pass, and nothing merged from dev conflicts with the setting. I still couldn't render the toggle here because there's no browser, so I'm relying on your screenshots. No new concerns.

Public API: unchanged (appearance.codeWrap is a local app preference with a default).

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This push rebases onto the latest dev; your commit is unchanged. On the new head, src/settings/model.test.ts and tsc -b in packages/app pass, and nothing in the updated dev interacts with the setting or its CSS rule. I still couldn't render the toggle here because there's no browser, so I'm relying on your screenshots. No new concerns.

Public API: unchanged (appearance.codeWrap is a local app preference with a default).

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This push rebases onto one more dev commit (#63259, packages/core only); your commit is unchanged. src/settings/model.test.ts and tsc -b in packages/app still pass. I still couldn't render the toggle here because there's no browser, so I'm relying on your screenshots. No new concerns.

Public API: unchanged (appearance.codeWrap is a local app preference with a default).

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This push only merges a one-line CI fix from dev; your changes are unchanged since the last review, where src/settings/model.test.ts and tsc -b in packages/app passed. No new concerns.

Public API: unchanged (appearance.codeWrap is a local app preference with a default).

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This push merges the latest dev; your changes are unchanged, and the new settings code from dev sits alongside them without conflicts. src/settings/model.test.ts and tsc -b in packages/app still pass. I still couldn't render the toggle here because there's no browser, so I'm relying on your screenshots. No new concerns.

Public API: unchanged (appearance.codeWrap is a local app preference with a default).

This branch has not been deployed

No deployments
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.

desktop: code blocks hide horizontal scrollbars and cannot wrap long lines

2 participants