Skip to content

fix: wrap code viewer lines by computing width#2070

Draft
nosthrillz wants to merge 7 commits intonpmx-dev:mainfrom
nosthrillz:fix/2027-code-wrapping
Draft

fix: wrap code viewer lines by computing width#2070
nosthrillz wants to merge 7 commits intonpmx-dev:mainfrom
nosthrillz:fix/2027-code-wrapping

Conversation

@nosthrillz
Copy link

@nosthrillz nosthrillz commented Mar 13, 2026

🔗 Linked issue

Attempts to resolve 2027

🧭 Context

The current approach has overflow-x-auto, but wrapping might be better.
It's unclear whether that's preferred, so making the PR to allow a preview.

📚 Description

We actually can compute available screen width, so we can avoid overflow, and instead set the width and use flex-wrap combined with pre-wrap to get the desired effect.

Potential points of contention / concerns

I didn't visually test the highlighting, I'm yet unsure what the implications would be :)

@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 15, 2026 5:24am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 15, 2026 5:24am
npmx-lunaria Ignored Ignored Mar 15, 2026 5:24am

Request Review

@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 93.10345% with 2 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/components/Code/Viewer.vue 92.30% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Reworks the Code Viewer to support wrapped lines by tracking per-line heights with a reactive lineMultipliers array, computing displayLines (real lines plus null placeholders), and updating multipliers via a watcher on html and a ResizeObserver. Template and CSS were adjusted: code-content and line elements now support wrapping (pre-wrap, flex wrap), highlighted lines span full width, gutter width is computed via a --line-digits CSS variable, and anchor navigation logic was realigned to the updated DOM. Separately, the package-code page adds layout scaffolding: a .main-content wrapper, fixed-width .file-tree sidebar and a responsive .file-viewer.

Possibly related PRs

Suggested labels

front

Suggested reviewers

  • ghostdevv
  • danielroe
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the motivation to replace overflow-x-auto with computed-width wrapping using flex-wrap and pre-wrap.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: db7d3812-0584-449a-ac8f-5830e35c5103

📥 Commits

Reviewing files that changed from the base of the PR and between 9e8c805 and da6f15a.

📒 Files selected for processing (2)
  • app/components/Code/Viewer.vue
  • app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue (1)

588-603: @screen usage now correct; minor responsiveness consideration remains.

The @screen lg directive is correctly placed at top-level (not nested), addressing the previous review feedback. The switch from 100vw to 100% is also appropriate.

However, .file-viewer width applies at all breakpoints, whilst .file-tree is only visible on md+ screens. On mobile, this subtracts sidebar space for a hidden element. The flex-1 class should compensate via flex-grow, but consider scoping the width rule to md+ for clarity:

Option: Scope width rules to md+ breakpoint
 .file-tree {
   width: var(--sidebar-space);
 }
+@screen md {
+  .file-viewer {
+    width: calc(100% - var(--sidebar-space));
+  }
+}
-.file-viewer {
-  width: calc(100% - var(--sidebar-space));
-}

Alternatively, since .file-viewer already has flex-1 min-w-0 in the template, the explicit width may be unnecessary if flexbox sizing is sufficient.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d420e8f0-1328-42ad-968d-7926a66f68b3

📥 Commits

Reviewing files that changed from the base of the PR and between da6f15a and a6e9ca5.

📒 Files selected for processing (2)
  • app/components/Code/Viewer.vue
  • app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue

@gameroman
Copy link
Contributor

@nosthrillz nosthrillz marked this pull request as draft March 14, 2026 17:26
@nosthrillz
Copy link
Author

Switching to Draft because the line numbers need to be recomputed. As attempted in https://github.com/npmx-dev/npmx.dev/pull/2028/changes as well

@WilcoSp
Copy link
Contributor

WilcoSp commented Mar 14, 2026

the wrapping is very readable and atm I also only see the issue of the line numbers & the highlighting included with it.

a file that may be good to check would be in nuxt the /dist/index.mjs file because it has large imports

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/components/Code/Viewer.vue (1)

30-30: Consider debouncing the resize observer callback.

