Skip to content

feat(ui): add line wrap toggle to markdown code blocks#599

Merged
shantur merged 3 commits into
devfrom
wrap-line-codeblocks
Jul 15, 2026
Merged

feat(ui): add line wrap toggle to markdown code blocks#599
shantur merged 3 commits into
devfrom
wrap-line-codeblocks

Conversation

@shantur

@shantur shantur commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a wrap toggle beside the copy action in Markdown code block headers
  • default Markdown code blocks to wrapped lines for both plain and Shiki-highlighted output
  • preserve per-block wrap choices across async Markdown and syntax-highlight re-renders
  • add localized labels for the new Markdown code block wrap action

Validation

  • npm run typecheck --workspace @codenomad/ui

Notes

  • Left unrelated untracked .opencode/package-lock.json out of the commit.

Render Markdown code blocks with a header-level word wrap control alongside the existing copy action, with wrapping enabled by default for both plain and Shiki-highlighted blocks.

Track per-block wrap overrides in the Markdown component so user choices survive async Markdown and syntax-highlight re-renders for the same message part. The toggle updates aria labels, pressed state, and virtual-list measurements after layout changes.

Add localized wrap labels for all existing Markdown message bundles and style the new control with the existing code block header patterns.

Validation: npm run typecheck --workspace @codenomad/ui
@shantur

shantur commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot

codenomadbot Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Gatekeeper review for latest PR state: I would not merge as-is.

Findings:

  1. High: the PR regresses the existing tool-output wrap preference. packages/ui/src/lib/markdown.ts:541, :546, :555, :565, and :571 unconditionally render every markdown code block with data-wrap-lines="true", and packages/ui/src/styles/markdown.css:327 applies wrapping from that attribute. Tool markdown still computes options.wrap in packages/ui/src/components/tool-call/markdown-render.tsx:33, but when options.wrap is false the absence of tool-call-markdown-wrap no longer matters because the inner markdown block already forces wrapping. Users who disable output wrapping will still get wrapped Markdown code blocks after this PR. The default wrap behavior needs to be plumbed as an explicit Markdown prop/render option, or the existing tool-call wrap setting needs to initialize data-wrap-lines to false for those contexts.

  2. Medium: per-block wrap state is keyed only by rendered position, not the block identity. packages/ui/src/components/markdown.tsx:114-120 stores overrides as ${cacheId}:${index}. If a streaming/async markdown update inserts, removes, or reorders code fences before a toggled block, the override will move to a different code block at the same index. That is a fragile implementation for the stated goal of preserving per-block choices across re-renders. Prefer emitting a stable block key from the renderer, such as a hash of language + decoded code + occurrence disambiguator, and keying overrides from that.

  3. Low: the new wrap button inherits rounded corners in packages/ui/src/styles/markdown.css:264-278 via border-radius: 0.25rem. This conflicts with the repo styling guideline to keep UI corners square. The existing copy button already had this, but this PR expands the pattern to a new control instead of avoiding it.

Implementation quality notes:

  • renderer.code now repeats the same full <div class="markdown-code-block" ...> wrapper in every branch. A small local helper in that function would reduce the chance that future attributes/actions are changed in one rendering branch but missed in another.
  • The i18n additions are present across locales and the labels/aria-pressed model are directionally correct.

Validation attempted:

  • npm run typecheck --workspace @codenomad/ui could not be independently verified in this workspace because dependencies are missing/unresolved locally (solid-js, @opencode-ai/sdk, Node typings, etc.).
  • node --test packages/ui/src/lib/markdown.test.ts also could not run directly in this TS/ESM workspace because the extensionless ./markdown import cannot be resolved by raw Node without the project’s test/build loader.

Recommendation: fix the output-wrap regression before merge. The positional key issue should also be addressed if the intended guarantee is truly per-block persistence through content updates, not only through the Shiki language-load re-render of identical content.

--
Yours,
CodeNomadBot

Plumb the default code block wrap state through the Markdown component and renderer so tool output Markdown can render code fences unwrapped when the existing output wrap toggle is disabled.

Use stable renderer-emitted code block keys based on language, decoded code, and occurrence count so per-block wrap overrides survive async re-renders without drifting across unrelated inserted or removed code fences.

Centralize Markdown code block wrapper creation and keep the wrap/copy controls square to match the UI styling guidelines.

Validation: npm run typecheck --workspace @codenomad/ui
@shantur

shantur commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeNomadBot

And now?

@github-actions

Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/29403464440

Artifacts expire in 7 days.
Artifacts:

  • pr-599-1d0fdcd5578dcb8432354281fa7a06e6731ee900-electron-macos
  • pr-599-1d0fdcd5578dcb8432354281fa7a06e6731ee900-tauri-macos-arm64
  • pr-599-1d0fdcd5578dcb8432354281fa7a06e6731ee900-tauri-linux
  • pr-599-1d0fdcd5578dcb8432354281fa7a06e6731ee900-electron-linux
  • pr-599-1d0fdcd5578dcb8432354281fa7a06e6731ee900-electron-windows

@codenomadbot

codenomadbot Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Re-review after 9929f829 fix(ui): respect tool markdown wrap preference: the prior blocking issues look addressed in the latest PR state.

Findings:

  • No blocking findings in the static review of the updated diff.

What changed relative to the previous gatekeeper review:

  • The tool-output wrap regression is fixed. packages/ui/src/components/tool-call/markdown-render.tsx now computes wrapEnabled = options.wrap ?? true, keeps the existing tool-call-markdown-wrap class behavior, and passes defaultCodeBlockWrap={wrapEnabled} into Markdown. packages/ui/src/components/markdown.tsx includes defaultCodeBlockWrap in the render/cache key and passes it through to renderMarkdown, so cached HTML no longer crosses wrap/no-wrap modes.
  • The renderer now emits data-wrap-lines="false" when the default is disabled, instead of forcing every markdown code block to wrapped lines. That restores the global/tool output no-wrap behavior while keeping normal Markdown code blocks wrapped by default.
  • The positional block key issue is materially improved. packages/ui/src/lib/markdown.ts now derives data-code-block-key from language + decoded code + occurrence, and the Solid wrapper keys overrides from that instead of raw render index. This is much less fragile across Shiki re-renders and code-fence insertions before unrelated blocks.
  • The repeated wrapper branches in renderer.code were collapsed into a local renderCodeBlock helper, which is a better implementation for future maintenance.
  • The newly-expanded code block action styling now uses square corners (border-radius: 0), which resolves the styling-guideline concern I called out.

Residual risks / non-blocking notes:

  • Duplicate identical code fences still rely on occurrence order in the stable key. If identical blocks are inserted before identical blocks during live streaming, a previous override can still follow the duplicate occurrence position. That is a reasonable tradeoff for this implementation and not a merge blocker unless the product requires identity preservation through arbitrary duplicate-block edits.
  • packages/ui/src/lib/markdown.ts is now about 677 lines, above the repo’s ~500-line refactor-warning threshold. Not a blocker, but future Markdown work should consider splitting renderer setup/helpers from highlighter/language loading.

Validation attempted:

  • git diff --check origin/dev...HEAD passed.
  • npm run typecheck --workspace @codenomad/ui still cannot be independently verified in this local workspace because dependencies/types are unresolved here (solid-js, @opencode-ai/sdk, Node typings, etc.). This appears to be the same environment problem as the prior run, not a new PR-specific compiler signal.
  • node --test packages/ui/src/lib/markdown.test.ts still cannot run directly under raw Node in this TS/ESM setup because the extensionless ./markdown import is not resolved without the project’s test/build loader.

Recommendation: acceptable to merge from the code-review perspective once CI’s real typecheck/build is green.

--
Yours,
CodeNomadBot

@shantur
shantur merged commit 40cee07 into dev Jul 15, 2026
10 of 11 checks passed
@shantur
shantur deleted the wrap-line-codeblocks branch July 15, 2026 09:34
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.

1 participant