updateLineMultipliers queries getComputedStyle for every line element. For large files (as mentioned in PR comments regarding Nuxt's /dist/index.mjs), this could cause performance issues during continuous resize events.

♻️ Suggested debounce implementation
+import { useDebounceFn } from '@vueuse/core'
+
+const debouncedUpdateLineMultipliers = useDebounceFn(updateLineMultipliers, 100)
+
-useResizeObserver(codeRef, updateLineMultipliers)
+useResizeObserver(codeRef, debouncedUpdateLineMultipliers)

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 59176b83-d3f1-482d-a169-c55c5dd5d568

📥 Commits

Reviewing files that changed from the base of the PR and between a6e9ca5 and f62fefe.

📒 Files selected for processing (1)
  • app/components/Code/Viewer.vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e43e32c-4ae1-4c98-9eb3-4e11d0ded3ee

📥 Commits

Reviewing files that changed from the base of the PR and between f62fefe and 81c79a9.

📒 Files selected for processing (2)
  • app/utils/chart-data-buckets.ts
  • test/unit/app/utils/chart-data-buckets.spec.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/components/Code/Viewer.vue (1)

26-30: Consider consolidating duplicate props.html watchers.

Both watchers react to the same source and schedule nextTick work. Merging them into a single watcher would simplify ordering and reduce reactive overhead.

♻️ Proposed refactor
-watch(
-  () => props.html,
-  () => nextTick(updateLineMultipliers),
-  { immediate: true },
-)
 useResizeObserver(codeRef, updateLineMultipliers)
@@
-watch(
-  () => props.html,
-  () => {
-    nextTick(handleImportLinkNavigate)
-  },
-  { immediate: true },
-)
+watch(
+  () => props.html,
+  () => {
+    nextTick(() => {
+      updateLineMultipliers()
+      handleImportLinkNavigate()
+    })
+  },
+  { immediate: true },
+)

Also applies to: 101-107


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cad5e82e-58c5-4e74-b233-5ed5eeac11fd

📥 Commits

Reviewing files that changed from the base of the PR and between 81c79a9 and a201a36.

📒 Files selected for processing (1)
  • app/components/Code/Viewer.vue

Comment on lines +18 to +23
function updateLineMultipliers() {
if (!codeRef.value) return
const lines = Array.from(codeRef.value.querySelectorAll('code > .line'))
lineMultipliers.value = lines.map(line =>
Math.max(1, Math.round(parseFloat(getComputedStyle(line).height) / LINE_HEIGHT_PX)),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Throttle resize-driven line measurement to avoid jank on large files.

Line 31 currently triggers a full recomputation, and Lines 20-23 read computed style for every rendered code line each time. On large files this can become expensive during resize and cause visible lag.

⚡ Proposed fix
+const scheduleLineMultiplierUpdate = useDebounceFn(() => {
+  updateLineMultipliers()
+}, 16)
+
 watch(
   () => props.html,
-  () => nextTick(updateLineMultipliers),
+  () => nextTick(scheduleLineMultiplierUpdate),
   { immediate: true },
 )
-useResizeObserver(codeRef, updateLineMultipliers)
+useResizeObserver(codeRef, scheduleLineMultiplierUpdate)

Also applies to: 31-31

Comment on lines +179 to 186
.code-content:deep(.line) {
display: flex;
flex-wrap: wrap;
/* Ensure consistent height matching line numbers */
line-height: 24px;
min-height: 24px;
max-height: 24px;
white-space: pre;
line-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
min-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
white-space: pre-wrap;
overflow: hidden;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Long unbroken tokens can still overflow in wrap mode.

Line 185 uses white-space: pre-wrap, which does not hard-break long strings without natural breakpoints (e.g. minified blobs, hashes, base64). This can reintroduce horizontal overflow despite the wrapping objective.

🩹 Proposed fix
 .code-content:deep(.line) {
   display: flex;
   flex-wrap: wrap;
   /* Ensure consistent height matching line numbers */
   line-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
   min-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
   white-space: pre-wrap;
+  overflow-wrap: anywhere;
+  word-break: break-word;
   overflow: hidden;
   transition: background-color 0.1s;
   max-width: 100%;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.code-content:deep(.line) {
display: flex;
flex-wrap: wrap;
/* Ensure consistent height matching line numbers */
line-height: 24px;
min-height: 24px;
max-height: 24px;
white-space: pre;
line-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
min-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
white-space: pre-wrap;
overflow: hidden;
.code-content:deep(.line) {
display: flex;
flex-wrap: wrap;
/* Ensure consistent height matching line numbers */
line-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
min-height: calc(v-bind(LINE_HEIGHT_PX) * 1px);
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
overflow: hidden;

@nosthrillz nosthrillz marked this pull request as draft March 15, 2026 07:02
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.

add word wrap to code viewer

4 participants